[
  {
    "path": ".codecov.yml",
    "content": "github_checks:\n  annotations: false\n"
  },
  {
    "path": ".githooks/README.md",
    "content": "# Git Hooks\n\nThis directory contains useful Git hooks for working with go-libp2p.\n\nInstall them by running\n```bash\ngit config core.hooksPath .githooks\n```\n"
  },
  {
    "path": ".githooks/pre-commit",
    "content": "#!/bin/bash\n\npushd ./test-plans > /dev/null\ngo mod tidy\nif [[ -n $(git diff --name-only -- \"go.mod\" \"go.sum\") ]]; then\n  echo \"go.mod / go.sum in test-plans not tidied\"\n  errored=true\nfi\npopd > /dev/null\n\nif [ \"$errored\" = true ]; then\n\texit 1\nfi\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report.md",
    "content": "---\nname: 'Bug Report'\nabout: 'Report a bug in go-libp2p.'\nlabels: bug\n---\n\n<!-- This is where you get to tell us what went wrong. When doing so, please make sure to include *all* relevant information.\n\nPlease try to include:\n  * What you were doing when you experienced the bug.\n  * Any error messages you saw, *where* you saw them, and what you believe may have caused them (if you have any ideas).\n  * When possible, steps to reliably produce the bug.\n-->\n\n<details>\n<summary>Version Information</summary>\n<pre>\n<!-- Insert the output of `go list -m all` HERE -->\n</pre>\n</details>\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: true\ncontact_links:\n  - name: Technical Questions\n    url: https://github.com/libp2p/go-libp2p/discussions/new?category=q-a\n    about: Please ask technical questions in the go-libp2p Github Discusions forum.\n  - name: Community-wide libp2p Discussion\n    url: https://discuss.libp2p.io\n    about: Discussions and questions about the libp2p community.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/doc.md",
    "content": "---\nname: 'Documentation Issue'\nabout: 'Report missing/erroneous documentation, propose new documentation, report broken links, etc.'\nlabels: documentation\n---\n\n#### Location\n\n<!-- In the case of missing/erroneous documentation, where is the error? If possible, a link/url would be great! -->\n\n#### Description\n\n<!-- Describe the documentation issue. -->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/enhancement.md",
    "content": "---\nname: 'Enhancement'\nabout: 'Suggest an improvement to an existing go-libp2p feature.'\nlabels: enhancement\n---\n\n<!--\nNote: If you'd like to suggest an idea related to libp2p but not specifically related to the Go implementation, please file an issue at https://github.com/libp2p/specs instead. Even better, create a new topic on the forums (https://discuss.libp2p.io).\n\nWhen requesting an _enhancement_, please be sure to include your motivation and try to be as specific as possible.\n-->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature.md",
    "content": "---\nname: 'Feature'\nabout: 'Suggest a new feature in go-libp2p.'\nlabels: feature\n---\n\n<!--\nNote: If you'd like to suggest an idea related to libp2p but not specifically related to the Go implementation, please file an issue at https://github.com/libp2p/specs instead. Even better, create a new topic on the forums (https://discuss.libp2p.io).\n\nWhen requesting a _feature_, please be sure to include:\n  * Your motivation. Why do you need the feature?\n  * How the feature should work.\n\nPlease try to be as specific and concrete as possible.\n-->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/question.md",
    "content": "---\nname: 'Question/Support'\nabout: 'Ask a question about go-libp2p or request support.'\nlabels: question, invalid\n---\n\nThis bug tracker is only for actionable bug reports and feature requests. Please direct any questions to https://discuss.libp2p.io or to our Matrix (#libp2p:matrix.org) or IRC (#libp2p on freenode) channels.\n\nIf you don't get an immediate response, please keep trying.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/release.md",
    "content": "---\nname: 'Libp2p Release'\nabout: 'Start a new libp2p release.'\n---\n\n## 🗺 What's left for release\n\n<List of items with remaining PRs and/or Issues to be considered for this release>\n\n## 🔦 Highlights\n\n< top highlights for this release notes >\n\n## Changelog\n\n< changelog generated by scripts/mkreleaselog >\n\n## ✅ Release Checklist\n\n- [ ] **Stage 0 - Finishing Touches**\n    - [ ] Go through relevant libp2p repos looking for unreleased changes that should make it into the release. If you find any, cut releases.\n    - [ ] Run `go get -u ./...` to see if there are any out-of-date deps that look important. If there are, bubble them. Try to avoid _directly_ updating indirect deps in go-libp2p's `go.mod` when possible.\n- [ ] **Stage 1 - Release**\n  - [ ] Publish the release through the GitHub UI, adding the release notes. Some users rely on this to receive notifications of new releases.\n  - [ ] Announce the release on the [discuss.libp2p.io](https://discuss.libp2p.io).\n- [ ] **Stage 2 - Update Upstream**\n  - [ ] Update the examples to the final release\n  - [ ] Update the upstream dependencies to the final release and create PRs.\n    - [ ] [filecoin-project/lotus](https://github.com/filecoin-project/lotus)\n    - [ ] [go-libp2p-kad-dht](https://github.com/libp2p/go-libp2p-kad-dht/)\n    - [ ] [go-libp2p-pubsub](https://github.com/libp2p/go-libp2p-pubsub) (In case of breaking changes.)\n    - [ ] [ipfs/kubo](https://github.com/ipfs/kubo)\n  - [ ] Add new release to interop tester in [test-plans](https://github.com/libp2p/test-plans/)\n- [ ] Make required changes to the release process.\n"
  },
  {
    "path": ".github/actions/go-check-setup/action.yml",
    "content": "runs:\n  using: \"composite\"\n  steps:\n    - name: Install Protoc\n      uses: trail-of-forks/setup-protoc@a97892a429d98fae78d26f40334ab7eb616d08b9 # include https://github.com/arduino/setup-protoc/pull/58\n      with:\n        version: '21.12'\n        repo-token: ${{ github.token }}\n    - name: Install Protobuf compiler\n      shell: bash\n      run: go install google.golang.org/protobuf/cmd/protoc-gen-go\n"
  },
  {
    "path": ".github/actions/go-test-setup/action.yml",
    "content": "runs:\n  using: \"composite\"\n  steps:\n    - name: increase the UDP receive buffer size # see https://github.com/lucas-clemente/quic-go/wiki/UDP-Receive-Buffer-Size\n      shell: bash\n      run: sysctl -w net.core.rmem_max=2500000\n      if: ${{ matrix.os == 'ubuntu' }}\n    - name: Run nocover tests. These are tests that require the coverage analysis to be off # See https://github.com/protocol/.github/issues/460\n      shell: bash\n      # This matches only tests with \"NoCover\" in their test name to avoid running all tests again.\n      run: go test -tags nocover -run NoCover -v ./...\n    - name: Run synctests tests. These are tests that require go 1.25 and the experimental testing/synctest package\n      shell: bash\n      if: ${{ contains(matrix.go, '1.25') }}\n      run: go test -run \"_synctest$\" -v ./...\n    - name: Install testing tools\n      shell: bash\n      run: cd scripts/test_analysis && go install ./cmd/gotest2sql\n    - name: Install test_analysis\n      shell: bash\n      run: cd scripts/test_analysis && go install .\n"
  },
  {
    "path": ".github/workflows/generated-pr.yml",
    "content": "name: Close Generated PRs\n\non:\n  schedule:\n    - cron: '0 0 * * *'\n  workflow_dispatch:\n\npermissions:\n  issues: write\n  pull-requests: write\n\njobs:\n  stale:\n    uses: ipdxco/unified-github-workflows/.github/workflows/reusable-generated-pr.yml@v1\n"
  },
  {
    "path": ".github/workflows/go-check-config.json",
    "content": "{\n  \"gogenerate\": true\n}\n"
  },
  {
    "path": ".github/workflows/go-check.yml",
    "content": "name: Go Checks\n\non:\n  pull_request:\n  push:\n    branches: [\"master\", \"release-v0[0-9][0-9]\"]\n  workflow_dispatch:\n\npermissions:\n  contents: read\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event_name == 'push' && github.sha || github.ref }}\n  cancel-in-progress: true\n\njobs:\n  go-check:\n    uses: ipdxco/unified-github-workflows/.github/workflows/go-check.yml@v1.0\n    with:\n      go-generate-ignore-protoc-version-comments: true\n"
  },
  {
    "path": ".github/workflows/go-test-config.json",
    "content": "{\n    \"skip32bit\": true\n}\n"
  },
  {
    "path": ".github/workflows/go-test-template.yml",
    "content": "name: Go Test\non:\n  workflow_call:\n    inputs:\n      go-versions:\n        required: false\n        type: string\n        default: '[\"this\", \"next\"]'\n    secrets:\n      CODECOV_TOKEN:\n        required: false\n\ndefaults:\n  run:\n    shell: bash\n\njobs:\n  unit:\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [\"ubuntu\", \"macos\", \"windows\"]\n        go: ${{ fromJSON(inputs.go-versions) }}\n    env:\n      GOTESTFLAGS: -cover -coverprofile=module-coverage.txt -coverpkg=./...\n      GO386FLAGS: \"\"\n      GORACEFLAGS: \"\"\n    runs-on: ${{ fromJSON(format('\"{0}-latest\"', matrix.os)) }}\n    name: ${{ matrix.os }} (go ${{ matrix.go }})\n    steps:\n      - name: Use msys2 on windows\n        if: matrix.os == 'windows'\n        # The executable for msys2 is also called bash.cmd\n        #   https://github.com/actions/virtual-environments/blob/main/images/win/Windows2019-Readme.md#shells\n        # If we prepend its location to the PATH\n        #   subsequent 'shell: bash' steps will use msys2 instead of gitbash\n        run: echo \"C:/msys64/usr/bin\" >> $GITHUB_PATH\n      - name: Check out the repository\n        uses: actions/checkout@v4\n        with:\n          submodules: recursive\n      - name: Check out the latest stable version of Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: stable\n          cache: ${{ matrix.os != 'windows' }} # Windows VMs are slow to use caching. Can add ~15m to the job\n      - name: Read the Unified GitHub Workflows configuration\n        id: config\n        uses: ipdxco/unified-github-workflows/.github/actions/read-config@main\n      - name: Read the go.mod file\n        id: go-mod\n        uses: ipdxco/unified-github-workflows/.github/actions/read-go-mod@main\n      - name: Determine the Go version to use based on the go.mod file\n        id: go\n        env:\n          MATRIX_GO: ${{ matrix.go }}\n          GO_MOD_VERSION: ${{ fromJSON(steps.go-mod.outputs.json).Go }}\n        run: |\n          if [[ \"$MATRIX_GO\" == \"this\" ]]; then\n            echo \"version=$GO_MOD_VERSION.x\" >> $GITHUB_OUTPUT\n          elif [[ \"$MATRIX_GO\" == \"next\" ]]; then\n            MAJOR=\"${GO_MOD_VERSION%.[0-9]*}\"\n            MINOR=\"${GO_MOD_VERSION#[0-9]*.}\"\n            echo \"version=$MAJOR.$(($MINOR+1)).x\" >> $GITHUB_OUTPUT\n          elif [[ \"$MATRIX_GO\" == \"prev\" ]]; then\n            MAJOR=\"${GO_MOD_VERSION%.[0-9]*}\"\n            MINOR=\"${GO_MOD_VERSION#[0-9]*.}\"\n            echo \"version=$MAJOR.$(($MINOR-1)).x\" >> $GITHUB_OUTPUT\n          else\n            echo \"version=$MATRIX_GO\" >> $GITHUB_OUTPUT\n          fi\n      - name: Enable shuffle flag for go test command\n        if: toJSON(fromJSON(steps.config.outputs.json).shuffle) != 'false'\n        run: |\n          echo \"GOTESTFLAGS=-shuffle=on $GOTESTFLAGS\" >> $GITHUB_ENV\n          echo \"GO386FLAGS=-shuffle=on $GO386FLAGS\" >> $GITHUB_ENV\n          echo \"GORACEFLAGS=-shuffle=on $GORACEFLAGS\" >> $GITHUB_ENV\n      - name: Enable verbose flag for go test command\n        if: toJSON(fromJSON(steps.config.outputs.json).verbose) != 'false'\n        run: |\n          echo \"GOTESTFLAGS=-v $GOTESTFLAGS\" >> $GITHUB_ENV\n          echo \"GO386FLAGS=-v $GO386FLAGS\" >> $GITHUB_ENV\n          echo \"GORACEFLAGS=-v $GORACEFLAGS\" >> $GITHUB_ENV\n      - name: Set extra flags for go test command\n        if: fromJSON(steps.config.outputs.json).gotestflags != ''\n        run: |\n          echo \"GOTESTFLAGS=${{ fromJSON(steps.config.outputs.json).gotestflags }} $GOTESTFLAGS\" >> $GITHUB_ENV\n      - name: Set extra flags for go test race command\n        if: fromJSON(steps.config.outputs.json).goraceflags != ''\n        run: |\n          echo \"GORACEFLAGS=${{ fromJSON(steps.config.outputs.json).goraceflags }} $GORACEFLAGS\" >> $GITHUB_ENV\n      - name: Set up the Go version read from the go.mod file\n        uses: actions/setup-go@v5\n        with:\n          go-version: ${{ steps.go.outputs.version }}\n          cache: ${{ matrix.os != 'windows' }} # Windows VMs are slow to use caching. Can add ~15m to the job\n      - name: Display the Go version and environment\n        run: |\n          go version\n          go env\n      - name: Run repo-specific setup\n        uses: ./.github/actions/go-test-setup\n        if: hashFiles('./.github/actions/go-test-setup') != ''\n      - name: Run tests\n        id: test\n        if: contains(fromJSON(steps.config.outputs.json).skipOSes, matrix.os) == false\n        uses: protocol/multiple-go-modules@v1.4\n        with:\n          run: test_analysis ${{ env.GOTESTFLAGS }}\n      - name: Upload test results\n        if: always()\n        uses: actions/upload-artifact@v4\n        with:\n          name: ${{ matrix.os }}_${{ matrix.go }}_test_results.db\n          path: ./test_results.db\n      - name: Add failure summary\n        if: always()\n        run: |\n          echo \"### Failure Summary\" >> $GITHUB_STEP_SUMMARY\n          test_analysis summarize >> $GITHUB_STEP_SUMMARY\n      - name: Remove test results\n        run: rm ./test_results.db\n      - name: Run tests with race detector\n        # speed things up. Windows and OSX VMs are slow\n        if: matrix.os == 'ubuntu' &&\n          fromJSON(steps.config.outputs.json).skipRace != true &&\n          contains(fromJSON(steps.config.outputs.json).skipOSes, matrix.os) == false\n        uses: protocol/multiple-go-modules@v1.4\n        id: race\n        with:\n          run: test_analysis -race ${{ env.GORACEFLAGS }} ./...\n      - name: Upload test results (Race)\n        if: (steps.race.conclusion == 'success' || steps.race.conclusion == 'failure')\n        uses: actions/upload-artifact@v4\n        with:\n          name: ${{ matrix.os }}_${{ matrix.go }}_test_results_race.db\n          path: ./test_results.db\n      - name: Add failure summary\n        if: (steps.race.conclusion == 'success' || steps.race.conclusion == 'failure')\n        run: |\n          echo \"# Tests with race detector failure summary\" >> $GITHUB_STEP_SUMMARY\n          test_analysis summarize >> $GITHUB_STEP_SUMMARY\n      - name: Adding Link to Run Analysis\n        run: echo \"### [Test flakiness analysis](https://observablehq.com/d/d74435ea5bbf24c7?run-id=$GITHUB_RUN_ID)\" >> $GITHUB_STEP_SUMMARY\n      - name: Collect coverage files\n        id: coverages\n        run: echo \"files=$(find . -type f -name 'module-coverage.txt' | tr -s '\\n' ',' | sed 's/,$//')\" >> $GITHUB_OUTPUT\n      - name: Upload coverage to Codecov\n        uses: codecov/codecov-action@54bcd8715eee62d40e33596ef5e8f0f48dbbccab # v4.1.0\n        with:\n          files: ${{ steps.coverages.outputs.files }}\n          env_vars: OS=${{ matrix.os }}, GO=${{ steps.go.outputs.version }}\n          token: ${{ secrets.CODECOV_TOKEN }}\n          fail_ci_if_error: false\n"
  },
  {
    "path": ".github/workflows/go-test.yml",
    "content": "name: Go Test\n\non:\n  pull_request:\n  push:\n    branches: [\"master\", \"release-v0[0-9][0-9]\"]\n  workflow_dispatch:\n\npermissions:\n  contents: read\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event_name == 'push' && github.sha || github.ref }}\n  cancel-in-progress: true\n\njobs:\n  go-test:\n    uses: ./.github/workflows/go-test-template.yml\n    with:\n      go-versions: '[\"1.25.x\", \"1.26.x\"]'\n    secrets:\n      CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/interop-test.yml",
    "content": "name: Interoperability Testing\n\non:\n  workflow_dispatch:\n  pull_request:\n    paths:\n      - \"config/**\"\n      - \"core/**\"\n      - \"internal/**\"\n      - \"p2p/**\"\n      - \"test-plans/**\"\n      - \".github/workflows/interop-test.yml\"\n  push:\n    branches:\n      - \"master\"\n    paths:\n      - \"config/**\"\n      - \"core/**\"\n      - \"internal/**\"\n      - \"p2p/**\"\n      - \"test-plans/**\"\n\njobs:\n  run-transport-interop:\n    name: Run transport interoperability tests\n    runs-on: ubuntu-latest\n    steps:\n      - name: Free Disk Space (Ubuntu)\n        if: vars['INTEROP_TEST_RUNNER_UBUNTU'] == ''\n        uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1\n        with:\n          tool-cache: true\n      - uses: actions/checkout@v4\n      - name: Build image\n        run: docker build -t go-libp2p-head -f test-plans/PingDockerfile .\n      - uses: libp2p/test-plans/.github/actions/run-transport-interop-test@master\n        with:\n          test-filter: go-libp2p-head\n          # Known incompatiblity between the draft versions of WebTransport\n          test-ignore: \"go-libp2p-head x go-v0.46 (webtransport)|go-libp2p-head x go-v0.45 (webtransport)\"\n          extra-versions: ${{ github.workspace }}/test-plans/ping-version.json\n          s3-cache-bucket: ${{ vars.S3_LIBP2P_BUILD_CACHE_BUCKET_NAME }}\n          s3-access-key-id: ${{ vars.S3_LIBP2P_BUILD_CACHE_AWS_ACCESS_KEY_ID }}\n          s3-secret-access-key: ${{ secrets.S3_LIBP2P_BUILD_CACHE_AWS_SECRET_ACCESS_KEY }}\n"
  },
  {
    "path": ".github/workflows/link-check.yml",
    "content": "name: Markdown Link Checking\non:\n  pull_request:\n  push:\n    branches:\n      - \"master\"\n\njobs:\n  check-links:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: tcort/github-action-markdown-link-check@v1\n        with:\n          use-quiet-mode: 'yes'   # show only broken links\n          use-verbose-mode: 'yes'\n          config-file: .github/workflows/markdown-links-config.json # for removing any false positives\n"
  },
  {
    "path": ".github/workflows/markdown-links-config.json",
    "content": "{\n  \"ignorePatterns\": [\n    {\n        \"pattern\": \"^http://localhost\"\n    },\n    {\n        \"pattern\": \"^https://twitter.com/\"\n    },\n    {\n        \"pattern\": \"^https://opensource.org/\"\n    }\n  ],\n  \"aliveStatusCodes\": [200],\n  \"httpHeaders\": [\n    {\n      \"urls\": [\"https://docs.github.com/\"],\n      \"headers\": {\n        \"Accept-Encoding\": \"*\"\n      }\n    }\n  ]\n}"
  },
  {
    "path": ".github/workflows/release-check.yml",
    "content": "name: Release Checker\n\non:\n  pull_request_target:\n    paths: [ 'version.json' ]\n    types: [ opened, synchronize, reopened, labeled, unlabeled ]\n  workflow_dispatch:\n\npermissions:\n  contents: write\n  pull-requests: write\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  release-check:\n    uses: ipdxco/unified-github-workflows/.github/workflows/release-check.yml@v1.0\n"
  },
  {
    "path": ".github/workflows/releaser.yml",
    "content": "name: Releaser\n\non:\n  push:\n    paths: [ 'version.json' ]\n  workflow_dispatch:\n\npermissions:\n  contents: write\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.sha }}\n  cancel-in-progress: true\n\njobs:\n  releaser:\n    uses: ipdxco/unified-github-workflows/.github/workflows/releaser.yml@v1.0\n"
  },
  {
    "path": ".github/workflows/stale.yml",
    "content": "name: Close Stale Issues\n\non:\n  schedule:\n    - cron: '0 0 * * *'\n  workflow_dispatch:\n\npermissions:\n  issues: write\n  pull-requests: write\n\njobs:\n  stale:\n    uses: ipdxco/unified-github-workflows/.github/workflows/reusable-stale-issue.yml@v1\n"
  },
  {
    "path": ".github/workflows/tagpush.yml",
    "content": "name: Tag Push Checker\n\non:\n  push:\n    tags:\n      - v*\n\npermissions:\n  contents: read\n  issues: write\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  releaser:\n    uses: ipdxco/unified-github-workflows/.github/workflows/tagpush.yml@v1.0\n"
  },
  {
    "path": ".github/workflows/upstream.yml",
    "content": "on:\n  pull_request:\n    branches:\n      - master\n  push:\n    branches:\n      - master\nname: Upstream Test\n\njobs:\n  unit:\n    strategy:\n      fail-fast: false\n      matrix:\n        os:\n          - 'ubuntu'\n        go:\n          # - '1.15.x'\n          - '1.16.x'\n        upstream:\n          # - 'libp2p/go-libp2p-pubsub' flaky\n          # - 'ipfs/go-bitswap' flaky\n          # - 'libp2p/go-libp2p-kad-dht'\n          - 'libp2p/go-libp2p-daemon'\n    runs-on: ${{ matrix.os }}-latest\n    name: ${{ matrix.upstream }} unit tests (${{ matrix.os }}, Go ${{ matrix.go }})\n    steps:\n      - uses: actions/setup-go@v2\n        with:\n          go-version: ${{ matrix.go }}\n      - name: Go information\n        run: |\n          go version\n          go env\n      - uses: actions/checkout@v4\n        with:\n          path: 'libp2p'\n      - uses: actions/checkout@v4\n        with:\n          repository: ${{ matrix.upstream }}\n          path: upstream\n      - name: Patch in new go-libp2p\n        working-directory: upstream\n        run: |\n          go mod edit -replace \"github.com/libp2p/go-libp2p=../libp2p\"\n          go mod tidy\n      - name: Run tests\n        working-directory: upstream\n        run: go test -v ./...\n      - name: Run tests (32 bit)\n        working-directory: upstream\n        if: ${{ matrix.os != 'macos' }} # can't run 32 bit tests on OSX.\n        env:\n          GOARCH: 386\n        run: go test -v ./...\n      - name: Run tests with race detector\n        working-directory: upstream\n        if: ${{ matrix.os == 'ubuntu' }} # speed things up. Windows and OSX VMs are slow\n        run: go test -v -race ./...\n"
  },
  {
    "path": ".gitignore",
    "content": "*.swp\n.idea\n*.qlog\n*.sqlog\n*.qlog.zst\n*.sqlog.zst\ngo.work\ngo.work.sum\n"
  },
  {
    "path": ".golangci.yml",
    "content": "version: \"2\"\n\nrun:\n  timeout: 5m\n\nissues:\n  max-issues-per-linter: 0\n  max-same-issues: 0\n  exclude-rules:\n    - path: _test\\.go\n      linters:\n        - prealloc\n\nlinters:\n  enable:\n    - revive\n    - unused\n    - prealloc\n  disable:\n    - errcheck\n    - staticcheck\n\n  settings:\n    revive:\n      severity: warning\n      rules:\n        - name: unused-parameter\n          severity: warning\n\nseverity:\n  default: warning\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Table Of Contents <!-- omit in toc -->\n- [v0.28.0](#v0280)\n- [v0.27.0](#v0270)\n- [v0.26.4](#v0264)\n- [v0.26.3](#v0263)\n- [v0.26.2](#v0262)\n- [v0.26.1](#v0261)\n- [v0.26.0](#v0260)\n- [v0.25.1](#v0251)\n- [v0.25.0](#v0250)\n\n# [v0.28.0](https://github.com/libp2p/go-libp2p/releases/tag/v0.28.0)\n\n## 🔦 Highlights <!-- omit in toc -->\n\n### Smart Dialing <!-- omit in toc -->\n\nThis release introduces smart dialing logic. Currently, libp2p dials all addresses of a remote peer in parallel, and\naborts all outstanding dials as soon as the first one succeeds.\nDialing many addresses in parallel creates a lot of churn on the client side, and unnecessary load on the network and\non the server side, and is heavily discouraged by the networking community (see [RFC 8305](https://www.rfc-editor.org/rfc/rfc8305) for example).\n\nWhen connecting to a peer we first determine the order to dial its addresses. This ranking logic considers a number of corner cases\ndescribed in detail in the documentation of the swarm package (`swarm.DefaultDialRanker`).\nAt a high level, this is what happens:\n* If a peer offers a WebTransport and a QUIC address (on the same IP:port), the QUIC address is preferred.\n* If a peer has a QUIC and a TCP address, the QUIC address is dialed first. Only if the connection attempt doesn't succeed within 250ms, a TCP connection is started.\n\nOur measurements on the IPFS network show that for >90% of established libp2p connections, the first connection attempt succeeds,\nleading a dramatic decrease in the number of aborted connection attempts.\n\nWe also added new metrics to the swarm Grafana dashboard, showing:\n* The number of connection attempts it took to establish a connection\n* The delay introduced by the ranking logic\n\nThis feature should be safe to enable for nodes running in data centers and for most nodes in home networks.\nHowever, there are some (mostly home and corporate networks) that block all UDP traffic. If enabled, the current implementation\nof the smart dialing logic will lead to a regression, since it preferes QUIC addresses over TCP addresses. Nodes would still be\nable to connect, but connection establishment of the TCP connection would be delayed by 250ms.\n\nIn a future release (see #1605 for details), we will introduce a feature called blackhole detection. By observing the outcome of\nQUIC connection attempts, we can determine if UDP traffic is blocked (namely, if all QUIC connection attempts fail), and stop\ndialing QUIC in this case altogether. Once this detection logic is in place, smart dialing will be enabled by default.\n\n### More Metrics! <!-- omit in toc -->\nSince the last release, we've added metrics for:\n* [Holepunching](https://github.com/libp2p/go-libp2p/pull/2246)\n* Smart Dialing (see above)\n\n### WebTransport <!-- omit in toc -->\n* [#2251](https://github.com/libp2p/go-libp2p/pull/2251): Infer public WebTransport address from `quic-v1` addresses if both transports are using the same port for both quic-v1 and WebTransport addresses.\n* [#2271](https://github.com/libp2p/go-libp2p/pull/2271): Only add certificate hashes to WebTransport mulitaddress if listening on WebTransport\n\n## Housekeeping updates <!-- omit in toc -->\n* Identify\n  * [#2303](https://github.com/libp2p/go-libp2p/pull/2303): Don't send default protocol version\n  * Prevent polluting PeerStore with local addrs\n    * [#2325](https://github.com/libp2p/go-libp2p/pull/2325): Don't save signed peer records\n    * [#2300](https://github.com/libp2p/go-libp2p/pull/2300): Filter received addresses based on the node's remote address\n* WebSocket\n  * [#2280](https://github.com/libp2p/go-libp2p/pull/2280): Reverted back to the Gorilla library for WebSocket\n* NAT\n  * [#2248](https://github.com/libp2p/go-libp2p/pull/2248): Move NAT mapping logic out of the host\n\n## 🐞 Bugfixes <!-- omit in toc -->\n* Identify\n  * [Reject signed peer records on peer ID mismatch](https://github.com/libp2p/go-libp2p/commit/8d771355b41297623e05b04a865d029a2522a074)\n  * [#2299](https://github.com/libp2p/go-libp2p/pull/2299): Avoid spuriously pushing updates\n* Swarm\n  * [#2322](https://github.com/libp2p/go-libp2p/pull/2322): Dedup addresses to dial\n  * [#2284](https://github.com/libp2p/go-libp2p/pull/2284): Change maps with multiaddress keys to use strings\n* QUIC\n  * [#2262](https://github.com/libp2p/go-libp2p/pull/2262): Prioritize listen connections for reuse\n  * [#2276](https://github.com/libp2p/go-libp2p/pull/2276): Don't panic when quic-go's accept call errors\n  * [#2263](https://github.com/libp2p/go-libp2p/pull/2263): Fix race condition when generating random holepunch packet\n\n**Full Changelog**: https://github.com/libp2p/go-libp2p/compare/v0.27.0...v0.28.0\n\n# [v0.27.0](https://github.com/libp2p/go-libp2p/releases/tag/v0.27.0)\n\n### Breaking Changes <!-- omit in toc -->\n\n* The `LocalPrivateKey` method was removed from the `network.Conn` interface. [#2144](https://github.com/libp2p/go-libp2p/pull/2144)\n\n## 🔦 Highlights <!-- omit in toc -->\n\n### Additional metrics <!-- omit in toc -->\nSince the last release, we've added metrics for:\n* [Relay Service](https://github.com/libp2p/go-libp2p/pull/2154): RequestStatus, RequestCounts, RejectionReasons for Reservation and Connection Requests,\nConnectionDuration, BytesTransferred, Relay Service Status.\n* [Autorelay](https://github.com/libp2p/go-libp2p/pull/2185): relay finder status, reservation request outcomes, current reservations, candidate circuit v2 support, current candidates, relay addresses updated, num relay address, and scheduled work times\n\n## 🐞 Bugfixes <!-- omit in toc -->\n\n* autonat: don't change status on dial request refused [2225](https://github.com/libp2p/go-libp2p/pull/2225)\n* relaysvc: fix flaky TestReachabilityChangeEvent [2215](https://github.com/libp2p/go-libp2p/pull/2215)\n* basichost: prevent duplicate dials [2196](https://github.com/libp2p/go-libp2p/pull/2196)\n* websocket: don't set a WSS multiaddr for accepted unencrypted conns [2199](https://github.com/libp2p/go-libp2p/pull/2199)\n* identify: Fix IdentifyWait when Connected events happen out of order [2173](https://github.com/libp2p/go-libp2p/pull/2173)\n* circuitv2: cleanup relay service properly [2164](https://github.com/libp2p/go-libp2p/pull/2164)\n\n**Full Changelog**: https://github.com/libp2p/go-libp2p/compare/v0.26.4...v0.27.0\n\n# [v0.26.4](https://github.com/libp2p/go-libp2p/releases/tag/v0.26.4)\n\nThis patch release fixes a busy-looping happening inside AutoRelay on private nodes, see [2208](https://github.com/libp2p/go-libp2p/pull/2208).\n\n**Full Changelog**: https://github.com/libp2p/go-libp2p/compare/v0.26.0...v0.26.4\n\n# [v0.26.3](https://github.com/libp2p/go-libp2p/releases/tag/v0.26.3)\n\n* rcmgr: fix JSON marshalling of ResourceManagerStat peer map [2156](https://github.com/libp2p/go-libp2p/pull/2156)\n* websocket: Don't limit message sizes in the websocket reader [2193](https://github.com/libp2p/go-libp2p/pull/2193)\n\n**Full Changelog**: https://github.com/libp2p/go-libp2p/compare/v0.26.0...v0.26.3\n\n# [v0.26.2](https://github.com/libp2p/go-libp2p/releases/tag/v0.26.2)\n\nThis patch release fixes two bugs:\n* A panic in WebTransport: https://github.com/quic-go/webtransport-go/releases/tag/v0.5.2\n* Incorrect accounting of accepted connections in the swarm metrics: [#2147](https://github.com/libp2p/go-libp2p/pull/2147)\n\n**Full Changelog**: https://github.com/libp2p/go-libp2p/compare/v0.26.0...v0.26.2\n\n# v0.26.1\n\nThis version was retracted due to errors when publishing the release.\n\n# [v0.26.0](https://github.com/libp2p/go-libp2p/releases/tag/v0.26.0)\n\n## 🔦 Highlights <!-- omit in toc -->\n\n### Circuit Relay Changes <!-- omit in toc -->\n\n#### [Removed Circuit Relay v1](https://github.com/libp2p/go-libp2p/pull/2107) <!-- omit in toc -->\n\nWe've decided to remove support for Circuit Relay v1 in this release. v1 Relays have been retired a few months ago. Notably, running the Relay v1 protocol was expensive and resulted in only a small number of nodes in the network. Users had to either manually configure these nodes as static relays, or discover them from the DHT.\nFurthermore, rust-libp2p [has dropped support](https://github.com/libp2p/rust-libp2p/pull/2549) and js-libp2p [is dropping support](https://github.com/libp2p/js-libp2p/pull/1533) for Relay v1.\n\nSupport for Relay v2 was first added in [late 2021 in v0.16.0](https://github.com/libp2p/go-libp2p/releases/tag/v0.16.0). With Circuit Relay v2 it became cheap to run (limited) relays. Public nodes also started the relay service by default. There's now a massive number of Relay v2 nodes on the IPFS network, and they don't advertise their service to the DHT any more. Because there's now so many of these nodes, connecting to just a small number of nodes (e.g. by joining the DHT), a node is statistically guaranteed to connect to some relays.\n\n#### [Unlimited Relay v2](https://github.com/libp2p/go-libp2p/pull/2125) <!-- omit in toc -->\n\nIn conjunction with removing relay v1, we also added an option to Circuit Relay v2 to disable limits.\nThis done by enabling `WithInfiniteLimits`. When enabled this allows for users to have a drop in replacement for Relay v1 with Relay v2.\n\n### Additional metrics <!-- omit in toc -->\n\nSince the last release, we've added additional metrics to different components.\nMetrics were added to:\n* [AutoNat](https://github.com/libp2p/go-libp2p/pull/2086): Current Reachability Status and Confidence, Client and Server DialResponses, Server DialRejections. The dashboard is [available here](https://github.com/libp2p/go-libp2p/blob/master/dashboards/autonat/autonat.json).\n* Swarm:\n  - [Early Muxer Selection](https://github.com/libp2p/go-libp2p/pull/2119): Added early_muxer label indicating whether a connection was established using early muxer selection.\n  - [IP Version](https://github.com/libp2p/go-libp2p/pull/2114): Added ip_version label to connection metrics\n* Identify:\n  - Metrics for Identify, IdentifyPush, PushesTriggered (https://github.com/libp2p/go-libp2p/pull/2069)\n  - Address Count, Protocol Count, Connection IDPush Support (https://github.com/libp2p/go-libp2p/pull/2126)\n\n\nWe also migrated the metric dashboards to a top-level [dashboards](https://github.com/libp2p/go-libp2p/tree/master/dashboards) directory.\n\n## 🐞 Bugfixes <!-- omit in toc -->\n\n### AutoNat <!-- omit in toc -->\n* [Fixed a bug](https://github.com/libp2p/go-libp2p/issues/2091) where AutoNat would emit events when the observed address has changed even though the node reachability hadn't changed.\n\n### Relay Manager <!-- omit in toc -->\n* [Fixed a bug](https://github.com/libp2p/go-libp2p/pull/2093) where the Relay Manager started a new relay even though the previous reachability was `Public` or if a relay already existed.\n\n### [Stop sending detailed error messages on closing QUIC connections](https://github.com/libp2p/go-libp2p/pull/2112) <!-- omit in toc -->\n\nUsers reported seeing confusing error messages and could not determine the root cause or if the error was from a local or remote peer:\n\n```{12D... Application error 0x0: conn-27571160: system: cannot reserve inbound connection: resource limit exceeded}```\n\nThis error occurred when a connection had been made with a remote peer but the remote peer dropped the connection (due to it exceeding limits).\nThis was actually an `Application error` emitted by `quic-go` and it was a bug in go-libp2p that we sent the whole message.\nFor now, we decided to stop sending this confusing error message. In the future, we will report such errors via [error codes](https://github.com/libp2p/specs/issues/479).\n\n**Full Changelog**: https://github.com/libp2p/go-libp2p/compare/v0.25.1...v0.26.0\n\n# [v0.25.1](https://github.com/libp2p/go-libp2p/releases/tag/v0.25.1)\n\nFix some test-utils used by https://github.com/libp2p/go-libp2p-kad-dht\n\n* mocknet: Start host in mocknet by @MarcoPolo in https://github.com/libp2p/go-libp2p/pull/2078\n* chore: update go-multistream by @MarcoPolo in https://github.com/libp2p/go-libp2p/pull/2081\n\n**Full Changelog**: https://github.com/libp2p/go-libp2p/compare/v0.25.0...v0.25.1\n\n# [v0.25.0](https://github.com/libp2p/go-libp2p/releases/tag/v0.25.0)\n\n## 🔦 Highlights <!-- omit in toc -->\n\n### Metrics <!-- omit in toc -->\n\nWe've started instrumenting the entire stack. In this release, we're adding metrics for:\n* the swarm: tracking incoming and outgoing connections, transports, security protocols and stream multiplexers in use: (https://github.com/libp2p/go-libp2p/blob/master/dashboards/swarm/swarm.json)\n* the event bus: tracking how different events are propagated through the stack and to external consumers (https://github.com/libp2p/go-libp2p/blob/master/dashboards/eventbus/eventbus.json)\n\nOur metrics effort is still ongoing, see https://github.com/libp2p/go-libp2p/issues/1356 for progress. We'll add metrics and dashboards for more libp2p components in a future release.\n\n### Switching to Google's official Protobuf compiler <!-- omit in toc -->\n\nSo far, we were using GoGo Protobuf to compile our Protobuf definitions to Go code. However, this library was deprecated in October last year: https://twitter.com/awalterschulze/status/1584553056100057088. We [benchmarked](https://github.com/libp2p/go-libp2p/issues/1976#issuecomment-1371527732) serialization and deserialization, and found that it's (only) 20% slower than GoGo. Since the vast majority of go-libp2p's CPU time is spent in code paths other than Protobuf handling, switching to the official compiler seemed like a worthwhile tradeoff.\n\n### Removal of OpenSSL <!-- omit in toc -->\n\nBefore this release, go-libp2p had an option to use OpenSSL bindings for certain cryptographic primitives, mostly to speed up the generation of signatures and their verification. When building go-libp2p using `go build`, we'd use the standard library crypto packages. OpenSSL was only used when passing in a build tag: `go build -tags openssl`.\nMaintaining our own fork of the long unmaintained [go-openssl package](https://github.com/libp2p/go-openssl) has proven to place a larger than expected maintenance burden on the libp2p stewards, and when we recently discovered a range of new bugs ([this](https://github.com/libp2p/go-openssl/issues/38) and [this](https://github.com/libp2p/go-libp2p/issues/1892) and [this](https://github.com/libp2p/go-libp2p/issues/1951)), we decided to re-evaluate if this code path is really worth it. The results surprised us, it turns out that:\n* The Go standard library is faster than OpenSSL for all key types that are not RSA.\n* Verifying RSA signatures is as fast as Ed25519 signatures using the Go standard library, and even faster in OpenSSL.\n* Generating RSA signatures is painfully slow, both using Go standard library crypto and using OpenSSL (but even slower using Go standard library).\n\nNow the good news is, that if your node is not using an RSA key, it will never create any RSA signatures (it might need to verify them though, when it connects to a node that uses RSA keys). If you're concerned about CPU performance, it's a good idea to avoid RSA keys (the same applies to bandwidth, RSA keys are huge!). Even for nodes using RSA keys, it turns out that generating the signatures is not a significant part of their CPU load, as verified by profiling one of Kubo's bootstrap nodes.\n\nWe therefore concluded that it's safe to drop this code path altogether, and thereby reduce our maintenance burden.\n\n### New Resource Manager types <!-- omit in toc -->\n\n* Introduces a new type `LimitVal` which can explicitly specify \"use default\", \"unlimited\", \"block all\", as well as any positive number. The zero value of `LimitVal` (the value when you create the object in Go) is \"Use default\".\n  * The JSON marshalling of this is straightforward.\n* Introduces a new `ResourceLimits` type which uses `LimitVal` instead of ints so it can encode the above for the resources.\n* Changes `LimitConfig` to `PartialLimitConfig` and uses `ResourceLimits`. This along with the marshalling changes means you can now marshal the fact that some resource limit is set to block all.\n  * Because the default is to use the defaults, this avoids the footgun of initializing the resource manager with 0 limits (that would block everything).\n\nIn general, you can go from a resource config with defaults to a concrete one with `.Build()`. e.g. `ResourceLimits.Build() => BaseLimit`, `PartialLimitConfig.Build() => ConcreteLimitConfig`, `LimitVal.Build() => int`. See PR #2000 for more details.\n\nIf you're using the defaults for the resource manager, there should be no changes needed.\n\n### Other Breaking Changes <!-- omit in toc -->\n\nWe've cleaned up our API to consistently use `protocol.ID` for libp2p and application protocols. Specifically, this means that the peer store now uses `protocol.ID`s, and the host's `SetStreamHandler` as well.\n\n## What's Changed <!-- omit in toc -->\n* chore: use generic LRU cache by @muXxer in https://github.com/libp2p/go-libp2p/pull/1980\n* core/crypto: drop all OpenSSL code paths by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/1953\n* add WebTransport to the list of default transports by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/1915\n* identify: remove old code targeting Go 1.17 by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/1964\n* core: remove introspection package by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/1978\n* identify: remove support for Identify Delta by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/1975\n* roadmap: remove optimizations of the TCP-based handshake by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/1959\n* circuitv2: correctly set the transport in the ConnectionState by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/1972\n* switch to Google's Protobuf library, make protobufs compile with go generate by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/1979\n* ci: run go generate as part of the go-check workflow by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/1986\n* ci: use GitHub token to install protoc by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/1996\n* feat: add some users to the readme by @p-shahi in https://github.com/libp2p/go-libp2p/pull/1981\n* CI: Fast multidimensional Interop tests by @MarcoPolo in https://github.com/libp2p/go-libp2p/pull/1991\n* Fix: Ignore zero values when marshalling Limits. by @ajnavarro in https://github.com/libp2p/go-libp2p/pull/1998\n* feat: add ci flakiness score to readme by @MarcoPolo in https://github.com/libp2p/go-libp2p/pull/2002\n* peerstore: make it possible to use an empty peer ID by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/2006\n* feat: rcmgr: Export resource manager errors by @MarcoPolo in https://github.com/libp2p/go-libp2p/pull/2008\n* feat: ci test-plans: Parse test timeout parameter for interop test by @MarcoPolo in https://github.com/libp2p/go-libp2p/pull/2014\n* Clean addresses with peer id before adding to addrbook by @sukunrt in https://github.com/libp2p/go-libp2p/pull/2007\n* Expose muxer ids by @aschmahmann in https://github.com/libp2p/go-libp2p/pull/2012\n* swarm: add a basic metrics tracer by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/1973\n* consistently use protocol.ID instead of strings by @sukunrt in https://github.com/libp2p/go-libp2p/pull/2004\n* swarm metrics: fix datasource for dashboard by @MarcoPolo in https://github.com/libp2p/go-libp2p/pull/2024\n* chore: remove textual roadmap in favor for Starmap by @p-shahi in https://github.com/libp2p/go-libp2p/pull/2036\n* rcmgr: *: Always close connscope by @MarcoPolo in https://github.com/libp2p/go-libp2p/pull/2037\n* chore: remove license files from the eventbus package by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/2042\n* Migrate to test-plan composite action by @thomaseizinger in https://github.com/libp2p/go-libp2p/pull/2039\n* use quic-go and webtransport-go from quic-go organization by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/2040\n* holepunch: fix flaky test by not removing holepunch protocol handler by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/1948\n* quic / webtransport: extend test to test dialing a draft-29 and a v1  by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/1957\n* p2p/test: add test for EvtLocalAddressesUpdated event by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/2016\n* quic, tcp: only register Prometheus counters when metrics are enabled by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/1971\n* p2p/test: fix flaky notification test by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/2051\n* quic: disable sending of Version Negotiation packets by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/2015\n* eventbus: add metrics by @sukunrt in https://github.com/libp2p/go-libp2p/pull/2038\n* metrics: use a single slice pool for all metrics tracer by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/2054\n* webtransport: tidy up some test output by @MarcoPolo in https://github.com/libp2p/go-libp2p/pull/2053\n* set names for eventbus event subscriptions by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/2057\n* autorelay: Split libp2p.EnableAutoRelay into 2 functions by @sukunrt in https://github.com/libp2p/go-libp2p/pull/2022\n* rcmgr: Use prometheus SDK for rcmgr metrics by @MarcoPolo in https://github.com/libp2p/go-libp2p/pull/2044\n* websocket: Replace gorilla websocket transport with nhooyr websocket transport by @MarcoPolo in https://github.com/libp2p/go-libp2p/pull/1982\n* rcmgr: add libp2p prefix to all metrics by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/2063\n* chore: git-ignore various flavors of qlog files by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/2064\n* interop: Update interop test to match spec by @MarcoPolo in https://github.com/libp2p/go-libp2p/pull/2049\n* chore: update webtransport-go to v0.5.1 by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/2072\n* identify: refactor sending of Identify pushes by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/1984\n* feat!: rcmgr: Change LimitConfig to use LimitVal type by @MarcoPolo in https://github.com/libp2p/go-libp2p/pull/2000\n* p2p/test/quic: use contexts with a timeout for Connect calls by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/2070\n* identify: add some basic metrics by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/2069\n* chore: Release v0.25.0 by @MarcoPolo in https://github.com/libp2p/go-libp2p/pull/2077\n\n## New Contributors <!-- omit in toc -->\n* @muXxer made their first contribution in https://github.com/libp2p/go-libp2p/pull/1980\n* @ajnavarro made their first contribution in https://github.com/libp2p/go-libp2p/pull/1998\n* @sukunrt made their first contribution in https://github.com/libp2p/go-libp2p/pull/2007\n* @thomaseizinger made their first contribution in https://github.com/libp2p/go-libp2p/pull/2039\n\n**Full Changelog**: https://github.com/libp2p/go-libp2p/compare/v0.24.2...v0.25.0\n"
  },
  {
    "path": "FUNDING.json",
    "content": "{\n    \"opRetro\": {\n      \"projectId\": \"0xc71faa1bcb4ceb785816c3f22823377e9e5e7c48649badd9f0a0ce491f20d4b3\"\n    },\n    \"drips\": {\n      \"filecoin\": {\n        \"ownedBy\": \"0xEF22379b7527762a00FC5820AF55BdE363624f03\"\n      }\n    }\n  }\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2014 Juan Batiz-Benet\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "\n<h1 align=\"center\">\n  <a href=\"https://libp2p.io/\"><img width=\"250\" src=\"https://github.com/libp2p/libp2p/blob/master/logo/black-bg-2.png?raw=true\" alt=\"libp2p hex logo\" /></a>\n</h1>\n\n<h3 align=\"center\">The Go implementation of the libp2p Networking Stack.</h3>\n\n<p align=\"center\">\n  <a href=\"http://protocol.ai\"><img src=\"https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square\" /></a>\n  <a href=\"http://libp2p.io/\"><img src=\"https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square\" /></a>\n  <a href=\"https://pkg.go.dev/github.com/libp2p/go-libp2p\"><img src=\"https://pkg.go.dev/badge/github.com/libp2p/go-libp2p.svg\" alt=\"Go Reference\"></a>\n  <a href=\"https://discuss.libp2p.io\"><img src=\"https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg\"/></a>\n  <a href=\"https://marcopolo.github.io/FlakyTests/\"><img src=\"https://marcopolo.github.io/FlakyTests/current-score.svg\"/></a>\n</p>\n\n# Table of Contents <!-- omit in toc -->\n- [Background](#background)\n- [Usage](#usage)\n  - [Examples](#examples)\n  - [Dashboards](#dashboards)\n- [Contribute](#contribute)\n  - [Supported Go Versions](#supported-go-versions)\n- [Notable Users](#notable-users)\n\n# Background\n\n[libp2p](https://github.com/libp2p/specs) is a networking stack and library modularized out of [The IPFS Project](https://github.com/ipfs/ipfs), and bundled separately for other tools to use.\n>\nlibp2p is the product of a long, and arduous quest of understanding -- a deep dive into the internet's network stack, and plentiful peer-to-peer protocols from the past. Building large-scale peer-to-peer systems has been complex and difficult in the last 15 years, and libp2p is a way to fix that. It is a \"network stack\" -- a protocol suite -- that cleanly separates concerns, and enables sophisticated applications to only use the protocols they absolutely need, without giving up interoperability and upgradeability. libp2p grew out of IPFS, but it is built so that lots of people can use it, for lots of different projects.\n\nTo learn more, check out the following resources:\n- [**Our documentation**](https://docs.libp2p.io)\n- [**Our community discussion forum**](https://discuss.libp2p.io)\n- [**The libp2p Specification**](https://github.com/libp2p/specs)\n- [**js-libp2p implementation**](https://github.com/libp2p/js-libp2p)\n- [**rust-libp2p implementation**](https://github.com/libp2p/rust-libp2p)\n\n# Usage\n\nThis repository (`go-libp2p`) serves as the entrypoint to the universe of packages that compose the Go implementation of the libp2p stack.\n\nYou can start using go-libp2p in your Go application simply by adding imports from our repos, e.g.:\n\n```go\nimport \"github.com/libp2p/go-libp2p\"\n```\n\n## Examples\n\nExamples can be found in the [examples folder](examples).\n\n## Dashboards\n\nWe provide prebuilt Grafana dashboards so that applications can better monitor libp2p in production.\nYou can find the [dashboard JSON files here](https://github.com/libp2p/go-libp2p/tree/master/dashboards).\n\nWe also have live [Public Dashboards](https://github.com/libp2p/go-libp2p/tree/master/dashboards/README.md#public-dashboards) that you can check out to see real time monitoring in action.\n\n\n# Contribute\n\ngo-libp2p is MIT-licensed open source software. We welcome contributions big and small! Take a look at the [community contributing notes](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md). Please make sure to check the [issues](https://github.com/libp2p/go-libp2p/issues). Search the closed ones before reporting things, and help us with the open ones.\n\nGuidelines:\n\n- read the [libp2p spec](https://github.com/libp2p/specs)\n- for general questions, use our [discussion forum](https://github.com/libp2p/go-libp2p/discussions)\n- for bug reports, open an [issue](https://github.com/libp2p/go-libp2p/issues)\n- for development questions of go-libp2p itself, please join the [mailing list](mailto:go+subscribe@libp2p.io)\n- chat at [#libp2p on Libera Chat](https://web.libera.chat/gamja/?channel=#libp2p)\n- ensure you are able to contribute (no legal issues please -- we use the DCO)\n- get in touch with @libp2p/go-libp2p-maintainers about how best to contribute\n- No drive-by contributions seeking to collect airdrops.\n  - Many projects aim to reward contributors to common goods. Great. However,\n    this creates an unfortunate incentive for low-effort PRs, submitted solely to\n    claim rewards. These PRs consume maintainers’ time and energy to triage, with\n    little to no impact on end users. If we suspect this is the intent of a PR,\n    we may close it without comment. If you believe this was done in error,\n    contact us via email. Reference this README section and explain why your PR\n    is not a “drive-by contribution.”\n- have fun!\n\nThere's a few things you can do right now to help out:\n - **Perform code reviews**.\n - **Add tests**. There can never be enough tests.\n - Go through the modules below and **check out existing issues**. This would\n   be especially useful for modules in active development. Some knowledge of\n   IPFS/libp2p may be required, as well as the infrastructure behind it - for\n   instance, you may need to read up on p2p and more complex operations like\n   muxing to be able to help technically.\n\n## AI Assistance Notice\n\n> [!IMPORTANT]\n>\n> If you are using **any kind of AI assistance** to contribute to libp2p,\n> it must be disclosed in the pull request.\n\nIf you are using any kind of AI assistance while contributing to libp2p,\n**this must be disclosed in the pull request**, along with the extent to\nwhich AI assistance was used (e.g. docs only vs. code generation).\nIf PR responses are being generated by an AI, disclose that as well.\nAs a small exception, trivial tab-completion doesn't need to be disclosed,\nso long as it is limited to single keywords or short phrases.\n\nAn example disclosure:\n\n> This PR was written primarily by Claude Code.\n\nOr a more detailed disclosure:\n\n> I consulted ChatGPT to understand the codebase but the solution\n> was fully authored manually by myself.\n\nFailure to disclose this is first and foremost rude to the human operators\non the other end of the pull request, but it also makes it difficult to\ndetermine how much scrutiny to apply to the contribution.\n\nIn a perfect world, AI assistance would produce equal or higher quality\nwork than any human. That isn't the world we live in today, and in most cases\nit's generating slop. I say this despite being a fan of and using them\nsuccessfully myself (with heavy supervision)!\n\nPlease be respectful to maintainers and disclose AI assistance.\n\n# Supported Go Versions\n\nWe test against and support the two most recent major releases of Go. This is\ninformed by Go's own [security policy](https://go.dev/doc/security/policy).\n\n# Notable Users\nSome notable users of go-libp2p are:\n- [Kubo](https://github.com/ipfs/kubo) - The original Go implementation of IPFS\n- [Lotus](https://github.com/filecoin-project/lotus) - An implementation of the Filecoin protocol\n- [Prysm](https://github.com/prysmaticlabs/prysm) - An Ethereum Beacon Chain consensus client built by [Prysmatic Labs](https://prysmaticlabs.com/)\n- [Berty](https://github.com/berty/berty) - An open, secure, offline-first, peer-to-peer and zero trust messaging app.\n- [Wasp](https://github.com/iotaledger/wasp) - A node that runs IOTA Smart Contracts built by the [IOTA Foundation](https://www.iota.org/)\n- [Mina](https://github.com/minaprotocol/mina) - A lightweight, constant-sized blockchain that runs zero-knowledge smart contracts\n- [Polygon Edge](https://github.com/0xPolygon/polygon-edge) - A modular, extensible framework for building Ethereum compatible networks\n- [Celestia Node](https://github.com/celestiaorg/celestia-node) - The Go implementation of Celestia's data availability nodes\n- [Status go](https://github.com/status-im/status-go) - Status bindings for go-ethereum, built by [Status.im](https://status.im/)\n- [Flow](https://github.com/onflow/flow-go) - A blockchain built to support games, apps, and digital assets built by [Dapper Labs](https://www.dapperlabs.com/)\n- [Swarm Bee](https://github.com/ethersphere/bee) - A client for connecting to the [Swarm network](https://www.ethswarm.org/)\n- [MultiversX Node](https://github.com/multiversx/mx-chain-go) - The Go implementation of the MultiversX network protocol\n- [Sonr](https://github.com/sonr-io/sonr) - A platform to integrate DID Documents, WebAuthn, and IPFS and manage digital identity and assets.\n- [EdgeVPN](https://github.com/mudler/edgevpn) - A decentralized, immutable, portable VPN and reverse proxy over p2p.\n- [Kairos](https://github.com/kairos-io/kairos) - A Kubernetes-focused, Cloud Native Linux meta-distribution.\n- [Oasis Core](https://github.com/oasisprotocol/oasis-core) - The consensus and runtime layers of the [Oasis protocol](https://oasisprotocol.org/).\n- [Spacemesh](https://github.com/spacemeshos/go-spacemesh/) - The Go implementation of the [Spacemesh protocol](https://spacemesh.io/), a novel layer one blockchain\n- [Tau](https://github.com/taubyte/tau/) - Open source distributed Platform as a Service (PaaS)\n\nPlease open a pull request if you want your project (min. 250 GitHub stars) to be added here.\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\ngo-libp2p is still in development. This means that there may be problems in our protocols,\nor there may be mistakes in our implementations.\nWe take security vulnerabilities very seriously. If you discover a security issue,\nplease bring it to our attention right away!\n\n## Reporting a Vulnerability\n\nIf you find a vulnerability that may affect live deployments -- for example, by exposing\na remote execution exploit -- please [**report privately**](https://github.com/libp2p/go-libp2p/security/advisories/new).\nPlease **DO NOT file a public issue**.\n\nIf the issue is an implementation weakness that cannot be immediately exploited or\nsomething not yet deployed, just discuss it openly.\nIf you need assistance, please reach out to [security@libp2p.io](mailto:security@libp2p.io).\n\n## Reporting a non security bug\n\nFor non-security bugs, please simply file a GitHub [issue](https://github.com/libp2p/go-libp2p/issues/new).\n"
  },
  {
    "path": "config/config.go",
    "content": "package config\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"slices\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/connmgr\"\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/event\"\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/metrics\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\t\"github.com/libp2p/go-libp2p/core/pnet\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\t\"github.com/libp2p/go-libp2p/core/routing\"\n\t\"github.com/libp2p/go-libp2p/core/sec\"\n\t\"github.com/libp2p/go-libp2p/core/transport\"\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/autonat\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/autorelay\"\n\tbhost \"github.com/libp2p/go-libp2p/p2p/host/basic\"\n\tblankhost \"github.com/libp2p/go-libp2p/p2p/host/blank\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/eventbus\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/observedaddrs\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoremem\"\n\trcmgr \"github.com/libp2p/go-libp2p/p2p/host/resource-manager\"\n\trouted \"github.com/libp2p/go-libp2p/p2p/host/routed\"\n\t\"github.com/libp2p/go-libp2p/p2p/net/swarm\"\n\ttptu \"github.com/libp2p/go-libp2p/p2p/net/upgrader\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/autonatv2\"\n\tcircuitv2 \"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/client\"\n\trelayv2 \"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/relay\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/holepunch\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/identify\"\n\t\"github.com/libp2p/go-libp2p/p2p/security/insecure\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/quicreuse\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/tcpreuse\"\n\tlibp2pwebrtc \"github.com/libp2p/go-libp2p/p2p/transport/webrtc\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n\t\"github.com/quic-go/quic-go\"\n\t\"go.uber.org/fx\"\n\t\"go.uber.org/fx/fxevent\"\n)\n\nvar log = logging.Logger(\"p2p-config\")\n\n// AddrsFactory is a function that takes a set of multiaddrs we're listening on and\n// returns the set of multiaddrs we should advertise to the network.\ntype AddrsFactory = bhost.AddrsFactory\n\n// NATManagerC is a NATManager constructor.\ntype NATManagerC func(network.Network) bhost.NATManager\n\ntype RoutingC func(host.Host) (routing.PeerRouting, error)\n\n// AutoNATConfig defines the AutoNAT behavior for the libp2p host.\ntype AutoNATConfig struct {\n\tForceReachability   *network.Reachability\n\tEnableService       bool\n\tThrottleGlobalLimit int\n\tThrottlePeerLimit   int\n\tThrottleInterval    time.Duration\n}\n\ntype Security struct {\n\tID          protocol.ID\n\tConstructor any\n}\n\n// Config describes a set of settings for a libp2p node\n//\n// This is *not* a stable interface. Use the options defined in the root\n// package.\ntype Config struct {\n\t// UserAgent is the identifier this node will send to other peers when\n\t// identifying itself, e.g. via the identify protocol.\n\t//\n\t// Set it via the UserAgent option function.\n\tUserAgent string\n\n\t// ProtocolVersion is the protocol version that identifies the family\n\t// of protocols used by the peer in the Identify protocol. It is set\n\t// using the [ProtocolVersion] option.\n\tProtocolVersion string\n\n\tPeerKey crypto.PrivKey\n\n\tQUICReuse          []fx.Option\n\tTransports         []fx.Option\n\tMuxers             []tptu.StreamMuxer\n\tSecurityTransports []Security\n\tInsecure           bool\n\tPSK                pnet.PSK\n\n\tDialTimeout time.Duration\n\n\tRelayCustom bool\n\tRelay       bool // should the relay transport be used\n\n\tEnableRelayService bool // should we run a circuitv2 relay (if publicly reachable)\n\tRelayServiceOpts   []relayv2.Option\n\n\tListenAddrs     []ma.Multiaddr\n\tAddrsFactory    bhost.AddrsFactory\n\tConnectionGater connmgr.ConnectionGater\n\n\tConnManager     connmgr.ConnManager\n\tResourceManager network.ResourceManager\n\n\tNATManager NATManagerC\n\tPeerstore  peerstore.Peerstore\n\tReporter   metrics.Reporter\n\n\tMultiaddrResolver network.MultiaddrDNSResolver\n\n\tDisablePing bool\n\n\tRouting RoutingC\n\n\tEnableAutoRelay bool\n\tAutoRelayOpts   []autorelay.Option\n\tAutoNATConfig\n\n\tEnableHolePunching  bool\n\tHolePunchingOptions []holepunch.Option\n\n\tDisableMetrics       bool\n\tPrometheusRegisterer prometheus.Registerer\n\n\tDialRanker network.DialRanker\n\n\tSwarmOpts []swarm.Option\n\n\tDisableIdentifyAddressDiscovery bool\n\n\tEnableAutoNATv2 bool\n\n\tUDPBlackHoleSuccessCounter        *swarm.BlackHoleSuccessCounter\n\tCustomUDPBlackHoleSuccessCounter  bool\n\tIPv6BlackHoleSuccessCounter       *swarm.BlackHoleSuccessCounter\n\tCustomIPv6BlackHoleSuccessCounter bool\n\n\tUserFxOptions []fx.Option\n\n\tShareTCPListener bool\n}\n\nfunc (cfg *Config) makeSwarm(eventBus event.Bus, enableMetrics bool) (*swarm.Swarm, error) {\n\tif cfg.Peerstore == nil {\n\t\treturn nil, fmt.Errorf(\"no peerstore specified\")\n\t}\n\n\t// Check this early. Prevents us from even *starting* without verifying this.\n\tif pnet.ForcePrivateNetwork && len(cfg.PSK) == 0 {\n\t\tlog.Error(\"tried to create a libp2p node with no Private Network Protector but usage of Private Networks is forced by the environment\")\n\t\t// Note: This is *also* checked the upgrader itself, so it'll be\n\t\t// enforced even *if* you don't use the libp2p constructor.\n\t\treturn nil, pnet.ErrNotInPrivateNetwork\n\t}\n\n\tif cfg.PeerKey == nil {\n\t\treturn nil, fmt.Errorf(\"no peer key specified\")\n\t}\n\n\t// Obtain Peer ID from public key\n\tpid, err := peer.IDFromPublicKey(cfg.PeerKey.GetPublic())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := cfg.Peerstore.AddPrivKey(pid, cfg.PeerKey); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := cfg.Peerstore.AddPubKey(pid, cfg.PeerKey.GetPublic()); err != nil {\n\t\treturn nil, err\n\t}\n\n\topts := append(cfg.SwarmOpts,\n\t\tswarm.WithUDPBlackHoleSuccessCounter(cfg.UDPBlackHoleSuccessCounter),\n\t\tswarm.WithIPv6BlackHoleSuccessCounter(cfg.IPv6BlackHoleSuccessCounter),\n\t)\n\tif cfg.Reporter != nil {\n\t\topts = append(opts, swarm.WithMetrics(cfg.Reporter))\n\t}\n\tif cfg.ConnectionGater != nil {\n\t\topts = append(opts, swarm.WithConnectionGater(cfg.ConnectionGater))\n\t}\n\tif cfg.DialTimeout != 0 {\n\t\topts = append(opts, swarm.WithDialTimeout(cfg.DialTimeout))\n\t}\n\tif cfg.ResourceManager != nil {\n\t\topts = append(opts, swarm.WithResourceManager(cfg.ResourceManager))\n\t}\n\tif cfg.MultiaddrResolver != nil {\n\t\topts = append(opts, swarm.WithMultiaddrResolver(cfg.MultiaddrResolver))\n\t}\n\tif cfg.DialRanker != nil {\n\t\topts = append(opts, swarm.WithDialRanker(cfg.DialRanker))\n\t}\n\n\tif enableMetrics {\n\t\topts = append(opts,\n\t\t\tswarm.WithMetricsTracer(swarm.NewMetricsTracer(swarm.WithRegisterer(cfg.PrometheusRegisterer))))\n\t}\n\t// TODO: Make the swarm implementation configurable.\n\treturn swarm.NewSwarm(pid, cfg.Peerstore, eventBus, opts...)\n}\n\nfunc (cfg *Config) makeAutoNATV2Host() (host.Host, error) {\n\tautonatPrivKey, _, err := crypto.GenerateEd25519Key(rand.Reader)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tps, err := pstoremem.NewPeerstore()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tautoNatCfg := Config{\n\t\tTransports:                  cfg.Transports,\n\t\tMuxers:                      cfg.Muxers,\n\t\tSecurityTransports:          cfg.SecurityTransports,\n\t\tInsecure:                    cfg.Insecure,\n\t\tPSK:                         cfg.PSK,\n\t\tConnectionGater:             cfg.ConnectionGater,\n\t\tReporter:                    cfg.Reporter,\n\t\tPeerKey:                     autonatPrivKey,\n\t\tPeerstore:                   ps,\n\t\tDialRanker:                  swarm.NoDelayDialRanker,\n\t\tUDPBlackHoleSuccessCounter:  cfg.UDPBlackHoleSuccessCounter,\n\t\tIPv6BlackHoleSuccessCounter: cfg.IPv6BlackHoleSuccessCounter,\n\t\tResourceManager:             cfg.ResourceManager,\n\t\tSwarmOpts: []swarm.Option{\n\t\t\t// Don't update black hole state for failed autonat dials\n\t\t\tswarm.WithReadOnlyBlackHoleDetector(),\n\t\t},\n\t}\n\tfxopts, err := autoNatCfg.addTransports()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar dialerHost host.Host\n\tfxopts = append(fxopts,\n\t\tfx.Provide(eventbus.NewBus),\n\t\tfx.Provide(func(lifecycle fx.Lifecycle, b event.Bus) (*swarm.Swarm, error) {\n\t\t\tlifecycle.Append(fx.Hook{\n\t\t\t\tOnStop: func(context.Context) error {\n\t\t\t\t\treturn ps.Close()\n\t\t\t\t}})\n\t\t\tsw, err := autoNatCfg.makeSwarm(b, false)\n\t\t\treturn sw, err\n\t\t}),\n\t\tfx.Provide(func(sw *swarm.Swarm) *blankhost.BlankHost {\n\t\t\treturn blankhost.NewBlankHost(sw)\n\t\t}),\n\t\tfx.Provide(func(bh *blankhost.BlankHost) host.Host {\n\t\t\treturn bh\n\t\t}),\n\t\tfx.Provide(func() crypto.PrivKey { return autonatPrivKey }),\n\t\tfx.Provide(func(bh host.Host) peer.ID { return bh.ID() }),\n\t\tfx.Invoke(func(bh *blankhost.BlankHost) {\n\t\t\tdialerHost = bh\n\t\t}),\n\t)\n\tapp := fx.New(fxopts...)\n\tif err := app.Err(); err != nil {\n\t\treturn nil, err\n\t}\n\terr = app.Start(context.Background())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tgo func() {\n\t\t<-dialerHost.Network().(*swarm.Swarm).Done()\n\t\tapp.Stop(context.Background())\n\t}()\n\treturn dialerHost, nil\n}\n\nfunc (cfg *Config) addTransports() ([]fx.Option, error) {\n\tfxopts := []fx.Option{\n\t\tfx.WithLogger(func() fxevent.Logger {\n\t\t\treturn &fxevent.SlogLogger{\n\t\t\t\tLogger: log.With(\"system\", \"fx\"),\n\t\t\t}\n\t\t}),\n\t\tfx.Provide(fx.Annotate(tptu.New, fx.ParamTags(`name:\"security\"`))),\n\t\tfx.Supply(cfg.Muxers),\n\t\tfx.Provide(func() connmgr.ConnectionGater { return cfg.ConnectionGater }),\n\t\tfx.Provide(func() pnet.PSK { return cfg.PSK }),\n\t\tfx.Provide(func() network.ResourceManager { return cfg.ResourceManager }),\n\t\tfx.Provide(func(upgrader transport.Upgrader) *tcpreuse.ConnMgr {\n\t\t\tif !cfg.ShareTCPListener {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn tcpreuse.NewConnMgr(tcpreuse.EnvReuseportVal, upgrader)\n\t\t}),\n\t\tfx.Provide(func(cm *quicreuse.ConnManager, sw *swarm.Swarm) libp2pwebrtc.ListenUDPFn {\n\t\t\thasQuicAddrPortFor := func(network string, laddr *net.UDPAddr) bool {\n\t\t\t\tquicAddrPorts := map[string]struct{}{}\n\t\t\t\tfor _, addr := range sw.ListenAddresses() {\n\t\t\t\t\tif _, err := addr.ValueForProtocol(ma.P_QUIC_V1); err == nil {\n\t\t\t\t\t\tnetw, addr, err := manet.DialArgs(addr)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn false\n\t\t\t\t\t\t}\n\t\t\t\t\t\tquicAddrPorts[netw+\"_\"+addr] = struct{}{}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t_, ok := quicAddrPorts[network+\"_\"+laddr.String()]\n\t\t\t\treturn ok\n\t\t\t}\n\n\t\t\treturn func(network string, laddr *net.UDPAddr) (net.PacketConn, error) {\n\t\t\t\tif hasQuicAddrPortFor(network, laddr) {\n\t\t\t\t\treturn cm.SharedNonQUICPacketConn(network, laddr)\n\t\t\t\t}\n\t\t\t\treturn net.ListenUDP(network, laddr)\n\t\t\t}\n\t\t}),\n\t}\n\tfxopts = append(fxopts, cfg.Transports...)\n\tif cfg.Insecure {\n\t\tfxopts = append(fxopts,\n\t\t\tfx.Provide(\n\t\t\t\tfx.Annotate(\n\t\t\t\t\tfunc(id peer.ID, priv crypto.PrivKey) []sec.SecureTransport {\n\t\t\t\t\t\treturn []sec.SecureTransport{insecure.NewWithIdentity(insecure.ID, id, priv)}\n\t\t\t\t\t},\n\t\t\t\t\tfx.ResultTags(`name:\"security\"`),\n\t\t\t\t),\n\t\t\t),\n\t\t)\n\t} else {\n\t\t// fx groups are unordered, but we need to preserve the order of the security transports\n\t\t// First of all, we construct the security transports that are needed,\n\t\t// and save them to a group call security_unordered.\n\t\tfor _, s := range cfg.SecurityTransports {\n\t\t\tfxName := fmt.Sprintf(`name:\"security_%s\"`, s.ID)\n\t\t\tfxopts = append(fxopts, fx.Supply(fx.Annotate(s.ID, fx.ResultTags(fxName))))\n\t\t\tfxopts = append(fxopts,\n\t\t\t\tfx.Provide(fx.Annotate(\n\t\t\t\t\ts.Constructor,\n\t\t\t\t\tfx.ParamTags(fxName),\n\t\t\t\t\tfx.As(new(sec.SecureTransport)),\n\t\t\t\t\tfx.ResultTags(`group:\"security_unordered\"`),\n\t\t\t\t)),\n\t\t\t)\n\t\t}\n\t\t// Then we consume the group security_unordered, and order them by the user's preference.\n\t\tfxopts = append(fxopts, fx.Provide(\n\t\t\tfx.Annotate(\n\t\t\t\tfunc(secs []sec.SecureTransport) ([]sec.SecureTransport, error) {\n\t\t\t\t\tif len(secs) != len(cfg.SecurityTransports) {\n\t\t\t\t\t\treturn nil, errors.New(\"inconsistent length for security transports\")\n\t\t\t\t\t}\n\t\t\t\t\tt := make([]sec.SecureTransport, 0, len(secs))\n\t\t\t\t\tfor _, s := range cfg.SecurityTransports {\n\t\t\t\t\t\tfor _, st := range secs {\n\t\t\t\t\t\t\tif s.ID != st.ID() {\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tt = append(t, st)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn t, nil\n\t\t\t\t},\n\t\t\t\tfx.ParamTags(`group:\"security_unordered\"`),\n\t\t\t\tfx.ResultTags(`name:\"security\"`),\n\t\t\t)))\n\t}\n\n\tfxopts = append(fxopts, fx.Provide(PrivKeyToStatelessResetKey))\n\tfxopts = append(fxopts, fx.Provide(PrivKeyToTokenGeneratorKey))\n\tif cfg.QUICReuse != nil {\n\t\tfxopts = append(fxopts, cfg.QUICReuse...)\n\t} else {\n\t\tfxopts = append(fxopts,\n\t\t\tfx.Provide(func(key quic.StatelessResetKey, tokenGenerator quic.TokenGeneratorKey, rcmgr network.ResourceManager, lifecycle fx.Lifecycle) (*quicreuse.ConnManager, error) {\n\t\t\t\topts := []quicreuse.Option{\n\t\t\t\t\tquicreuse.ConnContext(func(ctx context.Context, clientInfo *quic.ClientInfo) (context.Context, error) {\n\t\t\t\t\t\t// even if creating the quic maddr fails, let the rcmgr decide what to do with the connection\n\t\t\t\t\t\taddr, err := quicreuse.ToQuicMultiaddr(clientInfo.RemoteAddr, quic.Version1)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\taddr = nil\n\t\t\t\t\t\t}\n\t\t\t\t\t\tscope, err := rcmgr.OpenConnection(network.DirInbound, false, addr)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn ctx, err\n\t\t\t\t\t\t}\n\t\t\t\t\t\tctx = network.WithConnManagementScope(ctx, scope)\n\t\t\t\t\t\tcontext.AfterFunc(ctx, func() {\n\t\t\t\t\t\t\tscope.Done()\n\t\t\t\t\t\t})\n\t\t\t\t\t\treturn ctx, nil\n\t\t\t\t\t}),\n\t\t\t\t\tquicreuse.VerifySourceAddress(func(addr net.Addr) bool {\n\t\t\t\t\t\treturn rcmgr.VerifySourceAddress(addr)\n\t\t\t\t\t}),\n\t\t\t\t}\n\t\t\t\tif !cfg.DisableMetrics {\n\t\t\t\t\topts = append(opts, quicreuse.EnableMetrics(cfg.PrometheusRegisterer))\n\t\t\t\t}\n\t\t\t\tcm, err := quicreuse.NewConnManager(key, tokenGenerator, opts...)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tlifecycle.Append(fx.StopHook(cm.Close))\n\t\t\t\treturn cm, nil\n\t\t\t}),\n\t\t)\n\t}\n\n\tfxopts = append(fxopts, fx.Invoke(\n\t\tfx.Annotate(\n\t\t\tfunc(swrm *swarm.Swarm, tpts []transport.Transport) error {\n\t\t\t\tfor _, t := range tpts {\n\t\t\t\t\tif err := swrm.AddTransport(t); err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tfx.ParamTags(\"\", `group:\"transport\"`),\n\t\t)),\n\t)\n\tif cfg.Relay {\n\t\tfxopts = append(fxopts, fx.Invoke(circuitv2.AddTransport))\n\t}\n\treturn fxopts, nil\n}\n\nfunc (cfg *Config) newBasicHost(swrm *swarm.Swarm, eventBus event.Bus, an *autonatv2.AutoNAT, o bhost.ObservedAddrsManager) (*bhost.BasicHost, error) {\n\th, err := bhost.NewHost(swrm, &bhost.HostOpts{\n\t\tEventBus:             eventBus,\n\t\tConnManager:          cfg.ConnManager,\n\t\tAddrsFactory:         cfg.AddrsFactory,\n\t\tNATManager:           cfg.NATManager,\n\t\tEnablePing:           !cfg.DisablePing,\n\t\tUserAgent:            cfg.UserAgent,\n\t\tProtocolVersion:      cfg.ProtocolVersion,\n\t\tEnableHolePunching:   cfg.EnableHolePunching,\n\t\tHolePunchingOptions:  cfg.HolePunchingOptions,\n\t\tEnableRelayService:   cfg.EnableRelayService,\n\t\tRelayServiceOpts:     cfg.RelayServiceOpts,\n\t\tEnableMetrics:        !cfg.DisableMetrics,\n\t\tPrometheusRegisterer: cfg.PrometheusRegisterer,\n\t\tAutoNATv2:            an,\n\t\tObservedAddrsManager: o,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn h, nil\n}\n\nfunc (cfg *Config) validate() error {\n\tif cfg.EnableAutoRelay && !cfg.Relay {\n\t\treturn fmt.Errorf(\"cannot enable autorelay; relay is not enabled\")\n\t}\n\t// If possible check that the resource manager conn limit is higher than the\n\t// limit set in the conn manager.\n\tif l, ok := cfg.ResourceManager.(connmgr.GetConnLimiter); ok {\n\t\terr := cfg.ConnManager.CheckLimit(l)\n\t\tif err != nil {\n\t\t\tlog.Warn(\"rcmgr limit conflicts with connmgr limit\", \"err\", err)\n\t\t}\n\t}\n\n\tif len(cfg.PSK) > 0 && cfg.ShareTCPListener {\n\t\treturn errors.New(\"cannot use shared TCP listener with PSK\")\n\t}\n\n\treturn nil\n}\n\n// NewNode constructs a new libp2p Host from the Config.\n//\n// This function consumes the config. Do not reuse it (really!).\nfunc (cfg *Config) NewNode() (host.Host, error) {\n\n\tvalidateErr := cfg.validate()\n\tif validateErr != nil {\n\t\tif cfg.ResourceManager != nil {\n\t\t\tcfg.ResourceManager.Close()\n\t\t}\n\t\tif cfg.ConnManager != nil {\n\t\t\tcfg.ConnManager.Close()\n\t\t}\n\t\tif cfg.Peerstore != nil {\n\t\t\tcfg.Peerstore.Close()\n\t\t}\n\n\t\treturn nil, validateErr\n\t}\n\n\tif !cfg.DisableMetrics {\n\t\trcmgr.MustRegisterWith(cfg.PrometheusRegisterer)\n\t}\n\n\tfxopts := []fx.Option{\n\t\tfx.Provide(func() event.Bus {\n\t\t\treturn eventbus.NewBus(eventbus.WithMetricsTracer(eventbus.NewMetricsTracer(eventbus.WithRegisterer(cfg.PrometheusRegisterer))))\n\t\t}),\n\t\tfx.Provide(func() crypto.PrivKey {\n\t\t\treturn cfg.PeerKey\n\t\t}),\n\t\t// Make sure the swarm constructor depends on the quicreuse.ConnManager.\n\t\t// That way, the ConnManager will be started before the swarm, and more importantly,\n\t\t// the swarm will be stopped before the ConnManager.\n\t\tfx.Provide(func(eventBus event.Bus, _ *quicreuse.ConnManager, lifecycle fx.Lifecycle) (*swarm.Swarm, error) {\n\t\t\tsw, err := cfg.makeSwarm(eventBus, !cfg.DisableMetrics)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tlifecycle.Append(fx.Hook{\n\t\t\t\tOnStart: func(context.Context) error {\n\t\t\t\t\t// TODO: This method succeeds if listening on one address succeeds. We\n\t\t\t\t\t// should probably fail if listening on *any* addr fails.\n\t\t\t\t\treturn sw.Listen(cfg.ListenAddrs...)\n\t\t\t\t},\n\t\t\t\tOnStop: func(context.Context) error {\n\t\t\t\t\treturn sw.Close()\n\t\t\t\t},\n\t\t\t})\n\t\t\treturn sw, nil\n\t\t}),\n\t\tfx.Provide(func(eventBus event.Bus, s *swarm.Swarm, lifecycle fx.Lifecycle) (bhost.ObservedAddrsManager, error) {\n\t\t\tif cfg.DisableIdentifyAddressDiscovery {\n\t\t\t\treturn nil, nil\n\t\t\t}\n\t\t\to, err := observedaddrs.NewManager(eventBus, s)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tlifecycle.Append(fx.Hook{\n\t\t\t\tOnStart: func(context.Context) error {\n\t\t\t\t\to.Start(s)\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t\tOnStop: func(context.Context) error {\n\t\t\t\t\treturn o.Close()\n\t\t\t\t},\n\t\t\t})\n\t\t\treturn o, nil\n\t\t}),\n\t\tfx.Provide(func() (*autonatv2.AutoNAT, error) {\n\t\t\tif !cfg.EnableAutoNATv2 {\n\t\t\t\treturn nil, nil\n\t\t\t}\n\t\t\tah, err := cfg.makeAutoNATV2Host()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tvar mt autonatv2.MetricsTracer\n\t\t\tif !cfg.DisableMetrics {\n\t\t\t\tmt = autonatv2.NewMetricsTracer(cfg.PrometheusRegisterer)\n\t\t\t}\n\t\t\tautoNATv2, err := autonatv2.New(ah, autonatv2.WithMetricsTracer(mt))\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"failed to create autonatv2: %w\", err)\n\t\t\t}\n\t\t\treturn autoNATv2, nil\n\t\t}),\n\t\tfx.Provide(cfg.newBasicHost),\n\t\tfx.Provide(func(bh *bhost.BasicHost) identify.IDService {\n\t\t\treturn bh.IDService()\n\t\t}),\n\t\tfx.Provide(func(bh *bhost.BasicHost) host.Host {\n\t\t\treturn bh\n\t\t}),\n\t\tfx.Provide(func(h *swarm.Swarm) peer.ID { return h.LocalPeer() }),\n\t}\n\ttransportOpts, err := cfg.addTransports()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfxopts = append(fxopts, transportOpts...)\n\n\t// Configure routing\n\tif cfg.Routing != nil {\n\t\tfxopts = append(fxopts,\n\t\t\tfx.Provide(cfg.Routing),\n\t\t\tfx.Provide(func(h host.Host, router routing.PeerRouting) *routed.RoutedHost {\n\t\t\t\treturn routed.Wrap(h, router)\n\t\t\t}),\n\t\t)\n\t}\n\n\t// enable autorelay\n\tfxopts = append(fxopts,\n\t\tfx.Invoke(func(h *bhost.BasicHost, lifecycle fx.Lifecycle) error {\n\t\t\tif cfg.EnableAutoRelay {\n\t\t\t\tif !cfg.DisableMetrics {\n\t\t\t\t\tmt := autorelay.WithMetricsTracer(\n\t\t\t\t\t\tautorelay.NewMetricsTracer(autorelay.WithRegisterer(cfg.PrometheusRegisterer)))\n\t\t\t\t\tmtOpts := []autorelay.Option{mt}\n\t\t\t\t\tcfg.AutoRelayOpts = append(mtOpts, cfg.AutoRelayOpts...)\n\t\t\t\t}\n\n\t\t\t\tar, err := autorelay.NewAutoRelay(h, cfg.AutoRelayOpts...)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tlifecycle.Append(fx.StartStopHook(ar.Start, ar.Close))\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn nil\n\t\t}),\n\t)\n\n\tvar bh *bhost.BasicHost\n\tfxopts = append(fxopts, fx.Invoke(func(bho *bhost.BasicHost) { bh = bho }))\n\tfxopts = append(fxopts, fx.Invoke(func(h *bhost.BasicHost, lifecycle fx.Lifecycle) {\n\t\tlifecycle.Append(fx.StartHook(h.Start))\n\t}))\n\n\tvar rh *routed.RoutedHost\n\tif cfg.Routing != nil {\n\t\tfxopts = append(fxopts, fx.Invoke(func(bho *routed.RoutedHost) { rh = bho }))\n\t}\n\n\tfxopts = append(fxopts, cfg.UserFxOptions...)\n\n\tapp := fx.New(fxopts...)\n\tif err := app.Start(context.Background()); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := cfg.addAutoNAT(bh); err != nil {\n\t\tapp.Stop(context.Background())\n\t\tif cfg.Routing != nil {\n\t\t\trh.Close()\n\t\t} else {\n\t\t\tbh.Close()\n\t\t}\n\t\treturn nil, err\n\t}\n\n\tif cfg.Routing != nil {\n\t\treturn &closableRoutedHost{\n\t\t\tclosableBasicHost: closableBasicHost{\n\t\t\t\tApp:       app,\n\t\t\t\tBasicHost: bh,\n\t\t\t},\n\t\t\tRoutedHost: rh,\n\t\t}, nil\n\t}\n\treturn &closableBasicHost{App: app, BasicHost: bh}, nil\n}\n\nfunc (cfg *Config) addAutoNAT(h *bhost.BasicHost) error {\n\t// Only use public addresses for autonat\n\taddrFunc := func() []ma.Multiaddr {\n\t\treturn slices.DeleteFunc(h.AllAddrs(), func(a ma.Multiaddr) bool { return !manet.IsPublicAddr(a) })\n\t}\n\tif cfg.AddrsFactory != nil {\n\t\taddrFunc = func() []ma.Multiaddr {\n\t\t\treturn slices.DeleteFunc(\n\t\t\t\tslices.Clone(cfg.AddrsFactory(h.AllAddrs())),\n\t\t\t\tfunc(a ma.Multiaddr) bool { return !manet.IsPublicAddr(a) })\n\t\t}\n\t}\n\tautonatOpts := []autonat.Option{\n\t\tautonat.UsingAddresses(addrFunc),\n\t}\n\tif !cfg.DisableMetrics {\n\t\tautonatOpts = append(autonatOpts, autonat.WithMetricsTracer(\n\t\t\tautonat.NewMetricsTracer(autonat.WithRegisterer(cfg.PrometheusRegisterer)),\n\t\t))\n\t}\n\tif cfg.AutoNATConfig.ThrottleInterval != 0 {\n\t\tautonatOpts = append(autonatOpts,\n\t\t\tautonat.WithThrottling(cfg.AutoNATConfig.ThrottleGlobalLimit, cfg.AutoNATConfig.ThrottleInterval),\n\t\t\tautonat.WithPeerThrottling(cfg.AutoNATConfig.ThrottlePeerLimit))\n\t}\n\tif cfg.AutoNATConfig.EnableService {\n\t\tautonatPrivKey, _, err := crypto.GenerateEd25519Key(rand.Reader)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tps, err := pstoremem.NewPeerstore()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// Pull out the pieces of the config that we _actually_ care about.\n\t\t// Specifically, don't set up things like listeners, identify, etc.\n\t\tautoNatCfg := Config{\n\t\t\tTransports:         cfg.Transports,\n\t\t\tMuxers:             cfg.Muxers,\n\t\t\tSecurityTransports: cfg.SecurityTransports,\n\t\t\tInsecure:           cfg.Insecure,\n\t\t\tPSK:                cfg.PSK,\n\t\t\tConnectionGater:    cfg.ConnectionGater,\n\t\t\tReporter:           cfg.Reporter,\n\t\t\tPeerKey:            autonatPrivKey,\n\t\t\tPeerstore:          ps,\n\t\t\tDialRanker:         swarm.NoDelayDialRanker,\n\t\t\tResourceManager:    cfg.ResourceManager,\n\t\t\tSwarmOpts: []swarm.Option{\n\t\t\t\tswarm.WithUDPBlackHoleSuccessCounter(nil),\n\t\t\t\tswarm.WithIPv6BlackHoleSuccessCounter(nil),\n\t\t\t},\n\t\t}\n\n\t\tfxopts, err := autoNatCfg.addTransports()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tvar dialer *swarm.Swarm\n\n\t\tfxopts = append(fxopts,\n\t\t\tfx.Provide(eventbus.NewBus),\n\t\t\tfx.Provide(func(lifecycle fx.Lifecycle, b event.Bus) (*swarm.Swarm, error) {\n\t\t\t\tlifecycle.Append(fx.Hook{\n\t\t\t\t\tOnStop: func(context.Context) error {\n\t\t\t\t\t\treturn ps.Close()\n\t\t\t\t\t}})\n\t\t\t\tvar err error\n\t\t\t\tdialer, err = autoNatCfg.makeSwarm(b, false)\n\t\t\t\treturn dialer, err\n\n\t\t\t}),\n\t\t\tfx.Provide(func(s *swarm.Swarm) peer.ID { return s.LocalPeer() }),\n\t\t\tfx.Provide(func() crypto.PrivKey { return autonatPrivKey }),\n\t\t)\n\t\tapp := fx.New(fxopts...)\n\t\tif err := app.Err(); err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = app.Start(context.Background())\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tgo func() {\n\t\t\t<-dialer.Done() // The swarm used for autonat has closed, we can cleanup now\n\t\t\tapp.Stop(context.Background())\n\t\t}()\n\t\tautonatOpts = append(autonatOpts, autonat.EnableService(dialer))\n\t}\n\tif cfg.AutoNATConfig.ForceReachability != nil {\n\t\tautonatOpts = append(autonatOpts, autonat.WithReachability(*cfg.AutoNATConfig.ForceReachability))\n\t}\n\n\tautonat, err := autonat.New(h, autonatOpts...)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"autonat init failed: %w\", err)\n\t}\n\th.SetAutoNat(autonat)\n\treturn nil\n}\n\n// Option is a libp2p config option that can be given to the libp2p constructor\n// (`libp2p.New`).\ntype Option func(cfg *Config) error\n\n// Apply applies the given options to the config, returning the first error\n// encountered (if any).\nfunc (cfg *Config) Apply(opts ...Option) error {\n\tfor _, opt := range opts {\n\t\tif opt == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif err := opt(cfg); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "config/config_test.go",
    "content": "package config\n\nimport (\n\t\"testing\"\n)\n\nfunc TestNilOption(t *testing.T) {\n\tvar cfg Config\n\toptsRun := 0\n\topt := func(_ *Config) error {\n\t\toptsRun++\n\t\treturn nil\n\t}\n\tif err := cfg.Apply(nil); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := cfg.Apply(opt, nil, nil, opt, opt, nil); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif optsRun != 3 {\n\t\tt.Fatalf(\"expected to have handled 3 options, handled %d\", optsRun)\n\t}\n}\n"
  },
  {
    "path": "config/host.go",
    "content": "package config\n\nimport (\n\t\"context\"\n\n\tbasichost \"github.com/libp2p/go-libp2p/p2p/host/basic\"\n\trouted \"github.com/libp2p/go-libp2p/p2p/host/routed\"\n\n\t\"go.uber.org/fx\"\n)\n\ntype closableBasicHost struct {\n\t*fx.App\n\t*basichost.BasicHost\n}\n\nfunc (h *closableBasicHost) Close() error {\n\t_ = h.App.Stop(context.Background())\n\treturn h.BasicHost.Close()\n}\n\ntype closableRoutedHost struct {\n\t// closableBasicHost is embedded here so that interface assertions on\n\t// BasicHost exported methods work correctly.\n\tclosableBasicHost\n\t*routed.RoutedHost\n}\n\nfunc (h *closableRoutedHost) Close() error {\n\t_ = h.App.Stop(context.Background())\n\t// The routed host will close the basic host\n\treturn h.RoutedHost.Close()\n}\n"
  },
  {
    "path": "config/quic.go",
    "content": "package config\n\nimport (\n\t\"crypto/sha256\"\n\t\"io\"\n\n\t\"golang.org/x/crypto/hkdf\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\n\t\"github.com/quic-go/quic-go\"\n)\n\nconst (\n\tstatelessResetKeyInfo = \"libp2p quic stateless reset key\"\n\ttokenGeneratorKeyInfo = \"libp2p quic token generator key\"\n)\n\nfunc PrivKeyToStatelessResetKey(key crypto.PrivKey) (quic.StatelessResetKey, error) {\n\tvar statelessResetKey quic.StatelessResetKey\n\tkeyBytes, err := key.Raw()\n\tif err != nil {\n\t\treturn statelessResetKey, err\n\t}\n\tkeyReader := hkdf.New(sha256.New, keyBytes, nil, []byte(statelessResetKeyInfo))\n\tif _, err := io.ReadFull(keyReader, statelessResetKey[:]); err != nil {\n\t\treturn statelessResetKey, err\n\t}\n\treturn statelessResetKey, nil\n}\n\nfunc PrivKeyToTokenGeneratorKey(key crypto.PrivKey) (quic.TokenGeneratorKey, error) {\n\tvar tokenKey quic.TokenGeneratorKey\n\tkeyBytes, err := key.Raw()\n\tif err != nil {\n\t\treturn tokenKey, err\n\t}\n\tkeyReader := hkdf.New(sha256.New, keyBytes, nil, []byte(tokenGeneratorKeyInfo))\n\tif _, err := io.ReadFull(keyReader, tokenKey[:]); err != nil {\n\t\treturn tokenKey, err\n\t}\n\treturn tokenKey, nil\n}\n"
  },
  {
    "path": "core/alias.go",
    "content": "// Package core provides convenient access to foundational, central go-libp2p primitives via type aliases.\npackage core\n\nimport (\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\n\t\"github.com/multiformats/go-multiaddr\"\n)\n\n// Multiaddr aliases the Multiaddr type from github.com/multiformats/go-multiaddr.\n//\n// Refer to the docs on that type for more info.\ntype Multiaddr = multiaddr.Multiaddr\n\n// PeerID aliases peer.ID.\n//\n// Refer to the docs on that type for more info.\ntype PeerID = peer.ID\n\n// ProtocolID aliases protocol.ID.\n//\n// Refer to the docs on that type for more info.\ntype ProtocolID = protocol.ID\n\n// PeerAddrInfo aliases peer.AddrInfo.\n//\n// Refer to the docs on that type for more info.\ntype PeerAddrInfo = peer.AddrInfo\n\n// Host aliases host.Host.\n//\n// Refer to the docs on that type for more info.\ntype Host = host.Host\n\n// Network aliases network.Network.\n//\n// Refer to the docs on that type for more info.\ntype Network = network.Network\n\n// Conn aliases network.Conn.\n//\n// Refer to the docs on that type for more info.\ntype Conn = network.Conn\n\n// Stream aliases network.Stream.\n//\n// Refer to the docs on that type for more info.\ntype Stream = network.Stream\n"
  },
  {
    "path": "core/connmgr/decay.go",
    "content": "package connmgr\n\nimport (\n\t\"io\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n)\n\n// Decayer is implemented by connection managers supporting decaying tags. A\n// decaying tag is one whose value automatically decays over time.\n//\n// The actual application of the decay behaviour is encapsulated in a\n// user-provided decaying function (DecayFn). The function is called on every\n// tick (determined by the interval parameter), and returns either the new value\n// of the tag, or whether it should be erased altogether.\n//\n// We do not set values on a decaying tag. Rather, we \"bump\" decaying tags by a\n// delta. This calls the BumpFn with the old value and the delta, to determine\n// the new value.\n//\n// Such a pluggable design affords a great deal of flexibility and versatility.\n// Behaviours that are straightforward to implement include:\n//\n//   - Decay a tag by -1, or by half its current value, on every tick.\n//   - Every time a value is bumped, sum it to its current value.\n//   - Exponentially boost a score with every bump.\n//   - Sum the incoming score, but keep it within min, max bounds.\n//\n// Commonly used DecayFns and BumpFns are provided in this package.\ntype Decayer interface {\n\tio.Closer\n\n\t// RegisterDecayingTag creates and registers a new decaying tag, if and only\n\t// if a tag with the supplied name doesn't exist yet. Otherwise, an error is\n\t// returned.\n\t//\n\t// The caller provides the interval at which the tag is refreshed, as well\n\t// as the decay function and the bump function. Refer to godocs on DecayFn\n\t// and BumpFn for more info.\n\tRegisterDecayingTag(name string, interval time.Duration, decayFn DecayFn, bumpFn BumpFn) (DecayingTag, error)\n}\n\n// DecayFn applies a decay to the peer's score. The implementation must call\n// DecayFn at the interval supplied when registering the tag.\n//\n// It receives a copy of the decaying value, and returns the score after\n// applying the decay, as well as a flag to signal if the tag should be erased.\ntype DecayFn func(value DecayingValue) (after int, rm bool)\n\n// BumpFn applies a delta onto an existing score, and returns the new score.\n//\n// Non-trivial bump functions include exponential boosting, moving averages,\n// ceilings, etc.\ntype BumpFn func(value DecayingValue, delta int) (after int)\n\n// DecayingTag represents a decaying tag. The tag is a long-lived general\n// object, used to operate on tag values for peers.\ntype DecayingTag interface {\n\t// Name returns the name of the tag.\n\tName() string\n\n\t// Interval is the effective interval at which this tag will tick. Upon\n\t// registration, the desired interval may be overwritten depending on the\n\t// decayer's resolution, and this method allows you to obtain the effective\n\t// interval.\n\tInterval() time.Duration\n\n\t// Bump applies a delta to a tag value, calling its bump function. The bump\n\t// will be applied asynchronously, and a non-nil error indicates a fault\n\t// when queuing.\n\tBump(peer peer.ID, delta int) error\n\n\t// Remove removes a decaying tag from a peer. The removal will be applied\n\t// asynchronously, and a non-nil error indicates a fault when queuing.\n\tRemove(peer peer.ID) error\n\n\t// Close closes a decaying tag. The Decayer will stop tracking this tag,\n\t// and the state of all peers in the Connection Manager holding this tag\n\t// will be updated.\n\t//\n\t// The deletion is performed asynchronously.\n\t//\n\t// Once deleted, a tag should not be used, and further calls to Bump/Remove\n\t// will error.\n\t//\n\t// Duplicate calls to Remove will not return errors, but a failure to queue\n\t// the first actual removal, will (e.g. when the system is backlogged).\n\tClose() error\n}\n\n// DecayingValue represents a value for a decaying tag.\ntype DecayingValue struct {\n\t// Tag points to the tag this value belongs to.\n\tTag DecayingTag\n\n\t// Peer is the peer ID to whom this value is associated.\n\tPeer peer.ID\n\n\t// Added is the timestamp when this value was added for the first time for\n\t// a tag and a peer.\n\tAdded time.Time\n\n\t// LastVisit is the timestamp of the last visit.\n\tLastVisit time.Time\n\n\t// Value is the current value of the tag.\n\tValue int\n}\n"
  },
  {
    "path": "core/connmgr/gater.go",
    "content": "package connmgr\n\nimport (\n\tma \"github.com/multiformats/go-multiaddr\"\n\n\t\"github.com/libp2p/go-libp2p/core/control\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n)\n\n// ConnectionGater can be implemented by a type that supports active\n// inbound or outbound connection gating.\n//\n// ConnectionGaters are active, whereas ConnManagers tend to be passive.\n//\n// A ConnectionGater will be consulted during different states in the lifecycle\n// of a connection being established/upgraded. Specific functions will be called\n// throughout the process, to allow you to intercept the connection at that stage.\n//\n//\tInterceptPeerDial is called on an imminent outbound peer dial request, prior\n//\tto the addresses of that peer being available/resolved. Blocking connections\n//\tat this stage is typical for blacklisting scenarios.\n//\n//\tInterceptAddrDial is called on an imminent outbound dial to a peer on a\n//\tparticular address. Blocking connections at this stage is typical for\n//\taddress filtering.\n//\n//\tInterceptAccept is called as soon as a transport listener receives an\n//\tinbound connection request, before any upgrade takes place. Transports who\n//\taccept already secure and/or multiplexed connections (e.g. possibly QUIC)\n//\tMUST call this method regardless, for correctness/consistency.\n//\n//\tInterceptSecured is called for both inbound and outbound connections,\n//\tafter a security handshake has taken place and we've authenticated the peer.\n//\n//\tInterceptUpgraded is called for inbound and outbound connections, after\n//\tlibp2p has finished upgrading the connection entirely to a secure,\n//\tmultiplexed channel.\n//\n// This interface can be used to implement *strict/active* connection management\n// policies, such as hard limiting of connections once a maximum count has been\n// reached, maintaining a peer blacklist, or limiting connections by transport\n// quotas.\n//\n// EXPERIMENTAL: a DISCONNECT protocol/message will be supported in the future.\n// This allows gaters and other components to communicate the intention behind\n// a connection closure, to curtail potential reconnection attempts.\n//\n// For now, InterceptUpgraded can return a non-zero DisconnectReason when\n// blocking a connection, but this interface is likely to change in the future\n// as we solidify this feature. The reason why only this method can handle\n// DisconnectReasons is that we require stream multiplexing capability to open a\n// control protocol stream to transmit the message.\ntype ConnectionGater interface {\n\t// InterceptPeerDial tests whether we're permitted to Dial the specified peer.\n\t//\n\t// This is called by the network.Network implementation when dialling a peer.\n\tInterceptPeerDial(p peer.ID) (allow bool)\n\n\t// InterceptAddrDial tests whether we're permitted to dial the specified\n\t// multiaddr for the given peer.\n\t//\n\t// This is called by the network.Network implementation after it has\n\t// resolved the peer's addrs, and prior to dialling each.\n\tInterceptAddrDial(peer.ID, ma.Multiaddr) (allow bool)\n\n\t// InterceptAccept tests whether an incipient inbound connection is allowed.\n\t//\n\t// This is called by the upgrader, or by the transport directly (e.g. QUIC,\n\t// Bluetooth), straight after it has accepted a connection from its socket.\n\tInterceptAccept(network.ConnMultiaddrs) (allow bool)\n\n\t// InterceptSecured tests whether a given connection, now authenticated,\n\t// is allowed.\n\t//\n\t// This is called by the upgrader, after it has performed the security\n\t// handshake, and before it negotiates the muxer, or by the directly by the\n\t// transport, at the exact same checkpoint.\n\tInterceptSecured(network.Direction, peer.ID, network.ConnMultiaddrs) (allow bool)\n\n\t// InterceptUpgraded tests whether a fully capable connection is allowed.\n\t//\n\t// At this point, the connection a multiplexer has been selected.\n\t// When rejecting a connection, the gater can return a DisconnectReason.\n\t// Refer to the godoc on the ConnectionGater type for more information.\n\t//\n\t// NOTE: the go-libp2p implementation currently IGNORES the disconnect reason.\n\tInterceptUpgraded(network.Conn) (allow bool, reason control.DisconnectReason)\n}\n"
  },
  {
    "path": "core/connmgr/manager.go",
    "content": "// Package connmgr provides connection tracking and management interfaces for libp2p.\n//\n// The ConnManager interface exported from this package allows libp2p to enforce an\n// upper bound on the total number of open connections. To avoid service disruptions,\n// connections can be tagged with metadata and optionally \"protected\" to ensure that\n// essential connections are not arbitrarily cut.\npackage connmgr\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n)\n\n// SupportsDecay evaluates if the provided ConnManager supports decay, and if\n// so, it returns the Decayer object. Refer to godocs on Decayer for more info.\nfunc SupportsDecay(mgr ConnManager) (Decayer, bool) {\n\td, ok := mgr.(Decayer)\n\treturn d, ok\n}\n\n// ConnManager tracks connections to peers, and allows consumers to associate\n// metadata with each peer.\n//\n// It enables connections to be trimmed based on implementation-defined\n// heuristics. The ConnManager allows libp2p to enforce an upper bound on the\n// total number of open connections.\n//\n// ConnManagers supporting decaying tags implement Decayer. Use the\n// SupportsDecay function to safely cast an instance to Decayer, if supported.\ntype ConnManager interface {\n\t// TagPeer tags a peer with a string, associating a weight with the tag.\n\tTagPeer(peer.ID, string, int)\n\n\t// UntagPeer removes the tagged value from the peer.\n\tUntagPeer(p peer.ID, tag string)\n\n\t// UpsertTag updates an existing tag or inserts a new one.\n\t//\n\t// The connection manager calls the upsert function supplying the current\n\t// value of the tag (or zero if inexistent). The return value is used as\n\t// the new value of the tag.\n\tUpsertTag(p peer.ID, tag string, upsert func(int) int)\n\n\t// GetTagInfo returns the metadata associated with the peer,\n\t// or nil if no metadata has been recorded for the peer.\n\tGetTagInfo(p peer.ID) *TagInfo\n\n\t// TrimOpenConns terminates open connections based on an implementation-defined\n\t// heuristic.\n\tTrimOpenConns(ctx context.Context)\n\n\t// Notifee returns an implementation that can be called back to inform of\n\t// opened and closed connections.\n\tNotifee() network.Notifiee\n\n\t// Protect protects a peer from having its connection(s) pruned.\n\t//\n\t// Tagging allows different parts of the system to manage protections without interfering with one another.\n\t//\n\t// Calls to Protect() with the same tag are idempotent. They are not refcounted, so after multiple calls\n\t// to Protect() with the same tag, a single Unprotect() call bearing the same tag will revoke the protection.\n\tProtect(id peer.ID, tag string)\n\n\t// Unprotect removes a protection that may have been placed on a peer, under the specified tag.\n\t//\n\t// The return value indicates whether the peer continues to be protected after this call, by way of a different tag.\n\t// See notes on Protect() for more info.\n\tUnprotect(id peer.ID, tag string) (protected bool)\n\n\t// IsProtected returns true if the peer is protected for some tag; if the tag is the empty string\n\t// then it will return true if the peer is protected for any tag\n\tIsProtected(id peer.ID, tag string) (protected bool)\n\n\t// CheckLimit will return an error if the connection manager's internal\n\t// connection limit exceeds the provided system limit.\n\tCheckLimit(l GetConnLimiter) error\n\n\t// Close closes the connection manager and stops background processes.\n\tClose() error\n}\n\n// TagInfo stores metadata associated with a peer.\ntype TagInfo struct {\n\tFirstSeen time.Time\n\tValue     int\n\n\t// Tags maps tag ids to the numerical values.\n\tTags map[string]int\n\n\t// Conns maps connection ids (such as remote multiaddr) to their creation time.\n\tConns map[string]time.Time\n}\n\n// GetConnLimiter provides access to a component's total connection limit.\ntype GetConnLimiter interface {\n\t// GetConnLimit returns the total connection limit of the implementing component.\n\tGetConnLimit() int\n}\n"
  },
  {
    "path": "core/connmgr/null.go",
    "content": "package connmgr\n\nimport (\n\t\"context\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n)\n\n// NullConnMgr is a ConnMgr that provides no functionality.\ntype NullConnMgr struct{}\n\nvar _ ConnManager = (*NullConnMgr)(nil)\n\nfunc (NullConnMgr) TagPeer(peer.ID, string, int)             {}\nfunc (NullConnMgr) UntagPeer(peer.ID, string)                {}\nfunc (NullConnMgr) UpsertTag(peer.ID, string, func(int) int) {}\nfunc (NullConnMgr) GetTagInfo(peer.ID) *TagInfo              { return &TagInfo{} }\nfunc (NullConnMgr) TrimOpenConns(_ context.Context)          {}\nfunc (NullConnMgr) Notifee() network.Notifiee                { return network.GlobalNoopNotifiee }\nfunc (NullConnMgr) Protect(peer.ID, string)                  {}\nfunc (NullConnMgr) Unprotect(peer.ID, string) bool           { return false }\nfunc (NullConnMgr) IsProtected(peer.ID, string) bool         { return false }\nfunc (NullConnMgr) CheckLimit(_ GetConnLimiter) error        { return nil }\nfunc (NullConnMgr) Close() error                             { return nil }\n"
  },
  {
    "path": "core/connmgr/presets.go",
    "content": "package connmgr\n\nimport (\n\t\"math\"\n\t\"time\"\n)\n\n// DecayNone applies no decay.\nfunc DecayNone() DecayFn {\n\treturn func(value DecayingValue) (_ int, rm bool) {\n\t\treturn value.Value, false\n\t}\n}\n\n// DecayFixed subtracts from by the provided minuend, and deletes the tag when\n// first reaching 0 or negative.\nfunc DecayFixed(minuend int) DecayFn {\n\treturn func(value DecayingValue) (_ int, rm bool) {\n\t\tv := value.Value - minuend\n\t\treturn v, v <= 0\n\t}\n}\n\n// DecayLinear applies a fractional coefficient to the value of the current tag,\n// rounding down via math.Floor. It erases the tag when the result is zero.\nfunc DecayLinear(coef float64) DecayFn {\n\treturn func(value DecayingValue) (after int, rm bool) {\n\t\tv := math.Floor(float64(value.Value) * coef)\n\t\treturn int(v), v <= 0\n\t}\n}\n\n// DecayExpireWhenInactive expires a tag after a certain period of no bumps.\nfunc DecayExpireWhenInactive(after time.Duration) DecayFn {\n\treturn func(value DecayingValue) (_ int, rm bool) {\n\t\trm = time.Until(value.LastVisit) >= after\n\t\treturn 0, rm\n\t}\n}\n\n// BumpSumUnbounded adds the incoming value to the peer's score.\nfunc BumpSumUnbounded() BumpFn {\n\treturn func(value DecayingValue, delta int) (after int) {\n\t\treturn value.Value + delta\n\t}\n}\n\n// BumpSumBounded keeps summing the incoming score, keeping it within a\n// [min, max] range.\nfunc BumpSumBounded(min, max int) BumpFn {\n\treturn func(value DecayingValue, delta int) (after int) {\n\t\tv := value.Value + delta\n\t\tif v >= max {\n\t\t\treturn max\n\t\t} else if v <= min {\n\t\t\treturn min\n\t\t}\n\t\treturn v\n\t}\n}\n\n// BumpOverwrite replaces the current value of the tag with the incoming one.\nfunc BumpOverwrite() BumpFn {\n\treturn func(_ DecayingValue, delta int) (after int) {\n\t\treturn delta\n\t}\n}\n"
  },
  {
    "path": "core/control/disconnect.go",
    "content": "package control\n\n// DisconnectReason communicates the reason why a connection is being closed.\n//\n// A zero value stands for \"no reason\" / NA.\n//\n// This is an EXPERIMENTAL type. It will change in the future. Refer to the\n// connmgr.ConnectionGater godoc for more info.\ntype DisconnectReason int\n"
  },
  {
    "path": "core/crypto/bench_test.go",
    "content": "package crypto\n\nimport \"testing\"\n\nfunc BenchmarkSignRSA1B(b *testing.B)      { RunBenchmarkSignRSA(b, 1) }\nfunc BenchmarkSignRSA10B(b *testing.B)     { RunBenchmarkSignRSA(b, 10) }\nfunc BenchmarkSignRSA100B(b *testing.B)    { RunBenchmarkSignRSA(b, 100) }\nfunc BenchmarkSignRSA1000B(b *testing.B)   { RunBenchmarkSignRSA(b, 1000) }\nfunc BenchmarkSignRSA10000B(b *testing.B)  { RunBenchmarkSignRSA(b, 10000) }\nfunc BenchmarkSignRSA100000B(b *testing.B) { RunBenchmarkSignRSA(b, 100000) }\n\nfunc BenchmarkVerifyRSA1B(b *testing.B)      { RunBenchmarkVerifyRSA(b, 1) }\nfunc BenchmarkVerifyRSA10B(b *testing.B)     { RunBenchmarkVerifyRSA(b, 10) }\nfunc BenchmarkVerifyRSA100B(b *testing.B)    { RunBenchmarkVerifyRSA(b, 100) }\nfunc BenchmarkVerifyRSA1000B(b *testing.B)   { RunBenchmarkVerifyRSA(b, 1000) }\nfunc BenchmarkVerifyRSA10000B(b *testing.B)  { RunBenchmarkVerifyRSA(b, 10000) }\nfunc BenchmarkVerifyRSA100000B(b *testing.B) { RunBenchmarkVerifyRSA(b, 100000) }\n\nfunc BenchmarkSignEd255191B(b *testing.B)      { RunBenchmarkSignEd25519(b, 1) }\nfunc BenchmarkSignEd2551910B(b *testing.B)     { RunBenchmarkSignEd25519(b, 10) }\nfunc BenchmarkSignEd25519100B(b *testing.B)    { RunBenchmarkSignEd25519(b, 100) }\nfunc BenchmarkSignEd255191000B(b *testing.B)   { RunBenchmarkSignEd25519(b, 1000) }\nfunc BenchmarkSignEd2551910000B(b *testing.B)  { RunBenchmarkSignEd25519(b, 10000) }\nfunc BenchmarkSignEd25519100000B(b *testing.B) { RunBenchmarkSignEd25519(b, 100000) }\n\nfunc BenchmarkVerifyEd255191B(b *testing.B)      { RunBenchmarkVerifyEd25519(b, 1) }\nfunc BenchmarkVerifyEd2551910B(b *testing.B)     { RunBenchmarkVerifyEd25519(b, 10) }\nfunc BenchmarkVerifyEd25519100B(b *testing.B)    { RunBenchmarkVerifyEd25519(b, 100) }\nfunc BenchmarkVerifyEd255191000B(b *testing.B)   { RunBenchmarkVerifyEd25519(b, 1000) }\nfunc BenchmarkVerifyEd2551910000B(b *testing.B)  { RunBenchmarkVerifyEd25519(b, 10000) }\nfunc BenchmarkVerifyEd25519100000B(b *testing.B) { RunBenchmarkVerifyEd25519(b, 100000) }\n\nfunc RunBenchmarkSignRSA(b *testing.B, numBytes int) {\n\trunBenchmarkSign(b, numBytes, RSA)\n}\n\nfunc RunBenchmarkSignEd25519(b *testing.B, numBytes int) {\n\trunBenchmarkSign(b, numBytes, Ed25519)\n}\n\nfunc runBenchmarkSign(b *testing.B, numBytes int, t int) {\n\tsecret, _, err := GenerateKeyPair(t, 2048)\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\tsomeData := make([]byte, numBytes)\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, err := secret.Sign(someData)\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t}\n}\n\nfunc RunBenchmarkVerifyRSA(b *testing.B, numBytes int) {\n\trunBenchmarkVerify(b, numBytes, RSA)\n}\n\nfunc RunBenchmarkVerifyEd25519(b *testing.B, numBytes int) {\n\trunBenchmarkVerify(b, numBytes, Ed25519)\n}\n\nfunc runBenchmarkVerify(b *testing.B, numBytes int, t int) {\n\tsecret, public, err := GenerateKeyPair(t, 2048)\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\tsomeData := make([]byte, numBytes)\n\tsignature, err := secret.Sign(someData)\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tvalid, err := public.Verify(someData, signature)\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t\tif !valid {\n\t\t\tb.Fatal(\"signature should be valid\")\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "core/crypto/ecdsa.go",
    "content": "package crypto\n\nimport (\n\t\"crypto/ecdsa\"\n\t\"crypto/elliptic\"\n\t\"crypto/rand\"\n\t\"crypto/sha256\"\n\t\"crypto/x509\"\n\t\"encoding/asn1\"\n\t\"errors\"\n\t\"io\"\n\t\"math/big\"\n\n\tpb \"github.com/libp2p/go-libp2p/core/crypto/pb\"\n\t\"github.com/libp2p/go-libp2p/core/internal/catch\"\n)\n\n// ECDSAPrivateKey is an implementation of an ECDSA private key\ntype ECDSAPrivateKey struct {\n\tpriv *ecdsa.PrivateKey\n}\n\n// ECDSAPublicKey is an implementation of an ECDSA public key\ntype ECDSAPublicKey struct {\n\tpub *ecdsa.PublicKey\n}\n\n// ECDSASig holds the r and s values of an ECDSA signature\ntype ECDSASig struct {\n\tR, S *big.Int\n}\n\nvar (\n\t// ErrNotECDSAPubKey is returned when the public key passed is not an ecdsa public key\n\tErrNotECDSAPubKey = errors.New(\"not an ecdsa public key\")\n\t// ErrNilSig is returned when the signature is nil\n\tErrNilSig = errors.New(\"sig is nil\")\n\t// ErrNilPrivateKey is returned when a nil private key is provided\n\tErrNilPrivateKey = errors.New(\"private key is nil\")\n\t// ErrNilPublicKey is returned when a nil public key is provided\n\tErrNilPublicKey = errors.New(\"public key is nil\")\n\t// ECDSACurve is the default ecdsa curve used\n\tECDSACurve = elliptic.P256()\n)\n\n// GenerateECDSAKeyPair generates a new ecdsa private and public key\nfunc GenerateECDSAKeyPair(src io.Reader) (PrivKey, PubKey, error) {\n\treturn GenerateECDSAKeyPairWithCurve(ECDSACurve, src)\n}\n\n// GenerateECDSAKeyPairWithCurve generates a new ecdsa private and public key with a specified curve\nfunc GenerateECDSAKeyPairWithCurve(curve elliptic.Curve, src io.Reader) (PrivKey, PubKey, error) {\n\tpriv, err := ecdsa.GenerateKey(curve, src)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\treturn &ECDSAPrivateKey{priv}, &ECDSAPublicKey{&priv.PublicKey}, nil\n}\n\n// ECDSAKeyPairFromKey generates a new ecdsa private and public key from an input private key\nfunc ECDSAKeyPairFromKey(priv *ecdsa.PrivateKey) (PrivKey, PubKey, error) {\n\tif priv == nil {\n\t\treturn nil, nil, ErrNilPrivateKey\n\t}\n\n\treturn &ECDSAPrivateKey{priv}, &ECDSAPublicKey{&priv.PublicKey}, nil\n}\n\n// ECDSAPublicKeyFromPubKey generates a new ecdsa public key from an input public key\nfunc ECDSAPublicKeyFromPubKey(pub ecdsa.PublicKey) (PubKey, error) {\n\treturn &ECDSAPublicKey{pub: &pub}, nil\n}\n\n// MarshalECDSAPrivateKey returns x509 bytes from a private key\nfunc MarshalECDSAPrivateKey(ePriv ECDSAPrivateKey) (res []byte, err error) {\n\tdefer func() { catch.HandlePanic(recover(), &err, \"ECDSA private-key marshal\") }()\n\treturn x509.MarshalECPrivateKey(ePriv.priv)\n}\n\n// MarshalECDSAPublicKey returns x509 bytes from a public key\nfunc MarshalECDSAPublicKey(ePub ECDSAPublicKey) (res []byte, err error) {\n\tdefer func() { catch.HandlePanic(recover(), &err, \"ECDSA public-key marshal\") }()\n\treturn x509.MarshalPKIXPublicKey(ePub.pub)\n}\n\n// UnmarshalECDSAPrivateKey returns a private key from x509 bytes\nfunc UnmarshalECDSAPrivateKey(data []byte) (res PrivKey, err error) {\n\tdefer func() { catch.HandlePanic(recover(), &err, \"ECDSA private-key unmarshal\") }()\n\n\tpriv, err := x509.ParseECPrivateKey(data)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &ECDSAPrivateKey{priv}, nil\n}\n\n// UnmarshalECDSAPublicKey returns the public key from x509 bytes\nfunc UnmarshalECDSAPublicKey(data []byte) (key PubKey, err error) {\n\tdefer func() { catch.HandlePanic(recover(), &err, \"ECDSA public-key unmarshal\") }()\n\n\tpubIfc, err := x509.ParsePKIXPublicKey(data)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpub, ok := pubIfc.(*ecdsa.PublicKey)\n\tif !ok {\n\t\treturn nil, ErrNotECDSAPubKey\n\t}\n\n\treturn &ECDSAPublicKey{pub}, nil\n}\n\n// Type returns the key type\nfunc (ePriv *ECDSAPrivateKey) Type() pb.KeyType {\n\treturn pb.KeyType_ECDSA\n}\n\n// Raw returns x509 bytes from a private key\nfunc (ePriv *ECDSAPrivateKey) Raw() (res []byte, err error) {\n\tdefer func() { catch.HandlePanic(recover(), &err, \"ECDSA private-key marshal\") }()\n\treturn x509.MarshalECPrivateKey(ePriv.priv)\n}\n\n// Equals compares two private keys\nfunc (ePriv *ECDSAPrivateKey) Equals(o Key) bool {\n\treturn basicEquals(ePriv, o)\n}\n\n// Sign returns the signature of the input data\nfunc (ePriv *ECDSAPrivateKey) Sign(data []byte) (sig []byte, err error) {\n\tdefer func() { catch.HandlePanic(recover(), &err, \"ECDSA signing\") }()\n\thash := sha256.Sum256(data)\n\tr, s, err := ecdsa.Sign(rand.Reader, ePriv.priv, hash[:])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn asn1.Marshal(ECDSASig{\n\t\tR: r,\n\t\tS: s,\n\t})\n}\n\n// GetPublic returns a public key\nfunc (ePriv *ECDSAPrivateKey) GetPublic() PubKey {\n\treturn &ECDSAPublicKey{&ePriv.priv.PublicKey}\n}\n\n// Type returns the key type\nfunc (ePub *ECDSAPublicKey) Type() pb.KeyType {\n\treturn pb.KeyType_ECDSA\n}\n\n// Raw returns x509 bytes from a public key\nfunc (ePub *ECDSAPublicKey) Raw() ([]byte, error) {\n\treturn x509.MarshalPKIXPublicKey(ePub.pub)\n}\n\n// Equals compares to public keys\nfunc (ePub *ECDSAPublicKey) Equals(o Key) bool {\n\treturn basicEquals(ePub, o)\n}\n\n// Verify compares data to a signature\nfunc (ePub *ECDSAPublicKey) Verify(data, sigBytes []byte) (success bool, err error) {\n\tdefer func() {\n\t\tcatch.HandlePanic(recover(), &err, \"ECDSA signature verification\")\n\n\t\t// Just to be extra paranoid.\n\t\tif err != nil {\n\t\t\tsuccess = false\n\t\t}\n\t}()\n\n\tsig := new(ECDSASig)\n\tif _, err := asn1.Unmarshal(sigBytes, sig); err != nil {\n\t\treturn false, err\n\t}\n\n\thash := sha256.Sum256(data)\n\n\treturn ecdsa.Verify(ePub.pub, hash[:], sig.R, sig.S), nil\n}\n"
  },
  {
    "path": "core/crypto/ecdsa_test.go",
    "content": "package crypto\n\nimport (\n\t\"crypto/ecdsa\"\n\t\"crypto/rand\"\n\t\"testing\"\n)\n\nfunc TestECDSABasicSignAndVerify(t *testing.T) {\n\tpriv, pub, err := GenerateECDSAKeyPair(rand.Reader)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdata := []byte(\"hello! and welcome to some awesome crypto primitives\")\n\n\tsig, err := priv.Sign(data)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tok, err := pub.Verify(data, sig)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif !ok {\n\t\tt.Fatal(\"signature didn't match\")\n\t}\n\n\t// change data\n\tdata[0] = ^data[0]\n\tok, err = pub.Verify(data, sig)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif ok {\n\t\tt.Fatal(\"signature matched and shouldn't\")\n\t}\n}\n\nfunc TestECDSASignZero(t *testing.T) {\n\tpriv, pub, err := GenerateECDSAKeyPair(rand.Reader)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdata := make([]byte, 0)\n\tsig, err := priv.Sign(data)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tok, err := pub.Verify(data, sig)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif !ok {\n\t\tt.Fatal(\"signature didn't match\")\n\t}\n}\n\nfunc TestECDSAMarshalLoop(t *testing.T) {\n\tpriv, pub, err := GenerateECDSAKeyPair(rand.Reader)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tprivB, err := MarshalPrivateKey(priv)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tprivNew, err := UnmarshalPrivateKey(privB)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif !priv.Equals(privNew) || !privNew.Equals(priv) {\n\t\tt.Fatal(\"keys are not equal\")\n\t}\n\n\tpubB, err := MarshalPublicKey(pub)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tpubNew, err := UnmarshalPublicKey(pubB)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif !pub.Equals(pubNew) || !pubNew.Equals(pub) {\n\t\tt.Fatal(\"keys are not equal\")\n\t}\n\n}\n\nfunc TestECDSAPublicKeyFromPubKey(t *testing.T) {\n\tecdsaPrivK, err := ecdsa.GenerateKey(ECDSACurve, rand.Reader)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tprivK, _, err := ECDSAKeyPairFromKey(ecdsaPrivK)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdata := []byte(\"Hello world!\")\n\tsignature, err := privK.Sign(data)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tpubKey, err := ECDSAPublicKeyFromPubKey(ecdsaPrivK.PublicKey)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tok, err := pubKey.Verify(data, signature)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif !ok {\n\t\tt.Fatal(\"signature didn't match\")\n\t}\n\n\tpubB, err := MarshalPublicKey(pubKey)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tpubNew, err := UnmarshalPublicKey(pubB)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif !pubKey.Equals(pubNew) || !pubNew.Equals(pubKey) {\n\t\tt.Fatal(\"keys are not equal\")\n\t}\n}\n"
  },
  {
    "path": "core/crypto/ed25519.go",
    "content": "package crypto\n\nimport (\n\t\"bytes\"\n\t\"crypto/ed25519\"\n\t\"crypto/subtle\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\n\tpb \"github.com/libp2p/go-libp2p/core/crypto/pb\"\n\t\"github.com/libp2p/go-libp2p/core/internal/catch\"\n)\n\n// Ed25519PrivateKey is an ed25519 private key.\ntype Ed25519PrivateKey struct {\n\tk ed25519.PrivateKey\n}\n\n// Ed25519PublicKey is an ed25519 public key.\ntype Ed25519PublicKey struct {\n\tk ed25519.PublicKey\n}\n\n// GenerateEd25519Key generates a new ed25519 private and public key pair.\nfunc GenerateEd25519Key(src io.Reader) (PrivKey, PubKey, error) {\n\tpub, priv, err := ed25519.GenerateKey(src)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\treturn &Ed25519PrivateKey{\n\t\t\tk: priv,\n\t\t},\n\t\t&Ed25519PublicKey{\n\t\t\tk: pub,\n\t\t},\n\t\tnil\n}\n\n// Type of the private key (Ed25519).\nfunc (k *Ed25519PrivateKey) Type() pb.KeyType {\n\treturn pb.KeyType_Ed25519\n}\n\n// Raw private key bytes.\nfunc (k *Ed25519PrivateKey) Raw() ([]byte, error) {\n\t// The Ed25519 private key contains two 32-bytes curve points, the private\n\t// key and the public key.\n\t// It makes it more efficient to get the public key without re-computing an\n\t// elliptic curve multiplication.\n\tbuf := make([]byte, len(k.k))\n\tcopy(buf, k.k)\n\n\treturn buf, nil\n}\n\nfunc (k *Ed25519PrivateKey) pubKeyBytes() []byte {\n\treturn k.k[ed25519.PrivateKeySize-ed25519.PublicKeySize:]\n}\n\n// Equals compares two ed25519 private keys.\nfunc (k *Ed25519PrivateKey) Equals(o Key) bool {\n\tedk, ok := o.(*Ed25519PrivateKey)\n\tif !ok {\n\t\treturn basicEquals(k, o)\n\t}\n\n\treturn subtle.ConstantTimeCompare(k.k, edk.k) == 1\n}\n\n// GetPublic returns an ed25519 public key from a private key.\nfunc (k *Ed25519PrivateKey) GetPublic() PubKey {\n\treturn &Ed25519PublicKey{k: k.pubKeyBytes()}\n}\n\n// Sign returns a signature from an input message.\nfunc (k *Ed25519PrivateKey) Sign(msg []byte) (res []byte, err error) {\n\tdefer func() { catch.HandlePanic(recover(), &err, \"ed15519 signing\") }()\n\n\treturn ed25519.Sign(k.k, msg), nil\n}\n\n// Type of the public key (Ed25519).\nfunc (k *Ed25519PublicKey) Type() pb.KeyType {\n\treturn pb.KeyType_Ed25519\n}\n\n// Raw public key bytes.\nfunc (k *Ed25519PublicKey) Raw() ([]byte, error) {\n\treturn k.k, nil\n}\n\n// Equals compares two ed25519 public keys.\nfunc (k *Ed25519PublicKey) Equals(o Key) bool {\n\tedk, ok := o.(*Ed25519PublicKey)\n\tif !ok {\n\t\treturn basicEquals(k, o)\n\t}\n\n\treturn bytes.Equal(k.k, edk.k)\n}\n\n// Verify checks a signature against the input data.\nfunc (k *Ed25519PublicKey) Verify(data []byte, sig []byte) (success bool, err error) {\n\tdefer func() {\n\t\tcatch.HandlePanic(recover(), &err, \"ed15519 signature verification\")\n\n\t\t// To be safe.\n\t\tif err != nil {\n\t\t\tsuccess = false\n\t\t}\n\t}()\n\treturn ed25519.Verify(k.k, data, sig), nil\n}\n\n// UnmarshalEd25519PublicKey returns a public key from input bytes.\nfunc UnmarshalEd25519PublicKey(data []byte) (PubKey, error) {\n\tif len(data) != 32 {\n\t\treturn nil, errors.New(\"expect ed25519 public key data size to be 32\")\n\t}\n\n\treturn &Ed25519PublicKey{\n\t\tk: ed25519.PublicKey(data),\n\t}, nil\n}\n\n// UnmarshalEd25519PrivateKey returns a private key from input bytes.\nfunc UnmarshalEd25519PrivateKey(data []byte) (PrivKey, error) {\n\tswitch len(data) {\n\tcase ed25519.PrivateKeySize + ed25519.PublicKeySize:\n\t\t// Remove the redundant public key. See issue #36.\n\t\tredundantPk := data[ed25519.PrivateKeySize:]\n\t\tpk := data[ed25519.PrivateKeySize-ed25519.PublicKeySize : ed25519.PrivateKeySize]\n\t\tif subtle.ConstantTimeCompare(pk, redundantPk) == 0 {\n\t\t\treturn nil, errors.New(\"expected redundant ed25519 public key to be redundant\")\n\t\t}\n\n\t\t// No point in storing the extra data.\n\t\tnewKey := make([]byte, ed25519.PrivateKeySize)\n\t\tcopy(newKey, data[:ed25519.PrivateKeySize])\n\t\tdata = newKey\n\tcase ed25519.PrivateKeySize:\n\tdefault:\n\t\treturn nil, fmt.Errorf(\n\t\t\t\"expected ed25519 data size to be %d or %d, got %d\",\n\t\t\ted25519.PrivateKeySize,\n\t\t\ted25519.PrivateKeySize+ed25519.PublicKeySize,\n\t\t\tlen(data),\n\t\t)\n\t}\n\n\treturn &Ed25519PrivateKey{\n\t\tk: ed25519.PrivateKey(data),\n\t}, nil\n}\n"
  },
  {
    "path": "core/crypto/ed25519_test.go",
    "content": "package crypto\n\nimport (\n\t\"crypto/ed25519\"\n\t\"crypto/rand\"\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto/pb\"\n\n\t\"google.golang.org/protobuf/proto\"\n)\n\nfunc TestBasicSignAndVerify(t *testing.T) {\n\tpriv, pub, err := GenerateEd25519Key(rand.Reader)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdata := []byte(\"hello! and welcome to some awesome crypto primitives\")\n\n\tsig, err := priv.Sign(data)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tok, err := pub.Verify(data, sig)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif !ok {\n\t\tt.Fatal(\"signature didn't match\")\n\t}\n\n\t// change data\n\tdata[0] = ^data[0]\n\tok, err = pub.Verify(data, sig)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif ok {\n\t\tt.Fatal(\"signature matched and shouldn't\")\n\t}\n}\n\nfunc TestSignZero(t *testing.T) {\n\tpriv, pub, err := GenerateEd25519Key(rand.Reader)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdata := make([]byte, 0)\n\tsig, err := priv.Sign(data)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tok, err := pub.Verify(data, sig)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif !ok {\n\t\tt.Fatal(\"signature didn't match\")\n\t}\n}\n\nfunc TestMarshalLoop(t *testing.T) {\n\tpriv, pub, err := GenerateEd25519Key(rand.Reader)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tt.Run(\"PrivateKey\", func(t *testing.T) {\n\t\tfor name, f := range map[string]func() ([]byte, error){\n\t\t\t\"Marshal\": func() ([]byte, error) {\n\t\t\t\treturn MarshalPrivateKey(priv)\n\t\t\t},\n\t\t\t\"Redundant\": func() ([]byte, error) {\n\t\t\t\t// See issue #36.\n\t\t\t\t// Ed25519 private keys used to contain the public key twice.\n\t\t\t\t// For backwards-compatibility, we need to continue supporting\n\t\t\t\t// that scenario.\n\t\t\t\tdata, err := priv.Raw()\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t\tdata = append(data, data[len(data)-ed25519.PublicKeySize:]...)\n\t\t\t\treturn proto.Marshal(&pb.PrivateKey{\n\t\t\t\t\tType: priv.Type().Enum(),\n\t\t\t\t\tData: data,\n\t\t\t\t})\n\t\t\t},\n\t\t} {\n\t\t\tt.Run(name, func(t *testing.T) {\n\t\t\t\tbts, err := f()\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\n\t\t\t\tprivNew, err := UnmarshalPrivateKey(bts)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\n\t\t\t\tif !priv.Equals(privNew) || !privNew.Equals(priv) {\n\t\t\t\t\tt.Fatal(\"keys are not equal\")\n\t\t\t\t}\n\n\t\t\t\tmsg := []byte(\"My child, my sister,\\nThink of the rapture\\nOf living together there!\")\n\t\t\t\tsigned, err := privNew.Sign(msg)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\n\t\t\t\tok, err := privNew.GetPublic().Verify(msg, signed)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\n\t\t\t\tif !ok {\n\t\t\t\t\tt.Fatal(\"signature didn't match\")\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t})\n\n\tt.Run(\"PublicKey\", func(t *testing.T) {\n\t\tfor name, f := range map[string]func() ([]byte, error){\n\t\t\t\"Marshal\": func() ([]byte, error) {\n\t\t\t\treturn MarshalPublicKey(pub)\n\t\t\t},\n\t\t} {\n\t\t\tt.Run(name, func(t *testing.T) {\n\t\t\t\tbts, err := f()\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t\tpubNew, err := UnmarshalPublicKey(bts)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\n\t\t\t\tif !pub.Equals(pubNew) || !pubNew.Equals(pub) {\n\t\t\t\t\tt.Fatal(\"keys are not equal\")\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t})\n}\n\nfunc TestUnmarshalErrors(t *testing.T) {\n\tt.Run(\"PublicKey\", func(t *testing.T) {\n\t\tt.Run(\"Invalid data length\", func(t *testing.T) {\n\t\t\tdata, err := proto.Marshal(&pb.PublicKey{\n\t\t\t\tType: pb.KeyType_Ed25519.Enum(),\n\t\t\t\tData: []byte{42},\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif _, err := UnmarshalPublicKey(data); err == nil {\n\t\t\t\tt.Fatal(\"expected an error\")\n\t\t\t}\n\t\t})\n\t})\n\n\tt.Run(\"PrivateKey\", func(t *testing.T) {\n\t\tt.Run(\"Redundant public key mismatch\", func(t *testing.T) {\n\t\t\tpriv, _, err := GenerateEd25519Key(rand.Reader)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tdata, err := priv.Raw()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\t// Append the private key instead of the public key.\n\t\t\tdata = append(data, data[:ed25519.PublicKeySize]...)\n\n\t\t\tb, err := proto.Marshal(&pb.PrivateKey{\n\t\t\t\tType: priv.Type().Enum(),\n\t\t\t\tData: data,\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\t_, err = UnmarshalPrivateKey(b)\n\t\t\tif err == nil {\n\t\t\t\tt.Fatal(\"expected an error\")\n\t\t\t}\n\t\t\tif err.Error() != \"expected redundant ed25519 public key to be redundant\" {\n\t\t\t\tt.Fatalf(\"invalid error received: %s\", err.Error())\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"Invalid data length\", func(t *testing.T) {\n\t\t\tdata, err := proto.Marshal(&pb.PrivateKey{\n\t\t\t\tType: pb.KeyType_Ed25519.Enum(),\n\t\t\t\tData: []byte{42},\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\t_, err = UnmarshalPrivateKey(data)\n\t\t\tif err == nil {\n\t\t\t\tt.Fatal(\"expected an error\")\n\t\t\t}\n\t\t})\n\t})\n}\n"
  },
  {
    "path": "core/crypto/fixture_test.go",
    "content": "package crypto_test\n\nimport (\n\t\"bytes\"\n\t\"crypto/rand\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\tcrypto_pb \"github.com/libp2p/go-libp2p/core/crypto/pb\"\n)\n\nvar message = []byte(\"Libp2p is the _best_!\")\n\ntype testCase struct {\n\tkeyType          crypto_pb.KeyType\n\tgen              func(i io.Reader) (crypto.PrivKey, crypto.PubKey, error)\n\tsigDeterministic bool\n}\n\nvar keyTypes = []testCase{\n\t{\n\t\tkeyType: crypto_pb.KeyType_ECDSA,\n\t\tgen:     crypto.GenerateECDSAKeyPair,\n\t},\n\t{\n\t\tkeyType:          crypto_pb.KeyType_Secp256k1,\n\t\tsigDeterministic: true,\n\t\tgen:              crypto.GenerateSecp256k1Key,\n\t},\n\t{\n\t\tkeyType:          crypto_pb.KeyType_RSA,\n\t\tsigDeterministic: true,\n\t\tgen: func(i io.Reader) (crypto.PrivKey, crypto.PubKey, error) {\n\t\t\treturn crypto.GenerateRSAKeyPair(2048, i)\n\t\t},\n\t},\n}\n\nfunc fname(kt crypto_pb.KeyType, ext string) string {\n\treturn fmt.Sprintf(\"test_data/%d.%s\", kt, ext)\n}\n\nfunc TestFixtures(t *testing.T) {\n\tfor _, tc := range keyTypes {\n\t\tt.Run(tc.keyType.String(), func(t *testing.T) {\n\t\t\tpubBytes, err := os.ReadFile(fname(tc.keyType, \"pub\"))\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tprivBytes, err := os.ReadFile(fname(tc.keyType, \"priv\"))\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tsigBytes, err := os.ReadFile(fname(tc.keyType, \"sig\"))\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tpub, err := crypto.UnmarshalPublicKey(pubBytes)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tpubBytes2, err := crypto.MarshalPublicKey(pub)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif !bytes.Equal(pubBytes2, pubBytes) {\n\t\t\t\tt.Fatal(\"encoding round-trip failed\")\n\t\t\t}\n\t\t\tpriv, err := crypto.UnmarshalPrivateKey(privBytes)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tprivBytes2, err := crypto.MarshalPrivateKey(priv)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif !bytes.Equal(privBytes2, privBytes) {\n\t\t\t\tt.Fatal(\"encoding round-trip failed\")\n\t\t\t}\n\t\t\tok, err := pub.Verify(message, sigBytes)\n\t\t\tif !ok || err != nil {\n\t\t\t\tt.Fatal(\"failed to validate signature with public key\")\n\t\t\t}\n\n\t\t\tif tc.sigDeterministic {\n\t\t\t\tsigBytes2, err := priv.Sign(message)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t\tif !bytes.Equal(sigBytes2, sigBytes) {\n\t\t\t\t\tt.Fatal(\"signature not deterministic\")\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc init() {\n\t// set to true to re-generate test data\n\tif false {\n\t\tgenerate()\n\t\tpanic(\"generated\")\n\t}\n}\n\n// generate re-generates test data\nfunc generate() {\n\tfor _, tc := range keyTypes {\n\t\tpriv, pub, err := tc.gen(rand.Reader)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tpubb, err := crypto.MarshalPublicKey(pub)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tprivb, err := crypto.MarshalPrivateKey(priv)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tsig, err := priv.Sign(message)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tos.WriteFile(fname(tc.keyType, \"pub\"), pubb, 0666)\n\t\tos.WriteFile(fname(tc.keyType, \"priv\"), privb, 0666)\n\t\tos.WriteFile(fname(tc.keyType, \"sig\"), sig, 0666)\n\t}\n}\n"
  },
  {
    "path": "core/crypto/key.go",
    "content": "// Package crypto implements various cryptographic utilities used by libp2p.\n// This includes a Public and Private key interface and key implementations\n// for supported key algorithms.\npackage crypto\n\nimport (\n\t\"crypto/rand\"\n\t\"crypto/subtle\"\n\t\"encoding/base64\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto/pb\"\n\n\t\"google.golang.org/protobuf/proto\"\n)\n\nconst (\n\t// RSA is an enum for the supported RSA key type\n\tRSA = iota\n\t// Ed25519 is an enum for the supported Ed25519 key type\n\tEd25519\n\t// Secp256k1 is an enum for the supported Secp256k1 key type\n\tSecp256k1\n\t// ECDSA is an enum for the supported ECDSA key type\n\tECDSA\n)\n\nvar (\n\t// ErrBadKeyType is returned when a key is not supported\n\tErrBadKeyType = errors.New(\"invalid or unsupported key type\")\n\t// KeyTypes is a list of supported keys\n\tKeyTypes = []int{\n\t\tRSA,\n\t\tEd25519,\n\t\tSecp256k1,\n\t\tECDSA,\n\t}\n)\n\n// PubKeyUnmarshaller is a func that creates a PubKey from a given slice of bytes\ntype PubKeyUnmarshaller func(data []byte) (PubKey, error)\n\n// PrivKeyUnmarshaller is a func that creates a PrivKey from a given slice of bytes\ntype PrivKeyUnmarshaller func(data []byte) (PrivKey, error)\n\n// PubKeyUnmarshallers is a map of unmarshallers by key type\nvar PubKeyUnmarshallers = map[pb.KeyType]PubKeyUnmarshaller{\n\tpb.KeyType_RSA:       UnmarshalRsaPublicKey,\n\tpb.KeyType_Ed25519:   UnmarshalEd25519PublicKey,\n\tpb.KeyType_Secp256k1: UnmarshalSecp256k1PublicKey,\n\tpb.KeyType_ECDSA:     UnmarshalECDSAPublicKey,\n}\n\n// PrivKeyUnmarshallers is a map of unmarshallers by key type\nvar PrivKeyUnmarshallers = map[pb.KeyType]PrivKeyUnmarshaller{\n\tpb.KeyType_RSA:       UnmarshalRsaPrivateKey,\n\tpb.KeyType_Ed25519:   UnmarshalEd25519PrivateKey,\n\tpb.KeyType_Secp256k1: UnmarshalSecp256k1PrivateKey,\n\tpb.KeyType_ECDSA:     UnmarshalECDSAPrivateKey,\n}\n\n// Key represents a crypto key that can be compared to another key\ntype Key interface {\n\t// Equals checks whether two PubKeys are the same\n\tEquals(Key) bool\n\n\t// Raw returns the raw bytes of the key (not wrapped in the\n\t// libp2p-crypto protobuf).\n\t//\n\t// This function is the inverse of {Priv,Pub}KeyUnmarshaler.\n\tRaw() ([]byte, error)\n\n\t// Type returns the protobuf key type.\n\tType() pb.KeyType\n}\n\n// PrivKey represents a private key that can be used to generate a public key and sign data\ntype PrivKey interface {\n\tKey\n\n\t// Cryptographically sign the given bytes\n\tSign([]byte) ([]byte, error)\n\n\t// Return a public key paired with this private key\n\tGetPublic() PubKey\n}\n\n// PubKey is a public key that can be used to verify data signed with the corresponding private key\ntype PubKey interface {\n\tKey\n\n\t// Verify that 'sig' is the signed hash of 'data'\n\tVerify(data []byte, sig []byte) (bool, error)\n}\n\n// GenSharedKey generates the shared key from a given private key\ntype GenSharedKey func([]byte) ([]byte, error)\n\n// GenerateKeyPair generates a private and public key\nfunc GenerateKeyPair(typ, bits int) (PrivKey, PubKey, error) {\n\treturn GenerateKeyPairWithReader(typ, bits, rand.Reader)\n}\n\n// GenerateKeyPairWithReader returns a keypair of the given type and bit-size\nfunc GenerateKeyPairWithReader(typ, bits int, src io.Reader) (PrivKey, PubKey, error) {\n\tswitch typ {\n\tcase RSA:\n\t\treturn GenerateRSAKeyPair(bits, src)\n\tcase Ed25519:\n\t\treturn GenerateEd25519Key(src)\n\tcase Secp256k1:\n\t\treturn GenerateSecp256k1Key(src)\n\tcase ECDSA:\n\t\treturn GenerateECDSAKeyPair(src)\n\tdefault:\n\t\treturn nil, nil, ErrBadKeyType\n\t}\n}\n\n// UnmarshalPublicKey converts a protobuf serialized public key into its\n// representative object\nfunc UnmarshalPublicKey(data []byte) (PubKey, error) {\n\tpmes := new(pb.PublicKey)\n\terr := proto.Unmarshal(data, pmes)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn PublicKeyFromProto(pmes)\n}\n\n// PublicKeyFromProto converts an unserialized protobuf PublicKey message\n// into its representative object.\nfunc PublicKeyFromProto(pmes *pb.PublicKey) (PubKey, error) {\n\tum, ok := PubKeyUnmarshallers[pmes.GetType()]\n\tif !ok {\n\t\treturn nil, ErrBadKeyType\n\t}\n\n\tdata := pmes.GetData()\n\n\tpk, err := um(data)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch tpk := pk.(type) {\n\tcase *RsaPublicKey:\n\t\ttpk.cached, _ = proto.Marshal(pmes)\n\t}\n\n\treturn pk, nil\n}\n\n// MarshalPublicKey converts a public key object into a protobuf serialized\n// public key\nfunc MarshalPublicKey(k PubKey) ([]byte, error) {\n\tpbmes, err := PublicKeyToProto(k)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn proto.Marshal(pbmes)\n}\n\n// PublicKeyToProto converts a public key object into an unserialized\n// protobuf PublicKey message.\nfunc PublicKeyToProto(k PubKey) (*pb.PublicKey, error) {\n\tdata, err := k.Raw()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &pb.PublicKey{\n\t\tType: k.Type().Enum(),\n\t\tData: data,\n\t}, nil\n}\n\n// UnmarshalPrivateKey converts a protobuf serialized private key into its\n// representative object\nfunc UnmarshalPrivateKey(data []byte) (PrivKey, error) {\n\tpmes := new(pb.PrivateKey)\n\terr := proto.Unmarshal(data, pmes)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tum, ok := PrivKeyUnmarshallers[pmes.GetType()]\n\tif !ok {\n\t\treturn nil, ErrBadKeyType\n\t}\n\n\treturn um(pmes.GetData())\n}\n\n// MarshalPrivateKey converts a key object into its protobuf serialized form.\nfunc MarshalPrivateKey(k PrivKey) ([]byte, error) {\n\tdata, err := k.Raw()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn proto.Marshal(&pb.PrivateKey{\n\t\tType: k.Type().Enum(),\n\t\tData: data,\n\t})\n}\n\n// ConfigDecodeKey decodes from b64 (for config file) to a byte array that can be unmarshalled.\nfunc ConfigDecodeKey(b string) ([]byte, error) {\n\treturn base64.StdEncoding.DecodeString(b)\n}\n\n// ConfigEncodeKey encodes a marshalled key to b64 (for config file).\nfunc ConfigEncodeKey(b []byte) string {\n\treturn base64.StdEncoding.EncodeToString(b)\n}\n\n// KeyEqual checks whether two Keys are equivalent (have identical byte representations).\nfunc KeyEqual(k1, k2 Key) bool {\n\tif k1 == k2 {\n\t\treturn true\n\t}\n\n\treturn k1.Equals(k2)\n}\n\nfunc basicEquals(k1, k2 Key) bool {\n\tif k1.Type() != k2.Type() {\n\t\treturn false\n\t}\n\n\ta, err := k1.Raw()\n\tif err != nil {\n\t\treturn false\n\t}\n\tb, err := k2.Raw()\n\tif err != nil {\n\t\treturn false\n\t}\n\treturn subtle.ConstantTimeCompare(a, b) == 1\n}\n"
  },
  {
    "path": "core/crypto/key_test.go",
    "content": "package crypto_test\n\nimport (\n\t\"bytes\"\n\t\"crypto\"\n\t\"crypto/ecdsa\"\n\t\"crypto/ed25519\"\n\t\"crypto/elliptic\"\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"crypto/sha256\"\n\t\"crypto/x509\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"testing\"\n\n\t. \"github.com/libp2p/go-libp2p/core/crypto\"\n\tpb \"github.com/libp2p/go-libp2p/core/crypto/pb\"\n\t\"github.com/libp2p/go-libp2p/core/test\"\n\n\t\"github.com/decred/dcrd/dcrec/secp256k1/v4\"\n\tsecp256k1ecdsa \"github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa\"\n)\n\nfunc TestKeys(t *testing.T) {\n\tfor _, typ := range KeyTypes {\n\t\ttestKeyType(typ, t)\n\t}\n}\n\nfunc TestKeyPairFromKey(t *testing.T) {\n\tvar (\n\t\tdata   = []byte(`hello world`)\n\t\thashed = sha256.Sum256(data)\n\t)\n\n\tprivk, err := secp256k1.GeneratePrivateKey()\n\tif err != nil {\n\t\tt.Fatalf(\"err generating btcec priv key:\\n%v\", err)\n\t}\n\tsigK := secp256k1ecdsa.Sign(privk, hashed[:])\n\n\teKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)\n\tif err != nil {\n\t\tt.Fatalf(\"err generating ecdsa priv key:\\n%v\", err)\n\t}\n\tsigE, err := eKey.Sign(rand.Reader, hashed[:], crypto.SHA256)\n\tif err != nil {\n\t\tt.Fatalf(\"err generating ecdsa sig:\\n%v\", err)\n\t}\n\n\trKey, err := rsa.GenerateKey(rand.Reader, 2048)\n\tif err != nil {\n\t\tt.Fatalf(\"err generating rsa priv key:\\n%v\", err)\n\t}\n\tsigR, err := rKey.Sign(rand.Reader, hashed[:], crypto.SHA256)\n\tif err != nil {\n\t\tt.Fatalf(\"err generating rsa sig:\\n%v\", err)\n\t}\n\n\t_, edKey, err := ed25519.GenerateKey(rand.Reader)\n\tsigEd := ed25519.Sign(edKey, data[:])\n\tif err != nil {\n\t\tt.Fatalf(\"err generating ed25519 sig:\\n%v\", err)\n\t}\n\n\tfor i, tt := range []struct {\n\t\tin  crypto.PrivateKey\n\t\ttyp pb.KeyType\n\t\tsig []byte\n\t}{\n\t\t{\n\t\t\teKey,\n\t\t\tECDSA,\n\t\t\tsigE,\n\t\t},\n\t\t{\n\t\t\tprivk,\n\t\t\tSecp256k1,\n\t\t\tsigK.Serialize(),\n\t\t},\n\t\t{\n\t\t\trKey,\n\t\t\tRSA,\n\t\t\tsigR,\n\t\t},\n\t\t{\n\t\t\t&edKey,\n\t\t\tEd25519,\n\t\t\tsigEd,\n\t\t},\n\t} {\n\t\tt.Run(fmt.Sprintf(\"%v\", i), func(t *testing.T) {\n\t\t\tpriv, pub, err := KeyPairFromStdKey(tt.in)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tif priv == nil || pub == nil {\n\t\t\t\tt.Errorf(\"received nil private key or public key: %v, %v\", priv, pub)\n\t\t\t}\n\n\t\t\tif priv == nil || priv.Type() != tt.typ {\n\t\t\t\tt.Errorf(\"want %v; got %v\", tt.typ, priv.Type())\n\t\t\t}\n\n\t\t\tv, err := pub.Verify(data[:], tt.sig)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\n\t\t\tif !v {\n\t\t\t\tt.Error(\"signature was not verified\")\n\t\t\t}\n\n\t\t\tstdPub, err := PubKeyToStdKey(pub)\n\t\t\tif stdPub == nil {\n\t\t\t\tt.Errorf(\"err getting std public key from key: %v\", err)\n\t\t\t}\n\n\t\t\tvar stdPubBytes []byte\n\n\t\t\tswitch p := stdPub.(type) {\n\t\t\tcase *Secp256k1PublicKey:\n\t\t\t\tstdPubBytes, err = p.Raw()\n\t\t\tcase ed25519.PublicKey:\n\t\t\t\tstdPubBytes = []byte(p)\n\t\t\tdefault:\n\t\t\t\tstdPubBytes, err = x509.MarshalPKIXPublicKey(stdPub)\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Error while marshaling %v key: %v\", reflect.TypeOf(stdPub), err)\n\t\t\t}\n\n\t\t\tpubBytes, err := pub.Raw()\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"err getting raw bytes for %v key: %v\", reflect.TypeOf(pub), err)\n\t\t\t}\n\t\t\tif !bytes.Equal(stdPubBytes, pubBytes) {\n\t\t\t\tt.Errorf(\"err roundtripping %v key\", reflect.TypeOf(pub))\n\t\t\t}\n\n\t\t\tstdPriv, err := PrivKeyToStdKey(priv)\n\t\t\tif stdPub == nil {\n\t\t\t\tt.Errorf(\"err getting std private key from key: %v\", err)\n\t\t\t}\n\n\t\t\tvar stdPrivBytes []byte\n\n\t\t\tswitch p := stdPriv.(type) {\n\t\t\tcase *Secp256k1PrivateKey:\n\t\t\t\tstdPrivBytes, err = p.Raw()\n\t\t\tcase *ecdsa.PrivateKey:\n\t\t\t\tstdPrivBytes, err = x509.MarshalECPrivateKey(p)\n\t\t\tcase *ed25519.PrivateKey:\n\t\t\t\tstdPrivBytes = *p\n\t\t\tcase *rsa.PrivateKey:\n\t\t\t\tstdPrivBytes = x509.MarshalPKCS1PrivateKey(p)\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"err marshaling %v key: %v\", reflect.TypeOf(stdPriv), err)\n\t\t\t}\n\n\t\t\tprivBytes, err := priv.Raw()\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"err getting raw bytes for %v key: %v\", reflect.TypeOf(priv), err)\n\t\t\t}\n\n\t\t\tif !bytes.Equal(stdPrivBytes, privBytes) {\n\t\t\t\tt.Errorf(\"err roundtripping %v key\", reflect.TypeOf(priv))\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc testKeyType(typ int, t *testing.T) {\n\tbits := 512\n\tif typ == RSA {\n\t\tbits = 2048\n\t}\n\tsk, pk, err := test.RandTestKeyPair(typ, bits)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttestKeySignature(t, sk)\n\ttestKeyEncoding(t, sk)\n\ttestKeyEquals(t, sk)\n\ttestKeyEquals(t, pk)\n}\n\nfunc testKeySignature(t *testing.T, sk PrivKey) {\n\tpk := sk.GetPublic()\n\n\ttext := make([]byte, 16)\n\tif _, err := rand.Read(text); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tsig, err := sk.Sign(text)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tvalid, err := pk.Verify(text, sig)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif !valid {\n\t\tt.Fatal(\"Invalid signature.\")\n\t}\n}\n\nfunc testKeyEncoding(t *testing.T, sk PrivKey) {\n\tskbm, err := MarshalPrivateKey(sk)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tsk2, err := UnmarshalPrivateKey(skbm)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif !sk.Equals(sk2) {\n\t\tt.Error(\"Unmarshaled private key didn't match original.\\n\")\n\t}\n\n\tskbm2, err := MarshalPrivateKey(sk2)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif !bytes.Equal(skbm, skbm2) {\n\t\tt.Error(\"skb -> marshal -> unmarshal -> skb failed.\\n\", skbm, \"\\n\", skbm2)\n\t}\n\n\tpk := sk.GetPublic()\n\tpkbm, err := MarshalPublicKey(pk)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tpk2, err := UnmarshalPublicKey(pkbm)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif !pk.Equals(pk2) {\n\t\tt.Error(\"Unmarshaled public key didn't match original.\\n\")\n\t}\n\n\tpkbm2, err := MarshalPublicKey(pk)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif !bytes.Equal(pkbm, pkbm2) {\n\t\tt.Error(\"skb -> marshal -> unmarshal -> skb failed.\\n\", pkbm, \"\\n\", pkbm2)\n\t}\n}\n\nfunc testKeyEquals(t *testing.T, k Key) {\n\t// kb, err := k.Raw()\n\t// if err != nil {\n\t// \tt.Fatal(err)\n\t// }\n\n\tif !KeyEqual(k, k) {\n\t\tt.Fatal(\"Key not equal to itself.\")\n\t}\n\n\t// bad test, relies on deep internals..\n\t// if !KeyEqual(k, testkey(kb)) {\n\t// \tt.Fatal(\"Key not equal to key with same bytes.\")\n\t// }\n\n\tsk, pk, err := test.RandTestKeyPair(RSA, 2048)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif KeyEqual(k, sk) {\n\t\tt.Fatal(\"Keys should not equal.\")\n\t}\n\n\tif KeyEqual(k, pk) {\n\t\tt.Fatal(\"Keys should not equal.\")\n\t}\n}\n"
  },
  {
    "path": "core/crypto/key_to_stdlib.go",
    "content": "package crypto\n\nimport (\n\t\"crypto\"\n\t\"crypto/ecdsa\"\n\t\"crypto/ed25519\"\n\t\"crypto/rsa\"\n\n\t\"github.com/decred/dcrd/dcrec/secp256k1/v4\"\n)\n\n// KeyPairFromStdKey wraps standard library (and secp256k1) private keys in libp2p/go-libp2p/core/crypto keys\nfunc KeyPairFromStdKey(priv crypto.PrivateKey) (PrivKey, PubKey, error) {\n\tif priv == nil {\n\t\treturn nil, nil, ErrNilPrivateKey\n\t}\n\n\tswitch p := priv.(type) {\n\tcase *rsa.PrivateKey:\n\t\treturn &RsaPrivateKey{*p}, &RsaPublicKey{k: p.PublicKey}, nil\n\n\tcase *ecdsa.PrivateKey:\n\t\treturn &ECDSAPrivateKey{p}, &ECDSAPublicKey{&p.PublicKey}, nil\n\n\tcase *ed25519.PrivateKey:\n\t\tpubIfc := p.Public()\n\t\tpub, _ := pubIfc.(ed25519.PublicKey)\n\t\treturn &Ed25519PrivateKey{*p}, &Ed25519PublicKey{pub}, nil\n\n\tcase *secp256k1.PrivateKey:\n\t\tsPriv := Secp256k1PrivateKey(*p)\n\t\tsPub := Secp256k1PublicKey(*p.PubKey())\n\t\treturn &sPriv, &sPub, nil\n\n\tdefault:\n\t\treturn nil, nil, ErrBadKeyType\n\t}\n}\n\n// PrivKeyToStdKey converts libp2p/go-libp2p/core/crypto private keys to standard library (and secp256k1) private keys\nfunc PrivKeyToStdKey(priv PrivKey) (crypto.PrivateKey, error) {\n\tif priv == nil {\n\t\treturn nil, ErrNilPrivateKey\n\t}\n\n\tswitch p := priv.(type) {\n\tcase *RsaPrivateKey:\n\t\treturn &p.sk, nil\n\tcase *ECDSAPrivateKey:\n\t\treturn p.priv, nil\n\tcase *Ed25519PrivateKey:\n\t\treturn &p.k, nil\n\tcase *Secp256k1PrivateKey:\n\t\treturn p, nil\n\tdefault:\n\t\treturn nil, ErrBadKeyType\n\t}\n}\n\n// PubKeyToStdKey converts libp2p/go-libp2p/core/crypto private keys to standard library (and secp256k1) public keys\nfunc PubKeyToStdKey(pub PubKey) (crypto.PublicKey, error) {\n\tif pub == nil {\n\t\treturn nil, ErrNilPublicKey\n\t}\n\n\tswitch p := pub.(type) {\n\tcase *RsaPublicKey:\n\t\treturn &p.k, nil\n\tcase *ECDSAPublicKey:\n\t\treturn p.pub, nil\n\tcase *Ed25519PublicKey:\n\t\treturn p.k, nil\n\tcase *Secp256k1PublicKey:\n\t\treturn p, nil\n\tdefault:\n\t\treturn nil, ErrBadKeyType\n\t}\n}\n"
  },
  {
    "path": "core/crypto/pb/crypto.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.6\n// \tprotoc        v5.29.2\n// source: core/crypto/pb/crypto.proto\n\npackage pb\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype KeyType int32\n\nconst (\n\tKeyType_RSA       KeyType = 0\n\tKeyType_Ed25519   KeyType = 1\n\tKeyType_Secp256k1 KeyType = 2\n\tKeyType_ECDSA     KeyType = 3\n)\n\n// Enum value maps for KeyType.\nvar (\n\tKeyType_name = map[int32]string{\n\t\t0: \"RSA\",\n\t\t1: \"Ed25519\",\n\t\t2: \"Secp256k1\",\n\t\t3: \"ECDSA\",\n\t}\n\tKeyType_value = map[string]int32{\n\t\t\"RSA\":       0,\n\t\t\"Ed25519\":   1,\n\t\t\"Secp256k1\": 2,\n\t\t\"ECDSA\":     3,\n\t}\n)\n\nfunc (x KeyType) Enum() *KeyType {\n\tp := new(KeyType)\n\t*p = x\n\treturn p\n}\n\nfunc (x KeyType) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (KeyType) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_core_crypto_pb_crypto_proto_enumTypes[0].Descriptor()\n}\n\nfunc (KeyType) Type() protoreflect.EnumType {\n\treturn &file_core_crypto_pb_crypto_proto_enumTypes[0]\n}\n\nfunc (x KeyType) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Do not use.\nfunc (x *KeyType) UnmarshalJSON(b []byte) error {\n\tnum, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)\n\tif err != nil {\n\t\treturn err\n\t}\n\t*x = KeyType(num)\n\treturn nil\n}\n\n// Deprecated: Use KeyType.Descriptor instead.\nfunc (KeyType) EnumDescriptor() ([]byte, []int) {\n\treturn file_core_crypto_pb_crypto_proto_rawDescGZIP(), []int{0}\n}\n\ntype PublicKey struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tType          *KeyType               `protobuf:\"varint,1,req,name=Type,enum=crypto.pb.KeyType\" json:\"Type,omitempty\"`\n\tData          []byte                 `protobuf:\"bytes,2,req,name=Data\" json:\"Data,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *PublicKey) Reset() {\n\t*x = PublicKey{}\n\tmi := &file_core_crypto_pb_crypto_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *PublicKey) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*PublicKey) ProtoMessage() {}\n\nfunc (x *PublicKey) ProtoReflect() protoreflect.Message {\n\tmi := &file_core_crypto_pb_crypto_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use PublicKey.ProtoReflect.Descriptor instead.\nfunc (*PublicKey) Descriptor() ([]byte, []int) {\n\treturn file_core_crypto_pb_crypto_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *PublicKey) GetType() KeyType {\n\tif x != nil && x.Type != nil {\n\t\treturn *x.Type\n\t}\n\treturn KeyType_RSA\n}\n\nfunc (x *PublicKey) GetData() []byte {\n\tif x != nil {\n\t\treturn x.Data\n\t}\n\treturn nil\n}\n\ntype PrivateKey struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tType          *KeyType               `protobuf:\"varint,1,req,name=Type,enum=crypto.pb.KeyType\" json:\"Type,omitempty\"`\n\tData          []byte                 `protobuf:\"bytes,2,req,name=Data\" json:\"Data,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *PrivateKey) Reset() {\n\t*x = PrivateKey{}\n\tmi := &file_core_crypto_pb_crypto_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *PrivateKey) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*PrivateKey) ProtoMessage() {}\n\nfunc (x *PrivateKey) ProtoReflect() protoreflect.Message {\n\tmi := &file_core_crypto_pb_crypto_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use PrivateKey.ProtoReflect.Descriptor instead.\nfunc (*PrivateKey) Descriptor() ([]byte, []int) {\n\treturn file_core_crypto_pb_crypto_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *PrivateKey) GetType() KeyType {\n\tif x != nil && x.Type != nil {\n\t\treturn *x.Type\n\t}\n\treturn KeyType_RSA\n}\n\nfunc (x *PrivateKey) GetData() []byte {\n\tif x != nil {\n\t\treturn x.Data\n\t}\n\treturn nil\n}\n\nvar File_core_crypto_pb_crypto_proto protoreflect.FileDescriptor\n\nconst file_core_crypto_pb_crypto_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x1bcore/crypto/pb/crypto.proto\\x12\\tcrypto.pb\\\"G\\n\" +\n\t\"\\tPublicKey\\x12&\\n\" +\n\t\"\\x04Type\\x18\\x01 \\x02(\\x0e2\\x12.crypto.pb.KeyTypeR\\x04Type\\x12\\x12\\n\" +\n\t\"\\x04Data\\x18\\x02 \\x02(\\fR\\x04Data\\\"H\\n\" +\n\t\"\\n\" +\n\t\"PrivateKey\\x12&\\n\" +\n\t\"\\x04Type\\x18\\x01 \\x02(\\x0e2\\x12.crypto.pb.KeyTypeR\\x04Type\\x12\\x12\\n\" +\n\t\"\\x04Data\\x18\\x02 \\x02(\\fR\\x04Data*9\\n\" +\n\t\"\\aKeyType\\x12\\a\\n\" +\n\t\"\\x03RSA\\x10\\x00\\x12\\v\\n\" +\n\t\"\\aEd25519\\x10\\x01\\x12\\r\\n\" +\n\t\"\\tSecp256k1\\x10\\x02\\x12\\t\\n\" +\n\t\"\\x05ECDSA\\x10\\x03B,Z*github.com/libp2p/go-libp2p/core/crypto/pb\"\n\nvar (\n\tfile_core_crypto_pb_crypto_proto_rawDescOnce sync.Once\n\tfile_core_crypto_pb_crypto_proto_rawDescData []byte\n)\n\nfunc file_core_crypto_pb_crypto_proto_rawDescGZIP() []byte {\n\tfile_core_crypto_pb_crypto_proto_rawDescOnce.Do(func() {\n\t\tfile_core_crypto_pb_crypto_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_core_crypto_pb_crypto_proto_rawDesc), len(file_core_crypto_pb_crypto_proto_rawDesc)))\n\t})\n\treturn file_core_crypto_pb_crypto_proto_rawDescData\n}\n\nvar file_core_crypto_pb_crypto_proto_enumTypes = make([]protoimpl.EnumInfo, 1)\nvar file_core_crypto_pb_crypto_proto_msgTypes = make([]protoimpl.MessageInfo, 2)\nvar file_core_crypto_pb_crypto_proto_goTypes = []any{\n\t(KeyType)(0),       // 0: crypto.pb.KeyType\n\t(*PublicKey)(nil),  // 1: crypto.pb.PublicKey\n\t(*PrivateKey)(nil), // 2: crypto.pb.PrivateKey\n}\nvar file_core_crypto_pb_crypto_proto_depIdxs = []int32{\n\t0, // 0: crypto.pb.PublicKey.Type:type_name -> crypto.pb.KeyType\n\t0, // 1: crypto.pb.PrivateKey.Type:type_name -> crypto.pb.KeyType\n\t2, // [2:2] is the sub-list for method output_type\n\t2, // [2:2] is the sub-list for method input_type\n\t2, // [2:2] is the sub-list for extension type_name\n\t2, // [2:2] is the sub-list for extension extendee\n\t0, // [0:2] is the sub-list for field type_name\n}\n\nfunc init() { file_core_crypto_pb_crypto_proto_init() }\nfunc file_core_crypto_pb_crypto_proto_init() {\n\tif File_core_crypto_pb_crypto_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_core_crypto_pb_crypto_proto_rawDesc), len(file_core_crypto_pb_crypto_proto_rawDesc)),\n\t\t\tNumEnums:      1,\n\t\t\tNumMessages:   2,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_core_crypto_pb_crypto_proto_goTypes,\n\t\tDependencyIndexes: file_core_crypto_pb_crypto_proto_depIdxs,\n\t\tEnumInfos:         file_core_crypto_pb_crypto_proto_enumTypes,\n\t\tMessageInfos:      file_core_crypto_pb_crypto_proto_msgTypes,\n\t}.Build()\n\tFile_core_crypto_pb_crypto_proto = out.File\n\tfile_core_crypto_pb_crypto_proto_goTypes = nil\n\tfile_core_crypto_pb_crypto_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "core/crypto/pb/crypto.proto",
    "content": "syntax = \"proto2\";\n\npackage crypto.pb;\n\noption go_package = \"github.com/libp2p/go-libp2p/core/crypto/pb\";\n\nenum KeyType {\n\tRSA = 0;\n\tEd25519 = 1;\n\tSecp256k1 = 2;\n\tECDSA = 3;\n}\n\nmessage PublicKey {\n\trequired KeyType Type = 1;\n\trequired bytes Data = 2;\n}\n\nmessage PrivateKey {\n\trequired KeyType Type = 1;\n\trequired bytes Data = 2;\n}\n"
  },
  {
    "path": "core/crypto/rsa_common.go",
    "content": "package crypto\n\nimport (\n\t\"fmt\"\n\t\"os\"\n)\n\n// WeakRsaKeyEnv is an environment variable which, when set, lowers the\n// minimum required bits of RSA keys to 512. This should be used exclusively in\n// test situations.\nconst WeakRsaKeyEnv = \"LIBP2P_ALLOW_WEAK_RSA_KEYS\"\n\nvar MinRsaKeyBits = 2048\n\nvar maxRsaKeyBits = 8192\n\n// ErrRsaKeyTooSmall is returned when trying to generate or parse an RSA key\n// that's smaller than MinRsaKeyBits bits. In test\nvar ErrRsaKeyTooSmall error\nvar ErrRsaKeyTooBig error = fmt.Errorf(\"rsa keys must be <= %d bits\", maxRsaKeyBits)\n\nfunc init() {\n\tif _, ok := os.LookupEnv(WeakRsaKeyEnv); ok {\n\t\tMinRsaKeyBits = 512\n\t}\n\n\tErrRsaKeyTooSmall = fmt.Errorf(\"rsa keys must be >= %d bits to be useful\", MinRsaKeyBits)\n}\n"
  },
  {
    "path": "core/crypto/rsa_go.go",
    "content": "package crypto\n\nimport (\n\t\"crypto\"\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"crypto/sha256\"\n\t\"crypto/x509\"\n\t\"errors\"\n\t\"io\"\n\n\tpb \"github.com/libp2p/go-libp2p/core/crypto/pb\"\n\t\"github.com/libp2p/go-libp2p/core/internal/catch\"\n)\n\n// RsaPrivateKey is a rsa private key\ntype RsaPrivateKey struct {\n\tsk rsa.PrivateKey\n}\n\n// RsaPublicKey is a rsa public key\ntype RsaPublicKey struct {\n\tk rsa.PublicKey\n\n\tcached []byte\n}\n\n// GenerateRSAKeyPair generates a new rsa private and public key\nfunc GenerateRSAKeyPair(bits int, src io.Reader) (PrivKey, PubKey, error) {\n\tif bits < MinRsaKeyBits {\n\t\treturn nil, nil, ErrRsaKeyTooSmall\n\t}\n\tif bits > maxRsaKeyBits {\n\t\treturn nil, nil, ErrRsaKeyTooBig\n\t}\n\tpriv, err := rsa.GenerateKey(src, bits)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tpk := priv.PublicKey\n\treturn &RsaPrivateKey{sk: *priv}, &RsaPublicKey{k: pk}, nil\n}\n\n// Verify compares a signature against input data\nfunc (pk *RsaPublicKey) Verify(data, sig []byte) (success bool, err error) {\n\tdefer func() {\n\t\tcatch.HandlePanic(recover(), &err, \"RSA signature verification\")\n\n\t\t// To be safe\n\t\tif err != nil {\n\t\t\tsuccess = false\n\t\t}\n\t}()\n\thashed := sha256.Sum256(data)\n\terr = rsa.VerifyPKCS1v15(&pk.k, crypto.SHA256, hashed[:], sig)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treturn true, nil\n}\n\nfunc (pk *RsaPublicKey) Type() pb.KeyType {\n\treturn pb.KeyType_RSA\n}\n\nfunc (pk *RsaPublicKey) Raw() (res []byte, err error) {\n\tdefer func() { catch.HandlePanic(recover(), &err, \"RSA public-key marshaling\") }()\n\treturn x509.MarshalPKIXPublicKey(&pk.k)\n}\n\n// Equals checks whether this key is equal to another\nfunc (pk *RsaPublicKey) Equals(k Key) bool {\n\t// make sure this is a rsa public key\n\tother, ok := (k).(*RsaPublicKey)\n\tif !ok {\n\t\treturn basicEquals(pk, k)\n\t}\n\n\treturn pk.k.N.Cmp(other.k.N) == 0 && pk.k.E == other.k.E\n}\n\n// Sign returns a signature of the input data\nfunc (sk *RsaPrivateKey) Sign(message []byte) (sig []byte, err error) {\n\tdefer func() { catch.HandlePanic(recover(), &err, \"RSA signing\") }()\n\thashed := sha256.Sum256(message)\n\treturn rsa.SignPKCS1v15(rand.Reader, &sk.sk, crypto.SHA256, hashed[:])\n}\n\n// GetPublic returns a public key\nfunc (sk *RsaPrivateKey) GetPublic() PubKey {\n\treturn &RsaPublicKey{k: sk.sk.PublicKey}\n}\n\nfunc (sk *RsaPrivateKey) Type() pb.KeyType {\n\treturn pb.KeyType_RSA\n}\n\nfunc (sk *RsaPrivateKey) Raw() (res []byte, err error) {\n\tdefer func() { catch.HandlePanic(recover(), &err, \"RSA private-key marshaling\") }()\n\tb := x509.MarshalPKCS1PrivateKey(&sk.sk)\n\treturn b, nil\n}\n\n// Equals checks whether this key is equal to another\nfunc (sk *RsaPrivateKey) Equals(k Key) bool {\n\t// make sure this is a rsa public key\n\tother, ok := (k).(*RsaPrivateKey)\n\tif !ok {\n\t\treturn basicEquals(sk, k)\n\t}\n\n\ta := sk.sk\n\tb := other.sk\n\n\t// Don't care about constant time. We're only comparing the public half.\n\treturn a.PublicKey.N.Cmp(b.PublicKey.N) == 0 && a.PublicKey.E == b.PublicKey.E\n}\n\n// UnmarshalRsaPrivateKey returns a private key from the input x509 bytes\nfunc UnmarshalRsaPrivateKey(b []byte) (key PrivKey, err error) {\n\tdefer func() { catch.HandlePanic(recover(), &err, \"RSA private-key unmarshaling\") }()\n\tsk, err := x509.ParsePKCS1PrivateKey(b)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif sk.N.BitLen() < MinRsaKeyBits {\n\t\treturn nil, ErrRsaKeyTooSmall\n\t}\n\tif sk.N.BitLen() > maxRsaKeyBits {\n\t\treturn nil, ErrRsaKeyTooBig\n\t}\n\treturn &RsaPrivateKey{sk: *sk}, nil\n}\n\n// UnmarshalRsaPublicKey returns a public key from the input x509 bytes\nfunc UnmarshalRsaPublicKey(b []byte) (key PubKey, err error) {\n\tdefer func() { catch.HandlePanic(recover(), &err, \"RSA public-key unmarshaling\") }()\n\tpub, err := x509.ParsePKIXPublicKey(b)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tpk, ok := pub.(*rsa.PublicKey)\n\tif !ok {\n\t\treturn nil, errors.New(\"not actually an rsa public key\")\n\t}\n\tif pk.N.BitLen() < MinRsaKeyBits {\n\t\treturn nil, ErrRsaKeyTooSmall\n\t}\n\tif pk.N.BitLen() > maxRsaKeyBits {\n\t\treturn nil, ErrRsaKeyTooBig\n\t}\n\n\treturn &RsaPublicKey{k: *pk}, nil\n}\n"
  },
  {
    "path": "core/crypto/rsa_test.go",
    "content": "package crypto\n\nimport (\n\t\"crypto/rand\"\n\t\"testing\"\n)\n\nfunc TestRSABasicSignAndVerify(t *testing.T) {\n\tpriv, pub, err := GenerateRSAKeyPair(2048, rand.Reader)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdata := []byte(\"hello! and welcome to some awesome crypto primitives\")\n\n\tsig, err := priv.Sign(data)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tok, err := pub.Verify(data, sig)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif !ok {\n\t\tt.Fatal(\"signature didn't match\")\n\t}\n\n\t// change data\n\tdata[0] = ^data[0]\n\tok, err = pub.Verify(data, sig)\n\tif err == nil {\n\t\tt.Fatal(\"should have produced a verification error\")\n\t}\n\n\tif ok {\n\t\tt.Fatal(\"signature matched and shouldn't\")\n\t}\n}\n\nfunc TestRSASmallKey(t *testing.T) {\n\t_, _, err := GenerateRSAKeyPair(MinRsaKeyBits/2, rand.Reader)\n\tif err != ErrRsaKeyTooSmall {\n\t\tt.Fatal(\"should have refused to create small RSA key\")\n\t}\n\tMinRsaKeyBits /= 2\n\tbadPriv, badPub, err := GenerateRSAKeyPair(MinRsaKeyBits, rand.Reader)\n\tif err != nil {\n\t\tt.Fatalf(\"should have succeeded, got: %s\", err)\n\t}\n\tpubBytes, err := MarshalPublicKey(badPub)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tprivBytes, err := MarshalPrivateKey(badPriv)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tMinRsaKeyBits *= 2\n\t_, err = UnmarshalPublicKey(pubBytes)\n\tif err != ErrRsaKeyTooSmall {\n\t\tt.Fatal(\"should have refused to unmarshal a weak key\")\n\t}\n\t_, err = UnmarshalPrivateKey(privBytes)\n\tif err != ErrRsaKeyTooSmall {\n\t\tt.Fatal(\"should have refused to unmarshal a weak key\")\n\t}\n}\n\nfunc TestRSABigKeyFailsToGenerate(t *testing.T) {\n\t_, _, err := GenerateRSAKeyPair(maxRsaKeyBits*2, rand.Reader)\n\tif err != ErrRsaKeyTooBig {\n\t\tt.Fatal(\"should have refused to create too big RSA key\")\n\t}\n}\n\nfunc TestRSABigKey(t *testing.T) {\n\t// Make the global limit smaller for this test to run faster.\n\t// Note we also change the limit below, but this is different\n\torigSize := maxRsaKeyBits\n\tmaxRsaKeyBits = 2048\n\tdefer func() { maxRsaKeyBits = origSize }() //\n\n\tmaxRsaKeyBits *= 2\n\tbadPriv, badPub, err := GenerateRSAKeyPair(maxRsaKeyBits, rand.Reader)\n\tif err != nil {\n\t\tt.Fatalf(\"should have succeeded, got: %s\", err)\n\t}\n\tpubBytes, err := MarshalPublicKey(badPub)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tprivBytes, err := MarshalPrivateKey(badPriv)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tmaxRsaKeyBits /= 2\n\t_, err = UnmarshalPublicKey(pubBytes)\n\tif err != ErrRsaKeyTooBig {\n\t\tt.Fatal(\"should have refused to unmarshal a too big key\")\n\t}\n\t_, err = UnmarshalPrivateKey(privBytes)\n\tif err != ErrRsaKeyTooBig {\n\t\tt.Fatal(\"should have refused to unmarshal a too big key\")\n\t}\n}\n\nfunc TestRSASignZero(t *testing.T) {\n\tpriv, pub, err := GenerateRSAKeyPair(2048, rand.Reader)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdata := make([]byte, 0)\n\tsig, err := priv.Sign(data)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tok, err := pub.Verify(data, sig)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif !ok {\n\t\tt.Fatal(\"signature didn't match\")\n\t}\n}\n\nfunc TestRSAMarshalLoop(t *testing.T) {\n\tpriv, pub, err := GenerateRSAKeyPair(2048, rand.Reader)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tprivB, err := MarshalPrivateKey(priv)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tprivNew, err := UnmarshalPrivateKey(privB)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif !priv.Equals(privNew) || !privNew.Equals(priv) {\n\t\tt.Fatal(\"keys are not equal\")\n\t}\n\n\tpubB, err := MarshalPublicKey(pub)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tpubNew, err := UnmarshalPublicKey(pubB)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif !pub.Equals(pubNew) || !pubNew.Equals(pub) {\n\t\tt.Fatal(\"keys are not equal\")\n\t}\n}\n"
  },
  {
    "path": "core/crypto/secp256k1.go",
    "content": "package crypto\n\nimport (\n\t\"crypto/sha256\"\n\t\"fmt\"\n\t\"io\"\n\n\tpb \"github.com/libp2p/go-libp2p/core/crypto/pb\"\n\t\"github.com/libp2p/go-libp2p/core/internal/catch\"\n\n\t\"github.com/decred/dcrd/dcrec/secp256k1/v4\"\n\t\"github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa\"\n)\n\n// Secp256k1PrivateKey is a Secp256k1 private key\ntype Secp256k1PrivateKey secp256k1.PrivateKey\n\n// Secp256k1PublicKey is a Secp256k1 public key\ntype Secp256k1PublicKey secp256k1.PublicKey\n\n// GenerateSecp256k1Key generates a new Secp256k1 private and public key pair\nfunc GenerateSecp256k1Key(_ io.Reader) (PrivKey, PubKey, error) {\n\tprivk, err := secp256k1.GeneratePrivateKey()\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tk := (*Secp256k1PrivateKey)(privk)\n\treturn k, k.GetPublic(), nil\n}\n\n// UnmarshalSecp256k1PrivateKey returns a private key from bytes\nfunc UnmarshalSecp256k1PrivateKey(data []byte) (k PrivKey, err error) {\n\tif len(data) != secp256k1.PrivKeyBytesLen {\n\t\treturn nil, fmt.Errorf(\"expected secp256k1 data size to be %d\", secp256k1.PrivKeyBytesLen)\n\t}\n\tdefer func() { catch.HandlePanic(recover(), &err, \"secp256k1 private-key unmarshal\") }()\n\n\tprivk := secp256k1.PrivKeyFromBytes(data)\n\treturn (*Secp256k1PrivateKey)(privk), nil\n}\n\n// UnmarshalSecp256k1PublicKey returns a public key from bytes\nfunc UnmarshalSecp256k1PublicKey(data []byte) (_k PubKey, err error) {\n\tdefer func() { catch.HandlePanic(recover(), &err, \"secp256k1 public-key unmarshal\") }()\n\tk, err := secp256k1.ParsePubKey(data)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn (*Secp256k1PublicKey)(k), nil\n}\n\n// Type returns the private key type\nfunc (k *Secp256k1PrivateKey) Type() pb.KeyType {\n\treturn pb.KeyType_Secp256k1\n}\n\n// Raw returns the bytes of the key\nfunc (k *Secp256k1PrivateKey) Raw() ([]byte, error) {\n\treturn (*secp256k1.PrivateKey)(k).Serialize(), nil\n}\n\n// Equals compares two private keys\nfunc (k *Secp256k1PrivateKey) Equals(o Key) bool {\n\tsk, ok := o.(*Secp256k1PrivateKey)\n\tif !ok {\n\t\treturn basicEquals(k, o)\n\t}\n\n\treturn k.GetPublic().Equals(sk.GetPublic())\n}\n\n// Sign returns a signature from input data\nfunc (k *Secp256k1PrivateKey) Sign(data []byte) (_sig []byte, err error) {\n\tdefer func() { catch.HandlePanic(recover(), &err, \"secp256k1 signing\") }()\n\tkey := (*secp256k1.PrivateKey)(k)\n\thash := sha256.Sum256(data)\n\tsig := ecdsa.Sign(key, hash[:])\n\n\treturn sig.Serialize(), nil\n}\n\n// GetPublic returns a public key\nfunc (k *Secp256k1PrivateKey) GetPublic() PubKey {\n\treturn (*Secp256k1PublicKey)((*secp256k1.PrivateKey)(k).PubKey())\n}\n\n// Type returns the public key type\nfunc (k *Secp256k1PublicKey) Type() pb.KeyType {\n\treturn pb.KeyType_Secp256k1\n}\n\n// Raw returns the bytes of the key\nfunc (k *Secp256k1PublicKey) Raw() (res []byte, err error) {\n\tdefer func() { catch.HandlePanic(recover(), &err, \"secp256k1 public key marshaling\") }()\n\treturn (*secp256k1.PublicKey)(k).SerializeCompressed(), nil\n}\n\n// Equals compares two public keys\nfunc (k *Secp256k1PublicKey) Equals(o Key) bool {\n\tsk, ok := o.(*Secp256k1PublicKey)\n\tif !ok {\n\t\treturn basicEquals(k, o)\n\t}\n\n\treturn (*secp256k1.PublicKey)(k).IsEqual((*secp256k1.PublicKey)(sk))\n}\n\n// Verify compares a signature against the input data\nfunc (k *Secp256k1PublicKey) Verify(data []byte, sigStr []byte) (success bool, err error) {\n\tdefer func() {\n\t\tcatch.HandlePanic(recover(), &err, \"secp256k1 signature verification\")\n\n\t\t// To be extra safe.\n\t\tif err != nil {\n\t\t\tsuccess = false\n\t\t}\n\t}()\n\tsig, err := ecdsa.ParseDERSignature(sigStr)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\thash := sha256.Sum256(data)\n\treturn sig.Verify(hash[:], (*secp256k1.PublicKey)(k)), nil\n}\n"
  },
  {
    "path": "core/crypto/secp256k1_test.go",
    "content": "package crypto\n\nimport (\n\t\"crypto/rand\"\n\t\"testing\"\n)\n\nfunc TestSecp256k1BasicSignAndVerify(t *testing.T) {\n\tpriv, pub, err := GenerateSecp256k1Key(rand.Reader)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdata := []byte(\"hello! and welcome to some awesome crypto primitives\")\n\n\tsig, err := priv.Sign(data)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tok, err := pub.Verify(data, sig)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif !ok {\n\t\tt.Fatal(\"signature didn't match\")\n\t}\n\n\t// change data\n\tdata[0] = ^data[0]\n\tok, err = pub.Verify(data, sig)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif ok {\n\t\tt.Fatal(\"signature matched and shouldn't\")\n\t}\n}\n\nfunc TestSecp256k1SignZero(t *testing.T) {\n\tpriv, pub, err := GenerateSecp256k1Key(rand.Reader)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdata := make([]byte, 0)\n\tsig, err := priv.Sign(data)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tok, err := pub.Verify(data, sig)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif !ok {\n\t\tt.Fatal(\"signature didn't match\")\n\t}\n}\n\nfunc TestSecp256k1MarshalLoop(t *testing.T) {\n\tpriv, pub, err := GenerateSecp256k1Key(rand.Reader)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tprivB, err := MarshalPrivateKey(priv)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tprivNew, err := UnmarshalPrivateKey(privB)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif !priv.Equals(privNew) || !privNew.Equals(priv) {\n\t\tt.Fatal(\"keys are not equal\")\n\t}\n\n\tpubB, err := MarshalPublicKey(pub)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tpubNew, err := UnmarshalPublicKey(pubB)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif !pub.Equals(pubNew) || !pubNew.Equals(pub) {\n\t\tt.Fatal(\"keys are not equal\")\n\t}\n\n}\n"
  },
  {
    "path": "core/crypto/test_data/2.priv",
    "content": "\b\u0002\u0012 1A`jPLD4\u0017N[\u001f-\u0007XFX"
  },
  {
    "path": "core/crypto/test_data/2.pub",
    "content": "\b\u0002\u0012!\u00025@*5QMU&PkS\u0003\u0016֢"
  },
  {
    "path": "core/crypto/test_data/2.sig",
    "content": "0D\u0002 13Z\u0016\u0012Cu\u001aܛ@L\u0002 I!EGuCꏲpCG5I<@;Y"
  },
  {
    "path": "core/discovery/discovery.go",
    "content": "// Package discovery provides service advertisement and peer discovery interfaces for libp2p.\npackage discovery\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n)\n\n// Advertiser is an interface for advertising services\ntype Advertiser interface {\n\t// Advertise advertises a service\n\tAdvertise(ctx context.Context, ns string, opts ...Option) (time.Duration, error)\n}\n\n// Discoverer is an interface for peer discovery\ntype Discoverer interface {\n\t// FindPeers discovers peers providing a service\n\tFindPeers(ctx context.Context, ns string, opts ...Option) (<-chan peer.AddrInfo, error)\n}\n\n// Discovery is an interface that combines service advertisement and peer discovery\ntype Discovery interface {\n\tAdvertiser\n\tDiscoverer\n}\n"
  },
  {
    "path": "core/discovery/options.go",
    "content": "package discovery\n\nimport \"time\"\n\n// DiscoveryOpt is a single discovery option.\ntype Option func(opts *Options) error\n\n// DiscoveryOpts is a set of discovery options.\ntype Options struct {\n\tTtl   time.Duration\n\tLimit int\n\n\t// Other (implementation-specific) options\n\tOther map[any]any\n}\n\n// Apply applies the given options to this DiscoveryOpts\nfunc (opts *Options) Apply(options ...Option) error {\n\tfor _, o := range options {\n\t\tif err := o(opts); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// TTL is an option that provides a hint for the duration of an advertisement\nfunc TTL(ttl time.Duration) Option {\n\treturn func(opts *Options) error {\n\t\topts.Ttl = ttl\n\t\treturn nil\n\t}\n}\n\n// Limit is an option that provides an upper bound on the peer count for discovery\nfunc Limit(limit int) Option {\n\treturn func(opts *Options) error {\n\t\topts.Limit = limit\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "core/event/addrs.go",
    "content": "package event\n\nimport (\n\t\"github.com/libp2p/go-libp2p/core/record\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\n// AddrAction represents an action taken on one of a Host's listen addresses.\n// It is used to add context to address change events in EvtLocalAddressesUpdated.\ntype AddrAction int\n\nconst (\n\t// Unknown means that the event producer was unable to determine why the address\n\t// is in the current state.\n\tUnknown AddrAction = iota\n\n\t// Added means that the address is new and was not present prior to the event.\n\tAdded\n\n\t// Maintained means that the address was not altered between the current and\n\t// previous states.\n\tMaintained\n\n\t// Removed means that the address was removed from the Host.\n\tRemoved\n)\n\n// UpdatedAddress is used in the EvtLocalAddressesUpdated event to convey\n// address change information.\ntype UpdatedAddress struct {\n\t// Address contains the address that was updated.\n\tAddress ma.Multiaddr\n\n\t// Action indicates what action was taken on the address during the\n\t// event. May be Unknown if the event producer cannot produce diffs.\n\tAction AddrAction\n}\n\n// EvtLocalAddressesUpdated should be emitted when the set of listen addresses for\n// the local host changes. This may happen for a number of reasons. For example,\n// we may have opened a new relay connection, established a new NAT mapping via\n// UPnP, or been informed of our observed address by another peer.\n//\n// EvtLocalAddressesUpdated contains a snapshot of the current listen addresses,\n// and may also contain a diff between the current state and the previous state.\n// If the event producer is capable of creating a diff, the Diffs field will be\n// true, and event consumers can inspect the Action field of each UpdatedAddress\n// to see how each address was modified.\n//\n// For example, the Action will tell you whether an address in\n// the Current list was Added by the event producer, or was Maintained without\n// changes. Addresses that were removed from the Host will have the AddrAction\n// of Removed, and will be in the Removed list.\n//\n// If the event producer is not capable or producing diffs, the Diffs field will\n// be false, the Removed list will always be empty, and the Action for each\n// UpdatedAddress in the Current list will be Unknown.\n//\n// In addition to the above, EvtLocalAddressesUpdated also contains the updated peer.PeerRecord\n// for the Current set of listen addresses, wrapped in a record.Envelope and signed by the Host's private key.\n// This record can be shared with other peers to inform them of what we believe are our diallable addresses\n// a secure and authenticated way.\ntype EvtLocalAddressesUpdated struct {\n\n\t// Diffs indicates whether this event contains a diff of the Host's previous\n\t// address set.\n\tDiffs bool\n\n\t// Current contains all current listen addresses for the Host.\n\t// If Diffs == true, the Action field of each UpdatedAddress will tell\n\t// you whether an address was Added, or was Maintained from the previous\n\t// state.\n\tCurrent []UpdatedAddress\n\n\t// Removed contains addresses that were removed from the Host.\n\t// This field is only set when Diffs == true.\n\tRemoved []UpdatedAddress\n\n\t// SignedPeerRecord contains our own updated peer.PeerRecord, listing the addresses enumerated in Current.\n\t// wrapped in a record.Envelope and signed by the Host's private key.\n\tSignedPeerRecord *record.Envelope\n}\n\n// EvtAutoRelayAddrsUpdated is sent by the autorelay when the node's relay addresses are updated\ntype EvtAutoRelayAddrsUpdated struct {\n\tRelayAddrs []ma.Multiaddr\n}\n"
  },
  {
    "path": "core/event/bus.go",
    "content": "package event\n\nimport (\n\t\"io\"\n\t\"reflect\"\n)\n\n// SubscriptionOpt represents a subscriber option. Use the options exposed by the implementation of choice.\ntype SubscriptionOpt = func(any) error\n\n// EmitterOpt represents an emitter option. Use the options exposed by the implementation of choice.\ntype EmitterOpt = func(any) error\n\n// CancelFunc closes a subscriber.\ntype CancelFunc = func()\n\n// wildcardSubscriptionType is a virtual type to represent wildcard\n// subscriptions.\ntype wildcardSubscriptionType any\n\n// WildcardSubscription is the type to subscribe to receive all events\n// emitted in the eventbus.\nvar WildcardSubscription = new(wildcardSubscriptionType)\n\n// Emitter represents an actor that emits events onto the eventbus.\ntype Emitter interface {\n\tio.Closer\n\n\t// Emit emits an event onto the eventbus. If any channel subscribed to the topic is blocked,\n\t// calls to Emit will block.\n\t//\n\t// Calling this function with wrong event type will cause a panic.\n\tEmit(evt any) error\n}\n\n// Subscription represents a subscription to one or multiple event types.\ntype Subscription interface {\n\tio.Closer\n\n\t// Out returns the channel from which to consume events.\n\tOut() <-chan any\n\n\t// Name returns the name for the subscription\n\tName() string\n}\n\n// Bus is an interface for a type-based event delivery system.\ntype Bus interface {\n\t// Subscribe creates a new Subscription.\n\t//\n\t// eventType can be either a pointer to a single event type, or a slice of pointers to\n\t// subscribe to multiple event types at once, under a single subscription (and channel).\n\t//\n\t// Failing to drain the channel may cause publishers to block.\n\t//\n\t// If you want to subscribe to ALL events emitted in the bus, use\n\t// `WildcardSubscription` as the `eventType`:\n\t//\n\t//  eventbus.Subscribe(WildcardSubscription)\n\t//\n\t// Simple example\n\t//\n\t//  sub, err := eventbus.Subscribe(new(EventType))\n\t//  defer sub.Close()\n\t//  for e := range sub.Out() {\n\t//    event := e.(EventType) // guaranteed safe\n\t//    [...]\n\t//  }\n\t//\n\t// Multi-type example\n\t//\n\t//  sub, err := eventbus.Subscribe([]interface{}{new(EventA), new(EventB)})\n\t//  defer sub.Close()\n\t//  for e := range sub.Out() {\n\t//    select e.(type):\n\t//      case EventA:\n\t//        [...]\n\t//      case EventB:\n\t//        [...]\n\t//    }\n\t//  }\n\tSubscribe(eventType any, opts ...SubscriptionOpt) (Subscription, error)\n\n\t// Emitter creates a new event emitter.\n\t//\n\t// eventType accepts typed nil pointers, and uses the type information for wiring purposes.\n\t//\n\t// Example:\n\t//  em, err := eventbus.Emitter(new(EventT))\n\t//  defer em.Close() // MUST call this after being done with the emitter\n\t//  em.Emit(EventT{})\n\tEmitter(eventType any, opts ...EmitterOpt) (Emitter, error)\n\n\t// GetAllEventTypes returns all the event types that this bus knows about\n\t// (having emitters and subscribers). It omits the WildcardSubscription.\n\t//\n\t// The caller is guaranteed that this function will only return value types;\n\t// no pointer types will be returned.\n\tGetAllEventTypes() []reflect.Type\n}\n"
  },
  {
    "path": "core/event/dht.go",
    "content": "package event\n\n// RawJSON is a type that contains a raw JSON string.\ntype RawJSON string\n\n// GenericDHTEvent is a type that encapsulates an actual DHT event by carrying\n// its raw JSON.\n//\n// Context: the DHT event system is rather bespoke and a bit messy at the time,\n// so until we unify/clean that up, this event bridges the gap. It should only\n// be consumed for informational purposes.\n//\n// EXPERIMENTAL: this will likely be removed if/when the DHT event types are\n// hoisted to core, and the DHT event system is reconciled with the eventbus.\ntype GenericDHTEvent struct {\n\t// Type is the type of the DHT event that occurred.\n\tType string\n\n\t// Raw is the raw JSON representation of the event payload.\n\tRaw RawJSON\n}\n"
  },
  {
    "path": "core/event/doc.go",
    "content": "// Package event contains the abstractions for a local event bus, along with the standard events\n// that libp2p subsystems may emit.\n//\n// Source code is arranged as follows:\n//   - doc.go: this file.\n//   - bus.go: abstractions for the event bus.\n//   - rest: event structs, sensibly categorised in files by entity, and following this naming convention:\n//     Evt[Entity (noun)][Event (verb past tense / gerund)]\n//     The past tense is used to convey that something happened, whereas the gerund form of the verb (-ing)\n//     expresses that a process is in progress. Examples: EvtConnEstablishing, EvtConnEstablished.\npackage event\n"
  },
  {
    "path": "core/event/identify.go",
    "content": "package event\n\nimport (\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\t\"github.com/libp2p/go-libp2p/core/record\"\n\t\"github.com/multiformats/go-multiaddr\"\n)\n\n// EvtPeerIdentificationCompleted is emitted when the initial identification round for a peer is completed.\ntype EvtPeerIdentificationCompleted struct {\n\t// Peer is the ID of the peer whose identification succeeded.\n\tPeer peer.ID\n\n\t// Conn is the connection we identified.\n\tConn network.Conn\n\n\t// ListenAddrs is the list of addresses the peer is listening on.\n\tListenAddrs []multiaddr.Multiaddr\n\n\t// Protocols is the list of protocols the peer advertised on this connection.\n\tProtocols []protocol.ID\n\n\t// SignedPeerRecord is the provided signed peer record of the peer. May be nil.\n\tSignedPeerRecord *record.Envelope\n\n\t// AgentVersion is like a UserAgent string in browsers, or client version in\n\t// bittorrent includes the client name and client.\n\tAgentVersion string\n\n\t// ProtocolVersion is the protocolVersion field in the identify message\n\tProtocolVersion string\n\n\t// ObservedAddr is the our side's connection address as observed by the\n\t// peer. This is not verified, the peer could return anything here.\n\tObservedAddr multiaddr.Multiaddr\n}\n\n// EvtPeerIdentificationFailed is emitted when the initial identification round for a peer failed.\ntype EvtPeerIdentificationFailed struct {\n\t// Peer is the ID of the peer whose identification failed.\n\tPeer peer.ID\n\t// Reason is the reason why identification failed.\n\tReason error\n}\n"
  },
  {
    "path": "core/event/nattype.go",
    "content": "package event\n\nimport \"github.com/libp2p/go-libp2p/core/network\"\n\n// EvtNATDeviceTypeChanged is an event struct to be emitted when the type of the NAT device changes for a Transport Protocol.\n//\n// Note: This event is meaningful ONLY if the AutoNAT Reachability is Private.\n// Consumers of this event should ALSO consume the `EvtLocalReachabilityChanged` event and interpret\n// this event ONLY if the Reachability on the `EvtLocalReachabilityChanged` is Private.\ntype EvtNATDeviceTypeChanged struct {\n\t// TransportProtocol is the Transport Protocol for which the NAT Device Type has been determined.\n\tTransportProtocol network.NATTransportProtocol\n\t// NatDeviceType indicates the type of the NAT Device for the Transport Protocol.\n\t// Currently, it can be either a `EndpointIndependent NAT` or a `EndpointDependent NAT`. Please see the detailed documentation\n\t// on `network.NATDeviceType` enumerations for a better understanding of what these types mean and\n\t// how they impact Connectivity and Hole Punching.\n\tNatDeviceType network.NATDeviceType\n}\n"
  },
  {
    "path": "core/event/network.go",
    "content": "package event\n\nimport (\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n)\n\n// EvtPeerConnectednessChanged should be emitted every time the \"connectedness\" to a\n// given peer changes. Specifically, this event is emitted in the following\n// cases:\n//\n//   - Connectedness = Connected: Every time we transition from having no\n//     connections to a peer to having at least one connection to the peer.\n//   - Connectedness = NotConnected: Every time we transition from having at least\n//     one connection to a peer to having no connections to the peer.\n//\n// Additional connectedness states may be added in the future. This list should\n// not be considered exhaustive.\n//\n// Take note:\n//\n//   - It's possible to have _multiple_ connections to a given peer.\n//   - Both libp2p and networks are asynchronous.\n//\n// This means that all the following situations are possible:\n//\n// A connection is cut and is re-established:\n//\n//   - Peer A observes a transition from Connected -> NotConnected -> Connected\n//   - Peer B observes a transition from Connected -> NotConnected -> Connected\n//\n// Explanation: Both peers observe the connection die. This is the \"nice\" case.\n//\n// A connection is cut and is re-established.\n//\n//   - Peer A observes a transition from Connected -> NotConnected -> Connected.\n//   - Peer B observes no transition.\n//\n// Explanation: Peer A re-establishes the dead connection. Peer B observes the\n// new connection form before it observes the old connection die.\n//\n// A connection is cut:\n//\n//   - Peer A observes no transition.\n//   - Peer B observes no transition.\n//\n// Explanation: There were two connections and one was cut. This connection\n// might have been in active use but neither peer will observe a change in\n// \"connectedness\". Peers should always make sure to retry network requests.\ntype EvtPeerConnectednessChanged struct {\n\t// Peer is the remote peer whose connectedness has changed.\n\tPeer peer.ID\n\t// Connectedness is the new connectedness state.\n\tConnectedness network.Connectedness\n}\n"
  },
  {
    "path": "core/event/protocol.go",
    "content": "package event\n\nimport (\n\tpeer \"github.com/libp2p/go-libp2p/core/peer\"\n\tprotocol \"github.com/libp2p/go-libp2p/core/protocol\"\n)\n\n// EvtPeerProtocolsUpdated should be emitted when a peer we're connected to adds or removes protocols from their stack.\ntype EvtPeerProtocolsUpdated struct {\n\t// Peer is the peer whose protocols were updated.\n\tPeer peer.ID\n\t// Added enumerates the protocols that were added by this peer.\n\tAdded []protocol.ID\n\t// Removed enumerates the protocols that were removed by this peer.\n\tRemoved []protocol.ID\n}\n\n// EvtLocalProtocolsUpdated should be emitted when stream handlers are attached or detached from the local host.\n// For handlers attached with a matcher predicate (host.SetStreamHandlerMatch()), only the protocol ID will be\n// included in this event.\ntype EvtLocalProtocolsUpdated struct {\n\t// Added enumerates the protocols that were added locally.\n\tAdded []protocol.ID\n\t// Removed enumerates the protocols that were removed locally.\n\tRemoved []protocol.ID\n}\n"
  },
  {
    "path": "core/event/reachability.go",
    "content": "package event\n\nimport (\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\n// EvtLocalReachabilityChanged is an event struct to be emitted when the local's\n// node reachability changes state.\n//\n// This event is usually emitted by the AutoNAT subsystem.\ntype EvtLocalReachabilityChanged struct {\n\tReachability network.Reachability\n}\n\n// EvtHostReachableAddrsChanged is sent when host's reachable or unreachable addresses change\n// Reachable, Unreachable, and Unknown only contain Public IP or DNS addresses\n//\n// Experimental: This API is unstable. Any changes to this event will be done without a deprecation notice.\ntype EvtHostReachableAddrsChanged struct {\n\tReachable   []ma.Multiaddr\n\tUnreachable []ma.Multiaddr\n\tUnknown     []ma.Multiaddr\n}\n"
  },
  {
    "path": "core/host/helpers.go",
    "content": "package host\n\nimport \"github.com/libp2p/go-libp2p/core/peer\"\n\n// InfoFromHost returns a peer.AddrInfo struct with the Host's ID and all of its Addrs.\nfunc InfoFromHost(h Host) *peer.AddrInfo {\n\treturn &peer.AddrInfo{\n\t\tID:    h.ID(),\n\t\tAddrs: h.Addrs(),\n\t}\n}\n"
  },
  {
    "path": "core/host/host.go",
    "content": "// Package host provides the core Host interface for libp2p.\n//\n// Host represents a single libp2p node in a peer-to-peer network.\npackage host\n\nimport (\n\t\"context\"\n\n\t\"github.com/libp2p/go-libp2p/core/connmgr\"\n\t\"github.com/libp2p/go-libp2p/core/event\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\n// Host is an object participating in a p2p network, which\n// implements protocols or provides services. It handles\n// requests like a Server, and issues requests like a Client.\n// It is called Host because it is both Server and Client (and Peer\n// may be confusing).\ntype Host interface {\n\t// ID returns the (local) peer.ID associated with this Host\n\tID() peer.ID\n\n\t// Peerstore returns the Host's repository of Peer Addresses and Keys.\n\tPeerstore() peerstore.Peerstore\n\n\t// Addrs returns the listen addresses of the Host\n\tAddrs() []ma.Multiaddr\n\n\t// Network returns the Network interface of the Host\n\tNetwork() network.Network\n\n\t// Mux returns the Mux multiplexing incoming streams to protocol handlers\n\tMux() protocol.Switch\n\n\t// Connect ensures there is a connection between this host and the peer with\n\t// given peer.ID. Connect will absorb the addresses in pi into its internal\n\t// peerstore. If there is not an active connection, Connect will issue a\n\t// h.Network.Dial, and block until a connection is open, or an error is\n\t// returned.\n\tConnect(ctx context.Context, pi peer.AddrInfo) error\n\n\t// SetStreamHandler sets the protocol handler on the Host's Mux.\n\t// This is equivalent to:\n\t//   host.Mux().SetHandler(proto, handler)\n\t// (Thread-safe)\n\tSetStreamHandler(pid protocol.ID, handler network.StreamHandler)\n\n\t// SetStreamHandlerMatch sets the protocol handler on the Host's Mux\n\t// using a matching function for protocol selection.\n\tSetStreamHandlerMatch(protocol.ID, func(protocol.ID) bool, network.StreamHandler)\n\n\t// RemoveStreamHandler removes a handler on the mux that was set by\n\t// SetStreamHandler\n\tRemoveStreamHandler(pid protocol.ID)\n\n\t// NewStream opens a new stream to given peer p, and writes a p2p/protocol\n\t// header with given ProtocolID. If there is no connection to p, attempts\n\t// to create one. If ProtocolID is \"\", writes no header.\n\t// (Thread-safe)\n\tNewStream(ctx context.Context, p peer.ID, pids ...protocol.ID) (network.Stream, error)\n\n\t// Close shuts down the host, its Network, and services.\n\tClose() error\n\n\t// ConnManager returns this hosts connection manager\n\tConnManager() connmgr.ConnManager\n\n\t// EventBus returns the hosts eventbus\n\tEventBus() event.Bus\n}\n"
  },
  {
    "path": "core/internal/catch/catch.go",
    "content": "package catch\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"runtime/debug\"\n)\n\nvar panicWriter io.Writer = os.Stderr\n\n// HandlePanic handles and logs panics.\nfunc HandlePanic(rerr any, err *error, where string) {\n\tif rerr != nil {\n\t\tfmt.Fprintf(panicWriter, \"caught panic: %s\\n%s\\n\", rerr, debug.Stack())\n\t\t*err = fmt.Errorf(\"panic in %s: %s\", where, rerr)\n\t}\n}\n"
  },
  {
    "path": "core/internal/catch/catch_test.go",
    "content": "package catch\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestCatch(t *testing.T) {\n\tbuf := new(bytes.Buffer)\n\n\toldPanicWriter := panicWriter\n\tt.Cleanup(func() { panicWriter = oldPanicWriter })\n\tpanicWriter = buf\n\n\tpanicAndCatch := func() (err error) {\n\t\tdefer func() { HandlePanic(recover(), &err, \"somewhere\") }()\n\n\t\tpanic(\"here\")\n\t}\n\n\terr := panicAndCatch()\n\trequire.Error(t, err)\n\trequire.Contains(t, err.Error(), \"panic in somewhere: here\")\n\n\trequire.Contains(t, buf.String(), \"caught panic: here\")\n}\n"
  },
  {
    "path": "core/metrics/bandwidth.go",
    "content": "// Package metrics provides metrics collection and reporting interfaces for libp2p.\npackage metrics\n\nimport (\n\t\"time\"\n\n\t\"github.com/libp2p/go-flow-metrics\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n)\n\n// BandwidthCounter tracks incoming and outgoing data transferred by the local peer.\n// Metrics are available for total bandwidth across all peers / protocols, as well\n// as segmented by remote peer ID and protocol ID.\ntype BandwidthCounter struct {\n\ttotalIn  flow.Meter\n\ttotalOut flow.Meter\n\n\tprotocolIn  flow.MeterRegistry\n\tprotocolOut flow.MeterRegistry\n\n\tpeerIn  flow.MeterRegistry\n\tpeerOut flow.MeterRegistry\n}\n\n// NewBandwidthCounter creates a new BandwidthCounter.\nfunc NewBandwidthCounter() *BandwidthCounter {\n\treturn new(BandwidthCounter)\n}\n\n// LogSentMessage records the size of an outgoing message\n// without associating the bandwidth to a specific peer or protocol.\nfunc (bwc *BandwidthCounter) LogSentMessage(size int64) {\n\tbwc.totalOut.Mark(uint64(size))\n}\n\n// LogRecvMessage records the size of an incoming message\n// without associating the bandwidth to a specific peer or protocol.\nfunc (bwc *BandwidthCounter) LogRecvMessage(size int64) {\n\tbwc.totalIn.Mark(uint64(size))\n}\n\n// LogSentMessageStream records the size of an outgoing message over a single logical stream.\n// Bandwidth is associated with the given protocol.ID and peer.ID.\nfunc (bwc *BandwidthCounter) LogSentMessageStream(size int64, proto protocol.ID, p peer.ID) {\n\tbwc.protocolOut.Get(string(proto)).Mark(uint64(size))\n\tbwc.peerOut.Get(string(p)).Mark(uint64(size))\n}\n\n// LogRecvMessageStream records the size of an incoming message over a single logical stream.\n// Bandwidth is associated with the given protocol.ID and peer.ID.\nfunc (bwc *BandwidthCounter) LogRecvMessageStream(size int64, proto protocol.ID, p peer.ID) {\n\tbwc.protocolIn.Get(string(proto)).Mark(uint64(size))\n\tbwc.peerIn.Get(string(p)).Mark(uint64(size))\n}\n\n// GetBandwidthForPeer returns a Stats struct with bandwidth metrics associated with the given peer.ID.\n// The metrics returned include all traffic sent / received for the peer, regardless of protocol.\nfunc (bwc *BandwidthCounter) GetBandwidthForPeer(p peer.ID) (out Stats) {\n\tinSnap := bwc.peerIn.Get(string(p)).Snapshot()\n\toutSnap := bwc.peerOut.Get(string(p)).Snapshot()\n\n\treturn Stats{\n\t\tTotalIn:  int64(inSnap.Total),\n\t\tTotalOut: int64(outSnap.Total),\n\t\tRateIn:   inSnap.Rate,\n\t\tRateOut:  outSnap.Rate,\n\t}\n}\n\n// GetBandwidthForProtocol returns a Stats struct with bandwidth metrics associated with the given protocol.ID.\n// The metrics returned include all traffic sent / received for the protocol, regardless of which peers were\n// involved.\nfunc (bwc *BandwidthCounter) GetBandwidthForProtocol(proto protocol.ID) (out Stats) {\n\tinSnap := bwc.protocolIn.Get(string(proto)).Snapshot()\n\toutSnap := bwc.protocolOut.Get(string(proto)).Snapshot()\n\n\treturn Stats{\n\t\tTotalIn:  int64(inSnap.Total),\n\t\tTotalOut: int64(outSnap.Total),\n\t\tRateIn:   inSnap.Rate,\n\t\tRateOut:  outSnap.Rate,\n\t}\n}\n\n// GetBandwidthTotals returns a Stats struct with bandwidth metrics for all data sent / received by the\n// local peer, regardless of protocol or remote peer IDs.\nfunc (bwc *BandwidthCounter) GetBandwidthTotals() (out Stats) {\n\tinSnap := bwc.totalIn.Snapshot()\n\toutSnap := bwc.totalOut.Snapshot()\n\n\treturn Stats{\n\t\tTotalIn:  int64(inSnap.Total),\n\t\tTotalOut: int64(outSnap.Total),\n\t\tRateIn:   inSnap.Rate,\n\t\tRateOut:  outSnap.Rate,\n\t}\n}\n\n// GetBandwidthByPeer returns a map of all remembered peers and the bandwidth\n// metrics with respect to each. This method may be very expensive.\nfunc (bwc *BandwidthCounter) GetBandwidthByPeer() map[peer.ID]Stats {\n\tpeers := make(map[peer.ID]Stats)\n\n\tbwc.peerIn.ForEach(func(p string, meter *flow.Meter) {\n\t\tid := peer.ID(p)\n\t\tsnap := meter.Snapshot()\n\n\t\tstat := peers[id]\n\t\tstat.TotalIn = int64(snap.Total)\n\t\tstat.RateIn = snap.Rate\n\t\tpeers[id] = stat\n\t})\n\n\tbwc.peerOut.ForEach(func(p string, meter *flow.Meter) {\n\t\tid := peer.ID(p)\n\t\tsnap := meter.Snapshot()\n\n\t\tstat := peers[id]\n\t\tstat.TotalOut = int64(snap.Total)\n\t\tstat.RateOut = snap.Rate\n\t\tpeers[id] = stat\n\t})\n\n\treturn peers\n}\n\n// GetBandwidthByProtocol returns a map of all remembered protocols and\n// the bandwidth metrics with respect to each. This method may be moderately\n// expensive.\nfunc (bwc *BandwidthCounter) GetBandwidthByProtocol() map[protocol.ID]Stats {\n\tprotocols := make(map[protocol.ID]Stats)\n\n\tbwc.protocolIn.ForEach(func(p string, meter *flow.Meter) {\n\t\tid := protocol.ID(p)\n\t\tsnap := meter.Snapshot()\n\n\t\tstat := protocols[id]\n\t\tstat.TotalIn = int64(snap.Total)\n\t\tstat.RateIn = snap.Rate\n\t\tprotocols[id] = stat\n\t})\n\n\tbwc.protocolOut.ForEach(func(p string, meter *flow.Meter) {\n\t\tid := protocol.ID(p)\n\t\tsnap := meter.Snapshot()\n\n\t\tstat := protocols[id]\n\t\tstat.TotalOut = int64(snap.Total)\n\t\tstat.RateOut = snap.Rate\n\t\tprotocols[id] = stat\n\t})\n\n\treturn protocols\n}\n\n// Reset clears all stats.\nfunc (bwc *BandwidthCounter) Reset() {\n\tbwc.totalIn.Reset()\n\tbwc.totalOut.Reset()\n\n\tbwc.protocolIn.Clear()\n\tbwc.protocolOut.Clear()\n\n\tbwc.peerIn.Clear()\n\tbwc.peerOut.Clear()\n}\n\n// TrimIdle trims all timers idle since the given time.\nfunc (bwc *BandwidthCounter) TrimIdle(since time.Time) {\n\tbwc.peerIn.TrimIdle(since)\n\tbwc.peerOut.TrimIdle(since)\n\tbwc.protocolIn.TrimIdle(since)\n\tbwc.protocolOut.TrimIdle(since)\n}\n"
  },
  {
    "path": "core/metrics/bandwidth_test.go",
    "content": "package metrics\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\n\t\"github.com/libp2p/go-flow-metrics\"\n\n\t\"github.com/benbjohnson/clock\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nvar cl = clock.NewMock()\n\nfunc init() {\n\tflow.SetClock(cl)\n}\n\nfunc BenchmarkBandwidthCounter(b *testing.B) {\n\tb.StopTimer()\n\tb.ResetTimer()\n\n\tfor i := 0; i < b.N; i++ {\n\t\tbwc := NewBandwidthCounter()\n\t\tround(bwc, b)\n\t}\n}\n\nfunc round(bwc *BandwidthCounter, b *testing.B) {\n\tstart := make(chan struct{})\n\tvar wg sync.WaitGroup\n\twg.Add(10000)\n\tfor i := range 1000 {\n\t\tp := peer.ID(fmt.Sprintf(\"peer-%d\", i))\n\t\tfor j := range 10 {\n\t\t\tproto := protocol.ID(fmt.Sprintf(\"bitswap-%d\", j))\n\t\t\tgo func() {\n\t\t\t\tdefer wg.Done()\n\t\t\t\t<-start\n\n\t\t\t\tfor range 1000 {\n\t\t\t\t\tbwc.LogSentMessage(100)\n\t\t\t\t\tbwc.LogSentMessageStream(100, proto, p)\n\t\t\t\t\ttime.Sleep(1 * time.Millisecond)\n\t\t\t\t}\n\t\t\t}()\n\t\t}\n\t}\n\n\tb.StartTimer()\n\tclose(start)\n\twg.Wait()\n\tb.StopTimer()\n}\n\nfunc TestBandwidthCounter(t *testing.T) {\n\tbwc := NewBandwidthCounter()\n\tfor range 40 {\n\t\tfor i := range 100 {\n\t\t\tp := peer.ID(fmt.Sprintf(\"peer-%d\", i))\n\t\t\tfor j := range 2 {\n\t\t\t\tproto := protocol.ID(fmt.Sprintf(\"proto-%d\", j))\n\n\t\t\t\t// make sure the bandwidth counters are active\n\t\t\t\tbwc.LogSentMessage(100)\n\t\t\t\tbwc.LogRecvMessage(50)\n\t\t\t\tbwc.LogSentMessageStream(100, proto, p)\n\t\t\t\tbwc.LogRecvMessageStream(50, proto, p)\n\n\t\t\t\t// <-start\n\t\t\t}\n\t\t}\n\t\tcl.Add(100 * time.Millisecond)\n\t}\n\n\tassertProtocols := func(check func(Stats)) {\n\t\tbyProtocol := bwc.GetBandwidthByProtocol()\n\t\trequire.Len(t, byProtocol, 2, \"expected 2 protocols\")\n\t\tfor i := range 2 {\n\t\t\tp := protocol.ID(fmt.Sprintf(\"proto-%d\", i))\n\t\t\tfor _, stats := range [...]Stats{bwc.GetBandwidthForProtocol(p), byProtocol[p]} {\n\t\t\t\tcheck(stats)\n\t\t\t}\n\t\t}\n\t}\n\n\tassertPeers := func(check func(Stats)) {\n\t\tbyPeer := bwc.GetBandwidthByPeer()\n\t\trequire.Len(t, byPeer, 100, \"expected 100 peers\")\n\t\tfor i := range 100 {\n\t\t\tp := peer.ID(fmt.Sprintf(\"peer-%d\", i))\n\t\t\tfor _, stats := range [...]Stats{bwc.GetBandwidthForPeer(p), byPeer[p]} {\n\t\t\t\tcheck(stats)\n\t\t\t}\n\t\t}\n\t}\n\n\tassertPeers(func(stats Stats) {\n\t\trequire.Equal(t, int64(8000), stats.TotalOut)\n\t\trequire.Equal(t, int64(4000), stats.TotalIn)\n\t})\n\n\tassertProtocols(func(stats Stats) {\n\t\trequire.Equal(t, int64(400000), stats.TotalOut)\n\t\trequire.Equal(t, int64(200000), stats.TotalIn)\n\t})\n\n\tstats := bwc.GetBandwidthTotals()\n\trequire.Equal(t, int64(800000), stats.TotalOut)\n\trequire.Equal(t, int64(400000), stats.TotalIn)\n}\n\nfunc TestResetBandwidthCounter(t *testing.T) {\n\tbwc := NewBandwidthCounter()\n\n\tp := peer.ID(\"peer-0\")\n\tproto := protocol.ID(\"proto-0\")\n\n\t// We don't calculate bandwidth till we've been active for a second.\n\tbwc.LogSentMessage(42)\n\tbwc.LogRecvMessage(24)\n\tbwc.LogSentMessageStream(100, proto, p)\n\tbwc.LogRecvMessageStream(50, proto, p)\n\n\ttime.Sleep(200 * time.Millisecond) // make sure the meters are registered with the sweeper\n\tcl.Add(time.Second)\n\n\tbwc.LogSentMessage(42)\n\tbwc.LogRecvMessage(24)\n\tbwc.LogSentMessageStream(100, proto, p)\n\tbwc.LogRecvMessageStream(50, proto, p)\n\n\tcl.Add(time.Second)\n\n\t{\n\t\tstats := bwc.GetBandwidthTotals()\n\t\trequire.Equal(t, int64(84), stats.TotalOut)\n\t\trequire.Equal(t, int64(48), stats.TotalIn)\n\t}\n\n\t{\n\t\tstats := bwc.GetBandwidthByProtocol()\n\t\trequire.Len(t, stats, 1)\n\t\tstat := stats[proto]\n\t\trequire.Equal(t, float64(100), stat.RateOut)\n\t\trequire.Equal(t, float64(50), stat.RateIn)\n\t}\n\n\t{\n\t\tstats := bwc.GetBandwidthByPeer()\n\t\trequire.Len(t, stats, 1)\n\t\tstat := stats[p]\n\t\trequire.Equal(t, float64(100), stat.RateOut)\n\t\trequire.Equal(t, float64(50), stat.RateIn)\n\t}\n\n\tbwc.Reset()\n\t{\n\t\tstats := bwc.GetBandwidthTotals()\n\t\trequire.Zero(t, stats.TotalOut)\n\t\trequire.Zero(t, stats.TotalIn)\n\t\trequire.Empty(t, bwc.GetBandwidthByProtocol(), \"expected 0 protocols\")\n\t\trequire.Empty(t, bwc.GetBandwidthByPeer(), \"expected 0 peers\")\n\t}\n}\n"
  },
  {
    "path": "core/metrics/reporter.go",
    "content": "// Package metrics provides metrics collection and reporting interfaces for libp2p.\npackage metrics\n\nimport (\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\t\"time\"\n)\n\n// Stats represents a point-in-time snapshot of bandwidth metrics.\n//\n// The TotalIn and TotalOut fields record cumulative bytes sent / received.\n// The RateIn and RateOut fields record bytes sent / received per second.\ntype Stats struct {\n\tTotalIn  int64\n\tTotalOut int64\n\tRateIn   float64\n\tRateOut  float64\n}\n\n// Reporter provides methods for logging and retrieving metrics.\ntype Reporter interface {\n\tLogSentMessage(int64)\n\tLogRecvMessage(int64)\n\tLogSentMessageStream(int64, protocol.ID, peer.ID)\n\tLogRecvMessageStream(int64, protocol.ID, peer.ID)\n\tGetBandwidthForPeer(peer.ID) Stats\n\tGetBandwidthForProtocol(protocol.ID) Stats\n\tGetBandwidthTotals() Stats\n\tGetBandwidthByPeer() map[peer.ID]Stats\n\tGetBandwidthByProtocol() map[protocol.ID]Stats\n\tReset()\n\tTrimIdle(since time.Time)\n}\n"
  },
  {
    "path": "core/network/conn.go",
    "content": "package network\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\tic \"github.com/libp2p/go-libp2p/core/crypto\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\ntype ConnErrorCode uint32\n\ntype ConnError struct {\n\tRemote         bool\n\tErrorCode      ConnErrorCode\n\tTransportError error\n}\n\nfunc (c *ConnError) Error() string {\n\tside := \"local\"\n\tif c.Remote {\n\t\tside = \"remote\"\n\t}\n\tif c.TransportError != nil {\n\t\treturn fmt.Sprintf(\"connection closed (%s): code: 0x%x: transport error: %s\", side, c.ErrorCode, c.TransportError)\n\t}\n\treturn fmt.Sprintf(\"connection closed (%s): code: 0x%x\", side, c.ErrorCode)\n}\n\nfunc (c *ConnError) Is(target error) bool {\n\tif tce, ok := target.(*ConnError); ok {\n\t\treturn tce.ErrorCode == c.ErrorCode && tce.Remote == c.Remote\n\t}\n\treturn false\n}\n\nfunc (c *ConnError) Unwrap() []error {\n\treturn []error{ErrReset, c.TransportError}\n}\n\nconst (\n\tConnNoError                   ConnErrorCode = 0\n\tConnProtocolNegotiationFailed ConnErrorCode = 0x1000\n\tConnResourceLimitExceeded     ConnErrorCode = 0x1001\n\tConnRateLimited               ConnErrorCode = 0x1002\n\tConnProtocolViolation         ConnErrorCode = 0x1003\n\tConnSupplanted                ConnErrorCode = 0x1004\n\tConnGarbageCollected          ConnErrorCode = 0x1005\n\tConnShutdown                  ConnErrorCode = 0x1006\n\tConnGated                     ConnErrorCode = 0x1007\n\tConnCodeOutOfRange            ConnErrorCode = 0x1008\n)\n\n// Conn is a connection to a remote peer. It multiplexes streams.\n// Usually there is no need to use a Conn directly, but it may\n// be useful to get information about the peer on the other side:\n//\n//\tstream.Conn().RemotePeer()\ntype Conn interface {\n\tio.Closer\n\n\tConnSecurity\n\tConnMultiaddrs\n\tConnStat\n\tConnScoper\n\n\t// CloseWithError closes the connection with errCode. The errCode is sent to the\n\t// peer on a best effort basis. For transports that do not support sending error\n\t// codes on connection close, the behavior is identical to calling Close.\n\tCloseWithError(errCode ConnErrorCode) error\n\n\t// ID returns an identifier that uniquely identifies this Conn within this\n\t// host, during this run. Connection IDs may repeat across restarts.\n\tID() string\n\n\t// NewStream constructs a new Stream over this conn.\n\tNewStream(context.Context) (Stream, error)\n\n\t// GetStreams returns all open streams over this conn.\n\tGetStreams() []Stream\n\n\t// IsClosed returns whether a connection is fully closed, so it can\n\t// be garbage collected.\n\tIsClosed() bool\n\n\t// As finds the first conn in Conn's wrapped types that matches target, and\n\t// if one is found, sets target to that conn value and returns true.\n\t// Otherwise, it returns false. Similar to errors.As.\n\t//\n\t// target must be a pointer to the type you are matching against.\n\t//\n\t// This is an EXPERIMENTAL API. Getting access to the underlying type can\n\t// lead to hard to debug issues. For example, if you mutate connection state\n\t// on the underlying type, hooks that relied on only mutating that state\n\t// from the wrapped connection would never be called.\n\t//\n\t// You very likely do not need to use this method.\n\tAs(target any) bool\n}\n\n// ConnectionState holds information about the connection.\ntype ConnectionState struct {\n\t// The stream multiplexer used on this connection (if any). For example: /yamux/1.0.0\n\tStreamMultiplexer protocol.ID\n\t// The security protocol used on this connection (if any). For example: /tls/1.0.0\n\tSecurity protocol.ID\n\t// the transport used on this connection. For example: tcp\n\tTransport string\n\t// indicates whether StreamMultiplexer was selected using inlined muxer negotiation\n\tUsedEarlyMuxerNegotiation bool\n}\n\n// ConnSecurity is the interface that one can mix into a connection interface to\n// give it the security methods.\ntype ConnSecurity interface {\n\t// LocalPeer returns our peer ID\n\tLocalPeer() peer.ID\n\n\t// RemotePeer returns the peer ID of the remote peer.\n\tRemotePeer() peer.ID\n\n\t// RemotePublicKey returns the public key of the remote peer.\n\tRemotePublicKey() ic.PubKey\n\n\t// ConnState returns information about the connection state.\n\tConnState() ConnectionState\n}\n\n// ConnMultiaddrs is an interface mixin for connection types that provide multiaddr\n// addresses for the endpoints.\ntype ConnMultiaddrs interface {\n\t// LocalMultiaddr returns the local Multiaddr associated\n\t// with this connection\n\tLocalMultiaddr() ma.Multiaddr\n\n\t// RemoteMultiaddr returns the remote Multiaddr associated\n\t// with this connection\n\tRemoteMultiaddr() ma.Multiaddr\n}\n\n// ConnStat is an interface mixin for connection types that provide connection statistics.\ntype ConnStat interface {\n\t// Stat stores metadata pertaining to this conn.\n\tStat() ConnStats\n}\n\n// ConnScoper is the interface that one can mix into a connection interface to give it a resource\n// management scope\ntype ConnScoper interface {\n\t// Scope returns the user view of this connection's resource scope\n\tScope() ConnScope\n}\n"
  },
  {
    "path": "core/network/context.go",
    "content": "package network\n\nimport (\n\t\"context\"\n\t\"time\"\n)\n\n// DialPeerTimeout is the default timeout for a single call to `DialPeer`. When\n// there are multiple concurrent calls to `DialPeer`, this timeout will apply to\n// each independently.\nvar DialPeerTimeout = 60 * time.Second\n\ntype noDialCtxKey struct{}\ntype dialPeerTimeoutCtxKey struct{}\ntype forceDirectDialCtxKey struct{}\ntype allowLimitedConnCtxKey struct{}\ntype simConnectCtxKey struct{ isClient bool }\n\nvar noDial = noDialCtxKey{}\nvar forceDirectDial = forceDirectDialCtxKey{}\nvar allowLimitedConn = allowLimitedConnCtxKey{}\nvar simConnectIsServer = simConnectCtxKey{}\nvar simConnectIsClient = simConnectCtxKey{isClient: true}\n\n// EXPERIMENTAL\n// WithForceDirectDial constructs a new context with an option that instructs the network\n// to attempt to force a direct connection to a peer via a dial even if a proxied connection to it already exists.\nfunc WithForceDirectDial(ctx context.Context, reason string) context.Context {\n\treturn context.WithValue(ctx, forceDirectDial, reason)\n}\n\n// EXPERIMENTAL\n// GetForceDirectDial returns true if the force direct dial option is set in the context.\nfunc GetForceDirectDial(ctx context.Context) (forceDirect bool, reason string) {\n\tv := ctx.Value(forceDirectDial)\n\tif v != nil {\n\t\treturn true, v.(string)\n\t}\n\n\treturn false, \"\"\n}\n\n// WithSimultaneousConnect constructs a new context with an option that instructs the transport\n// to apply hole punching logic where applicable.\n// EXPERIMENTAL\nfunc WithSimultaneousConnect(ctx context.Context, isClient bool, reason string) context.Context {\n\tif isClient {\n\t\treturn context.WithValue(ctx, simConnectIsClient, reason)\n\t}\n\treturn context.WithValue(ctx, simConnectIsServer, reason)\n}\n\n// GetSimultaneousConnect returns true if the simultaneous connect option is set in the context.\n// EXPERIMENTAL\nfunc GetSimultaneousConnect(ctx context.Context) (simconnect bool, isClient bool, reason string) {\n\tif v := ctx.Value(simConnectIsClient); v != nil {\n\t\treturn true, true, v.(string)\n\t}\n\tif v := ctx.Value(simConnectIsServer); v != nil {\n\t\treturn true, false, v.(string)\n\t}\n\treturn false, false, \"\"\n}\n\n// WithNoDial constructs a new context with an option that instructs the network\n// to not attempt a new dial when opening a stream.\nfunc WithNoDial(ctx context.Context, reason string) context.Context {\n\treturn context.WithValue(ctx, noDial, reason)\n}\n\n// GetNoDial returns true if the no dial option is set in the context.\nfunc GetNoDial(ctx context.Context) (nodial bool, reason string) {\n\tv := ctx.Value(noDial)\n\tif v != nil {\n\t\treturn true, v.(string)\n\t}\n\n\treturn false, \"\"\n}\n\n// GetDialPeerTimeout returns the current DialPeer timeout (or the default).\nfunc GetDialPeerTimeout(ctx context.Context) time.Duration {\n\tif to, ok := ctx.Value(dialPeerTimeoutCtxKey{}).(time.Duration); ok {\n\t\treturn to\n\t}\n\treturn DialPeerTimeout\n}\n\n// WithDialPeerTimeout returns a new context with the DialPeer timeout applied.\n//\n// This timeout overrides the default DialPeerTimeout and applies per-dial\n// independently.\nfunc WithDialPeerTimeout(ctx context.Context, timeout time.Duration) context.Context {\n\treturn context.WithValue(ctx, dialPeerTimeoutCtxKey{}, timeout)\n}\n\n// WithAllowLimitedConn constructs a new context with an option that instructs\n// the network that it is acceptable to use a limited connection when opening a\n// new stream.\nfunc WithAllowLimitedConn(ctx context.Context, reason string) context.Context {\n\treturn context.WithValue(ctx, allowLimitedConn, reason)\n}\n\n// WithUseTransient constructs a new context with an option that instructs the network\n// that it is acceptable to use a transient connection when opening a new stream.\n//\n// Deprecated: Use WithAllowLimitedConn instead.\nfunc WithUseTransient(ctx context.Context, reason string) context.Context {\n\treturn context.WithValue(ctx, allowLimitedConn, reason)\n}\n\n// GetAllowLimitedConn returns true if the allow limited conn option is set in the context.\nfunc GetAllowLimitedConn(ctx context.Context) (usetransient bool, reason string) {\n\tv := ctx.Value(allowLimitedConn)\n\tif v != nil {\n\t\treturn true, v.(string)\n\t}\n\treturn false, \"\"\n}\n\n// GetUseTransient returns true if the use transient option is set in the context.\n//\n// Deprecated: Use GetAllowLimitedConn instead.\nfunc GetUseTransient(ctx context.Context) (usetransient bool, reason string) {\n\tv := ctx.Value(allowLimitedConn)\n\tif v != nil {\n\t\treturn true, v.(string)\n\t}\n\treturn false, \"\"\n}\n"
  },
  {
    "path": "core/network/context_test.go",
    "content": "package network\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestDefaultTimeout(t *testing.T) {\n\tctx := context.Background()\n\tdur := GetDialPeerTimeout(ctx)\n\tif dur != DialPeerTimeout {\n\t\tt.Fatal(\"expected default peer timeout\")\n\t}\n}\n\nfunc TestNonDefaultTimeout(t *testing.T) {\n\tcustomTimeout := time.Duration(1)\n\tctx := context.WithValue(\n\t\tcontext.Background(),\n\t\tdialPeerTimeoutCtxKey{},\n\t\tcustomTimeout,\n\t)\n\tdur := GetDialPeerTimeout(ctx)\n\tif dur != customTimeout {\n\t\tt.Fatal(\"peer timeout doesn't match set timeout\")\n\t}\n}\n\nfunc TestSettingTimeout(t *testing.T) {\n\tcustomTimeout := time.Duration(1)\n\tctx := WithDialPeerTimeout(\n\t\tcontext.Background(),\n\t\tcustomTimeout,\n\t)\n\tdur := GetDialPeerTimeout(ctx)\n\tif dur != customTimeout {\n\t\tt.Fatal(\"peer timeout doesn't match set timeout\")\n\t}\n}\n\nfunc TestSimultaneousConnect(t *testing.T) {\n\tt.Run(\"for the server\", func(t *testing.T) {\n\t\tserverCtx := WithSimultaneousConnect(context.Background(), false, \"foobar\")\n\t\tok, isClient, reason := GetSimultaneousConnect(serverCtx)\n\t\trequire.True(t, ok)\n\t\trequire.False(t, isClient)\n\t\trequire.Equal(t, \"foobar\", reason)\n\t})\n\tt.Run(\"for the client\", func(t *testing.T) {\n\t\tserverCtx := WithSimultaneousConnect(context.Background(), true, \"foo\")\n\t\tok, isClient, reason := GetSimultaneousConnect(serverCtx)\n\t\trequire.True(t, ok)\n\t\trequire.True(t, isClient)\n\t\trequire.Equal(t, \"foo\", reason)\n\t})\n}\n"
  },
  {
    "path": "core/network/errors.go",
    "content": "package network\n\nimport (\n\t\"errors\"\n\t\"net\"\n)\n\ntype temporaryError string\n\nfunc (e temporaryError) Error() string   { return string(e) }\nfunc (e temporaryError) Temporary() bool { return true }\nfunc (e temporaryError) Timeout() bool   { return false }\n\nvar _ net.Error = temporaryError(\"\")\n\n// ErrNoRemoteAddrs is returned when there are no addresses associated with a peer during a dial.\nvar ErrNoRemoteAddrs = errors.New(\"no remote addresses\")\n\n// ErrNoConn is returned when attempting to open a stream to a peer with the NoDial\n// option and no usable connection is available.\nvar ErrNoConn = errors.New(\"no usable connection to peer\")\n\n// ErrTransientConn is returned when attempting to open a stream to a peer with only a transient\n// connection, without specifying the UseTransient option.\n//\n// Deprecated: Use ErrLimitedConn instead.\nvar ErrTransientConn = ErrLimitedConn\n\n// ErrLimitedConn is returned when attempting to open a stream to a peer with only a conn\n// connection, without specifying the AllowLimitedConn option.\nvar ErrLimitedConn = errors.New(\"limited connection to peer\")\n\n// ErrResourceLimitExceeded is returned when attempting to perform an operation that would\n// exceed system resource limits.\nvar ErrResourceLimitExceeded = temporaryError(\"resource limit exceeded\")\n\n// ErrResourceScopeClosed is returned when attempting to reserve resources in a closed resource\n// scope.\nvar ErrResourceScopeClosed = errors.New(\"resource scope closed\")\n"
  },
  {
    "path": "core/network/mocks/mock_conn_management_scope.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github.com/libp2p/go-libp2p/core/network (interfaces: ConnManagementScope)\n//\n// Generated by this command:\n//\n//\tmockgen -package mocknetwork -destination mock_conn_management_scope.go github.com/libp2p/go-libp2p/core/network ConnManagementScope\n//\n\n// Package mocknetwork is a generated GoMock package.\npackage mocknetwork\n\nimport (\n\treflect \"reflect\"\n\n\tnetwork \"github.com/libp2p/go-libp2p/core/network\"\n\tpeer \"github.com/libp2p/go-libp2p/core/peer\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockConnManagementScope is a mock of ConnManagementScope interface.\ntype MockConnManagementScope struct {\n\tctrl     *gomock.Controller\n\trecorder *MockConnManagementScopeMockRecorder\n\tisgomock struct{}\n}\n\n// MockConnManagementScopeMockRecorder is the mock recorder for MockConnManagementScope.\ntype MockConnManagementScopeMockRecorder struct {\n\tmock *MockConnManagementScope\n}\n\n// NewMockConnManagementScope creates a new mock instance.\nfunc NewMockConnManagementScope(ctrl *gomock.Controller) *MockConnManagementScope {\n\tmock := &MockConnManagementScope{ctrl: ctrl}\n\tmock.recorder = &MockConnManagementScopeMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockConnManagementScope) EXPECT() *MockConnManagementScopeMockRecorder {\n\treturn m.recorder\n}\n\n// BeginSpan mocks base method.\nfunc (m *MockConnManagementScope) BeginSpan() (network.ResourceScopeSpan, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"BeginSpan\")\n\tret0, _ := ret[0].(network.ResourceScopeSpan)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// BeginSpan indicates an expected call of BeginSpan.\nfunc (mr *MockConnManagementScopeMockRecorder) BeginSpan() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"BeginSpan\", reflect.TypeOf((*MockConnManagementScope)(nil).BeginSpan))\n}\n\n// Done mocks base method.\nfunc (m *MockConnManagementScope) Done() {\n\tm.ctrl.T.Helper()\n\tm.ctrl.Call(m, \"Done\")\n}\n\n// Done indicates an expected call of Done.\nfunc (mr *MockConnManagementScopeMockRecorder) Done() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Done\", reflect.TypeOf((*MockConnManagementScope)(nil).Done))\n}\n\n// PeerScope mocks base method.\nfunc (m *MockConnManagementScope) PeerScope() network.PeerScope {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"PeerScope\")\n\tret0, _ := ret[0].(network.PeerScope)\n\treturn ret0\n}\n\n// PeerScope indicates an expected call of PeerScope.\nfunc (mr *MockConnManagementScopeMockRecorder) PeerScope() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"PeerScope\", reflect.TypeOf((*MockConnManagementScope)(nil).PeerScope))\n}\n\n// ReleaseMemory mocks base method.\nfunc (m *MockConnManagementScope) ReleaseMemory(size int) {\n\tm.ctrl.T.Helper()\n\tm.ctrl.Call(m, \"ReleaseMemory\", size)\n}\n\n// ReleaseMemory indicates an expected call of ReleaseMemory.\nfunc (mr *MockConnManagementScopeMockRecorder) ReleaseMemory(size any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReleaseMemory\", reflect.TypeOf((*MockConnManagementScope)(nil).ReleaseMemory), size)\n}\n\n// ReserveMemory mocks base method.\nfunc (m *MockConnManagementScope) ReserveMemory(size int, prio uint8) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReserveMemory\", size, prio)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// ReserveMemory indicates an expected call of ReserveMemory.\nfunc (mr *MockConnManagementScopeMockRecorder) ReserveMemory(size, prio any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReserveMemory\", reflect.TypeOf((*MockConnManagementScope)(nil).ReserveMemory), size, prio)\n}\n\n// SetPeer mocks base method.\nfunc (m *MockConnManagementScope) SetPeer(arg0 peer.ID) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SetPeer\", arg0)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// SetPeer indicates an expected call of SetPeer.\nfunc (mr *MockConnManagementScopeMockRecorder) SetPeer(arg0 any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SetPeer\", reflect.TypeOf((*MockConnManagementScope)(nil).SetPeer), arg0)\n}\n\n// Stat mocks base method.\nfunc (m *MockConnManagementScope) Stat() network.ScopeStat {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Stat\")\n\tret0, _ := ret[0].(network.ScopeStat)\n\treturn ret0\n}\n\n// Stat indicates an expected call of Stat.\nfunc (mr *MockConnManagementScopeMockRecorder) Stat() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Stat\", reflect.TypeOf((*MockConnManagementScope)(nil).Stat))\n}\n"
  },
  {
    "path": "core/network/mocks/mock_peer_scope.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github.com/libp2p/go-libp2p/core/network (interfaces: PeerScope)\n//\n// Generated by this command:\n//\n//\tmockgen -package mocknetwork -destination mock_peer_scope.go github.com/libp2p/go-libp2p/core/network PeerScope\n//\n\n// Package mocknetwork is a generated GoMock package.\npackage mocknetwork\n\nimport (\n\treflect \"reflect\"\n\n\tnetwork \"github.com/libp2p/go-libp2p/core/network\"\n\tpeer \"github.com/libp2p/go-libp2p/core/peer\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockPeerScope is a mock of PeerScope interface.\ntype MockPeerScope struct {\n\tctrl     *gomock.Controller\n\trecorder *MockPeerScopeMockRecorder\n\tisgomock struct{}\n}\n\n// MockPeerScopeMockRecorder is the mock recorder for MockPeerScope.\ntype MockPeerScopeMockRecorder struct {\n\tmock *MockPeerScope\n}\n\n// NewMockPeerScope creates a new mock instance.\nfunc NewMockPeerScope(ctrl *gomock.Controller) *MockPeerScope {\n\tmock := &MockPeerScope{ctrl: ctrl}\n\tmock.recorder = &MockPeerScopeMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockPeerScope) EXPECT() *MockPeerScopeMockRecorder {\n\treturn m.recorder\n}\n\n// BeginSpan mocks base method.\nfunc (m *MockPeerScope) BeginSpan() (network.ResourceScopeSpan, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"BeginSpan\")\n\tret0, _ := ret[0].(network.ResourceScopeSpan)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// BeginSpan indicates an expected call of BeginSpan.\nfunc (mr *MockPeerScopeMockRecorder) BeginSpan() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"BeginSpan\", reflect.TypeOf((*MockPeerScope)(nil).BeginSpan))\n}\n\n// Peer mocks base method.\nfunc (m *MockPeerScope) Peer() peer.ID {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Peer\")\n\tret0, _ := ret[0].(peer.ID)\n\treturn ret0\n}\n\n// Peer indicates an expected call of Peer.\nfunc (mr *MockPeerScopeMockRecorder) Peer() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Peer\", reflect.TypeOf((*MockPeerScope)(nil).Peer))\n}\n\n// ReleaseMemory mocks base method.\nfunc (m *MockPeerScope) ReleaseMemory(size int) {\n\tm.ctrl.T.Helper()\n\tm.ctrl.Call(m, \"ReleaseMemory\", size)\n}\n\n// ReleaseMemory indicates an expected call of ReleaseMemory.\nfunc (mr *MockPeerScopeMockRecorder) ReleaseMemory(size any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReleaseMemory\", reflect.TypeOf((*MockPeerScope)(nil).ReleaseMemory), size)\n}\n\n// ReserveMemory mocks base method.\nfunc (m *MockPeerScope) ReserveMemory(size int, prio uint8) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReserveMemory\", size, prio)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// ReserveMemory indicates an expected call of ReserveMemory.\nfunc (mr *MockPeerScopeMockRecorder) ReserveMemory(size, prio any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReserveMemory\", reflect.TypeOf((*MockPeerScope)(nil).ReserveMemory), size, prio)\n}\n\n// Stat mocks base method.\nfunc (m *MockPeerScope) Stat() network.ScopeStat {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Stat\")\n\tret0, _ := ret[0].(network.ScopeStat)\n\treturn ret0\n}\n\n// Stat indicates an expected call of Stat.\nfunc (mr *MockPeerScopeMockRecorder) Stat() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Stat\", reflect.TypeOf((*MockPeerScope)(nil).Stat))\n}\n"
  },
  {
    "path": "core/network/mocks/mock_protocol_scope.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github.com/libp2p/go-libp2p/core/network (interfaces: ProtocolScope)\n//\n// Generated by this command:\n//\n//\tmockgen -package mocknetwork -destination mock_protocol_scope.go github.com/libp2p/go-libp2p/core/network ProtocolScope\n//\n\n// Package mocknetwork is a generated GoMock package.\npackage mocknetwork\n\nimport (\n\treflect \"reflect\"\n\n\tnetwork \"github.com/libp2p/go-libp2p/core/network\"\n\tprotocol \"github.com/libp2p/go-libp2p/core/protocol\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockProtocolScope is a mock of ProtocolScope interface.\ntype MockProtocolScope struct {\n\tctrl     *gomock.Controller\n\trecorder *MockProtocolScopeMockRecorder\n\tisgomock struct{}\n}\n\n// MockProtocolScopeMockRecorder is the mock recorder for MockProtocolScope.\ntype MockProtocolScopeMockRecorder struct {\n\tmock *MockProtocolScope\n}\n\n// NewMockProtocolScope creates a new mock instance.\nfunc NewMockProtocolScope(ctrl *gomock.Controller) *MockProtocolScope {\n\tmock := &MockProtocolScope{ctrl: ctrl}\n\tmock.recorder = &MockProtocolScopeMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockProtocolScope) EXPECT() *MockProtocolScopeMockRecorder {\n\treturn m.recorder\n}\n\n// BeginSpan mocks base method.\nfunc (m *MockProtocolScope) BeginSpan() (network.ResourceScopeSpan, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"BeginSpan\")\n\tret0, _ := ret[0].(network.ResourceScopeSpan)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// BeginSpan indicates an expected call of BeginSpan.\nfunc (mr *MockProtocolScopeMockRecorder) BeginSpan() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"BeginSpan\", reflect.TypeOf((*MockProtocolScope)(nil).BeginSpan))\n}\n\n// Protocol mocks base method.\nfunc (m *MockProtocolScope) Protocol() protocol.ID {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Protocol\")\n\tret0, _ := ret[0].(protocol.ID)\n\treturn ret0\n}\n\n// Protocol indicates an expected call of Protocol.\nfunc (mr *MockProtocolScopeMockRecorder) Protocol() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Protocol\", reflect.TypeOf((*MockProtocolScope)(nil).Protocol))\n}\n\n// ReleaseMemory mocks base method.\nfunc (m *MockProtocolScope) ReleaseMemory(size int) {\n\tm.ctrl.T.Helper()\n\tm.ctrl.Call(m, \"ReleaseMemory\", size)\n}\n\n// ReleaseMemory indicates an expected call of ReleaseMemory.\nfunc (mr *MockProtocolScopeMockRecorder) ReleaseMemory(size any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReleaseMemory\", reflect.TypeOf((*MockProtocolScope)(nil).ReleaseMemory), size)\n}\n\n// ReserveMemory mocks base method.\nfunc (m *MockProtocolScope) ReserveMemory(size int, prio uint8) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReserveMemory\", size, prio)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// ReserveMemory indicates an expected call of ReserveMemory.\nfunc (mr *MockProtocolScopeMockRecorder) ReserveMemory(size, prio any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReserveMemory\", reflect.TypeOf((*MockProtocolScope)(nil).ReserveMemory), size, prio)\n}\n\n// Stat mocks base method.\nfunc (m *MockProtocolScope) Stat() network.ScopeStat {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Stat\")\n\tret0, _ := ret[0].(network.ScopeStat)\n\treturn ret0\n}\n\n// Stat indicates an expected call of Stat.\nfunc (mr *MockProtocolScopeMockRecorder) Stat() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Stat\", reflect.TypeOf((*MockProtocolScope)(nil).Stat))\n}\n"
  },
  {
    "path": "core/network/mocks/mock_resource_manager.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github.com/libp2p/go-libp2p/core/network (interfaces: ResourceManager)\n//\n// Generated by this command:\n//\n//\tmockgen -package mocknetwork -destination mock_resource_manager.go github.com/libp2p/go-libp2p/core/network ResourceManager\n//\n\n// Package mocknetwork is a generated GoMock package.\npackage mocknetwork\n\nimport (\n\tnet \"net\"\n\treflect \"reflect\"\n\n\tnetwork \"github.com/libp2p/go-libp2p/core/network\"\n\tpeer \"github.com/libp2p/go-libp2p/core/peer\"\n\tprotocol \"github.com/libp2p/go-libp2p/core/protocol\"\n\tmultiaddr \"github.com/multiformats/go-multiaddr\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockResourceManager is a mock of ResourceManager interface.\ntype MockResourceManager struct {\n\tctrl     *gomock.Controller\n\trecorder *MockResourceManagerMockRecorder\n\tisgomock struct{}\n}\n\n// MockResourceManagerMockRecorder is the mock recorder for MockResourceManager.\ntype MockResourceManagerMockRecorder struct {\n\tmock *MockResourceManager\n}\n\n// NewMockResourceManager creates a new mock instance.\nfunc NewMockResourceManager(ctrl *gomock.Controller) *MockResourceManager {\n\tmock := &MockResourceManager{ctrl: ctrl}\n\tmock.recorder = &MockResourceManagerMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockResourceManager) EXPECT() *MockResourceManagerMockRecorder {\n\treturn m.recorder\n}\n\n// Close mocks base method.\nfunc (m *MockResourceManager) Close() error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Close\")\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Close indicates an expected call of Close.\nfunc (mr *MockResourceManagerMockRecorder) Close() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Close\", reflect.TypeOf((*MockResourceManager)(nil).Close))\n}\n\n// OpenConnection mocks base method.\nfunc (m *MockResourceManager) OpenConnection(dir network.Direction, usefd bool, endpoint multiaddr.Multiaddr) (network.ConnManagementScope, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"OpenConnection\", dir, usefd, endpoint)\n\tret0, _ := ret[0].(network.ConnManagementScope)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// OpenConnection indicates an expected call of OpenConnection.\nfunc (mr *MockResourceManagerMockRecorder) OpenConnection(dir, usefd, endpoint any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"OpenConnection\", reflect.TypeOf((*MockResourceManager)(nil).OpenConnection), dir, usefd, endpoint)\n}\n\n// OpenStream mocks base method.\nfunc (m *MockResourceManager) OpenStream(p peer.ID, dir network.Direction) (network.StreamManagementScope, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"OpenStream\", p, dir)\n\tret0, _ := ret[0].(network.StreamManagementScope)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// OpenStream indicates an expected call of OpenStream.\nfunc (mr *MockResourceManagerMockRecorder) OpenStream(p, dir any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"OpenStream\", reflect.TypeOf((*MockResourceManager)(nil).OpenStream), p, dir)\n}\n\n// VerifySourceAddress mocks base method.\nfunc (m *MockResourceManager) VerifySourceAddress(addr net.Addr) bool {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"VerifySourceAddress\", addr)\n\tret0, _ := ret[0].(bool)\n\treturn ret0\n}\n\n// VerifySourceAddress indicates an expected call of VerifySourceAddress.\nfunc (mr *MockResourceManagerMockRecorder) VerifySourceAddress(addr any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"VerifySourceAddress\", reflect.TypeOf((*MockResourceManager)(nil).VerifySourceAddress), addr)\n}\n\n// ViewPeer mocks base method.\nfunc (m *MockResourceManager) ViewPeer(arg0 peer.ID, arg1 func(network.PeerScope) error) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ViewPeer\", arg0, arg1)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// ViewPeer indicates an expected call of ViewPeer.\nfunc (mr *MockResourceManagerMockRecorder) ViewPeer(arg0, arg1 any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ViewPeer\", reflect.TypeOf((*MockResourceManager)(nil).ViewPeer), arg0, arg1)\n}\n\n// ViewProtocol mocks base method.\nfunc (m *MockResourceManager) ViewProtocol(arg0 protocol.ID, arg1 func(network.ProtocolScope) error) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ViewProtocol\", arg0, arg1)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// ViewProtocol indicates an expected call of ViewProtocol.\nfunc (mr *MockResourceManagerMockRecorder) ViewProtocol(arg0, arg1 any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ViewProtocol\", reflect.TypeOf((*MockResourceManager)(nil).ViewProtocol), arg0, arg1)\n}\n\n// ViewService mocks base method.\nfunc (m *MockResourceManager) ViewService(arg0 string, arg1 func(network.ServiceScope) error) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ViewService\", arg0, arg1)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// ViewService indicates an expected call of ViewService.\nfunc (mr *MockResourceManagerMockRecorder) ViewService(arg0, arg1 any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ViewService\", reflect.TypeOf((*MockResourceManager)(nil).ViewService), arg0, arg1)\n}\n\n// ViewSystem mocks base method.\nfunc (m *MockResourceManager) ViewSystem(arg0 func(network.ResourceScope) error) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ViewSystem\", arg0)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// ViewSystem indicates an expected call of ViewSystem.\nfunc (mr *MockResourceManagerMockRecorder) ViewSystem(arg0 any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ViewSystem\", reflect.TypeOf((*MockResourceManager)(nil).ViewSystem), arg0)\n}\n\n// ViewTransient mocks base method.\nfunc (m *MockResourceManager) ViewTransient(arg0 func(network.ResourceScope) error) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ViewTransient\", arg0)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// ViewTransient indicates an expected call of ViewTransient.\nfunc (mr *MockResourceManagerMockRecorder) ViewTransient(arg0 any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ViewTransient\", reflect.TypeOf((*MockResourceManager)(nil).ViewTransient), arg0)\n}\n"
  },
  {
    "path": "core/network/mocks/mock_resource_scope_span.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github.com/libp2p/go-libp2p/core/network (interfaces: ResourceScopeSpan)\n//\n// Generated by this command:\n//\n//\tmockgen -package mocknetwork -destination mock_resource_scope_span.go github.com/libp2p/go-libp2p/core/network ResourceScopeSpan\n//\n\n// Package mocknetwork is a generated GoMock package.\npackage mocknetwork\n\nimport (\n\treflect \"reflect\"\n\n\tnetwork \"github.com/libp2p/go-libp2p/core/network\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockResourceScopeSpan is a mock of ResourceScopeSpan interface.\ntype MockResourceScopeSpan struct {\n\tctrl     *gomock.Controller\n\trecorder *MockResourceScopeSpanMockRecorder\n\tisgomock struct{}\n}\n\n// MockResourceScopeSpanMockRecorder is the mock recorder for MockResourceScopeSpan.\ntype MockResourceScopeSpanMockRecorder struct {\n\tmock *MockResourceScopeSpan\n}\n\n// NewMockResourceScopeSpan creates a new mock instance.\nfunc NewMockResourceScopeSpan(ctrl *gomock.Controller) *MockResourceScopeSpan {\n\tmock := &MockResourceScopeSpan{ctrl: ctrl}\n\tmock.recorder = &MockResourceScopeSpanMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockResourceScopeSpan) EXPECT() *MockResourceScopeSpanMockRecorder {\n\treturn m.recorder\n}\n\n// BeginSpan mocks base method.\nfunc (m *MockResourceScopeSpan) BeginSpan() (network.ResourceScopeSpan, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"BeginSpan\")\n\tret0, _ := ret[0].(network.ResourceScopeSpan)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// BeginSpan indicates an expected call of BeginSpan.\nfunc (mr *MockResourceScopeSpanMockRecorder) BeginSpan() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"BeginSpan\", reflect.TypeOf((*MockResourceScopeSpan)(nil).BeginSpan))\n}\n\n// Done mocks base method.\nfunc (m *MockResourceScopeSpan) Done() {\n\tm.ctrl.T.Helper()\n\tm.ctrl.Call(m, \"Done\")\n}\n\n// Done indicates an expected call of Done.\nfunc (mr *MockResourceScopeSpanMockRecorder) Done() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Done\", reflect.TypeOf((*MockResourceScopeSpan)(nil).Done))\n}\n\n// ReleaseMemory mocks base method.\nfunc (m *MockResourceScopeSpan) ReleaseMemory(size int) {\n\tm.ctrl.T.Helper()\n\tm.ctrl.Call(m, \"ReleaseMemory\", size)\n}\n\n// ReleaseMemory indicates an expected call of ReleaseMemory.\nfunc (mr *MockResourceScopeSpanMockRecorder) ReleaseMemory(size any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReleaseMemory\", reflect.TypeOf((*MockResourceScopeSpan)(nil).ReleaseMemory), size)\n}\n\n// ReserveMemory mocks base method.\nfunc (m *MockResourceScopeSpan) ReserveMemory(size int, prio uint8) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReserveMemory\", size, prio)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// ReserveMemory indicates an expected call of ReserveMemory.\nfunc (mr *MockResourceScopeSpanMockRecorder) ReserveMemory(size, prio any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReserveMemory\", reflect.TypeOf((*MockResourceScopeSpan)(nil).ReserveMemory), size, prio)\n}\n\n// Stat mocks base method.\nfunc (m *MockResourceScopeSpan) Stat() network.ScopeStat {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Stat\")\n\tret0, _ := ret[0].(network.ScopeStat)\n\treturn ret0\n}\n\n// Stat indicates an expected call of Stat.\nfunc (mr *MockResourceScopeSpanMockRecorder) Stat() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Stat\", reflect.TypeOf((*MockResourceScopeSpan)(nil).Stat))\n}\n"
  },
  {
    "path": "core/network/mocks/mock_stream_management_scope.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github.com/libp2p/go-libp2p/core/network (interfaces: StreamManagementScope)\n//\n// Generated by this command:\n//\n//\tmockgen -package mocknetwork -destination mock_stream_management_scope.go github.com/libp2p/go-libp2p/core/network StreamManagementScope\n//\n\n// Package mocknetwork is a generated GoMock package.\npackage mocknetwork\n\nimport (\n\treflect \"reflect\"\n\n\tnetwork \"github.com/libp2p/go-libp2p/core/network\"\n\tprotocol \"github.com/libp2p/go-libp2p/core/protocol\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockStreamManagementScope is a mock of StreamManagementScope interface.\ntype MockStreamManagementScope struct {\n\tctrl     *gomock.Controller\n\trecorder *MockStreamManagementScopeMockRecorder\n\tisgomock struct{}\n}\n\n// MockStreamManagementScopeMockRecorder is the mock recorder for MockStreamManagementScope.\ntype MockStreamManagementScopeMockRecorder struct {\n\tmock *MockStreamManagementScope\n}\n\n// NewMockStreamManagementScope creates a new mock instance.\nfunc NewMockStreamManagementScope(ctrl *gomock.Controller) *MockStreamManagementScope {\n\tmock := &MockStreamManagementScope{ctrl: ctrl}\n\tmock.recorder = &MockStreamManagementScopeMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockStreamManagementScope) EXPECT() *MockStreamManagementScopeMockRecorder {\n\treturn m.recorder\n}\n\n// BeginSpan mocks base method.\nfunc (m *MockStreamManagementScope) BeginSpan() (network.ResourceScopeSpan, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"BeginSpan\")\n\tret0, _ := ret[0].(network.ResourceScopeSpan)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// BeginSpan indicates an expected call of BeginSpan.\nfunc (mr *MockStreamManagementScopeMockRecorder) BeginSpan() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"BeginSpan\", reflect.TypeOf((*MockStreamManagementScope)(nil).BeginSpan))\n}\n\n// Done mocks base method.\nfunc (m *MockStreamManagementScope) Done() {\n\tm.ctrl.T.Helper()\n\tm.ctrl.Call(m, \"Done\")\n}\n\n// Done indicates an expected call of Done.\nfunc (mr *MockStreamManagementScopeMockRecorder) Done() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Done\", reflect.TypeOf((*MockStreamManagementScope)(nil).Done))\n}\n\n// PeerScope mocks base method.\nfunc (m *MockStreamManagementScope) PeerScope() network.PeerScope {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"PeerScope\")\n\tret0, _ := ret[0].(network.PeerScope)\n\treturn ret0\n}\n\n// PeerScope indicates an expected call of PeerScope.\nfunc (mr *MockStreamManagementScopeMockRecorder) PeerScope() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"PeerScope\", reflect.TypeOf((*MockStreamManagementScope)(nil).PeerScope))\n}\n\n// ProtocolScope mocks base method.\nfunc (m *MockStreamManagementScope) ProtocolScope() network.ProtocolScope {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ProtocolScope\")\n\tret0, _ := ret[0].(network.ProtocolScope)\n\treturn ret0\n}\n\n// ProtocolScope indicates an expected call of ProtocolScope.\nfunc (mr *MockStreamManagementScopeMockRecorder) ProtocolScope() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ProtocolScope\", reflect.TypeOf((*MockStreamManagementScope)(nil).ProtocolScope))\n}\n\n// ReleaseMemory mocks base method.\nfunc (m *MockStreamManagementScope) ReleaseMemory(size int) {\n\tm.ctrl.T.Helper()\n\tm.ctrl.Call(m, \"ReleaseMemory\", size)\n}\n\n// ReleaseMemory indicates an expected call of ReleaseMemory.\nfunc (mr *MockStreamManagementScopeMockRecorder) ReleaseMemory(size any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReleaseMemory\", reflect.TypeOf((*MockStreamManagementScope)(nil).ReleaseMemory), size)\n}\n\n// ReserveMemory mocks base method.\nfunc (m *MockStreamManagementScope) ReserveMemory(size int, prio uint8) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReserveMemory\", size, prio)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// ReserveMemory indicates an expected call of ReserveMemory.\nfunc (mr *MockStreamManagementScopeMockRecorder) ReserveMemory(size, prio any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReserveMemory\", reflect.TypeOf((*MockStreamManagementScope)(nil).ReserveMemory), size, prio)\n}\n\n// ServiceScope mocks base method.\nfunc (m *MockStreamManagementScope) ServiceScope() network.ServiceScope {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ServiceScope\")\n\tret0, _ := ret[0].(network.ServiceScope)\n\treturn ret0\n}\n\n// ServiceScope indicates an expected call of ServiceScope.\nfunc (mr *MockStreamManagementScopeMockRecorder) ServiceScope() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ServiceScope\", reflect.TypeOf((*MockStreamManagementScope)(nil).ServiceScope))\n}\n\n// SetProtocol mocks base method.\nfunc (m *MockStreamManagementScope) SetProtocol(proto protocol.ID) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SetProtocol\", proto)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// SetProtocol indicates an expected call of SetProtocol.\nfunc (mr *MockStreamManagementScopeMockRecorder) SetProtocol(proto any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SetProtocol\", reflect.TypeOf((*MockStreamManagementScope)(nil).SetProtocol), proto)\n}\n\n// SetService mocks base method.\nfunc (m *MockStreamManagementScope) SetService(srv string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SetService\", srv)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// SetService indicates an expected call of SetService.\nfunc (mr *MockStreamManagementScopeMockRecorder) SetService(srv any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SetService\", reflect.TypeOf((*MockStreamManagementScope)(nil).SetService), srv)\n}\n\n// Stat mocks base method.\nfunc (m *MockStreamManagementScope) Stat() network.ScopeStat {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Stat\")\n\tret0, _ := ret[0].(network.ScopeStat)\n\treturn ret0\n}\n\n// Stat indicates an expected call of Stat.\nfunc (mr *MockStreamManagementScopeMockRecorder) Stat() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Stat\", reflect.TypeOf((*MockStreamManagementScope)(nil).Stat))\n}\n"
  },
  {
    "path": "core/network/mocks/network.go",
    "content": "package mocknetwork\n\n//go:generate sh -c \"go run go.uber.org/mock/mockgen -package mocknetwork -destination mock_resource_manager.go github.com/libp2p/go-libp2p/core/network ResourceManager\"\n//go:generate sh -c \"go run go.uber.org/mock/mockgen -package mocknetwork -destination mock_conn_management_scope.go github.com/libp2p/go-libp2p/core/network ConnManagementScope\"\n//go:generate sh -c \"go run go.uber.org/mock/mockgen -package mocknetwork -destination mock_stream_management_scope.go github.com/libp2p/go-libp2p/core/network StreamManagementScope\"\n//go:generate sh -c \"go run go.uber.org/mock/mockgen -package mocknetwork -destination mock_peer_scope.go github.com/libp2p/go-libp2p/core/network PeerScope\"\n//go:generate sh -c \"go run go.uber.org/mock/mockgen -package mocknetwork -destination mock_protocol_scope.go github.com/libp2p/go-libp2p/core/network ProtocolScope\"\n//go:generate sh -c \"go run go.uber.org/mock/mockgen -package mocknetwork -destination mock_resource_scope_span.go github.com/libp2p/go-libp2p/core/network ResourceScopeSpan\"\n"
  },
  {
    "path": "core/network/mux.go",
    "content": "package network\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"time\"\n)\n\n// ErrReset is returned when reading or writing on a reset stream.\nvar ErrReset = errors.New(\"stream reset\")\n\ntype StreamErrorCode uint32\n\ntype StreamError struct {\n\tErrorCode      StreamErrorCode\n\tRemote         bool\n\tTransportError error\n}\n\nfunc (s *StreamError) Error() string {\n\tside := \"local\"\n\tif s.Remote {\n\t\tside = \"remote\"\n\t}\n\tif s.TransportError != nil {\n\t\treturn fmt.Sprintf(\"stream reset (%s): code: 0x%x: transport error: %s\", side, s.ErrorCode, s.TransportError)\n\t}\n\treturn fmt.Sprintf(\"stream reset (%s): code: 0x%x\", side, s.ErrorCode)\n}\n\nfunc (s *StreamError) Is(target error) bool {\n\tif tse, ok := target.(*StreamError); ok {\n\t\treturn tse.ErrorCode == s.ErrorCode && tse.Remote == s.Remote\n\t}\n\treturn false\n}\n\nfunc (s *StreamError) Unwrap() []error {\n\treturn []error{ErrReset, s.TransportError}\n}\n\nconst (\n\tStreamNoError                   StreamErrorCode = 0\n\tStreamProtocolNegotiationFailed StreamErrorCode = 0x1001\n\tStreamResourceLimitExceeded     StreamErrorCode = 0x1002\n\tStreamRateLimited               StreamErrorCode = 0x1003\n\tStreamProtocolViolation         StreamErrorCode = 0x1004\n\tStreamSupplanted                StreamErrorCode = 0x1005\n\tStreamGarbageCollected          StreamErrorCode = 0x1006\n\tStreamShutdown                  StreamErrorCode = 0x1007\n\tStreamGated                     StreamErrorCode = 0x1008\n\tStreamCodeOutOfRange            StreamErrorCode = 0x1009\n)\n\n// MuxedStream is a bidirectional io pipe within a connection.\ntype MuxedStream interface {\n\tio.Reader\n\tio.Writer\n\n\t// Close closes the stream.\n\t//\n\t// * Any buffered data for writing will be flushed.\n\t// * Future reads will fail.\n\t// * Any in-progress reads/writes will be interrupted.\n\t//\n\t// Close may be asynchronous and _does not_ guarantee receipt of the\n\t// data.\n\t//\n\t// Close closes the stream for both reading and writing.\n\t// Close is equivalent to calling `CloseRead` and `CloseWrite`. Importantly, Close will not wait for any form of acknowledgment.\n\t// If acknowledgment is required, the caller must call `CloseWrite`, then wait on the stream for a response (or an EOF),\n\t// then call Close() to free the stream object.\n\t//\n\t// When done with a stream, the user must call either Close() or `Reset()` to discard the stream, even after calling `CloseRead` and/or `CloseWrite`.\n\tio.Closer\n\n\t// CloseWrite closes the stream for writing but leaves it open for\n\t// reading.\n\t//\n\t// CloseWrite does not free the stream, users must still call Close or\n\t// Reset.\n\tCloseWrite() error\n\n\t// CloseRead closes the stream for reading but leaves it open for\n\t// writing.\n\t//\n\t// When CloseRead is called, all in-progress Read calls are interrupted with a non-EOF error and\n\t// no further calls to Read will succeed.\n\t//\n\t// The handling of new incoming data on the stream after calling this function is implementation defined.\n\t//\n\t// CloseRead does not free the stream, users must still call Close or\n\t// Reset.\n\tCloseRead() error\n\n\t// Reset closes both ends of the stream. Use this to tell the remote\n\t// side to hang up and go away.\n\tReset() error\n\n\t// ResetWithError aborts both ends of the stream with `errCode`. `errCode` is sent\n\t// to the peer on a best effort basis. For transports that do not support sending\n\t// error codes to remote peer, the behavior is identical to calling Reset\n\tResetWithError(errCode StreamErrorCode) error\n\n\tSetDeadline(time.Time) error\n\tSetReadDeadline(time.Time) error\n\tSetWriteDeadline(time.Time) error\n}\n\n// MuxedConn represents a connection to a remote peer that has been\n// extended to support stream multiplexing.\n//\n// A MuxedConn allows a single net.Conn connection to carry many logically\n// independent bidirectional streams of binary data.\n//\n// Together with network.ConnSecurity, MuxedConn is a component of the\n// transport.CapableConn interface, which represents a \"raw\" network\n// connection that has been \"upgraded\" to support the libp2p capabilities\n// of secure communication and stream multiplexing.\ntype MuxedConn interface {\n\t// Close closes the stream muxer and the the underlying net.Conn.\n\tio.Closer\n\n\t// CloseWithError closes the connection with errCode. The errCode is sent\n\t// to the peer.\n\tCloseWithError(errCode ConnErrorCode) error\n\n\t// IsClosed returns whether a connection is fully closed, so it can\n\t// be garbage collected.\n\tIsClosed() bool\n\n\t// OpenStream creates a new stream.\n\tOpenStream(context.Context) (MuxedStream, error)\n\n\t// AcceptStream accepts a stream opened by the other side.\n\tAcceptStream() (MuxedStream, error)\n\n\t// As finds the first conn in MuxedConn's wrapped types that matches target,\n\t// and if one is found, sets target to that conn value and returns true.\n\t// Otherwise, it returns false. Similar to errors.As.\n\t//\n\t// target must be a pointer to the type you are matching against.\n\t//\n\t// This is an EXPERIMENTAL API. Getting access to the underlying type can\n\t// lead to hard to debug issues. For example, if you mutate connection state\n\t// on the underlying type, hooks that relied on only mutating that state\n\t// from the wrapped connection would never be called.\n\t//\n\t// You very likely do not need to use this method.\n\tAs(target any) bool\n}\n\n// Multiplexer wraps a net.Conn with a stream multiplexing\n// implementation and returns a MuxedConn that supports opening\n// multiple streams over the underlying net.Conn\ntype Multiplexer interface {\n\t// NewConn constructs a new connection\n\tNewConn(c net.Conn, isServer bool, scope PeerScope) (MuxedConn, error)\n}\n"
  },
  {
    "path": "core/network/nattype.go",
    "content": "package network\n\n// NATDeviceType indicates the type of the NAT device.\ntype NATDeviceType int\n\nconst (\n\t// NATDeviceTypeUnknown indicates that the type of the NAT device is unknown.\n\tNATDeviceTypeUnknown NATDeviceType = iota\n\n\t// NATDeviceTypeEndpointIndependent is a NAT device that maps addresses\n\t// independent of the destination address. An EndpointIndependent NAT is\n\t// a NAT where all outgoing connections from the same source IP address\n\t// and port are mapped by the NAT device to the same IP address and port\n\t// irrespective of the destination endpoint.\n\t//\n\t// NAT traversal with hole punching is possible with an\n\t// EndpointIndependent NAT ONLY if the remote peer is ALSO behind an\n\t// EndpointIndependent NAT. If the remote peer is behind an\n\t// EndpointDependent NAT, hole punching will fail.\n\tNATDeviceTypeEndpointIndependent\n\n\t// NATDeviceTypeEndpointDependent is a NAT device that maps addresses\n\t// depending on the destination address. An EndpointDependent NAT maps\n\t// outgoing connections with different destination addresses to\n\t// different IP addresses and ports, even if they originate from the\n\t// same source IP address and port.\n\t//\n\t// NAT traversal with hole-punching is currently NOT possible in libp2p\n\t// with EndpointDependent NATs irrespective of the remote peer's NAT\n\t// type.\n\tNATDeviceTypeEndpointDependent\n)\n\nconst (\n\t// NATDeviceTypeCone is the same as endpoint independent\n\t//\n\t// Deprecated: Use NATDeviceTypeEndpointIndependent\n\tNATDeviceTypeCone = NATDeviceTypeEndpointIndependent\n\t// NATDeviceTypeSymmetric is the same as endpoint dependent\n\t//\n\t// Deprecated: Use NATDeviceTypeEndpointDependent\n\tNATDeviceTypeSymmetric = NATDeviceTypeEndpointDependent\n)\n\nfunc (r NATDeviceType) String() string {\n\tswitch r {\n\tcase 0:\n\t\treturn \"Unknown\"\n\tcase 1:\n\t\treturn \"Endpoint Independent\"\n\tcase 2:\n\t\treturn \"Endpoint Dependent\"\n\tdefault:\n\t\treturn \"unrecognized\"\n\t}\n}\n\n// NATTransportProtocol is the transport protocol for which the NAT Device Type has been determined.\ntype NATTransportProtocol int\n\nconst (\n\t// NATTransportUDP means that the NAT Device Type has been determined for the UDP Protocol.\n\tNATTransportUDP NATTransportProtocol = iota\n\t// NATTransportTCP means that the NAT Device Type has been determined for the TCP Protocol.\n\tNATTransportTCP\n)\n\nfunc (n NATTransportProtocol) String() string {\n\tswitch n {\n\tcase 0:\n\t\treturn \"UDP\"\n\tcase 1:\n\t\treturn \"TCP\"\n\tdefault:\n\t\treturn \"unrecognized\"\n\t}\n}\n"
  },
  {
    "path": "core/network/network.go",
    "content": "// Package network provides core networking abstractions for libp2p.\n//\n// The network package provides the high-level Network interface for interacting\n// with other libp2p peers, which is the primary public API for initiating and\n// accepting connections to remote peers.\npackage network\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\n// MessageSizeMax is a soft (recommended) maximum for network messages.\n// One can write more, as the interface is a stream. But it is useful\n// to bunch it up into multiple read/writes when the whole message is\n// a single, large serialized object.\nconst MessageSizeMax = 1 << 22 // 4 MB\n\n// Direction represents which peer in a stream initiated a connection.\ntype Direction int\n\nconst (\n\t// DirUnknown is the default direction.\n\tDirUnknown Direction = iota\n\t// DirInbound is for when the remote peer initiated a connection.\n\tDirInbound\n\t// DirOutbound is for when the local peer initiated a connection.\n\tDirOutbound\n)\n\nconst unrecognized = \"(unrecognized)\"\n\nfunc (d Direction) String() string {\n\tstr := [...]string{\"Unknown\", \"Inbound\", \"Outbound\"}\n\tif d < 0 || int(d) >= len(str) {\n\t\treturn unrecognized\n\t}\n\treturn str[d]\n}\n\n// Connectedness signals the capacity for a connection with a given node.\n// It is used to signal to services and other peers whether a node is reachable.\ntype Connectedness int\n\nconst (\n\t// NotConnected means no connection to peer, and no extra information (default)\n\tNotConnected Connectedness = iota\n\n\t// Connected means has an open, live connection to peer\n\tConnected\n\n\t// Deprecated: CanConnect is deprecated and will be removed in a future release.\n\t//\n\t// CanConnect means recently connected to peer, terminated gracefully\n\tCanConnect\n\n\t// Deprecated: CannotConnect is deprecated and will be removed in a future release.\n\t//\n\t// CannotConnect means recently attempted connecting but failed to connect.\n\t// (should signal \"made effort, failed\")\n\tCannotConnect\n\n\t// Limited means we have a transient connection to the peer, but aren't fully connected.\n\tLimited\n)\n\nfunc (c Connectedness) String() string {\n\tstr := [...]string{\"NotConnected\", \"Connected\", \"CanConnect\", \"CannotConnect\", \"Limited\"}\n\tif c < 0 || int(c) >= len(str) {\n\t\treturn unrecognized\n\t}\n\treturn str[c]\n}\n\n// Reachability indicates how reachable a node is.\ntype Reachability int\n\nconst (\n\t// ReachabilityUnknown indicates that the reachability status of the\n\t// node is unknown.\n\tReachabilityUnknown Reachability = iota\n\n\t// ReachabilityPublic indicates that the node is reachable from the\n\t// public internet.\n\tReachabilityPublic\n\n\t// ReachabilityPrivate indicates that the node is not reachable from the\n\t// public internet.\n\t//\n\t// NOTE: This node may _still_ be reachable via relays.\n\tReachabilityPrivate\n)\n\nfunc (r Reachability) String() string {\n\tstr := [...]string{\"Unknown\", \"Public\", \"Private\"}\n\tif r < 0 || int(r) >= len(str) {\n\t\treturn unrecognized\n\t}\n\treturn str[r]\n}\n\n// ConnStats stores metadata pertaining to a given Conn.\ntype ConnStats struct {\n\tStats\n\t// NumStreams is the number of streams on the connection.\n\tNumStreams int\n}\n\n// Stats stores metadata pertaining to a given Stream / Conn.\ntype Stats struct {\n\t// Direction specifies whether this is an inbound or an outbound connection.\n\tDirection Direction\n\t// Opened is the timestamp when this connection was opened.\n\tOpened time.Time\n\t// Limited indicates that this connection is Limited. It maybe limited by\n\t// bytes or time. In practice, this is a connection formed over a circuit v2\n\t// relay.\n\tLimited bool\n\t// Extra stores additional metadata about this connection.\n\tExtra map[any]any\n}\n\n// StreamHandler is the type of function used to listen for\n// streams opened by the remote side.\ntype StreamHandler func(Stream)\n\n// Network is the interface used to connect to the outside world.\n// It dials and listens for connections. it uses a Swarm to pool\n// connections (see swarm pkg, and peerstream.Swarm). Connections\n// are encrypted with a TLS-like protocol.\ntype Network interface {\n\tDialer\n\tio.Closer\n\n\t// SetStreamHandler sets the handler for new streams opened by the\n\t// remote side. This operation is thread-safe.\n\tSetStreamHandler(StreamHandler)\n\n\t// NewStream returns a new stream to given peer p.\n\t// If there is no connection to p, attempts to create one.\n\tNewStream(context.Context, peer.ID) (Stream, error)\n\n\t// Listen tells the network to start listening on given multiaddrs.\n\tListen(...ma.Multiaddr) error\n\n\t// ListenAddresses returns a list of addresses at which this network listens.\n\tListenAddresses() []ma.Multiaddr\n\n\t// InterfaceListenAddresses returns a list of addresses at which this network\n\t// listens. It expands \"any interface\" addresses (/ip4/0.0.0.0, /ip6/::) to\n\t// use the known local interfaces.\n\tInterfaceListenAddresses() ([]ma.Multiaddr, error)\n\n\t// ResourceManager returns the ResourceManager associated with this network\n\tResourceManager() ResourceManager\n}\n\ntype MultiaddrDNSResolver interface {\n\t// ResolveDNSAddr resolves the first /dnsaddr component in a multiaddr.\n\t// Recurisvely resolves DNSADDRs up to the recursion limit\n\tResolveDNSAddr(ctx context.Context, expectedPeerID peer.ID, maddr ma.Multiaddr, recursionLimit, outputLimit int) ([]ma.Multiaddr, error)\n\t// ResolveDNSComponent resolves the first /{dns,dns4,dns6} component in a multiaddr.\n\tResolveDNSComponent(ctx context.Context, maddr ma.Multiaddr, outputLimit int) ([]ma.Multiaddr, error)\n}\n\n// Dialer represents a service that can dial out to peers\n// (this is usually just a Network, but other services may not need the whole\n// stack, and thus it becomes easier to mock)\ntype Dialer interface {\n\t// Peerstore returns the internal peerstore\n\t// This is useful to tell the dialer about a new address for a peer.\n\t// Or use one of the public keys found out over the network.\n\tPeerstore() peerstore.Peerstore\n\n\t// LocalPeer returns the local peer associated with this network\n\tLocalPeer() peer.ID\n\n\t// DialPeer establishes a connection to a given peer\n\tDialPeer(context.Context, peer.ID) (Conn, error)\n\n\t// ClosePeer closes the connection to a given peer\n\tClosePeer(peer.ID) error\n\n\t// Connectedness returns a state signaling connection capabilities\n\tConnectedness(peer.ID) Connectedness\n\n\t// Peers returns the peers connected\n\tPeers() []peer.ID\n\n\t// Conns returns the connections in this Network\n\tConns() []Conn\n\n\t// ConnsToPeer returns the connections in this Network for given peer.\n\tConnsToPeer(p peer.ID) []Conn\n\n\t// Notify/StopNotify register and unregister a notifiee for signals\n\tNotify(Notifiee)\n\tStopNotify(Notifiee)\n\n\t// CanDial returns whether the dialer can dial peer p at addr\n\tCanDial(p peer.ID, addr ma.Multiaddr) bool\n}\n\n// AddrDelay provides an address along with the delay after which the address\n// should be dialed\ntype AddrDelay struct {\n\tAddr  ma.Multiaddr\n\tDelay time.Duration\n}\n\n// DialRanker provides a schedule of dialing the provided addresses\ntype DialRanker func([]ma.Multiaddr) []AddrDelay\n"
  },
  {
    "path": "core/network/notifee.go",
    "content": "package network\n\nimport (\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\n// Notifiee is an interface for an object wishing to receive\n// notifications from a Network.\ntype Notifiee interface {\n\tListen(Network, ma.Multiaddr)      // called when network starts listening on an addr\n\tListenClose(Network, ma.Multiaddr) // called when network stops listening on an addr\n\tConnected(Network, Conn)           // called when a connection opened\n\tDisconnected(Network, Conn)        // called when a connection closed\n}\n\n// NotifyBundle implements Notifiee by calling any of the functions set on it,\n// and nop'ing if they are unset. This is the easy way to register for\n// notifications.\ntype NotifyBundle struct {\n\tListenF      func(Network, ma.Multiaddr)\n\tListenCloseF func(Network, ma.Multiaddr)\n\n\tConnectedF    func(Network, Conn)\n\tDisconnectedF func(Network, Conn)\n}\n\nvar _ Notifiee = (*NotifyBundle)(nil)\n\n// Listen calls ListenF if it is not null.\nfunc (nb *NotifyBundle) Listen(n Network, a ma.Multiaddr) {\n\tif nb.ListenF != nil {\n\t\tnb.ListenF(n, a)\n\t}\n}\n\n// ListenClose calls ListenCloseF if it is not null.\nfunc (nb *NotifyBundle) ListenClose(n Network, a ma.Multiaddr) {\n\tif nb.ListenCloseF != nil {\n\t\tnb.ListenCloseF(n, a)\n\t}\n}\n\n// Connected calls ConnectedF if it is not null.\nfunc (nb *NotifyBundle) Connected(n Network, c Conn) {\n\tif nb.ConnectedF != nil {\n\t\tnb.ConnectedF(n, c)\n\t}\n}\n\n// Disconnected calls DisconnectedF if it is not null.\nfunc (nb *NotifyBundle) Disconnected(n Network, c Conn) {\n\tif nb.DisconnectedF != nil {\n\t\tnb.DisconnectedF(n, c)\n\t}\n}\n\n// Global noop notifiee. Do not change.\nvar GlobalNoopNotifiee = &NoopNotifiee{}\n\ntype NoopNotifiee struct{}\n\nvar _ Notifiee = (*NoopNotifiee)(nil)\n\nfunc (nn *NoopNotifiee) Connected(_ Network, _ Conn)           {}\nfunc (nn *NoopNotifiee) Disconnected(_ Network, _ Conn)        {}\nfunc (nn *NoopNotifiee) Listen(_ Network, _ ma.Multiaddr)      {}\nfunc (nn *NoopNotifiee) ListenClose(_ Network, _ ma.Multiaddr) {}\n"
  },
  {
    "path": "core/network/notifee_test.go",
    "content": "package network\n\nimport (\n\t\"testing\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\nfunc TestListen(T *testing.T) {\n\tvar notifee NotifyBundle\n\taddr, err := ma.NewMultiaddr(\"/ip4/127.0.0.1/udp/1234\")\n\tif err != nil {\n\t\tT.Fatal(\"unexpected multiaddr error\")\n\t}\n\tnotifee.Listen(nil, addr)\n\n\tcalled := false\n\tnotifee.ListenF = func(Network, ma.Multiaddr) {\n\t\tcalled = true\n\t}\n\tif called {\n\t\tT.Fatal(\"called should be false\")\n\t}\n\n\tnotifee.Listen(nil, addr)\n\tif !called {\n\t\tT.Fatal(\"Listen should have been called\")\n\t}\n}\n\nfunc TestListenClose(T *testing.T) {\n\tvar notifee NotifyBundle\n\taddr, err := ma.NewMultiaddr(\"/ip4/127.0.0.1/udp/1234\")\n\tif err != nil {\n\t\tT.Fatal(\"unexpected multiaddr error\")\n\t}\n\tnotifee.ListenClose(nil, addr)\n\n\tcalled := false\n\tnotifee.ListenCloseF = func(Network, ma.Multiaddr) {\n\t\tcalled = true\n\t}\n\tif called {\n\t\tT.Fatal(\"called should be false\")\n\t}\n\n\tnotifee.ListenClose(nil, addr)\n\tif !called {\n\t\tT.Fatal(\"ListenClose should have been called\")\n\t}\n}\n\nfunc TestConnected(T *testing.T) {\n\tvar notifee NotifyBundle\n\tnotifee.Connected(nil, nil)\n\n\tcalled := false\n\tnotifee.ConnectedF = func(Network, Conn) {\n\t\tcalled = true\n\t}\n\tif called {\n\t\tT.Fatal(\"called should be false\")\n\t}\n\n\tnotifee.Connected(nil, nil)\n\tif !called {\n\t\tT.Fatal(\"Connected should have been called\")\n\t}\n}\n\nfunc TestDisconnected(T *testing.T) {\n\tvar notifee NotifyBundle\n\tnotifee.Disconnected(nil, nil)\n\n\tcalled := false\n\tnotifee.DisconnectedF = func(Network, Conn) {\n\t\tcalled = true\n\t}\n\tif called {\n\t\tT.Fatal(\"called should be false\")\n\t}\n\n\tnotifee.Disconnected(nil, nil)\n\tif !called {\n\t\tT.Fatal(\"Disconnected should have been called\")\n\t}\n}\n"
  },
  {
    "path": "core/network/rcmgr.go",
    "content": "package network\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\t\"github.com/multiformats/go-multiaddr\"\n)\n\n// ResourceManager is the interface to the network resource management subsystem.\n// The ResourceManager tracks and accounts for resource usage in the stack, from the internals\n// to the application, and provides a mechanism to limit resource usage according to a user\n// configurable policy.\n//\n// Resource Management through the ResourceManager is based on the concept of Resource\n// Management Scopes, whereby resource usage is constrained by a DAG of scopes,\n// The following diagram illustrates the structure of the resource constraint DAG:\n// System\n//\n//\t+------------> Transient.............+................+\n//\t|                                    .                .\n//\t+------------>  Service------------- . ----------+    .\n//\t|                                    .           |    .\n//\t+------------->  Protocol----------- . ----------+    .\n//\t|                                    .           |    .\n//\t+-------------->  Peer               \\           |    .\n//\t                   +------------> Connection     |    .\n//\t                   |                             \\    \\\n//\t                   +--------------------------->  Stream\n//\n// The basic resources accounted by the ResourceManager include memory, streams, connections,\n// and file descriptors. These account for both space and time used by\n// the stack, as each resource has a direct effect on the system\n// availability and performance.\n//\n// The modus operandi of the resource manager is to restrict resource usage at the time of\n// reservation. When a component of the stack needs to use a resource, it reserves it in the\n// appropriate scope. The resource manager gates the reservation against the scope applicable\n// limits; if the limit is exceeded, then an error (wrapping ErrResourceLimitExceeded) and it\n// is up the component to act accordingly. At the lower levels of the stack, this will normally\n// signal a failure of some sorts, like failing to opening a stream or a connection, which will\n// propagate to the programmer. Some components may be able to handle resource reservation failure\n// more gracefully; for instance a muxer trying to grow a buffer for a window change, will simply\n// retain the existing window size and continue to operate normally albeit with some degraded\n// throughput.\n// All resources reserved in some scope are released when the scope is closed. For low level\n// scopes, mainly Connection and Stream scopes, this happens when the connection or stream is\n// closed.\n//\n// Service programmers will typically use the resource manager to reserve memory\n// for their subsystem.\n// This happens with two avenues: the programmer can attach a stream to a service, whereby\n// resources reserved by the stream are automatically accounted in the service budget; or the\n// programmer may directly interact with the service scope, by using ViewService through the\n// resource manager interface.\n//\n// Application programmers can also directly reserve memory in some applicable scope. In order\n// to facilitate control flow delimited resource accounting, all scopes defined in the system\n// allow for the user to create spans. Spans are temporary scopes rooted at some\n// other scope and release their resources when the programmer is done with them. Span\n// scopes can form trees, with nested spans.\n//\n// Typical Usage:\n//   - Low level components of the system (transports, muxers) all have access to the resource\n//     manager and create connection and stream scopes through it. These scopes are accessible\n//     to the user, albeit with a narrower interface, through Conn and Stream objects who have\n//     a Scope method.\n//   - Services typically center around streams, where the programmer can attach streams to a\n//     particular service. They can also directly reserve memory for a service by accessing the\n//     service scope using the ResourceManager interface.\n//   - Applications that want to account for their network resource usage can reserve memory,\n//     typically using a span, directly in the System or a Service scope; they can also\n//     opt to use appropriate stream scopes for streams that they create or own.\n//\n// User Serviceable Parts: the user has the option to specify their own implementation of the\n// interface. We provide a canonical implementation in the go-libp2p-resource-manager package.\n// The user of that package can specify limits for the various scopes, which can be static\n// or dynamic.\n//\n// WARNING The ResourceManager interface is considered experimental and subject to change\n// in subsequent releases.\ntype ResourceManager interface {\n\tResourceScopeViewer\n\n\t// OpenConnection creates a new connection scope not yet associated with any peer; the connection\n\t// is scoped at the transient scope.\n\t// The caller owns the returned scope and is responsible for calling Done in order to signify\n\t// the end of the scope's span.\n\tOpenConnection(dir Direction, usefd bool, endpoint multiaddr.Multiaddr) (ConnManagementScope, error)\n\n\t// VerifySourceAddress tells the transport to verify the source address for an incoming connection\n\t// before gating the connection with OpenConnection.\n\tVerifySourceAddress(addr net.Addr) bool\n\n\t// OpenStream creates a new stream scope, initially unnegotiated.\n\t// An unnegotiated stream will be initially unattached to any protocol scope\n\t// and constrained by the transient scope.\n\t// The caller owns the returned scope and is responsible for calling Done in order to signify\n\t// the end of th scope's span.\n\tOpenStream(p peer.ID, dir Direction) (StreamManagementScope, error)\n\n\t// Close closes the resource manager\n\tClose() error\n}\n\n// ResourceScopeViewer is a mixin interface providing view methods for accessing top level\n// scopes.\ntype ResourceScopeViewer interface {\n\t// ViewSystem views the system-wide resource scope.\n\t// The system scope is the top level scope that accounts for global\n\t// resource usage at all levels of the system. This scope constrains all\n\t// other scopes and institutes global hard limits.\n\tViewSystem(func(ResourceScope) error) error\n\n\t// ViewTransient views the transient (DMZ) resource scope.\n\t// The transient scope accounts for resources that are in the process of\n\t// full establishment. For instance, a new connection prior to the\n\t// handshake does not belong to any peer, but it still needs to be\n\t// constrained as this opens an avenue for attacks in transient resource\n\t// usage. Similarly, a stream that has not negotiated a protocol yet is\n\t// constrained by the transient scope.\n\tViewTransient(func(ResourceScope) error) error\n\n\t// ViewService retrieves a service-specific scope.\n\tViewService(string, func(ServiceScope) error) error\n\n\t// ViewProtocol views the resource management scope for a specific protocol.\n\tViewProtocol(protocol.ID, func(ProtocolScope) error) error\n\n\t// ViewPeer views the resource management scope for a specific peer.\n\tViewPeer(peer.ID, func(PeerScope) error) error\n}\n\nconst (\n\t// ReservationPriorityLow is a reservation priority that indicates a reservation if the scope\n\t// memory utilization is at 40% or less.\n\tReservationPriorityLow uint8 = 101\n\t// Reservation PriorityMedium is a reservation priority that indicates a reservation if the scope\n\t// memory utilization is at 60% or less.\n\tReservationPriorityMedium uint8 = 152\n\t// ReservationPriorityHigh is a reservation priority that indicates a reservation if the scope\n\t// memory utilization is at 80% or less.\n\tReservationPriorityHigh uint8 = 203\n\t// ReservationPriorityAlways is a reservation priority that indicates a reservation if there is\n\t// enough memory, regardless of scope utilization.\n\tReservationPriorityAlways uint8 = 255\n)\n\n// ResourceScope is the interface for all scopes.\ntype ResourceScope interface {\n\t// ReserveMemory reserves memory/buffer space in the scope; the unit is bytes.\n\t//\n\t// If ReserveMemory returns an error, then no memory was reserved and the caller should handle\n\t// the failure condition.\n\t//\n\t// The priority argument indicates the priority of the memory reservation. A reservation\n\t// will fail if the available memory is less than (1+prio)/256 of the scope limit, providing\n\t// a mechanism to gracefully handle optional reservations that might overload the system.\n\t// For instance, a muxer growing a window buffer will use a low priority and only grow the buffer\n\t// if there is no memory pressure in the system.\n\t//\n\t// There are 4 predefined priority levels, Low, Medium, High and Always,\n\t// capturing common patterns, but the user is free to use any granularity applicable to his case.\n\tReserveMemory(size int, prio uint8) error\n\n\t// ReleaseMemory explicitly releases memory previously reserved with ReserveMemory\n\tReleaseMemory(size int)\n\n\t// Stat retrieves current resource usage for the scope.\n\tStat() ScopeStat\n\n\t// BeginSpan creates a new span scope rooted at this scope\n\tBeginSpan() (ResourceScopeSpan, error)\n}\n\n// ResourceScopeSpan is a ResourceScope with a delimited span.\n// Span scopes are control flow delimited and release all their associated resources\n// when the programmer calls Done.\n//\n// Example:\n//\n//\ts, err := someScope.BeginSpan()\n//\tif err != nil { ... }\n//\tdefer s.Done()\n//\n//\tif err := s.ReserveMemory(...); err != nil { ... }\n//\t// ... use memory\ntype ResourceScopeSpan interface {\n\tResourceScope\n\t// Done ends the span and releases associated resources.\n\tDone()\n}\n\n// ServiceScope is the interface for service resource scopes\ntype ServiceScope interface {\n\tResourceScope\n\n\t// Name returns the name of this service\n\tName() string\n}\n\n// ProtocolScope is the interface for protocol resource scopes.\ntype ProtocolScope interface {\n\tResourceScope\n\n\t// Protocol returns the protocol for this scope\n\tProtocol() protocol.ID\n}\n\n// PeerScope is the interface for peer resource scopes.\ntype PeerScope interface {\n\tResourceScope\n\n\t// Peer returns the peer ID for this scope\n\tPeer() peer.ID\n}\n\n// ConnManagementScope is the low level interface for connection resource scopes.\n// This interface is used by the low level components of the system who create and own\n// the span of a connection scope.\ntype ConnManagementScope interface {\n\tResourceScopeSpan\n\n\t// PeerScope returns the peer scope associated with this connection.\n\t// It returns nil if the connection is not yet associated with any peer.\n\tPeerScope() PeerScope\n\n\t// SetPeer sets the peer for a previously unassociated connection\n\tSetPeer(peer.ID) error\n}\n\n// ConnScope is the user view of a connection scope\ntype ConnScope interface {\n\tResourceScope\n}\n\n// StreamManagementScope is the interface for stream resource scopes.\n// This interface is used by the low level components of the system who create and own\n// the span of a stream scope.\ntype StreamManagementScope interface {\n\tResourceScopeSpan\n\n\t// ProtocolScope returns the protocol resource scope associated with this stream.\n\t// It returns nil if the stream is not associated with any protocol scope.\n\tProtocolScope() ProtocolScope\n\t// SetProtocol sets the protocol for a previously unnegotiated stream\n\tSetProtocol(proto protocol.ID) error\n\n\t// ServiceScope returns the service owning the stream, if any.\n\tServiceScope() ServiceScope\n\t// SetService sets the service owning this stream.\n\tSetService(srv string) error\n\n\t// PeerScope returns the peer resource scope associated with this stream.\n\tPeerScope() PeerScope\n}\n\n// StreamScope is the user view of a StreamScope.\ntype StreamScope interface {\n\tResourceScope\n\n\t// SetService sets the service owning this stream.\n\tSetService(srv string) error\n}\n\n// ScopeStat is a struct containing resource accounting information.\ntype ScopeStat struct {\n\tNumStreamsInbound  int\n\tNumStreamsOutbound int\n\tNumConnsInbound    int\n\tNumConnsOutbound   int\n\tNumFD              int\n\n\tMemory int64\n}\n\n// connManagementScopeKey is the key to store Scope in contexts\ntype connManagementScopeKey struct{}\n\nfunc WithConnManagementScope(ctx context.Context, scope ConnManagementScope) context.Context {\n\treturn context.WithValue(ctx, connManagementScopeKey{}, scope)\n}\n\nfunc UnwrapConnManagementScope(ctx context.Context) (ConnManagementScope, error) {\n\tv := ctx.Value(connManagementScopeKey{})\n\tif v == nil {\n\t\treturn nil, errors.New(\"context has no ConnManagementScope\")\n\t}\n\tscope, ok := v.(ConnManagementScope)\n\tif !ok {\n\t\treturn nil, errors.New(\"context has no ConnManagementScope\")\n\t}\n\treturn scope, nil\n}\n\n// NullResourceManager is a stub for tests and initialization of default values\ntype NullResourceManager struct{}\n\nvar _ ResourceManager = (*NullResourceManager)(nil)\n\nvar _ ResourceScope = (*NullScope)(nil)\nvar _ ResourceScopeSpan = (*NullScope)(nil)\nvar _ ServiceScope = (*NullScope)(nil)\nvar _ ProtocolScope = (*NullScope)(nil)\nvar _ PeerScope = (*NullScope)(nil)\nvar _ ConnManagementScope = (*NullScope)(nil)\nvar _ ConnScope = (*NullScope)(nil)\nvar _ StreamManagementScope = (*NullScope)(nil)\nvar _ StreamScope = (*NullScope)(nil)\n\n// NullScope is a stub for tests and initialization of default values\ntype NullScope struct{}\n\nfunc (n *NullResourceManager) ViewSystem(f func(ResourceScope) error) error {\n\treturn f(&NullScope{})\n}\nfunc (n *NullResourceManager) ViewTransient(f func(ResourceScope) error) error {\n\treturn f(&NullScope{})\n}\nfunc (n *NullResourceManager) ViewService(_ string, f func(ServiceScope) error) error {\n\treturn f(&NullScope{})\n}\nfunc (n *NullResourceManager) ViewProtocol(_ protocol.ID, f func(ProtocolScope) error) error {\n\treturn f(&NullScope{})\n}\nfunc (n *NullResourceManager) ViewPeer(_ peer.ID, f func(PeerScope) error) error {\n\treturn f(&NullScope{})\n}\nfunc (n *NullResourceManager) OpenConnection(_ Direction, _ bool, _ multiaddr.Multiaddr) (ConnManagementScope, error) {\n\treturn &NullScope{}, nil\n}\nfunc (n *NullResourceManager) OpenStream(_ peer.ID, _ Direction) (StreamManagementScope, error) {\n\treturn &NullScope{}, nil\n}\nfunc (*NullResourceManager) VerifySourceAddress(_ net.Addr) bool {\n\treturn false\n}\n\nfunc (n *NullResourceManager) Close() error {\n\treturn nil\n}\n\nfunc (n *NullScope) ReserveMemory(_ int, _ uint8) error    { return nil }\nfunc (n *NullScope) ReleaseMemory(_ int)                   {}\nfunc (n *NullScope) Stat() ScopeStat                       { return ScopeStat{} }\nfunc (n *NullScope) BeginSpan() (ResourceScopeSpan, error) { return &NullScope{}, nil }\nfunc (n *NullScope) Done()                                 {}\nfunc (n *NullScope) Name() string                          { return \"\" }\nfunc (n *NullScope) Protocol() protocol.ID                 { return \"\" }\nfunc (n *NullScope) Peer() peer.ID                         { return \"\" }\nfunc (n *NullScope) PeerScope() PeerScope                  { return &NullScope{} }\nfunc (n *NullScope) SetPeer(peer.ID) error                 { return nil }\nfunc (n *NullScope) ProtocolScope() ProtocolScope          { return &NullScope{} }\nfunc (n *NullScope) SetProtocol(_ protocol.ID) error       { return nil }\nfunc (n *NullScope) ServiceScope() ServiceScope            { return &NullScope{} }\nfunc (n *NullScope) SetService(_ string) error             { return nil }\nfunc (n *NullScope) VerifySourceAddress(_ net.Addr) bool   { return false }\n"
  },
  {
    "path": "core/network/stream.go",
    "content": "package network\n\nimport (\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n)\n\n// Stream represents a bidirectional channel between two agents in\n// a libp2p network. \"agent\" is as granular as desired, potentially\n// being a \"request -> reply\" pair, or whole protocols.\n//\n// Streams are backed by a multiplexer underneath the hood.\ntype Stream interface {\n\tMuxedStream\n\n\t// ID returns an identifier that uniquely identifies this Stream within this\n\t// host, during this run. Stream IDs may repeat across restarts.\n\tID() string\n\n\tProtocol() protocol.ID\n\tSetProtocol(id protocol.ID) error\n\n\t// Stat returns metadata pertaining to this stream.\n\tStat() Stats\n\n\t// Conn returns the connection this stream is part of.\n\tConn() Conn\n\n\t// Scope returns the user's view of this stream's resource scope\n\tScope() StreamScope\n\n\t// ResetWithError closes both ends of the stream with errCode. The errCode is sent\n\t// to the peer.\n\tResetWithError(errCode StreamErrorCode) error\n}\n"
  },
  {
    "path": "core/peer/addrinfo.go",
    "content": "package peer\n\nimport (\n\t\"fmt\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\n// AddrInfo is a small struct used to pass around a peer with\n// a set of addresses (and later, keys?).\ntype AddrInfo struct {\n\tID    ID\n\tAddrs []ma.Multiaddr\n}\n\nvar _ fmt.Stringer = AddrInfo{}\n\nfunc (pi AddrInfo) String() string {\n\treturn fmt.Sprintf(\"{%v: %v}\", pi.ID, pi.Addrs)\n}\n\nvar ErrInvalidAddr = fmt.Errorf(\"invalid p2p multiaddr\")\n\n// AddrInfosFromP2pAddrs converts a set of Multiaddrs to a set of AddrInfos.\nfunc AddrInfosFromP2pAddrs(maddrs ...ma.Multiaddr) ([]AddrInfo, error) {\n\tm := make(map[ID][]ma.Multiaddr)\n\tfor _, maddr := range maddrs {\n\t\ttransport, id := SplitAddr(maddr)\n\t\tif id == \"\" {\n\t\t\treturn nil, ErrInvalidAddr\n\t\t}\n\t\tif transport == nil {\n\t\t\tif _, ok := m[id]; !ok {\n\t\t\t\tm[id] = nil\n\t\t\t}\n\t\t} else {\n\t\t\tm[id] = append(m[id], transport)\n\t\t}\n\t}\n\tais := make([]AddrInfo, 0, len(m))\n\tfor id, maddrs := range m {\n\t\tais = append(ais, AddrInfo{ID: id, Addrs: maddrs})\n\t}\n\treturn ais, nil\n}\n\n// SplitAddr splits a p2p Multiaddr into a transport multiaddr and a peer ID.\n//\n// * Returns a nil transport if the address only contains a /p2p part.\n// * Returns an empty peer ID if the address doesn't contain a /p2p part.\nfunc SplitAddr(m ma.Multiaddr) (transport ma.Multiaddr, id ID) {\n\tif m == nil {\n\t\treturn nil, \"\"\n\t}\n\n\ttransport, p2ppart := ma.SplitLast(m)\n\tif p2ppart == nil || p2ppart.Protocol().Code != ma.P_P2P {\n\t\treturn m, \"\"\n\t}\n\tid = ID(p2ppart.RawValue()) // already validated by the multiaddr library.\n\treturn transport, id\n}\n\n// IDFromP2PAddr extracts the peer ID from a p2p Multiaddr\nfunc IDFromP2PAddr(m ma.Multiaddr) (ID, error) {\n\tif m == nil {\n\t\treturn \"\", ErrInvalidAddr\n\t}\n\tvar lastComponent ma.Component\n\tma.ForEach(m, func(c ma.Component) bool {\n\t\tlastComponent = c\n\t\treturn true\n\t})\n\tif lastComponent.Protocol().Code != ma.P_P2P {\n\t\treturn \"\", ErrInvalidAddr\n\t}\n\n\tid := ID(lastComponent.RawValue()) // already validated by the multiaddr library.\n\treturn id, nil\n}\n\n// AddrInfoFromString builds an AddrInfo from the string representation of a Multiaddr\nfunc AddrInfoFromString(s string) (*AddrInfo, error) {\n\ta, err := ma.NewMultiaddr(s)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn AddrInfoFromP2pAddr(a)\n}\n\n// AddrInfoFromP2pAddr converts a Multiaddr to an AddrInfo.\nfunc AddrInfoFromP2pAddr(m ma.Multiaddr) (*AddrInfo, error) {\n\ttransport, id := SplitAddr(m)\n\tif id == \"\" {\n\t\treturn nil, ErrInvalidAddr\n\t}\n\tinfo := &AddrInfo{ID: id}\n\tif transport != nil {\n\t\tinfo.Addrs = []ma.Multiaddr{transport}\n\t}\n\treturn info, nil\n}\n\n// AddrInfoToP2pAddrs converts an AddrInfo to a list of Multiaddrs.\nfunc AddrInfoToP2pAddrs(pi *AddrInfo) ([]ma.Multiaddr, error) {\n\tp2ppart, err := ma.NewComponent(\"p2p\", pi.ID.String())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(pi.Addrs) == 0 {\n\t\treturn []ma.Multiaddr{p2ppart.Multiaddr()}, nil\n\t}\n\taddrs := make([]ma.Multiaddr, 0, len(pi.Addrs))\n\tfor _, addr := range pi.Addrs {\n\t\taddrs = append(addrs, addr.Encapsulate(p2ppart))\n\t}\n\treturn addrs, nil\n}\n\nfunc (pi *AddrInfo) Loggable() map[string]any {\n\treturn map[string]any{\n\t\t\"peerID\": pi.ID.String(),\n\t\t\"addrs\":  pi.Addrs,\n\t}\n}\n\n// AddrInfosToIDs extracts the peer IDs from the passed AddrInfos and returns them in-order.\nfunc AddrInfosToIDs(pis []AddrInfo) []ID {\n\tps := make([]ID, len(pis))\n\tfor i, pi := range pis {\n\t\tps[i] = pi.ID\n\t}\n\treturn ps\n}\n"
  },
  {
    "path": "core/peer/addrinfo_serde.go",
    "content": "package peer\n\nimport (\n\t\"encoding/json\"\n\n\t\"github.com/libp2p/go-libp2p/core/internal/catch\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\n// Helper struct for decoding as we can't unmarshal into an interface (Multiaddr).\ntype addrInfoJson struct {\n\tID    ID\n\tAddrs []string\n}\n\nfunc (pi AddrInfo) MarshalJSON() (res []byte, err error) {\n\tdefer func() { catch.HandlePanic(recover(), &err, \"libp2p addr info marshal\") }()\n\n\taddrs := make([]string, len(pi.Addrs))\n\tfor i, addr := range pi.Addrs {\n\t\taddrs[i] = addr.String()\n\t}\n\treturn json.Marshal(&addrInfoJson{\n\t\tID:    pi.ID,\n\t\tAddrs: addrs,\n\t})\n}\n\nfunc (pi *AddrInfo) UnmarshalJSON(b []byte) (err error) {\n\tdefer func() { catch.HandlePanic(recover(), &err, \"libp2p addr info unmarshal\") }()\n\tvar data addrInfoJson\n\tif err := json.Unmarshal(b, &data); err != nil {\n\t\treturn err\n\t}\n\taddrs := make([]ma.Multiaddr, len(data.Addrs))\n\tfor i, addr := range data.Addrs {\n\t\tmaddr, err := ma.NewMultiaddr(addr)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\taddrs[i] = maddr\n\t}\n\n\tpi.ID = data.ID\n\tpi.Addrs = addrs\n\treturn nil\n}\n"
  },
  {
    "path": "core/peer/addrinfo_test.go",
    "content": "package peer_test\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/stretchr/testify/require\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\nvar (\n\ttestID                         ID\n\tmaddrFull, maddrTpt, maddrPeer ma.Multiaddr\n)\n\nfunc init() {\n\tvar err error\n\ttestID, err = Decode(\"QmS3zcG7LhYZYSJMhyRZvTddvbNUqtt8BJpaSs6mi1K5Va\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tmaddrPeer = ma.StringCast(\"/p2p/\" + testID.String())\n\tmaddrTpt = ma.StringCast(\"/ip4/127.0.0.1/tcp/1234\")\n\tmaddrFull = maddrTpt.Encapsulate(maddrPeer)\n}\n\nfunc TestSplitAddr(t *testing.T) {\n\ttpt, id := SplitAddr(maddrFull)\n\tif !tpt.Equal(maddrTpt) {\n\t\tt.Fatal(\"expected transport\")\n\t}\n\tif id != testID {\n\t\tt.Fatalf(\"%s != %s\", id, testID)\n\t}\n\n\ttpt, id = SplitAddr(maddrPeer)\n\tif tpt != nil {\n\t\tt.Fatal(\"expected no transport\")\n\t}\n\tif id != testID {\n\t\tt.Fatalf(\"%s != %s\", id, testID)\n\t}\n\n\ttpt, id = SplitAddr(maddrTpt)\n\tif !tpt.Equal(maddrTpt) {\n\t\tt.Fatal(\"expected a transport\")\n\t}\n\tif id != \"\" {\n\t\tt.Fatal(\"expected no peer ID\")\n\t}\n}\n\nfunc TestIDFromP2PAddr(t *testing.T) {\n\tid, err := IDFromP2PAddr(maddrFull)\n\trequire.NoError(t, err)\n\trequire.Equal(t, testID, id)\n\n\tid, err = IDFromP2PAddr(maddrPeer)\n\trequire.NoError(t, err)\n\trequire.Equal(t, testID, id)\n\n\t_, err = IDFromP2PAddr(maddrTpt)\n\trequire.ErrorIs(t, err, ErrInvalidAddr)\n}\n\nfunc TestAddrInfoFromP2pAddr(t *testing.T) {\n\tai, err := AddrInfoFromP2pAddr(maddrFull)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif len(ai.Addrs) != 1 || !ai.Addrs[0].Equal(maddrTpt) {\n\t\tt.Fatal(\"expected transport\")\n\t}\n\tif ai.ID != testID {\n\t\tt.Fatalf(\"%s != %s\", ai.ID, testID)\n\t}\n\n\tai, err = AddrInfoFromP2pAddr(maddrPeer)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif len(ai.Addrs) != 0 {\n\t\tt.Fatal(\"expected transport\")\n\t}\n\tif ai.ID != testID {\n\t\tt.Fatalf(\"%s != %s\", ai.ID, testID)\n\t}\n\n\t_, err = AddrInfoFromP2pAddr(maddrTpt)\n\tif err != ErrInvalidAddr {\n\t\tt.Fatalf(\"wrong error: %s\", err)\n\t}\n}\n\nfunc TestAddrInfosFromP2pAddrs(t *testing.T) {\n\tinfos, err := AddrInfosFromP2pAddrs()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif len(infos) != 0 {\n\t\tt.Fatal(\"expected no addrs\")\n\t}\n\tif _, err = AddrInfosFromP2pAddrs(nil); err == nil {\n\t\tt.Fatal(\"expected nil multiaddr to fail\")\n\t}\n\n\taddrs := []ma.Multiaddr{\n\t\tma.StringCast(\"/ip4/128.199.219.111/tcp/4001/ipfs/QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64\"),\n\t\tma.StringCast(\"/ip4/104.236.76.40/tcp/4001/ipfs/QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64\"),\n\n\t\tma.StringCast(\"/ipfs/QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd\"),\n\t\tma.StringCast(\"/ip4/178.62.158.247/tcp/4001/ipfs/QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd\"),\n\n\t\tma.StringCast(\"/ipfs/QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM\"),\n\t}\n\texpected := map[string][]ma.Multiaddr{\n\t\t\"QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64\": {\n\t\t\tma.StringCast(\"/ip4/128.199.219.111/tcp/4001\"),\n\t\t\tma.StringCast(\"/ip4/104.236.76.40/tcp/4001\"),\n\t\t},\n\t\t\"QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd\": {\n\t\t\tma.StringCast(\"/ip4/178.62.158.247/tcp/4001\"),\n\t\t},\n\t\t\"QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM\": nil,\n\t}\n\tinfos, err = AddrInfosFromP2pAddrs(addrs...)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tfor _, info := range infos {\n\t\texaddrs, ok := expected[info.ID.String()]\n\t\tif !ok {\n\t\t\tt.Fatalf(\"didn't expect peer %s\", info.ID)\n\t\t}\n\t\tif len(info.Addrs) != len(exaddrs) {\n\t\t\tt.Fatalf(\"got %d addrs, expected %d\", len(info.Addrs), len(exaddrs))\n\t\t}\n\t\t// AddrInfosFromP2pAddrs preserves order. I'd like to keep this\n\t\t// guarantee for now.\n\t\tfor i, addr := range info.Addrs {\n\t\t\tif !exaddrs[i].Equal(addr) {\n\t\t\t\tt.Fatalf(\"expected %s, got %s\", exaddrs[i], addr)\n\t\t\t}\n\t\t}\n\t\tdelete(expected, info.ID.String())\n\t}\n}\n\nfunc TestAddrInfoJSON(t *testing.T) {\n\tai := AddrInfo{ID: testID, Addrs: []ma.Multiaddr{maddrFull}}\n\tout, err := ai.MarshalJSON()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tvar addrInfo AddrInfo\n\tif err := addrInfo.UnmarshalJSON(out); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif addrInfo.ID != testID {\n\t\tt.Fatalf(\"expected ID to equal %s, got %s\", testID, addrInfo.ID)\n\t}\n\tif len(addrInfo.Addrs) != 1 || !addrInfo.Addrs[0].Equal(maddrFull) {\n\t\tt.Fatalf(\"expected addrs to match %v, got %v\", maddrFull, addrInfo.Addrs)\n\t}\n}\n"
  },
  {
    "path": "core/peer/pb/peer_record.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.6\n// \tprotoc        v5.29.2\n// source: core/peer/pb/peer_record.proto\n\npackage pb\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// PeerRecord messages contain information that is useful to share with other peers.\n// Currently, a PeerRecord contains the public listen addresses for a peer, but this\n// is expected to expand to include other information in the future.\n//\n// PeerRecords are designed to be serialized to bytes and placed inside of\n// SignedEnvelopes before sharing with other peers.\n// See https://github.com/libp2p/go-libp2p/blob/master/core/record/pb/envelope.proto for\n// the SignedEnvelope definition.\ntype PeerRecord struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// peer_id contains a libp2p peer id in its binary representation.\n\tPeerId []byte `protobuf:\"bytes,1,opt,name=peer_id,json=peerId,proto3\" json:\"peer_id,omitempty\"`\n\t// seq contains a monotonically-increasing sequence counter to order PeerRecords in time.\n\tSeq uint64 `protobuf:\"varint,2,opt,name=seq,proto3\" json:\"seq,omitempty\"`\n\t// addresses is a list of public listen addresses for the peer.\n\tAddresses     []*PeerRecord_AddressInfo `protobuf:\"bytes,3,rep,name=addresses,proto3\" json:\"addresses,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *PeerRecord) Reset() {\n\t*x = PeerRecord{}\n\tmi := &file_core_peer_pb_peer_record_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *PeerRecord) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*PeerRecord) ProtoMessage() {}\n\nfunc (x *PeerRecord) ProtoReflect() protoreflect.Message {\n\tmi := &file_core_peer_pb_peer_record_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use PeerRecord.ProtoReflect.Descriptor instead.\nfunc (*PeerRecord) Descriptor() ([]byte, []int) {\n\treturn file_core_peer_pb_peer_record_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *PeerRecord) GetPeerId() []byte {\n\tif x != nil {\n\t\treturn x.PeerId\n\t}\n\treturn nil\n}\n\nfunc (x *PeerRecord) GetSeq() uint64 {\n\tif x != nil {\n\t\treturn x.Seq\n\t}\n\treturn 0\n}\n\nfunc (x *PeerRecord) GetAddresses() []*PeerRecord_AddressInfo {\n\tif x != nil {\n\t\treturn x.Addresses\n\t}\n\treturn nil\n}\n\n// AddressInfo is a wrapper around a binary multiaddr. It is defined as a\n// separate message to allow us to add per-address metadata in the future.\ntype PeerRecord_AddressInfo struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tMultiaddr     []byte                 `protobuf:\"bytes,1,opt,name=multiaddr,proto3\" json:\"multiaddr,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *PeerRecord_AddressInfo) Reset() {\n\t*x = PeerRecord_AddressInfo{}\n\tmi := &file_core_peer_pb_peer_record_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *PeerRecord_AddressInfo) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*PeerRecord_AddressInfo) ProtoMessage() {}\n\nfunc (x *PeerRecord_AddressInfo) ProtoReflect() protoreflect.Message {\n\tmi := &file_core_peer_pb_peer_record_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use PeerRecord_AddressInfo.ProtoReflect.Descriptor instead.\nfunc (*PeerRecord_AddressInfo) Descriptor() ([]byte, []int) {\n\treturn file_core_peer_pb_peer_record_proto_rawDescGZIP(), []int{0, 0}\n}\n\nfunc (x *PeerRecord_AddressInfo) GetMultiaddr() []byte {\n\tif x != nil {\n\t\treturn x.Multiaddr\n\t}\n\treturn nil\n}\n\nvar File_core_peer_pb_peer_record_proto protoreflect.FileDescriptor\n\nconst file_core_peer_pb_peer_record_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x1ecore/peer/pb/peer_record.proto\\x12\\apeer.pb\\\"\\xa3\\x01\\n\" +\n\t\"\\n\" +\n\t\"PeerRecord\\x12\\x17\\n\" +\n\t\"\\apeer_id\\x18\\x01 \\x01(\\fR\\x06peerId\\x12\\x10\\n\" +\n\t\"\\x03seq\\x18\\x02 \\x01(\\x04R\\x03seq\\x12=\\n\" +\n\t\"\\taddresses\\x18\\x03 \\x03(\\v2\\x1f.peer.pb.PeerRecord.AddressInfoR\\taddresses\\x1a+\\n\" +\n\t\"\\vAddressInfo\\x12\\x1c\\n\" +\n\t\"\\tmultiaddr\\x18\\x01 \\x01(\\fR\\tmultiaddrB*Z(github.com/libp2p/go-libp2p/core/peer/pbb\\x06proto3\"\n\nvar (\n\tfile_core_peer_pb_peer_record_proto_rawDescOnce sync.Once\n\tfile_core_peer_pb_peer_record_proto_rawDescData []byte\n)\n\nfunc file_core_peer_pb_peer_record_proto_rawDescGZIP() []byte {\n\tfile_core_peer_pb_peer_record_proto_rawDescOnce.Do(func() {\n\t\tfile_core_peer_pb_peer_record_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_core_peer_pb_peer_record_proto_rawDesc), len(file_core_peer_pb_peer_record_proto_rawDesc)))\n\t})\n\treturn file_core_peer_pb_peer_record_proto_rawDescData\n}\n\nvar file_core_peer_pb_peer_record_proto_msgTypes = make([]protoimpl.MessageInfo, 2)\nvar file_core_peer_pb_peer_record_proto_goTypes = []any{\n\t(*PeerRecord)(nil),             // 0: peer.pb.PeerRecord\n\t(*PeerRecord_AddressInfo)(nil), // 1: peer.pb.PeerRecord.AddressInfo\n}\nvar file_core_peer_pb_peer_record_proto_depIdxs = []int32{\n\t1, // 0: peer.pb.PeerRecord.addresses:type_name -> peer.pb.PeerRecord.AddressInfo\n\t1, // [1:1] is the sub-list for method output_type\n\t1, // [1:1] is the sub-list for method input_type\n\t1, // [1:1] is the sub-list for extension type_name\n\t1, // [1:1] is the sub-list for extension extendee\n\t0, // [0:1] is the sub-list for field type_name\n}\n\nfunc init() { file_core_peer_pb_peer_record_proto_init() }\nfunc file_core_peer_pb_peer_record_proto_init() {\n\tif File_core_peer_pb_peer_record_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_core_peer_pb_peer_record_proto_rawDesc), len(file_core_peer_pb_peer_record_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   2,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_core_peer_pb_peer_record_proto_goTypes,\n\t\tDependencyIndexes: file_core_peer_pb_peer_record_proto_depIdxs,\n\t\tMessageInfos:      file_core_peer_pb_peer_record_proto_msgTypes,\n\t}.Build()\n\tFile_core_peer_pb_peer_record_proto = out.File\n\tfile_core_peer_pb_peer_record_proto_goTypes = nil\n\tfile_core_peer_pb_peer_record_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "core/peer/pb/peer_record.proto",
    "content": "syntax = \"proto3\";\n\npackage peer.pb;\n\noption go_package = \"github.com/libp2p/go-libp2p/core/peer/pb\";\n\n// PeerRecord messages contain information that is useful to share with other peers.\n// Currently, a PeerRecord contains the public listen addresses for a peer, but this\n// is expected to expand to include other information in the future.\n//\n// PeerRecords are designed to be serialized to bytes and placed inside of\n// SignedEnvelopes before sharing with other peers.\n// See https://github.com/libp2p/go-libp2p/blob/master/core/record/pb/envelope.proto for\n// the SignedEnvelope definition.\nmessage PeerRecord {\n\n    // AddressInfo is a wrapper around a binary multiaddr. It is defined as a\n    // separate message to allow us to add per-address metadata in the future.\n    message AddressInfo {\n        bytes multiaddr = 1;\n    }\n\n    // peer_id contains a libp2p peer id in its binary representation.\n    bytes peer_id = 1;\n\n    // seq contains a monotonically-increasing sequence counter to order PeerRecords in time.\n    uint64 seq = 2;\n\n    // addresses is a list of public listen addresses for the peer.\n    repeated AddressInfo addresses = 3;\n}\n"
  },
  {
    "path": "core/peer/peer.go",
    "content": "// Package peer implements an object used to represent peers in the libp2p network.\npackage peer\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/ipfs/go-cid\"\n\tic \"github.com/libp2p/go-libp2p/core/crypto\"\n\tb58 \"github.com/mr-tron/base58/base58\"\n\tmc \"github.com/multiformats/go-multicodec\"\n\tmh \"github.com/multiformats/go-multihash\"\n)\n\nvar (\n\t// ErrEmptyPeerID is an error for empty peer ID.\n\tErrEmptyPeerID = errors.New(\"empty peer ID\")\n\t// ErrNoPublicKey is an error for peer IDs that don't embed public keys\n\tErrNoPublicKey = errors.New(\"public key is not embedded in peer ID\")\n)\n\n// AdvancedEnableInlining enables automatically inlining keys shorter than\n// 42 bytes into the peer ID (using the \"identity\" multihash function).\n//\n// WARNING: This flag will likely be set to false in the future and eventually\n// be removed in favor of using a hash function specified by the key itself.\n// See: https://github.com/libp2p/specs/issues/138\n//\n// DO NOT change this flag unless you know what you're doing.\n//\n// This currently defaults to true for backwards compatibility but will likely\n// be set to false by default when an upgrade path is determined.\nvar AdvancedEnableInlining = true\n\nconst maxInlineKeyLength = 42\n\n// ID is a libp2p peer identity.\n//\n// Peer IDs are derived by hashing a peer's public key and encoding the\n// hash output as a multihash. See IDFromPublicKey for details.\ntype ID string\n\n// Loggable returns a pretty peer ID string in loggable JSON format.\nfunc (id ID) Loggable() map[string]any {\n\treturn map[string]any{\n\t\t\"peerID\": id.String(),\n\t}\n}\n\nfunc (id ID) String() string {\n\treturn b58.Encode([]byte(id))\n}\n\n// ShortString prints out the peer ID.\n//\n// TODO(brian): ensure correctness at ID generation and\n// enforce this by only exposing functions that generate\n// IDs safely. Then any peer.ID type found in the\n// codebase is known to be correct.\nfunc (id ID) ShortString() string {\n\tpid := id.String()\n\tif len(pid) <= 10 {\n\t\treturn fmt.Sprintf(\"<peer.ID %s>\", pid)\n\t}\n\treturn fmt.Sprintf(\"<peer.ID %s*%s>\", pid[:2], pid[len(pid)-6:])\n}\n\n// MatchesPrivateKey tests whether this ID was derived from the secret key sk.\nfunc (id ID) MatchesPrivateKey(sk ic.PrivKey) bool {\n\treturn id.MatchesPublicKey(sk.GetPublic())\n}\n\n// MatchesPublicKey tests whether this ID was derived from the public key pk.\nfunc (id ID) MatchesPublicKey(pk ic.PubKey) bool {\n\toid, err := IDFromPublicKey(pk)\n\tif err != nil {\n\t\treturn false\n\t}\n\treturn oid == id\n}\n\n// ExtractPublicKey attempts to extract the public key from an ID.\n//\n// This method returns ErrNoPublicKey if the peer ID looks valid, but it can't extract\n// the public key.\nfunc (id ID) ExtractPublicKey() (ic.PubKey, error) {\n\tdecoded, err := mh.Decode([]byte(id))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif decoded.Code != mh.IDENTITY {\n\t\treturn nil, ErrNoPublicKey\n\t}\n\tpk, err := ic.UnmarshalPublicKey(decoded.Digest)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn pk, nil\n}\n\n// Validate checks if ID is empty or not.\nfunc (id ID) Validate() error {\n\tif id == ID(\"\") {\n\t\treturn ErrEmptyPeerID\n\t}\n\n\treturn nil\n}\n\n// IDFromBytes casts a byte slice to the ID type, and validates\n// the value to make sure it is a multihash.\nfunc IDFromBytes(b []byte) (ID, error) {\n\tif _, err := mh.Cast(b); err != nil {\n\t\treturn ID(\"\"), err\n\t}\n\treturn ID(b), nil\n}\n\n// Decode accepts an encoded peer ID and returns the decoded ID if the input is\n// valid.\n//\n// The encoded peer ID can either be a CID of a key or a raw multihash (identity\n// or sha256-256).\nfunc Decode(s string) (ID, error) {\n\tif strings.HasPrefix(s, \"Qm\") || strings.HasPrefix(s, \"1\") {\n\t\t// base58 encoded sha256 or identity multihash\n\t\tm, err := mh.FromB58String(s)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"failed to parse peer ID: %s\", err)\n\t\t}\n\t\treturn ID(m), nil\n\t}\n\n\tc, err := cid.Decode(s)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to parse peer ID: %s\", err)\n\t}\n\treturn FromCid(c)\n}\n\n// FromCid converts a CID to a peer ID, if possible.\nfunc FromCid(c cid.Cid) (ID, error) {\n\tcode := mc.Code(c.Type())\n\tif code != mc.Libp2pKey {\n\t\treturn \"\", fmt.Errorf(\"can't convert CID of type %q to a peer ID\", code)\n\t}\n\treturn ID(c.Hash()), nil\n}\n\n// ToCid encodes a peer ID as a CID of the public key.\n//\n// If the peer ID is invalid (e.g., empty), this will return the empty CID.\nfunc ToCid(id ID) cid.Cid {\n\tm, err := mh.Cast([]byte(id))\n\tif err != nil {\n\t\treturn cid.Cid{}\n\t}\n\treturn cid.NewCidV1(cid.Libp2pKey, m)\n}\n\n// IDFromPublicKey returns the Peer ID corresponding to the public key pk.\nfunc IDFromPublicKey(pk ic.PubKey) (ID, error) {\n\tb, err := ic.MarshalPublicKey(pk)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tvar alg uint64 = mh.SHA2_256\n\tif AdvancedEnableInlining && len(b) <= maxInlineKeyLength {\n\t\talg = mh.IDENTITY\n\t}\n\thash, err := mh.Sum(b, alg, -1)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn ID(hash), nil\n}\n\n// IDFromPrivateKey returns the Peer ID corresponding to the secret key sk.\nfunc IDFromPrivateKey(sk ic.PrivKey) (ID, error) {\n\treturn IDFromPublicKey(sk.GetPublic())\n}\n\n// IDSlice for sorting peers.\ntype IDSlice []ID\n\nfunc (es IDSlice) Len() int           { return len(es) }\nfunc (es IDSlice) Swap(i, j int)      { es[i], es[j] = es[j], es[i] }\nfunc (es IDSlice) Less(i, j int) bool { return string(es[i]) < string(es[j]) }\n\nfunc (es IDSlice) String() string {\n\tpeersStrings := make([]string, len(es))\n\tfor i, id := range es {\n\t\tpeersStrings[i] = id.String()\n\t}\n\treturn strings.Join(peersStrings, \", \")\n}\n"
  },
  {
    "path": "core/peer/peer_serde.go",
    "content": "// Package peer contains Protobuf and JSON serialization/deserialization methods for peer IDs.\npackage peer\n\nimport (\n\t\"encoding\"\n\t\"encoding/json\"\n)\n\n// Interface assertions commented out to avoid introducing hard dependencies to protobuf.\n// var _ proto.Marshaler = (*ID)(nil)\n// var _ proto.Unmarshaler = (*ID)(nil)\nvar _ json.Marshaler = (*ID)(nil)\nvar _ json.Unmarshaler = (*ID)(nil)\n\nvar _ encoding.BinaryMarshaler = (*ID)(nil)\nvar _ encoding.BinaryUnmarshaler = (*ID)(nil)\nvar _ encoding.TextMarshaler = (*ID)(nil)\nvar _ encoding.TextUnmarshaler = (*ID)(nil)\n\nfunc (id ID) Marshal() ([]byte, error) {\n\treturn []byte(id), nil\n}\n\n// MarshalBinary returns the byte representation of the peer ID.\nfunc (id ID) MarshalBinary() ([]byte, error) {\n\treturn id.Marshal()\n}\n\nfunc (id ID) MarshalTo(data []byte) (n int, err error) {\n\treturn copy(data, []byte(id)), nil\n}\n\nfunc (id *ID) Unmarshal(data []byte) (err error) {\n\t*id, err = IDFromBytes(data)\n\treturn err\n}\n\n// UnmarshalBinary sets the ID from its binary representation.\nfunc (id *ID) UnmarshalBinary(data []byte) error {\n\treturn id.Unmarshal(data)\n}\n\nfunc (id ID) Size() int {\n\treturn len([]byte(id))\n}\n\nfunc (id ID) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(id.String())\n}\n\nfunc (id *ID) UnmarshalJSON(data []byte) (err error) {\n\tvar v string\n\tif err = json.Unmarshal(data, &v); err != nil {\n\t\treturn err\n\t}\n\t*id, err = Decode(v)\n\treturn err\n}\n\n// MarshalText returns the text encoding of the ID.\nfunc (id ID) MarshalText() ([]byte, error) {\n\treturn []byte(id.String()), nil\n}\n\n// UnmarshalText restores the ID from its text encoding.\nfunc (id *ID) UnmarshalText(data []byte) error {\n\tpid, err := Decode(string(data))\n\tif err != nil {\n\t\treturn err\n\t}\n\t*id = pid\n\treturn nil\n}\n"
  },
  {
    "path": "core/peer/peer_serde_test.go",
    "content": "package peer_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t. \"github.com/libp2p/go-libp2p/core/test\"\n)\n\nfunc TestPeerSerdePB(t *testing.T) {\n\tid, err := RandPeerID()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tb, err := id.Marshal()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tvar id2 peer.ID\n\tif err = id2.Unmarshal(b); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif id != id2 {\n\t\tt.Error(\"expected equal ids in circular serde test\")\n\t}\n}\n\nfunc TestPeerSerdeJSON(t *testing.T) {\n\tid, err := RandPeerID()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tb, err := id.MarshalJSON()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tvar id2 peer.ID\n\tif err = id2.UnmarshalJSON(b); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif id != id2 {\n\t\tt.Error(\"expected equal ids in circular serde test\")\n\t}\n}\n\nfunc TestBinaryMarshaler(t *testing.T) {\n\tid, err := RandPeerID()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tb, err := id.MarshalBinary()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tvar id2 peer.ID\n\tif err = id2.UnmarshalBinary(b); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif id != id2 {\n\t\tt.Error(\"expected equal ids in circular serde test\")\n\t}\n}\n\nfunc TestTextMarshaler(t *testing.T) {\n\tid, err := RandPeerID()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tb, err := id.MarshalText()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tvar id2 peer.ID\n\tif err = id2.UnmarshalText(b); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif id != id2 {\n\t\tt.Error(\"expected equal ids in circular serde test\")\n\t}\n}\n"
  },
  {
    "path": "core/peer/peer_test.go",
    "content": "package peer_test\n\nimport (\n\t\"crypto/rand\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\n\tic \"github.com/libp2p/go-libp2p/core/crypto\"\n\t. \"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/test\"\n\n\tb58 \"github.com/mr-tron/base58/base58\"\n\tmh \"github.com/multiformats/go-multihash\"\n)\n\nvar gen1 keyset // generated\nvar gen2 keyset // generated\nvar man keyset  // manual\n\nfunc hash(b []byte) []byte {\n\th, _ := mh.Sum(b, mh.SHA2_256, -1)\n\treturn []byte(h)\n}\n\nfunc init() {\n\tif err := gen1.generate(); err != nil {\n\t\tpanic(err)\n\t}\n\tif err := gen2.generate(); err != nil {\n\t\tpanic(err)\n\t}\n\n\tskManBytes = strings.Replace(skManBytes, \"\\n\", \"\", -1)\n\tif err := man.load(hpkpMan, skManBytes); err != nil {\n\t\tpanic(err)\n\t}\n}\n\ntype keyset struct {\n\tsk   ic.PrivKey\n\tpk   ic.PubKey\n\thpk  string\n\thpkp string\n}\n\nfunc (ks *keyset) generate() error {\n\tvar err error\n\tks.sk, ks.pk, err = test.RandTestKeyPair(ic.RSA, 2048)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tbpk, err := ic.MarshalPublicKey(ks.pk)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tks.hpk = string(hash(bpk))\n\tks.hpkp = b58.Encode([]byte(ks.hpk))\n\treturn nil\n}\n\nfunc (ks *keyset) load(hpkp, skBytesStr string) error {\n\tskBytes, err := base64.StdEncoding.DecodeString(skBytesStr)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tks.sk, err = ic.UnmarshalPrivateKey(skBytes)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tks.pk = ks.sk.GetPublic()\n\tbpk, err := ic.MarshalPublicKey(ks.pk)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tks.hpk = string(hash(bpk))\n\tks.hpkp = b58.Encode([]byte(ks.hpk))\n\tif ks.hpkp != hpkp {\n\t\treturn fmt.Errorf(\"hpkp doesn't match key. %s\", hpkp)\n\t}\n\treturn nil\n}\n\nfunc TestIDMatchesPublicKey(t *testing.T) {\n\ttest := func(ks keyset) {\n\t\tp1, err := Decode(ks.hpkp)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif ks.hpk != string(p1) {\n\t\t\tt.Error(\"p1 and hpk differ\")\n\t\t}\n\n\t\tif !p1.MatchesPublicKey(ks.pk) {\n\t\t\tt.Fatal(\"p1 does not match pk\")\n\t\t}\n\n\t\tp2, err := IDFromPublicKey(ks.pk)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif p1 != p2 {\n\t\t\tt.Error(\"p1 and p2 differ\", p1.String(), p2.String())\n\t\t}\n\n\t\tif p2.String() != ks.hpkp {\n\t\t\tt.Error(\"hpkp and p2.String differ\", ks.hpkp, p2.String())\n\t\t}\n\t}\n\n\ttest(gen1)\n\ttest(gen2)\n\ttest(man)\n}\n\nfunc TestIDMatchesPrivateKey(t *testing.T) {\n\n\ttest := func(ks keyset) {\n\t\tp1, err := Decode(ks.hpkp)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif ks.hpk != string(p1) {\n\t\t\tt.Error(\"p1 and hpk differ\")\n\t\t}\n\n\t\tif !p1.MatchesPrivateKey(ks.sk) {\n\t\t\tt.Fatal(\"p1 does not match sk\")\n\t\t}\n\n\t\tp2, err := IDFromPrivateKey(ks.sk)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif p1 != p2 {\n\t\t\tt.Error(\"p1 and p2 differ\", p1.String(), p2.String())\n\t\t}\n\t}\n\n\ttest(gen1)\n\ttest(gen2)\n\ttest(man)\n}\n\nfunc TestIDEncoding(t *testing.T) {\n\ttest := func(ks keyset) {\n\t\tp1, err := Decode(ks.hpkp)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif ks.hpk != string(p1) {\n\t\t\tt.Error(\"p1 and hpk differ\")\n\t\t}\n\n\t\tc := ToCid(p1)\n\t\tp2, err := FromCid(c)\n\t\tif err != nil || p1 != p2 {\n\t\t\tt.Fatal(\"failed to round-trip through CID:\", err)\n\t\t}\n\t\tp3, err := Decode(c.String())\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif p3 != p1 {\n\t\t\tt.Fatal(\"failed to round trip through CID string\")\n\t\t}\n\n\t\tif ks.hpkp != p1.String() {\n\t\t\tt.Fatal(\"should always encode peer IDs as base58 by default\")\n\t\t}\n\t}\n\n\ttest(gen1)\n\ttest(gen2)\n\ttest(man)\n\n\texampleCid := \"bafkreifoybygix7fh3r3g5rqle3wcnhqldgdg4shzf4k3ulyw3gn7mabt4\"\n\t_, err := Decode(exampleCid)\n\tif err == nil {\n\t\tt.Fatal(\"should refuse to decode a non-peer ID CID\")\n\t}\n\n\tc := ToCid(\"\")\n\tif c.Defined() {\n\t\tt.Fatal(\"cid of empty peer ID should have been undefined\")\n\t}\n}\n\nfunc TestPublicKeyExtraction(t *testing.T) {\n\tt.Skip(\"disabled until libp2p/go-libp2p-crypto#51 is fixed\")\n\t// Happy path\n\t_, originalPub, err := ic.GenerateEd25519Key(rand.Reader)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tid, err := IDFromPublicKey(originalPub)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\textractedPub, err := id.ExtractPublicKey()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif extractedPub == nil {\n\t\tt.Fatal(\"failed to extract public key\")\n\t}\n\tif !originalPub.Equals(extractedPub) {\n\t\tt.Fatal(\"extracted public key doesn't match\")\n\t}\n\n\t// Test invalid multihash (invariant of the type of public key)\n\tpk, err := ID(\"\").ExtractPublicKey()\n\tif err == nil {\n\t\tt.Fatal(\"expected an error\")\n\t}\n\tif pk != nil {\n\t\tt.Fatal(\"expected a nil public key\")\n\t}\n\n\t// Shouldn't work for, e.g. RSA keys (too large)\n\n\t_, rsaPub, err := ic.GenerateKeyPair(ic.RSA, 2048)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\trsaId, err := IDFromPublicKey(rsaPub)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\textractedRsaPub, err := rsaId.ExtractPublicKey()\n\tif err != ErrNoPublicKey {\n\t\tt.Fatal(err)\n\t}\n\tif extractedRsaPub != nil {\n\t\tt.Fatal(\"expected to fail to extract public key from rsa ID\")\n\t}\n}\n\nfunc TestValidate(t *testing.T) {\n\t// Empty peer ID invalidates\n\terr := ID(\"\").Validate()\n\tif err == nil {\n\t\tt.Error(\"expected error\")\n\t} else if err != ErrEmptyPeerID {\n\t\tt.Error(\"expected error message: \" + ErrEmptyPeerID.Error())\n\t}\n\n\t// Non-empty peer ID validates\n\tp, err := test.RandPeerID()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = p.Validate()\n\tif err != nil {\n\t\tt.Error(\"expected nil, but found \" + err.Error())\n\t}\n}\n\nvar hpkpMan = `QmcJeseojbPW9hSejUM1sQ1a2QmbrryPK4Z8pWbRUPaYEn`\nvar skManBytes = `\nCAASqAkwggSkAgEAAoIBAQC3hjPtPli71gFNzGJ6rUhYdb65BDwW7IrniEaZKi6z\ntW4Iz0MouEJY8GPG1iQfqZKp5w9H2ENh4I1bk2dsezrJ7Nneg4Eqd78CmeHTAgaP\n3PKsxohdMo/TOFNxwl8SkEF8FyVbio2TCoijYNHUuprZuq7MPEAJYr3Z1eEkM/xR\npMp3YI9S2SYsZQxbmmQ0/GfHOEvYajdow1qttreVTQkvmCppKtNLEU5InpX/W5fe\naQCj0pd7l74daZgM2WWz3juEUCVG7tdRUPg7ix1TYosbN96CKC3q2MJxe/wJ9gR5\nJvjnaaaoon+mci5vrKzxdKBDmZ/ZbLiHDfVljMkbdOQLAgMBAAECggEAEULaF3JJ\nvkD+lmamzIsHxuosKhKv5CgTWHuEyFsjUVu7IbD8zBOoidzyRX1WoHO+i6Rj14oL\nrGUGZpqSm61rdhqE01zjBS+GE6SNjN8f5uANIxr5MGrVBDTEBGsXrhNLVXSH2vhJ\nII9ZEqTEl5GFhvz7+9Ge5EMZQCfRqSoKjVMdrs+Rueuusr9p0wNg9PH1myA+cXGt\niNZA17Rj2IiWVZLDgYNo4DVQUt4mFb+wTJW4NSspGKaFebpn0hf4z21laoGoJqTC\ncNETJw+QwQ0uDaRoYotTLT2/55e8XBFTdcTg5cmbZoKgMyGqZEHfRyD9reVDAZlM\nEZwKtrm41kz94QKBgQDmPp5zVtFXQNONmje1NE0IjCaUKcqURXk4ZiILztfT9XLC\nOXAUCs3TCq21jirCkZZ6gLfo12Wx0xJYmsKlaUOGNTa8FI5Xa7OyheYKixUvV6FW\nJ95P/sNuWscTjh7oZHgZk/L3yKrNzNBz7awComwV6qciXW7EP1uACHf5fS/RdQKB\ngQDMDa38W9OeegRDrhCeYGsniJK7btOCzhNooruQKPPXxk+O4dyJm7VBbC/3Ch55\na83W66T4k0Q7ysLVRT5Vqd5z3AM0sEM3ZoxUKCinG3NwPxVeXcoLasyEiq1vOFK6\nGqZKCMThCj7ZpbkWy0DPJagnYfZGC62lammuj+XQx7mvfwKBgQCTKhka/bXmgD/3\n9UeAIcLPIM2TzDZ4mQNHIjjGtVnMV8kXDaFung06xEuNjSYVoPq+qEFkqTCN/axv\nR9P76BFJ2f93LehhRizggacsvAM5dFhh+i+lj+AYTBuMiz2EKpt9NcyJxhAuZKgk\nQRi9wlU1mPtlArVG6HwylLcil3qV9QKBgQDJHtaU/KEY+2TGnIMuxxP2lEsjyLla\nnOlOYc8C6Qpma8UwrHelfj5p7Eteb6/Xt6Tbp8kjZGuFj3T3plcpMdPbWEgkn3Kw\n4TeBH0/qXUkrolHagBDLrglEvjbxf48ydV/fasM6l9GYzhofWFhZk+EoaArHwWz2\ntGrTrmsynBjt2wKBgErdYe+zZ2Wo+wXQGAoZi4pfcwiw4a97Kdh0dx+WZz7acHms\nh+V20VRmEHm5h8WnJ/Wv5uK94t6NY17wzjQ7y2BN5mY5cA2cZAcpeqtv/N06tH4S\ncn1UEuRB8VpwkjaPUNZhqtYK40qff2OTdJy8taFtQiN7fz9euWTC78zjph2s\n`\n"
  },
  {
    "path": "core/peer/record.go",
    "content": "package peer\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/internal/catch\"\n\t\"github.com/libp2p/go-libp2p/core/peer/pb\"\n\t\"github.com/libp2p/go-libp2p/core/record\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\n\t\"google.golang.org/protobuf/proto\"\n)\n\nvar _ record.Record = (*PeerRecord)(nil)\n\nfunc init() {\n\trecord.RegisterType(&PeerRecord{})\n}\n\n// PeerRecordEnvelopeDomain is the domain string used for peer records contained in an Envelope.\nconst PeerRecordEnvelopeDomain = \"libp2p-peer-record\"\n\n// PeerRecordEnvelopePayloadType is the type hint used to identify peer records in an Envelope.\n// Defined in https://github.com/multiformats/multicodec/blob/master/table.csv\n// with name \"libp2p-peer-record\".\nvar PeerRecordEnvelopePayloadType = []byte{0x03, 0x01}\n\n// PeerRecord contains information that is broadly useful to share with other peers,\n// either through a direct exchange (as in the libp2p identify protocol), or through\n// a Peer Routing provider, such as a DHT.\n//\n// Currently, a PeerRecord contains the public listen addresses for a peer, but this\n// is expected to expand to include other information in the future.\n//\n// PeerRecords are ordered in time by their Seq field. Newer PeerRecords must have\n// greater Seq values than older records. The NewPeerRecord function will create\n// a PeerRecord with a timestamp-based Seq value. The other PeerRecord fields should\n// be set by the caller:\n//\n//\trec := peer.NewPeerRecord()\n//\trec.PeerID = aPeerID\n//\trec.Addrs = someAddrs\n//\n// Alternatively, you can construct a PeerRecord struct directly and use the TimestampSeq\n// helper to set the Seq field:\n//\n//\trec := peer.PeerRecord{PeerID: aPeerID, Addrs: someAddrs, Seq: peer.TimestampSeq()}\n//\n// Failing to set the Seq field will not result in an error, however, a PeerRecord with a\n// Seq value of zero may be ignored or rejected by other peers.\n//\n// PeerRecords are intended to be shared with other peers inside a signed\n// routing.Envelope, and PeerRecord implements the routing.Record interface\n// to facilitate this.\n//\n// To share a PeerRecord, first call Sign to wrap the record in an Envelope\n// and sign it with the local peer's private key:\n//\n//\trec := &PeerRecord{PeerID: myPeerId, Addrs: myAddrs}\n//\tenvelope, err := rec.Sign(myPrivateKey)\n//\n// The resulting record.Envelope can be marshalled to a []byte and shared\n// publicly. As a convenience, the MarshalSigned method will produce the\n// Envelope and marshal it to a []byte in one go:\n//\n//\trec := &PeerRecord{PeerID: myPeerId, Addrs: myAddrs}\n//\trecordBytes, err := rec.MarshalSigned(myPrivateKey)\n//\n// To validate and unmarshal a signed PeerRecord from a remote peer,\n// \"consume\" the containing envelope, which will return both the\n// routing.Envelope and the inner Record. The Record must be cast to\n// a PeerRecord pointer before use:\n//\n//\tenvelope, untypedRecord, err := ConsumeEnvelope(envelopeBytes, PeerRecordEnvelopeDomain)\n//\tif err != nil {\n//\t  handleError(err)\n//\t  return\n//\t}\n//\tpeerRec := untypedRecord.(*PeerRecord)\ntype PeerRecord struct {\n\t// PeerID is the ID of the peer this record pertains to.\n\tPeerID ID\n\n\t// Addrs contains the public addresses of the peer this record pertains to.\n\tAddrs []ma.Multiaddr\n\n\t// Seq is a monotonically-increasing sequence counter that's used to order\n\t// PeerRecords in time. The interval between Seq values is unspecified,\n\t// but newer PeerRecords MUST have a greater Seq value than older records\n\t// for the same peer.\n\tSeq uint64\n}\n\n// NewPeerRecord returns a PeerRecord with a timestamp-based sequence number.\n// The returned record is otherwise empty and should be populated by the caller.\nfunc NewPeerRecord() *PeerRecord {\n\treturn &PeerRecord{Seq: TimestampSeq()}\n}\n\n// PeerRecordFromAddrInfo creates a PeerRecord from an AddrInfo struct.\n// The returned record will have a timestamp-based sequence number.\nfunc PeerRecordFromAddrInfo(info AddrInfo) *PeerRecord {\n\trec := NewPeerRecord()\n\trec.PeerID = info.ID\n\trec.Addrs = info.Addrs\n\treturn rec\n}\n\n// PeerRecordFromProtobuf creates a PeerRecord from a protobuf PeerRecord\n// struct.\nfunc PeerRecordFromProtobuf(msg *pb.PeerRecord) (*PeerRecord, error) {\n\trecord := &PeerRecord{}\n\n\tvar id ID\n\tif err := id.UnmarshalBinary(msg.PeerId); err != nil {\n\t\treturn nil, err\n\t}\n\n\trecord.PeerID = id\n\trecord.Addrs = addrsFromProtobuf(msg.Addresses)\n\trecord.Seq = msg.Seq\n\n\treturn record, nil\n}\n\nvar (\n\tlastTimestampMu sync.Mutex\n\tlastTimestamp   uint64\n)\n\n// TimestampSeq is a helper to generate a timestamp-based sequence number for a PeerRecord.\nfunc TimestampSeq() uint64 {\n\tnow := uint64(time.Now().UnixNano())\n\tlastTimestampMu.Lock()\n\tdefer lastTimestampMu.Unlock()\n\t// Not all clocks are strictly increasing, but we need these sequence numbers to be strictly\n\t// increasing.\n\tif now <= lastTimestamp {\n\t\tnow = lastTimestamp + 1\n\t}\n\tlastTimestamp = now\n\treturn now\n}\n\n// Domain is used when signing and validating PeerRecords contained in Envelopes.\n// It is constant for all PeerRecord instances.\nfunc (r *PeerRecord) Domain() string {\n\treturn PeerRecordEnvelopeDomain\n}\n\n// Codec is a binary identifier for the PeerRecord type. It is constant for all PeerRecord instances.\nfunc (r *PeerRecord) Codec() []byte {\n\treturn PeerRecordEnvelopePayloadType\n}\n\n// UnmarshalRecord parses a PeerRecord from a byte slice.\n// This method is called automatically when consuming a record.Envelope\n// whose PayloadType indicates that it contains a PeerRecord.\n// It is generally not necessary or recommended to call this method directly.\nfunc (r *PeerRecord) UnmarshalRecord(bytes []byte) (err error) {\n\tif r == nil {\n\t\treturn fmt.Errorf(\"cannot unmarshal PeerRecord to nil receiver\")\n\t}\n\n\tdefer func() { catch.HandlePanic(recover(), &err, \"libp2p peer record unmarshal\") }()\n\n\tvar msg pb.PeerRecord\n\terr = proto.Unmarshal(bytes, &msg)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\trPtr, err := PeerRecordFromProtobuf(&msg)\n\tif err != nil {\n\t\treturn err\n\t}\n\t*r = *rPtr\n\n\treturn nil\n}\n\n// MarshalRecord serializes a PeerRecord to a byte slice.\n// This method is called automatically when constructing a routing.Envelope\n// using Seal or PeerRecord.Sign.\nfunc (r *PeerRecord) MarshalRecord() (res []byte, err error) {\n\tdefer func() { catch.HandlePanic(recover(), &err, \"libp2p peer record marshal\") }()\n\n\tmsg, err := r.ToProtobuf()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn proto.Marshal(msg)\n}\n\n// Equal returns true if the other PeerRecord is identical to this one.\nfunc (r *PeerRecord) Equal(other *PeerRecord) bool {\n\tif other == nil {\n\t\treturn r == nil\n\t}\n\tif r.PeerID != other.PeerID {\n\t\treturn false\n\t}\n\tif r.Seq != other.Seq {\n\t\treturn false\n\t}\n\tif len(r.Addrs) != len(other.Addrs) {\n\t\treturn false\n\t}\n\tfor i := range r.Addrs {\n\t\tif !r.Addrs[i].Equal(other.Addrs[i]) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n// ToProtobuf returns the equivalent Protocol Buffer struct object of a PeerRecord.\nfunc (r *PeerRecord) ToProtobuf() (*pb.PeerRecord, error) {\n\tidBytes, err := r.PeerID.MarshalBinary()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &pb.PeerRecord{\n\t\tPeerId:    idBytes,\n\t\tAddresses: addrsToProtobuf(r.Addrs),\n\t\tSeq:       r.Seq,\n\t}, nil\n}\n\nfunc addrsFromProtobuf(addrs []*pb.PeerRecord_AddressInfo) []ma.Multiaddr {\n\tout := make([]ma.Multiaddr, 0, len(addrs))\n\tfor _, addr := range addrs {\n\t\ta, err := ma.NewMultiaddrBytes(addr.Multiaddr)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tout = append(out, a)\n\t}\n\treturn out\n}\n\nfunc addrsToProtobuf(addrs []ma.Multiaddr) []*pb.PeerRecord_AddressInfo {\n\tout := make([]*pb.PeerRecord_AddressInfo, 0, len(addrs))\n\tfor _, addr := range addrs {\n\t\tout = append(out, &pb.PeerRecord_AddressInfo{Multiaddr: addr.Bytes()})\n\t}\n\treturn out\n}\n"
  },
  {
    "path": "core/peer/record_test.go",
    "content": "package peer_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t. \"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/record\"\n\t\"github.com/libp2p/go-libp2p/core/test\"\n)\n\nfunc TestPeerRecordConstants(t *testing.T) {\n\tmsgf := \"Changing the %s may cause peer records to be incompatible with older versions. \" +\n\t\t\"If you've already thought that through, please update this test so that it passes with the new values.\"\n\trec := PeerRecord{}\n\tif rec.Domain() != \"libp2p-peer-record\" {\n\t\tt.Errorf(msgf, \"signing domain\")\n\t}\n\tif !bytes.Equal(rec.Codec(), []byte{0x03, 0x01}) {\n\t\tt.Errorf(msgf, \"codec value\")\n\t}\n}\n\nfunc TestSignedPeerRecordFromEnvelope(t *testing.T) {\n\tpriv, _, err := test.RandTestKeyPair(crypto.Ed25519, 256)\n\ttest.AssertNilError(t, err)\n\n\taddrs := test.GenerateTestAddrs(10)\n\tid, err := IDFromPrivateKey(priv)\n\ttest.AssertNilError(t, err)\n\n\trec := &PeerRecord{PeerID: id, Addrs: addrs, Seq: TimestampSeq()}\n\tenvelope, err := record.Seal(rec, priv)\n\ttest.AssertNilError(t, err)\n\n\tt.Run(\"is unaltered after round-trip serde\", func(t *testing.T) {\n\t\tenvBytes, err := envelope.Marshal()\n\t\ttest.AssertNilError(t, err)\n\n\t\tenv2, untypedRecord, err := record.ConsumeEnvelope(envBytes, PeerRecordEnvelopeDomain)\n\t\ttest.AssertNilError(t, err)\n\t\trec2, ok := untypedRecord.(*PeerRecord)\n\t\tif !ok {\n\t\t\tt.Error(\"unmarshaled record is not a *PeerRecord\")\n\t\t}\n\t\tif !rec.Equal(rec2) {\n\t\t\tt.Error(\"expected peer record to be unaltered after round-trip serde\")\n\t\t}\n\t\tif !envelope.Equal(env2) {\n\t\t\tt.Error(\"expected signed envelope to be unchanged after round-trip serde\")\n\t\t}\n\t})\n}\n\n// This is pretty much guaranteed to pass on Linux no matter how we implement it, but Windows has\n// low clock precision. This makes sure we never get a duplicate.\nfunc TestTimestampSeq(t *testing.T) {\n\tvar last uint64\n\tfor range 1000 {\n\t\tnext := TimestampSeq()\n\t\tif next <= last {\n\t\t\tt.Errorf(\"non-increasing timestamp found: %d <= %d\", next, last)\n\t\t}\n\t\tlast = next\n\t}\n}\n"
  },
  {
    "path": "core/peerstore/helpers.go",
    "content": "package peerstore\n\nimport (\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n)\n\n// AddrInfos returns an AddrInfo for each specified peer ID, in-order.\nfunc AddrInfos(ps Peerstore, peers []peer.ID) []peer.AddrInfo {\n\tpi := make([]peer.AddrInfo, len(peers))\n\tfor i, p := range peers {\n\t\tpi[i] = ps.PeerInfo(p)\n\t}\n\treturn pi\n}\n"
  },
  {
    "path": "core/peerstore/peerstore.go",
    "content": "// Package peerstore provides types and interfaces for local storage of address information,\n// metadata, and public key material about libp2p peers.\npackage peerstore\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\t\"math\"\n\t\"time\"\n\n\tic \"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\t\"github.com/libp2p/go-libp2p/core/record\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\nvar ErrNotFound = errors.New(\"item not found\")\n\nvar (\n\t// AddressTTL is the expiration time of addresses.\n\tAddressTTL = time.Hour\n\n\t// TempAddrTTL is the ttl used for a short-lived address.\n\tTempAddrTTL = time.Minute * 2\n\n\t// RecentlyConnectedAddrTTL is used when we recently connected to a peer.\n\t// It means that we are reasonably certain of the peer's address.\n\tRecentlyConnectedAddrTTL = time.Minute * 15\n\n\t// OwnObservedAddrTTL is used for our own external addresses observed by peers.\n\t//\n\t// Deprecated: observed addresses are maintained till we disconnect from the peer which provided it\n\tOwnObservedAddrTTL = time.Minute * 30\n)\n\n// Permanent TTLs (distinct so we can distinguish between them, constant as they\n// are, in fact, permanent)\nconst (\n\t// PermanentAddrTTL is the ttl for a \"permanent address\" (e.g. bootstrap nodes).\n\tPermanentAddrTTL = math.MaxInt64 - iota\n\n\t// ConnectedAddrTTL is the ttl used for the addresses of a peer to whom\n\t// we're connected directly. This is basically permanent, as we will\n\t// clear them + re-add under a TempAddrTTL after disconnecting.\n\tConnectedAddrTTL\n)\n\n// Peerstore provides a thread-safe store of Peer related\n// information.\ntype Peerstore interface {\n\tio.Closer\n\n\tAddrBook\n\tKeyBook\n\tPeerMetadata\n\tMetrics\n\tProtoBook\n\n\t// PeerInfo returns a peer.PeerInfo struct for given peer.ID.\n\t// This is a small slice of the information Peerstore has on\n\t// that peer, useful to other services.\n\tPeerInfo(peer.ID) peer.AddrInfo\n\n\t// Peers returns all the peer IDs stored across all inner stores.\n\tPeers() peer.IDSlice\n\n\t// RemovePeer removes all the peer related information except its addresses. To remove the\n\t// addresses use `AddrBook.ClearAddrs` or set the address ttls to 0.\n\tRemovePeer(peer.ID)\n}\n\n// PeerMetadata can handle values of any type. Serializing values is\n// up to the implementation. Dynamic type introspection may not be\n// supported, in which case explicitly enlisting types in the\n// serializer may be required.\n//\n// Refer to the docs of the underlying implementation for more\n// information.\ntype PeerMetadata interface {\n\t// Get / Put is a simple registry for other peer-related key/value pairs.\n\t// If we find something we use often, it should become its own set of\n\t// methods. This is a last resort.\n\tGet(p peer.ID, key string) (any, error)\n\tPut(p peer.ID, key string, val any) error\n\n\t// RemovePeer removes all values stored for a peer.\n\tRemovePeer(peer.ID)\n}\n\n// AddrBook holds the multiaddrs of peers.\ntype AddrBook interface {\n\t// AddAddr calls AddAddrs(p, []ma.Multiaddr{addr}, ttl)\n\tAddAddr(p peer.ID, addr ma.Multiaddr, ttl time.Duration)\n\n\t// AddAddrs gives this AddrBook addresses to use, with a given ttl\n\t// (time-to-live), after which the address is no longer valid.\n\t// If the manager has a longer TTL, the operation is a no-op for that address\n\tAddAddrs(p peer.ID, addrs []ma.Multiaddr, ttl time.Duration)\n\n\t// SetAddr calls mgr.SetAddrs(p, addr, ttl)\n\tSetAddr(p peer.ID, addr ma.Multiaddr, ttl time.Duration)\n\n\t// SetAddrs sets the ttl on addresses. This clears any TTL there previously.\n\t// This is used when we receive the best estimate of the validity of an address.\n\tSetAddrs(p peer.ID, addrs []ma.Multiaddr, ttl time.Duration)\n\n\t// UpdateAddrs updates the addresses associated with the given peer that have\n\t// the given oldTTL to have the given newTTL.\n\tUpdateAddrs(p peer.ID, oldTTL time.Duration, newTTL time.Duration)\n\n\t// Addrs returns all known (and valid) addresses for a given peer.\n\tAddrs(p peer.ID) []ma.Multiaddr\n\n\t// AddrStream returns a channel that gets all addresses for a given\n\t// peer sent on it. If new addresses are added after the call is made\n\t// they will be sent along through the channel as well.\n\tAddrStream(context.Context, peer.ID) <-chan ma.Multiaddr\n\n\t// ClearAddresses removes all previously stored addresses.\n\tClearAddrs(p peer.ID)\n\n\t// PeersWithAddrs returns all the peer IDs stored in the AddrBook.\n\tPeersWithAddrs() peer.IDSlice\n}\n\n// CertifiedAddrBook manages signed peer records and \"self-certified\" addresses\n// contained within them.\n// Use this interface with an `AddrBook`.\n//\n// To test whether a given AddrBook / Peerstore implementation supports\n// certified addresses, callers should use the GetCertifiedAddrBook helper or\n// type-assert on the CertifiedAddrBook interface:\n//\n//\tif cab, ok := aPeerstore.(CertifiedAddrBook); ok {\n//\t    cab.ConsumePeerRecord(signedPeerRecord, aTTL)\n//\t}\ntype CertifiedAddrBook interface {\n\t// ConsumePeerRecord stores a signed peer record and the contained addresses for\n\t// ttl duration.\n\t// The addresses contained in the signed peer record will expire after ttl. If any\n\t// address is already present in the peer store, it'll expire at max of existing ttl and\n\t// provided ttl.\n\t// The signed peer record itself will be expired when all the addresses associated with the peer,\n\t// self-certified or not, are removed from the AddrBook.\n\t//\n\t// To delete the signed peer record, use `AddrBook.UpdateAddrs`,`AddrBook.SetAddrs`, or\n\t// `AddrBook.ClearAddrs` with ttl 0.\n\t// Note: Future calls to ConsumePeerRecord will not expire self-certified addresses from the\n\t// previous calls.\n\t//\n\t// The `accepted` return value indicates that the record was successfully processed. If\n\t// `accepted` is false but no error is returned, it means that the record was ignored, most\n\t// likely because a newer record exists for the same peer with a greater seq value.\n\t//\n\t// The Envelopes containing the signed peer records can be retrieved by calling\n\t// GetPeerRecord(peerID).\n\tConsumePeerRecord(s *record.Envelope, ttl time.Duration) (accepted bool, err error)\n\n\t// GetPeerRecord returns an Envelope containing a peer record for the\n\t// peer, or nil if no record exists.\n\tGetPeerRecord(p peer.ID) *record.Envelope\n}\n\n// GetCertifiedAddrBook is a helper to \"upcast\" an AddrBook to a\n// CertifiedAddrBook by using type assertion. If the given AddrBook\n// is also a CertifiedAddrBook, it will be returned, and the ok return\n// value will be true. Returns (nil, false) if the AddrBook is not a\n// CertifiedAddrBook.\n//\n// Note that since Peerstore embeds the AddrBook interface, you can also\n// call GetCertifiedAddrBook(myPeerstore).\nfunc GetCertifiedAddrBook(ab AddrBook) (cab CertifiedAddrBook, ok bool) {\n\tcab, ok = ab.(CertifiedAddrBook)\n\treturn cab, ok\n}\n\n// KeyBook tracks the keys of Peers.\ntype KeyBook interface {\n\t// PubKey returns the public key of a peer.\n\tPubKey(peer.ID) ic.PubKey\n\n\t// AddPubKey stores the public key of a peer.\n\tAddPubKey(peer.ID, ic.PubKey) error\n\n\t// PrivKey returns the private key of a peer, if known. Generally this might only be our own\n\t// private key, see\n\t// https://discuss.libp2p.io/t/what-is-the-purpose-of-having-map-peer-id-privatekey-in-peerstore/74.\n\tPrivKey(peer.ID) ic.PrivKey\n\n\t// AddPrivKey stores the private key of a peer.\n\tAddPrivKey(peer.ID, ic.PrivKey) error\n\n\t// PeersWithKeys returns all the peer IDs stored in the KeyBook.\n\tPeersWithKeys() peer.IDSlice\n\n\t// RemovePeer removes all keys associated with a peer.\n\tRemovePeer(peer.ID)\n}\n\n// Metrics tracks metrics across a set of peers.\ntype Metrics interface {\n\t// RecordLatency records a new latency measurement\n\tRecordLatency(peer.ID, time.Duration)\n\n\t// LatencyEWMA returns an exponentially-weighted moving avg.\n\t// of all measurements of a peer's latency.\n\tLatencyEWMA(peer.ID) time.Duration\n\n\t// RemovePeer removes all metrics stored for a peer.\n\tRemovePeer(peer.ID)\n}\n\n// ProtoBook tracks the protocols supported by peers.\ntype ProtoBook interface {\n\tGetProtocols(peer.ID) ([]protocol.ID, error)\n\tAddProtocols(peer.ID, ...protocol.ID) error\n\tSetProtocols(peer.ID, ...protocol.ID) error\n\tRemoveProtocols(peer.ID, ...protocol.ID) error\n\n\t// SupportsProtocols returns the set of protocols the peer supports from among the given protocols.\n\t// If the returned error is not nil, the result is indeterminate.\n\tSupportsProtocols(peer.ID, ...protocol.ID) ([]protocol.ID, error)\n\n\t// FirstSupportedProtocol returns the first protocol that the peer supports among the given protocols.\n\t// If the peer does not support any of the given protocols, this function will return an empty protocol.ID and a nil error.\n\t// If the returned error is not nil, the result is indeterminate.\n\tFirstSupportedProtocol(peer.ID, ...protocol.ID) (protocol.ID, error)\n\n\t// RemovePeer removes all protocols associated with a peer.\n\tRemovePeer(peer.ID)\n}\n"
  },
  {
    "path": "core/pnet/codec.go",
    "content": "package pnet\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"encoding/base64\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"io\"\n)\n\nvar (\n\tpathPSKv1  = []byte(\"/key/swarm/psk/1.0.0/\")\n\tpathBin    = \"/bin/\"\n\tpathBase16 = \"/base16/\"\n\tpathBase64 = \"/base64/\"\n)\n\nfunc readHeader(r *bufio.Reader) ([]byte, error) {\n\theader, err := r.ReadBytes('\\n')\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn bytes.TrimRight(header, \"\\r\\n\"), nil\n}\n\nfunc expectHeader(r *bufio.Reader, expected []byte) error {\n\theader, err := readHeader(r)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !bytes.Equal(header, expected) {\n\t\treturn fmt.Errorf(\"expected file header %s, got: %s\", expected, header)\n\t}\n\treturn nil\n}\n\n// DecodeV1PSK reads a Multicodec encoded V1 PSK.\nfunc DecodeV1PSK(in io.Reader) (PSK, error) {\n\treader := bufio.NewReader(in)\n\tif err := expectHeader(reader, pathPSKv1); err != nil {\n\t\treturn nil, err\n\t}\n\theader, err := readHeader(reader)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar decoder io.Reader\n\tswitch string(header) {\n\tcase pathBase16:\n\t\tdecoder = hex.NewDecoder(reader)\n\tcase pathBase64:\n\t\tdecoder = base64.NewDecoder(base64.StdEncoding, reader)\n\tcase pathBin:\n\t\tdecoder = reader\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unknown encoding: %s\", header)\n\t}\n\tout := make([]byte, 32)\n\tif _, err = io.ReadFull(decoder, out[:]); err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n"
  },
  {
    "path": "core/pnet/codec_test.go",
    "content": "package pnet\n\nimport (\n\t\"bytes\"\n\t\"encoding/base64\"\n\t\"testing\"\n)\n\nfunc bufWithBase(base string, windows bool) *bytes.Buffer {\n\tb := &bytes.Buffer{}\n\tb.Write(pathPSKv1)\n\tif windows {\n\t\tb.WriteString(\"\\r\")\n\t}\n\tb.WriteString(\"\\n\")\n\tb.WriteString(base)\n\tif windows {\n\t\tb.WriteString(\"\\r\")\n\t}\n\tb.WriteString(\"\\n\")\n\treturn b\n}\n\nfunc TestDecodeHex(t *testing.T) {\n\ttestDecodeHex(t, true)\n\ttestDecodeHex(t, false)\n}\n\nfunc TestDecodeBad(t *testing.T) {\n\ttestDecodeBad(t, true)\n\ttestDecodeBad(t, false)\n}\n\nfunc testDecodeBad(t *testing.T, windows bool) {\n\tb := bufWithBase(\"/verybadbase/\", windows)\n\tb.WriteString(\"Have fun decoding that key\")\n\n\t_, err := DecodeV1PSK(b)\n\tif err == nil {\n\t\tt.Fatal(\"expected 'unknown encoding' got nil\")\n\t}\n}\n\nfunc testDecodeHex(t *testing.T, windows bool) {\n\tb := bufWithBase(\"/base16/\", windows)\n\tfor range 32 {\n\t\tb.WriteString(\"FF\")\n\t}\n\n\tpsk, err := DecodeV1PSK(b)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor _, b := range psk {\n\t\tif b != 255 {\n\t\t\tt.Fatal(\"byte was wrong\")\n\t\t}\n\t}\n}\n\nfunc TestDecodeB64(t *testing.T) {\n\ttestDecodeB64(t, true)\n\ttestDecodeB64(t, false)\n}\n\nfunc testDecodeB64(t *testing.T, windows bool) {\n\tb := bufWithBase(\"/base64/\", windows)\n\tkey := make([]byte, 32)\n\tfor i := range 32 {\n\t\tkey[i] = byte(i)\n\t}\n\n\te := base64.NewEncoder(base64.StdEncoding, b)\n\t_, err := e.Write(key)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\terr = e.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tpsk, err := DecodeV1PSK(b)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor i, b := range psk {\n\t\tif b != psk[i] {\n\t\t\tt.Fatal(\"byte was wrong\")\n\t\t}\n\t}\n\n}\n\nfunc TestDecodeBin(t *testing.T) {\n\ttestDecodeBin(t, true)\n\ttestDecodeBin(t, false)\n}\n\nfunc testDecodeBin(t *testing.T, windows bool) {\n\tb := bufWithBase(\"/bin/\", windows)\n\tkey := make([]byte, 32)\n\tfor i := range 32 {\n\t\tkey[i] = byte(i)\n\t}\n\n\tb.Write(key)\n\n\tpsk, err := DecodeV1PSK(b)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor i, b := range psk {\n\t\tif b != psk[i] {\n\t\t\tt.Fatal(\"byte was wrong\")\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "core/pnet/env.go",
    "content": "package pnet\n\nimport \"os\"\n\n// EnvKey defines environment variable name for forcing usage of PNet in libp2p\n// When environment variable of this name is set to \"1\" the ForcePrivateNetwork\n// variable will be set to true.\nconst EnvKey = \"LIBP2P_FORCE_PNET\"\n\n// ForcePrivateNetwork is boolean variable that forces usage of PNet in libp2p\n// Setting this variable to true or setting LIBP2P_FORCE_PNET environment variable\n// to true will make libp2p to require private network protector.\n// If no network protector is provided and this variable is set to true libp2p will\n// refuse to connect.\nvar ForcePrivateNetwork = false\n\nfunc init() {\n\tForcePrivateNetwork = os.Getenv(EnvKey) == \"1\"\n}\n"
  },
  {
    "path": "core/pnet/error.go",
    "content": "package pnet\n\n// ErrNotInPrivateNetwork is an error that should be returned by libp2p when it\n// tries to dial with ForcePrivateNetwork set and no PNet Protector\nvar ErrNotInPrivateNetwork = NewError(\"private network was not configured but\" +\n\t\" is enforced by the environment\")\n\n// Error is error type for ease of detecting PNet errors\ntype Error interface {\n\tIsPNetError() bool\n}\n\n// NewError creates new Error\nfunc NewError(err string) error {\n\treturn pnetErr(\"privnet: \" + err)\n}\n\n// IsPNetError checks if given error is PNet Error\nfunc IsPNetError(err error) bool {\n\tv, ok := err.(Error)\n\treturn ok && v.IsPNetError()\n}\n\ntype pnetErr string\n\nvar _ Error = (*pnetErr)(nil)\n\nfunc (p pnetErr) Error() string {\n\treturn string(p)\n}\n\nfunc (pnetErr) IsPNetError() bool {\n\treturn true\n}\n"
  },
  {
    "path": "core/pnet/error_test.go",
    "content": "package pnet\n\nimport (\n\t\"errors\"\n\t\"testing\"\n)\n\nfunc TestIsPnetErr(t *testing.T) {\n\terr := NewError(\"test\")\n\n\tif err.Error() != \"privnet: test\" {\n\t\tt.Fatalf(\"expected 'privnet: test' got '%s'\", err.Error())\n\t}\n\tif !IsPNetError(err) {\n\t\tt.Fatal(\"expected the pnetErr to be detected by IsPnetError\")\n\t}\n\tif IsPNetError(errors.New(\"not pnet error\")) {\n\t\tt.Fatal(\"expected generic error not to be pnetError\")\n\t}\n}\n"
  },
  {
    "path": "core/pnet/protector.go",
    "content": "// Package pnet provides interfaces for private networking in libp2p.\npackage pnet\n\n// A PSK enables private network implementation to be transparent in libp2p.\n// It is used to ensure that peers can only establish connections to other peers\n// that are using the same PSK.\ntype PSK []byte\n"
  },
  {
    "path": "core/protocol/id.go",
    "content": "package protocol\n\n// ID is an identifier used to write protocol headers in streams.\ntype ID string\n\n// These are reserved protocol.IDs.\nconst (\n\tTestingID ID = \"/p2p/_testing\"\n)\n\n// ConvertFromStrings is a convenience function that takes a slice of strings and\n// converts it to a slice of protocol.ID.\nfunc ConvertFromStrings(ids []string) (res []ID) {\n\tres = make([]ID, 0, len(ids))\n\tfor _, id := range ids {\n\t\tres = append(res, ID(id))\n\t}\n\treturn res\n}\n\n// ConvertToStrings is a convenience function that takes a slice of protocol.ID and\n// converts it to a slice of strings.\nfunc ConvertToStrings(ids []ID) (res []string) {\n\tres = make([]string, 0, len(ids))\n\tfor _, id := range ids {\n\t\tres = append(res, string(id))\n\t}\n\treturn res\n}\n"
  },
  {
    "path": "core/protocol/switch.go",
    "content": "// Package protocol provides core interfaces for protocol routing and negotiation in libp2p.\npackage protocol\n\nimport (\n\t\"io\"\n\n\t\"github.com/multiformats/go-multistream\"\n)\n\n// HandlerFunc is a user-provided function used by the Router to\n// handle a protocol/stream.\n//\n// Will be invoked with the protocol ID string as the first argument,\n// which may differ from the ID used for registration if the handler\n// was registered using a match function.\ntype HandlerFunc = multistream.HandlerFunc[ID]\n\n// Router is an interface that allows users to add and remove protocol handlers,\n// which will be invoked when incoming stream requests for registered protocols\n// are accepted.\n//\n// Upon receiving an incoming stream request, the Router will check all registered\n// protocol handlers to determine which (if any) is capable of handling the stream.\n// The handlers are checked in order of registration; if multiple handlers are\n// eligible, only the first to be registered will be invoked.\ntype Router interface {\n\n\t// AddHandler registers the given handler to be invoked for\n\t// an exact literal match of the given protocol ID string.\n\tAddHandler(protocol ID, handler HandlerFunc)\n\n\t// AddHandlerWithFunc registers the given handler to be invoked\n\t// when the provided match function returns true.\n\t//\n\t// The match function will be invoked with an incoming protocol\n\t// ID string, and should return true if the handler supports\n\t// the protocol. Note that the protocol ID argument is not\n\t// used for matching; if you want to match the protocol ID\n\t// string exactly, you must check for it in your match function.\n\tAddHandlerWithFunc(protocol ID, match func(ID) bool, handler HandlerFunc)\n\n\t// RemoveHandler removes the registered handler (if any) for the\n\t// given protocol ID string.\n\tRemoveHandler(protocol ID)\n\n\t// Protocols returns a list of all registered protocol ID strings.\n\t// Note that the Router may be able to handle protocol IDs not\n\t// included in this list if handlers were added with match functions\n\t// using AddHandlerWithFunc.\n\tProtocols() []ID\n}\n\n// Negotiator is a component capable of reaching agreement over what protocols\n// to use for inbound streams of communication.\ntype Negotiator interface {\n\t// Negotiate will return the registered protocol handler to use for a given\n\t// inbound stream, returning after the protocol has been determined and the\n\t// Negotiator has finished using the stream for negotiation. Returns an\n\t// error if negotiation fails.\n\tNegotiate(rwc io.ReadWriteCloser) (ID, HandlerFunc, error)\n\n\t// Handle calls Negotiate to determine which protocol handler to use for an\n\t// inbound stream, then invokes the protocol handler function, passing it\n\t// the protocol ID and the stream. Returns an error if negotiation fails.\n\tHandle(rwc io.ReadWriteCloser) error\n}\n\n// Switch is the component responsible for \"dispatching\" incoming stream requests to\n// their corresponding stream handlers. It is both a Negotiator and a Router.\ntype Switch interface {\n\tRouter\n\tNegotiator\n}\n"
  },
  {
    "path": "core/record/envelope.go",
    "content": "package record\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"sync\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/internal/catch\"\n\t\"github.com/libp2p/go-libp2p/core/record/pb\"\n\n\tpool \"github.com/libp2p/go-buffer-pool\"\n\n\t\"google.golang.org/protobuf/proto\"\n)\n\n// Envelope contains an arbitrary []byte payload, signed by a libp2p peer.\n//\n// Envelopes are signed in the context of a particular \"domain\", which is a\n// string specified when creating and verifying the envelope. You must know the\n// domain string used to produce the envelope in order to verify the signature\n// and access the payload.\ntype Envelope struct {\n\t// The public key that can be used to verify the signature and derive the peer id of the signer.\n\tPublicKey crypto.PubKey\n\n\t// A binary identifier that indicates what kind of data is contained in the payload.\n\t// TODO(yusef): enforce multicodec prefix\n\tPayloadType []byte\n\n\t// The envelope payload.\n\tRawPayload []byte\n\n\t// The signature of the domain string :: type hint :: payload.\n\tsignature []byte\n\n\t// the unmarshalled payload as a Record, cached on first access via the Record accessor method\n\tcached         Record\n\tunmarshalError error\n\tunmarshalOnce  sync.Once\n}\n\nvar ErrEmptyDomain = errors.New(\"envelope domain must not be empty\")\nvar ErrEmptyPayloadType = errors.New(\"payloadType must not be empty\")\nvar ErrInvalidSignature = errors.New(\"invalid signature or incorrect domain\")\n\n// Seal marshals the given Record, places the marshaled bytes inside an Envelope,\n// and signs with the given private key.\nfunc Seal(rec Record, privateKey crypto.PrivKey) (*Envelope, error) {\n\tpayload, err := rec.MarshalRecord()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error marshaling record: %v\", err)\n\t}\n\n\tdomain := rec.Domain()\n\tpayloadType := rec.Codec()\n\tif domain == \"\" {\n\t\treturn nil, ErrEmptyDomain\n\t}\n\n\tif len(payloadType) == 0 {\n\t\treturn nil, ErrEmptyPayloadType\n\t}\n\n\tunsigned, err := makeUnsigned(domain, payloadType, payload)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer pool.Put(unsigned)\n\n\tsig, err := privateKey.Sign(unsigned)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &Envelope{\n\t\tPublicKey:   privateKey.GetPublic(),\n\t\tPayloadType: payloadType,\n\t\tRawPayload:  payload,\n\t\tsignature:   sig,\n\t}, nil\n}\n\n// ConsumeEnvelope unmarshals a serialized Envelope and validates its\n// signature using the provided 'domain' string. If validation fails, an error\n// is returned, along with the unmarshalled envelope, so it can be inspected.\n//\n// On success, ConsumeEnvelope returns the Envelope itself, as well as the inner payload,\n// unmarshalled into a concrete Record type. The actual type of the returned Record depends\n// on what has been registered for the Envelope's PayloadType (see RegisterType for details).\n//\n// You can type assert on the returned Record to convert it to an instance of the concrete\n// Record type:\n//\n//\tenvelope, rec, err := ConsumeEnvelope(envelopeBytes, peer.PeerRecordEnvelopeDomain)\n//\tif err != nil {\n//\t  handleError(envelope, err)  // envelope may be non-nil, even if errors occur!\n//\t  return\n//\t}\n//\tpeerRec, ok := rec.(*peer.PeerRecord)\n//\tif ok {\n//\t  doSomethingWithPeerRecord(peerRec)\n//\t}\n//\n// If the Envelope signature is valid, but no Record type is registered for the Envelope's\n// PayloadType, ErrPayloadTypeNotRegistered will be returned, along with the Envelope and\n// a nil Record.\nfunc ConsumeEnvelope(data []byte, domain string) (envelope *Envelope, rec Record, err error) {\n\te, err := UnmarshalEnvelope(data)\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"failed when unmarshalling the envelope: %w\", err)\n\t}\n\n\terr = e.validate(domain)\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"failed to validate envelope: %w\", err)\n\t}\n\n\trec, err = e.Record()\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"failed to unmarshal envelope payload: %w\", err)\n\t}\n\treturn e, rec, nil\n}\n\n// ConsumeTypedEnvelope unmarshals a serialized Envelope and validates its\n// signature. If validation fails, an error is returned, along with the unmarshalled\n// envelope, so it can be inspected.\n//\n// Unlike ConsumeEnvelope, ConsumeTypedEnvelope does not try to automatically determine\n// the type of Record to unmarshal the Envelope's payload into. Instead, the caller provides\n// a destination Record instance, which will unmarshal the Envelope payload. It is the caller's\n// responsibility to determine whether the given Record type is able to unmarshal the payload\n// correctly.\n//\n//\trec := &MyRecordType{}\n//\tenvelope, err := ConsumeTypedEnvelope(envelopeBytes, rec)\n//\tif err != nil {\n//\t  handleError(envelope, err)\n//\t}\n//\tdoSomethingWithRecord(rec)\n//\n// Important: you MUST check the error value before using the returned Envelope. In some error\n// cases, including when the envelope signature is invalid, both the Envelope and an error will\n// be returned. This allows you to inspect the unmarshalled but invalid Envelope. As a result,\n// you must not assume that any non-nil Envelope returned from this function is valid.\nfunc ConsumeTypedEnvelope(data []byte, destRecord Record) (envelope *Envelope, err error) {\n\te, err := UnmarshalEnvelope(data)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed when unmarshalling the envelope: %w\", err)\n\t}\n\n\terr = e.validate(destRecord.Domain())\n\tif err != nil {\n\t\treturn e, fmt.Errorf(\"failed to validate envelope: %w\", err)\n\t}\n\n\terr = destRecord.UnmarshalRecord(e.RawPayload)\n\tif err != nil {\n\t\treturn e, fmt.Errorf(\"failed to unmarshal envelope payload: %w\", err)\n\t}\n\te.cached = destRecord\n\treturn e, nil\n}\n\n// UnmarshalEnvelope unmarshals a serialized Envelope protobuf message,\n// without validating its contents. Most users should use ConsumeEnvelope.\nfunc UnmarshalEnvelope(data []byte) (*Envelope, error) {\n\tvar e pb.Envelope\n\tif err := proto.Unmarshal(data, &e); err != nil {\n\t\treturn nil, err\n\t}\n\n\tkey, err := crypto.PublicKeyFromProto(e.PublicKey)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &Envelope{\n\t\tPublicKey:   key,\n\t\tPayloadType: e.PayloadType,\n\t\tRawPayload:  e.Payload,\n\t\tsignature:   e.Signature,\n\t}, nil\n}\n\n// Marshal returns a byte slice containing a serialized protobuf representation\n// of an Envelope.\nfunc (e *Envelope) Marshal() (res []byte, err error) {\n\tdefer func() { catch.HandlePanic(recover(), &err, \"libp2p envelope marshal\") }()\n\tkey, err := crypto.PublicKeyToProto(e.PublicKey)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tmsg := pb.Envelope{\n\t\tPublicKey:   key,\n\t\tPayloadType: e.PayloadType,\n\t\tPayload:     e.RawPayload,\n\t\tSignature:   e.signature,\n\t}\n\treturn proto.Marshal(&msg)\n}\n\n// Equal returns true if the other Envelope has the same public key,\n// payload, payload type, and signature. This implies that they were also\n// created with the same domain string.\nfunc (e *Envelope) Equal(other *Envelope) bool {\n\tif other == nil {\n\t\treturn e == nil\n\t}\n\treturn e.PublicKey.Equals(other.PublicKey) &&\n\t\tbytes.Equal(e.PayloadType, other.PayloadType) &&\n\t\tbytes.Equal(e.signature, other.signature) &&\n\t\tbytes.Equal(e.RawPayload, other.RawPayload)\n}\n\n// Record returns the Envelope's payload unmarshalled as a Record.\n// The concrete type of the returned Record depends on which Record\n// type was registered for the Envelope's PayloadType - see record.RegisterType.\n//\n// Once unmarshalled, the Record is cached for future access.\nfunc (e *Envelope) Record() (Record, error) {\n\te.unmarshalOnce.Do(func() {\n\t\tif e.cached != nil {\n\t\t\treturn\n\t\t}\n\t\te.cached, e.unmarshalError = unmarshalRecordPayload(e.PayloadType, e.RawPayload)\n\t})\n\treturn e.cached, e.unmarshalError\n}\n\n// TypedRecord unmarshals the Envelope's payload to the given Record instance.\n// It is the caller's responsibility to ensure that the Record type is capable\n// of unmarshalling the Envelope payload. Callers can inspect the Envelope's\n// PayloadType field to determine the correct type of Record to use.\n//\n// This method will always unmarshal the Envelope payload even if a cached record\n// exists.\nfunc (e *Envelope) TypedRecord(dest Record) error {\n\treturn dest.UnmarshalRecord(e.RawPayload)\n}\n\n// validate returns nil if the envelope signature is valid for the given 'domain',\n// or an error if signature validation fails.\nfunc (e *Envelope) validate(domain string) error {\n\tunsigned, err := makeUnsigned(domain, e.PayloadType, e.RawPayload)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer pool.Put(unsigned)\n\n\tvalid, err := e.PublicKey.Verify(unsigned, e.signature)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed while verifying signature: %w\", err)\n\t}\n\tif !valid {\n\t\treturn ErrInvalidSignature\n\t}\n\treturn nil\n}\n\n// makeUnsigned is a helper function that prepares a buffer to sign or verify.\n// It returns a byte slice from a pool. The caller MUST return this slice to the\n// pool.\nfunc makeUnsigned(domain string, payloadType []byte, payload []byte) ([]byte, error) {\n\tvar (\n\t\tfields = [][]byte{[]byte(domain), payloadType, payload}\n\n\t\t// fields are prefixed with their length as an unsigned varint. we\n\t\t// compute the lengths before allocating the sig buffer, so we know how\n\t\t// much space to add for the lengths\n\t\tflen = make([][]byte, len(fields))\n\t\tsize = 0\n\t)\n\n\tfor i, f := range fields {\n\t\tl := len(f)\n\t\tflen[i] = binary.AppendUvarint(nil, uint64(l))\n\t\tsize += l + len(flen[i])\n\t}\n\n\tb := pool.Get(size)\n\n\tvar s int\n\tfor i, f := range fields {\n\t\ts += copy(b[s:], flen[i])\n\t\ts += copy(b[s:], f)\n\t}\n\n\treturn b[:s], nil\n}\n"
  },
  {
    "path": "core/record/envelope_test.go",
    "content": "package record_test\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t. \"github.com/libp2p/go-libp2p/core/record\"\n\t\"github.com/libp2p/go-libp2p/core/record/pb\"\n\t\"github.com/libp2p/go-libp2p/core/test\"\n\n\t\"google.golang.org/protobuf/proto\"\n)\n\ntype simpleRecord struct {\n\ttestDomain *string\n\ttestCodec  []byte\n\tmessage    string\n}\n\nfunc (r *simpleRecord) Domain() string {\n\tif r.testDomain != nil {\n\t\treturn *r.testDomain\n\t}\n\treturn \"libp2p-testing\"\n}\n\nfunc (r *simpleRecord) Codec() []byte {\n\tif r.testCodec != nil {\n\t\treturn r.testCodec\n\t}\n\treturn []byte(\"/libp2p/testdata\")\n}\n\nfunc (r *simpleRecord) MarshalRecord() ([]byte, error) {\n\treturn []byte(r.message), nil\n}\n\nfunc (r *simpleRecord) UnmarshalRecord(buf []byte) error {\n\tr.message = string(buf)\n\treturn nil\n}\n\n// Make an envelope, verify & open it, marshal & unmarshal it\nfunc TestEnvelopeHappyPath(t *testing.T) {\n\tvar (\n\t\trec            = &simpleRecord{message: \"hello world!\"}\n\t\tpriv, pub, err = test.RandTestKeyPair(crypto.Ed25519, 256)\n\t)\n\n\ttest.AssertNilError(t, err)\n\n\tpayload, err := rec.MarshalRecord()\n\ttest.AssertNilError(t, err)\n\n\tenvelope, err := Seal(rec, priv)\n\ttest.AssertNilError(t, err)\n\n\tif !envelope.PublicKey.Equals(pub) {\n\t\tt.Error(\"envelope has unexpected public key\")\n\t}\n\n\tif !bytes.Equal(rec.Codec(), envelope.PayloadType) {\n\t\tt.Error(\"PayloadType does not match record Codec\")\n\t}\n\n\tserialized, err := envelope.Marshal()\n\ttest.AssertNilError(t, err)\n\n\tRegisterType(&simpleRecord{})\n\tdeserialized, rec2, err := ConsumeEnvelope(serialized, rec.Domain())\n\ttest.AssertNilError(t, err)\n\n\tif !bytes.Equal(deserialized.RawPayload, payload) {\n\t\tt.Error(\"payload of envelope does not match input\")\n\t}\n\n\tif !envelope.Equal(deserialized) {\n\t\tt.Error(\"round-trip serde results in unequal envelope structures\")\n\t}\n\n\ttypedRec, ok := rec2.(*simpleRecord)\n\tif !ok {\n\t\tt.Error(\"expected ConsumeEnvelope to return record with type registered for payloadType\")\n\t}\n\tif typedRec.message != \"hello world!\" {\n\t\tt.Error(\"unexpected alteration of record\")\n\t}\n}\n\nfunc TestConsumeTypedEnvelope(t *testing.T) {\n\tvar (\n\t\trec        = simpleRecord{message: \"hello world!\"}\n\t\tpriv, _, _ = test.RandTestKeyPair(crypto.Ed25519, 256)\n\t)\n\n\tenvelope, err := Seal(&rec, priv)\n\ttest.AssertNilError(t, err)\n\n\tenvelopeBytes, err := envelope.Marshal()\n\ttest.AssertNilError(t, err)\n\n\trec2 := &simpleRecord{}\n\t_, err = ConsumeTypedEnvelope(envelopeBytes, rec2)\n\ttest.AssertNilError(t, err)\n\n\tif rec2.message != \"hello world!\" {\n\t\tt.Error(\"unexpected alteration of record\")\n\t}\n}\n\nfunc TestMakeEnvelopeFailsWithEmptyDomain(t *testing.T) {\n\tvar (\n\t\trec          = simpleRecord{message: \"hello world!\"}\n\t\tdomain       = \"\"\n\t\tpriv, _, err = test.RandTestKeyPair(crypto.Ed25519, 256)\n\t)\n\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// override domain with empty string\n\trec.testDomain = &domain\n\n\t_, err = Seal(&rec, priv)\n\ttest.ExpectError(t, err, \"making an envelope with an empty domain should fail\")\n}\n\nfunc TestMakeEnvelopeFailsWithEmptyPayloadType(t *testing.T) {\n\tvar (\n\t\trec          = simpleRecord{message: \"hello world!\"}\n\t\tpriv, _, err = test.RandTestKeyPair(crypto.Ed25519, 256)\n\t)\n\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// override payload with empty slice\n\trec.testCodec = []byte{}\n\n\t_, err = Seal(&rec, priv)\n\ttest.ExpectError(t, err, \"making an envelope with an empty payloadType should fail\")\n}\n\ntype failingRecord struct {\n\tallowMarshal   bool\n\tallowUnmarshal bool\n}\n\nfunc (r failingRecord) Domain() string {\n\treturn \"testing\"\n}\n\nfunc (r failingRecord) Codec() []byte {\n\treturn []byte(\"doesn't matter\")\n}\n\nfunc (r failingRecord) MarshalRecord() ([]byte, error) {\n\tif r.allowMarshal {\n\t\treturn []byte{}, nil\n\t}\n\treturn nil, errors.New(\"marshal failed\")\n}\nfunc (r failingRecord) UnmarshalRecord(_ []byte) error {\n\tif r.allowUnmarshal {\n\t\treturn nil\n\t}\n\treturn errors.New(\"unmarshal failed\")\n}\n\nfunc TestSealFailsIfRecordMarshalFails(t *testing.T) {\n\tvar (\n\t\tpriv, _, err = test.RandTestKeyPair(crypto.Ed25519, 256)\n\t)\n\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\trec := failingRecord{}\n\t_, err = Seal(rec, priv)\n\ttest.ExpectError(t, err, \"Seal should fail if Record fails to marshal\")\n}\n\nfunc TestConsumeEnvelopeFailsIfEnvelopeUnmarshalFails(t *testing.T) {\n\t_, _, err := ConsumeEnvelope([]byte(\"not an Envelope protobuf\"), \"doesn't-matter\")\n\ttest.ExpectError(t, err, \"ConsumeEnvelope should fail if Envelope fails to unmarshal\")\n}\n\nfunc TestConsumeEnvelopeFailsIfRecordUnmarshalFails(t *testing.T) {\n\tvar (\n\t\tpriv, _, err = test.RandTestKeyPair(crypto.Ed25519, 256)\n\t)\n\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tRegisterType(failingRecord{})\n\trec := failingRecord{allowMarshal: true}\n\tenv, err := Seal(rec, priv)\n\ttest.AssertNilError(t, err)\n\tenvBytes, err := env.Marshal()\n\ttest.AssertNilError(t, err)\n\n\t_, _, err = ConsumeEnvelope(envBytes, rec.Domain())\n\ttest.ExpectError(t, err, \"ConsumeEnvelope should fail if Record fails to unmarshal\")\n}\n\nfunc TestConsumeTypedEnvelopeFailsIfRecordUnmarshalFails(t *testing.T) {\n\tvar (\n\t\tpriv, _, err = test.RandTestKeyPair(crypto.Ed25519, 256)\n\t)\n\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tRegisterType(failingRecord{})\n\trec := failingRecord{allowMarshal: true}\n\tenv, err := Seal(rec, priv)\n\ttest.AssertNilError(t, err)\n\tenvBytes, err := env.Marshal()\n\ttest.AssertNilError(t, err)\n\n\trec2 := failingRecord{}\n\t_, err = ConsumeTypedEnvelope(envBytes, rec2)\n\ttest.ExpectError(t, err, \"ConsumeTypedEnvelope should fail if Record fails to unmarshal\")\n}\n\nfunc TestEnvelopeValidateFailsForDifferentDomain(t *testing.T) {\n\tvar (\n\t\trec          = &simpleRecord{message: \"hello world\"}\n\t\tpriv, _, err = test.RandTestKeyPair(crypto.Ed25519, 256)\n\t)\n\n\ttest.AssertNilError(t, err)\n\n\tenvelope, err := Seal(rec, priv)\n\ttest.AssertNilError(t, err)\n\n\tserialized, err := envelope.Marshal()\n\ttest.AssertNilError(t, err)\n\n\t// try to open our modified envelope\n\t_, _, err = ConsumeEnvelope(serialized, \"wrong-domain\")\n\ttest.ExpectError(t, err, \"should not be able to open envelope with incorrect domain\")\n}\n\nfunc TestEnvelopeValidateFailsIfPayloadTypeIsAltered(t *testing.T) {\n\tvar (\n\t\trec          = &simpleRecord{message: \"hello world!\"}\n\t\tdomain       = \"libp2p-testing\"\n\t\tpriv, _, err = test.RandTestKeyPair(crypto.Ed25519, 256)\n\t)\n\n\ttest.AssertNilError(t, err)\n\n\tenvelope, err := Seal(rec, priv)\n\ttest.AssertNilError(t, err)\n\n\tserialized := alterMessageAndMarshal(t, envelope, func(msg *pb.Envelope) {\n\t\tmsg.PayloadType = []byte(\"foo\")\n\t})\n\n\t// try to open our modified envelope\n\t_, _, err = ConsumeEnvelope(serialized, domain)\n\ttest.ExpectError(t, err, \"should not be able to open envelope with modified PayloadType\")\n}\n\nfunc TestEnvelopeValidateFailsIfContentsAreAltered(t *testing.T) {\n\tvar (\n\t\trec          = &simpleRecord{message: \"hello world!\"}\n\t\tdomain       = \"libp2p-testing\"\n\t\tpriv, _, err = test.RandTestKeyPair(crypto.Ed25519, 256)\n\t)\n\n\ttest.AssertNilError(t, err)\n\n\tenvelope, err := Seal(rec, priv)\n\ttest.AssertNilError(t, err)\n\n\tserialized := alterMessageAndMarshal(t, envelope, func(msg *pb.Envelope) {\n\t\tmsg.Payload = []byte(\"totally legit, trust me\")\n\t})\n\n\t// try to open our modified envelope\n\t_, _, err = ConsumeEnvelope(serialized, domain)\n\ttest.ExpectError(t, err, \"should not be able to open envelope with modified payload\")\n}\n\n// Since we're outside of the crypto package (to avoid import cycles with test package),\n// we can't alter the fields in a Envelope directly. This helper marshals\n// the envelope to a protobuf and calls the alterMsg function, which should\n// alter the protobuf message.\n// Returns the serialized altered protobuf message.\nfunc alterMessageAndMarshal(t *testing.T, envelope *Envelope, alterMsg func(*pb.Envelope)) []byte {\n\tt.Helper()\n\n\tserialized, err := envelope.Marshal()\n\ttest.AssertNilError(t, err)\n\n\tmsg := pb.Envelope{}\n\terr = proto.Unmarshal(serialized, &msg)\n\ttest.AssertNilError(t, err)\n\n\talterMsg(&msg)\n\tserialized, err = proto.Marshal(&msg)\n\ttest.AssertNilError(t, err)\n\n\treturn serialized\n}\n"
  },
  {
    "path": "core/record/pb/envelope.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.6\n// \tprotoc        v5.29.2\n// source: core/record/pb/envelope.proto\n\npackage pb\n\nimport (\n\tpb \"github.com/libp2p/go-libp2p/core/crypto/pb\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// Envelope encloses a signed payload produced by a peer, along with the public\n// key of the keypair it was signed with so that it can be statelessly validated\n// by the receiver.\n//\n// The payload is prefixed with a byte string that determines the type, so it\n// can be deserialized deterministically. Often, this byte string is a\n// multicodec.\ntype Envelope struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// public_key is the public key of the keypair the enclosed payload was\n\t// signed with.\n\tPublicKey *pb.PublicKey `protobuf:\"bytes,1,opt,name=public_key,json=publicKey,proto3\" json:\"public_key,omitempty\"`\n\t// payload_type encodes the type of payload, so that it can be deserialized\n\t// deterministically.\n\tPayloadType []byte `protobuf:\"bytes,2,opt,name=payload_type,json=payloadType,proto3\" json:\"payload_type,omitempty\"`\n\t// payload is the actual payload carried inside this envelope.\n\tPayload []byte `protobuf:\"bytes,3,opt,name=payload,proto3\" json:\"payload,omitempty\"`\n\t// signature is the signature produced by the private key corresponding to\n\t// the enclosed public key, over the payload, prefixing a domain string for\n\t// additional security.\n\tSignature     []byte `protobuf:\"bytes,5,opt,name=signature,proto3\" json:\"signature,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Envelope) Reset() {\n\t*x = Envelope{}\n\tmi := &file_core_record_pb_envelope_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Envelope) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Envelope) ProtoMessage() {}\n\nfunc (x *Envelope) ProtoReflect() protoreflect.Message {\n\tmi := &file_core_record_pb_envelope_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Envelope.ProtoReflect.Descriptor instead.\nfunc (*Envelope) Descriptor() ([]byte, []int) {\n\treturn file_core_record_pb_envelope_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Envelope) GetPublicKey() *pb.PublicKey {\n\tif x != nil {\n\t\treturn x.PublicKey\n\t}\n\treturn nil\n}\n\nfunc (x *Envelope) GetPayloadType() []byte {\n\tif x != nil {\n\t\treturn x.PayloadType\n\t}\n\treturn nil\n}\n\nfunc (x *Envelope) GetPayload() []byte {\n\tif x != nil {\n\t\treturn x.Payload\n\t}\n\treturn nil\n}\n\nfunc (x *Envelope) GetSignature() []byte {\n\tif x != nil {\n\t\treturn x.Signature\n\t}\n\treturn nil\n}\n\nvar File_core_record_pb_envelope_proto protoreflect.FileDescriptor\n\nconst file_core_record_pb_envelope_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x1dcore/record/pb/envelope.proto\\x12\\trecord.pb\\x1a\\x1bcore/crypto/pb/crypto.proto\\\"\\x9a\\x01\\n\" +\n\t\"\\bEnvelope\\x123\\n\" +\n\t\"\\n\" +\n\t\"public_key\\x18\\x01 \\x01(\\v2\\x14.crypto.pb.PublicKeyR\\tpublicKey\\x12!\\n\" +\n\t\"\\fpayload_type\\x18\\x02 \\x01(\\fR\\vpayloadType\\x12\\x18\\n\" +\n\t\"\\apayload\\x18\\x03 \\x01(\\fR\\apayload\\x12\\x1c\\n\" +\n\t\"\\tsignature\\x18\\x05 \\x01(\\fR\\tsignatureB,Z*github.com/libp2p/go-libp2p/core/record/pbb\\x06proto3\"\n\nvar (\n\tfile_core_record_pb_envelope_proto_rawDescOnce sync.Once\n\tfile_core_record_pb_envelope_proto_rawDescData []byte\n)\n\nfunc file_core_record_pb_envelope_proto_rawDescGZIP() []byte {\n\tfile_core_record_pb_envelope_proto_rawDescOnce.Do(func() {\n\t\tfile_core_record_pb_envelope_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_core_record_pb_envelope_proto_rawDesc), len(file_core_record_pb_envelope_proto_rawDesc)))\n\t})\n\treturn file_core_record_pb_envelope_proto_rawDescData\n}\n\nvar file_core_record_pb_envelope_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_core_record_pb_envelope_proto_goTypes = []any{\n\t(*Envelope)(nil),     // 0: record.pb.Envelope\n\t(*pb.PublicKey)(nil), // 1: crypto.pb.PublicKey\n}\nvar file_core_record_pb_envelope_proto_depIdxs = []int32{\n\t1, // 0: record.pb.Envelope.public_key:type_name -> crypto.pb.PublicKey\n\t1, // [1:1] is the sub-list for method output_type\n\t1, // [1:1] is the sub-list for method input_type\n\t1, // [1:1] is the sub-list for extension type_name\n\t1, // [1:1] is the sub-list for extension extendee\n\t0, // [0:1] is the sub-list for field type_name\n}\n\nfunc init() { file_core_record_pb_envelope_proto_init() }\nfunc file_core_record_pb_envelope_proto_init() {\n\tif File_core_record_pb_envelope_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_core_record_pb_envelope_proto_rawDesc), len(file_core_record_pb_envelope_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_core_record_pb_envelope_proto_goTypes,\n\t\tDependencyIndexes: file_core_record_pb_envelope_proto_depIdxs,\n\t\tMessageInfos:      file_core_record_pb_envelope_proto_msgTypes,\n\t}.Build()\n\tFile_core_record_pb_envelope_proto = out.File\n\tfile_core_record_pb_envelope_proto_goTypes = nil\n\tfile_core_record_pb_envelope_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "core/record/pb/envelope.proto",
    "content": "syntax = \"proto3\";\n\npackage record.pb;\n\nimport \"core/crypto/pb/crypto.proto\";\n\noption go_package = \"github.com/libp2p/go-libp2p/core/record/pb\";\n\n// Envelope encloses a signed payload produced by a peer, along with the public\n// key of the keypair it was signed with so that it can be statelessly validated\n// by the receiver.\n//\n// The payload is prefixed with a byte string that determines the type, so it\n// can be deserialized deterministically. Often, this byte string is a\n// multicodec.\nmessage Envelope {\n    // public_key is the public key of the keypair the enclosed payload was\n    // signed with.\n    crypto.pb.PublicKey public_key = 1;\n\n    // payload_type encodes the type of payload, so that it can be deserialized\n    // deterministically.\n    bytes payload_type = 2;\n\n    // payload is the actual payload carried inside this envelope.\n    bytes payload = 3;\n\n    // signature is the signature produced by the private key corresponding to\n    // the enclosed public key, over the payload, prefixing a domain string for\n    // additional security.\n    bytes signature = 5;\n}\n"
  },
  {
    "path": "core/record/record.go",
    "content": "package record\n\nimport (\n\t\"errors\"\n\t\"reflect\"\n\n\t\"github.com/libp2p/go-libp2p/core/internal/catch\"\n)\n\nvar (\n\t// ErrPayloadTypeNotRegistered is returned from ConsumeEnvelope when the Envelope's\n\t// PayloadType does not match any registered Record types.\n\tErrPayloadTypeNotRegistered = errors.New(\"payload type is not registered\")\n\n\tpayloadTypeRegistry = make(map[string]reflect.Type)\n)\n\n// Record represents a data type that can be used as the payload of an Envelope.\n// The Record interface defines the methods used to marshal and unmarshal a Record\n// type to a byte slice.\n//\n// Record types may be \"registered\" as the default for a given Envelope.PayloadType\n// using the RegisterType function. Once a Record type has been registered,\n// an instance of that type will be created and used to unmarshal the payload of\n// any Envelope with the registered PayloadType when the Envelope is opened using\n// the ConsumeEnvelope function.\n//\n// To use an unregistered Record type instead, use ConsumeTypedEnvelope and pass in\n// an instance of the Record type that you'd like the Envelope's payload to be\n// unmarshaled into.\ntype Record interface {\n\n\t// Domain is the \"signature domain\" used when signing and verifying a particular\n\t// Record type. The Domain string should be unique to your Record type, and all\n\t// instances of the Record type must have the same Domain string.\n\tDomain() string\n\n\t// Codec is a binary identifier for this type of record, ideally a registered multicodec\n\t// (see https://github.com/multiformats/multicodec).\n\t// When a Record is put into an Envelope (see record.Seal), the Codec value will be used\n\t// as the Envelope's PayloadType. When the Envelope is later unsealed, the PayloadType\n\t// will be used to look up the correct Record type to unmarshal the Envelope payload into.\n\tCodec() []byte\n\n\t// MarshalRecord converts a Record instance to a []byte, so that it can be used as an\n\t// Envelope payload.\n\tMarshalRecord() ([]byte, error)\n\n\t// UnmarshalRecord unmarshals a []byte payload into an instance of a particular Record type.\n\tUnmarshalRecord([]byte) error\n}\n\n// RegisterType associates a binary payload type identifier with a concrete\n// Record type. This is used to automatically unmarshal Record payloads from Envelopes\n// when using ConsumeEnvelope, and to automatically marshal Records and determine the\n// correct PayloadType when calling Seal.\n//\n// Callers must provide an instance of the record type to be registered, which must be\n// a pointer type. Registration should be done in the init function of the package\n// where the Record type is defined:\n//\n//\tpackage hello_record\n//\timport record \"github.com/libp2p/go-libp2p/core/record\"\n//\n//\tfunc init() {\n//\t    record.RegisterType(&HelloRecord{})\n//\t}\n//\n//\ttype HelloRecord struct { } // etc..\nfunc RegisterType(prototype Record) {\n\tpayloadTypeRegistry[string(prototype.Codec())] = getValueType(prototype)\n}\n\nfunc unmarshalRecordPayload(payloadType []byte, payloadBytes []byte) (_rec Record, err error) {\n\tdefer func() { catch.HandlePanic(recover(), &err, \"libp2p envelope record unmarshal\") }()\n\n\trec, err := blankRecordForPayloadType(payloadType)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = rec.UnmarshalRecord(payloadBytes)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn rec, nil\n}\n\nfunc blankRecordForPayloadType(payloadType []byte) (Record, error) {\n\tvalueType, ok := payloadTypeRegistry[string(payloadType)]\n\tif !ok {\n\t\treturn nil, ErrPayloadTypeNotRegistered\n\t}\n\n\tval := reflect.New(valueType)\n\tasRecord := val.Interface().(Record)\n\treturn asRecord, nil\n}\n\nfunc getValueType(i any) reflect.Type {\n\tvalueType := reflect.TypeOf(i)\n\tif valueType.Kind() == reflect.Pointer {\n\t\tvalueType = valueType.Elem()\n\t}\n\treturn valueType\n}\n"
  },
  {
    "path": "core/record/record_test.go",
    "content": "package record\n\nimport \"testing\"\n\nvar testPayloadType = []byte(\"/libp2p/test/record/payload-type\")\n\ntype testPayload struct {\n\tunmarshalPayloadCalled bool\n}\n\nfunc (p *testPayload) Domain() string {\n\treturn \"testing\"\n}\n\nfunc (p *testPayload) Codec() []byte {\n\treturn testPayloadType\n}\n\nfunc (p *testPayload) MarshalRecord() ([]byte, error) {\n\treturn []byte(\"hello\"), nil\n}\n\nfunc (p *testPayload) UnmarshalRecord(_ []byte) error {\n\tp.unmarshalPayloadCalled = true\n\treturn nil\n}\n\nfunc TestUnmarshalPayload(t *testing.T) {\n\tt.Run(\"fails if payload type is unregistered\", func(t *testing.T) {\n\t\t_, err := unmarshalRecordPayload([]byte(\"unknown type\"), []byte{})\n\t\tif err != ErrPayloadTypeNotRegistered {\n\t\t\tt.Error(\"Expected error when unmarshalling payload with unregistered payload type\")\n\t\t}\n\t})\n\n\tt.Run(\"calls UnmarshalRecord on concrete Record type\", func(t *testing.T) {\n\t\tRegisterType(&testPayload{})\n\n\t\tpayload, err := unmarshalRecordPayload(testPayloadType, []byte{})\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error unmarshalling registered payload type: %v\", err)\n\t\t}\n\t\ttypedPayload, ok := payload.(*testPayload)\n\t\tif !ok {\n\t\t\tt.Error(\"expected unmarshalled payload to be of the correct type\")\n\t\t}\n\t\tif !typedPayload.unmarshalPayloadCalled {\n\t\t\tt.Error(\"expected UnmarshalRecord to be called on concrete Record instance\")\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "core/routing/options.go",
    "content": "package routing\n\nimport \"maps\"\n\n// Option is a single routing option.\ntype Option func(opts *Options) error\n\n// Options is a set of routing options\ntype Options struct {\n\t// Allow expired values.\n\tExpired bool\n\tOffline bool\n\t// Other (ValueStore implementation specific) options.\n\tOther map[any]any\n}\n\n// Apply applies the given options to this Options\nfunc (opts *Options) Apply(options ...Option) error {\n\tfor _, o := range options {\n\t\tif err := o(opts); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// ToOption converts this Options to a single Option.\nfunc (opts *Options) ToOption() Option {\n\treturn func(nopts *Options) error {\n\t\t*nopts = *opts\n\t\tif opts.Other != nil {\n\t\t\tnopts.Other = make(map[any]any, len(opts.Other))\n\t\t\tmaps.Copy(nopts.Other, opts.Other)\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// Expired is an option that tells the routing system to return expired records\n// when no newer records are known.\nvar Expired Option = func(opts *Options) error {\n\topts.Expired = true\n\treturn nil\n}\n\n// Offline is an option that tells the routing system to operate offline (i.e., rely on cached/local data only).\nvar Offline Option = func(opts *Options) error {\n\topts.Offline = true\n\treturn nil\n}\n"
  },
  {
    "path": "core/routing/query.go",
    "content": "package routing\n\nimport (\n\t\"context\"\n\t\"sync\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n)\n\n// QueryEventType indicates the query event's type.\ntype QueryEventType int\n\n// Number of events to buffer.\nvar QueryEventBufferSize = 16\n\nconst (\n\t// Sending a query to a peer.\n\tSendingQuery QueryEventType = iota\n\t// Got a response from a peer.\n\tPeerResponse\n\t// Found a \"closest\" peer (not currently used).\n\tFinalPeer\n\t// Got an error when querying.\n\tQueryError\n\t// Found a provider.\n\tProvider\n\t// Found a value.\n\tValue\n\t// Adding a peer to the query.\n\tAddingPeer\n\t// Dialing a peer.\n\tDialingPeer\n)\n\n// QueryEvent is emitted for every notable event that happens during a DHT query.\ntype QueryEvent struct {\n\tID        peer.ID\n\tType      QueryEventType\n\tResponses []*peer.AddrInfo\n\tExtra     string\n}\n\ntype routingQueryKey struct{}\ntype eventChannel struct {\n\tmu  sync.Mutex\n\tctx context.Context\n\tch  chan<- *QueryEvent\n}\n\n// waitThenClose is spawned in a goroutine when the channel is registered. This\n// safely cleans up the channel when the context has been canceled.\nfunc (e *eventChannel) waitThenClose() {\n\t<-e.ctx.Done()\n\te.mu.Lock()\n\tclose(e.ch)\n\t// 1. Signals that we're done.\n\t// 2. Frees memory (in case we end up hanging on to this for a while).\n\te.ch = nil\n\te.mu.Unlock()\n}\n\n// send sends an event on the event channel, aborting if either the passed or\n// the internal context expire.\nfunc (e *eventChannel) send(ctx context.Context, ev *QueryEvent) {\n\te.mu.Lock()\n\t// Closed.\n\tif e.ch == nil {\n\t\te.mu.Unlock()\n\t\treturn\n\t}\n\t// in case the passed context is unrelated, wait on both.\n\tselect {\n\tcase e.ch <- ev:\n\tcase <-e.ctx.Done():\n\tcase <-ctx.Done():\n\t}\n\te.mu.Unlock()\n}\n\n// RegisterForQueryEvents registers a query event channel with the given\n// context. The returned context can be passed to DHT queries to receive query\n// events on the returned channels.\n//\n// The passed context MUST be canceled when the caller is no longer interested\n// in query events.\nfunc RegisterForQueryEvents(ctx context.Context) (context.Context, <-chan *QueryEvent) {\n\tch := make(chan *QueryEvent, QueryEventBufferSize)\n\tech := &eventChannel{ch: ch, ctx: ctx}\n\tgo ech.waitThenClose()\n\treturn context.WithValue(ctx, routingQueryKey{}, ech), ch\n}\n\n// PublishQueryEvent publishes a query event to the query event channel\n// associated with the given context, if any.\nfunc PublishQueryEvent(ctx context.Context, ev *QueryEvent) {\n\tich := ctx.Value(routingQueryKey{})\n\tif ich == nil {\n\t\treturn\n\t}\n\n\t// We *want* to panic here.\n\tech := ich.(*eventChannel)\n\tech.send(ctx, ev)\n}\n\n// SubscribesToQueryEvents returns true if the context subscribes to query\n// events. If this function returns false, calling `PublishQueryEvent` on the\n// context will be a no-op.\nfunc SubscribesToQueryEvents(ctx context.Context) bool {\n\treturn ctx.Value(routingQueryKey{}) != nil\n}\n"
  },
  {
    "path": "core/routing/query_serde.go",
    "content": "package routing\n\nimport (\n\t\"encoding/json\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n)\n\nfunc (qe *QueryEvent) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(map[string]any{\n\t\t\"ID\":        qe.ID.String(),\n\t\t\"Type\":      int(qe.Type),\n\t\t\"Responses\": qe.Responses,\n\t\t\"Extra\":     qe.Extra,\n\t})\n}\n\nfunc (qe *QueryEvent) UnmarshalJSON(b []byte) error {\n\ttemp := struct {\n\t\tID        string\n\t\tType      int\n\t\tResponses []*peer.AddrInfo\n\t\tExtra     string\n\t}{}\n\terr := json.Unmarshal(b, &temp)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(temp.ID) > 0 {\n\t\tpid, err := peer.Decode(temp.ID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tqe.ID = pid\n\t}\n\tqe.Type = QueryEventType(temp.Type)\n\tqe.Responses = temp.Responses\n\tqe.Extra = temp.Extra\n\treturn nil\n}\n"
  },
  {
    "path": "core/routing/query_test.go",
    "content": "package routing\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"sync\"\n\t\"testing\"\n)\n\nfunc TestEventsCancel(t *testing.T) {\n\tctx, cancel := context.WithCancel(context.Background())\n\tctx, events := RegisterForQueryEvents(ctx)\n\tgoch := make(chan struct{})\n\tvar wg sync.WaitGroup\n\twg.Add(2)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tfor i := range 100 {\n\t\t\tPublishQueryEvent(ctx, &QueryEvent{Extra: fmt.Sprint(i)})\n\t\t}\n\t\tclose(goch)\n\t\tfor i := 100; i < 1000; i++ {\n\t\t\tPublishQueryEvent(ctx, &QueryEvent{Extra: fmt.Sprint(i)})\n\t\t}\n\t}()\n\tgo func() {\n\t\tdefer wg.Done()\n\t\ti := 0\n\t\tfor e := range events {\n\t\t\tif i < 100 {\n\t\t\t\tif e.Extra != fmt.Sprint(i) {\n\t\t\t\t\tt.Errorf(\"expected %d, got %s\", i, e.Extra)\n\t\t\t\t}\n\t\t\t}\n\t\t\ti++\n\t\t}\n\t\tif i < 100 {\n\t\t\tt.Errorf(\"expected at least 100 events, got %d\", i)\n\t\t}\n\t}()\n\t<-goch\n\tcancel()\n\twg.Wait()\n}\n"
  },
  {
    "path": "core/routing/routing.go",
    "content": "// Package routing provides interfaces for peer routing and content routing in libp2p.\npackage routing\n\nimport (\n\t\"context\"\n\t\"errors\"\n\n\tci \"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\n\tcid \"github.com/ipfs/go-cid\"\n)\n\n// ErrNotFound is returned when the router fails to find the requested record.\nvar ErrNotFound = errors.New(\"routing: not found\")\n\n// ErrNotSupported is returned when the router doesn't support the given record\n// type/operation.\nvar ErrNotSupported = errors.New(\"routing: operation or key not supported\")\n\n// ContentProviding is able to announce where to find content on the Routing\n// system.\ntype ContentProviding interface {\n\t// Provide adds the given cid to the content routing system. If 'true' is\n\t// passed, it also announces it, otherwise it is just kept in the local\n\t// accounting of which objects are being provided.\n\tProvide(context.Context, cid.Cid, bool) error\n}\n\n// ContentDiscovery is able to retrieve providers for a given CID using\n// the Routing system.\ntype ContentDiscovery interface {\n\t// Search for peers who are able to provide a given key\n\t//\n\t// When count is 0, this method will return an unbounded number of\n\t// results.\n\tFindProvidersAsync(context.Context, cid.Cid, int) <-chan peer.AddrInfo\n}\n\n// ContentRouting is a value provider layer of indirection. It is used to find\n// information about who has what content.\n//\n// Content is identified by CID (content identifier), which encodes a hash\n// of the identified content in a future-proof manner.\ntype ContentRouting interface {\n\tContentProviding\n\tContentDiscovery\n}\n\n// PeerRouting is a way to find address information about certain peers.\n// This can be implemented by a simple lookup table, a tracking server,\n// or even a DHT.\ntype PeerRouting interface {\n\t// FindPeer searches for a peer with given ID, returns a peer.AddrInfo\n\t// with relevant addresses.\n\tFindPeer(context.Context, peer.ID) (peer.AddrInfo, error)\n}\n\n// ValueStore is a basic Put/Get interface.\ntype ValueStore interface {\n\n\t// PutValue adds value corresponding to given Key.\n\tPutValue(context.Context, string, []byte, ...Option) error\n\n\t// GetValue searches for the value corresponding to given Key.\n\tGetValue(context.Context, string, ...Option) ([]byte, error)\n\n\t// SearchValue searches for better and better values from this value\n\t// store corresponding to the given Key. By default, implementations must\n\t// stop the search after a good value is found. A 'good' value is a value\n\t// that would be returned from GetValue.\n\t//\n\t// Useful when you want a result *now* but still want to hear about\n\t// better/newer results.\n\t//\n\t// Implementations of this methods won't return ErrNotFound. When a value\n\t// couldn't be found, the channel will get closed without passing any results\n\tSearchValue(context.Context, string, ...Option) (<-chan []byte, error)\n}\n\n// Routing is the combination of different routing types supported by libp2p.\n// It can be satisfied by a single item (such as a DHT) or multiple different\n// pieces that are more optimized to each task.\ntype Routing interface {\n\tContentRouting\n\tPeerRouting\n\tValueStore\n\n\t// Bootstrap allows callers to hint to the routing system to get into a\n\t// Bootstrapped state and remain there. It is not a synchronous call.\n\tBootstrap(context.Context) error\n\n\t// TODO expose io.Closer or plain-old Close error\n}\n\n// PubKeyFetcher is an interfaces that should be implemented by value stores\n// that can optimize retrieval of public keys.\n//\n// TODO(steb): Consider removing, see https://github.com/libp2p/go-libp2p-routing/issues/22.\ntype PubKeyFetcher interface {\n\t// GetPublicKey returns the public key for the given peer.\n\tGetPublicKey(context.Context, peer.ID) (ci.PubKey, error)\n}\n\n// KeyForPublicKey returns the key used to retrieve public keys\n// from a value store.\nfunc KeyForPublicKey(id peer.ID) string {\n\treturn \"/pk/\" + string(id)\n}\n\n// GetPublicKey retrieves the public key associated with the given peer ID from\n// the value store.\n//\n// If the ValueStore is also a PubKeyFetcher, this method will call GetPublicKey\n// (which may be better optimized) instead of GetValue.\nfunc GetPublicKey(r ValueStore, ctx context.Context, p peer.ID) (ci.PubKey, error) {\n\tswitch k, err := p.ExtractPublicKey(); err {\n\tcase peer.ErrNoPublicKey:\n\t\t// check the datastore\n\tcase nil:\n\t\treturn k, nil\n\tdefault:\n\t\treturn nil, err\n\t}\n\n\tif dht, ok := r.(PubKeyFetcher); ok {\n\t\t// If we have a DHT as our routing system, use optimized fetcher\n\t\treturn dht.GetPublicKey(ctx, p)\n\t}\n\tkey := KeyForPublicKey(p)\n\tpkval, err := r.GetValue(ctx, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// get PublicKey from node.Data\n\treturn ci.UnmarshalPublicKey(pkval)\n}\n"
  },
  {
    "path": "core/sec/security.go",
    "content": "// Package sec provides secure connection and transport interfaces for libp2p.\npackage sec\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n)\n\n// SecureConn is an authenticated, encrypted connection.\ntype SecureConn interface {\n\tnet.Conn\n\tnetwork.ConnSecurity\n}\n\n// A SecureTransport turns inbound and outbound unauthenticated,\n// plain-text, native connections into authenticated, encrypted connections.\ntype SecureTransport interface {\n\t// SecureInbound secures an inbound connection.\n\t// If p is empty, connections from any peer are accepted.\n\tSecureInbound(ctx context.Context, insecure net.Conn, p peer.ID) (SecureConn, error)\n\n\t// SecureOutbound secures an outbound connection.\n\tSecureOutbound(ctx context.Context, insecure net.Conn, p peer.ID) (SecureConn, error)\n\n\t// ID is the protocol ID of the security protocol.\n\tID() protocol.ID\n}\n\ntype ErrPeerIDMismatch struct {\n\tExpected peer.ID\n\tActual   peer.ID\n}\n\nfunc (e ErrPeerIDMismatch) Error() string {\n\treturn fmt.Sprintf(\"peer id mismatch: expected %s, but remote key matches %s\", e.Expected, e.Actual)\n}\n\nvar _ error = (*ErrPeerIDMismatch)(nil)\n"
  },
  {
    "path": "core/test/addrs.go",
    "content": "package test\n\nimport (\n\t\"fmt\"\n\t\"slices\"\n\t\"testing\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\nfunc GenerateTestAddrs(n int) []ma.Multiaddr {\n\tout := make([]ma.Multiaddr, n)\n\tfor i := range n {\n\t\ta, err := ma.NewMultiaddr(fmt.Sprintf(\"/ip4/1.2.3.4/tcp/%d\", i))\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tout[i] = a\n\t}\n\treturn out\n}\n\nfunc AssertAddressesEqual(t *testing.T, exp, act []ma.Multiaddr) {\n\tt.Helper()\n\tif len(exp) != len(act) {\n\t\tt.Fatalf(\"lengths not the same. expected %d, got %d\\n\", len(exp), len(act))\n\t}\n\n\tfor _, a := range exp {\n\t\tfound := slices.ContainsFunc(act, a.Equal)\n\n\t\tif !found {\n\t\t\tt.Fatalf(\"expected address %s not found\", a)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "core/test/crypto.go",
    "content": "package test\n\nimport (\n\t\"math/rand\"\n\t\"sync/atomic\"\n\n\tci \"github.com/libp2p/go-libp2p/core/crypto\"\n)\n\nvar globalSeed atomic.Int64\n\nfunc RandTestKeyPair(typ, bits int) (ci.PrivKey, ci.PubKey, error) {\n\t// workaround for low time resolution\n\tseed := globalSeed.Add(1)\n\treturn SeededTestKeyPair(typ, bits, seed)\n}\n\nfunc SeededTestKeyPair(typ, bits int, seed int64) (ci.PrivKey, ci.PubKey, error) {\n\tr := rand.New(rand.NewSource(seed))\n\treturn ci.GenerateKeyPairWithReader(typ, bits, r)\n}\n"
  },
  {
    "path": "core/test/errors.go",
    "content": "package test\n\nimport (\n\t\"testing\"\n)\n\nfunc AssertNilError(t *testing.T, err error) {\n\tt.Helper()\n\tif err != nil {\n\t\tt.Errorf(\"unexpected error: %v\", err)\n\t}\n}\n\nfunc ExpectError(t *testing.T, err error, msg string) {\n\tt.Helper()\n\tif err == nil {\n\t\tt.Error(msg)\n\t}\n}\n"
  },
  {
    "path": "core/test/mockclock.go",
    "content": "package test\n\nimport (\n\t\"sort\"\n\t\"sync\"\n\t\"time\"\n)\n\ntype MockClock struct {\n\tmu           sync.Mutex\n\tnow          time.Time\n\ttimers       []*mockInstantTimer\n\tadvanceBySem chan struct{}\n}\n\ntype mockInstantTimer struct {\n\tc      *MockClock\n\tmu     sync.Mutex\n\twhen   time.Time\n\tactive bool\n\tch     chan time.Time\n}\n\nfunc (t *mockInstantTimer) Ch() <-chan time.Time {\n\treturn t.ch\n}\n\nfunc (t *mockInstantTimer) Reset(d time.Time) bool {\n\tt.mu.Lock()\n\tdefer t.mu.Unlock()\n\twasActive := t.active\n\tt.active = true\n\tt.when = d\n\n\t// Schedule any timers that need to run. This will run this timer if t.when is before c.now\n\tgo t.c.AdvanceBy(0)\n\n\treturn wasActive\n}\n\nfunc (t *mockInstantTimer) Stop() bool {\n\tt.mu.Lock()\n\tdefer t.mu.Unlock()\n\twasActive := t.active\n\tt.active = false\n\treturn wasActive\n}\n\nfunc NewMockClock() *MockClock {\n\treturn &MockClock{now: time.Unix(0, 0), advanceBySem: make(chan struct{}, 1)}\n}\n\n// InstantTimer implements a timer that triggers at a fixed instant in time as opposed to after a\n// fixed duration from the moment of creation/reset.\n//\n// In test environments, when using a Timer which fires after a duration, there is a race between\n// the goroutine moving time forward using `clock.Advanceby` and the goroutine resetting the\n// timer by doing `timer.Reset(desiredInstant.Sub(time.Now()))`. The value of\n// `desiredInstance.sub(time.Now())` is different depending on whether `clock.AdvanceBy` finishes\n// before or after the timer reset.\nfunc (c *MockClock) InstantTimer(when time.Time) *mockInstantTimer {\n\tc.mu.Lock()\n\tdefer c.mu.Unlock()\n\tt := &mockInstantTimer{\n\t\tc:      c,\n\t\twhen:   when,\n\t\tch:     make(chan time.Time, 1),\n\t\tactive: true,\n\t}\n\tc.timers = append(c.timers, t)\n\treturn t\n}\n\n// Since implements autorelay.ClockWithInstantTimer\nfunc (c *MockClock) Since(t time.Time) time.Duration {\n\tc.mu.Lock()\n\tdefer c.mu.Unlock()\n\treturn c.now.Sub(t)\n}\n\nfunc (c *MockClock) Now() time.Time {\n\tc.mu.Lock()\n\tdefer c.mu.Unlock()\n\treturn c.now\n}\n\nfunc (c *MockClock) AdvanceBy(dur time.Duration) {\n\tc.advanceBySem <- struct{}{}\n\tdefer func() { <-c.advanceBySem }()\n\n\tc.mu.Lock()\n\tnow := c.now\n\tendTime := c.now.Add(dur)\n\tc.mu.Unlock()\n\n\t// sort timers by when\n\tif len(c.timers) > 1 {\n\t\tsort.Slice(c.timers, func(i, j int) bool {\n\t\t\tc.timers[i].mu.Lock()\n\t\t\tc.timers[j].mu.Lock()\n\t\t\tdefer c.timers[i].mu.Unlock()\n\t\t\tdefer c.timers[j].mu.Unlock()\n\t\t\treturn c.timers[i].when.Before(c.timers[j].when)\n\t\t})\n\t}\n\n\tfor _, t := range c.timers {\n\t\tt.mu.Lock()\n\t\tif !t.active {\n\t\t\tt.mu.Unlock()\n\t\t\tcontinue\n\t\t}\n\t\tif !t.when.After(now) {\n\t\t\tt.active = false\n\t\t\tt.mu.Unlock()\n\t\t\t// This may block if the channel is full, but that's intended. This way our mock clock never gets too far ahead of consumer.\n\t\t\t// This also prevents us from dropping times because we're advancing too fast.\n\t\t\tt.ch <- now\n\t\t} else if !t.when.After(endTime) {\n\t\t\tnow = t.when\n\t\t\tc.mu.Lock()\n\t\t\tc.now = now\n\t\t\tc.mu.Unlock()\n\n\t\t\tt.active = false\n\t\t\tt.mu.Unlock()\n\t\t\t// This may block if the channel is full, but that's intended. See comment above\n\t\t\tt.ch <- c.now\n\t\t} else {\n\t\t\tt.mu.Unlock()\n\t\t}\n\t}\n\tc.mu.Lock()\n\tc.now = endTime\n\tc.mu.Unlock()\n}\n"
  },
  {
    "path": "core/test/mockclock_test.go",
    "content": "package test\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestMockClock(t *testing.T) {\n\tcl := NewMockClock()\n\tt1 := cl.InstantTimer(cl.Now().Add(2 * time.Second))\n\tt2 := cl.InstantTimer(cl.Now().Add(time.Second))\n\n\t// Advance the clock by 500ms\n\tcl.AdvanceBy(time.Millisecond * 500)\n\n\t// No event\n\tselect {\n\tcase <-t1.Ch():\n\t\tt.Fatal(\"t1 fired early\")\n\tcase <-t2.Ch():\n\t\tt.Fatal(\"t2 fired early\")\n\tdefault:\n\t}\n\n\t// Advance the clock by 500ms\n\tcl.AdvanceBy(time.Millisecond * 500)\n\n\t// t2 fires\n\tselect {\n\tcase <-t1.Ch():\n\t\tt.Fatal(\"t1 fired early\")\n\tcase <-t2.Ch():\n\t}\n\n\t// Advance the clock by 2s\n\tcl.AdvanceBy(time.Second * 2)\n\n\t// t1 fires\n\tselect {\n\tcase <-t1.Ch():\n\tcase <-t2.Ch():\n\t\tt.Fatal(\"t2 fired again\")\n\t}\n}\n"
  },
  {
    "path": "core/test/peer.go",
    "content": "package test\n\nimport (\n\t\"crypto/rand\"\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\n\tmh \"github.com/multiformats/go-multihash\"\n)\n\nfunc RandPeerID() (peer.ID, error) {\n\tbuf := make([]byte, 16)\n\trand.Read(buf)\n\th, _ := mh.Sum(buf, mh.SHA2_256, -1)\n\treturn peer.ID(h), nil\n}\n\nfunc RandPeerIDFatal(t testing.TB) peer.ID {\n\tp, err := RandPeerID()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\treturn p\n}\n"
  },
  {
    "path": "core/transport/transport.go",
    "content": "// Package transport provides the Transport interface, which represents\n// the devices and network protocols used to send and receive data.\npackage transport\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\n// A CapableConn represents a connection that has offers the basic\n// capabilities required by libp2p: stream multiplexing, encryption and\n// peer authentication.\n//\n// These capabilities may be natively provided by the transport, or they\n// may be shimmed via the \"connection upgrade\" process, which converts a\n// \"raw\" network connection into one that supports such capabilities by\n// layering an encryption channel and a stream multiplexer.\n//\n// CapableConn provides accessors for the local and remote multiaddrs used to\n// establish the connection and an accessor for the underlying Transport.\ntype CapableConn interface {\n\tnetwork.MuxedConn\n\tnetwork.ConnSecurity\n\tnetwork.ConnMultiaddrs\n\tnetwork.ConnScoper\n\n\t// Transport returns the transport to which this connection belongs.\n\tTransport() Transport\n}\n\n// Transport represents any device by which you can connect to and accept\n// connections from other peers.\n//\n// The Transport interface allows you to open connections to other peers\n// by dialing them, and also lets you listen for incoming connections.\n//\n// Connections returned by Dial and passed into Listeners are of type\n// CapableConn, which means that they have been upgraded to support\n// stream multiplexing and connection security (encryption and authentication).\n//\n// If a transport implements `io.Closer` (optional), libp2p will call `Close` on\n// shutdown. NOTE: `Dial` and `Listen` may be called after or concurrently with\n// `Close`.\n//\n// In addition to the Transport interface, transports may implement\n// Resolver or SkipResolver interface. When wrapping/embedding a transport, you should\n// ensure that the Resolver/SkipResolver interface is handled correctly.\n//\n// For a conceptual overview, see https://docs.libp2p.io/concepts/transport/\ntype Transport interface {\n\t// Dial dials a remote peer. It should try to reuse local listener\n\t// addresses if possible, but it may choose not to.\n\tDial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (CapableConn, error)\n\n\t// CanDial returns true if this transport knows how to dial the given\n\t// multiaddr.\n\t//\n\t// Returning true does not guarantee that dialing this multiaddr will\n\t// succeed. This function should *only* be used to preemptively filter\n\t// out addresses that we can't dial.\n\tCanDial(addr ma.Multiaddr) bool\n\n\t// Listen listens on the passed multiaddr.\n\tListen(laddr ma.Multiaddr) (Listener, error)\n\n\t// Protocol returns the set of protocols handled by this transport.\n\t//\n\t// See the Network interface for an explanation of how this is used.\n\tProtocols() []int\n\n\t// Proxy returns true if this is a proxy transport.\n\t//\n\t// See the Network interface for an explanation of how this is used.\n\t// TODO: Make this a part of the go-multiaddr protocol instead?\n\tProxy() bool\n}\n\n// Resolver can be optionally implemented by transports that want to resolve or transform the\n// multiaddr.\ntype Resolver interface {\n\tResolve(ctx context.Context, maddr ma.Multiaddr) ([]ma.Multiaddr, error)\n}\n\n// SkipResolver can be optionally implemented by transports that don't want to\n// resolve or transform the multiaddr. Useful for transports that indirectly\n// wrap other transports (e.g. p2p-circuit). This lets the inner transport\n// specify how a multiaddr is resolved later.\ntype SkipResolver interface {\n\tSkipResolve(ctx context.Context, maddr ma.Multiaddr) bool\n}\n\n// Listener is an interface closely resembling the net.Listener interface. The\n// only real difference is that Accept() returns Conn's of the type in this\n// package, and also exposes a Multiaddr method as opposed to a regular Addr\n// method\ntype Listener interface {\n\tAccept() (CapableConn, error)\n\tClose() error\n\tAddr() net.Addr\n\tMultiaddr() ma.Multiaddr\n}\n\n// ErrListenerClosed is returned by Listener.Accept when the listener is gracefully closed.\nvar ErrListenerClosed = errors.New(\"listener closed\")\n\n// TransportNetwork is an inet.Network with methods for managing transports.\ntype TransportNetwork interface {\n\tnetwork.Network\n\n\t// AddTransport adds a transport to this Network.\n\t//\n\t// When dialing, this Network will iterate over the protocols in the\n\t// remote multiaddr and pick the first protocol registered with a proxy\n\t// transport, if any. Otherwise, it'll pick the transport registered to\n\t// handle the last protocol in the multiaddr.\n\t//\n\t// When listening, this Network will iterate over the protocols in the\n\t// local multiaddr and pick the *last* protocol registered with a proxy\n\t// transport, if any. Otherwise, it'll pick the transport registered to\n\t// handle the last protocol in the multiaddr.\n\tAddTransport(t Transport) error\n}\n\n// GatedMaListener is listener that listens for raw(unsecured and non-multiplexed) incoming connections,\n// gates them with a `connmgr.ConnGater`and creates a resource management scope for them.\n// It can be upgraded to a full libp2p transport listener by the Upgrader.\n//\n// Compared to manet.Listener, this listener creates the resource management scope for the accepted connection.\ntype GatedMaListener interface {\n\t// Accept waits for and returns the next connection to the listener.\n\tAccept() (manet.Conn, network.ConnManagementScope, error)\n\n\t// Close closes the listener.\n\t// Any blocked Accept operations will be unblocked and return errors.\n\tClose() error\n\n\t// Multiaddr returns the listener's (local) Multiaddr.\n\tMultiaddr() ma.Multiaddr\n\n\t// Addr returns the net.Listener's network address.\n\tAddr() net.Addr\n}\n\n// Upgrader is a multistream upgrader that can upgrade an underlying connection\n// to a full transport connection (secure and multiplexed).\ntype Upgrader interface {\n\t// UpgradeListener upgrades the passed multiaddr-net listener into a full libp2p-transport listener.\n\t//\n\t// Deprecated: Use UpgradeGatedMaListener(upgrader.GateMaListener(manet.Listener)) instead.\n\tUpgradeListener(Transport, manet.Listener) Listener\n\n\t// GateMaListener creates a GatedMaListener from a manet.Listener. It gates the accepted connection\n\t// and creates a resource scope for it.\n\tGateMaListener(manet.Listener) GatedMaListener\n\n\t// UpgradeGatedMaListener upgrades the passed GatedMaListener into a full libp2p-transport listener.\n\tUpgradeGatedMaListener(Transport, GatedMaListener) Listener\n\n\t// Upgrade upgrades the multiaddr/net connection into a full libp2p-transport connection.\n\tUpgrade(ctx context.Context, t Transport, maconn manet.Conn, dir network.Direction, p peer.ID, scope network.ConnManagementScope) (CapableConn, error)\n}\n\n// DialUpdater provides updates on in progress dials.\ntype DialUpdater interface {\n\t// DialWithUpdates dials a remote peer and provides updates on the passed channel.\n\tDialWithUpdates(context.Context, ma.Multiaddr, peer.ID, chan<- DialUpdate) (CapableConn, error)\n}\n\n// DialUpdateKind indicates the type of DialUpdate event.\ntype DialUpdateKind int\n\nconst (\n\t// UpdateKindDialFailed indicates dial failed.\n\tUpdateKindDialFailed DialUpdateKind = iota\n\t// UpdateKindDialSuccessful indicates dial succeeded.\n\tUpdateKindDialSuccessful\n\t// UpdateKindHandshakeProgressed indicates successful completion of the TCP 3-way\n\t// handshake\n\tUpdateKindHandshakeProgressed\n)\n\nfunc (k DialUpdateKind) String() string {\n\tswitch k {\n\tcase UpdateKindDialFailed:\n\t\treturn \"DialFailed\"\n\tcase UpdateKindDialSuccessful:\n\t\treturn \"DialSuccessful\"\n\tcase UpdateKindHandshakeProgressed:\n\t\treturn \"UpdateKindHandshakeProgressed\"\n\tdefault:\n\t\treturn fmt.Sprintf(\"DialUpdateKind<Unknown-%d>\", k)\n\t}\n}\n\n// DialUpdate is used by DialUpdater to provide dial updates.\ntype DialUpdate struct {\n\t// Kind is the kind of update event.\n\tKind DialUpdateKind\n\t// Addr is the peer's address.\n\tAddr ma.Multiaddr\n\t// Conn is the resulting connection on success.\n\tConn CapableConn\n\t// Err is the reason for dial failure.\n\tErr error\n}\n"
  },
  {
    "path": "dashboards/README.md",
    "content": "# Grafana Dashboards\n\nThis directory contains prebuilt dashboards (provided as JSON files) for various components.\nFor steps on how to import and use them [please read the official Grafana documentation.](https://grafana.com/docs/grafana/latest/dashboards/export-import/#import-dashboard)\n\n## Public dashboards\n\nYou can check the following prebuilt dashboards in action:\n\n1. [AutoNAT](https://protocollabs.grafana.net/public-dashboards/fce8fdeb629742c89bd70f0ce38dfd97)\n2. [Auto Relay](https://protocollabs.grafana.net/public-dashboards/380d52aded12404e9cf6ceccb824b7f9)\n3. [Eventbus](https://protocollabs.grafana.net/public-dashboards/048029ac2d7e4a71b281ffea3535026e)\n4. [Identify](https://protocollabs.grafana.net/public-dashboards/96b70328253d47c0b352dfae06f12a1b)\n5. [Relay Service](https://protocollabs.grafana.net/public-dashboards/4a8cb5d245294893874ed65279b049be)\n6. [Swarm](https://protocollabs.grafana.net/public-dashboards/2bd3f1bee9964d40b6786fbe3eafd9fc)\n\nThese metrics come from one of the public IPFS DHT [bootstrap nodes](https://docs.ipfs.tech/concepts/nodes/#bootstrap) run by Protocol Labs.\nAt the time of writing (2023-08), these nodes handle many connections across various libp2p implementations, versions, and configurations (they don't handle large file transfers).\n\n## Using locally\n\nFor local development and debugging, it can be useful to spin up a local Prometheus and Grafana instance.\n\nTo expose metrics, we first need to expose a metrics collection endpoint. Add this to your code:\n\n```go\nimport \"github.com/prometheus/client_golang/prometheus/promhttp\"\n\ngo func() {\n    http.Handle(\"/debug/metrics/prometheus\", promhttp.Handler())\n    log.Fatal(http.ListenAndServe(\":5001\", nil))\n}()\n```\n\nThis exposes a metrics collection endpoint at http://localhost:5001/debug/metrics/prometheus. Note that this is the same endpoint that [Kubo](https://github.com/ipfs/kubo) uses, so if you want to gather metrics from Kubo, you can skip this step.\n\nOn macOS:\n```bash\ndocker compose -f docker-compose.base.yml up\n```\nOn Linux, dashboards can be inspected locally by running:\n```bash\ndocker compose -f docker-compose.base.yml -f docker-compose-linux.yml up\n```\n\nand opening Grafana at http://localhost:3000.\n\n\n### Making Dashboards usable with Provisioning\n\nThe following section is only relevant for creators of dashboards.\n\nDue to a bug in Grafana, it's not possible to provision dashboards shared for external use directly. We need to apply the workaround described in https://github.com/grafana/grafana/issues/10786#issuecomment-568788499 (adding a few lines in the dashboard JSON file).\n"
  },
  {
    "path": "dashboards/autonat/autonat.json",
    "content": "{\n  \"__inputs\": [\n    {\n      \"name\": \"DS_PROMETHEUS\",\n      \"label\": \"Prometheus\",\n      \"description\": \"\",\n      \"type\": \"datasource\",\n      \"pluginId\": \"prometheus\",\n      \"pluginName\": \"Prometheus\"\n    }\n  ],\n  \"__elements\": {},\n  \"__requires\": [\n    {\n      \"type\": \"panel\",\n      \"id\": \"gauge\",\n      \"name\": \"Gauge\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"grafana\",\n      \"id\": \"grafana\",\n      \"name\": \"Grafana\",\n      \"version\": \"9.3.6\"\n    },\n    {\n      \"type\": \"datasource\",\n      \"id\": \"prometheus\",\n      \"name\": \"Prometheus\",\n      \"version\": \"1.0.0\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"stat\",\n      \"name\": \"Stat\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"state-timeline\",\n      \"name\": \"State timeline\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"timeseries\",\n      \"name\": \"Time series\",\n      \"version\": \"\"\n    }\n  ],\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": {\n          \"type\": \"grafana\",\n          \"uid\": \"-- Grafana --\"\n        },\n        \"enable\": true,\n        \"hide\": true,\n        \"iconColor\": \"rgba(0, 211, 255, 1)\",\n        \"name\": \"Annotations & Alerts\",\n        \"target\": {\n          \"limit\": 100,\n          \"matchAny\": false,\n          \"tags\": [],\n          \"type\": \"dashboard\"\n        },\n        \"type\": \"dashboard\"\n      }\n    ]\n  },\n  \"editable\": true,\n  \"fiscalYearStartMonth\": 0,\n  \"graphTooltip\": 0,\n  \"id\": null,\n  \"links\": [],\n  \"liveNow\": false,\n  \"panels\": [\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"description\": \"\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"continuous-GrYlRd\"\n          },\n          \"custom\": {\n            \"fillOpacity\": 70,\n            \"lineWidth\": 0,\n            \"spanNulls\": true\n          },\n          \"mappings\": [\n            {\n              \"options\": {\n                \"0\": {\n                  \"color\": \"yellow\",\n                  \"index\": 0,\n                  \"text\": \"Unknown\"\n                },\n                \"1\": {\n                  \"color\": \"green\",\n                  \"index\": 1,\n                  \"text\": \"Public\"\n                },\n                \"2\": {\n                  \"color\": \"purple\",\n                  \"index\": 2,\n                  \"text\": \"Private\"\n                }\n              },\n              \"type\": \"value\"\n            }\n          ],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 18,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 2,\n      \"options\": {\n        \"alignValue\": \"center\",\n        \"legend\": {\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"mergeValues\": true,\n        \"rowHeight\": 0.9,\n        \"showValue\": \"auto\",\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"builder\",\n          \"expr\": \"libp2p_autonat_reachability_status{instance=~\\\"$instance\\\"}\",\n          \"legendFormat\": \" \",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Reachability Status\",\n      \"type\": \"state-timeline\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"mappings\": [],\n          \"max\": 3,\n          \"min\": 0,\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"none\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 6,\n        \"x\": 18,\n        \"y\": 0\n      },\n      \"id\": 10,\n      \"options\": {\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showThresholdLabels\": false,\n        \"showThresholdMarkers\": true\n      },\n      \"pluginVersion\": \"9.3.6\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"builder\",\n          \"expr\": \"libp2p_autonat_reachability_status_confidence{instance=~\\\"$instance\\\"}\",\n          \"legendFormat\": \"__auto\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Reachability status confidence\",\n      \"type\": \"gauge\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"fixedColor\": \"green\",\n            \"mode\": \"fixed\"\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              }\n            ]\n          },\n          \"unit\": \"dateTimeFromNow\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 9,\n        \"w\": 3,\n        \"x\": 0,\n        \"y\": 7\n      },\n      \"id\": 12,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"area\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"/^Value$/\",\n          \"values\": false\n        },\n        \"textMode\": \"auto\"\n      },\n      \"pluginVersion\": \"9.3.6\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"exemplar\": false,\n          \"expr\": \"libp2p_autonat_next_probe_timestamp{instance=~\\\"$instance\\\"} * 1000\",\n          \"format\": \"table\",\n          \"instant\": true,\n          \"legendFormat\": \"__auto\",\n          \"range\": false,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Next Probe Time\",\n      \"type\": \"stat\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"dial error\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"purple\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"dial refused\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"yellow\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"ok\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"green\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 9,\n        \"w\": 21,\n        \"x\": 3,\n        \"y\": 7\n      },\n      \"id\": 4,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"exemplar\": false,\n          \"expr\": \"increase(libp2p_autonat_received_dial_response_total{instance=~\\\"$instance\\\"}[$__rate_interval])\",\n          \"instant\": false,\n          \"legendFormat\": \"{{response_status}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Received Dial Responses\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"description\": \"Responses sent to peers that are asking us to dial them\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"dial refused\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"yellow\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"dial error\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"red\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"ok\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"green\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 9,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 16\n      },\n      \"id\": 6,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"increase(libp2p_autonat_outgoing_dial_response_total{instance=~\\\"$instance\\\"}[$__rate_interval])\",\n          \"legendFormat\": \"{{response_status}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Outgoing Dial Responses\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"description\": \"There are multiple reasons why we'd refuse a dial-back request from a remote node:\\n* rate limiting\\n* no valid addresses\\n* dial blocked by policy\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"rate limited\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"red\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 9,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 16\n      },\n      \"id\": 14,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"increase(libp2p_autonat_outgoing_dial_refused_total{instance=~\\\"$instance\\\"}[$__rate_interval])\",\n          \"legendFormat\": \"{{refusal_reason}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Outgoing Dial Refused\",\n      \"type\": \"timeseries\"\n    }\n  ],\n  \"schemaVersion\": 37,\n  \"style\": \"dark\",\n  \"tags\": [],\n  \"templating\": {\n    \"list\": [\n       {\n        \"hide\": 0,\n        \"label\": \"datasource\",\n        \"name\": \"DS_PROMETHEUS\",\n        \"options\": [],\n        \"query\": \"prometheus\",\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"type\": \"datasource\"\n      },\n      {\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${DS_PROMETHEUS}\"\n        },\n        \"definition\": \"label_values(up, instance)\",\n        \"hide\": 0,\n        \"includeAll\": true,\n        \"multi\": true,\n        \"name\": \"instance\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(up, instance)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"type\": \"query\"\n      }\n    ]\n  },\n  \"time\": {\n    \"from\": \"now-6h\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {},\n  \"timezone\": \"\",\n  \"title\": \"go-libp2p autonat\",\n  \"uid\": \"YNWSyiJ4k\",\n  \"version\": 5,\n  \"weekStart\": \"\"\n}\n"
  },
  {
    "path": "dashboards/autonatv2/autonatv2.json",
    "content": "{\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": {\n          \"type\": \"grafana\",\n          \"uid\": \"-- Grafana --\"\n        },\n        \"enable\": true,\n        \"hide\": true,\n        \"iconColor\": \"rgba(0, 211, 255, 1)\",\n        \"name\": \"Annotations & Alerts\",\n        \"target\": {\n          \"limit\": 100,\n          \"matchAny\": false,\n          \"tags\": [],\n          \"type\": \"dashboard\"\n        },\n        \"type\": \"dashboard\"\n      }\n    ]\n  },\n  \"editable\": true,\n  \"fiscalYearStartMonth\": 0,\n  \"graphTooltip\": 0,\n  \"id\": 5,\n  \"links\": [],\n  \"panels\": [\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${data_source}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisBorderShow\": false,\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"barWidthFactor\": 0.6,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"insertNulls\": false,\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\"\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 6,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"hideZeros\": false,\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"pluginVersion\": \"11.6.1\",\n      \"targets\": [\n        {\n          \"editorMode\": \"code\",\n          \"expr\": \"sum (increase(libp2p_autonatv2_client_requests_completed_total{instance=~\\\"$instance\\\",dial_refused=\\\"false\\\"}[5m])) by (instance, ip_or_dns_version, transport, reachability)\",\n          \"legendFormat\": \"{{instance}} {{ip_or_dns_version}} {{transport}} {{reachability}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Requests by Reachability\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${data_source}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisBorderShow\": false,\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"barWidthFactor\": 0.6,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"insertNulls\": false,\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\"\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 0\n      },\n      \"id\": 5,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"hideZeros\": false,\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"pluginVersion\": \"11.6.1\",\n      \"targets\": [\n        {\n          \"editorMode\": \"code\",\n          \"expr\": \"increase(libp2p_autonatv2_client_requests_total{instance=~\\\"$instance\\\"}[$__rate_interval])\",\n          \"legendFormat\": \"{{instance}} {{outcome}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${data_source}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"sum (increase(libp2p_autonatv2_client_requests_completed_total{instance=~\\\"$instance\\\", dial_refused=\\\"true\\\"}[$__rate_interval])) by (instance)\",\n          \"hide\": false,\n          \"instant\": false,\n          \"legendFormat\": \"{{instance}} refused\",\n          \"range\": true,\n          \"refId\": \"B\"\n        }\n      ],\n      \"title\": \"All Requests\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 8\n      },\n      \"id\": 4,\n      \"panels\": [],\n      \"title\": \"Server\",\n      \"type\": \"row\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${data_source}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\",\n            \"seriesBy\": \"last\"\n          },\n          \"custom\": {\n            \"axisBorderShow\": false,\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"barWidthFactor\": 0.6,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"insertNulls\": false,\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\"\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"OK\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"green\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"E_DIAL_REFUSED\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"purple\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 9\n      },\n      \"id\": 1,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"hideZeros\": false,\n          \"maxHeight\": 600,\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"pluginVersion\": \"11.6.1\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${data_source}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"sum by (response_status) (increase(libp2p_autonatv2_requests_completed_total{instance=~\\\"$instance\\\", server_error=\\\"nil\\\"}[$__rate_interval]))\\n\",\n          \"instant\": false,\n          \"legendFormat\": \"__auto\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Dial Request by Response Status\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${data_source}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\",\n            \"seriesBy\": \"last\"\n          },\n          \"custom\": {\n            \"axisBorderShow\": false,\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"barWidthFactor\": 0.6,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"insertNulls\": false,\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\"\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"OK\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"green\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"E_DIAL_REFUSED\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"purple\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"E_DIAL_ERROR\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"purple\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 9\n      },\n      \"id\": 2,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"hideZeros\": false,\n          \"maxHeight\": 600,\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"pluginVersion\": \"11.6.1\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${data_source}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"sum by (ip_or_dns_version, transport, dial_status) (increase(libp2p_autonatv2_requests_completed_total{instance=~\\\"$instance\\\", server_error=\\\"nil\\\", response_status=\\\"OK\\\"}[$__rate_interval]))\\n\",\n          \"instant\": false,\n          \"legendFormat\": \"__auto\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Dial Request by Dial Status\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${data_source}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\",\n            \"seriesBy\": \"last\"\n          },\n          \"custom\": {\n            \"axisBorderShow\": false,\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"barWidthFactor\": 0.6,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"insertNulls\": false,\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\"\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"OK\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"green\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"E_DIAL_REFUSED\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"purple\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"E_DIAL_ERROR\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"purple\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 5,\n        \"y\": 17\n      },\n      \"id\": 3,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"hideZeros\": false,\n          \"maxHeight\": 600,\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"pluginVersion\": \"11.6.1\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${data_source}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"sum by (server_error) (increase(libp2p_autonatv2_requests_completed_total{instance=~\\\"$instance\\\", server_error!=\\\"nil\\\"}[$__rate_interval]))\\n\",\n          \"instant\": false,\n          \"legendFormat\": \"__auto\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Dial Request Errors\",\n      \"type\": \"timeseries\"\n    }\n  ],\n  \"preload\": false,\n  \"refresh\": \"\",\n  \"schemaVersion\": 41,\n  \"tags\": [],\n  \"templating\": {\n    \"list\": [\n      {\n        \"current\": {\n          \"text\": \"All\",\n          \"value\": [\n            \"$__all\"\n          ]\n        },\n        \"definition\": \"label_values(up,instance)\",\n        \"includeAll\": true,\n        \"label\": \"instance\",\n        \"multi\": true,\n        \"name\": \"instance\",\n        \"options\": [],\n        \"query\": {\n          \"qryType\": 1,\n          \"query\": \"label_values(up,instance)\",\n          \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"type\": \"query\"\n      },\n      {\n        \"current\": {\n          \"text\": \"prometheus\",\n          \"value\": \"${data_source}\"\n        },\n        \"includeAll\": false,\n        \"name\": \"data_source\",\n        \"options\": [],\n        \"query\": \"prometheus\",\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"type\": \"datasource\"\n      }\n    ]\n  },\n  \"time\": {\n    \"from\": \"now-1h\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {},\n  \"timezone\": \"browser\",\n  \"title\": \"go-libp2p autoNATv2\",\n  \"uid\": \"cdpusyp3xtfcwa\",\n  \"version\": 9\n}"
  },
  {
    "path": "dashboards/autorelay/autorelay.json",
    "content": "{\n  \"__inputs\": [\n    {\n      \"name\": \"DS_PROMETHEUS\",\n      \"label\": \"Prometheus\",\n      \"description\": \"\",\n      \"type\": \"datasource\",\n      \"pluginId\": \"prometheus\",\n      \"pluginName\": \"Prometheus\"\n    }\n  ],\n  \"__elements\": {},\n  \"__requires\": [\n    {\n      \"type\": \"panel\",\n      \"id\": \"gauge\",\n      \"name\": \"Gauge\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"grafana\",\n      \"id\": \"grafana\",\n      \"name\": \"Grafana\",\n      \"version\": \"9.4.7\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"piechart\",\n      \"name\": \"Pie chart\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"datasource\",\n      \"id\": \"prometheus\",\n      \"name\": \"Prometheus\",\n      \"version\": \"1.0.0\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"stat\",\n      \"name\": \"Stat\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"state-timeline\",\n      \"name\": \"State timeline\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"timeseries\",\n      \"name\": \"Time series\",\n      \"version\": \"\"\n    }\n  ],\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": {\n          \"type\": \"grafana\",\n          \"uid\": \"-- Grafana --\"\n        },\n        \"enable\": true,\n        \"hide\": true,\n        \"iconColor\": \"rgba(0, 211, 255, 1)\",\n        \"name\": \"Annotations & Alerts\",\n        \"target\": {\n          \"limit\": 100,\n          \"matchAny\": false,\n          \"tags\": [],\n          \"type\": \"dashboard\"\n        },\n        \"type\": \"dashboard\"\n      }\n    ]\n  },\n  \"editable\": true,\n  \"fiscalYearStartMonth\": 0,\n  \"graphTooltip\": 0,\n  \"id\": null,\n  \"links\": [],\n  \"liveNow\": false,\n  \"panels\": [\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 16,\n      \"panels\": [],\n      \"title\": \"Status\",\n      \"type\": \"row\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"continuous-GrYlRd\"\n          },\n          \"custom\": {\n            \"fillOpacity\": 70,\n            \"lineWidth\": 0,\n            \"spanNulls\": true\n          },\n          \"mappings\": [\n            {\n              \"options\": {\n                \"0\": {\n                  \"color\": \"purple\",\n                  \"index\": 1,\n                  \"text\": \"No\"\n                },\n                \"1\": {\n                  \"color\": \"green\",\n                  \"index\": 0,\n                  \"text\": \"Yes\"\n                }\n              },\n              \"type\": \"value\"\n            }\n          ],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 5,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 1\n      },\n      \"id\": 2,\n      \"options\": {\n        \"alignValue\": \"center\",\n        \"legend\": {\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"mergeValues\": true,\n        \"rowHeight\": 0.9,\n        \"showValue\": \"auto\",\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"libp2p_autorelay_status{instance=~\\\"$instance\\\"}\",\n          \"legendFormat\": \"active\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Relay Finder Status\",\n      \"type\": \"state-timeline\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 6\n      },\n      \"id\": 18,\n      \"panels\": [],\n      \"title\": \"Reservations\",\n      \"type\": \"row\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"description\": \"\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"mappings\": [],\n          \"min\": 0,\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              }\n            ]\n          },\n          \"unit\": \"none\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 6,\n        \"x\": 0,\n        \"y\": 7\n      },\n      \"id\": 4,\n      \"options\": {\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showThresholdLabels\": false,\n        \"showThresholdMarkers\": true\n      },\n      \"pluginVersion\": \"9.4.7\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"exemplar\": false,\n          \"expr\": \"libp2p_autorelay_reservations_opened_total{instance=~\\\"$instance\\\"} -  libp2p_autorelay_reservations_closed_total{instance=~\\\"$instance\\\"}\",\n          \"instant\": true,\n          \"legendFormat\": \"current reservations\",\n          \"range\": false,\n          \"refId\": \"A\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"exemplar\": false,\n          \"expr\": \"libp2p_autorelay_desired_reservations{instance=~\\\"$instance\\\"}\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"instant\": true,\n          \"legendFormat\": \"desired\",\n          \"range\": false,\n          \"refId\": \"config-query\"\n        }\n      ],\n      \"title\": \"Current Reservations\",\n      \"transformations\": [\n        {\n          \"id\": \"configFromData\",\n          \"options\": {\n            \"applyTo\": {\n              \"id\": \"byFrameRefID\",\n              \"options\": \"A\"\n            },\n            \"configRefId\": \"config-query\",\n            \"mappings\": [\n              {\n                \"fieldName\": \"desired\",\n                \"handlerKey\": \"max\"\n              },\n              {\n                \"fieldName\": \"Time\",\n                \"handlerKey\": \"__ignore\"\n              }\n            ]\n          }\n        }\n      ],\n      \"type\": \"gauge\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              }\n            ]\n          }\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"new: success\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"green\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"refresh: success\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"super-light-green\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 18,\n        \"x\": 6,\n        \"y\": 7\n      },\n      \"id\": 10,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"exemplar\": false,\n          \"expr\": \"increase(libp2p_autorelay_reservation_requests_outcome_total{instance=~\\\"$instance\\\"}[$__rate_interval])\",\n          \"format\": \"time_series\",\n          \"instant\": false,\n          \"interval\": \"\",\n          \"legendFormat\": \"{{request_type}}: {{outcome}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Reservation Requests Outcome\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 15\n      },\n      \"id\": 20,\n      \"panels\": [],\n      \"title\": \"Candidates\",\n      \"type\": \"row\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"fixed\"\n          },\n          \"custom\": {\n            \"fillOpacity\": 70,\n            \"lineWidth\": 0,\n            \"spanNulls\": false\n          },\n          \"mappings\": [\n            {\n              \"options\": {\n                \"0\": {\n                  \"color\": \"purple\",\n                  \"index\": 0,\n                  \"text\": \"peer source rate limited\"\n                },\n                \"1\": {\n                  \"color\": \"blue\",\n                  \"index\": 1,\n                  \"text\": \"waiting on peer chan\"\n                },\n                \"2\": {\n                  \"color\": \"green\",\n                  \"index\": 2,\n                  \"text\": \"waiting for trigger\"\n                },\n                \"3\": {\n                  \"color\": \"light-yellow\",\n                  \"index\": 3,\n                  \"text\": \"stopped\"\n                }\n              },\n              \"type\": \"value\"\n            }\n          ],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 6,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 16\n      },\n      \"id\": 28,\n      \"options\": {\n        \"alignValue\": \"center\",\n        \"legend\": {\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"mergeValues\": true,\n        \"rowHeight\": 0.9,\n        \"showValue\": \"auto\",\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"exemplar\": true,\n          \"expr\": \"libp2p_autorelay_candidate_loop_state{instance=~\\\"$instance\\\"}\",\n          \"instant\": false,\n          \"legendFormat\": \"state\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Candidate Loop State\",\n      \"type\": \"state-timeline\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"fixedColor\": \"blue\",\n            \"mode\": \"fixed\"\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 5,\n        \"x\": 1,\n        \"y\": 22\n      },\n      \"id\": 6,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"area\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"textMode\": \"auto\"\n      },\n      \"pluginVersion\": \"9.4.7\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"libp2p_autorelay_candidates_total{type=\\\"added\\\",instance=~\\\"$instance\\\"} - ignoring(type) libp2p_autorelay_candidates_total{type=\\\"removed\\\",instance=~\\\"$instance\\\"}\",\n          \"legendFormat\": \"num candidates\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Current Candidates\",\n      \"type\": \"stat\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            }\n          },\n          \"mappings\": []\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"yes\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"blue\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"no\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"purple\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 5,\n        \"x\": 6,\n        \"y\": 22\n      },\n      \"id\": 12,\n      \"options\": {\n        \"legend\": {\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"pieType\": \"pie\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"increase(libp2p_autorelay_candidates_circuit_v2_support_total{instance=~\\\"$instance\\\"}[$__range])\",\n          \"legendFormat\": \"{{support}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Candidates Circuit V2 Support\",\n      \"type\": \"piechart\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"fixedColor\": \"blue\",\n            \"mode\": \"fixed\"\n          },\n          \"mappings\": [\n            {\n              \"options\": {\n                \"from\": -9223372036854776000,\n                \"result\": {\n                  \"index\": 0,\n                  \"text\": \"-\"\n                },\n                \"to\": -86400\n              },\n              \"type\": \"range\"\n            }\n          ],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              }\n            ]\n          },\n          \"unit\": \"dateTimeFromNow\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 6,\n        \"x\": 11,\n        \"y\": 22\n      },\n      \"id\": 26,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"none\",\n        \"justifyMode\": \"center\",\n        \"orientation\": \"horizontal\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"textMode\": \"auto\"\n      },\n      \"pluginVersion\": \"9.4.7\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"exemplar\": false,\n          \"expr\": \"libp2p_autorelay_scheduled_work_time{work_type=\\\"old candidate check\\\",instance=~\\\"$instance\\\"} * 1000\\n\",\n          \"instant\": true,\n          \"legendFormat\": \"__auto\",\n          \"range\": false,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Next Old Candidate Check\",\n      \"type\": \"stat\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"fixedColor\": \"blue\",\n            \"mode\": \"fixed\"\n          },\n          \"mappings\": [\n            {\n              \"options\": {\n                \"from\": -86399,\n                \"result\": {\n                  \"index\": 0,\n                  \"text\": \"immediately\"\n                },\n                \"to\": 0\n              },\n              \"type\": \"range\"\n            },\n            {\n              \"options\": {\n                \"from\": -9223372036854776000,\n                \"result\": {\n                  \"index\": 1,\n                  \"text\": \"-\"\n                },\n                \"to\": -86400\n              },\n              \"type\": \"range\"\n            }\n          ],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              }\n            ]\n          },\n          \"unit\": \"s\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 6,\n        \"x\": 17,\n        \"y\": 22\n      },\n      \"id\": 30,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"area\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"textMode\": \"auto\"\n      },\n      \"pluginVersion\": \"9.4.7\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"exemplar\": false,\n          \"expr\": \"round(libp2p_autorelay_scheduled_work_time{work_type=\\\"allowed peer source call\\\",instance=~\\\"$instance\\\"} - time()) \",\n          \"instant\": true,\n          \"legendFormat\": \"__auto\",\n          \"range\": false,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Next Allowed Call to peer source\",\n      \"type\": \"stat\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 30\n      },\n      \"id\": 22,\n      \"panels\": [],\n      \"title\": \"Relay Addresses\",\n      \"type\": \"row\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"fixedColor\": \"yellow\",\n            \"mode\": \"fixed\"\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 6,\n        \"x\": 0,\n        \"y\": 31\n      },\n      \"id\": 8,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"area\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"textMode\": \"auto\"\n      },\n      \"pluginVersion\": \"9.4.7\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"libp2p_autorelay_relay_addresses_count{instance=~\\\"$instance\\\"}\",\n          \"legendFormat\": \"num addresses\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Relay Addresses\",\n      \"type\": \"stat\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"update triggered\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"yellow\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 18,\n        \"x\": 6,\n        \"y\": 31\n      },\n      \"id\": 14,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"increase(libp2p_autorelay_relay_addresses_updated_total{instance=~\\\"$instance\\\"}[$__rate_interval])\",\n          \"legendFormat\": \"update triggered\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Relay Addresses Updated\",\n      \"type\": \"timeseries\"\n    }\n  ],\n  \"refresh\": \"\",\n  \"revision\": 1,\n  \"schemaVersion\": 38,\n  \"style\": \"dark\",\n  \"tags\": [],\n  \"templating\": {\n    \"list\": [\n       {\n        \"hide\": 0,\n        \"label\": \"datasource\",\n        \"name\": \"DS_PROMETHEUS\",\n        \"options\": [],\n        \"query\": \"prometheus\",\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"type\": \"datasource\"\n      },\n      {\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${DS_PROMETHEUS}\"\n        },\n        \"definition\": \"label_values(up, instance)\",\n        \"hide\": 0,\n        \"includeAll\": true,\n        \"multi\": true,\n        \"name\": \"instance\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(up, instance)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"type\": \"query\"\n      }\n    ]\n  },\n  \"time\": {\n    \"from\": \"now-1h\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {},\n  \"timezone\": \"\",\n  \"title\": \"libp2p Autorelay\",\n  \"uid\": \"deQ_uf-4k\",\n  \"version\": 6,\n  \"weekStart\": \"\"\n}\n"
  },
  {
    "path": "dashboards/dashboard.yml",
    "content": "apiVersion: 1\n\nproviders:\n  - name: \"libp2p dashboard provider\"\n    orgId: 1\n    type: file\n    disableDeletion: false\n    updateIntervalSeconds: 10\n    allowUiUpdates: false\n    options:\n      path: /var/lib/grafana/dashboards\n      foldersFromFilesStructure: true\n"
  },
  {
    "path": "dashboards/datasources.yml",
    "content": "apiVersion: 1\n\ndeleteDatasources:\n  - name: Prometheus\n    orgId: 1\n\ndatasources:\n  - name: Prometheus\n    orgId: 1\n    type: prometheus\n    access: proxy\n    url: http://prometheus:9090\n    editable: false\n"
  },
  {
    "path": "dashboards/docker-compose-linux.yml",
    "content": "version: \"3.7\"\nservices:\n  prometheus:\n    network_mode: \"host\"\n    extra_hosts:\n      # define a host.docker.internal alias, so we can use the same prometheus.yml on Linux and macOS\n      - \"host.docker.internal:127.0.0.1\"\n  grafana:\n    network_mode: \"host\"\n    extra_hosts:\n      # define a prometheus alias, so we can use the same datasources.yml on Linux and macOS\n      - \"prometheus:127.0.0.1\"\n"
  },
  {
    "path": "dashboards/docker-compose.base.yml",
    "content": "version: \"3.7\"\nservices:\n  prometheus:\n    image: prom/prometheus:latest\n    ports:\n      - \"9090:9090\"\n    volumes:\n      - ./prometheus.yml:/etc/prometheus/prometheus.yml\n  grafana:\n    image: grafana/grafana:latest\n    depends_on:\n      - prometheus\n    ports:\n      - \"3000:3000\"\n    environment:\n      - GF_AUTH_DISABLE_LOGIN_FORM=true\n      - GF_AUTH_ANONYMOUS_ENABLED=true\n      - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin\n    volumes:\n      - ./dashboard.yml:/etc/grafana/provisioning/dashboards/main.yml\n      - ./datasources.yml:/etc/grafana/provisioning/datasources/prom.yml\n      - ./autonat/autonat.json:/var/lib/grafana/dashboards/autonat.json\n      - ./autorelay/autorelay.json:/var/lib/grafana/dashboards/autorelay.json\n      - ./eventbus/eventbus.json:/var/lib/grafana/dashboards/eventbus.json\n      - ./holepunch/holepunch.json:/var/lib/grafana/dashboards/holepunch.json\n      - ./identify/identify.json:/var/lib/grafana/dashboards/identify.json\n      - ./relaysvc/relaysvc.json:/var/lib/grafana/dashboards/relaysvc.json\n      - ./swarm/swarm.json:/var/lib/grafana/dashboards/swarm.json\n      - ./resource-manager/resource-manager.json:/var/lib/grafana/dashboards/resource-manager.json\n"
  },
  {
    "path": "dashboards/eventbus/eventbus.json",
    "content": "{\n  \"__inputs\": [\n    {\n      \"name\": \"DS_PROMETHEUS\",\n      \"label\": \"Prometheus\",\n      \"description\": \"\",\n      \"type\": \"datasource\",\n      \"pluginId\": \"prometheus\",\n      \"pluginName\": \"Prometheus\"\n    }\n  ],\n  \"__elements\": {},\n  \"__requires\": [\n    {\n      \"type\": \"panel\",\n      \"id\": \"gauge\",\n      \"name\": \"Gauge\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"grafana\",\n      \"id\": \"grafana\",\n      \"name\": \"Grafana\",\n      \"version\": \"9.3.6\"\n    },\n    {\n      \"type\": \"datasource\",\n      \"id\": \"prometheus\",\n      \"name\": \"Prometheus\",\n      \"version\": \"1.0.0\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"state-timeline\",\n      \"name\": \"State timeline\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"timeseries\",\n      \"name\": \"Time series\",\n      \"version\": \"\"\n    }\n  ],\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": {\n          \"type\": \"grafana\",\n          \"uid\": \"-- Grafana --\"\n        },\n        \"enable\": true,\n        \"hide\": true,\n        \"iconColor\": \"rgba(0, 211, 255, 1)\",\n        \"name\": \"Annotations & Alerts\",\n        \"target\": {\n          \"limit\": 100,\n          \"matchAny\": false,\n          \"tags\": [],\n          \"type\": \"dashboard\"\n        },\n        \"type\": \"dashboard\"\n      }\n    ]\n  },\n  \"editable\": true,\n  \"fiscalYearStartMonth\": 0,\n  \"graphTooltip\": 0,\n  \"id\": null,\n  \"links\": [],\n  \"liveNow\": false,\n  \"panels\": [\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 9,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 2,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"exemplar\": false,\n          \"expr\": \"rate(libp2p_eventbus_events_emitted_total{instance=~\\\"$instance\\\"}[$__rate_interval])\",\n          \"format\": \"time_series\",\n          \"instant\": false,\n          \"interval\": \"\",\n          \"legendFormat\": \"{{event}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Events Types emitted\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"mappings\": [],\n          \"min\": 0,\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 6,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 9\n      },\n      \"id\": 6,\n      \"options\": {\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showThresholdLabels\": false,\n        \"showThresholdMarkers\": true,\n        \"text\": {\n          \"titleSize\": 14\n        }\n      },\n      \"pluginVersion\": \"9.3.6\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"builder\",\n          \"expr\": \"libp2p_eventbus_subscribers_total{instance=~\\\"$instance\\\"}\",\n          \"legendFormat\": \"{{event}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Event Subscribers\",\n      \"type\": \"gauge\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 15\n      },\n      \"id\": 12,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"builder\",\n          \"expr\": \"rate(libp2p_eventbus_subscriber_event_queued{instance=~\\\"$instance\\\"}[$__rate_interval])\",\n          \"legendFormat\": \"{{subscriber_name}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Events emitted By Subscriber\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"description\": \"Event Subscribers need to consume events quickly enough, otherwise they risk stalling the libp2p process.\\nSubscribers use a buffered channel to catch temporary bursts. A queue length that doesn't return to 0 might be indicative of a problem.\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 15\n      },\n      \"id\": 8,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"builder\",\n          \"expr\": \"libp2p_eventbus_subscriber_queue_length{instance=~\\\"$instance\\\"}\",\n          \"hide\": false,\n          \"legendFormat\": \"{{subscriber_name}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Subscriber Queue Length\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"description\": \"When the subscriber event queue fills up, it blocks the libp2p process. This can be mitigated by\\n1. consuming events quickly enough on the subscriber side\\n2. using a large enough buffer to absorb bursts\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"continuous-GrYlRd\"\n          },\n          \"custom\": {\n            \"fillOpacity\": 75,\n            \"lineWidth\": 0,\n            \"spanNulls\": 60000\n          },\n          \"mappings\": [\n            {\n              \"options\": {\n                \"0\": {\n                  \"color\": \"green\",\n                  \"index\": 0,\n                  \"text\": \"OK\"\n                },\n                \"1\": {\n                  \"color\": \"red\",\n                  \"index\": 1,\n                  \"text\": \"FULL\"\n                }\n              },\n              \"type\": \"value\"\n            }\n          ],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"#73BF69\",\n                \"value\": null\n              }\n            ]\n          },\n          \"unit\": \"string\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 11,\n        \"w\": 23,\n        \"x\": 0,\n        \"y\": 23\n      },\n      \"id\": 10,\n      \"options\": {\n        \"alignValue\": \"center\",\n        \"legend\": {\n          \"displayMode\": \"list\",\n          \"placement\": \"right\",\n          \"showLegend\": false\n        },\n        \"mergeValues\": true,\n        \"rowHeight\": 0.94,\n        \"showValue\": \"always\",\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"builder\",\n          \"exemplar\": false,\n          \"expr\": \"libp2p_eventbus_subscriber_queue_full{instance=~\\\"$instance\\\"}\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"instant\": false,\n          \"legendFormat\": \"{{subscriber_name}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Subscriber Queue Full\",\n      \"type\": \"state-timeline\"\n    }\n  ],\n  \"refresh\": false,\n  \"schemaVersion\": 37,\n  \"style\": \"dark\",\n  \"tags\": [],\n  \"templating\": {\n    \"list\": [\n       {\n        \"hide\": 0,\n        \"label\": \"datasource\",\n        \"name\": \"DS_PROMETHEUS\",\n        \"options\": [],\n        \"query\": \"prometheus\",\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"type\": \"datasource\"\n      },\n      {\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${DS_PROMETHEUS}\"\n        },\n        \"definition\": \"label_values(up, instance)\",\n        \"hide\": 0,\n        \"includeAll\": true,\n        \"multi\": true,\n        \"name\": \"instance\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(up, instance)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"type\": \"query\"\n      }\n    ]\n  },\n  \"time\": {\n    \"from\": \"now-15m\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {},\n  \"timezone\": \"\",\n  \"title\": \"go-libp2p EventBus\",\n  \"uid\": \"ZFbI6NAVn\",\n  \"version\": 4,\n  \"weekStart\": \"\"\n}\n"
  },
  {
    "path": "dashboards/holepunch/holepunch.json",
    "content": "{\n  \"__inputs\": [\n    {\n      \"name\": \"DS_PROMETHEUS\",\n      \"label\": \"Prometheus\",\n      \"description\": \"\",\n      \"type\": \"datasource\",\n      \"pluginId\": \"prometheus\",\n      \"pluginName\": \"Prometheus\"\n    }\n  ],\n  \"__elements\": {},\n  \"__requires\": [\n    {\n      \"type\": \"grafana\",\n      \"id\": \"grafana\",\n      \"name\": \"Grafana\",\n      \"version\": \"9.3.6\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"piechart\",\n      \"name\": \"Pie chart\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"datasource\",\n      \"id\": \"prometheus\",\n      \"name\": \"Prometheus\",\n      \"version\": \"1.0.0\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"timeseries\",\n      \"name\": \"Time series\",\n      \"version\": \"\"\n    }\n  ],\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": {\n          \"type\": \"grafana\",\n          \"uid\": \"-- Grafana --\"\n        },\n        \"enable\": true,\n        \"hide\": true,\n        \"iconColor\": \"rgba(0, 211, 255, 1)\",\n        \"name\": \"Annotations & Alerts\",\n        \"target\": {\n          \"limit\": 100,\n          \"matchAny\": false,\n          \"tags\": [],\n          \"type\": \"dashboard\"\n        },\n        \"type\": \"dashboard\"\n      }\n    ]\n  },\n  \"editable\": true,\n  \"fiscalYearStartMonth\": 0,\n  \"graphTooltip\": 0,\n  \"id\": null,\n  \"links\": [],\n  \"liveNow\": false,\n  \"panels\": [\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 8,\n      \"panels\": [],\n      \"title\": \"DCUtR Initiator\",\n      \"type\": \"row\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            }\n          },\n          \"mappings\": []\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"failed\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"purple\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"success\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"green\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 1\n      },\n      \"id\": 19,\n      \"options\": {\n        \"legend\": {\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"pieType\": \"pie\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"exemplar\": false,\n          \"expr\": \"sum by (outcome) (increase(libp2p_holepunch_address_outcomes_total {side=\\\"initiator\\\", transport=\\\"tcp\\\", outcome=~\\\"success|failed\\\"}[$__range]))\",\n          \"legendFormat\": \"{{outcome}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Hole punches: TCP\",\n      \"type\": \"piechart\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            }\n          },\n          \"mappings\": []\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"failed\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"purple\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"success\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"green\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 1\n      },\n      \"id\": 20,\n      \"options\": {\n        \"legend\": {\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"pieType\": \"pie\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"sum by (outcome) (increase(libp2p_holepunch_address_outcomes_total {side=\\\"initiator\\\", transport=~\\\"quic|quic-v1\\\", outcome=~\\\"success|failed\\\"}[$__range]))\",\n          \"legendFormat\": \"{{outcome}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Hole punches: QUIC\",\n      \"type\": \"piechart\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            }\n          },\n          \"mappings\": []\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"failed\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"purple\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"success\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"green\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"no_suitable_address\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"blue\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 9\n      },\n      \"id\": 6,\n      \"options\": {\n        \"legend\": {\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"pieType\": \"pie\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"sum by (outcome) (increase(libp2p_holepunch_outcomes_total{side=\\\"initiator\\\",instance=~\\\"$instance\\\"}[$__range]))\",\n          \"legendFormat\": \"{{outcome}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Hole punches: Total\",\n      \"type\": \"piechart\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"success\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"green\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"failed\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"purple\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 9\n      },\n      \"id\": 4,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"increase(libp2p_holepunch_direct_dials_total{instance=~\\\"$instance\\\"}[$__rate_interval])\",\n          \"legendFormat\": \"{{outcome}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Direct dials\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"success\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"green\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"failed\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"purple\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"no_suitable_address\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"blue\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 17\n      },\n      \"id\": 23,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"sum by (outcome) (libp2p_holepunch_outcomes_total{side=\\\"initiator\\\",instance=~\\\"$instance\\\"}) - (sum by (outcome)(libp2p_holepunch_outcomes_total{side=\\\"initiator\\\",instance=~\\\"$instance\\\"} offset $__interval) or vector(0))\",\n          \"legendFormat\": \"__auto\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Hole punches\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 25\n      },\n      \"id\": 14,\n      \"panels\": [],\n      \"title\": \"DCUtR Receiver\",\n      \"type\": \"row\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            }\n          },\n          \"mappings\": []\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"failed\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"purple\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"success\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"green\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 26\n      },\n      \"id\": 18,\n      \"options\": {\n        \"legend\": {\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"pieType\": \"pie\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"sum by (outcome) (increase(libp2p_holepunch_address_outcomes_total {side=\\\"receiver\\\", transport=~\\\"quic|quic-v1\\\", outcome=~\\\"success|failed\\\"}[$__range]))\",\n          \"legendFormat\": \"{{outcome}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Hole punches: QUIC \",\n      \"type\": \"piechart\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            }\n          },\n          \"mappings\": []\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"failed\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"purple\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"success\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"green\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 26\n      },\n      \"id\": 21,\n      \"options\": {\n        \"legend\": {\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"pieType\": \"pie\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"sum by (outcome) (increase(libp2p_holepunch_address_outcomes_total {side=\\\"receiver\\\", transport=\\\"tcp\\\", outcome=~\\\"success|failed\\\"}[$__range]))\",\n          \"legendFormat\": \"{{outcome}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Hole punches: TCP\",\n      \"type\": \"piechart\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            }\n          },\n          \"mappings\": []\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"failed\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"purple\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"success\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"green\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"no_suitable_address\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"light-blue\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 34\n      },\n      \"id\": 17,\n      \"options\": {\n        \"legend\": {\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"pieType\": \"pie\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"sum by (outcome) (increase(libp2p_holepunch_outcomes_total{side=\\\"receiver\\\",instance=~\\\"$instance\\\"}[$__range]))\",\n          \"legendFormat\": \"{{outcome}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Hole punches: Total\",\n      \"type\": \"piechart\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"success\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"green\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"failed\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"purple\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"no_suitable_address\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"blue\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 34\n      },\n      \"id\": 24,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"(sum by (outcome) (libp2p_holepunch_outcomes_total{side=\\\"receiver\\\",instance=~\\\"$instance\\\"})) - (sum by (outcome)(libp2p_holepunch_outcomes_total{side=\\\"receiver\\\",instance=~\\\"$instance\\\"} offset $__interval) or vector(0))\",\n          \"legendFormat\": \"{{outcome}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Hole punches\",\n      \"type\": \"timeseries\"\n    }\n  ],\n  \"refresh\": \"\",\n  \"revision\": 1,\n  \"schemaVersion\": 37,\n  \"style\": \"dark\",\n  \"tags\": [],\n  \"templating\": {\n    \"list\": [\n       {\n        \"hide\": 0,\n        \"label\": \"datasource\",\n        \"name\": \"DS_PROMETHEUS\",\n        \"options\": [],\n        \"query\": \"prometheus\",\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"type\": \"datasource\"\n      },\n      {\n        \"current\": {},\n        \"definition\": \"label_values(instance)\",\n        \"hide\": 0,\n        \"includeAll\": true,\n        \"multi\": true,\n        \"name\": \"instance\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(instance)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"type\": \"query\"\n      }\n    ]\n  },\n  \"time\": {\n    \"from\": \"now-1h\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {},\n  \"timezone\": \"\",\n  \"title\": \"go-libp2p Hole Punches\",\n  \"uid\": \"Ao24vOBVk\",\n  \"version\": 6,\n  \"weekStart\": \"\"\n}\n"
  },
  {
    "path": "dashboards/host-addrs/host-addrs.json",
    "content": "{\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": {\n          \"type\": \"grafana\",\n          \"uid\": \"-- Grafana --\"\n        },\n        \"enable\": true,\n        \"hide\": true,\n        \"iconColor\": \"rgba(0, 211, 255, 1)\",\n        \"name\": \"Annotations & Alerts\",\n        \"type\": \"dashboard\"\n      }\n    ]\n  },\n  \"editable\": true,\n  \"fiscalYearStartMonth\": 0,\n  \"graphTooltip\": 0,\n  \"id\": 16,\n  \"links\": [],\n  \"panels\": [\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"bdpgk86mw6jgga\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\"\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 1,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"area\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"auto\",\n        \"percentChangeColorMode\": \"standard\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showPercentChange\": false,\n        \"textMode\": \"auto\",\n        \"wideLayout\": true\n      },\n      \"pluginVersion\": \"11.6.1\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"bdpgk86mw6jgga\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"libp2p_host_addrs_reachable{instance=~\\\"$instance\\\"}\",\n          \"legendFormat\": \"{{instance}} {{ipv}}, {{transport}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Reachable Addrs\",\n      \"type\": \"stat\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"bdpgk86mw6jgga\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\"\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 8\n      },\n      \"id\": 2,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"area\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"auto\",\n        \"percentChangeColorMode\": \"standard\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showPercentChange\": false,\n        \"textMode\": \"auto\",\n        \"wideLayout\": true\n      },\n      \"pluginVersion\": \"11.6.1\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"bdpgk86mw6jgga\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"libp2p_host_addrs_unreachable{instance=~\\\"$instance\\\"}\",\n          \"legendFormat\": \"{{instance}} {{ipv}}, {{transport}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Unreachable Addrs\",\n      \"type\": \"stat\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"bdpgk86mw6jgga\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\"\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 16\n      },\n      \"id\": 3,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"area\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"auto\",\n        \"percentChangeColorMode\": \"standard\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showPercentChange\": false,\n        \"textMode\": \"auto\",\n        \"wideLayout\": true\n      },\n      \"pluginVersion\": \"11.6.1\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"bdpgk86mw6jgga\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"libp2p_host_addrs_unknown{instance=~\\\"$instance\\\"}\",\n          \"legendFormat\": \"{{instance}} {{ipv}}, {{transport}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Unknown Reachability Addrs\",\n      \"type\": \"stat\"\n    }\n  ],\n  \"preload\": false,\n  \"schemaVersion\": 41,\n  \"tags\": [],\n  \"templating\": {\n    \"list\": [\n      {\n        \"current\": {\n          \"text\": [\n            \"All\"\n          ],\n          \"value\": [\n            \"$__all\"\n          ]\n        },\n        \"definition\": \"label_values(up,instance)\",\n        \"includeAll\": true,\n        \"label\": \"instance\",\n        \"multi\": true,\n        \"name\": \"instance\",\n        \"options\": [],\n        \"query\": {\n          \"qryType\": 1,\n          \"query\": \"label_values(up,instance)\",\n          \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"type\": \"query\"\n      },\n      {\n        \"current\": {\n          \"text\": \"prometheus\",\n          \"value\": \"bdpgk86mw6jgga\"\n        },\n        \"includeAll\": false,\n        \"name\": \"data_source\",\n        \"options\": [],\n        \"query\": \"prometheus\",\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"type\": \"datasource\"\n      }\n    ]\n  },\n  \"time\": {\n    \"from\": \"now-6h\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {},\n  \"timezone\": \"browser\",\n  \"title\": \"go-libp2p Host Addresses\",\n  \"uid\": \"beon8z59rh7nkf\",\n  \"version\": 5\n}"
  },
  {
    "path": "dashboards/identify/identify.json",
    "content": "{\n  \"__inputs\": [\n    {\n      \"name\": \"DS_PROMETHEUS\",\n      \"label\": \"Prometheus\",\n      \"description\": \"\",\n      \"type\": \"datasource\",\n      \"pluginId\": \"prometheus\",\n      \"pluginName\": \"Prometheus\"\n    }\n  ],\n  \"__elements\": {},\n  \"__requires\": [\n    {\n      \"type\": \"grafana\",\n      \"id\": \"grafana\",\n      \"name\": \"Grafana\",\n      \"version\": \"9.3.6\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"piechart\",\n      \"name\": \"Pie chart\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"datasource\",\n      \"id\": \"prometheus\",\n      \"name\": \"Prometheus\",\n      \"version\": \"1.0.0\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"stat\",\n      \"name\": \"Stat\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"timeseries\",\n      \"name\": \"Time series\",\n      \"version\": \"\"\n    }\n  ],\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": {\n          \"type\": \"grafana\",\n          \"uid\": \"-- Grafana --\"\n        },\n        \"enable\": true,\n        \"hide\": true,\n        \"iconColor\": \"rgba(0, 211, 255, 1)\",\n        \"name\": \"Annotations & Alerts\",\n        \"target\": {\n          \"limit\": 100,\n          \"matchAny\": false,\n          \"tags\": [],\n          \"type\": \"dashboard\"\n        },\n        \"type\": \"dashboard\"\n      }\n    ]\n  },\n  \"editable\": true,\n  \"fiscalYearStartMonth\": 0,\n  \"graphTooltip\": 0,\n  \"id\": null,\n  \"links\": [],\n  \"liveNow\": false,\n  \"panels\": [\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 9,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 2,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"multi\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"increase(libp2p_identify_identify_pushes_triggered_total{instance=~\\\"$instance\\\"}[$__rate_interval])\",\n          \"legendFormat\": \"{{trigger}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Pushes triggered\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 9,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 0\n      },\n      \"id\": 4,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"multi\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"rate(libp2p_identify_identify_total{instance=~\\\"$instance\\\"}[$__rate_interval])\",\n          \"legendFormat\": \"{{dir}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Identify Messages\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"mappings\": [],\n          \"min\": 0,\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              }\n            ]\n          },\n          \"unit\": \"none\"\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"address count\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"blue\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 6,\n        \"x\": 0,\n        \"y\": 9\n      },\n      \"id\": 9,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"area\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"textMode\": \"auto\"\n      },\n      \"pluginVersion\": \"9.3.6\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"exemplar\": false,\n          \"expr\": \"libp2p_identify_addrs_count{instance=~\\\"$instance\\\"}\",\n          \"legendFormat\": \"address count\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Outgoing Address Count\",\n      \"type\": \"stat\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"mappings\": [],\n          \"min\": 0,\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              }\n            ]\n          }\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"protocols count\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"purple\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 6,\n        \"x\": 6,\n        \"y\": 9\n      },\n      \"id\": 17,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"area\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"textMode\": \"auto\"\n      },\n      \"pluginVersion\": \"9.3.6\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"libp2p_identify_protocols_count{instance=~\\\"$instance\\\"}\",\n          \"legendFormat\": \"protocols count\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Outgoing Protocols Count\",\n      \"type\": \"stat\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 9\n      },\n      \"id\": 5,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"multi\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"rate(libp2p_identify_identify_push_total{instance=~\\\"$instance\\\"}[$__rate_interval])\",\n          \"legendFormat\": \"{{dir}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Identify Push Messages\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"address count\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"blue\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 17\n      },\n      \"id\": 11,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"histogram_quantile(0.5, sum(rate(libp2p_identify_addrs_received_bucket{instance=~\\\"$instance\\\"}[$__rate_interval])) by (le))\",\n          \"legendFormat\": \"50th percentile\",\n          \"range\": true,\n          \"refId\": \"A\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"histogram_quantile(0.90, sum(rate(libp2p_identify_addrs_received_bucket{instance=~\\\"$instance\\\"}[$__rate_interval])) by (le))\",\n          \"hide\": false,\n          \"legendFormat\": \"90th percentile\",\n          \"range\": true,\n          \"refId\": \"B\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"histogram_quantile(0.99, sum(rate(libp2p_identify_addrs_received_bucket{instance=~\\\"$instance\\\"}[$__rate_interval])) by (le))\",\n          \"hide\": false,\n          \"legendFormat\": \"99 percentile\",\n          \"range\": true,\n          \"refId\": \"C\"\n        }\n      ],\n      \"title\": \"Incoming Address Count (Avg)\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"protocols count\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"purple\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 17\n      },\n      \"id\": 13,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"histogram_quantile(0.5, sum(rate(libp2p_identify_protocols_received_bucket{instance=~\\\"$instance\\\"}[$__rate_interval])) by (le))\",\n          \"legendFormat\": \"50th percentile\",\n          \"range\": true,\n          \"refId\": \"A\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"histogram_quantile(0.90, sum(rate(libp2p_identify_protocols_received_bucket{instance=~\\\"$instance\\\"}[$__rate_interval])) by (le))\",\n          \"hide\": false,\n          \"legendFormat\": \"90th percentile\",\n          \"range\": true,\n          \"refId\": \"B\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"histogram_quantile(0.99, sum(rate(libp2p_identify_protocols_received_bucket{instance=~\\\"$instance\\\"}[$__rate_interval])) by (le))\",\n          \"hide\": false,\n          \"legendFormat\": \"99th percentile\",\n          \"range\": true,\n          \"refId\": \"C\"\n        }\n      ],\n      \"title\": \"Incoming Protocols Count (Avg)\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            }\n          },\n          \"mappings\": []\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 25\n      },\n      \"id\": 15,\n      \"options\": {\n        \"legend\": {\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"pieType\": \"pie\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"increase(libp2p_identify_conn_push_support_total{instance=~\\\"$instance\\\"}[$__rate_interval])\",\n          \"legendFormat\": \"{{support}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"New Connections: Push Support\",\n      \"type\": \"piechart\"\n    }\n  ],\n  \"schemaVersion\": 37,\n  \"style\": \"dark\",\n  \"tags\": [],\n  \"templating\": {\n    \"list\": [\n       {\n        \"hide\": 0,\n        \"label\": \"datasource\",\n        \"name\": \"DS_PROMETHEUS\",\n        \"options\": [],\n        \"query\": \"prometheus\",\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"type\": \"datasource\"\n      },\n      {\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${DS_PROMETHEUS}\"\n        },\n        \"definition\": \"label_values(up, instance)\",\n        \"hide\": 0,\n        \"includeAll\": true,\n        \"multi\": true,\n        \"name\": \"instance\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(up, instance)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"type\": \"query\"\n      }\n    ]\n  },\n  \"time\": {\n    \"from\": \"now-15m\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {},\n  \"timezone\": \"\",\n  \"title\": \"go-libp2p Identify\",\n  \"uid\": \"0NDzQQ0Vz\",\n  \"version\": 2,\n  \"weekStart\": \"\"\n}\n"
  },
  {
    "path": "dashboards/prometheus.yml",
    "content": "global:\n  scrape_interval: 15s\n  scrape_timeout: 10s\n  evaluation_interval: 15s\nalerting:\n  alertmanagers:\n  - scheme: http\n    timeout: 10s\n    api_version: v2\n    static_configs:\n    - targets: []\nscrape_configs:\n- job_name: prometheus\n  honor_timestamps: true\n  scrape_interval: 15s\n  scrape_timeout: 10s\n  metrics_path: /metrics\n  scheme: http\n  static_configs:\n  - targets:\n    - localhost:9090\n- job_name: libp2p\n  honor_timestamps: true\n  scrape_interval: 15s\n  scrape_timeout: 10s\n  metrics_path: /debug/metrics/prometheus\n  scheme: http\n  static_configs:\n  - targets:\n    - host.docker.internal:5001\n"
  },
  {
    "path": "dashboards/relaysvc/relaysvc.json",
    "content": "{\n  \"__inputs\": [\n    {\n      \"name\": \"DS_PROMETHEUS\",\n      \"label\": \"Prometheus\",\n      \"description\": \"\",\n      \"type\": \"datasource\",\n      \"pluginId\": \"prometheus\",\n      \"pluginName\": \"Prometheus\"\n    }\n  ],\n  \"__elements\": {},\n  \"__requires\": [\n    {\n      \"type\": \"grafana\",\n      \"id\": \"grafana\",\n      \"name\": \"Grafana\",\n      \"version\": \"9.3.6\"\n    },\n    {\n      \"type\": \"datasource\",\n      \"id\": \"prometheus\",\n      \"name\": \"Prometheus\",\n      \"version\": \"1.0.0\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"stat\",\n      \"name\": \"Stat\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"state-timeline\",\n      \"name\": \"State timeline\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"timeseries\",\n      \"name\": \"Time series\",\n      \"version\": \"\"\n    }\n  ],\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": {\n          \"type\": \"grafana\",\n          \"uid\": \"-- Grafana --\"\n        },\n        \"enable\": true,\n        \"hide\": true,\n        \"iconColor\": \"rgba(0, 211, 255, 1)\",\n        \"name\": \"Annotations & Alerts\",\n        \"target\": {\n          \"limit\": 100,\n          \"matchAny\": false,\n          \"tags\": [],\n          \"type\": \"dashboard\"\n        },\n        \"type\": \"dashboard\"\n      }\n    ]\n  },\n  \"editable\": true,\n  \"fiscalYearStartMonth\": 0,\n  \"graphTooltip\": 0,\n  \"id\": null,\n  \"links\": [],\n  \"liveNow\": false,\n  \"panels\": [\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 20,\n      \"panels\": [],\n      \"title\": \"Relay Service\",\n      \"type\": \"row\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"continuous-GrYlRd\"\n          },\n          \"custom\": {\n            \"fillOpacity\": 70,\n            \"lineWidth\": 0,\n            \"spanNulls\": false\n          },\n          \"mappings\": [\n            {\n              \"options\": {\n                \"0\": {\n                  \"color\": \"purple\",\n                  \"index\": 0,\n                  \"text\": \"no\"\n                },\n                \"1\": {\n                  \"color\": \"green\",\n                  \"index\": 1,\n                  \"text\": \"yes\"\n                }\n              },\n              \"type\": \"value\"\n            }\n          ],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 1\n      },\n      \"id\": 2,\n      \"options\": {\n        \"alignValue\": \"center\",\n        \"legend\": {\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"mergeValues\": true,\n        \"rowHeight\": 0.9,\n        \"showValue\": \"auto\",\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"libp2p_relaysvc_status{instance=~\\\"$instance\\\"}\",\n          \"legendFormat\": \"active\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Status\",\n      \"type\": \"state-timeline\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 8\n      },\n      \"id\": 22,\n      \"panels\": [],\n      \"title\": \"Reservations\",\n      \"type\": \"row\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"mappings\": [\n            {\n              \"options\": {\n                \"match\": \"null\",\n                \"result\": {\n                  \"index\": 0,\n                  \"text\": \"0\"\n                }\n              },\n              \"type\": \"special\"\n            }\n          ],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 6,\n        \"x\": 0,\n        \"y\": 9\n      },\n      \"id\": 4,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"area\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"textMode\": \"auto\"\n      },\n      \"pluginVersion\": \"9.3.6\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"libp2p_relaysvc_reservations_total{type=\\\"opened\\\",instance=~\\\"$instance\\\"} - ignoring(type) libp2p_relaysvc_reservations_total{type=\\\"closed\\\",instance=~\\\"$instance\\\"}\",\n          \"legendFormat\": \"active reservations\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Active Reservations\",\n      \"type\": \"stat\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"error\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"purple\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"ok\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"green\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 18,\n        \"x\": 6,\n        \"y\": 9\n      },\n      \"id\": 8,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"increase(libp2p_relaysvc_reservation_request_response_status_total{instance=~\\\"$instance\\\"}[$__rate_interval])\",\n          \"legendFormat\": \"{{status}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Reservation Request Response Status\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 17\n      },\n      \"id\": 26,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"increase(libp2p_relaysvc_reservations_total{type=\\\"opened\\\",instance=~\\\"$instance\\\"}[$__rate_interval])\",\n          \"legendFormat\": \"new\",\n          \"range\": true,\n          \"refId\": \"A\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"increase(libp2p_relaysvc_reservations_total{type=\\\"renewed\\\",instance=~\\\"$instance\\\"}[$__rate_interval])\",\n          \"hide\": false,\n          \"legendFormat\": \"renewed\",\n          \"range\": true,\n          \"refId\": \"B\"\n        }\n      ],\n      \"title\": \"Reservation Requests: New vs Renewal\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"description\": \"\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 17\n      },\n      \"id\": 12,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"increase(libp2p_relaysvc_reservation_rejections_total{instance=~\\\"$instance\\\"}[$__rate_interval])\",\n          \"legendFormat\": \"{{reason}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Reservation Request Rejected\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 25\n      },\n      \"id\": 24,\n      \"panels\": [],\n      \"title\": \"Connections\",\n      \"type\": \"row\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"description\": \"\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"decbytes\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 6,\n        \"x\": 0,\n        \"y\": 26\n      },\n      \"id\": 28,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"area\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"textMode\": \"auto\"\n      },\n      \"pluginVersion\": \"9.3.6\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"increase(libp2p_relaysvc_data_transferred_bytes_total{instance=~\\\"$instance\\\"}[$__range])\",\n          \"legendFormat\": \"data transferred\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Total Data Transferred\",\n      \"type\": \"stat\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"fixedColor\": \"blue\",\n            \"mode\": \"fixed\"\n          },\n          \"mappings\": [\n            {\n              \"options\": {\n                \"match\": \"null\",\n                \"result\": {\n                  \"index\": 0,\n                  \"text\": \"0\"\n                }\n              },\n              \"type\": \"special\"\n            }\n          ],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 6,\n        \"x\": 6,\n        \"y\": 26\n      },\n      \"id\": 6,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"area\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"textMode\": \"auto\"\n      },\n      \"pluginVersion\": \"9.3.6\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"libp2p_relaysvc_connections_total{type=\\\"opened\\\",instance=~\\\"$instance\\\"} - ignoring(type) libp2p_relaysvc_connections_total{type=\\\"closed\\\",instance=~\\\"$instance\\\"}\",\n          \"legendFormat\": \"active connections\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Active Connections\",\n      \"type\": \"stat\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": [\n          {\n            \"__systemRef\": \"hideSeriesFrom\",\n            \"matcher\": {\n              \"id\": \"byNames\",\n              \"options\": {\n                \"mode\": \"exclude\",\n                \"names\": [\n                  \"error\"\n                ],\n                \"prefix\": \"All except:\",\n                \"readOnly\": true\n              }\n            },\n            \"properties\": [\n              {\n                \"id\": \"custom.hideFrom\",\n                \"value\": {\n                  \"legend\": false,\n                  \"tooltip\": false,\n                  \"viz\": true\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"error\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"purple\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"ok\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"green\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 26\n      },\n      \"id\": 10,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"increase(libp2p_relaysvc_connection_request_response_status_total{instance=~\\\"$instance\\\"}[$__rate_interval])\",\n          \"legendFormat\": \"{{status}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Connection Request Response Status\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              }\n            ]\n          },\n          \"unit\": \"Bps\"\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"bandwidth\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"blue\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"__systemRef\": \"hideSeriesFrom\",\n            \"matcher\": {\n              \"id\": \"byNames\",\n              \"options\": {\n                \"mode\": \"exclude\",\n                \"names\": [\n                  \"bandwidth\"\n                ],\n                \"prefix\": \"All except:\",\n                \"readOnly\": true\n              }\n            },\n            \"properties\": [\n              {\n                \"id\": \"custom.hideFrom\",\n                \"value\": {\n                  \"legend\": false,\n                  \"tooltip\": false,\n                  \"viz\": true\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 34\n      },\n      \"id\": 16,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"2 * rate(libp2p_relaysvc_data_transferred_bytes_total{instance=~\\\"$instance\\\"}[$__rate_interval])\",\n          \"legendFormat\": \"bandwidth\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Bandwidth Used\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 34\n      },\n      \"id\": 14,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"increase(libp2p_relaysvc_connection_rejections_total{instance=~\\\"$instance\\\"}[$__rate_interval])\",\n          \"legendFormat\": \"{{reason}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Connection Request Rejected\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"description\": \"\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              }\n            ]\n          },\n          \"unit\": \"s\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 42\n      },\n      \"id\": 18,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"rate(libp2p_relaysvc_connection_duration_seconds_sum{instance=~\\\"$instance\\\"}[$__range])/rate(libp2p_relaysvc_connection_duration_seconds_count{instance=~\\\"$instance\\\"}[$__range])\\n\",\n          \"hide\": false,\n          \"legendFormat\": \"rolling average\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Connection Duration\",\n      \"type\": \"timeseries\"\n    }\n  ],\n  \"refresh\": \"1m\",\n  \"schemaVersion\": 37,\n  \"style\": \"dark\",\n  \"tags\": [],\n  \"templating\": {\n    \"list\": [\n      {\n        \"hide\": 0,\n        \"label\": \"datasource\",\n        \"name\": \"DS_PROMETHEUS\",\n        \"options\": [],\n        \"query\": \"prometheus\",\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"type\": \"datasource\"\n      },\n      {\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${DS_PROMETHEUS}\"\n        },\n        \"definition\": \"label_values(up, instance)\",\n        \"hide\": 0,\n        \"includeAll\": true,\n        \"multi\": true,\n        \"name\": \"instance\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(up, instance)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"type\": \"query\"\n      }\n    ]\n  },\n  \"time\": {\n    \"from\": \"now-1h\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {},\n  \"timezone\": \"\",\n  \"title\": \"go-libp2p Relay Service\",\n  \"uid\": \"C6RUfAx4z\",\n  \"version\": 5,\n  \"weekStart\": \"\"\n}\n"
  },
  {
    "path": "dashboards/resource-manager/README.md",
    "content": "# Ready to go Grafana Dashboard\n\nHere are some prebuilt dashboards that you can add to your Grafana instance. To\nimport follow the Grafana docs [here](https://grafana.com/docs/grafana/latest/dashboards/export-import/#import-dashboard)\n\n## Setup\n\nMetrics are enabled by default. By default, metrics will be sent to\n`prometheus.DefaultRegisterer`. To use a different Registerer use the libp2p\noption `libp2p.PrometheusRegisterer`.\n\n## Updating Dashboard json\n\nUse the share functionality on an existing dashboard, and make sure to toggle\n\"Export for sharing externally\". See the [Grafana\nDocs](https://grafana.com/docs/grafana/latest/dashboards/export-import/#exporting-a-dashboard)\nfor more details.\n"
  },
  {
    "path": "dashboards/resource-manager/resource-manager.json",
    "content": "{\n  \"__inputs\": [\n    {\n      \"name\": \"DS_PROMETHEUS\",\n      \"label\": \"Prometheus\",\n      \"description\": \"\",\n      \"type\": \"datasource\",\n      \"pluginId\": \"prometheus\",\n      \"pluginName\": \"Prometheus\"\n    }\n  ],\n  \"__elements\": {},\n  \"__requires\": [\n    {\n      \"type\": \"panel\",\n      \"id\": \"bargauge\",\n      \"name\": \"Bar gauge\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"grafana\",\n      \"id\": \"grafana\",\n      \"name\": \"Grafana\",\n      \"version\": \"9.3.6\"\n    },\n    {\n      \"type\": \"datasource\",\n      \"id\": \"prometheus\",\n      \"name\": \"Prometheus\",\n      \"version\": \"1.0.0\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"text\",\n      \"name\": \"Text\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"timeseries\",\n      \"name\": \"Time series\",\n      \"version\": \"\"\n    }\n  ],\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": {\n          \"type\": \"datasource\",\n          \"uid\": \"grafana\"\n        },\n        \"enable\": true,\n        \"hide\": true,\n        \"iconColor\": \"rgba(0, 211, 255, 1)\",\n        \"name\": \"Annotations & Alerts\",\n        \"target\": {\n          \"limit\": 100,\n          \"matchAny\": false,\n          \"tags\": [],\n          \"type\": \"dashboard\"\n        },\n        \"type\": \"dashboard\"\n      }\n    ]\n  },\n  \"editable\": true,\n  \"fiscalYearStartMonth\": 0,\n  \"graphTooltip\": 0,\n  \"id\": null,\n  \"links\": [],\n  \"liveNow\": false,\n  \"panels\": [\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 65,\n      \"panels\": [],\n      \"title\": \"Blocked Resources\",\n      \"type\": \"row\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"description\": \"This should be empty. If it's large you are running into your resource manager limits.\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 1\n      },\n      \"id\": 67,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"exemplar\": true,\n          \"expr\": \"rate(libp2p_rcmgr_blocked_resources{instance=~\\\"$instance\\\"}[$__rate_interval])\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Number of blocked resource requests\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 9\n      },\n      \"id\": 42,\n      \"panels\": [],\n      \"title\": \"Streams\",\n      \"type\": \"row\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 10\n      },\n      \"id\": 48,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"exemplar\": true,\n          \"expr\": \"libp2p_rcmgr_streams{scope=\\\"system\\\",instance=~\\\"$instance\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"{{dir}} {{instance}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"System Streams\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 10\n      },\n      \"id\": 44,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"exemplar\": true,\n          \"expr\": \"libp2p_rcmgr_streams{scope=\\\"transient\\\",instance=~\\\"$instance\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"{{dir}} {{instance}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Transient Streams\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"description\": \"How many streams does each protocol have open.\\n\\nA protocol is similiar to a service except there may be many protocols for a single service. A service may have \\nmultiple protocols for backwards compatibility. For example, bitswap the service is a single entity, but it supports bitswap protocols v1.1 and v1.2.\\n\\nA service is attached to a stream manually by the protocol developer. A service may be missing if there is no `StreamScope.SetService` call.\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 9,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 18\n      },\n      \"id\": 52,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"exemplar\": true,\n          \"expr\": \"libp2p_rcmgr_streams{scope=\\\"protocol\\\",instance=~\\\"$instance\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"{{dir}} {{protocol}} {{instance}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Streams by protocol\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"description\": \"\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 10,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 27\n      },\n      \"id\": 35,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"exemplar\": true,\n          \"expr\": \"histogram_quantile(0.50, (libp2p_rcmgr_peer_streams_bucket{instance=~\\\"$instance\\\"} - libp2p_rcmgr_previous_peer_streams_bucket{instance=~\\\"$instance\\\"})) - 0.1\",\n          \"interval\": \"\",\n          \"legendFormat\": \"p50 {{dir}} streams per peer – {{instance}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"exemplar\": true,\n          \"expr\": \"histogram_quantile(0.90, (libp2p_rcmgr_peer_streams_bucket{instance=~\\\"$instance\\\"} - libp2p_rcmgr_previous_peer_streams_bucket{instance=~\\\"$instance\\\"})) - 0.1\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"legendFormat\": \"p90 {{dir}} streams per peer – {{instance}}\",\n          \"refId\": \"B\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"exemplar\": true,\n          \"expr\": \"histogram_quantile(1, (libp2p_rcmgr_peer_streams_bucket{instance=~\\\"$instance\\\"} - libp2p_rcmgr_previous_peer_streams_bucket{instance=~\\\"$instance\\\"})) - 0.1\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"legendFormat\": \"max {{dir}} streams per peer – {{instance}}\",\n          \"refId\": \"C\"\n        }\n      ],\n      \"title\": \"Streams per peer, aggregated\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"description\": \"How many peers have N streams open\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 37\n      },\n      \"id\": 46,\n      \"options\": {\n        \"displayMode\": \"gradient\",\n        \"minVizHeight\": 10,\n        \"minVizWidth\": 0,\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"last\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showUnfilled\": true\n      },\n      \"pluginVersion\": \"9.3.6\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"exemplar\": false,\n          \"expr\": \"sum without (instance) (libp2p_rcmgr_peer_streams_bucket{dir=\\\"inbound\\\",instance=~\\\"$instance\\\"}-libp2p_rcmgr_previous_peer_streams_bucket{dir=\\\"inbound\\\",instance=~\\\"$instance\\\"})\",\n          \"format\": \"heatmap\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"legendFormat\": \"{{le}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Current inbound streams per peer histogram. Across all instances\",\n      \"type\": \"bargauge\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"description\": \"How many peers have N streams open\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 37\n      },\n      \"id\": 47,\n      \"options\": {\n        \"displayMode\": \"gradient\",\n        \"minVizHeight\": 10,\n        \"minVizWidth\": 0,\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"last\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showUnfilled\": true\n      },\n      \"pluginVersion\": \"9.3.6\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"exemplar\": false,\n          \"expr\": \"sum without (instance) (libp2p_rcmgr_peer_streams_bucket{dir=\\\"outbound\\\",instance=~\\\"$instance\\\"}-libp2p_rcmgr_previous_peer_streams_bucket{dir=\\\"outbound\\\",instance=~\\\"$instance\\\"})\",\n          \"format\": \"heatmap\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"legendFormat\": \"{{le}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Current outbound streams per peer histogram. Across all instances\",\n      \"type\": \"bargauge\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 45\n      },\n      \"id\": 29,\n      \"panels\": [],\n      \"title\": \"Connections\",\n      \"type\": \"row\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"gridPos\": {\n        \"h\": 9,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 46\n      },\n      \"id\": 31,\n      \"options\": {\n        \"code\": {\n          \"language\": \"plaintext\",\n          \"showLineNumbers\": false,\n          \"showMiniMap\": false\n        },\n        \"content\": \"# Libp2p Connections\\n\\nBroken down by [Resource Scope](https://github.com/libp2p/go-libp2p/blob/master/p2p/host/resource-manager/README.md#resource-scopes). \\nScopes represent what is imposing limits on this resource. For connections, we have three main scopes:\\n\\n1. System. The total number of connections owned by the process. Includes both application usable connections + the number of transient connections.\\n2. Transient. The total number of connections that are being upgraded into usable connections in the process.\\n3. Peer. The total number of connections associated with this peer. When a connection has this scope it is usable by the application.\\n\\nAn example of a System connection is a connection you can open a libp2p stream on and send data.\\nA transient connection is not yet usable for application data since it may be negotiating \\na security handshake or a multiplexer.\\n\\nConnections start in the transient scope and move over to the System and Peer scopes once they are ready to be used.\\n\\nIt would be unusual to see a lot of transient connections. It would also be unusal to see a peer with a lot of connections.\",\n        \"mode\": \"markdown\"\n      },\n      \"pluginVersion\": \"9.3.6\",\n      \"title\": \"libp2p Connections\",\n      \"type\": \"text\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 55\n      },\n      \"id\": 33,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"exemplar\": true,\n          \"expr\": \"libp2p_rcmgr_connections{scope=\\\"system\\\",instance=~\\\"$instance\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"{{dir}} {{instance}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"System Connections\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 55\n      },\n      \"id\": 36,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"exemplar\": true,\n          \"expr\": \"libp2p_rcmgr_connections{scope=\\\"transient\\\",instance=~\\\"$instance\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"{{dir}} {{instance}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Transient Connections\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 63\n      },\n      \"id\": 38,\n      \"options\": {\n        \"code\": {\n          \"language\": \"plaintext\",\n          \"showLineNumbers\": false,\n          \"showMiniMap\": false\n        },\n        \"content\": \"These are aggregated stats. They are grouped by buckets. Each bucket represents how many peers have N number of connections.\",\n        \"mode\": \"markdown\"\n      },\n      \"pluginVersion\": \"9.3.6\",\n      \"title\": \"Connections per Peer\",\n      \"type\": \"text\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"description\": \"\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 10,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 66\n      },\n      \"id\": 45,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"exemplar\": true,\n          \"expr\": \"histogram_quantile(0.50, (libp2p_rcmgr_peer_connections_bucket{instance=~\\\"$instance\\\"} - libp2p_rcmgr_previous_peer_connections_bucket{instance=~\\\"$instance\\\"})) - 0.1\",\n          \"interval\": \"\",\n          \"legendFormat\": \"p50 {{dir}} connections per peer – {{instance}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"exemplar\": true,\n          \"expr\": \"histogram_quantile(0.90, (libp2p_rcmgr_peer_connections_bucket{instance=~\\\"$instance\\\"} - libp2p_rcmgr_previous_peer_connections_bucket{instance=~\\\"$instance\\\"})) - 0.1\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"legendFormat\": \"p90 {{dir}} connections per peer – {{instance}}\",\n          \"refId\": \"B\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"exemplar\": true,\n          \"expr\": \"histogram_quantile(1, (libp2p_rcmgr_peer_connections_bucket{instance=~\\\"$instance\\\"} - libp2p_rcmgr_previous_peer_connections_bucket{instance=~\\\"$instance\\\"})) - 0.1\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"legendFormat\": \"max {{dir}} connections per peer – {{instance}}\",\n          \"refId\": \"C\"\n        }\n      ],\n      \"title\": \"Connections per peer, aggregated\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"description\": \"How many peers have N-0.1 connections open\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 76\n      },\n      \"id\": 39,\n      \"options\": {\n        \"displayMode\": \"gradient\",\n        \"minVizHeight\": 10,\n        \"minVizWidth\": 0,\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"last\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showUnfilled\": true\n      },\n      \"pluginVersion\": \"9.3.6\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"exemplar\": false,\n          \"expr\": \"sum without (instance) (libp2p_rcmgr_peer_connections_bucket{dir=\\\"inbound\\\",instance=~\\\"$instance\\\"}-libp2p_rcmgr_previous_peer_connections_bucket{dir=\\\"inbound\\\",instance=~\\\"$instance\\\"})\",\n          \"format\": \"heatmap\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"legendFormat\": \"{{le}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Current inbound connections per peer histogram. Across all instances\",\n      \"type\": \"bargauge\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"description\": \"How many peers have N-0.1 connections open\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 76\n      },\n      \"id\": 40,\n      \"options\": {\n        \"displayMode\": \"gradient\",\n        \"minVizHeight\": 10,\n        \"minVizWidth\": 0,\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"last\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showUnfilled\": true\n      },\n      \"pluginVersion\": \"9.3.6\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"exemplar\": false,\n          \"expr\": \"sum without (instance) (libp2p_rcmgr_peer_connections_bucket{dir=\\\"outbound\\\",instance=~\\\"$instance\\\"}-libp2p_rcmgr_previous_peer_connections_bucket{dir=\\\"outbound\\\",instance=~\\\"$instance\\\"})\",\n          \"format\": \"heatmap\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"legendFormat\": \"{{le}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Curent outbound connections per peer histogram. Across all instances\",\n      \"type\": \"bargauge\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 84\n      },\n      \"id\": 54,\n      \"panels\": [],\n      \"title\": \"Memory\",\n      \"type\": \"row\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"description\": \"As reported to Resource Manager\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"bytes\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 9,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 85\n      },\n      \"id\": 56,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"exemplar\": true,\n          \"expr\": \"libp2p_rcmgr_memory{scope=\\\"system\\\",instance=~\\\"$instance\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"Bytes Reserved\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"System memory reservations\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"description\": \"As reported to Resource Manager\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"bytes\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 94\n      },\n      \"id\": 57,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"exemplar\": true,\n          \"expr\": \"libp2p_rcmgr_memory{scope=\\\"protocol\\\",instance=~\\\"$instance\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"{{protocol}} {{instance}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Memory reservations by protocol\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"description\": \"As reported to Resource Manager\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"bytes\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 94\n      },\n      \"id\": 58,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"exemplar\": true,\n          \"expr\": \"libp2p_rcmgr_memory{scope=\\\"service\\\",instance=~\\\"$instance\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"{{service}} {{instance}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Memory reservations by service\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"description\": \"As reported to the resource manager\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"bytes\"\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"Number of peers aggregated\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"dark-purple\",\n                  \"mode\": \"fixed\"\n                }\n              },\n              {\n                \"id\": \"custom.axisPlacement\",\n                \"value\": \"right\"\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 10,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 102\n      },\n      \"id\": 59,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"exemplar\": true,\n          \"expr\": \"histogram_quantile(0.50, sum by (le) (libp2p_rcmgr_peer_memory_bucket{instance=~\\\"$instance\\\"} - libp2p_rcmgr_previous_peer_memory_bucket{instance=~\\\"$instance\\\"})) - 0.1\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"legendFormat\": \"p50 memory usage per peer\",\n          \"refId\": \"A\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"exemplar\": true,\n          \"expr\": \"histogram_quantile(0.90, sum by (le) (libp2p_rcmgr_peer_memory_bucket{instance=~\\\"$instance\\\"} - libp2p_rcmgr_previous_peer_memory_bucket{instance=~\\\"$instance\\\"})) - 0.1\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"legendFormat\": \"p90 memory usage per peer\",\n          \"refId\": \"B\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"exemplar\": true,\n          \"expr\": \"histogram_quantile(1, sum by (le) (libp2p_rcmgr_peer_memory_bucket{instance=~\\\"$instance\\\"} - libp2p_rcmgr_previous_peer_memory_bucket{instance=~\\\"$instance\\\"})) - 0.1\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"legendFormat\": \"max memory usage per peer\",\n          \"refId\": \"C\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"exemplar\": true,\n          \"expr\": \"sum(libp2p_rcmgr_peer_memory_count{instance=~\\\"$instance\\\"}-libp2p_rcmgr_previous_peer_memory_count{instance=~\\\"$instance\\\"})\",\n          \"hide\": false,\n          \"instant\": false,\n          \"interval\": \"\",\n          \"legendFormat\": \"Number of peers aggregated\",\n          \"range\": true,\n          \"refId\": \"D\"\n        }\n      ],\n      \"title\": \"Memory reservations per peer, aggregated across all instances\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 112\n      },\n      \"id\": 62,\n      \"panels\": [],\n      \"title\": \"File Descriptors\",\n      \"type\": \"row\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"description\": \"As reported to the resource manager\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 113\n      },\n      \"id\": 60,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"exemplar\": true,\n          \"expr\": \"libp2p_rcmgr_fds{instance=~\\\"$instance\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"{{scope}} {{instance}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"FDs in use\",\n      \"type\": \"timeseries\"\n    }\n  ],\n  \"refresh\": false,\n  \"schemaVersion\": 38,\n  \"style\": \"dark\",\n  \"tags\": [],\n  \"templating\": {\n    \"list\": [\n      {\n        \"hide\": 0,\n        \"label\": \"datasource\",\n        \"name\": \"DS_PROMETHEUS\",\n        \"options\": [],\n        \"query\": \"prometheus\",\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"type\": \"datasource\"\n      },\n      {\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${DS_PROMETHEUS}\"\n        },\n        \"definition\": \"label_values(up, instance)\",\n        \"hide\": 0,\n        \"includeAll\": true,\n        \"multi\": true,\n        \"name\": \"instance\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(up, instance)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"type\": \"query\"\n      }\n    ]\n  },\n  \"time\": {\n    \"from\": \"now-5m\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {},\n  \"timezone\": \"\",\n  \"title\": \"go-libp2p Resource Manager\",\n  \"uid\": \"MgmGIjjnj\",\n  \"version\": 1,\n  \"weekStart\": \"\"\n}\n"
  },
  {
    "path": "dashboards/swarm/swarm.json",
    "content": "{\n  \"__inputs\": [\n    {\n      \"name\": \"DS_PROMETHEUS\",\n      \"label\": \"Prometheus\",\n      \"description\": \"\",\n      \"type\": \"datasource\",\n      \"pluginId\": \"prometheus\",\n      \"pluginName\": \"Prometheus\"\n    }\n  ],\n  \"__elements\": {},\n  \"__requires\": [\n    {\n      \"type\": \"panel\",\n      \"id\": \"gauge\",\n      \"name\": \"Gauge\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"grafana\",\n      \"id\": \"grafana\",\n      \"name\": \"Grafana\",\n      \"version\": \"9.3.6\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"piechart\",\n      \"name\": \"Pie chart\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"datasource\",\n      \"id\": \"prometheus\",\n      \"name\": \"Prometheus\",\n      \"version\": \"1.0.0\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"stat\",\n      \"name\": \"Stat\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"state-timeline\",\n      \"name\": \"State timeline\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"timeseries\",\n      \"name\": \"Time series\",\n      \"version\": \"\"\n    }\n  ],\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": {\n          \"type\": \"grafana\",\n          \"uid\": \"-- Grafana --\"\n        },\n        \"enable\": true,\n        \"hide\": true,\n        \"iconColor\": \"rgba(0, 211, 255, 1)\",\n        \"name\": \"Annotations & Alerts\",\n        \"target\": {\n          \"limit\": 100,\n          \"matchAny\": false,\n          \"tags\": [],\n          \"type\": \"dashboard\"\n        },\n        \"type\": \"dashboard\"\n      },\n      {\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${DS_PROMETHEUS}\"\n        },\n        \"enable\": true,\n        \"iconColor\": \"red\",\n        \"name\": \"New annotation\"\n      }\n    ]\n  },\n  \"editable\": true,\n  \"fiscalYearStartMonth\": 0,\n  \"graphTooltip\": 0,\n  \"id\": null,\n  \"links\": [],\n  \"liveNow\": false,\n  \"panels\": [\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 21,\n      \"panels\": [],\n      \"title\": \"Currently Established Connections\",\n      \"type\": \"row\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"fixed\"\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"percentage\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 50\n              }\n            ]\n          }\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"quic\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"super-light-blue\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"quic-v1\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"dark-blue\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"webtransport\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"purple\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"tcp\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"yellow\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"websocket\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"p2p-circuit\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"orange\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 1\n      },\n      \"id\": 23,\n      \"options\": {\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\"lastNotNull\"],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showThresholdLabels\": false,\n        \"showThresholdMarkers\": true\n      },\n      \"pluginVersion\": \"9.3.6\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"sum by (transport) (libp2p_swarm_connections_opened_total{instance=~\\\"$instance\\\"}) - sum by (transport) (libp2p_swarm_connections_closed_total{instance=~\\\"$instance\\\"})\",\n          \"legendFormat\": \"__auto\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Active Connections\",\n      \"type\": \"gauge\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"quic  \"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"super-light-blue\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"quic-v1  \"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"dark-blue\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"webtransport  \"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"purple\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"tcp /noise /yamux/1.0.0\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"yellow\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"tcp /tls/1.0.0 /yamux/1.0.0\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"red\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"tcp /noise /mplex/6.7.0\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"super-light-yellow\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"websocket /noise /yamux/1.0.0\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"p2p-circuit  \"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"orange\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"tcp /tls/1.0.0 /mplex/6.7.0\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"super-light-red\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 9\n      },\n      \"id\": 10,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"sum by (transport, security, muxer) (libp2p_swarm_connections_opened_total{dir=\\\"inbound\\\",instance=~\\\"$instance\\\"}) - sum by (transport, security, muxer) (libp2p_swarm_connections_closed_total{dir=\\\"inbound\\\",instance=~\\\"$instance\\\"})\",\n          \"legendFormat\": \"{{transport}} {{security}} {{muxer}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Active Connections: Inbound\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"quic  \"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"super-light-blue\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"quic-v1  \"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"dark-blue\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"webtransport  \"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"purple\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"tcp /noise /yamux/1.0.0\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"yellow\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"tcp /tls/1.0.0 /yamux/1.0.0\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"red\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"tcp /noise /mplex/6.7.0\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"super-light-yellow\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"websocket /noise /yamux/1.0.0\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"p2p-circuit  \"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"orange\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"tcp /tls/1.0.0 /mplex/6.7.0\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"super-light-red\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 9\n      },\n      \"id\": 11,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"sum by (transport, security, muxer)(libp2p_swarm_connections_opened_total{dir=\\\"outbound\\\",instance=~\\\"$instance\\\"}) - sum by (transport, security, muxer) (libp2p_swarm_connections_closed_total{dir=\\\"outbound\\\",instance=~\\\"$instance\\\"})\",\n          \"legendFormat\": \"{{transport}} {{security}} {{muxer}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Active Connections: Outgoing\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"s\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 17\n      },\n      \"id\": 29,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"histogram_quantile(0.50, sum by(le) (rate(libp2p_swarm_handshake_latency_seconds_bucket{transport=~\\\"quic|quic-v1\\\",instance=~\\\"$instance\\\"}[$__rate_interval])))\",\n          \"hide\": false,\n          \"legendFormat\": \"50th percentile\",\n          \"range\": true,\n          \"refId\": \"A\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"histogram_quantile(0.90, sum by(le) (rate(libp2p_swarm_handshake_latency_seconds_bucket{transport=~\\\"quic|quic-v1\\\",instance=~\\\"$instance\\\"}[$__rate_interval])))\",\n          \"hide\": false,\n          \"legendFormat\": \"90th percentile\",\n          \"range\": true,\n          \"refId\": \"B\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"histogram_quantile(0.95, sum by(le) (rate(libp2p_swarm_handshake_latency_seconds_bucket{transport=~\\\"quic|quic-v1\\\",instance=~\\\"$instance\\\"}[$__rate_interval])))\",\n          \"hide\": false,\n          \"legendFormat\": \"95th percentile\",\n          \"range\": true,\n          \"refId\": \"C\"\n        }\n      ],\n      \"title\": \"Handshake Latency (QUIC, QUIC v1)\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"s\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 17\n      },\n      \"id\": 30,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"histogram_quantile(0.50, sum by(le) (rate(libp2p_swarm_handshake_latency_seconds_bucket{transport=\\\"tcp\\\",instance=~\\\"$instance\\\"}[$__rate_interval])))\",\n          \"hide\": false,\n          \"legendFormat\": \"50th percentile\",\n          \"range\": true,\n          \"refId\": \"A\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"histogram_quantile(0.90, sum by(le) (rate(libp2p_swarm_handshake_latency_seconds_bucket{transport=\\\"tcp\\\",instance=~\\\"$instance\\\"}[$__rate_interval])))\",\n          \"hide\": false,\n          \"legendFormat\": \"90th percentile\",\n          \"range\": true,\n          \"refId\": \"B\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"histogram_quantile(0.95, sum by(le) (rate(libp2p_swarm_handshake_latency_seconds_bucket{transport=\\\"tcp\\\",instance=~\\\"$instance\\\"}[$__rate_interval])))\",\n          \"hide\": false,\n          \"legendFormat\": \"95th percentile\",\n          \"range\": true,\n          \"refId\": \"C\"\n        }\n      ],\n      \"title\": \"Handshake Latency (TCP)\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineStyle\": {\n              \"fill\": \"solid\"\n            },\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"log\": 2,\n              \"type\": \"log\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"s\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 25\n      },\n      \"id\": 27,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"exemplar\": false,\n          \"expr\": \"histogram_quantile(0.5, sum(rate(libp2p_swarm_connection_duration_seconds_bucket{instance=~\\\"$instance\\\"}[$__rate_interval])) by (le))\",\n          \"instant\": false,\n          \"legendFormat\": \"50th percentile\",\n          \"range\": true,\n          \"refId\": \"A\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"histogram_quantile(0.9, sum(rate(libp2p_swarm_connection_duration_seconds_bucket{instance=~\\\"$instance\\\"}[$__rate_interval])) by (le))\",\n          \"hide\": false,\n          \"legendFormat\": \"90th percentile\",\n          \"range\": true,\n          \"refId\": \"B\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"histogram_quantile(0.95, sum(rate(libp2p_swarm_connection_duration_seconds_bucket{instance=~\\\"$instance\\\"}[$__rate_interval])) by (le))\",\n          \"hide\": false,\n          \"legendFormat\": \"95th percentile\",\n          \"range\": true,\n          \"refId\": \"C\"\n        }\n      ],\n      \"title\": \"Connection Duration\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"description\": \"\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            }\n          },\n          \"mappings\": []\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"regular\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"yellow\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"early muxer\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"green\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 25\n      },\n      \"id\": 36,\n      \"options\": {\n        \"legend\": {\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"pieType\": \"pie\",\n        \"reduceOptions\": {\n          \"calcs\": [\"lastNotNull\"],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"sum(libp2p_swarm_connections_opened_total{transport=\\\"tcp\\\",early_muxer=\\\"true\\\",instance=~\\\"$instance\\\"} - libp2p_swarm_connections_closed_total{transport=\\\"tcp\\\",early_muxer=\\\"true\\\",instance=~\\\"$instance\\\"})\",\n          \"legendFormat\": \"early muxer\",\n          \"range\": true,\n          \"refId\": \"A\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"sum(libp2p_swarm_connections_opened_total{transport=\\\"tcp\\\",early_muxer=\\\"false\\\",instance=~\\\"$instance\\\"} - libp2p_swarm_connections_closed_total{transport=\\\"tcp\\\",early_muxer=\\\"false\\\",instance=~\\\"$instance\\\"})\",\n          \"hide\": false,\n          \"legendFormat\": \"regular\",\n          \"range\": true,\n          \"refId\": \"B\"\n        }\n      ],\n      \"title\": \"Active Connections: Early Muxer Negotiation\",\n      \"type\": \"piechart\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 33\n      },\n      \"id\": 19,\n      \"panels\": [],\n      \"title\": \"Connection Establishment\",\n      \"type\": \"row\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"quic  \"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"super-light-blue\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"quic-v1  \"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"dark-blue\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"webtransport  \"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"purple\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"tcp /noise /yamux/1.0.0\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"yellow\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"tcp /tls/1.0.0 /yamux/1.0.0\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"red\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"tcp /noise /mplex/6.7.0\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"super-light-yellow\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"websocket /noise /yamux/1.0.0\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"p2p-circuit  \"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"orange\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"tcp /tls/1.0.0 /mplex/6.7.0\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"super-light-red\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 9,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 34\n      },\n      \"id\": 2,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"sum (rate(libp2p_swarm_connections_opened_total{dir=\\\"inbound\\\",instance=~\\\"$instance\\\"}[$__rate_interval])) by (transport, security, muxer)\",\n          \"legendFormat\": \"{{transport}} {{security}} {{muxer}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"New Connections: Inbound\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"quic  \"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"super-light-blue\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"quic-v1  \"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"dark-blue\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"webtransport  \"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"purple\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"tcp /noise /yamux/1.0.0\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"yellow\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"tcp /tls/1.0.0 /yamux/1.0.0\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"red\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"tcp /noise /mplex/6.7.0\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"super-light-yellow\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"websocket /noise /yamux/1.0.0\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"p2p-circuit  \"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"orange\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"tcp /tls/1.0.0 /mplex/6.7.0\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"super-light-red\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 9,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 34\n      },\n      \"id\": 5,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"sum (rate(libp2p_swarm_connections_opened_total{dir=\\\"outbound\\\",instance=~\\\"$instance\\\"}[$__rate_interval])) by (transport, security, muxer)\",\n          \"legendFormat\": \"{{transport}} {{security}} {{muxer}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"New Connections: Outgoing\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            }\n          },\n          \"mappings\": []\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"quic  \"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"super-light-blue\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"quic-v1  \"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"dark-blue\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"webtransport  \"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"purple\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"tcp /noise /yamux/1.0.0\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"yellow\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"tcp /tls/1.0.0 /yamux/1.0.0\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"red\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"tcp /noise /mplex/6.7.0\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"super-light-yellow\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"websocket /noise /yamux/1.0.0\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"p2p-circuit  \"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"orange\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"tcp /tls/1.0.0 /mplex/6.7.0\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"super-light-red\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 43\n      },\n      \"id\": 4,\n      \"options\": {\n        \"legend\": {\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"pieType\": \"pie\",\n        \"reduceOptions\": {\n          \"calcs\": [\"lastNotNull\"],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"tooltip\": {\n          \"mode\": \"multi\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"sum (increase(libp2p_swarm_connections_opened_total{dir=\\\"inbound\\\",instance=~\\\"$instance\\\"}[$__range])) by (transport, security, muxer)\",\n          \"legendFormat\": \"{{transport}} {{security}} {{muxer}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"New Inbound Connections: Transports / Security / Muxers\",\n      \"type\": \"piechart\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            }\n          },\n          \"mappings\": [],\n          \"unit\": \"none\"\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"quic  \"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"super-light-blue\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"quic-v1  \"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"dark-blue\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"webtransport  \"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"purple\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"tcp /noise /yamux/1.0.0\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"yellow\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"tcp /tls/1.0.0 /yamux/1.0.0\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"red\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"tcp /noise /mplex/6.7.0\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"super-light-yellow\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"websocket /noise /yamux/1.0.0\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"p2p-circuit  \"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"orange\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"tcp /tls/1.0.0 /mplex/6.7.0\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"super-light-red\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 43\n      },\n      \"id\": 6,\n      \"options\": {\n        \"legend\": {\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true,\n          \"values\": []\n        },\n        \"pieType\": \"pie\",\n        \"reduceOptions\": {\n          \"calcs\": [\"lastNotNull\"],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"tooltip\": {\n          \"mode\": \"multi\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"sum (increase(libp2p_swarm_connections_opened_total{dir=\\\"outbound\\\",instance=~\\\"$instance\\\"}[$__range])) by (transport, security, muxer)\",\n          \"legendFormat\": \"{{transport}} {{security}} {{muxer}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"New Outgoing Connections: Transports / Security / Muxers\",\n      \"type\": \"piechart\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            }\n          },\n          \"mappings\": []\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"ip4\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"light-blue\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"ip6\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"super-light-purple\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 51\n      },\n      \"id\": 32,\n      \"options\": {\n        \"legend\": {\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"pieType\": \"pie\",\n        \"reduceOptions\": {\n          \"calcs\": [\"lastNotNull\"],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"sum by (ip_version) (libp2p_swarm_connections_opened_total{dir=\\\"inbound\\\",instance=~\\\"$instance\\\"}) - sum by (ip_version) (libp2p_swarm_connections_closed_total{dir=\\\"inbound\\\",instance=~\\\"$instance\\\"})\",\n          \"legendFormat\": \"{{ip_version}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"New Inbound Connections: IP Version\",\n      \"type\": \"piechart\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"description\": \"\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            }\n          },\n          \"mappings\": []\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"ip6\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"super-light-purple\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"ip4\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"light-blue\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 51\n      },\n      \"id\": 34,\n      \"options\": {\n        \"legend\": {\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"pieType\": \"pie\",\n        \"reduceOptions\": {\n          \"calcs\": [\"lastNotNull\"],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"sum by (ip_version) (libp2p_swarm_connections_opened_total{dir=\\\"outbound\\\",instance=~\\\"$instance\\\"}) - sum by (ip_version) (libp2p_swarm_connections_closed_total{dir=\\\"outbound\\\",instance=~\\\"$instance\\\"})\",\n          \"legendFormat\": \"__auto\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"New Outbound Connections: IP Version\",\n      \"type\": \"piechart\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            }\n          },\n          \"mappings\": []\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"canceled: concurrent dial successful\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"super-light-blue\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"application canceled\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"blue\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"canceled: other\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"dark-blue\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"timeout\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"orange\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"other\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"red\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"deadline\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"super-light-orange\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"connection refused\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"green\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 59\n      },\n      \"id\": 15,\n      \"options\": {\n        \"displayLabels\": [],\n        \"legend\": {\n          \"displayMode\": \"table\",\n          \"placement\": \"right\",\n          \"showLegend\": true,\n          \"values\": [\"percent\"]\n        },\n        \"pieType\": \"donut\",\n        \"reduceOptions\": {\n          \"calcs\": [\"lastNotNull\"],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"pluginVersion\": \"9.3.2-45365\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"sum(increase(libp2p_swarm_dial_errors_total{instance=~\\\"$instance\\\"}[$__range])) by (error)\",\n          \"legendFormat\": \"__auto\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Dial Errors\",\n      \"type\": \"piechart\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"percentunit\"\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"quic\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"super-light-blue\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"quic-v1\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"dark-blue\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"webtransport\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"purple\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"tcp\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"yellow\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"websocket\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"p2p-circuit\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"orange\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 59\n      },\n      \"id\": 17,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"pluginVersion\": \"9.3.2-67a213dc85\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"sum by (transport) (rate(libp2p_swarm_connections_opened_total{dir=\\\"outbound\\\",instance=~\\\"$instance\\\"}[$__rate_interval])) \",\n          \"hide\": true,\n          \"legendFormat\": \"{{transport}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"sum by(transport, error) (rate(libp2p_swarm_dial_errors_total{instance=~\\\"$instance\\\"}[$__rate_interval]))\",\n          \"hide\": true,\n          \"legendFormat\": \"dial error ({{error}}, {{transport}})\",\n          \"range\": true,\n          \"refId\": \"B\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"sum(rate(libp2p_swarm_connections_opened_total{dir=\\\"outbound\\\",instance=~\\\"$instance\\\"}[$__rate_interval])) by (transport)  / (sum(rate(libp2p_swarm_connections_opened_total{dir=\\\"outbound\\\",instance=~\\\"$instance\\\"}[$__rate_interval])) by (transport) + (sum(rate(libp2p_swarm_dial_errors_total{instance=~\\\"$instance\\\"}[$__rate_interval])) by (transport)))\",\n          \"hide\": false,\n          \"legendFormat\": \"__auto\",\n          \"range\": true,\n          \"refId\": \"C\"\n        }\n      ],\n      \"title\": \"Dial Success Rates\",\n      \"type\": \"timeseries\"\n    },    \n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisBorderShow\": false,\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"barWidthFactor\": 0.6,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"insertNulls\": false,\n            \"lineInterpolation\": \"linear\",\n            \"lineStyle\": {\n              \"fill\": \"solid\"\n            },\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"log\": 2,\n              \"type\": \"log\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              }\n            ]\n          },\n          \"unit\": \"s\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 67\n      },\n      \"id\": 50,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"exemplar\": false,\n          \"expr\": \"histogram_quantile(0.5, sum(rate(libp2p_swarm_dial_latency_seconds_bucket{outcome=\\\"success\\\", instance=~\\\"$instance\\\"}[$__rate_interval])) by (le))\",\n          \"instant\": false,\n          \"legendFormat\": \"50th percentile\",\n          \"range\": true,\n          \"refId\": \"A\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"histogram_quantile(0.9, sum(rate(libp2p_swarm_dial_latency_seconds_bucket{outcome=\\\"success\\\", instance=~\\\"$instance\\\"}[$__rate_interval])) by (le))\",\n          \"hide\": false,\n          \"legendFormat\": \"90th percentile\",\n          \"range\": true,\n          \"refId\": \"B\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"histogram_quantile(0.95, sum(rate(libp2p_swarm_dial_latency_seconds_bucket{outcome=\\\"success\\\", instance=~\\\"$instance\\\"}[$__rate_interval])) by (le))\",\n          \"hide\": false,\n          \"legendFormat\": \"95th percentile\",\n          \"range\": true,\n          \"refId\": \"C\"\n        }\n      ],\n      \"title\": \"Peer Dial Latency (Seconds)\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"description\": \"on newly established connections\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            }\n          },\n          \"mappings\": []\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 67\n      },\n      \"id\": 25,\n      \"options\": {\n        \"legend\": {\n          \"displayMode\": \"list\",\n          \"placement\": \"right\",\n          \"showLegend\": true\n        },\n        \"pieType\": \"pie\",\n        \"reduceOptions\": {\n          \"calcs\": [\"lastNotNull\"],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"tooltip\": {\n          \"mode\": \"multi\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"sum by (key_type) (increase(libp2p_swarm_key_types_total{instance=~\\\"$instance\\\"}[$__range]))\",\n          \"legendFormat\": \"{{key_type}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"libp2p key types\",\n      \"type\": \"piechart\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 75\n      },\n      \"id\": 40,\n      \"panels\": [],\n      \"title\": \"Dial Prioritisation\",\n      \"type\": \"row\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            }\n          },\n          \"mappings\": [],\n          \"unit\": \"none\"\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"<=300ms\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"purple\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"<=500ms\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"red\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"<=750ms\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"light-red\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"<=50ms\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"dark-blue\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"<=10ms\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"blue\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 76\n      },\n      \"id\": 38,\n      \"options\": {\n        \"displayLabels\": [\"percent\"],\n        \"legend\": {\n          \"displayMode\": \"table\",\n          \"placement\": \"right\",\n          \"showLegend\": true,\n          \"values\": [\"percent\"]\n        },\n        \"pieType\": \"donut\",\n        \"reduceOptions\": {\n          \"calcs\": [\"lastNotNull\"],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"exemplar\": false,\n          \"expr\": \"sum(increase(libp2p_swarm_dial_ranking_delay_seconds_bucket{instance=~\\\"$instance\\\",le=\\\"0.001\\\"}[$__range]))\",\n          \"format\": \"time_series\",\n          \"instant\": false,\n          \"legendFormat\": \"No delay\",\n          \"range\": true,\n          \"refId\": \"A\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"exemplar\": false,\n          \"expr\": \"sum(increase(libp2p_swarm_dial_ranking_delay_seconds_bucket{instance=~\\\"$instance\\\",le=\\\"0.01\\\"}[$__range])) - ignoring(le) sum(increase(libp2p_swarm_dial_ranking_delay_seconds_bucket{instance=~\\\"$instance\\\",le=\\\"0.001\\\"}[$__range]))\",\n          \"hide\": false,\n          \"instant\": false,\n          \"legendFormat\": \"<=10ms\",\n          \"range\": true,\n          \"refId\": \"B\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"sum(increase(libp2p_swarm_dial_ranking_delay_seconds_bucket{instance=~\\\"$instance\\\",le=\\\"0.05\\\"}[$__range])) - ignoring(le) sum(increase(libp2p_swarm_dial_ranking_delay_seconds_bucket{instance=~\\\"$instance\\\",le=\\\"0.01\\\"}[$__range]))\",\n          \"hide\": false,\n          \"legendFormat\": \"<=50ms\",\n          \"range\": true,\n          \"refId\": \"F\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"sum(increase(libp2p_swarm_dial_ranking_delay_seconds_bucket{instance=~\\\"$instance\\\",le=\\\"0.3\\\"}[$__range])) - ignoring(le) sum(increase(libp2p_swarm_dial_ranking_delay_seconds_bucket{instance=~\\\"$instance\\\",le=\\\"0.05\\\"}[$__range]))\",\n          \"hide\": false,\n          \"legendFormat\": \"<=300ms\",\n          \"range\": true,\n          \"refId\": \"D\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"sum(increase(libp2p_swarm_dial_ranking_delay_seconds_bucket{instance=~\\\"$instance\\\",le=\\\"0.5\\\"}[$__range])) - ignoring(le) sum(increase(libp2p_swarm_dial_ranking_delay_seconds_bucket{instance=~\\\"$instance\\\",le=\\\"0.3\\\"}[$__range]))\",\n          \"hide\": false,\n          \"legendFormat\": \"<=500ms\",\n          \"range\": true,\n          \"refId\": \"E\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"sum(increase(libp2p_swarm_dial_ranking_delay_seconds_bucket{instance=~\\\"$instance\\\",le=\\\"0.75\\\"}[$__range])) - ignoring(le) sum(increase(libp2p_swarm_dial_ranking_delay_seconds_bucket{instance=~\\\"$instance\\\",le=\\\"0.5\\\"}[$__range]))\",\n          \"hide\": false,\n          \"legendFormat\": \"<=750ms\",\n          \"range\": true,\n          \"refId\": \"G\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"sum(increase(libp2p_swarm_dial_ranking_delay_seconds_bucket{instance=~\\\"$instance\\\",le=\\\"+Inf\\\"}[$__range])) - ignoring(le) sum(increase(libp2p_swarm_dial_ranking_delay_seconds_bucket{instance=~\\\"$instance\\\",le=\\\"0.75\\\"}[$__range]))\",\n          \"hide\": false,\n          \"legendFormat\": \">750ms\",\n          \"range\": true,\n          \"refId\": \"H\"\n        }\n      ],\n      \"title\": \"Dial Ranking Delay\",\n      \"transformations\": [],\n      \"type\": \"piechart\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            }\n          },\n          \"mappings\": []\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \">=6\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"light-red\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"5\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"dark-red\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"2\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"purple\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"1\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"green\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"3\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"orange\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          },\n          {\n            \"matcher\": {\n              \"id\": \"byName\",\n              \"options\": \"4\"\n            },\n            \"properties\": [\n              {\n                \"id\": \"color\",\n                \"value\": {\n                  \"fixedColor\": \"dark-orange\",\n                  \"mode\": \"fixed\"\n                }\n              }\n            ]\n          }\n        ]\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 76\n      },\n      \"id\": 42,\n      \"options\": {\n        \"displayLabels\": [\"percent\", \"name\"],\n        \"legend\": {\n          \"displayMode\": \"table\",\n          \"placement\": \"right\",\n          \"showLegend\": true,\n          \"values\": [\"percent\"]\n        },\n        \"pieType\": \"donut\",\n        \"reduceOptions\": {\n          \"calcs\": [\"lastNotNull\"],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"increase(libp2p_swarm_dials_per_peer_total{instance=~\\\"$instance\\\", outcome=\\\"success\\\", num_dials=\\\"0\\\"}[$__range])\",\n          \"legendFormat\": \"0\",\n          \"range\": true,\n          \"refId\": \"A\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"increase(libp2p_swarm_dials_per_peer_total{instance=~\\\"$instance\\\", outcome=\\\"success\\\", num_dials=\\\"1\\\"}[$__range])\",\n          \"hide\": false,\n          \"legendFormat\": \"1\",\n          \"range\": true,\n          \"refId\": \"B\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"increase(libp2p_swarm_dials_per_peer_total{instance=~\\\"$instance\\\", outcome=\\\"success\\\", num_dials=\\\"2\\\"}[$__range])\",\n          \"hide\": false,\n          \"legendFormat\": \"2\",\n          \"range\": true,\n          \"refId\": \"C\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"increase(libp2p_swarm_dials_per_peer_total{instance=~\\\"$instance\\\", outcome=\\\"success\\\", num_dials=\\\"3\\\"}[$__range])\",\n          \"hide\": false,\n          \"legendFormat\": \"3\",\n          \"range\": true,\n          \"refId\": \"D\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"increase(libp2p_swarm_dials_per_peer_total{instance=~\\\"$instance\\\", outcome=\\\"success\\\", num_dials=\\\"4\\\"}[$__range])\",\n          \"hide\": false,\n          \"legendFormat\": \"4\",\n          \"range\": true,\n          \"refId\": \"E\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"increase(libp2p_swarm_dials_per_peer_total{instance=~\\\"$instance\\\", outcome=\\\"success\\\", num_dials=\\\"5\\\"}[$__range])\",\n          \"hide\": false,\n          \"legendFormat\": \"5\",\n          \"range\": true,\n          \"refId\": \"F\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"increase(libp2p_swarm_dials_per_peer_total{instance=~\\\"$instance\\\", outcome=\\\"success\\\", num_dials=\\\">=6\\\"}[$__range])\",\n          \"hide\": false,\n          \"legendFormat\": \">=6\",\n          \"range\": true,\n          \"refId\": \"G\"\n        }\n      ],\n      \"title\": \"Dials per connection\",\n      \"type\": \"piechart\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 84\n      },\n      \"id\": 44,\n      \"panels\": [],\n      \"title\": \"Black Hole Detection\",\n      \"type\": \"row\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"fixed\"\n          },\n          \"custom\": {\n            \"fillOpacity\": 76,\n            \"lineWidth\": 0,\n            \"spanNulls\": true\n          },\n          \"mappings\": [\n            {\n              \"options\": {\n                \"0\": {\n                  \"color\": \"blue\",\n                  \"index\": 0,\n                  \"text\": \"Probing\"\n                },\n                \"1\": {\n                  \"color\": \"green\",\n                  \"index\": 1,\n                  \"text\": \"Allowed\"\n                },\n                \"2\": {\n                  \"color\": \"purple\",\n                  \"index\": 2,\n                  \"text\": \"Blocked\"\n                }\n              },\n              \"type\": \"value\"\n            }\n          ],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 6,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 85\n      },\n      \"id\": 46,\n      \"options\": {\n        \"alignValue\": \"center\",\n        \"legend\": {\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"mergeValues\": true,\n        \"rowHeight\": 0.9,\n        \"showValue\": \"always\",\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"libp2p_swarm_black_hole_filter_state{instance=~\\\"$instance\\\"}\",\n          \"legendFormat\": \"{{instance}} {{name}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Black Hole Filter State\",\n      \"type\": \"state-timeline\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"fixedColor\": \"purple\",\n            \"mode\": \"fixed\"\n          },\n          \"mappings\": [\n            {\n              \"options\": {\n                \"0\": {\n                  \"index\": 0,\n                  \"text\": \"-\"\n                }\n              },\n              \"type\": \"value\"\n            }\n          ],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 91\n      },\n      \"id\": 49,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"none\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"horizontal\",\n        \"reduceOptions\": {\n          \"calcs\": [\"lastNotNull\"],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"textMode\": \"value_and_name\"\n      },\n      \"pluginVersion\": \"10.0.1\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"libp2p_swarm_black_hole_filter_next_request_allowed_after{instance=~\\\"$instance\\\"}\",\n          \"legendFormat\": \"{{instance}}: {{name}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Black Hole Filter Requests Till Next Probe\",\n      \"type\": \"stat\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"mappings\": [],\n          \"max\": 100,\n          \"min\": 0,\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"purple\",\n                \"value\": null\n              },\n              {\n                \"color\": \"green\",\n                \"value\": 5\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 91\n      },\n      \"id\": 47,\n      \"options\": {\n        \"orientation\": \"vertical\",\n        \"reduceOptions\": {\n          \"calcs\": [\"lastNotNull\"],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showThresholdLabels\": false,\n        \"showThresholdMarkers\": true\n      },\n      \"pluginVersion\": \"10.0.1\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"exemplar\": false,\n          \"expr\": \"libp2p_swarm_black_hole_filter_success_fraction{instance=~\\\"$instance\\\"} * 100\",\n          \"instant\": true,\n          \"legendFormat\": \"{{instance}} {{name}}\",\n          \"range\": false,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Dial Success Rate\",\n      \"type\": \"gauge\"\n    }\n  ],\n  \"refresh\": \"\",\n  \"schemaVersion\": 38,\n  \"style\": \"dark\",\n  \"tags\": [],\n  \"templating\": {\n    \"list\": [\n      {\n        \"hide\": 0,\n        \"label\": \"datasource\",\n        \"name\": \"DS_PROMETHEUS\",\n        \"options\": [],\n        \"query\": \"prometheus\",\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"type\": \"datasource\"\n      },\n      {\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${DS_PROMETHEUS}\"\n        },\n        \"definition\": \"label_values(up, instance)\",\n        \"hide\": 0,\n        \"includeAll\": true,\n        \"multi\": true,\n        \"name\": \"instance\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(up, instance)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"type\": \"query\"\n      }\n    ]\n  },\n  \"time\": {\n    \"from\": \"now-15m\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {},\n  \"timezone\": \"\",\n  \"title\": \"go-libp2p Swarm\",\n  \"uid\": \"a15PyhO4z\",\n  \"version\": 4,\n  \"weekStart\": \"\"\n}\n"
  },
  {
    "path": "defaults.go",
    "content": "package libp2p\n\n// This file contains all the default configuration options.\n\nimport (\n\t\"crypto/rand\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoremem\"\n\trcmgr \"github.com/libp2p/go-libp2p/p2p/host/resource-manager\"\n\t\"github.com/libp2p/go-libp2p/p2p/muxer/yamux\"\n\t\"github.com/libp2p/go-libp2p/p2p/net/connmgr\"\n\t\"github.com/libp2p/go-libp2p/p2p/net/swarm\"\n\t\"github.com/libp2p/go-libp2p/p2p/security/noise\"\n\ttls \"github.com/libp2p/go-libp2p/p2p/security/tls\"\n\tquic \"github.com/libp2p/go-libp2p/p2p/transport/quic\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/tcp\"\n\tlibp2pwebrtc \"github.com/libp2p/go-libp2p/p2p/transport/webrtc\"\n\tws \"github.com/libp2p/go-libp2p/p2p/transport/websocket\"\n\twebtransport \"github.com/libp2p/go-libp2p/p2p/transport/webtransport\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\n\t\"github.com/multiformats/go-multiaddr\"\n)\n\n// DefaultSecurity is the default security option.\n//\n// Useful when you want to extend, but not replace, the supported transport\n// security protocols.\nvar DefaultSecurity = ChainOptions(\n\tSecurity(tls.ID, tls.New),\n\tSecurity(noise.ID, noise.New),\n)\n\n// DefaultMuxers configures libp2p to use the stream connection multiplexers.\n//\n// Use this option when you want to *extend* the set of multiplexers used by\n// libp2p instead of replacing them.\nvar DefaultMuxers = Muxer(yamux.ID, yamux.DefaultTransport)\n\n// DefaultTransports are the default libp2p transports.\n//\n// Use this option when you want to *extend* the set of transports used by\n// libp2p instead of replacing them.\nvar DefaultTransports = ChainOptions(\n\tTransport(tcp.NewTCPTransport),\n\tTransport(quic.NewTransport),\n\tTransport(ws.New),\n\tTransport(webtransport.New),\n\tTransport(libp2pwebrtc.New),\n)\n\n// DefaultPrivateTransports are the default libp2p transports when a PSK is supplied.\n//\n// Use this option when you want to *extend* the set of transports used by\n// libp2p instead of replacing them.\nvar DefaultPrivateTransports = ChainOptions(\n\tTransport(tcp.NewTCPTransport),\n\tTransport(ws.New),\n)\n\n// DefaultPeerstore configures libp2p to use the default peerstore.\nvar DefaultPeerstore Option = func(cfg *Config) error {\n\tps, err := pstoremem.NewPeerstore()\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn cfg.Apply(Peerstore(ps))\n}\n\n// RandomIdentity generates a random identity. (default behaviour)\nvar RandomIdentity = func(cfg *Config) error {\n\tpriv, _, err := crypto.GenerateEd25519Key(rand.Reader)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn cfg.Apply(Identity(priv))\n}\n\n// DefaultListenAddrs configures libp2p to use default listen address.\nvar DefaultListenAddrs = func(cfg *Config) error {\n\taddrs := []string{\n\t\t\"/ip4/0.0.0.0/tcp/0\",\n\t\t\"/ip4/0.0.0.0/udp/0/quic-v1\",\n\t\t\"/ip4/0.0.0.0/udp/0/quic-v1/webtransport\",\n\t\t\"/ip4/0.0.0.0/udp/0/webrtc-direct\",\n\t\t\"/ip6/::/tcp/0\",\n\t\t\"/ip6/::/udp/0/quic-v1\",\n\t\t\"/ip6/::/udp/0/quic-v1/webtransport\",\n\t\t\"/ip6/::/udp/0/webrtc-direct\",\n\t}\n\tlistenAddrs := make([]multiaddr.Multiaddr, 0, len(addrs))\n\tfor _, s := range addrs {\n\t\taddr, err := multiaddr.NewMultiaddr(s)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tlistenAddrs = append(listenAddrs, addr)\n\t}\n\treturn cfg.Apply(ListenAddrs(listenAddrs...))\n}\n\n// DefaultEnableRelay enables relay dialing and listening by default.\nvar DefaultEnableRelay = func(cfg *Config) error {\n\treturn cfg.Apply(EnableRelay())\n}\n\nvar DefaultResourceManager = func(cfg *Config) error {\n\t// Default memory limit: 1/8th of total memory, minimum 128MB, maximum 1GB\n\tlimits := rcmgr.DefaultLimits\n\tSetDefaultServiceLimits(&limits)\n\tmgr, err := rcmgr.NewResourceManager(rcmgr.NewFixedLimiter(limits.AutoScale()))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn cfg.Apply(ResourceManager(mgr))\n}\n\n// DefaultConnectionManager creates a default connection manager\nvar DefaultConnectionManager = func(cfg *Config) error {\n\tmgr, err := connmgr.NewConnManager(160, 192)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn cfg.Apply(ConnectionManager(mgr))\n}\n\n// DefaultPrometheusRegisterer configures libp2p to use the default registerer\nvar DefaultPrometheusRegisterer = func(cfg *Config) error {\n\treturn cfg.Apply(PrometheusRegisterer(prometheus.DefaultRegisterer))\n}\n\nvar defaultUDPBlackHoleDetector = func(cfg *Config) error {\n\t// A black hole is a binary property. On a network if UDP dials are blocked, all dials will\n\t// fail. So a low success rate of 5 out 100 dials is good enough.\n\treturn cfg.Apply(UDPBlackHoleSuccessCounter(&swarm.BlackHoleSuccessCounter{N: 100, MinSuccesses: 5, Name: \"UDP\"}))\n}\n\nvar defaultIPv6BlackHoleDetector = func(cfg *Config) error {\n\t// A black hole is a binary property. On a network if there is no IPv6 connectivity, all\n\t// dials will fail. So a low success rate of 5 out 100 dials is good enough.\n\treturn cfg.Apply(IPv6BlackHoleSuccessCounter(&swarm.BlackHoleSuccessCounter{N: 100, MinSuccesses: 5, Name: \"IPv6\"}))\n}\n\n// Complete list of default options and when to fallback on them.\n//\n// Please *DON'T* specify default options any other way. Putting this all here\n// makes tracking defaults *much* easier.\nvar defaults = []struct {\n\tfallback func(cfg *Config) bool\n\topt      Option\n}{\n\t{\n\t\tfallback: func(cfg *Config) bool { return cfg.Transports == nil && cfg.ListenAddrs == nil },\n\t\topt:      DefaultListenAddrs,\n\t},\n\t{\n\t\tfallback: func(cfg *Config) bool { return cfg.Transports == nil && cfg.PSK == nil },\n\t\topt:      DefaultTransports,\n\t},\n\t{\n\t\tfallback: func(cfg *Config) bool { return cfg.Transports == nil && cfg.PSK != nil },\n\t\topt:      DefaultPrivateTransports,\n\t},\n\t{\n\t\tfallback: func(cfg *Config) bool { return cfg.Muxers == nil },\n\t\topt:      DefaultMuxers,\n\t},\n\t{\n\t\tfallback: func(cfg *Config) bool { return !cfg.Insecure && cfg.SecurityTransports == nil },\n\t\topt:      DefaultSecurity,\n\t},\n\t{\n\t\tfallback: func(cfg *Config) bool { return cfg.PeerKey == nil },\n\t\topt:      RandomIdentity,\n\t},\n\t{\n\t\tfallback: func(cfg *Config) bool { return cfg.Peerstore == nil },\n\t\topt:      DefaultPeerstore,\n\t},\n\t{\n\t\tfallback: func(cfg *Config) bool { return !cfg.RelayCustom },\n\t\topt:      DefaultEnableRelay,\n\t},\n\t{\n\t\tfallback: func(cfg *Config) bool { return cfg.ResourceManager == nil },\n\t\topt:      DefaultResourceManager,\n\t},\n\t{\n\t\tfallback: func(cfg *Config) bool { return cfg.ConnManager == nil },\n\t\topt:      DefaultConnectionManager,\n\t},\n\t{\n\t\tfallback: func(cfg *Config) bool { return !cfg.DisableMetrics && cfg.PrometheusRegisterer == nil },\n\t\topt:      DefaultPrometheusRegisterer,\n\t},\n\t{\n\t\tfallback: func(cfg *Config) bool {\n\t\t\treturn !cfg.CustomUDPBlackHoleSuccessCounter && cfg.UDPBlackHoleSuccessCounter == nil\n\t\t},\n\t\topt: defaultUDPBlackHoleDetector,\n\t},\n\t{\n\t\tfallback: func(cfg *Config) bool {\n\t\t\treturn !cfg.CustomIPv6BlackHoleSuccessCounter && cfg.IPv6BlackHoleSuccessCounter == nil\n\t\t},\n\t\topt: defaultIPv6BlackHoleDetector,\n\t},\n}\n\n// Defaults configures libp2p to use the default options. Can be combined with\n// other options to *extend* the default options.\nvar Defaults Option = func(cfg *Config) error {\n\tfor _, def := range defaults {\n\t\tif err := cfg.Apply(def.opt); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// FallbackDefaults applies default options to the libp2p node if and only if no\n// other relevant options have been applied. will be appended to the options\n// passed into New.\nvar FallbackDefaults Option = func(cfg *Config) error {\n\tfor _, def := range defaults {\n\t\tif !def.fallback(cfg) {\n\t\t\tcontinue\n\t\t}\n\t\tif err := cfg.Apply(def.opt); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "docs/flaky-tests.md",
    "content": "# Debugging Flaky Tests\n\nIf a test is flaky in CI it's probably because there's some timing issue. The\ntest probably depends on some Go routine making progress in the background and\npolling to see if the expected outcome is achieved.\n\nThis will pretty much always work locally because your local machine is likely\npretty capable and there isn't too many concurrent processes running. In CI, we\nare susceptible to both slower hardware and noisier neighbors. However we can\nmimic this environment locally with\n[cgroups](https://man7.org/linux/man-pages/man7/cgroups.7.html).\n\n# Replicating noisy neighbors\n\nWe can limit the amount of CPU time relative to real time a process gets with\ncgroups. This lets us replicate the environment where many other neighboring\nprocesses are vying for CPU time.\n\n```bash\n  # Compile some test we want to run. We do this outside the cgroup so this is\n  # fast\n  go test -c ./p2p/host/autorelay\n\n  # Create the group\n  sudo cgcreate -g cpu:/cpulimit\n\n  # Limit the time to 10,000 microseconds for every 1s\n  sudo cgset -r cpu.cfs_quota_us=10000 cpulimit\n  sudo cgset -r cpu.cfs_period_us=1000000 cpulimit\n\n  # Run a shell with in our limited environment\n  sudo cgexec -g cpu:cpulimit bash\n\n  # In the shell, run the test\n  ./autorelay.test -test.v\n```\n\n# Flakiness with coverage profile\n\nSometimes adding the `-coverprofile=module-coverage.txt` introduces flaky\nbehavior since it adds another goroutine to the mix. If you're having trouble\nreproducing a flaky test, try enabling this flag.\n\n"
  },
  {
    "path": "examples/README.md",
    "content": "# go-libp2p examples and tutorials\n\nIn this folder, you can find a variety of examples to help you get started in using go-libp2p. Every example as a specific purpose and some of each incorporate a full tutorial that you can follow through, helping you expand your knowledge about libp2p and p2p networks in general.\n\nLet us know if you find any issue or if you want to contribute and add a new tutorial, feel welcome to submit a PR, thank you!\n\n## Examples and Tutorials\n\n- [The libp2p 'host'](./libp2p-host)\n- [The libp2p 'host' with Secure WebSockets and AutoTLS](./autotls)\n- [Building an http proxy with libp2p](./http-proxy)\n- [An echo host](./echo)\n- [Routed echo host](./routed-echo/)\n- [Multicodecs with protobufs](./multipro)\n- [Relay-based P2P Communication](./relay/)\n- [P2P chat application](./chat)\n- [P2P chat application w/ rendezvous peer discovery](./chat-with-rendezvous)\n- [P2P chat application with peer discovery using mdns](./chat-with-mdns)\n- [P2P chat using pubsub](./pubsub)\n- [A chapter based approach to building a libp2p application](./ipfs-camp-2019/) _Created for [IPFS Camp 2019](https://github.com/ipfs/camp/tree/master/CORE_AND_ELECTIVE_COURSES/CORE_COURSE_B)_\n- [View metrics using Prometheus and Grafana](./metrics-and-dashboards)\n\nFor js-libp2p examples, check https://github.com/libp2p/js-libp2p-examples\n"
  },
  {
    "path": "examples/go.mod",
    "content": "module github.com/libp2p/go-libp2p/examples\n\ngo 1.24\n\nrequire (\n\tgithub.com/caddyserver/certmagic v0.21.6\n\tgithub.com/gogo/protobuf v1.3.2\n\tgithub.com/google/uuid v1.6.0\n\tgithub.com/ipfs/go-datastore v0.6.0\n\tgithub.com/ipfs/go-log/v2 v2.5.1\n\tgithub.com/ipshipyard/p2p-forge v0.5.0\n\tgithub.com/libp2p/go-libp2p v0.41.1\n\tgithub.com/libp2p/go-libp2p-kad-dht v0.28.1\n\tgithub.com/multiformats/go-multiaddr v0.15.0\n\tgithub.com/prometheus/client_golang v1.21.1\n)\n\nrequire (\n\tgithub.com/benbjohnson/clock v1.3.5 // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/caddyserver/zerossl v0.1.3 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/containerd/cgroups v1.1.0 // indirect\n\tgithub.com/coreos/go-systemd/v22 v22.5.0 // indirect\n\tgithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect\n\tgithub.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect\n\tgithub.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect\n\tgithub.com/docker/go-units v0.5.0 // indirect\n\tgithub.com/elastic/gosigar v0.14.3 // indirect\n\tgithub.com/flynn/noise v1.1.0 // indirect\n\tgithub.com/francoispqt/gojay v1.2.13 // indirect\n\tgithub.com/go-logr/logr v1.4.2 // indirect\n\tgithub.com/go-logr/stdr v1.2.2 // indirect\n\tgithub.com/go-task/slim-sprig/v3 v3.0.0 // indirect\n\tgithub.com/godbus/dbus/v5 v5.1.0 // indirect\n\tgithub.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect\n\tgithub.com/google/gopacket v1.1.19 // indirect\n\tgithub.com/google/pprof v0.0.0-20250208200701-d0013a598941 // indirect\n\tgithub.com/gorilla/websocket v1.5.3 // indirect\n\tgithub.com/hashicorp/errwrap v1.1.0 // indirect\n\tgithub.com/hashicorp/go-multierror v1.1.1 // indirect\n\tgithub.com/hashicorp/golang-lru v1.0.2 // indirect\n\tgithub.com/huin/goupnp v1.3.0 // indirect\n\tgithub.com/ipfs/boxo v0.25.0 // indirect\n\tgithub.com/ipfs/go-cid v0.5.0 // indirect\n\tgithub.com/ipld/go-ipld-prime v0.21.0 // indirect\n\tgithub.com/jackpal/go-nat-pmp v1.0.2 // indirect\n\tgithub.com/jbenet/go-temp-err-catcher v0.1.0 // indirect\n\tgithub.com/jbenet/goprocess v0.1.4 // indirect\n\tgithub.com/klauspost/compress v1.18.0 // indirect\n\tgithub.com/klauspost/cpuid/v2 v2.2.10 // indirect\n\tgithub.com/koron/go-ssdp v0.0.5 // indirect\n\tgithub.com/libdns/libdns v0.2.2 // indirect\n\tgithub.com/libp2p/go-buffer-pool v0.1.0 // indirect\n\tgithub.com/libp2p/go-cidranger v1.1.0 // indirect\n\tgithub.com/libp2p/go-flow-metrics v0.2.0 // indirect\n\tgithub.com/libp2p/go-libp2p-asn-util v0.4.1 // indirect\n\tgithub.com/libp2p/go-libp2p-kbucket v0.6.4 // indirect\n\tgithub.com/libp2p/go-libp2p-record v0.2.0 // indirect\n\tgithub.com/libp2p/go-libp2p-routing-helpers v0.7.4 // indirect\n\tgithub.com/libp2p/go-msgio v0.3.0 // indirect\n\tgithub.com/libp2p/go-netroute v0.2.2 // indirect\n\tgithub.com/libp2p/go-reuseport v0.4.0 // indirect\n\tgithub.com/libp2p/go-yamux/v5 v5.0.0 // indirect\n\tgithub.com/libp2p/zeroconf/v2 v2.2.0 // indirect\n\tgithub.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect\n\tgithub.com/mattn/go-isatty v0.0.20 // indirect\n\tgithub.com/mholt/acmez/v3 v3.0.0 // indirect\n\tgithub.com/miekg/dns v1.1.64 // indirect\n\tgithub.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect\n\tgithub.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect\n\tgithub.com/minio/sha256-simd v1.0.1 // indirect\n\tgithub.com/mr-tron/base58 v1.2.0 // indirect\n\tgithub.com/multiformats/go-base32 v0.1.0 // indirect\n\tgithub.com/multiformats/go-base36 v0.2.0 // indirect\n\tgithub.com/multiformats/go-multiaddr-dns v0.4.1 // indirect\n\tgithub.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect\n\tgithub.com/multiformats/go-multibase v0.2.0 // indirect\n\tgithub.com/multiformats/go-multicodec v0.9.0 // indirect\n\tgithub.com/multiformats/go-multihash v0.2.3 // indirect\n\tgithub.com/multiformats/go-multistream v0.6.0 // indirect\n\tgithub.com/multiformats/go-varint v0.0.7 // indirect\n\tgithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect\n\tgithub.com/onsi/ginkgo/v2 v2.22.2 // indirect\n\tgithub.com/opencontainers/runtime-spec v1.2.0 // indirect\n\tgithub.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect\n\tgithub.com/pion/datachannel v1.5.10 // indirect\n\tgithub.com/pion/dtls/v2 v2.2.12 // indirect\n\tgithub.com/pion/dtls/v3 v3.0.4 // indirect\n\tgithub.com/pion/ice/v4 v4.0.8 // indirect\n\tgithub.com/pion/interceptor v0.1.37 // indirect\n\tgithub.com/pion/logging v0.2.3 // indirect\n\tgithub.com/pion/mdns/v2 v2.0.7 // indirect\n\tgithub.com/pion/randutil v0.1.0 // indirect\n\tgithub.com/pion/rtcp v1.2.15 // indirect\n\tgithub.com/pion/rtp v1.8.11 // indirect\n\tgithub.com/pion/sctp v1.8.37 // indirect\n\tgithub.com/pion/sdp/v3 v3.0.10 // indirect\n\tgithub.com/pion/srtp/v3 v3.0.4 // indirect\n\tgithub.com/pion/stun v0.6.1 // indirect\n\tgithub.com/pion/stun/v3 v3.0.0 // indirect\n\tgithub.com/pion/transport/v2 v2.2.10 // indirect\n\tgithub.com/pion/transport/v3 v3.0.7 // indirect\n\tgithub.com/pion/turn/v4 v4.0.0 // indirect\n\tgithub.com/pion/webrtc/v4 v4.0.10 // indirect\n\tgithub.com/pkg/errors v0.9.1 // indirect\n\tgithub.com/polydawn/refmt v0.89.0 // indirect\n\tgithub.com/prometheus/client_model v0.6.1 // indirect\n\tgithub.com/prometheus/common v0.62.0 // indirect\n\tgithub.com/prometheus/procfs v0.15.1 // indirect\n\tgithub.com/quic-go/qpack v0.5.1 // indirect\n\tgithub.com/quic-go/quic-go v0.50.1 // indirect\n\tgithub.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66 // indirect\n\tgithub.com/raulk/go-watchdog v1.3.0 // indirect\n\tgithub.com/spaolacci/murmur3 v1.1.0 // indirect\n\tgithub.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect\n\tgithub.com/wlynxg/anet v0.0.5 // indirect\n\tgithub.com/zeebo/blake3 v0.2.4 // indirect\n\tgo.opencensus.io v0.24.0 // indirect\n\tgo.opentelemetry.io/auto/sdk v1.1.0 // indirect\n\tgo.opentelemetry.io/otel v1.33.0 // indirect\n\tgo.opentelemetry.io/otel/metric v1.33.0 // indirect\n\tgo.opentelemetry.io/otel/trace v1.33.0 // indirect\n\tgo.uber.org/dig v1.18.0 // indirect\n\tgo.uber.org/fx v1.23.0 // indirect\n\tgo.uber.org/mock v0.5.0 // indirect\n\tgo.uber.org/multierr v1.11.0 // indirect\n\tgo.uber.org/zap v1.27.0 // indirect\n\tgo.uber.org/zap/exp v0.3.0 // indirect\n\tgolang.org/x/crypto v0.35.0 // indirect\n\tgolang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa // indirect\n\tgolang.org/x/mod v0.23.0 // indirect\n\tgolang.org/x/net v0.35.0 // indirect\n\tgolang.org/x/sync v0.11.0 // indirect\n\tgolang.org/x/sys v0.30.0 // indirect\n\tgolang.org/x/text v0.22.0 // indirect\n\tgolang.org/x/time v0.8.0 // indirect\n\tgolang.org/x/tools v0.30.0 // indirect\n\tgonum.org/v1/gonum v0.15.1 // indirect\n\tgoogle.golang.org/protobuf v1.36.6 // indirect\n\tlukechampine.com/blake3 v1.4.0 // indirect\n)\n"
  },
  {
    "path": "examples/go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=\ndmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=\ndmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=\ndmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=\ndmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=\ngit.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=\ngithub.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=\ngithub.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=\ngithub.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=\ngithub.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=\ngithub.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=\ngithub.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=\ngithub.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=\ngithub.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=\ngithub.com/caddyserver/certmagic v0.21.6 h1:1th6GfprVfsAtFNOu4StNMF5IxK5XiaI0yZhAHlZFPE=\ngithub.com/caddyserver/certmagic v0.21.6/go.mod h1:n1sCo7zV1Ez2j+89wrzDxo4N/T1Ws/Vx8u5NvuBFabw=\ngithub.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=\ngithub.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE=\ngithub.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=\ngithub.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=\ngithub.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=\ngithub.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=\ngithub.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=\ngithub.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU=\ngithub.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U=\ngithub.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8=\ngithub.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=\ngithub.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=\ngithub.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=\ngithub.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=\ngithub.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=\ngithub.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=\ngithub.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=\ngithub.com/elastic/gosigar v0.12.0/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs=\ngithub.com/elastic/gosigar v0.14.3 h1:xwkKwPia+hSfg9GqrCUKYdId102m9qTJIIr7egmK/uo=\ngithub.com/elastic/gosigar v0.14.3/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=\ngithub.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg=\ngithub.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag=\ngithub.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=\ngithub.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=\ngithub.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=\ngithub.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=\ngithub.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=\ngithub.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=\ngithub.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=\ngithub.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=\ngithub.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=\ngithub.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=\ngithub.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=\ngithub.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=\ngithub.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=\ngithub.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=\ngithub.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=\ngithub.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=\ngithub.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=\ngithub.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=\ngithub.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=\ngithub.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=\ngithub.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=\ngithub.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=\ngithub.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=\ngithub.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=\ngithub.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=\ngithub.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=\ngithub.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=\ngithub.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=\ngithub.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=\ngithub.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20250208200701-d0013a598941 h1:43XjGa6toxLpeksjcxs1jIoIyr+vUfOqY2c6HB4bpoc=\ngithub.com/google/pprof v0.0.0-20250208200701-d0013a598941/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=\ngithub.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=\ngithub.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=\ngithub.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=\ngithub.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4=\ngithub.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=\ngithub.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=\ngithub.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=\ngithub.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=\ngithub.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=\ngithub.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=\ngithub.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=\ngithub.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=\ngithub.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=\ngithub.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=\ngithub.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=\ngithub.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=\ngithub.com/ipfs/boxo v0.25.0 h1:FNZaKVirUDafGz3Y9sccztynAUazs9GfSapLk/5c7is=\ngithub.com/ipfs/boxo v0.25.0/go.mod h1:MQVkL3V8RfuIsn+aajCR0MXLl8nRlz+5uGlHMWFVyuE=\ngithub.com/ipfs/go-block-format v0.2.0 h1:ZqrkxBA2ICbDRbK8KJs/u0O3dlp6gmAuuXUJNiW1Ycs=\ngithub.com/ipfs/go-block-format v0.2.0/go.mod h1:+jpL11nFx5A/SPpsoBn6Bzkra/zaArfSmsknbPMYgzM=\ngithub.com/ipfs/go-cid v0.5.0 h1:goEKKhaGm0ul11IHA7I6p1GmKz8kEYniqFopaB5Otwg=\ngithub.com/ipfs/go-cid v0.5.0/go.mod h1:0L7vmeNXpQpUS9vt+yEARkJ8rOg43DF3iPgn4GIN0mk=\ngithub.com/ipfs/go-datastore v0.6.0 h1:JKyz+Gvz1QEZw0LsX1IBn+JFCJQH4SJVFtM4uWU0Myk=\ngithub.com/ipfs/go-datastore v0.6.0/go.mod h1:rt5M3nNbSO/8q1t4LNkLyUwRs8HupMeN/8O4Vn9YAT8=\ngithub.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk=\ngithub.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps=\ngithub.com/ipfs/go-ipfs-util v0.0.3 h1:2RFdGez6bu2ZlZdI+rWfIdbQb1KudQp3VGwPtdNCmE0=\ngithub.com/ipfs/go-ipfs-util v0.0.3/go.mod h1:LHzG1a0Ig4G+iZ26UUOMjHd+lfM84LZCrn17xAKWBvs=\ngithub.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY=\ngithub.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI=\ngithub.com/ipfs/go-test v0.0.4 h1:DKT66T6GBB6PsDFLoO56QZPrOmzJkqU1FZH5C9ySkew=\ngithub.com/ipfs/go-test v0.0.4/go.mod h1:qhIM1EluEfElKKM6fnWxGn822/z9knUGM1+I/OAQNKI=\ngithub.com/ipld/go-ipld-prime v0.21.0 h1:n4JmcpOlPDIxBcY037SVfpd1G+Sj1nKZah0m6QH9C2E=\ngithub.com/ipld/go-ipld-prime v0.21.0/go.mod h1:3RLqy//ERg/y5oShXXdx5YIp50cFGOanyMctpPjsvxQ=\ngithub.com/ipshipyard/p2p-forge v0.5.0 h1:U1ta2RYkSOLPXNbeCWGT5iv5t5TS1GNDvE1hSupwPZA=\ngithub.com/ipshipyard/p2p-forge v0.5.0/go.mod h1:GNDXM2CR8KRS8mJGw7ARIRVlrG9NH8MdewgNVfIIByA=\ngithub.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=\ngithub.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=\ngithub.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA=\ngithub.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk=\ngithub.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk=\ngithub.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o=\ngithub.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4=\ngithub.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=\ngithub.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=\ngithub.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=\ngithub.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=\ngithub.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=\ngithub.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=\ngithub.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=\ngithub.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=\ngithub.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=\ngithub.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=\ngithub.com/koron/go-ssdp v0.0.5 h1:E1iSMxIs4WqxTbIBLtmNBeOOC+1sCIXQeqTWVnpmwhk=\ngithub.com/koron/go-ssdp v0.0.5/go.mod h1:Qm59B7hpKpDqfyRNWRNr00jGwLdXjDyZh6y7rH6VS0w=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=\ngithub.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=\ngithub.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=\ngithub.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=\ngithub.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8=\ngithub.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=\ngithub.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38yPW7c=\ngithub.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0dXIXt15Okic=\ngithub.com/libp2p/go-flow-metrics v0.2.0 h1:EIZzjmeOE6c8Dav0sNv35vhZxATIXWZg6j/C08XmmDw=\ngithub.com/libp2p/go-flow-metrics v0.2.0/go.mod h1:st3qqfu8+pMfh+9Mzqb2GTiwrAGjIPszEjZmtksN8Jc=\ngithub.com/libp2p/go-libp2p v0.41.1 h1:8ecNQVT5ev/jqALTvisSJeVNvXYJyK4NhQx1nNRXQZE=\ngithub.com/libp2p/go-libp2p v0.41.1/go.mod h1:DcGTovJzQl/I7HMrby5ZRjeD0kQkGiy+9w6aEkSZpRI=\ngithub.com/libp2p/go-libp2p-asn-util v0.4.1 h1:xqL7++IKD9TBFMgnLPZR6/6iYhawHKHl950SO9L6n94=\ngithub.com/libp2p/go-libp2p-asn-util v0.4.1/go.mod h1:d/NI6XZ9qxw67b4e+NgpQexCIiFYJjErASrYW4PFDN8=\ngithub.com/libp2p/go-libp2p-kad-dht v0.28.1 h1:DVTfzG8Ybn88g9RycIq47evWCRss5f0Wm8iWtpwyHso=\ngithub.com/libp2p/go-libp2p-kad-dht v0.28.1/go.mod h1:0wHURlSFdAC42+wF7GEmpLoARw8JuS8do2guCtc/Y/w=\ngithub.com/libp2p/go-libp2p-kbucket v0.6.4 h1:OjfiYxU42TKQSB8t8WYd8MKhYhMJeO2If+NiuKfb6iQ=\ngithub.com/libp2p/go-libp2p-kbucket v0.6.4/go.mod h1:jp6w82sczYaBsAypt5ayACcRJi0lgsba7o4TzJKEfWA=\ngithub.com/libp2p/go-libp2p-record v0.2.0 h1:oiNUOCWno2BFuxt3my4i1frNrt7PerzB3queqa1NkQ0=\ngithub.com/libp2p/go-libp2p-record v0.2.0/go.mod h1:I+3zMkvvg5m2OcSdoL0KPljyJyvNDFGKX7QdlpYUcwk=\ngithub.com/libp2p/go-libp2p-routing-helpers v0.7.4 h1:6LqS1Bzn5CfDJ4tzvP9uwh42IB7TJLNFJA6dEeGBv84=\ngithub.com/libp2p/go-libp2p-routing-helpers v0.7.4/go.mod h1:we5WDj9tbolBXOuF1hGOkR+r7Uh1408tQbAKaT5n1LE=\ngithub.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA=\ngithub.com/libp2p/go-libp2p-testing v0.12.0/go.mod h1:KcGDRXyN7sQCllucn1cOOS+Dmm7ujhfEyXQL5lvkcPg=\ngithub.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0=\ngithub.com/libp2p/go-msgio v0.3.0/go.mod h1:nyRM819GmVaF9LX3l03RMh10QdOroF++NBbxAb0mmDM=\ngithub.com/libp2p/go-netroute v0.2.2 h1:Dejd8cQ47Qx2kRABg6lPwknU7+nBnFRpko45/fFPuZ8=\ngithub.com/libp2p/go-netroute v0.2.2/go.mod h1:Rntq6jUAH0l9Gg17w5bFGhcC9a+vk4KNXs6s7IljKYE=\ngithub.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s=\ngithub.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU=\ngithub.com/libp2p/go-yamux/v5 v5.0.0 h1:2djUh96d3Jiac/JpGkKs4TO49YhsfLopAoryfPmf+Po=\ngithub.com/libp2p/go-yamux/v5 v5.0.0/go.mod h1:en+3cdX51U0ZslwRdRLrvQsdayFt3TSUKvBGErzpWbU=\ngithub.com/libp2p/zeroconf/v2 v2.2.0 h1:Cup06Jv6u81HLhIj1KasuNM/RHHrJ8T7wOTS4+Tv53Q=\ngithub.com/libp2p/zeroconf/v2 v2.2.0/go.mod h1:fuJqLnUwZTshS3U/bMRJ3+ow/v9oid1n0DmyYyNO1Xs=\ngithub.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=\ngithub.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=\ngithub.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk=\ngithub.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU=\ngithub.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=\ngithub.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=\ngithub.com/mholt/acmez/v3 v3.0.0 h1:r1NcjuWR0VaKP2BTjDK9LRFBw/WvURx3jlaEUl9Ht8E=\ngithub.com/mholt/acmez/v3 v3.0.0/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=\ngithub.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=\ngithub.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=\ngithub.com/miekg/dns v1.1.64 h1:wuZgD9wwCE6XMT05UU/mlSko71eRSXEAm2EbjQXLKnQ=\ngithub.com/miekg/dns v1.1.64/go.mod h1:Dzw9769uoKVaLuODMDZz9M6ynFU6Em65csPuoi8G0ck=\ngithub.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c h1:bzE/A84HN25pxAuk9Eej1Kz9OUelF97nAc82bDquQI8=\ngithub.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c/go.mod h1:0SQS9kMwD2VsyFEB++InYyBJroV/FRmBgcydeSUcJms=\ngithub.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b h1:z78hV3sbSMAUoyUMM0I83AUIT6Hu17AWfgjzIbtrYFc=\ngithub.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b/go.mod h1:lxPUiZwKoFL8DUUmalo2yJJUCxbPKtm8OKfqr2/FTNU=\ngithub.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc h1:PTfri+PuQmWDqERdnNMiD9ZejrlswWrCpBEZgWOiTrc=\ngithub.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc/go.mod h1:cGKTAVKx4SxOuR/czcZ/E2RSJ3sfHs8FpHhQ5CWMf9s=\ngithub.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ=\ngithub.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=\ngithub.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=\ngithub.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=\ngithub.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=\ngithub.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=\ngithub.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE=\ngithub.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI=\ngithub.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0=\ngithub.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4=\ngithub.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo=\ngithub.com/multiformats/go-multiaddr v0.15.0 h1:zB/HeaI/apcZiTDwhY5YqMvNVl/oQYvs3XySU+qeAVo=\ngithub.com/multiformats/go-multiaddr v0.15.0/go.mod h1:JSVUmXDjsVFiW7RjIFMP7+Ev+h1DTbiJgVeTV/tcmP0=\ngithub.com/multiformats/go-multiaddr-dns v0.4.1 h1:whi/uCLbDS3mSEUMb1MsoT4uzUeZB0N32yzufqS0i5M=\ngithub.com/multiformats/go-multiaddr-dns v0.4.1/go.mod h1:7hfthtB4E4pQwirrz+J0CcDUfbWzTqEzVyYKKIKpgkc=\ngithub.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E=\ngithub.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo=\ngithub.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g=\ngithub.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk=\ngithub.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg=\ngithub.com/multiformats/go-multicodec v0.9.0/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k=\ngithub.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew=\ngithub.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U=\ngithub.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM=\ngithub.com/multiformats/go-multistream v0.6.0 h1:ZaHKbsL404720283o4c/IHQXiS6gb8qAN5EIJ4PN5EA=\ngithub.com/multiformats/go-multistream v0.6.0/go.mod h1:MOyoG5otO24cHIg8kf9QW2/NozURlkP/rvi2FQJyCPg=\ngithub.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8=\ngithub.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=\ngithub.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=\ngithub.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=\ngithub.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU=\ngithub.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk=\ngithub.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=\ngithub.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=\ngithub.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=\ngithub.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk=\ngithub.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=\ngithub.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=\ngithub.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=\ngithub.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=\ngithub.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o=\ngithub.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M=\ngithub.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=\ngithub.com/pion/dtls/v2 v2.2.12 h1:KP7H5/c1EiVAAKUmXyCzPiQe5+bCJrpOeKg/L05dunk=\ngithub.com/pion/dtls/v2 v2.2.12/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE=\ngithub.com/pion/dtls/v3 v3.0.4 h1:44CZekewMzfrn9pmGrj5BNnTMDCFwr+6sLH+cCuLM7U=\ngithub.com/pion/dtls/v3 v3.0.4/go.mod h1:R373CsjxWqNPf6MEkfdy3aSe9niZvL/JaKlGeFphtMg=\ngithub.com/pion/ice/v4 v4.0.8 h1:ajNx0idNG+S+v9Phu4LSn2cs8JEfTsA1/tEjkkAVpFY=\ngithub.com/pion/ice/v4 v4.0.8/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw=\ngithub.com/pion/interceptor v0.1.37 h1:aRA8Zpab/wE7/c0O3fh1PqY0AJI3fCSEM5lRWJVorwI=\ngithub.com/pion/interceptor v0.1.37/go.mod h1:JzxbJ4umVTlZAf+/utHzNesY8tmRkM2lVmkS82TTj8Y=\ngithub.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=\ngithub.com/pion/logging v0.2.3 h1:gHuf0zpoh1GW67Nr6Gj4cv5Z9ZscU7g/EaoC/Ke/igI=\ngithub.com/pion/logging v0.2.3/go.mod h1:z8YfknkquMe1csOrxK5kc+5/ZPAzMxbKLX5aXpbpC90=\ngithub.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM=\ngithub.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA=\ngithub.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=\ngithub.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=\ngithub.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo=\ngithub.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0=\ngithub.com/pion/rtp v1.8.11 h1:17xjnY5WO5hgO6SD3/NTIUPvSFw/PbLsIJyz1r1yNIk=\ngithub.com/pion/rtp v1.8.11/go.mod h1:8uMBJj32Pa1wwx8Fuv/AsFhn8jsgw+3rUC2PfoBZ8p4=\ngithub.com/pion/sctp v1.8.37 h1:ZDmGPtRPX9mKCiVXtMbTWybFw3z/hVKAZgU81wcOrqs=\ngithub.com/pion/sctp v1.8.37/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE=\ngithub.com/pion/sdp/v3 v3.0.10 h1:6MChLE/1xYB+CjumMw+gZ9ufp2DPApuVSnDT8t5MIgA=\ngithub.com/pion/sdp/v3 v3.0.10/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E=\ngithub.com/pion/srtp/v3 v3.0.4 h1:2Z6vDVxzrX3UHEgrUyIGM4rRouoC7v+NiF1IHtp9B5M=\ngithub.com/pion/srtp/v3 v3.0.4/go.mod h1:1Jx3FwDoxpRaTh1oRV8A/6G1BnFL+QI82eK4ms8EEJQ=\ngithub.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4=\ngithub.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8=\ngithub.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw=\ngithub.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU=\ngithub.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=\ngithub.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=\ngithub.com/pion/transport/v2 v2.2.10 h1:ucLBLE8nuxiHfvkFKnkDQRYWYfp8ejf4YBOPfaQpw6Q=\ngithub.com/pion/transport/v2 v2.2.10/go.mod h1:sq1kSLWs+cHW9E+2fJP95QudkzbK7wscs8yYgQToO5E=\ngithub.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0=\ngithub.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo=\ngithub.com/pion/turn/v4 v4.0.0 h1:qxplo3Rxa9Yg1xXDxxH8xaqcyGUtbHYw4QSCvmFWvhM=\ngithub.com/pion/turn/v4 v4.0.0/go.mod h1:MuPDkm15nYSklKpN8vWJ9W2M0PlyQZqYt1McGuxG7mA=\ngithub.com/pion/webrtc/v4 v4.0.10 h1:Hq/JLjhqLxi+NmCtE8lnRPDr8H4LcNvwg8OxVcdv56Q=\ngithub.com/pion/webrtc/v4 v4.0.10/go.mod h1:ViHLVaNpiuvaH8pdiuQxuA9awuE6KVzAXx3vVWilOck=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/polydawn/refmt v0.89.0 h1:ADJTApkvkeBZsN0tBTx8QjpD9JkmxbKp0cxfr9qszm4=\ngithub.com/polydawn/refmt v0.89.0/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw=\ngithub.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=\ngithub.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=\ngithub.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=\ngithub.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=\ngithub.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=\ngithub.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=\ngithub.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=\ngithub.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=\ngithub.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=\ngithub.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=\ngithub.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=\ngithub.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=\ngithub.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=\ngithub.com/quic-go/quic-go v0.50.1 h1:unsgjFIUqW8a2oopkY7YNONpV1gYND6Nt9hnt1PN94Q=\ngithub.com/quic-go/quic-go v0.50.1/go.mod h1:Vim6OmUvlYdwBhXP9ZVrtGmCMWa3wEqhq3NgYrI8b4E=\ngithub.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66 h1:4WFk6u3sOT6pLa1kQ50ZVdm8BQFgJNA117cepZxtLIg=\ngithub.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66/go.mod h1:Vp72IJajgeOL6ddqrAhmp7IM9zbTcgkQxD/YdxrVwMw=\ngithub.com/raulk/go-watchdog v1.3.0 h1:oUmdlHxdkXRJlwfG0O9omj8ukerm8MEQavSiDTEtBsk=\ngithub.com/raulk/go-watchdog v1.3.0/go.mod h1:fIvOnLbF0b0ZwkB9YU4mOW9Did//4vPZtDqv66NfsMU=\ngithub.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=\ngithub.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=\ngithub.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=\ngithub.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=\ngithub.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=\ngithub.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=\ngithub.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=\ngithub.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=\ngithub.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=\ngithub.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=\ngithub.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=\ngithub.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=\ngithub.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=\ngithub.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=\ngithub.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=\ngithub.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=\ngithub.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=\ngithub.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=\ngithub.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=\ngithub.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=\ngithub.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=\ngithub.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=\ngithub.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=\ngithub.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=\ngithub.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=\ngithub.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=\ngithub.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=\ngithub.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=\ngithub.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=\ngithub.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=\ngithub.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=\ngithub.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM=\ngithub.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=\ngithub.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=\ngithub.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=\ngithub.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=\ngithub.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=\ngithub.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=\ngithub.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=\ngithub.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=\ngithub.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=\ngithub.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=\ngithub.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=\ngithub.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=\ngithub.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ=\ngithub.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw=\ngithub.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k=\ngithub.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc=\ngithub.com/wlynxg/anet v0.0.3/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=\ngithub.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=\ngithub.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngithub.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=\ngithub.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=\ngithub.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=\ngithub.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=\ngithub.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=\ngithub.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=\ngo.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=\ngo.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=\ngo.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=\ngo.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=\ngo.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=\ngo.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw=\ngo.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I=\ngo.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ=\ngo.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M=\ngo.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s=\ngo.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck=\ngo.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=\ngo.uber.org/dig v1.18.0 h1:imUL1UiY0Mg4bqbFfsRQO5G4CGRBec/ZujWTvSVp3pw=\ngo.uber.org/dig v1.18.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=\ngo.uber.org/fx v1.23.0 h1:lIr/gYWQGfTwGcSXWXu4vP5Ws6iqnNEIY+F/aFzCKTg=\ngo.uber.org/fx v1.23.0/go.mod h1:o/D9n+2mLP6v1EG+qsdT1O8wKopYAsqZasju97SDFCU=\ngo.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngo.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=\ngo.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=\ngo.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=\ngo.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=\ngo.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=\ngo.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=\ngo.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=\ngo.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=\ngo.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=\ngo.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=\ngo4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=\ngolang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=\ngolang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=\ngolang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=\ngolang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=\ngolang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=\ngolang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=\ngolang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa h1:t2QcU6V556bFjYgu4L6C+6VrCPyJZ+eyRsABUPs1mz4=\ngolang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk=\ngolang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=\ngolang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=\ngolang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=\ngolang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=\ngolang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=\ngolang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=\ngolang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=\ngolang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=\ngolang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=\ngolang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=\ngolang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=\ngolang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=\ngolang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=\ngolang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=\ngolang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=\ngolang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=\ngolang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=\ngolang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=\ngolang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=\ngolang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=\ngolang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=\ngolang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=\ngolang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=\ngolang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=\ngolang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=\ngolang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngonum.org/v1/gonum v0.15.1 h1:FNy7N6OUZVUaWG9pTiD+jlhdQ3lMP+/LcTpJ6+a8sQ0=\ngonum.org/v1/gonum v0.15.1/go.mod h1:eZTZuRFrzu5pcyjN5wJhcIhnUdNijYxX1T2IcrOGY0o=\ngoogle.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=\ngoogle.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=\ngoogle.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=\ngoogle.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=\ngoogle.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=\ngoogle.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngoogle.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=\ngoogle.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=\ngopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngrpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=\nhonnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nlukechampine.com/blake3 v1.4.0 h1:xDbKOZCVbnZsfzM6mHSYcGRHZ3YrLDzqz8XnV4uaD5w=\nlukechampine.com/blake3 v1.4.0/go.mod h1:MQJNQCTnR+kwOP/JEZSxj3MaQjp80FOFSNMMHXcSeX0=\nsourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=\nsourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=\n"
  },
  {
    "path": "examples/ipfs-camp-2019/README.md",
    "content": "# IPFS Camp 2019 - Course B\nWe've included some scaffolding to help you through the workshop. The folders\nin this repository are \"checkpoints\" of the project as we progress through the\nproject goals. Should you get stuck at one and find yourself eager to push on,\nfeel free to save your work and move on to the next stage!\n\n## Dependencies\n- [golang 1.12+](https://golang.org)\n\n## Optional Tooling\nIf you'd like a more pleasant editing experience, VS Code's golang plugin has\nfantastic support for the nascent go language server implementation. I\nrecommend saibing's fork which adds lots of useful features.\n\n- [saibing's go language server](https://github.com/saibing/tools)\n  Specifically tools/cmd/gopls\n- [VS Code](https://code.visualstudio.com/)\n\n## Running the Examples\nAll of the examples, 01-Transports through 08-End, will compile as written. To\nexecute, simply change into the respective example's directory and run\n`go run .`\n"
  },
  {
    "path": "examples/ipfs-camp-2019/go.mod",
    "content": "module github.com/libp2p/go-libp2p/examples/ipfs-camp-2019\n\ngo 1.24\n\nrequire (\n\tgithub.com/gogo/protobuf v1.3.2\n\tgithub.com/libp2p/go-libp2p v0.33.0\n\tgithub.com/libp2p/go-libp2p-kad-dht v0.25.1\n\tgithub.com/libp2p/go-libp2p-pubsub v0.10.0\n\tgithub.com/multiformats/go-multiaddr v0.12.2\n)\n\nrequire (\n\tgithub.com/benbjohnson/clock v1.3.5 // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.2.0 // indirect\n\tgithub.com/containerd/cgroups v1.1.0 // indirect\n\tgithub.com/coreos/go-systemd/v22 v22.5.0 // indirect\n\tgithub.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect\n\tgithub.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect\n\tgithub.com/docker/go-units v0.5.0 // indirect\n\tgithub.com/elastic/gosigar v0.14.2 // indirect\n\tgithub.com/flynn/noise v1.1.0 // indirect\n\tgithub.com/francoispqt/gojay v1.2.13 // indirect\n\tgithub.com/go-logr/logr v1.3.0 // indirect\n\tgithub.com/go-logr/stdr v1.2.2 // indirect\n\tgithub.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect\n\tgithub.com/godbus/dbus/v5 v5.1.0 // indirect\n\tgithub.com/google/gopacket v1.1.19 // indirect\n\tgithub.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 // indirect\n\tgithub.com/google/uuid v1.4.0 // indirect\n\tgithub.com/gorilla/websocket v1.5.1 // indirect\n\tgithub.com/hashicorp/errwrap v1.1.0 // indirect\n\tgithub.com/hashicorp/go-multierror v1.1.1 // indirect\n\tgithub.com/hashicorp/golang-lru v0.5.4 // indirect\n\tgithub.com/hashicorp/golang-lru/v2 v2.0.5 // indirect\n\tgithub.com/huin/goupnp v1.3.0 // indirect\n\tgithub.com/ipfs/boxo v0.10.0 // indirect\n\tgithub.com/ipfs/go-cid v0.4.1 // indirect\n\tgithub.com/ipfs/go-datastore v0.6.0 // indirect\n\tgithub.com/ipfs/go-log v1.0.5 // indirect\n\tgithub.com/ipfs/go-log/v2 v2.5.1 // indirect\n\tgithub.com/ipld/go-ipld-prime v0.20.0 // indirect\n\tgithub.com/jackpal/go-nat-pmp v1.0.2 // indirect\n\tgithub.com/jbenet/go-temp-err-catcher v0.1.0 // indirect\n\tgithub.com/jbenet/goprocess v0.1.4 // indirect\n\tgithub.com/klauspost/compress v1.17.6 // indirect\n\tgithub.com/klauspost/cpuid/v2 v2.2.7 // indirect\n\tgithub.com/koron/go-ssdp v0.0.4 // indirect\n\tgithub.com/libp2p/go-buffer-pool v0.1.0 // indirect\n\tgithub.com/libp2p/go-cidranger v1.1.0 // indirect\n\tgithub.com/libp2p/go-flow-metrics v0.1.0 // indirect\n\tgithub.com/libp2p/go-libp2p-asn-util v0.4.1 // indirect\n\tgithub.com/libp2p/go-libp2p-kbucket v0.6.3 // indirect\n\tgithub.com/libp2p/go-libp2p-record v0.2.0 // indirect\n\tgithub.com/libp2p/go-libp2p-routing-helpers v0.7.2 // indirect\n\tgithub.com/libp2p/go-msgio v0.3.0 // indirect\n\tgithub.com/libp2p/go-nat v0.2.0 // indirect\n\tgithub.com/libp2p/go-netroute v0.2.1 // indirect\n\tgithub.com/libp2p/go-reuseport v0.4.0 // indirect\n\tgithub.com/libp2p/go-yamux/v4 v4.0.1 // indirect\n\tgithub.com/libp2p/zeroconf/v2 v2.2.0 // indirect\n\tgithub.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect\n\tgithub.com/mattn/go-isatty v0.0.20 // indirect\n\tgithub.com/miekg/dns v1.1.58 // indirect\n\tgithub.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect\n\tgithub.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect\n\tgithub.com/minio/sha256-simd v1.0.1 // indirect\n\tgithub.com/mr-tron/base58 v1.2.0 // indirect\n\tgithub.com/multiformats/go-base32 v0.1.0 // indirect\n\tgithub.com/multiformats/go-base36 v0.2.0 // indirect\n\tgithub.com/multiformats/go-multiaddr-dns v0.3.1 // indirect\n\tgithub.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect\n\tgithub.com/multiformats/go-multibase v0.2.0 // indirect\n\tgithub.com/multiformats/go-multicodec v0.9.0 // indirect\n\tgithub.com/multiformats/go-multihash v0.2.3 // indirect\n\tgithub.com/multiformats/go-multistream v0.5.0 // indirect\n\tgithub.com/multiformats/go-varint v0.0.7 // indirect\n\tgithub.com/onsi/ginkgo/v2 v2.15.0 // indirect\n\tgithub.com/opencontainers/runtime-spec v1.2.0 // indirect\n\tgithub.com/opentracing/opentracing-go v1.2.0 // indirect\n\tgithub.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect\n\tgithub.com/pkg/errors v0.9.1 // indirect\n\tgithub.com/polydawn/refmt v0.89.0 // indirect\n\tgithub.com/prometheus/client_golang v1.18.0 // indirect\n\tgithub.com/prometheus/client_model v0.6.0 // indirect\n\tgithub.com/prometheus/common v0.47.0 // indirect\n\tgithub.com/prometheus/procfs v0.12.0 // indirect\n\tgithub.com/quic-go/qpack v0.4.0 // indirect\n\tgithub.com/quic-go/quic-go v0.41.0 // indirect\n\tgithub.com/quic-go/webtransport-go v0.6.0 // indirect\n\tgithub.com/raulk/go-watchdog v1.3.0 // indirect\n\tgithub.com/spaolacci/murmur3 v1.1.0 // indirect\n\tgithub.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect\n\tgo.opencensus.io v0.24.0 // indirect\n\tgo.opentelemetry.io/otel v1.16.0 // indirect\n\tgo.opentelemetry.io/otel/metric v1.16.0 // indirect\n\tgo.opentelemetry.io/otel/trace v1.16.0 // indirect\n\tgo.uber.org/dig v1.17.1 // indirect\n\tgo.uber.org/fx v1.20.1 // indirect\n\tgo.uber.org/mock v0.4.0 // indirect\n\tgo.uber.org/multierr v1.11.0 // indirect\n\tgo.uber.org/zap v1.27.0 // indirect\n\tgolang.org/x/crypto v0.19.0 // indirect\n\tgolang.org/x/exp v0.0.0-20240213143201-ec583247a57a // indirect\n\tgolang.org/x/mod v0.15.0 // indirect\n\tgolang.org/x/net v0.21.0 // indirect\n\tgolang.org/x/sync v0.6.0 // indirect\n\tgolang.org/x/sys v0.17.0 // indirect\n\tgolang.org/x/text v0.14.0 // indirect\n\tgolang.org/x/tools v0.18.0 // indirect\n\tgonum.org/v1/gonum v0.13.0 // indirect\n\tgoogle.golang.org/protobuf v1.32.0 // indirect\n\tlukechampine.com/blake3 v1.2.1 // indirect\n)\n"
  },
  {
    "path": "examples/ipfs-camp-2019/go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=\ndmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=\ndmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=\ndmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=\ndmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=\ngit.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=\ngithub.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=\ngithub.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=\ngithub.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=\ngithub.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=\ngithub.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=\ngithub.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=\ngithub.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=\ngithub.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=\ngithub.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE=\ngithub.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=\ngithub.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=\ngithub.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=\ngithub.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=\ngithub.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=\ngithub.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU=\ngithub.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U=\ngithub.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y=\ngithub.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=\ngithub.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=\ngithub.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=\ngithub.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=\ngithub.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=\ngithub.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=\ngithub.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=\ngithub.com/elastic/gosigar v0.12.0/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs=\ngithub.com/elastic/gosigar v0.14.2 h1:Dg80n8cr90OZ7x+bAax/QjoW/XqTI11RmA79ZwIm9/4=\ngithub.com/elastic/gosigar v0.14.2/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=\ngithub.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg=\ngithub.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag=\ngithub.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=\ngithub.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=\ngithub.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=\ngithub.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=\ngithub.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=\ngithub.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=\ngithub.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=\ngithub.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=\ngithub.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=\ngithub.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=\ngithub.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=\ngithub.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=\ngithub.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=\ngithub.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=\ngithub.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=\ngithub.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=\ngithub.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=\ngithub.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=\ngithub.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=\ngithub.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=\ngithub.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=\ngithub.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=\ngithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=\ngithub.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=\ngithub.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=\ngithub.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=\ngithub.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=\ngithub.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=\ngithub.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=\ngithub.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=\ngithub.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 h1:E/LAvt58di64hlYjx7AsNS6C/ysHWYo+2qPCZKTQhRo=\ngithub.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=\ngithub.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=\ngithub.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=\ngithub.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=\ngithub.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=\ngithub.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=\ngithub.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4=\ngithub.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=\ngithub.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=\ngithub.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=\ngithub.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=\ngithub.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=\ngithub.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=\ngithub.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=\ngithub.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=\ngithub.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=\ngithub.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=\ngithub.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4=\ngithub.com/hashicorp/golang-lru/v2 v2.0.5/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=\ngithub.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=\ngithub.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=\ngithub.com/ipfs/boxo v0.10.0 h1:tdDAxq8jrsbRkYoF+5Rcqyeb91hgWe2hp7iLu7ORZLY=\ngithub.com/ipfs/boxo v0.10.0/go.mod h1:Fg+BnfxZ0RPzR0nOodzdIq3A7KgoWAOWsEIImrIQdBM=\ngithub.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s=\ngithub.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk=\ngithub.com/ipfs/go-datastore v0.6.0 h1:JKyz+Gvz1QEZw0LsX1IBn+JFCJQH4SJVFtM4uWU0Myk=\ngithub.com/ipfs/go-datastore v0.6.0/go.mod h1:rt5M3nNbSO/8q1t4LNkLyUwRs8HupMeN/8O4Vn9YAT8=\ngithub.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk=\ngithub.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps=\ngithub.com/ipfs/go-ipfs-util v0.0.2 h1:59Sswnk1MFaiq+VcaknX7aYEyGyGDAA73ilhEK2POp8=\ngithub.com/ipfs/go-ipfs-util v0.0.2/go.mod h1:CbPtkWJzjLdEcezDns2XYaehFVNXG9zrdrtMecczcsQ=\ngithub.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8=\ngithub.com/ipfs/go-log v1.0.5/go.mod h1:j0b8ZoR+7+R99LD9jZ6+AJsrzkPbSXbZfGakb5JPtIo=\ngithub.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g=\ngithub.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY=\ngithub.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI=\ngithub.com/ipld/go-ipld-prime v0.20.0 h1:Ud3VwE9ClxpO2LkCYP7vWPc0Fo+dYdYzgxUJZ3uRG4g=\ngithub.com/ipld/go-ipld-prime v0.20.0/go.mod h1:PzqZ/ZR981eKbgdr3y2DJYeD/8bgMawdGVlJDE8kK+M=\ngithub.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=\ngithub.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=\ngithub.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA=\ngithub.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk=\ngithub.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk=\ngithub.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o=\ngithub.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4=\ngithub.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=\ngithub.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=\ngithub.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=\ngithub.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=\ngithub.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=\ngithub.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=\ngithub.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=\ngithub.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=\ngithub.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=\ngithub.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=\ngithub.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0=\ngithub.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8=\ngithub.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=\ngithub.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38yPW7c=\ngithub.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0dXIXt15Okic=\ngithub.com/libp2p/go-flow-metrics v0.1.0 h1:0iPhMI8PskQwzh57jB9WxIuIOQ0r+15PChFGkx3Q3WM=\ngithub.com/libp2p/go-flow-metrics v0.1.0/go.mod h1:4Xi8MX8wj5aWNDAZttg6UPmc0ZrnFNsMtpsYUClFtro=\ngithub.com/libp2p/go-libp2p v0.33.0 h1:yTPSr8sJRbfeEYXyeN8VPVSlTlFjtMUwGDRniwaf/xQ=\ngithub.com/libp2p/go-libp2p v0.33.0/go.mod h1:RIJFRQVUBKy82dnW7J5f1homqqv6NcsDJAl3e7CRGfE=\ngithub.com/libp2p/go-libp2p-asn-util v0.4.1 h1:xqL7++IKD9TBFMgnLPZR6/6iYhawHKHl950SO9L6n94=\ngithub.com/libp2p/go-libp2p-asn-util v0.4.1/go.mod h1:d/NI6XZ9qxw67b4e+NgpQexCIiFYJjErASrYW4PFDN8=\ngithub.com/libp2p/go-libp2p-kad-dht v0.25.1 h1:ofFNrf6MMEy4vi3R1VbJ7LOcTn3Csh0cDcaWHTxtWNA=\ngithub.com/libp2p/go-libp2p-kad-dht v0.25.1/go.mod h1:6za56ncRHYXX4Nc2vn8z7CZK0P4QiMcrn77acKLM2Oo=\ngithub.com/libp2p/go-libp2p-kbucket v0.6.3 h1:p507271wWzpy2f1XxPzCQG9NiN6R6lHL9GiSErbQQo0=\ngithub.com/libp2p/go-libp2p-kbucket v0.6.3/go.mod h1:RCseT7AH6eJWxxk2ol03xtP9pEHetYSPXOaJnOiD8i0=\ngithub.com/libp2p/go-libp2p-pubsub v0.10.0 h1:wS0S5FlISavMaAbxyQn3dxMOe2eegMfswM471RuHJwA=\ngithub.com/libp2p/go-libp2p-pubsub v0.10.0/go.mod h1:1OxbaT/pFRO5h+Dpze8hdHQ63R0ke55XTs6b6NwLLkw=\ngithub.com/libp2p/go-libp2p-record v0.2.0 h1:oiNUOCWno2BFuxt3my4i1frNrt7PerzB3queqa1NkQ0=\ngithub.com/libp2p/go-libp2p-record v0.2.0/go.mod h1:I+3zMkvvg5m2OcSdoL0KPljyJyvNDFGKX7QdlpYUcwk=\ngithub.com/libp2p/go-libp2p-routing-helpers v0.7.2 h1:xJMFyhQ3Iuqnk9Q2dYE1eUTzsah7NLw3Qs2zjUV78T0=\ngithub.com/libp2p/go-libp2p-routing-helpers v0.7.2/go.mod h1:cN4mJAD/7zfPKXBcs9ze31JGYAZgzdABEm+q/hkswb8=\ngithub.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA=\ngithub.com/libp2p/go-libp2p-testing v0.12.0/go.mod h1:KcGDRXyN7sQCllucn1cOOS+Dmm7ujhfEyXQL5lvkcPg=\ngithub.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0=\ngithub.com/libp2p/go-msgio v0.3.0/go.mod h1:nyRM819GmVaF9LX3l03RMh10QdOroF++NBbxAb0mmDM=\ngithub.com/libp2p/go-nat v0.2.0 h1:Tyz+bUFAYqGyJ/ppPPymMGbIgNRH+WqC5QrT5fKrrGk=\ngithub.com/libp2p/go-nat v0.2.0/go.mod h1:3MJr+GRpRkyT65EpVPBstXLvOlAPzUVlG6Pwg9ohLJk=\ngithub.com/libp2p/go-netroute v0.2.1 h1:V8kVrpD8GK0Riv15/7VN6RbUQ3URNZVosw7H2v9tksU=\ngithub.com/libp2p/go-netroute v0.2.1/go.mod h1:hraioZr0fhBjG0ZRXJJ6Zj2IVEVNx6tDTFQfSmcq7mQ=\ngithub.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s=\ngithub.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU=\ngithub.com/libp2p/go-yamux/v4 v4.0.1 h1:FfDR4S1wj6Bw2Pqbc8Uz7pCxeRBPbwsBbEdfwiCypkQ=\ngithub.com/libp2p/go-yamux/v4 v4.0.1/go.mod h1:NWjl8ZTLOGlozrXSOZ/HlfG++39iKNnM5wwmtQP1YB4=\ngithub.com/libp2p/zeroconf/v2 v2.2.0 h1:Cup06Jv6u81HLhIj1KasuNM/RHHrJ8T7wOTS4+Tv53Q=\ngithub.com/libp2p/zeroconf/v2 v2.2.0/go.mod h1:fuJqLnUwZTshS3U/bMRJ3+ow/v9oid1n0DmyYyNO1Xs=\ngithub.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=\ngithub.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=\ngithub.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk=\ngithub.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU=\ngithub.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=\ngithub.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=\ngithub.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=\ngithub.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=\ngithub.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=\ngithub.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=\ngithub.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=\ngithub.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c h1:bzE/A84HN25pxAuk9Eej1Kz9OUelF97nAc82bDquQI8=\ngithub.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c/go.mod h1:0SQS9kMwD2VsyFEB++InYyBJroV/FRmBgcydeSUcJms=\ngithub.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b h1:z78hV3sbSMAUoyUMM0I83AUIT6Hu17AWfgjzIbtrYFc=\ngithub.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b/go.mod h1:lxPUiZwKoFL8DUUmalo2yJJUCxbPKtm8OKfqr2/FTNU=\ngithub.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc h1:PTfri+PuQmWDqERdnNMiD9ZejrlswWrCpBEZgWOiTrc=\ngithub.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc/go.mod h1:cGKTAVKx4SxOuR/czcZ/E2RSJ3sfHs8FpHhQ5CWMf9s=\ngithub.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ=\ngithub.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=\ngithub.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=\ngithub.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=\ngithub.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=\ngithub.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=\ngithub.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE=\ngithub.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI=\ngithub.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0=\ngithub.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4=\ngithub.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo=\ngithub.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4=\ngithub.com/multiformats/go-multiaddr v0.12.2 h1:9G9sTY/wCYajKa9lyfWPmpZAwe6oV+Wb1zcmMS1HG24=\ngithub.com/multiformats/go-multiaddr v0.12.2/go.mod h1:GKyaTYjZRdcUhyOetrxTk9z0cW+jA/YrnqTOvKgi44M=\ngithub.com/multiformats/go-multiaddr-dns v0.3.1 h1:QgQgR+LQVt3NPTjbrLLpsaT2ufAA2y0Mkk+QRVJbW3A=\ngithub.com/multiformats/go-multiaddr-dns v0.3.1/go.mod h1:G/245BRQ6FJGmryJCrOuTdB37AMA5AMOVuO6NY3JwTk=\ngithub.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E=\ngithub.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo=\ngithub.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g=\ngithub.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk=\ngithub.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg=\ngithub.com/multiformats/go-multicodec v0.9.0/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k=\ngithub.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew=\ngithub.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U=\ngithub.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM=\ngithub.com/multiformats/go-multistream v0.5.0 h1:5htLSLl7lvJk3xx3qT/8Zm9J4K8vEOf/QGkvOGQAyiE=\ngithub.com/multiformats/go-multistream v0.5.0/go.mod h1:n6tMZiwiP2wUsR8DgfDWw1dydlEqV3l6N3/GBsX6ILA=\ngithub.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=\ngithub.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8=\ngithub.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=\ngithub.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=\ngithub.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=\ngithub.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY=\ngithub.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM=\ngithub.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=\ngithub.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=\ngithub.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=\ngithub.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk=\ngithub.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=\ngithub.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=\ngithub.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=\ngithub.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=\ngithub.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=\ngithub.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/polydawn/refmt v0.89.0 h1:ADJTApkvkeBZsN0tBTx8QjpD9JkmxbKp0cxfr9qszm4=\ngithub.com/polydawn/refmt v0.89.0/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw=\ngithub.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=\ngithub.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=\ngithub.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=\ngithub.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos=\ngithub.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8=\ngithub.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=\ngithub.com/prometheus/common v0.47.0 h1:p5Cz0FNHo7SnWOmWmoRozVcjEp0bIVU8cV7OShpjL1k=\ngithub.com/prometheus/common v0.47.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=\ngithub.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=\ngithub.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=\ngithub.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=\ngithub.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=\ngithub.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=\ngithub.com/quic-go/quic-go v0.41.0 h1:aD8MmHfgqTURWNJy48IYFg2OnxwHT3JL7ahGs73lb4k=\ngithub.com/quic-go/quic-go v0.41.0/go.mod h1:qCkNjqczPEvgsOnxZ0eCD14lv+B2LHlFAB++CNOh9hA=\ngithub.com/quic-go/webtransport-go v0.6.0 h1:CvNsKqc4W2HljHJnoT+rMmbRJybShZ0YPFDD3NxaZLY=\ngithub.com/quic-go/webtransport-go v0.6.0/go.mod h1:9KjU4AEBqEQidGHNDkZrb8CAa1abRaosM2yGOyiikEc=\ngithub.com/raulk/go-watchdog v1.3.0 h1:oUmdlHxdkXRJlwfG0O9omj8ukerm8MEQavSiDTEtBsk=\ngithub.com/raulk/go-watchdog v1.3.0/go.mod h1:fIvOnLbF0b0ZwkB9YU4mOW9Did//4vPZtDqv66NfsMU=\ngithub.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=\ngithub.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=\ngithub.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=\ngithub.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=\ngithub.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=\ngithub.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=\ngithub.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=\ngithub.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=\ngithub.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=\ngithub.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=\ngithub.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=\ngithub.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=\ngithub.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=\ngithub.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=\ngithub.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=\ngithub.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=\ngithub.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=\ngithub.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=\ngithub.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=\ngithub.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=\ngithub.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=\ngithub.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=\ngithub.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=\ngithub.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=\ngithub.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=\ngithub.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=\ngithub.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=\ngithub.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=\ngithub.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=\ngithub.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=\ngithub.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=\ngithub.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=\ngithub.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM=\ngithub.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=\ngithub.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=\ngithub.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=\ngithub.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=\ngithub.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=\ngithub.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=\ngithub.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=\ngithub.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=\ngithub.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=\ngithub.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=\ngithub.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ=\ngithub.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw=\ngithub.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k=\ngithub.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=\ngo.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=\ngo.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=\ngo.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=\ngo.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=\ngo.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=\ngo.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=\ngo.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=\ngo.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=\ngo.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=\ngo.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=\ngo.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=\ngo.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=\ngo.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=\ngo.uber.org/dig v1.17.1 h1:Tga8Lz8PcYNsWsyHMZ1Vm0OQOUaJNDyvPImgbAu9YSc=\ngo.uber.org/dig v1.17.1/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=\ngo.uber.org/fx v1.20.1 h1:zVwVQGS8zYvhh9Xxcu4w1M6ESyeMzebzj2NbSayZ4Mk=\ngo.uber.org/fx v1.20.1/go.mod h1:iSYNbHf2y55acNCwCXKx7LbWb5WG1Bnue5RDXz1OREg=\ngo.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngo.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=\ngo.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=\ngo.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=\ngo.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=\ngo.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=\ngo.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=\ngo.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=\ngo.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=\ngo.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=\ngo.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=\ngo.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=\ngo4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=\ngolang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=\ngolang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=\ngolang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=\ngolang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE=\ngolang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=\ngolang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=\ngolang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=\ngolang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=\ngolang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=\ngolang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=\ngolang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=\ngolang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=\ngolang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=\ngolang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=\ngolang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ=\ngolang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngonum.org/v1/gonum v0.13.0 h1:a0T3bh+7fhRyqeNbiC3qVHYmkiQgit3wnNan/2c0HMM=\ngonum.org/v1/gonum v0.13.0/go.mod h1:/WPYRckkfWrhWefxyYTfrTtQR0KH4iyHNuzxqXAKyAU=\ngoogle.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=\ngoogle.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=\ngoogle.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=\ngoogle.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=\ngoogle.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=\ngoogle.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngoogle.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=\ngoogle.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=\ngopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=\ngopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngrpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=\nhonnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=\nlukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI=\nlukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=\nsourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=\nsourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=\n"
  },
  {
    "path": "examples/metrics-and-dashboards/README.md",
    "content": "# Metrics and Dashboards\n\nAn example to demonstrate using Prometheus and Grafana to view go-libp2p\nmetrics. Sets up a Prometheus server and Grafana server via Docker compose. A\nsmall go-libp2p dummy application is included to emit metrics.\n\nRun it with:\n\n```\ndocker compose -f ../../dashboards/docker-compose.base.yml -f ./compose.yml up\n```\n\nGo to http://localhost:3000/dashboards to see the dashboards.\n"
  },
  {
    "path": "examples/metrics-and-dashboards/compose.yml",
    "content": "services:\n  prometheus:\n    image: prom/prometheus:latest\n    ports:\n      - \"9090:9090\"\n    volumes:\n      - ../examples/metrics-and-dashboards/prometheus.yml:/etc/prometheus/prometheus.yml\n  go-libp2p-node:\n    build:\n      context: ../examples/metrics-and-dashboards/\n      dockerfile: go-libp2p-node.Dockerfile\n    ports:\n      - 5001:5001\n    expose:\n      - 5001\n"
  },
  {
    "path": "examples/metrics-and-dashboards/go-libp2p-node.Dockerfile",
    "content": "FROM golang:alpine\nWORKDIR /app\nCOPY ./main.go .\nRUN go mod init example.com/m/v2\nRUN go mod tidy\nRUN go build main.go\nENTRYPOINT [ \"/app/main\" ]\n"
  },
  {
    "path": "examples/metrics-and-dashboards/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"math/rand\"\n\t\"net/http\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/ping\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promhttp\"\n\n\trcmgr \"github.com/libp2p/go-libp2p/p2p/host/resource-manager\"\n)\n\nconst ClientCount = 32\n\nfunc main() {\n\thttp.Handle(\"/metrics\", promhttp.Handler())\n\tgo func() {\n\t\thttp.Handle(\"/debug/metrics/prometheus\", promhttp.Handler())\n\t\tlog.Fatal(http.ListenAndServe(\":5001\", nil))\n\t}()\n\n\trcmgr.MustRegisterWith(prometheus.DefaultRegisterer)\n\n\tstr, err := rcmgr.NewStatsTraceReporter()\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\trmgr, err := rcmgr.NewResourceManager(rcmgr.NewFixedLimiter(rcmgr.DefaultLimits.AutoScale()), rcmgr.WithTraceReporter(str))\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tserver, err := libp2p.New(libp2p.ResourceManager(rmgr))\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Make a bunch of clients that all ping the server at various times\n\twg := sync.WaitGroup{}\n\tfor i := 0; i < ClientCount; i++ {\n\t\twg.Add(1)\n\t\tgo func(i int) {\n\t\t\tdefer wg.Done()\n\t\t\ttime.Sleep(time.Duration(i%100) * 100 * time.Millisecond)\n\t\t\tnewClient(peer.AddrInfo{\n\t\t\t\tID:    server.ID(),\n\t\t\t\tAddrs: server.Addrs(),\n\t\t\t}, i)\n\t\t}(i)\n\t}\n\twg.Wait()\n}\n\nfunc newClient(serverInfo peer.AddrInfo, pings int) {\n\t// Sleep some random amount of time to spread out the clients so the graphs look more interesting\n\ttime.Sleep(time.Duration(rand.Intn(100)) * time.Second)\n\tfmt.Println(\"Started client\", pings)\n\n\tclient, err := libp2p.New(\n\t\t// We just want metrics from the server\n\t\tlibp2p.DisableMetrics(),\n\t\tlibp2p.NoListenAddrs,\n\t)\n\tdefer func() {\n\t\t_ = client.Close()\n\t}()\n\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tclient.Connect(context.Background(), serverInfo)\n\n\tp := ping.Ping(context.Background(), client, serverInfo.ID)\n\n\tpingSoFar := 0\n\tfor pingSoFar < pings {\n\t\tres := <-p\n\t\tpingSoFar++\n\t\tif res.Error != nil {\n\t\t\tlog.Fatal(res.Error)\n\t\t}\n\t\ttime.Sleep(5 * time.Second)\n\t}\n}\n"
  },
  {
    "path": "examples/metrics-and-dashboards/prometheus.yml",
    "content": "global:\n  scrape_interval: 1m\n\nscrape_configs:\n  - job_name: \"prometheus\"\n    scrape_interval: 1m\n    static_configs:\n      - targets: [\"localhost:9090\"]\n\n  - job_name: \"node\"\n    static_configs:\n      - targets: [\"node-exporter:9100\"]\n  - job_name: \"go-libp2p\"\n    metrics_path: /debug/metrics/prometheus\n    static_configs:\n      - targets: [\"go-libp2p-node:5001\"]\n"
  },
  {
    "path": "examples/pubsub/README.md",
    "content": "# go-libp2p-pubsub examples\n\nThis directory contains example projects that use [go-libp2p-pubsub](https://github.com/libp2p/go-libp2p-pubsub),\nthe Go implementation of libp2p's [Publish / Subscribe system](https://docs.libp2p.io/concepts/publish-subscribe).\n\n- The [chat room example](./chat) covers the basics of using the PubSub API to build a peer-to-peer chat application.\n- The [pubsub rendezvous example](./basic-chat-with-rendezvous) allows multiple peers to chat among each other using go-libp2p-pubsub using rendezvous names.\n"
  },
  {
    "path": "examples/pubsub/basic-chat-with-rendezvous/.gitignore",
    "content": "chat\n"
  },
  {
    "path": "examples/pubsub/basic-chat-with-rendezvous/README.md",
    "content": "# go-libp2p-pubsub chat with rendezvous example\n\nThis example project allows multiple peers to chat among each other using go-libp2p-pubsub. \n\nPeers are discovered using a DHT, so no prior information (other than the rendezvous name) is required for each peer.\n\n## Running\n\nClone this repo, then `cd` into the `examples/pubsub/basic-chat-with-rendezvous` directory:\n\n```shell\ngit clone https://github.com/libp2p/go-libp2p\ncd go-libp2p/examples/pubsub/basic-chat-with-rendezvous\n```\n\nNow you can either run with `go run`, or build and run the binary:\n\n```shell\ngo run .\n\n# or, build and run separately\ngo build .\n./chat\n```\n\nTo change the topic name, use the `-topicName` flag:\n\n```shell\ngo run . -topicName=adifferenttopic\n```\n\nTry opening several terminals, each running the app. When you type a message and hit enter in one, it\nshould appear in all others that are connected to the same topic.\n\nTo quit, hit `Ctrl-C`.\n\n"
  },
  {
    "path": "examples/pubsub/basic-chat-with-rendezvous/go.mod",
    "content": "module github.com/libp2p/go-libp2p/examples/pubsub/chat\n\ngo 1.24\n\nrequire (\n\tgithub.com/libp2p/go-libp2p v0.33.0\n\tgithub.com/libp2p/go-libp2p-kad-dht v0.25.1\n\tgithub.com/libp2p/go-libp2p-pubsub v0.10.0\n)\n\nrequire (\n\tgithub.com/benbjohnson/clock v1.3.5 // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.2.0 // indirect\n\tgithub.com/containerd/cgroups v1.1.0 // indirect\n\tgithub.com/coreos/go-systemd/v22 v22.5.0 // indirect\n\tgithub.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect\n\tgithub.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect\n\tgithub.com/docker/go-units v0.5.0 // indirect\n\tgithub.com/elastic/gosigar v0.14.2 // indirect\n\tgithub.com/flynn/noise v1.1.0 // indirect\n\tgithub.com/francoispqt/gojay v1.2.13 // indirect\n\tgithub.com/go-logr/logr v1.3.0 // indirect\n\tgithub.com/go-logr/stdr v1.2.2 // indirect\n\tgithub.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect\n\tgithub.com/godbus/dbus/v5 v5.1.0 // indirect\n\tgithub.com/gogo/protobuf v1.3.2 // indirect\n\tgithub.com/google/gopacket v1.1.19 // indirect\n\tgithub.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 // indirect\n\tgithub.com/google/uuid v1.4.0 // indirect\n\tgithub.com/gorilla/websocket v1.5.1 // indirect\n\tgithub.com/hashicorp/errwrap v1.1.0 // indirect\n\tgithub.com/hashicorp/go-multierror v1.1.1 // indirect\n\tgithub.com/hashicorp/golang-lru v0.5.4 // indirect\n\tgithub.com/hashicorp/golang-lru/v2 v2.0.5 // indirect\n\tgithub.com/huin/goupnp v1.3.0 // indirect\n\tgithub.com/ipfs/boxo v0.10.0 // indirect\n\tgithub.com/ipfs/go-cid v0.4.1 // indirect\n\tgithub.com/ipfs/go-datastore v0.6.0 // indirect\n\tgithub.com/ipfs/go-log v1.0.5 // indirect\n\tgithub.com/ipfs/go-log/v2 v2.5.1 // indirect\n\tgithub.com/ipld/go-ipld-prime v0.20.0 // indirect\n\tgithub.com/jackpal/go-nat-pmp v1.0.2 // indirect\n\tgithub.com/jbenet/go-temp-err-catcher v0.1.0 // indirect\n\tgithub.com/jbenet/goprocess v0.1.4 // indirect\n\tgithub.com/klauspost/compress v1.17.6 // indirect\n\tgithub.com/klauspost/cpuid/v2 v2.2.7 // indirect\n\tgithub.com/koron/go-ssdp v0.0.4 // indirect\n\tgithub.com/libp2p/go-buffer-pool v0.1.0 // indirect\n\tgithub.com/libp2p/go-cidranger v1.1.0 // indirect\n\tgithub.com/libp2p/go-flow-metrics v0.1.0 // indirect\n\tgithub.com/libp2p/go-libp2p-asn-util v0.4.1 // indirect\n\tgithub.com/libp2p/go-libp2p-kbucket v0.6.3 // indirect\n\tgithub.com/libp2p/go-libp2p-record v0.2.0 // indirect\n\tgithub.com/libp2p/go-libp2p-routing-helpers v0.7.2 // indirect\n\tgithub.com/libp2p/go-msgio v0.3.0 // indirect\n\tgithub.com/libp2p/go-nat v0.2.0 // indirect\n\tgithub.com/libp2p/go-netroute v0.2.1 // indirect\n\tgithub.com/libp2p/go-reuseport v0.4.0 // indirect\n\tgithub.com/libp2p/go-yamux/v4 v4.0.1 // indirect\n\tgithub.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect\n\tgithub.com/mattn/go-isatty v0.0.20 // indirect\n\tgithub.com/miekg/dns v1.1.58 // indirect\n\tgithub.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect\n\tgithub.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect\n\tgithub.com/minio/sha256-simd v1.0.1 // indirect\n\tgithub.com/mr-tron/base58 v1.2.0 // indirect\n\tgithub.com/multiformats/go-base32 v0.1.0 // indirect\n\tgithub.com/multiformats/go-base36 v0.2.0 // indirect\n\tgithub.com/multiformats/go-multiaddr v0.12.2 // indirect\n\tgithub.com/multiformats/go-multiaddr-dns v0.3.1 // indirect\n\tgithub.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect\n\tgithub.com/multiformats/go-multibase v0.2.0 // indirect\n\tgithub.com/multiformats/go-multicodec v0.9.0 // indirect\n\tgithub.com/multiformats/go-multihash v0.2.3 // indirect\n\tgithub.com/multiformats/go-multistream v0.5.0 // indirect\n\tgithub.com/multiformats/go-varint v0.0.7 // indirect\n\tgithub.com/onsi/ginkgo/v2 v2.15.0 // indirect\n\tgithub.com/opencontainers/runtime-spec v1.2.0 // indirect\n\tgithub.com/opentracing/opentracing-go v1.2.0 // indirect\n\tgithub.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect\n\tgithub.com/pkg/errors v0.9.1 // indirect\n\tgithub.com/polydawn/refmt v0.89.0 // indirect\n\tgithub.com/prometheus/client_golang v1.18.0 // indirect\n\tgithub.com/prometheus/client_model v0.6.0 // indirect\n\tgithub.com/prometheus/common v0.47.0 // indirect\n\tgithub.com/prometheus/procfs v0.12.0 // indirect\n\tgithub.com/quic-go/qpack v0.4.0 // indirect\n\tgithub.com/quic-go/quic-go v0.41.0 // indirect\n\tgithub.com/quic-go/webtransport-go v0.6.0 // indirect\n\tgithub.com/raulk/go-watchdog v1.3.0 // indirect\n\tgithub.com/spaolacci/murmur3 v1.1.0 // indirect\n\tgithub.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect\n\tgo.opencensus.io v0.24.0 // indirect\n\tgo.opentelemetry.io/otel v1.16.0 // indirect\n\tgo.opentelemetry.io/otel/metric v1.16.0 // indirect\n\tgo.opentelemetry.io/otel/trace v1.16.0 // indirect\n\tgo.uber.org/dig v1.17.1 // indirect\n\tgo.uber.org/fx v1.20.1 // indirect\n\tgo.uber.org/mock v0.4.0 // indirect\n\tgo.uber.org/multierr v1.11.0 // indirect\n\tgo.uber.org/zap v1.27.0 // indirect\n\tgolang.org/x/crypto v0.19.0 // indirect\n\tgolang.org/x/exp v0.0.0-20240213143201-ec583247a57a // indirect\n\tgolang.org/x/mod v0.15.0 // indirect\n\tgolang.org/x/net v0.21.0 // indirect\n\tgolang.org/x/sync v0.6.0 // indirect\n\tgolang.org/x/sys v0.17.0 // indirect\n\tgolang.org/x/text v0.14.0 // indirect\n\tgolang.org/x/tools v0.18.0 // indirect\n\tgonum.org/v1/gonum v0.13.0 // indirect\n\tgoogle.golang.org/protobuf v1.32.0 // indirect\n\tlukechampine.com/blake3 v1.2.1 // indirect\n)\n"
  },
  {
    "path": "examples/pubsub/basic-chat-with-rendezvous/go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=\ndmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=\ndmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=\ndmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=\ndmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=\ngit.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=\ngithub.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=\ngithub.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=\ngithub.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=\ngithub.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=\ngithub.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=\ngithub.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=\ngithub.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=\ngithub.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=\ngithub.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE=\ngithub.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=\ngithub.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=\ngithub.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=\ngithub.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=\ngithub.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=\ngithub.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU=\ngithub.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U=\ngithub.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y=\ngithub.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=\ngithub.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=\ngithub.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=\ngithub.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=\ngithub.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=\ngithub.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=\ngithub.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=\ngithub.com/elastic/gosigar v0.12.0/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs=\ngithub.com/elastic/gosigar v0.14.2 h1:Dg80n8cr90OZ7x+bAax/QjoW/XqTI11RmA79ZwIm9/4=\ngithub.com/elastic/gosigar v0.14.2/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=\ngithub.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg=\ngithub.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag=\ngithub.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=\ngithub.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=\ngithub.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=\ngithub.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=\ngithub.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=\ngithub.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=\ngithub.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=\ngithub.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=\ngithub.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=\ngithub.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=\ngithub.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=\ngithub.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=\ngithub.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=\ngithub.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=\ngithub.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=\ngithub.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=\ngithub.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=\ngithub.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=\ngithub.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=\ngithub.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=\ngithub.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=\ngithub.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=\ngithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=\ngithub.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=\ngithub.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=\ngithub.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=\ngithub.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=\ngithub.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=\ngithub.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=\ngithub.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=\ngithub.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 h1:E/LAvt58di64hlYjx7AsNS6C/ysHWYo+2qPCZKTQhRo=\ngithub.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=\ngithub.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=\ngithub.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=\ngithub.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=\ngithub.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=\ngithub.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=\ngithub.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4=\ngithub.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=\ngithub.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=\ngithub.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=\ngithub.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=\ngithub.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=\ngithub.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=\ngithub.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=\ngithub.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=\ngithub.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=\ngithub.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=\ngithub.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4=\ngithub.com/hashicorp/golang-lru/v2 v2.0.5/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=\ngithub.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=\ngithub.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=\ngithub.com/ipfs/boxo v0.10.0 h1:tdDAxq8jrsbRkYoF+5Rcqyeb91hgWe2hp7iLu7ORZLY=\ngithub.com/ipfs/boxo v0.10.0/go.mod h1:Fg+BnfxZ0RPzR0nOodzdIq3A7KgoWAOWsEIImrIQdBM=\ngithub.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s=\ngithub.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk=\ngithub.com/ipfs/go-datastore v0.6.0 h1:JKyz+Gvz1QEZw0LsX1IBn+JFCJQH4SJVFtM4uWU0Myk=\ngithub.com/ipfs/go-datastore v0.6.0/go.mod h1:rt5M3nNbSO/8q1t4LNkLyUwRs8HupMeN/8O4Vn9YAT8=\ngithub.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk=\ngithub.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps=\ngithub.com/ipfs/go-ipfs-util v0.0.2 h1:59Sswnk1MFaiq+VcaknX7aYEyGyGDAA73ilhEK2POp8=\ngithub.com/ipfs/go-ipfs-util v0.0.2/go.mod h1:CbPtkWJzjLdEcezDns2XYaehFVNXG9zrdrtMecczcsQ=\ngithub.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8=\ngithub.com/ipfs/go-log v1.0.5/go.mod h1:j0b8ZoR+7+R99LD9jZ6+AJsrzkPbSXbZfGakb5JPtIo=\ngithub.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g=\ngithub.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY=\ngithub.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI=\ngithub.com/ipld/go-ipld-prime v0.20.0 h1:Ud3VwE9ClxpO2LkCYP7vWPc0Fo+dYdYzgxUJZ3uRG4g=\ngithub.com/ipld/go-ipld-prime v0.20.0/go.mod h1:PzqZ/ZR981eKbgdr3y2DJYeD/8bgMawdGVlJDE8kK+M=\ngithub.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=\ngithub.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=\ngithub.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA=\ngithub.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk=\ngithub.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk=\ngithub.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o=\ngithub.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4=\ngithub.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=\ngithub.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=\ngithub.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=\ngithub.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=\ngithub.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=\ngithub.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=\ngithub.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=\ngithub.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=\ngithub.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=\ngithub.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=\ngithub.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0=\ngithub.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8=\ngithub.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=\ngithub.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38yPW7c=\ngithub.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0dXIXt15Okic=\ngithub.com/libp2p/go-flow-metrics v0.1.0 h1:0iPhMI8PskQwzh57jB9WxIuIOQ0r+15PChFGkx3Q3WM=\ngithub.com/libp2p/go-flow-metrics v0.1.0/go.mod h1:4Xi8MX8wj5aWNDAZttg6UPmc0ZrnFNsMtpsYUClFtro=\ngithub.com/libp2p/go-libp2p v0.33.0 h1:yTPSr8sJRbfeEYXyeN8VPVSlTlFjtMUwGDRniwaf/xQ=\ngithub.com/libp2p/go-libp2p v0.33.0/go.mod h1:RIJFRQVUBKy82dnW7J5f1homqqv6NcsDJAl3e7CRGfE=\ngithub.com/libp2p/go-libp2p-asn-util v0.4.1 h1:xqL7++IKD9TBFMgnLPZR6/6iYhawHKHl950SO9L6n94=\ngithub.com/libp2p/go-libp2p-asn-util v0.4.1/go.mod h1:d/NI6XZ9qxw67b4e+NgpQexCIiFYJjErASrYW4PFDN8=\ngithub.com/libp2p/go-libp2p-kad-dht v0.25.1 h1:ofFNrf6MMEy4vi3R1VbJ7LOcTn3Csh0cDcaWHTxtWNA=\ngithub.com/libp2p/go-libp2p-kad-dht v0.25.1/go.mod h1:6za56ncRHYXX4Nc2vn8z7CZK0P4QiMcrn77acKLM2Oo=\ngithub.com/libp2p/go-libp2p-kbucket v0.6.3 h1:p507271wWzpy2f1XxPzCQG9NiN6R6lHL9GiSErbQQo0=\ngithub.com/libp2p/go-libp2p-kbucket v0.6.3/go.mod h1:RCseT7AH6eJWxxk2ol03xtP9pEHetYSPXOaJnOiD8i0=\ngithub.com/libp2p/go-libp2p-pubsub v0.10.0 h1:wS0S5FlISavMaAbxyQn3dxMOe2eegMfswM471RuHJwA=\ngithub.com/libp2p/go-libp2p-pubsub v0.10.0/go.mod h1:1OxbaT/pFRO5h+Dpze8hdHQ63R0ke55XTs6b6NwLLkw=\ngithub.com/libp2p/go-libp2p-record v0.2.0 h1:oiNUOCWno2BFuxt3my4i1frNrt7PerzB3queqa1NkQ0=\ngithub.com/libp2p/go-libp2p-record v0.2.0/go.mod h1:I+3zMkvvg5m2OcSdoL0KPljyJyvNDFGKX7QdlpYUcwk=\ngithub.com/libp2p/go-libp2p-routing-helpers v0.7.2 h1:xJMFyhQ3Iuqnk9Q2dYE1eUTzsah7NLw3Qs2zjUV78T0=\ngithub.com/libp2p/go-libp2p-routing-helpers v0.7.2/go.mod h1:cN4mJAD/7zfPKXBcs9ze31JGYAZgzdABEm+q/hkswb8=\ngithub.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA=\ngithub.com/libp2p/go-libp2p-testing v0.12.0/go.mod h1:KcGDRXyN7sQCllucn1cOOS+Dmm7ujhfEyXQL5lvkcPg=\ngithub.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0=\ngithub.com/libp2p/go-msgio v0.3.0/go.mod h1:nyRM819GmVaF9LX3l03RMh10QdOroF++NBbxAb0mmDM=\ngithub.com/libp2p/go-nat v0.2.0 h1:Tyz+bUFAYqGyJ/ppPPymMGbIgNRH+WqC5QrT5fKrrGk=\ngithub.com/libp2p/go-nat v0.2.0/go.mod h1:3MJr+GRpRkyT65EpVPBstXLvOlAPzUVlG6Pwg9ohLJk=\ngithub.com/libp2p/go-netroute v0.2.1 h1:V8kVrpD8GK0Riv15/7VN6RbUQ3URNZVosw7H2v9tksU=\ngithub.com/libp2p/go-netroute v0.2.1/go.mod h1:hraioZr0fhBjG0ZRXJJ6Zj2IVEVNx6tDTFQfSmcq7mQ=\ngithub.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s=\ngithub.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU=\ngithub.com/libp2p/go-yamux/v4 v4.0.1 h1:FfDR4S1wj6Bw2Pqbc8Uz7pCxeRBPbwsBbEdfwiCypkQ=\ngithub.com/libp2p/go-yamux/v4 v4.0.1/go.mod h1:NWjl8ZTLOGlozrXSOZ/HlfG++39iKNnM5wwmtQP1YB4=\ngithub.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=\ngithub.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=\ngithub.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk=\ngithub.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU=\ngithub.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=\ngithub.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=\ngithub.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=\ngithub.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=\ngithub.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=\ngithub.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=\ngithub.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c h1:bzE/A84HN25pxAuk9Eej1Kz9OUelF97nAc82bDquQI8=\ngithub.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c/go.mod h1:0SQS9kMwD2VsyFEB++InYyBJroV/FRmBgcydeSUcJms=\ngithub.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b h1:z78hV3sbSMAUoyUMM0I83AUIT6Hu17AWfgjzIbtrYFc=\ngithub.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b/go.mod h1:lxPUiZwKoFL8DUUmalo2yJJUCxbPKtm8OKfqr2/FTNU=\ngithub.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc h1:PTfri+PuQmWDqERdnNMiD9ZejrlswWrCpBEZgWOiTrc=\ngithub.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc/go.mod h1:cGKTAVKx4SxOuR/czcZ/E2RSJ3sfHs8FpHhQ5CWMf9s=\ngithub.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ=\ngithub.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=\ngithub.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=\ngithub.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=\ngithub.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=\ngithub.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=\ngithub.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE=\ngithub.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI=\ngithub.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0=\ngithub.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4=\ngithub.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo=\ngithub.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4=\ngithub.com/multiformats/go-multiaddr v0.12.2 h1:9G9sTY/wCYajKa9lyfWPmpZAwe6oV+Wb1zcmMS1HG24=\ngithub.com/multiformats/go-multiaddr v0.12.2/go.mod h1:GKyaTYjZRdcUhyOetrxTk9z0cW+jA/YrnqTOvKgi44M=\ngithub.com/multiformats/go-multiaddr-dns v0.3.1 h1:QgQgR+LQVt3NPTjbrLLpsaT2ufAA2y0Mkk+QRVJbW3A=\ngithub.com/multiformats/go-multiaddr-dns v0.3.1/go.mod h1:G/245BRQ6FJGmryJCrOuTdB37AMA5AMOVuO6NY3JwTk=\ngithub.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E=\ngithub.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo=\ngithub.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g=\ngithub.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk=\ngithub.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg=\ngithub.com/multiformats/go-multicodec v0.9.0/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k=\ngithub.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew=\ngithub.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U=\ngithub.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM=\ngithub.com/multiformats/go-multistream v0.5.0 h1:5htLSLl7lvJk3xx3qT/8Zm9J4K8vEOf/QGkvOGQAyiE=\ngithub.com/multiformats/go-multistream v0.5.0/go.mod h1:n6tMZiwiP2wUsR8DgfDWw1dydlEqV3l6N3/GBsX6ILA=\ngithub.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=\ngithub.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8=\ngithub.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=\ngithub.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=\ngithub.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=\ngithub.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY=\ngithub.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM=\ngithub.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=\ngithub.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=\ngithub.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=\ngithub.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk=\ngithub.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=\ngithub.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=\ngithub.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=\ngithub.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=\ngithub.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=\ngithub.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/polydawn/refmt v0.89.0 h1:ADJTApkvkeBZsN0tBTx8QjpD9JkmxbKp0cxfr9qszm4=\ngithub.com/polydawn/refmt v0.89.0/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw=\ngithub.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=\ngithub.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=\ngithub.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=\ngithub.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos=\ngithub.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8=\ngithub.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=\ngithub.com/prometheus/common v0.47.0 h1:p5Cz0FNHo7SnWOmWmoRozVcjEp0bIVU8cV7OShpjL1k=\ngithub.com/prometheus/common v0.47.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=\ngithub.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=\ngithub.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=\ngithub.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=\ngithub.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=\ngithub.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=\ngithub.com/quic-go/quic-go v0.41.0 h1:aD8MmHfgqTURWNJy48IYFg2OnxwHT3JL7ahGs73lb4k=\ngithub.com/quic-go/quic-go v0.41.0/go.mod h1:qCkNjqczPEvgsOnxZ0eCD14lv+B2LHlFAB++CNOh9hA=\ngithub.com/quic-go/webtransport-go v0.6.0 h1:CvNsKqc4W2HljHJnoT+rMmbRJybShZ0YPFDD3NxaZLY=\ngithub.com/quic-go/webtransport-go v0.6.0/go.mod h1:9KjU4AEBqEQidGHNDkZrb8CAa1abRaosM2yGOyiikEc=\ngithub.com/raulk/go-watchdog v1.3.0 h1:oUmdlHxdkXRJlwfG0O9omj8ukerm8MEQavSiDTEtBsk=\ngithub.com/raulk/go-watchdog v1.3.0/go.mod h1:fIvOnLbF0b0ZwkB9YU4mOW9Did//4vPZtDqv66NfsMU=\ngithub.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=\ngithub.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=\ngithub.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=\ngithub.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=\ngithub.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=\ngithub.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=\ngithub.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=\ngithub.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=\ngithub.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=\ngithub.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=\ngithub.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=\ngithub.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=\ngithub.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=\ngithub.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=\ngithub.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=\ngithub.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=\ngithub.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=\ngithub.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=\ngithub.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=\ngithub.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=\ngithub.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=\ngithub.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=\ngithub.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=\ngithub.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=\ngithub.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=\ngithub.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=\ngithub.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=\ngithub.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=\ngithub.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=\ngithub.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=\ngithub.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=\ngithub.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=\ngithub.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM=\ngithub.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=\ngithub.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=\ngithub.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=\ngithub.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=\ngithub.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=\ngithub.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=\ngithub.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=\ngithub.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=\ngithub.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=\ngithub.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=\ngithub.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ=\ngithub.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw=\ngithub.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k=\ngithub.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=\ngo.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=\ngo.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=\ngo.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=\ngo.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=\ngo.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=\ngo.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=\ngo.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=\ngo.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=\ngo.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=\ngo.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=\ngo.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=\ngo.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=\ngo.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=\ngo.uber.org/dig v1.17.1 h1:Tga8Lz8PcYNsWsyHMZ1Vm0OQOUaJNDyvPImgbAu9YSc=\ngo.uber.org/dig v1.17.1/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=\ngo.uber.org/fx v1.20.1 h1:zVwVQGS8zYvhh9Xxcu4w1M6ESyeMzebzj2NbSayZ4Mk=\ngo.uber.org/fx v1.20.1/go.mod h1:iSYNbHf2y55acNCwCXKx7LbWb5WG1Bnue5RDXz1OREg=\ngo.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngo.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=\ngo.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=\ngo.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=\ngo.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=\ngo.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=\ngo.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=\ngo.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=\ngo.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=\ngo.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=\ngo.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=\ngo.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=\ngo4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=\ngolang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=\ngolang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=\ngolang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=\ngolang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE=\ngolang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=\ngolang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=\ngolang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=\ngolang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=\ngolang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=\ngolang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=\ngolang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=\ngolang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=\ngolang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=\ngolang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ=\ngolang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngonum.org/v1/gonum v0.13.0 h1:a0T3bh+7fhRyqeNbiC3qVHYmkiQgit3wnNan/2c0HMM=\ngonum.org/v1/gonum v0.13.0/go.mod h1:/WPYRckkfWrhWefxyYTfrTtQR0KH4iyHNuzxqXAKyAU=\ngoogle.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=\ngoogle.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=\ngoogle.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=\ngoogle.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=\ngoogle.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=\ngoogle.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngoogle.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=\ngoogle.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=\ngopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=\ngopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngrpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=\nhonnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=\nlukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI=\nlukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=\nsourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=\nsourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=\n"
  },
  {
    "path": "examples/pubsub/basic-chat-with-rendezvous/main.go",
    "content": "package main\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\t\"sync\"\n\n\t\"github.com/libp2p/go-libp2p\"\n\tdht \"github.com/libp2p/go-libp2p-kad-dht\"\n\tpubsub \"github.com/libp2p/go-libp2p-pubsub\"\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\tdrouting \"github.com/libp2p/go-libp2p/p2p/discovery/routing\"\n\tdutil \"github.com/libp2p/go-libp2p/p2p/discovery/util\"\n)\n\nvar (\n\ttopicNameFlag = flag.String(\"topicName\", \"applesauce\", \"name of topic to join\")\n)\n\nfunc main() {\n\tflag.Parse()\n\tctx := context.Background()\n\n\th, err := libp2p.New(libp2p.ListenAddrStrings(\"/ip4/0.0.0.0/tcp/0\"))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tgo discoverPeers(ctx, h)\n\n\tps, err := pubsub.NewGossipSub(ctx, h)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\ttopic, err := ps.Join(*topicNameFlag)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tgo streamConsoleTo(ctx, topic)\n\n\tsub, err := topic.Subscribe()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tprintMessagesFrom(ctx, sub)\n}\n\nfunc initDHT(ctx context.Context, h host.Host) *dht.IpfsDHT {\n\t// Start a DHT, for use in peer discovery. We can't just make a new DHT\n\t// client because we want each peer to maintain its own local copy of the\n\t// DHT, so that the bootstrapping node of the DHT can go down without\n\t// inhibiting future peer discovery.\n\tkademliaDHT, err := dht.New(ctx, h)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tif err = kademliaDHT.Bootstrap(ctx); err != nil {\n\t\tpanic(err)\n\t}\n\tvar wg sync.WaitGroup\n\tfor _, peerAddr := range dht.DefaultBootstrapPeers {\n\t\tpeerinfo, _ := peer.AddrInfoFromP2pAddr(peerAddr)\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tif err := h.Connect(ctx, *peerinfo); err != nil {\n\t\t\t\tfmt.Println(\"Bootstrap warning:\", err)\n\t\t\t}\n\t\t}()\n\t}\n\twg.Wait()\n\n\treturn kademliaDHT\n}\n\nfunc discoverPeers(ctx context.Context, h host.Host) {\n\tkademliaDHT := initDHT(ctx, h)\n\troutingDiscovery := drouting.NewRoutingDiscovery(kademliaDHT)\n\tdutil.Advertise(ctx, routingDiscovery, *topicNameFlag)\n\n\t// Look for others who have announced and attempt to connect to them\n\tanyConnected := false\n\tfor !anyConnected {\n\t\tfmt.Println(\"Searching for peers...\")\n\t\tpeerChan, err := routingDiscovery.FindPeers(ctx, *topicNameFlag)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tfor peer := range peerChan {\n\t\t\tif peer.ID == h.ID() {\n\t\t\t\tcontinue // No self connection\n\t\t\t}\n\t\t\terr := h.Connect(ctx, peer)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Printf(\"Failed connecting to %s, error: %s\\n\", peer.ID, err)\n\t\t\t} else {\n\t\t\t\tfmt.Println(\"Connected to:\", peer.ID)\n\t\t\t\tanyConnected = true\n\t\t\t}\n\t\t}\n\t}\n\tfmt.Println(\"Peer discovery complete\")\n}\n\nfunc streamConsoleTo(ctx context.Context, topic *pubsub.Topic) {\n\treader := bufio.NewReader(os.Stdin)\n\tfor {\n\t\ts, err := reader.ReadString('\\n')\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tif err := topic.Publish(ctx, []byte(s)); err != nil {\n\t\t\tfmt.Println(\"### Publish error:\", err)\n\t\t}\n\t}\n}\n\nfunc printMessagesFrom(ctx context.Context, sub *pubsub.Subscription) {\n\tfor {\n\t\tm, err := sub.Next(ctx)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tfmt.Println(m.ReceivedFrom, \": \", string(m.Message.Data))\n\t}\n}\n"
  },
  {
    "path": "examples/testutils/logharness.go",
    "content": "package testutils\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n)\n\n// A LogHarness runs sets of assertions against the log output of a function. Assertions are grouped\n// into sequences of messages that are expected to be found in the log output. Calling one of the Expect\n// methods on the harness adds an expectation to the default sequence of messages. Additional sequences\n// can be created by calling NewSequence.\ntype LogHarness struct {\n\tbuf       bytes.Buffer\n\tsequences []*Sequence\n}\n\ntype Expectation interface {\n\tIsMatch(line string) bool\n\tString() string\n}\n\n// Run executes the function f and captures any output written using Go's standard log. Each sequence\n// of expected messages is then\nfunc (h *LogHarness) Run(t *testing.T, f func()) {\n\t// Capture raw log output\n\tfl := log.Flags()\n\tlog.SetFlags(0)\n\tlog.SetOutput(&h.buf)\n\tf()\n\tlog.SetFlags(fl)\n\tlog.SetOutput(os.Stderr)\n\n\tfor _, seq := range h.sequences {\n\t\tseq.Assert(t, bufio.NewScanner(bytes.NewReader(h.buf.Bytes())))\n\t}\n}\n\n// Expect adds an expectation to the default sequence that the log contains a line equal to s\nfunc (h *LogHarness) Expect(s string) {\n\tif len(h.sequences) == 0 {\n\t\th.sequences = append(h.sequences, &Sequence{name: \"\"})\n\t}\n\th.sequences[0].Expect(s)\n}\n\n// ExpectPrefix adds an to the default sequence expectation that the log contains a line starting with s\nfunc (h *LogHarness) ExpectPrefix(s string) {\n\tif len(h.sequences) == 0 {\n\t\th.sequences = append(h.sequences, &Sequence{name: \"\"})\n\t}\n\th.sequences[0].ExpectPrefix(s)\n}\n\n// NewSequence creates a new sequence of expected log messages\nfunc (h *LogHarness) NewSequence(name string) *Sequence {\n\tseq := &Sequence{name: name}\n\th.sequences = append(h.sequences, seq)\n\treturn seq\n}\n\ntype prefix string\n\nfunc (p prefix) IsMatch(line string) bool {\n\treturn strings.HasPrefix(line, string(p))\n}\n\nfunc (p prefix) String() string {\n\treturn fmt.Sprintf(\"prefix %q\", string(p))\n}\n\ntype text string\n\nfunc (t text) IsMatch(line string) bool {\n\treturn line == string(t)\n}\n\nfunc (t text) String() string {\n\treturn fmt.Sprintf(\"text %q\", string(t))\n}\n\ntype Sequence struct {\n\tname string\n\texp  []Expectation\n}\n\nfunc (seq *Sequence) Assert(t *testing.T, s *bufio.Scanner) {\n\tvar tag string\n\tif seq.name != \"\" {\n\t\ttag = fmt.Sprintf(\"[%s] \", seq.name)\n\t}\n\t// Match raw log lines against expectations\nexploop:\n\tfor _, e := range seq.exp {\n\t\tfor s.Scan() {\n\t\t\tif e.IsMatch(s.Text()) {\n\t\t\t\tt.Logf(\"%ssaw: %s\", tag, s.Text())\n\t\t\t\tcontinue exploop\n\t\t\t}\n\t\t}\n\t\tif s.Err() == nil {\n\t\t\tt.Errorf(\"%sdid not see expected %s\", tag, e.String())\n\t\t\treturn\n\t\t}\n\t}\n}\n\n// Expect adds an expectation that the log contains a line equal to s\nfunc (seq *Sequence) Expect(s string) {\n\tseq.exp = append(seq.exp, text(s))\n}\n\n// ExpectPrefix adds an expectation that the log contains a line starting with s\nfunc (seq *Sequence) ExpectPrefix(s string) {\n\tseq.exp = append(seq.exp, prefix(s))\n}\n"
  },
  {
    "path": "examples/testutils/net.go",
    "content": "package testutils\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"testing\"\n)\n\n// FindFreePort attempts to find an unused tcp port\nfunc FindFreePort(t *testing.T, host string, maxAttempts int) (int, error) {\n\tt.Helper()\n\n\tif host == \"\" {\n\t\thost = \"localhost\"\n\t}\n\n\tfor i := 0; i < maxAttempts; i++ {\n\t\taddr, err := net.ResolveTCPAddr(\"tcp\", net.JoinHostPort(host, \"0\"))\n\t\tif err != nil {\n\t\t\tt.Logf(\"unable to resolve tcp addr: %v\", err)\n\t\t\tcontinue\n\t\t}\n\t\tl, err := net.ListenTCP(\"tcp\", addr)\n\t\tif err != nil {\n\t\t\tl.Close()\n\t\t\tt.Logf(\"unable to listen on addr %q: %v\", addr, err)\n\t\t\tcontinue\n\t\t}\n\n\t\tport := l.Addr().(*net.TCPAddr).Port\n\t\tl.Close()\n\t\treturn port, nil\n\n\t}\n\n\treturn 0, fmt.Errorf(\"no free port found\")\n}\n"
  },
  {
    "path": "fx_options_test.go",
    "content": "package libp2p\n\nimport (\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p/core/event\"\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/identify\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/fx\"\n)\n\nfunc TestGetPeerID(t *testing.T) {\n\tvar id peer.ID\n\thost, err := New(\n\t\tWithFxOption(fx.Populate(&id)),\n\t)\n\trequire.NoError(t, err)\n\tdefer host.Close()\n\n\trequire.Equal(t, host.ID(), id)\n\n}\n\nfunc TestGetEventBus(t *testing.T) {\n\tvar eb event.Bus\n\thost, err := New(\n\t\tNoTransports,\n\t\tWithFxOption(fx.Populate(&eb)),\n\t)\n\trequire.NoError(t, err)\n\tdefer host.Close()\n\n\trequire.NotNil(t, eb)\n}\n\nfunc TestGetHost(t *testing.T) {\n\tvar h host.Host\n\thost, err := New(\n\t\tNoTransports,\n\t\tWithFxOption(fx.Populate(&h)),\n\t)\n\trequire.NoError(t, err)\n\tdefer host.Close()\n\n\trequire.NotNil(t, h)\n}\n\nfunc TestGetIDService(t *testing.T) {\n\tvar id identify.IDService\n\thost, err := New(\n\t\tNoTransports,\n\t\tWithFxOption(fx.Populate(&id)),\n\t)\n\trequire.NoError(t, err)\n\tdefer host.Close()\n\n\trequire.NotNil(t, id)\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/libp2p/go-libp2p\n\ngo 1.25.7\n\nretract v0.26.1 // Tag was applied incorrectly due to a bug in the release workflow.\n\nretract v0.36.0 // Accidentally modified the tag.\n\nrequire (\n\tfilippo.io/keygen v0.0.0-20260114151900-8e2790ea4c5b\n\tgithub.com/benbjohnson/clock v1.3.5\n\tgithub.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c\n\tgithub.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0\n\tgithub.com/flynn/noise v1.1.0\n\tgithub.com/gorilla/websocket v1.5.3\n\tgithub.com/hashicorp/golang-lru/arc/v2 v2.0.7\n\tgithub.com/hashicorp/golang-lru/v2 v2.0.7\n\tgithub.com/huin/goupnp v1.3.0\n\tgithub.com/ipfs/go-cid v0.5.0\n\tgithub.com/ipfs/go-datastore v0.8.2\n\tgithub.com/jackpal/go-nat-pmp v1.0.2\n\tgithub.com/jbenet/go-temp-err-catcher v0.1.0\n\tgithub.com/koron/go-ssdp v0.0.6\n\tgithub.com/libp2p/go-buffer-pool v0.1.0\n\tgithub.com/libp2p/go-flow-metrics v0.2.0\n\tgithub.com/libp2p/go-libp2p-asn-util v0.4.1\n\tgithub.com/libp2p/go-libp2p-testing v0.12.0\n\tgithub.com/libp2p/go-msgio v0.3.0\n\tgithub.com/libp2p/go-netroute v0.4.0\n\tgithub.com/libp2p/go-reuseport v0.4.0\n\tgithub.com/libp2p/go-yamux/v5 v5.0.1\n\tgithub.com/libp2p/zeroconf/v2 v2.2.0\n\tgithub.com/marcopolo/simnet v0.0.4\n\tgithub.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd\n\tgithub.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b\n\tgithub.com/mr-tron/base58 v1.2.0\n\tgithub.com/multiformats/go-base32 v0.1.0\n\tgithub.com/multiformats/go-multiaddr v0.16.0\n\tgithub.com/multiformats/go-multiaddr-dns v0.4.1\n\tgithub.com/multiformats/go-multiaddr-fmt v0.1.0\n\tgithub.com/multiformats/go-multibase v0.2.0\n\tgithub.com/multiformats/go-multicodec v0.9.1\n\tgithub.com/multiformats/go-multihash v0.2.3\n\tgithub.com/multiformats/go-multistream v0.6.1\n\tgithub.com/multiformats/go-varint v0.0.7\n\tgithub.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58\n\tgithub.com/pion/datachannel v1.5.10\n\tgithub.com/pion/ice/v4 v4.0.10\n\tgithub.com/pion/logging v0.2.4\n\tgithub.com/pion/sctp v1.8.39\n\tgithub.com/pion/stun/v3 v3.1.1\n\tgithub.com/pion/webrtc/v4 v4.1.2\n\tgithub.com/prometheus/client_golang v1.22.0\n\tgithub.com/prometheus/client_model v0.6.2\n\tgithub.com/quic-go/quic-go v0.59.0\n\tgithub.com/quic-go/webtransport-go v0.10.0\n\tgithub.com/stretchr/testify v1.11.1\n\tgo.uber.org/fx v1.24.0\n\tgo.uber.org/goleak v1.3.0\n\tgo.uber.org/mock v0.5.2\n\tgolang.org/x/crypto v0.48.0\n\tgolang.org/x/sync v0.19.0\n\tgolang.org/x/sys v0.41.0\n\tgolang.org/x/time v0.12.0\n\tgolang.org/x/tools v0.41.0\n\tgoogle.golang.org/protobuf v1.36.6\n)\n\nrequire (\n\tfilippo.io/bigmod v0.1.1-0.20260103110540-f8a47775ebe5 // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/dunglas/httpsfv v1.1.0 // indirect\n\tgithub.com/google/uuid v1.6.0 // indirect\n\tgithub.com/klauspost/cpuid/v2 v2.2.10 // indirect\n\tgithub.com/miekg/dns v1.1.66 // indirect\n\tgithub.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect\n\tgithub.com/minio/sha256-simd v1.0.1 // indirect\n\tgithub.com/multiformats/go-base36 v0.2.0 // indirect\n\tgithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect\n\tgithub.com/pion/dtls/v3 v3.1.2 // indirect\n\tgithub.com/pion/interceptor v0.1.40 // indirect\n\tgithub.com/pion/mdns/v2 v2.0.7 // indirect\n\tgithub.com/pion/randutil v0.1.0 // indirect\n\tgithub.com/pion/rtcp v1.2.16 // indirect\n\tgithub.com/pion/rtp v1.8.19 // indirect\n\tgithub.com/pion/sdp/v3 v3.0.18 // indirect\n\tgithub.com/pion/srtp/v3 v3.0.6 // indirect\n\tgithub.com/pion/transport/v3 v3.0.7 // indirect\n\tgithub.com/pion/transport/v4 v4.0.1 // indirect\n\tgithub.com/pion/turn/v4 v4.0.2 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgithub.com/prometheus/common v0.64.0 // indirect\n\tgithub.com/prometheus/procfs v0.16.1 // indirect\n\tgithub.com/quic-go/qpack v0.6.0 // indirect\n\tgithub.com/spaolacci/murmur3 v1.1.0 // indirect\n\tgithub.com/wlynxg/anet v0.0.5 // indirect\n\tgo.uber.org/dig v1.19.0 // indirect\n\tgo.uber.org/multierr v1.11.0 // indirect\n\tgo.uber.org/zap v1.27.0 // indirect\n\tgolang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476 // indirect\n\tgolang.org/x/mod v0.32.0 // indirect\n\tgolang.org/x/net v0.50.0 // indirect\n\tgolang.org/x/telemetry v0.0.0-20260109210033-bd525da824e2 // indirect\n\tgolang.org/x/text v0.34.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n\tlukechampine.com/blake3 v1.4.1 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "filippo.io/bigmod v0.1.1-0.20260103110540-f8a47775ebe5 h1:JA0fFr+kxpqTdxR9LOBiTWpGNchqmkcsgmdeJZRclZ0=\nfilippo.io/bigmod v0.1.1-0.20260103110540-f8a47775ebe5/go.mod h1:OjOXDNlClLblvXdwgFFOQFJEocLhhtai8vGLy0JCZlI=\nfilippo.io/keygen v0.0.0-20260114151900-8e2790ea4c5b h1:REI1FbdW71yO56Are4XAxD+OS/e+BQsB3gE4mZRQEXY=\nfilippo.io/keygen v0.0.0-20260114151900-8e2790ea4c5b/go.mod h1:9nnw1SlYHYuPSo/3wjQzNjSbeHlq2NsKo5iEtfJPWP0=\ngithub.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=\ngithub.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=\ngithub.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=\ngithub.com/canonical/go-sp800.90a-drbg v0.0.0-20210314144037-6eeb1040d6c3 h1:oe6fCvaEpkhyW3qAicT0TnGtyht/UrgvOwMcEgLb7Aw=\ngithub.com/canonical/go-sp800.90a-drbg v0.0.0-20210314144037-6eeb1040d6c3/go.mod h1:qdP0gaj0QtgX2RUZhnlVrceJ+Qln8aSlDyJwelLLFeM=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU=\ngithub.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U=\ngithub.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8=\ngithub.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=\ngithub.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=\ngithub.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=\ngithub.com/dunglas/httpsfv v1.1.0 h1:Jw76nAyKWKZKFrpMMcL76y35tOpYHqQPzHQiwDvpe54=\ngithub.com/dunglas/httpsfv v1.1.0/go.mod h1:zID2mqw9mFsnt7YC3vYQ9/cjq30q41W+1AnDwH8TiMg=\ngithub.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg=\ngithub.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=\ngithub.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=\ngithub.com/hashicorp/golang-lru/arc/v2 v2.0.7 h1:QxkVTxwColcduO+LP7eJO56r2hFiG8zEbfAAzRv52KQ=\ngithub.com/hashicorp/golang-lru/arc/v2 v2.0.7/go.mod h1:Pe7gBlGdc8clY5LJ0LpJXMt5AmgmWNH1g+oFFVUHOEc=\ngithub.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=\ngithub.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=\ngithub.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=\ngithub.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=\ngithub.com/ipfs/go-cid v0.5.0 h1:goEKKhaGm0ul11IHA7I6p1GmKz8kEYniqFopaB5Otwg=\ngithub.com/ipfs/go-cid v0.5.0/go.mod h1:0L7vmeNXpQpUS9vt+yEARkJ8rOg43DF3iPgn4GIN0mk=\ngithub.com/ipfs/go-datastore v0.8.2 h1:Jy3wjqQR6sg/LhyY0NIePZC3Vux19nLtg7dx0TVqr6U=\ngithub.com/ipfs/go-datastore v0.8.2/go.mod h1:W+pI1NsUsz3tcsAACMtfC+IZdnQTnC/7VfPoJBQuts0=\ngithub.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk=\ngithub.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps=\ngithub.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=\ngithub.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=\ngithub.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk=\ngithub.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk=\ngithub.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=\ngithub.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=\ngithub.com/koron/go-ssdp v0.0.6 h1:Jb0h04599eq/CY7rB5YEqPS83HmRfHP2azkxMN2rFtU=\ngithub.com/koron/go-ssdp v0.0.6/go.mod h1:0R9LfRJGek1zWTjN3JUNlm5INCDYGpRDfAptnct63fI=\ngithub.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8=\ngithub.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=\ngithub.com/libp2p/go-flow-metrics v0.2.0 h1:EIZzjmeOE6c8Dav0sNv35vhZxATIXWZg6j/C08XmmDw=\ngithub.com/libp2p/go-flow-metrics v0.2.0/go.mod h1:st3qqfu8+pMfh+9Mzqb2GTiwrAGjIPszEjZmtksN8Jc=\ngithub.com/libp2p/go-libp2p-asn-util v0.4.1 h1:xqL7++IKD9TBFMgnLPZR6/6iYhawHKHl950SO9L6n94=\ngithub.com/libp2p/go-libp2p-asn-util v0.4.1/go.mod h1:d/NI6XZ9qxw67b4e+NgpQexCIiFYJjErASrYW4PFDN8=\ngithub.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA=\ngithub.com/libp2p/go-libp2p-testing v0.12.0/go.mod h1:KcGDRXyN7sQCllucn1cOOS+Dmm7ujhfEyXQL5lvkcPg=\ngithub.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0=\ngithub.com/libp2p/go-msgio v0.3.0/go.mod h1:nyRM819GmVaF9LX3l03RMh10QdOroF++NBbxAb0mmDM=\ngithub.com/libp2p/go-netroute v0.4.0 h1:sZZx9hyANYUx9PZyqcgE/E1GUG3iEtTZHUEvdtXT7/Q=\ngithub.com/libp2p/go-netroute v0.4.0/go.mod h1:Nkd5ShYgSMS5MUKy/MU2T57xFoOKvvLR92Lic48LEyA=\ngithub.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s=\ngithub.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU=\ngithub.com/libp2p/go-yamux/v5 v5.0.1 h1:f0WoX/bEF2E8SbE4c/k1Mo+/9z0O4oC/hWEA+nfYRSg=\ngithub.com/libp2p/go-yamux/v5 v5.0.1/go.mod h1:en+3cdX51U0ZslwRdRLrvQsdayFt3TSUKvBGErzpWbU=\ngithub.com/libp2p/zeroconf/v2 v2.2.0 h1:Cup06Jv6u81HLhIj1KasuNM/RHHrJ8T7wOTS4+Tv53Q=\ngithub.com/libp2p/zeroconf/v2 v2.2.0/go.mod h1:fuJqLnUwZTshS3U/bMRJ3+ow/v9oid1n0DmyYyNO1Xs=\ngithub.com/marcopolo/simnet v0.0.4 h1:50Kx4hS9kFGSRIbrt9xUS3NJX33EyPqHVmpXvaKLqrY=\ngithub.com/marcopolo/simnet v0.0.4/go.mod h1:tfQF1u2DmaB6WHODMtQaLtClEf3a296CKQLq5gAsIS0=\ngithub.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk=\ngithub.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU=\ngithub.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=\ngithub.com/miekg/dns v1.1.66 h1:FeZXOS3VCVsKnEAd+wBkjMC3D2K+ww66Cq3VnCINuJE=\ngithub.com/miekg/dns v1.1.66/go.mod h1:jGFzBsSNbJw6z1HYut1RKBKHA9PBdxeHrZG8J+gC2WE=\ngithub.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c h1:bzE/A84HN25pxAuk9Eej1Kz9OUelF97nAc82bDquQI8=\ngithub.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c/go.mod h1:0SQS9kMwD2VsyFEB++InYyBJroV/FRmBgcydeSUcJms=\ngithub.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b h1:z78hV3sbSMAUoyUMM0I83AUIT6Hu17AWfgjzIbtrYFc=\ngithub.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b/go.mod h1:lxPUiZwKoFL8DUUmalo2yJJUCxbPKtm8OKfqr2/FTNU=\ngithub.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc h1:PTfri+PuQmWDqERdnNMiD9ZejrlswWrCpBEZgWOiTrc=\ngithub.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc/go.mod h1:cGKTAVKx4SxOuR/czcZ/E2RSJ3sfHs8FpHhQ5CWMf9s=\ngithub.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ=\ngithub.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=\ngithub.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=\ngithub.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=\ngithub.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=\ngithub.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=\ngithub.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=\ngithub.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE=\ngithub.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI=\ngithub.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0=\ngithub.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4=\ngithub.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo=\ngithub.com/multiformats/go-multiaddr v0.16.0 h1:oGWEVKioVQcdIOBlYM8BH1rZDWOGJSqr9/BKl6zQ4qc=\ngithub.com/multiformats/go-multiaddr v0.16.0/go.mod h1:JSVUmXDjsVFiW7RjIFMP7+Ev+h1DTbiJgVeTV/tcmP0=\ngithub.com/multiformats/go-multiaddr-dns v0.4.1 h1:whi/uCLbDS3mSEUMb1MsoT4uzUeZB0N32yzufqS0i5M=\ngithub.com/multiformats/go-multiaddr-dns v0.4.1/go.mod h1:7hfthtB4E4pQwirrz+J0CcDUfbWzTqEzVyYKKIKpgkc=\ngithub.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E=\ngithub.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo=\ngithub.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g=\ngithub.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk=\ngithub.com/multiformats/go-multicodec v0.9.1 h1:x/Fuxr7ZuR4jJV4Os5g444F7xC4XmyUaT/FWtE+9Zjo=\ngithub.com/multiformats/go-multicodec v0.9.1/go.mod h1:LLWNMtyV5ithSBUo3vFIMaeDy+h3EbkMTek1m+Fybbo=\ngithub.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew=\ngithub.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U=\ngithub.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM=\ngithub.com/multiformats/go-multistream v0.6.1 h1:4aoX5v6T+yWmc2raBHsTvzmFhOI8WVOer28DeBBEYdQ=\ngithub.com/multiformats/go-multistream v0.6.1/go.mod h1:ksQf6kqHAb6zIsyw7Zm+gAuVo57Qbq84E27YlYqavqw=\ngithub.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8=\ngithub.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=\ngithub.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=\ngithub.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=\ngithub.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o=\ngithub.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M=\ngithub.com/pion/dtls/v3 v3.1.2 h1:gqEdOUXLtCGW+afsBLO0LtDD8GnuBBjEy6HRtyofZTc=\ngithub.com/pion/dtls/v3 v3.1.2/go.mod h1:Hw/igcX4pdY69z1Hgv5x7wJFrUkdgHwAn/Q/uo7YHRo=\ngithub.com/pion/ice/v4 v4.0.10 h1:P59w1iauC/wPk9PdY8Vjl4fOFL5B+USq1+xbDcN6gT4=\ngithub.com/pion/ice/v4 v4.0.10/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw=\ngithub.com/pion/interceptor v0.1.40 h1:e0BjnPcGpr2CFQgKhrQisBU7V3GXK6wrfYrGYaU6Jq4=\ngithub.com/pion/interceptor v0.1.40/go.mod h1:Z6kqH7M/FYirg3frjGJ21VLSRJGBXB/KqaTIrdqnOic=\ngithub.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=\ngithub.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=\ngithub.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM=\ngithub.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA=\ngithub.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=\ngithub.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=\ngithub.com/pion/rtcp v1.2.16 h1:fk1B1dNW4hsI78XUCljZJlC4kZOPk67mNRuQ0fcEkSo=\ngithub.com/pion/rtcp v1.2.16/go.mod h1:/as7VKfYbs5NIb4h6muQ35kQF/J0ZVNz2Z3xKoCBYOo=\ngithub.com/pion/rtp v1.8.19 h1:jhdO/3XhL/aKm/wARFVmvTfq0lC/CvN1xwYKmduly3c=\ngithub.com/pion/rtp v1.8.19/go.mod h1:bAu2UFKScgzyFqvUKmbvzSdPr+NGbZtv6UB2hesqXBk=\ngithub.com/pion/sctp v1.8.39 h1:PJma40vRHa3UTO3C4MyeJDQ+KIobVYRZQZ0Nt7SjQnE=\ngithub.com/pion/sctp v1.8.39/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE=\ngithub.com/pion/sdp/v3 v3.0.18 h1:l0bAXazKHpepazVdp+tPYnrsy9dfh7ZbT8DxesH5ZnI=\ngithub.com/pion/sdp/v3 v3.0.18/go.mod h1:ZREGo6A9ZygQ9XkqAj5xYCQtQpif0i6Pa81HOiAdqQ8=\ngithub.com/pion/srtp/v3 v3.0.6 h1:E2gyj1f5X10sB/qILUGIkL4C2CqK269Xq167PbGCc/4=\ngithub.com/pion/srtp/v3 v3.0.6/go.mod h1:BxvziG3v/armJHAaJ87euvkhHqWe9I7iiOy50K2QkhY=\ngithub.com/pion/stun/v3 v3.1.1 h1:CkQxveJ4xGQjulGSROXbXq94TAWu8gIX2dT+ePhUkqw=\ngithub.com/pion/stun/v3 v3.1.1/go.mod h1:qC1DfmcCTQjl9PBaMa5wSn3x9IPmKxSdcCsxBcDBndM=\ngithub.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0=\ngithub.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo=\ngithub.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o=\ngithub.com/pion/transport/v4 v4.0.1/go.mod h1:nEuEA4AD5lPdcIegQDpVLgNoDGreqM/YqmEx3ovP4jM=\ngithub.com/pion/turn/v4 v4.0.2 h1:ZqgQ3+MjP32ug30xAbD6Mn+/K4Sxi3SdNOTFf+7mpps=\ngithub.com/pion/turn/v4 v4.0.2/go.mod h1:pMMKP/ieNAG/fN5cZiN4SDuyKsXtNTr0ccN7IToA1zs=\ngithub.com/pion/webrtc/v4 v4.1.2 h1:mpuUo/EJ1zMNKGE79fAdYNFZBX790KE7kQQpLMjjR54=\ngithub.com/pion/webrtc/v4 v4.1.2/go.mod h1:xsCXiNAmMEjIdFxAYU0MbB3RwRieJsegSB2JZsGN+8U=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=\ngithub.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=\ngithub.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=\ngithub.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=\ngithub.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4=\ngithub.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=\ngithub.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=\ngithub.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=\ngithub.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=\ngithub.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=\ngithub.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=\ngithub.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=\ngithub.com/quic-go/webtransport-go v0.10.0 h1:LqXXPOXuETY5Xe8ITdGisBzTYmUOy5eSj+9n4hLTjHI=\ngithub.com/quic-go/webtransport-go v0.10.0/go.mod h1:LeGIXr5BQKE3UsynwVBeQrU1TPrbh73MGoC6jd+V7ow=\ngithub.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=\ngithub.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=\ngithub.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=\ngithub.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=\ngithub.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=\ngo.uber.org/dig v1.19.0 h1:BACLhebsYdpQ7IROQ1AGPjrXcP5dF80U3gKoFzbaq/4=\ngo.uber.org/dig v1.19.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=\ngo.uber.org/fx v1.24.0 h1:wE8mruvpg2kiiL1Vqd0CC+tr0/24XIB10Iwp2lLWzkg=\ngo.uber.org/fx v1.24.0/go.mod h1:AmDeGyS+ZARGKM4tlH4FY2Jr63VjbEDJHtqXTGP5hbo=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngo.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=\ngo.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=\ngo.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=\ngo.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=\ngo.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=\ngo.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=\ngolang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=\ngolang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=\ngolang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476 h1:bsqhLWFR6G6xiQcb+JoGqdKdRU6WzPWmK8E0jxTjzo4=\ngolang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=\ngolang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=\ngolang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=\ngolang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=\ngolang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=\ngolang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=\ngolang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=\ngolang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/telemetry v0.0.0-20260109210033-bd525da824e2 h1:O1cMQHRfwNpDfDJerqRoE2oD+AFlyid87D40L/OkkJo=\ngolang.org/x/telemetry v0.0.0-20260109210033-bd525da824e2/go.mod h1:b7fPSJ0pKZ3ccUh8gnTONJxhn3c/PS6tyzQvyqw4iA8=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=\ngolang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=\ngolang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=\ngolang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=\ngolang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=\ngoogle.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nlukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=\nlukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=\n"
  },
  {
    "path": "gologshim/gologshim.go",
    "content": "// Package gologshim provides slog-based logging for go-libp2p that works\n// standalone or integrates with go-log for unified log management across\n// IPFS/libp2p applications, without adding go-log as a dependency.\n//\n// # Usage\n//\n// Create loggers using the Logger function:\n//\n//\tvar log = gologshim.Logger(\"subsystem\")\n//\tlog.Debug(\"message\", \"key\", \"value\")\n//\n// # Integration with go-log\n//\n// Applications can optionally connect go-libp2p to go-log by calling SetDefaultHandler:\n//\n//\timport golog \"github.com/ipfs/go-log/v2\"\n//\n//\tfunc init() {\n//\t\tgologshim.SetDefaultHandler(golog.SlogHandler())\n//\t}\n//\n// When integrated, go-libp2p logs use go-log's formatting and can be controlled\n// programmatically via go-log's SetLogLevel(\"subsystem\", \"level\") API to adjust\n// log verbosity per subsystem at runtime without restarting.\n//\n// Note: SlogHandler() works even when GOLOG_CAPTURE_DEFAULT_SLOG=false, making\n// it more reliable than using slog.Default().Handler().\n//\n// # Standalone Usage\n//\n// Without calling SetDefaultHandler, gologshim creates standalone slog handlers\n// writing to stderr. This mode is useful when go-log is not present or when you\n// want independent log configuration via backward-compatible (go-log) environment variables:\n//\n//   - GOLOG_LOG_LEVEL: Set log levels per subsystem (e.g., \"error,ping=debug\")\n//   - GOLOG_LOG_FORMAT/GOLOG_LOG_FMT: Output format (\"json\" or text)\n//   - GOLOG_LOG_ADD_SOURCE: Include source location (default: true)\n//   - GOLOG_LOG_LABELS: Add key=value labels to all logs\n//\n// For integration details, see: https://github.com/ipfs/go-log/blob/master/README.md#slog-integration\n//\n// Note: This package exists as an intermediate solution while go-log uses zap\n// internally. If go-log migrates from zap to native slog, this bridge layer\n// could be simplified or removed entirely.\npackage gologshim\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n)\n\nvar lvlToLower = map[slog.Level]slog.Value{\n\tslog.LevelDebug: slog.StringValue(\"debug\"),\n\tslog.LevelInfo:  slog.StringValue(\"info\"),\n\tslog.LevelWarn:  slog.StringValue(\"warn\"),\n\tslog.LevelError: slog.StringValue(\"error\"),\n}\n\nvar defaultHandler atomic.Pointer[slog.Handler]\n\n// SetDefaultHandler allows an application to change the underlying handler used\n// by gologshim as long as it's changed *before* the first log by the logger.\nfunc SetDefaultHandler(handler slog.Handler) {\n\tdefaultHandler.Store(&handler)\n}\n\n// dynamicHandler delays bridge detection until first log call to handle init order issues\ntype dynamicHandler struct {\n\tsystem  string\n\tconfig  *Config\n\tonce    sync.Once\n\thandler slog.Handler\n}\n\nfunc (h *dynamicHandler) ensureHandler() slog.Handler {\n\th.once.Do(func() {\n\t\tif hPtr := defaultHandler.Load(); hPtr != nil {\n\t\t\th.handler = *hPtr\n\t\t} else {\n\t\t\th.handler = h.createFallbackHandler()\n\t\t}\n\t\tattrs := make([]slog.Attr, 0, 1+len(h.config.labels))\n\t\t// Use \"logger\" attribute for compatibility with go-log's Zap-based format\n\t\t// and existing IPFS/libp2p tooling and dashboards.\n\t\tattrs = append(attrs, slog.String(\"logger\", h.system))\n\t\tattrs = append(attrs, h.config.labels...)\n\t\th.handler = h.handler.WithAttrs(attrs)\n\t})\n\n\treturn h.handler\n}\n\nfunc (h *dynamicHandler) createFallbackHandler() slog.Handler {\n\topts := &slog.HandlerOptions{\n\t\tLevel:     h.config.LevelForSystem(h.system),\n\t\tAddSource: h.config.addSource,\n\t\tReplaceAttr: func(_ []string, a slog.Attr) slog.Attr {\n\t\t\tswitch a.Key {\n\t\t\tcase slog.TimeKey:\n\t\t\t\t// ipfs go-log uses \"ts\" for time\n\t\t\t\ta.Key = \"ts\"\n\t\t\tcase slog.LevelKey:\n\t\t\t\tif lvl, ok := a.Value.Any().(slog.Level); ok {\n\t\t\t\t\t// ipfs go-log uses lowercase level names\n\t\t\t\t\tif s, ok := lvlToLower[lvl]; ok {\n\t\t\t\t\t\ta.Value = s\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn a\n\t\t},\n\t}\n\tif h.config.format == logFormatText {\n\t\treturn slog.NewTextHandler(os.Stderr, opts)\n\t}\n\n\treturn slog.NewJSONHandler(os.Stderr, opts)\n}\n\nfunc (h *dynamicHandler) Enabled(ctx context.Context, lvl slog.Level) bool {\n\treturn h.ensureHandler().Enabled(ctx, lvl)\n}\n\nfunc (h *dynamicHandler) Handle(ctx context.Context, r slog.Record) error {\n\treturn h.ensureHandler().Handle(ctx, r)\n}\n\nfunc (h *dynamicHandler) WithAttrs(attrs []slog.Attr) slog.Handler {\n\treturn h.ensureHandler().WithAttrs(attrs)\n}\n\nfunc (h *dynamicHandler) WithGroup(name string) slog.Handler {\n\treturn h.ensureHandler().WithGroup(name)\n}\n\n// Logger returns a *slog.Logger with a logging level defined by the\n// GOLOG_LOG_LEVEL env var. Supports different levels for different systems. e.g.\n// GOLOG_LOG_LEVEL=foo=info,bar=debug,warn\n// sets the foo system at level info, the bar system at level debug and the\n// fallback level to warn.\nfunc Logger(system string) *slog.Logger {\n\tc := ConfigFromEnv()\n\treturn slog.New(&dynamicHandler{\n\t\tsystem: system,\n\t\tconfig: c,\n\t})\n}\n\ntype logFormat = int\n\nconst (\n\tlogFormatText logFormat = iota\n\tlogFormatJSON\n)\n\ntype Config struct {\n\tfallbackLvl   slog.Level\n\tsystemToLevel map[string]slog.Level\n\tformat        logFormat\n\taddSource     bool\n\tlabels        []slog.Attr\n}\n\nfunc (c *Config) LevelForSystem(system string) slog.Level {\n\tif lvl, ok := c.systemToLevel[system]; ok {\n\t\treturn lvl\n\t}\n\treturn c.fallbackLvl\n}\n\nvar ConfigFromEnv func() *Config = sync.OnceValue(func() *Config {\n\tfallback, systemToLevel, err := parseIPFSGoLogEnv(os.Getenv(\"GOLOG_LOG_LEVEL\"))\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"Failed to parse GOLOG_LOG_LEVEL: %v\", err)\n\t\tfallback = slog.LevelInfo\n\t}\n\tc := &Config{\n\t\tfallbackLvl:   fallback,\n\t\tsystemToLevel: systemToLevel,\n\t\taddSource:     true,\n\t}\n\n\tlogFmt := os.Getenv(\"GOLOG_LOG_FORMAT\")\n\tif logFmt == \"\" {\n\t\tlogFmt = os.Getenv(\"GOLOG_LOG_FMT\")\n\t}\n\tif logFmt == \"json\" {\n\t\tc.format = logFormatJSON\n\t}\n\n\tlogFmt = os.Getenv(\"GOLOG_LOG_ADD_SOURCE\")\n\tif logFmt == \"0\" || logFmt == \"false\" {\n\t\tc.addSource = false\n\t}\n\n\tlabels := os.Getenv(\"GOLOG_LOG_LABELS\")\n\tif labels != \"\" {\n\t\tlabels := strings.Split(labels, \",\")\n\t\tif len(labels) > 0 {\n\t\t\tfor _, label := range labels {\n\t\t\t\tkv := strings.SplitN(label, \"=\", 2)\n\t\t\t\tif len(kv) == 2 {\n\t\t\t\t\tc.labels = append(c.labels, slog.String(kv[0], kv[1]))\n\t\t\t\t} else {\n\t\t\t\t\tfmt.Fprintf(os.Stderr, \"Invalid label format: %s\", label)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn c\n})\n\nfunc parseIPFSGoLogEnv(loggingLevelEnvStr string) (slog.Level, map[string]slog.Level, error) {\n\tfallbackLvl := slog.LevelError\n\tvar systemToLevel map[string]slog.Level\n\tif loggingLevelEnvStr != \"\" {\n\t\tfor kvs := range strings.SplitSeq(loggingLevelEnvStr, \",\") {\n\t\t\tkv := strings.SplitN(kvs, \"=\", 2)\n\t\t\tvar lvl slog.Level\n\t\t\terr := lvl.UnmarshalText([]byte(kv[len(kv)-1]))\n\t\t\tif err != nil {\n\t\t\t\treturn lvl, nil, err\n\t\t\t}\n\t\t\tswitch len(kv) {\n\t\t\tcase 1:\n\t\t\t\tfallbackLvl = lvl\n\t\t\tcase 2:\n\t\t\t\tif systemToLevel == nil {\n\t\t\t\t\tsystemToLevel = make(map[string]slog.Level)\n\t\t\t\t}\n\t\t\t\tsystemToLevel[kv[0]] = lvl\n\t\t\t}\n\t\t}\n\t}\n\treturn fallbackLvl, systemToLevel, nil\n}\n"
  },
  {
    "path": "gologshim/gologshim_test.go",
    "content": "package gologshim\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"log/slog\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n)\n\n// mockBridge simulates go-log's slog bridge for testing duck typing detection\ntype mockBridge struct {\n\tsync.Mutex\n\tslog.Handler\n\tlogs *bytes.Buffer\n}\n\nfunc (m *mockBridge) Handle(_ context.Context, r slog.Record) error {\n\tm.Lock()\n\tdefer m.Unlock()\n\tm.logs.WriteString(r.Message)\n\tm.logs.WriteString(\"\\n\")\n\treturn nil\n}\n\nfunc (m *mockBridge) WithAttrs(_ []slog.Attr) slog.Handler {\n\t// Simple mock - just return self\n\treturn m\n}\n\n// TestGoLogBridgeDetection verifies the duck typing mechanism that detects\n// go-log's slog bridge without requiring a direct dependency. This is the core\n// integration mechanism that allows gologshim to work with go-log when available.\nfunc TestGoLogBridgeDetection(t *testing.T) {\n\t// Save and restore original default\n\toriginalDefault := slog.Default()\n\tdefer slog.SetDefault(originalDefault)\n\n\tt.Run(\"bridge detected when present\", func(t *testing.T) {\n\t\t// When go-log's bridge is installed, Logger() should detect it via\n\t\t// the GoLogBridge() marker method and use it for logging\n\t\tvar buf bytes.Buffer\n\t\tbridge := &mockBridge{\n\t\t\tHandler: slog.NewTextHandler(&buf, nil),\n\t\t\tlogs:    &buf,\n\t\t}\n\t\tSetDefaultHandler(bridge)\n\n\t\t// Create logger - should detect bridge\n\t\tlog := Logger(\"test-subsystem\")\n\t\tlog.Info(\"test message\")\n\n\t\toutput := buf.String()\n\t\tif !strings.Contains(output, \"test message\") {\n\t\t\tt.Errorf(\"Expected bridge to handle log, got: %s\", output)\n\t\t}\n\t})\n\n\tt.Run(\"fallback when bridge not present\", func(_ *testing.T) {\n\t\t// When go-log is not available, Logger() should create a fallback\n\t\t// handler that writes to stderr. This ensures gologshim works standalone.\n\t\tvar buf bytes.Buffer\n\t\thandler := slog.NewTextHandler(&buf, nil)\n\t\tslog.SetDefault(slog.New(handler))\n\n\t\t// Create logger - should use fallback\n\t\tlog := Logger(\"test-fallback\")\n\n\t\t// Fallback writes to stderr, but we can verify it doesn't panic\n\t\tlog.Info(\"fallback message\")\n\t})\n}\n\n// TestLazyBridgeInitialization verifies the lazy handler solves initialization\n// order issues. Package-level logger variables are initialized before go-log's\n// init() runs, so the lazy handler defers bridge detection until first log call.\nfunc TestLazyBridgeInitialization(t *testing.T) {\n\t// Save and restore original default\n\toriginalDefault := slog.Default()\n\tdefer slog.SetDefault(originalDefault)\n\n\t// Simulate initialization order: gologshim.Logger() called before go-log loads\n\tvar initialBuf bytes.Buffer\n\tslog.SetDefault(slog.New(slog.NewTextHandler(&initialBuf, nil)))\n\n\t// Create logger BEFORE bridge is installed (mimics package-level var log = Logger(\"foo\"))\n\tlog := Logger(\"test-lazy\")\n\n\t// Now install the bridge (mimics go-log's init() running)\n\tvar bridgeBuf bytes.Buffer\n\tbridge := &mockBridge{\n\t\tHandler: slog.NewTextHandler(&bridgeBuf, nil),\n\t\tlogs:    &bridgeBuf,\n\t}\n\tSetDefaultHandler(bridge)\n\n\t// Log in goroutine to detect races\n\tgo log.Info(\"lazy init message\")\n\t// First log call should detect the bridge via lazy initialization\n\tlog.Info(\"lazy init message\")\n\n\tbridge.Lock()\n\toutput := bridgeBuf.String()\n\tbridge.Unlock()\n\tif !strings.Contains(output, \"lazy init message\") {\n\t\tt.Errorf(\"Lazy handler should have detected bridge, got: %s\", output)\n\t}\n}\n\n// TestConfigFromEnv verifies environment variable parsing for all GOLOG_* vars.\n// These env vars configure logging behavior and must be compatible with go-log.\nfunc TestConfigFromEnv(t *testing.T) {\n\t// Save and restore env vars\n\toriginalLevel := os.Getenv(\"GOLOG_LOG_LEVEL\")\n\toriginalFormat := os.Getenv(\"GOLOG_LOG_FORMAT\")\n\toriginalLabels := os.Getenv(\"GOLOG_LOG_LABELS\")\n\tdefer func() {\n\t\tos.Setenv(\"GOLOG_LOG_LEVEL\", originalLevel)\n\t\tos.Setenv(\"GOLOG_LOG_FORMAT\", originalFormat)\n\t\tos.Setenv(\"GOLOG_LOG_LABELS\", originalLabels)\n\t}()\n\n\tt.Run(\"parse GOLOG_LOG_LEVEL\", func(t *testing.T) {\n\t\t// GOLOG_LOG_LEVEL supports per-subsystem levels: \"error,ping=debug,swarm=info\"\n\t\tos.Setenv(\"GOLOG_LOG_LEVEL\", \"error,test-subsystem=debug,another=info\")\n\n\t\t// Force re-evaluation by calling parseIPFSGoLogEnv directly\n\t\tfallback, systemToLevel, err := parseIPFSGoLogEnv(os.Getenv(\"GOLOG_LOG_LEVEL\"))\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed to parse GOLOG_LOG_LEVEL: %v\", err)\n\t\t}\n\n\t\tif fallback != slog.LevelError {\n\t\t\tt.Errorf(\"Expected fallback level ERROR, got %v\", fallback)\n\t\t}\n\n\t\tif systemToLevel[\"test-subsystem\"] != slog.LevelDebug {\n\t\t\tt.Errorf(\"Expected test-subsystem level DEBUG, got %v\", systemToLevel[\"test-subsystem\"])\n\t\t}\n\n\t\tif systemToLevel[\"another\"] != slog.LevelInfo {\n\t\t\tt.Errorf(\"Expected another level INFO, got %v\", systemToLevel[\"another\"])\n\t\t}\n\t})\n\n\tt.Run(\"parse GOLOG_LOG_FORMAT\", func(t *testing.T) {\n\t\t// GOLOG_LOG_FORMAT controls output format: \"json\" or text (default)\n\t\tos.Setenv(\"GOLOG_LOG_FORMAT\", \"json\")\n\t\tos.Setenv(\"GOLOG_LOG_LEVEL\", \"\")\n\n\t\t// Note: ConfigFromEnv is cached via sync.OnceValue, so we test the parsing directly\n\t\t// In real usage, this would be set before first Logger() call\n\t\tlogFmt := os.Getenv(\"GOLOG_LOG_FORMAT\")\n\t\tif logFmt != \"json\" {\n\t\t\tt.Errorf(\"Expected format json, got %s\", logFmt)\n\t\t}\n\t})\n\n\tt.Run(\"parse GOLOG_LOG_LABELS\", func(t *testing.T) {\n\t\t// GOLOG_LOG_LABELS adds key=value pairs to all logs: \"app=myapp,dc=us-west\"\n\t\tos.Setenv(\"GOLOG_LOG_LABELS\", \"app=test-app,dc=us-west\")\n\n\t\tlabels := os.Getenv(\"GOLOG_LOG_LABELS\")\n\t\tif !strings.Contains(labels, \"app=test-app\") {\n\t\t\tt.Error(\"Expected labels to contain app=test-app\")\n\t\t}\n\t\tif !strings.Contains(labels, \"dc=us-west\") {\n\t\t\tt.Error(\"Expected labels to contain dc=us-west\")\n\t\t}\n\t})\n}\n\n// TestParseIPFSGoLogEnv verifies the GOLOG_LOG_LEVEL parser handles all valid\n// formats and returns appropriate errors for invalid input. This parser must\n// remain compatible with go-log's format.\nfunc TestParseIPFSGoLogEnv(t *testing.T) {\n\ttests := []struct {\n\t\tname           string\n\t\tinput          string\n\t\twantFallback   slog.Level\n\t\twantSubsystems map[string]slog.Level\n\t\twantErr        bool\n\t}{\n\t\t{\n\t\t\tname:           \"empty string uses default level\",\n\t\t\tinput:          \"\",\n\t\t\twantFallback:   slog.LevelError,\n\t\t\twantSubsystems: nil,\n\t\t\twantErr:        false,\n\t\t},\n\t\t{\n\t\t\tname:           \"fallback only sets global level\",\n\t\t\tinput:          \"debug\",\n\t\t\twantFallback:   slog.LevelDebug,\n\t\t\twantSubsystems: nil,\n\t\t\twantErr:        false,\n\t\t},\n\t\t{\n\t\t\tname:         \"fallback and subsystems\",\n\t\t\tinput:        \"error,ping=debug,swarm=info\",\n\t\t\twantFallback: slog.LevelError,\n\t\t\twantSubsystems: map[string]slog.Level{\n\t\t\t\t\"ping\":  slog.LevelDebug,\n\t\t\t\t\"swarm\": slog.LevelInfo,\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:           \"invalid level returns error\",\n\t\t\tinput:          \"invalid-level\",\n\t\t\twantFallback:   slog.LevelError,\n\t\t\twantSubsystems: nil,\n\t\t\twantErr:        true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tfallback, subsystems, err := parseIPFSGoLogEnv(tt.input)\n\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"parseIPFSGoLogEnv() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif fallback != tt.wantFallback {\n\t\t\t\tt.Errorf(\"fallback = %v, want %v\", fallback, tt.wantFallback)\n\t\t\t}\n\n\t\t\tif len(subsystems) != len(tt.wantSubsystems) {\n\t\t\t\tt.Errorf(\"subsystems length = %d, want %d\", len(subsystems), len(tt.wantSubsystems))\n\t\t\t}\n\n\t\t\tfor k, v := range tt.wantSubsystems {\n\t\t\t\tif subsystems[k] != v {\n\t\t\t\t\tt.Errorf(\"subsystems[%s] = %v, want %v\", k, subsystems[k], v)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "leaky_tests/README.md",
    "content": "Tests that leak goroutines for various reasons. Mostly because libp2p node shutdown logic doesn't run if we fail to construct the node.\n"
  },
  {
    "path": "leaky_tests/leaky_test.go",
    "content": "package leaky_test\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p\"\n)\n\nfunc TestBadTransportConstructor(t *testing.T) {\n\th, err := libp2p.New(libp2p.Transport(func() {}))\n\tif err == nil {\n\t\th.Close()\n\t\tt.Fatal(\"expected an error\")\n\t}\n\tif !strings.Contains(err.Error(), \"_test.go\") {\n\t\tt.Error(\"expected error to contain debugging info\")\n\t}\n}\n"
  },
  {
    "path": "libp2p.go",
    "content": "package libp2p\n\nimport (\n\t\"github.com/libp2p/go-libp2p/config\"\n\t\"github.com/libp2p/go-libp2p/core/host\"\n)\n\n// Config describes a set of settings for a libp2p node.\ntype Config = config.Config\n\n// Option is a libp2p config option that can be given to the libp2p constructor\n// (`libp2p.New`).\ntype Option = config.Option\n\n// ChainOptions chains multiple options into a single option.\nfunc ChainOptions(opts ...Option) Option {\n\treturn func(cfg *Config) error {\n\t\tfor _, opt := range opts {\n\t\t\tif opt == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif err := opt(cfg); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// New constructs a new libp2p node with the given options, falling back on\n// reasonable defaults. The defaults are:\n//\n// - If no transport and listen addresses are provided, the node listens to\n// the multiaddresses \"/ip4/0.0.0.0/tcp/0\" and \"/ip6/::/tcp/0\";\n//\n// - If no transport options are provided, the node uses TCP, websocket and QUIC\n// transport protocols;\n//\n// - If no multiplexer configuration is provided, the node is configured by\n// default to use yamux;\n//\n// - If no security transport is provided, the host uses the go-libp2p's noise\n// and/or tls encrypted transport to encrypt all traffic;\n//\n// - If no peer identity is provided, it generates a random Ed25519 key-pair\n// and derives a new identity from it;\n//\n// - If no peerstore is provided, the host is initialized with an empty\n// peerstore.\n//\n// To stop/shutdown the returned libp2p node, the user needs to call `Close` on the returned Host.\nfunc New(opts ...Option) (host.Host, error) {\n\treturn NewWithoutDefaults(append(opts, FallbackDefaults)...)\n}\n\n// NewWithoutDefaults constructs a new libp2p node with the given options but\n// *without* falling back on reasonable defaults.\n//\n// Warning: This function should not be considered a stable interface. We may\n// choose to add required services at any time and, by using this function, you\n// opt-out of any defaults we may provide.\nfunc NewWithoutDefaults(opts ...Option) (host.Host, error) {\n\tvar cfg Config\n\tif err := cfg.Apply(opts...); err != nil {\n\t\treturn nil, err\n\t}\n\treturn cfg.NewNode()\n}\n"
  },
  {
    "path": "libp2p_test.go",
    "content": "package libp2p\n\nimport (\n\t\"context\"\n\t\"crypto/ecdsa\"\n\t\"crypto/elliptic\"\n\t\"crypto/rand\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"crypto/x509/pkix\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"math/big\"\n\t\"net\"\n\t\"net/netip\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/connmgr\"\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\t\"github.com/libp2p/go-libp2p/core/pnet\"\n\t\"github.com/libp2p/go-libp2p/core/routing\"\n\t\"github.com/libp2p/go-libp2p/core/transport\"\n\trcmgr \"github.com/libp2p/go-libp2p/p2p/host/resource-manager\"\n\t\"github.com/libp2p/go-libp2p/p2p/net/swarm\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/ping\"\n\t\"github.com/libp2p/go-libp2p/p2p/security/noise\"\n\tsectls \"github.com/libp2p/go-libp2p/p2p/security/tls\"\n\tquic \"github.com/libp2p/go-libp2p/p2p/transport/quic\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/quicreuse\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/tcp\"\n\tlibp2pwebrtc \"github.com/libp2p/go-libp2p/p2p/transport/webrtc\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/websocket\"\n\twebtransport \"github.com/libp2p/go-libp2p/p2p/transport/webtransport\"\n\t\"github.com/libp2p/go-yamux/v5\"\n\t\"github.com/pion/webrtc/v4\"\n\tquicgo \"github.com/quic-go/quic-go\"\n\twtgo \"github.com/quic-go/webtransport-go\"\n\t\"go.uber.org/goleak\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestNewHost(t *testing.T) {\n\th, err := makeRandomHost(t, 9191)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\th.Close()\n}\n\nfunc TestTransportConstructor(t *testing.T) {\n\tctor := func(\n\t\t_ host.Host,\n\t\t_ connmgr.ConnectionGater,\n\t\tupgrader transport.Upgrader,\n\t) transport.Transport {\n\t\ttpt, err := tcp.NewTCPTransport(upgrader, nil, nil)\n\t\trequire.NoError(t, err)\n\t\treturn tpt\n\t}\n\th, err := New(Transport(ctor))\n\trequire.NoError(t, err)\n\th.Close()\n}\n\nfunc TestNoListenAddrs(t *testing.T) {\n\th, err := New(NoListenAddrs)\n\trequire.NoError(t, err)\n\tdefer h.Close()\n\tif len(h.Addrs()) != 0 {\n\t\tt.Fatal(\"expected no addresses\")\n\t}\n}\n\nfunc TestNoTransports(t *testing.T) {\n\tctx := context.Background()\n\ta, err := New(NoTransports)\n\trequire.NoError(t, err)\n\tdefer a.Close()\n\n\tb, err := New(ListenAddrStrings(\"/ip4/127.0.0.1/tcp/0\"))\n\trequire.NoError(t, err)\n\tdefer b.Close()\n\n\terr = a.Connect(ctx, peer.AddrInfo{\n\t\tID:    b.ID(),\n\t\tAddrs: b.Addrs(),\n\t})\n\tif err == nil {\n\t\tt.Error(\"dial should have failed as no transports have been configured\")\n\t}\n}\n\nfunc TestInsecure(t *testing.T) {\n\th, err := New(NoSecurity)\n\trequire.NoError(t, err)\n\th.Close()\n}\n\nfunc TestDefaultListenAddrs(t *testing.T) {\n\treTCP := regexp.MustCompile(\"/(ip)[4|6]/((0.0.0.0)|(::))/tcp/\")\n\treQUIC := regexp.MustCompile(\"/(ip)[4|6]/((0.0.0.0)|(::))/udp/([0-9]*)/quic-v1\")\n\treWebRTC := regexp.MustCompile(\"/(ip)[4|6]/((0.0.0.0)|(::))/udp/([0-9]*)/webrtc-direct/certhash/(.*)\")\n\treCircuit := regexp.MustCompile(\"/p2p-circuit\")\n\n\t// Test 1: Setting the correct listen addresses if userDefined.Transport == nil && userDefined.ListenAddrs == nil\n\th, err := New()\n\trequire.NoError(t, err)\n\tfor _, addr := range h.Network().ListenAddresses() {\n\t\tif reTCP.FindStringSubmatchIndex(addr.String()) == nil &&\n\t\t\treQUIC.FindStringSubmatchIndex(addr.String()) == nil &&\n\t\t\treWebRTC.FindStringSubmatchIndex(addr.String()) == nil &&\n\t\t\treCircuit.FindStringSubmatchIndex(addr.String()) == nil {\n\t\t\tt.Error(\"expected ip4 or ip6 or relay interface\")\n\t\t}\n\t}\n\n\th.Close()\n\n\t// Test 2: Listen addr only include relay if user defined transport is passed.\n\th, err = New(Transport(tcp.NewTCPTransport))\n\trequire.NoError(t, err)\n\n\tif len(h.Network().ListenAddresses()) != 1 {\n\t\tt.Error(\"expected one listen addr with user defined transport\")\n\t}\n\tif reCircuit.FindStringSubmatchIndex(h.Network().ListenAddresses()[0].String()) == nil {\n\t\tt.Error(\"expected relay address\")\n\t}\n\th.Close()\n}\n\nfunc makeRandomHost(t *testing.T, port int) (host.Host, error) {\n\tpriv, _, err := crypto.GenerateKeyPair(crypto.RSA, 2048)\n\trequire.NoError(t, err)\n\n\treturn New([]Option{\n\t\tListenAddrStrings(fmt.Sprintf(\"/ip4/127.0.0.1/tcp/%d\", port)),\n\t\tIdentity(priv),\n\t\tDefaultTransports,\n\t\tDefaultMuxers,\n\t\tDefaultSecurity,\n\t\tNATPortMap(),\n\t}...)\n}\n\nfunc TestChainOptions(t *testing.T) {\n\tvar cfg Config\n\tvar optsRun []int\n\toptcount := 0\n\tnewOpt := func() Option {\n\t\tindex := optcount\n\t\toptcount++\n\t\treturn func(_ *Config) error {\n\t\t\toptsRun = append(optsRun, index)\n\t\t\treturn nil\n\t\t}\n\t}\n\n\tif err := cfg.Apply(newOpt(), nil, ChainOptions(newOpt(), newOpt(), ChainOptions(), ChainOptions(nil, newOpt()))); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Make sure we ran all options.\n\tif optcount != 4 {\n\t\tt.Errorf(\"expected to have handled %d options, handled %d\", optcount, len(optsRun))\n\t}\n\n\t// Make sure we ran the options in-order.\n\tfor i, x := range optsRun {\n\t\tif i != x {\n\t\t\tt.Errorf(\"expected opt %d, got opt %d\", i, x)\n\t\t}\n\t}\n}\n\nfunc TestTransportConstructorTCP(t *testing.T) {\n\th, err := New(\n\t\tTransport(tcp.NewTCPTransport, tcp.DisableReuseport()),\n\t\tDisableRelay(),\n\t)\n\trequire.NoError(t, err)\n\tdefer h.Close()\n\trequire.NoError(t, h.Network().Listen(ma.StringCast(\"/ip4/127.0.0.1/tcp/0\")))\n\terr = h.Network().Listen(ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1\"))\n\trequire.Error(t, err)\n\trequire.Contains(t, err.Error(), swarm.ErrNoTransport.Error())\n}\n\nfunc TestTransportConstructorQUIC(t *testing.T) {\n\th, err := New(\n\t\tTransport(quic.NewTransport),\n\t\tDisableRelay(),\n\t)\n\trequire.NoError(t, err)\n\tdefer h.Close()\n\trequire.NoError(t, h.Network().Listen(ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1\")))\n\terr = h.Network().Listen(ma.StringCast(\"/ip4/127.0.0.1/tcp/0\"))\n\trequire.Error(t, err)\n\trequire.Contains(t, err.Error(), swarm.ErrNoTransport.Error())\n}\n\ntype mockTransport struct{}\n\nfunc (m mockTransport) Dial(context.Context, ma.Multiaddr, peer.ID) (transport.CapableConn, error) {\n\tpanic(\"implement me\")\n}\n\nfunc (m mockTransport) CanDial(ma.Multiaddr) bool                       { panic(\"implement me\") }\nfunc (m mockTransport) Listen(ma.Multiaddr) (transport.Listener, error) { panic(\"implement me\") }\nfunc (m mockTransport) Protocols() []int                                { return []int{1337} }\nfunc (m mockTransport) Proxy() bool                                     { panic(\"implement me\") }\n\nvar _ transport.Transport = &mockTransport{}\n\nfunc TestTransportConstructorWithoutOpts(t *testing.T) {\n\tt.Run(\"successful\", func(t *testing.T) {\n\t\tvar called bool\n\t\tconstructor := func() transport.Transport {\n\t\t\tcalled = true\n\t\t\treturn &mockTransport{}\n\t\t}\n\n\t\th, err := New(\n\t\t\tTransport(constructor),\n\t\t\tDisableRelay(),\n\t\t)\n\t\trequire.NoError(t, err)\n\t\trequire.True(t, called, \"expected constructor to be called\")\n\t\tdefer h.Close()\n\t})\n\n\tt.Run(\"with options\", func(t *testing.T) {\n\t\tvar called bool\n\t\tconstructor := func() transport.Transport {\n\t\t\tcalled = true\n\t\t\treturn &mockTransport{}\n\t\t}\n\n\t\t_, err := New(\n\t\t\tTransport(constructor, tcp.DisableReuseport()),\n\t\t\tDisableRelay(),\n\t\t)\n\t\trequire.EqualError(t, err, \"transport constructor doesn't take any options\")\n\t\trequire.False(t, called, \"didn't expected constructor to be called\")\n\t})\n}\n\nfunc TestTransportConstructorWithWrongOpts(t *testing.T) {\n\t_, err := New(\n\t\tTransport(quic.NewTransport, tcp.DisableReuseport()),\n\t\tDisableRelay(),\n\t)\n\trequire.EqualError(t, err, \"transport constructor doesn't take any options\")\n}\n\nfunc TestSecurityConstructor(t *testing.T) {\n\th, err := New(\n\t\tTransport(tcp.NewTCPTransport),\n\t\tSecurity(\"/noisy\", noise.New),\n\t\tSecurity(\"/tls\", sectls.New),\n\t\tDefaultListenAddrs,\n\t\tDisableRelay(),\n\t)\n\trequire.NoError(t, err)\n\tdefer h.Close()\n\n\th1, err := New(\n\t\tNoListenAddrs,\n\t\tTransport(tcp.NewTCPTransport),\n\t\tSecurity(\"/noise\", noise.New), // different name\n\t\tDisableRelay(),\n\t)\n\trequire.NoError(t, err)\n\tdefer h1.Close()\n\n\th2, err := New(\n\t\tNoListenAddrs,\n\t\tTransport(tcp.NewTCPTransport),\n\t\tSecurity(\"/noisy\", noise.New),\n\t\tDisableRelay(),\n\t)\n\trequire.NoError(t, err)\n\tdefer h2.Close()\n\n\tai := peer.AddrInfo{\n\t\tID:    h.ID(),\n\t\tAddrs: h.Addrs(),\n\t}\n\terr = h1.Connect(context.Background(), ai)\n\trequire.Error(t, err)\n\trequire.Contains(t, err.Error(), \"failed to negotiate security protocol\")\n\trequire.NoError(t, h2.Connect(context.Background(), ai))\n}\n\nfunc TestTransportConstructorWebTransport(t *testing.T) {\n\th, err := New(\n\t\tTransport(webtransport.New),\n\t\tDisableRelay(),\n\t)\n\trequire.NoError(t, err)\n\tdefer h.Close()\n\trequire.NoError(t, h.Network().Listen(ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1/webtransport\")))\n\terr = h.Network().Listen(ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1/\"))\n\trequire.Error(t, err)\n\trequire.Contains(t, err.Error(), swarm.ErrNoTransport.Error())\n}\n\nfunc TestTransportCustomAddressWebTransport(t *testing.T) {\n\tcustomAddr, err := ma.NewMultiaddr(\"/ip4/127.0.0.1/udp/0/quic-v1/webtransport\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\th, err := New(\n\t\tTransport(webtransport.New),\n\t\tListenAddrs(customAddr),\n\t\tDisableRelay(),\n\t\tAddrsFactory(func(_ []ma.Multiaddr) []ma.Multiaddr {\n\t\t\treturn []ma.Multiaddr{customAddr}\n\t\t}),\n\t)\n\trequire.NoError(t, err)\n\tdefer h.Close()\n\trequire.NoError(t, h.Network().Listen(ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1/webtransport\")))\n\taddrs := h.Addrs()\n\trequire.Len(t, addrs, 1)\n\trequire.NotEqual(t, addrs[0], customAddr)\n\trestOfAddr, lastComp := ma.SplitLast(addrs[0])\n\trestOfAddr, secondToLastComp := ma.SplitLast(restOfAddr)\n\trequire.Equal(t, ma.P_CERTHASH, lastComp.Protocol().Code)\n\trequire.Equal(t, ma.P_CERTHASH, secondToLastComp.Protocol().Code)\n\trequire.True(t, restOfAddr.Equal(customAddr))\n}\n\n// TestTransportCustomAddressWebTransportDoesNotStall tests that if the user\n// manually returns a webtransport address from AddrsFactory, but we aren't\n// listening on a webtranport address, we don't stall.\nfunc TestTransportCustomAddressWebTransportDoesNotStall(t *testing.T) {\n\tcustomAddr, err := ma.NewMultiaddr(\"/ip4/127.0.0.1/udp/0/quic-v1/webtransport\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\th, err := New(\n\t\tTransport(webtransport.New),\n\t\t// Purposely not listening on the custom address so that we make sure the node doesn't stall if it fails to add a certhash to the multiaddr\n\t\t// ListenAddrs(customAddr),\n\t\tDisableRelay(),\n\t\tAddrsFactory(func(_ []ma.Multiaddr) []ma.Multiaddr {\n\t\t\treturn []ma.Multiaddr{customAddr}\n\t\t}),\n\t)\n\trequire.NoError(t, err)\n\tdefer h.Close()\n\taddrs := h.Addrs()\n\trequire.Len(t, addrs, 1)\n\t_, lastComp := ma.SplitLast(addrs[0])\n\trequire.NotEqual(t, ma.P_CERTHASH, lastComp.Protocol().Code)\n\t// We did not add the certhash to the multiaddr\n\trequire.Equal(t, addrs[0], customAddr)\n}\n\ntype mockPeerRouting struct {\n\tqueried []peer.ID\n}\n\nfunc (r *mockPeerRouting) FindPeer(_ context.Context, id peer.ID) (peer.AddrInfo, error) {\n\tr.queried = append(r.queried, id)\n\treturn peer.AddrInfo{}, errors.New(\"mock peer routing error\")\n}\n\nfunc TestRoutedHost(t *testing.T) {\n\tmockRouter := &mockPeerRouting{}\n\th, err := New(\n\t\tNoListenAddrs,\n\t\tRouting(func(host.Host) (routing.PeerRouting, error) { return mockRouter, nil }),\n\t\tDisableRelay(),\n\t)\n\trequire.NoError(t, err)\n\tdefer h.Close()\n\n\tpriv, _, err := crypto.GenerateEd25519Key(rand.Reader)\n\trequire.NoError(t, err)\n\tid, err := peer.IDFromPrivateKey(priv)\n\trequire.NoError(t, err)\n\trequire.EqualError(t, h.Connect(context.Background(), peer.AddrInfo{ID: id}), \"mock peer routing error\")\n\trequire.Equal(t, []peer.ID{id}, mockRouter.queried)\n}\n\nfunc TestAutoNATService(t *testing.T) {\n\th, err := New(EnableNATService())\n\trequire.NoError(t, err)\n\th.Close()\n}\n\nfunc TestInsecureConstructor(t *testing.T) {\n\th, err := New(\n\t\tEnableNATService(),\n\t\tNoSecurity,\n\t)\n\trequire.NoError(t, err)\n\th.Close()\n\n\th, err = New(\n\t\tNoSecurity,\n\t)\n\trequire.NoError(t, err)\n\th.Close()\n}\n\nfunc TestAutoNATv2Service(t *testing.T) {\n\th, err := New(EnableAutoNATv2())\n\trequire.NoError(t, err)\n\th.Close()\n}\n\nfunc TestDisableIdentifyAddressDiscovery(t *testing.T) {\n\th, err := New(DisableIdentifyAddressDiscovery())\n\trequire.NoError(t, err)\n\th.Close()\n}\n\nfunc TestMain(m *testing.M) {\n\tgoleak.VerifyTestMain(\n\t\tm,\n\t\t// This will return eventually (5s timeout) but doesn't take a context.\n\t\tgoleak.IgnoreAnyFunction(\"github.com/koron/go-ssdp.Search\"),\n\t\tgoleak.IgnoreAnyFunction(\"github.com/pion/sctp.(*Stream).SetReadDeadline.func1\"),\n\t\t// Stats\n\t\tgoleak.IgnoreTopFunction(\"go.opencensus.io/stats/view.(*worker).start\"),\n\t\t// nat-pmp\n\t\tgoleak.IgnoreAnyFunction(\"github.com/jackpal/go-nat-pmp.(*Client).GetExternalAddress\"),\n\t)\n}\n\nfunc TestDialCircuitAddrWithWrappedResourceManager(t *testing.T) {\n\trelay, err := New(EnableRelayService(), ForceReachabilityPublic())\n\trequire.NoError(t, err)\n\tdefer relay.Close()\n\n\tpeerBehindRelay, err := New(\n\t\tEnableAutoRelayWithStaticRelays([]peer.AddrInfo{{ID: relay.ID(), Addrs: relay.Addrs()}}),\n\t\tForceReachabilityPrivate())\n\trequire.NoError(t, err)\n\tdefer peerBehindRelay.Close()\n\n\t// Use a wrapped resource manager to test that the circuit dialing works\n\t// with it. Look at the PR introducing this test for context\n\ttype wrappedRcmgr struct{ network.ResourceManager }\n\tmgr, err := rcmgr.NewResourceManager(rcmgr.NewFixedLimiter(rcmgr.DefaultLimits.AutoScale()))\n\trequire.NoError(t, err)\n\twmgr := wrappedRcmgr{mgr}\n\th, err := New(ResourceManager(wmgr))\n\trequire.NoError(t, err)\n\tdefer h.Close()\n\n\th.Peerstore().AddAddrs(relay.ID(), relay.Addrs(), 10*time.Minute)\n\th.Peerstore().AddAddr(peerBehindRelay.ID(),\n\t\tma.StringCast(\n\t\t\tfmt.Sprintf(\"/p2p/%s/p2p-circuit\", relay.ID()),\n\t\t),\n\t\tpeerstore.TempAddrTTL,\n\t)\n\n\trequire.Eventually(t, func() bool {\n\t\tctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)\n\t\tdefer cancel()\n\t\tres := <-ping.Ping(ctx, h, peerBehindRelay.ID())\n\t\treturn res.Error == nil\n\t}, 5*time.Second, 50*time.Millisecond)\n}\n\nfunc TestHostAddrsFactoryAddsCerthashes(t *testing.T) {\n\taddr := ma.StringCast(\"/ip4/1.2.3.4/udp/1/quic-v1/webtransport\")\n\th, err := New(\n\t\tAddrsFactory(func(_ []ma.Multiaddr) []ma.Multiaddr {\n\t\t\treturn []ma.Multiaddr{addr}\n\t\t}),\n\t)\n\trequire.NoError(t, err)\n\trequire.Eventually(t, func() bool {\n\t\taddrs := h.Addrs()\n\t\tfor _, a := range addrs {\n\t\t\tfirst, last := ma.SplitFunc(a, func(c ma.Component) bool {\n\t\t\t\treturn c.Protocol().Code == ma.P_CERTHASH\n\t\t\t})\n\t\t\tif addr.Equal(first) && last != nil {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\treturn false\n\t}, 5*time.Second, 50*time.Millisecond)\n\th.Close()\n}\n\nfunc newRandomPort(t *testing.T) string {\n\tt.Helper()\n\t// Find an available port\n\tc, err := net.ListenUDP(\"udp4\", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})\n\trequire.NoError(t, err)\n\tc.LocalAddr().Network()\n\tipPort := netip.MustParseAddrPort(c.LocalAddr().String())\n\tport := strconv.Itoa(int(ipPort.Port()))\n\trequire.NoError(t, c.Close())\n\treturn port\n}\n\nfunc TestWebRTCReuseAddrWithQUIC(t *testing.T) {\n\tport := newRandomPort(t)\n\torder := [][]string{\n\t\t{\"/ip4/127.0.0.1/udp/\" + port + \"/quic-v1\", \"/ip4/127.0.0.1/udp/\" + port + \"/webrtc-direct\"},\n\t\t{\"/ip4/127.0.0.1/udp/\" + port + \"/webrtc-direct\", \"/ip4/127.0.0.1/udp/\" + port + \"/quic-v1\"},\n\t\t// We do not support WebRTC automatically reusing QUIC addresses if port is not specified, yet.\n\t\t// {\"/ip4/127.0.0.1/udp/0/webrtc-direct\", \"/ip4/127.0.0.1/udp/0/quic-v1\"},\n\t}\n\tfor i, addrs := range order {\n\t\tt.Run(\"Order \"+strconv.Itoa(i), func(t *testing.T) {\n\t\t\th1, err := New(ListenAddrStrings(addrs...), Transport(quic.NewTransport), Transport(libp2pwebrtc.New))\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer h1.Close()\n\n\t\t\tseenPorts := make(map[string]struct{})\n\t\t\tfor _, addr := range h1.Addrs() {\n\t\t\t\ts, err := addr.ValueForProtocol(ma.P_UDP)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tseenPorts[s] = struct{}{}\n\t\t\t}\n\t\t\trequire.Len(t, seenPorts, 1)\n\n\t\t\tquicClient, err := New(NoListenAddrs, Transport(quic.NewTransport))\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer quicClient.Close()\n\n\t\t\twebrtcClient, err := New(NoListenAddrs, Transport(libp2pwebrtc.New))\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer webrtcClient.Close()\n\n\t\t\tfor _, client := range []host.Host{quicClient, webrtcClient} {\n\t\t\t\terr := client.Connect(context.Background(), peer.AddrInfo{ID: h1.ID(), Addrs: h1.Addrs()})\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\n\t\t\tt.Run(\"quic client can connect\", func(t *testing.T) {\n\t\t\t\tctx := t.Context()\n\t\t\t\tp := ping.NewPingService(quicClient)\n\t\t\t\tresCh := p.Ping(ctx, h1.ID())\n\t\t\t\tres := <-resCh\n\t\t\t\trequire.NoError(t, res.Error)\n\t\t\t})\n\n\t\t\tt.Run(\"webrtc client can connect\", func(t *testing.T) {\n\t\t\t\tctx := t.Context()\n\t\t\t\tp := ping.NewPingService(webrtcClient)\n\t\t\t\tresCh := p.Ping(ctx, h1.ID())\n\t\t\t\tres := <-resCh\n\t\t\t\trequire.NoError(t, res.Error)\n\t\t\t})\n\t\t})\n\t}\n\n\tswapPort := func(addrStrs []string, oldPort, newPort string) []string {\n\t\tout := make([]string, 0, len(addrStrs))\n\t\tfor _, addrStr := range addrStrs {\n\t\t\tout = append(out, strings.Replace(addrStr, oldPort, newPort, 1))\n\t\t}\n\t\treturn out\n\t}\n\n\tt.Run(\"setup with no reuseport. Should fail\", func(t *testing.T) {\n\t\toldPort := port\n\t\tnewPort := newRandomPort(t)\n\t\th1, err := New(ListenAddrStrings(swapPort(order[0], oldPort, newPort)...), Transport(quic.NewTransport), Transport(libp2pwebrtc.New), QUICReuse(quicreuse.NewConnManager, quicreuse.DisableReuseport()))\n\t\trequire.NoError(t, err) // It's a bug/feature that swarm.Listen does not error if at least one transport succeeds in listening.\n\t\tdefer h1.Close()\n\t\t// Check that webrtc did fail to listen\n\t\trequire.Equal(t, 1, len(h1.Addrs()))\n\t\trequire.Contains(t, h1.Addrs()[0].String(), \"quic-v1\")\n\t})\n\n\tt.Run(\"setup with autonat\", func(t *testing.T) {\n\t\toldPort := port\n\t\tnewPort := newRandomPort(t)\n\t\th1, err := New(EnableAutoNATv2(), ListenAddrStrings(swapPort(order[0], oldPort, newPort)...), Transport(quic.NewTransport), Transport(libp2pwebrtc.New), QUICReuse(quicreuse.NewConnManager, quicreuse.DisableReuseport()))\n\t\trequire.NoError(t, err) // It's a bug/feature that swarm.Listen does not error if at least one transport succeeds in listening.\n\t\tdefer h1.Close()\n\t\t// Check that webrtc did fail to listen\n\t\trequire.Equal(t, 1, len(h1.Addrs()))\n\t\trequire.Contains(t, h1.Addrs()[0].String(), \"quic-v1\")\n\t})\n}\n\nfunc TestUseCorrectTransportForDialOut(t *testing.T) {\n\tlistAddrOrder := [][]string{\n\t\t{\"/ip4/127.0.0.1/udp/0/quic-v1\", \"/ip4/127.0.0.1/udp/0/quic-v1/webtransport\"},\n\t\t{\"/ip4/127.0.0.1/udp/0/quic-v1/webtransport\", \"/ip4/127.0.0.1/udp/0/quic-v1\"},\n\t\t{\"/ip4/0.0.0.0/udp/0/quic-v1\", \"/ip4/0.0.0.0/udp/0/quic-v1/webtransport\"},\n\t\t{\"/ip4/0.0.0.0/udp/0/quic-v1/webtransport\", \"/ip4/0.0.0.0/udp/0/quic-v1\"},\n\t}\n\tfor _, order := range listAddrOrder {\n\t\th1, err := New(ListenAddrStrings(order...), Transport(quic.NewTransport), Transport(webtransport.New))\n\t\trequire.NoError(t, err)\n\t\tt.Cleanup(func() {\n\t\t\th1.Close()\n\t\t})\n\n\t\tgo func() {\n\t\t\th1.SetStreamHandler(\"/echo-port\", func(s network.Stream) {\n\t\t\t\tm := s.Conn().RemoteMultiaddr()\n\t\t\t\tv, err := m.ValueForProtocol(ma.P_UDP)\n\t\t\t\tif err != nil {\n\t\t\t\t\ts.Reset()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\ts.Write([]byte(v))\n\t\t\t\ts.Close()\n\t\t\t})\n\t\t}()\n\n\t\tfor _, addr := range h1.Addrs() {\n\t\t\tt.Run(\"order \"+strings.Join(order, \",\")+\" Dial to \"+addr.String(), func(t *testing.T) {\n\t\t\t\th2, err := New(ListenAddrStrings(\n\t\t\t\t\t\"/ip4/0.0.0.0/udp/0/quic-v1\",\n\t\t\t\t\t\"/ip4/0.0.0.0/udp/0/quic-v1/webtransport\",\n\t\t\t\t), Transport(quic.NewTransport), Transport(webtransport.New))\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tdefer h2.Close()\n\t\t\t\tt.Log(\"H2 Addrs\", h2.Addrs())\n\t\t\t\tvar myExpectedDialOutAddr ma.Multiaddr\n\t\t\t\taddrIsWT, _ := webtransport.IsWebtransportMultiaddr(addr)\n\t\t\t\tisLocal := func(a ma.Multiaddr) bool {\n\t\t\t\t\treturn strings.Contains(a.String(), \"127.0.0.1\")\n\t\t\t\t}\n\t\t\t\taddrIsLocal := isLocal(addr)\n\t\t\t\tfor _, a := range h2.Addrs() {\n\t\t\t\t\taIsWT, _ := webtransport.IsWebtransportMultiaddr(a)\n\t\t\t\t\tif addrIsWT == aIsWT && isLocal(a) == addrIsLocal {\n\t\t\t\t\t\tmyExpectedDialOutAddr = a\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\terr = h2.Connect(context.Background(), peer.AddrInfo{ID: h1.ID(), Addrs: []ma.Multiaddr{addr}})\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\ts, err := h2.NewStream(context.Background(), h1.ID(), \"/echo-port\")\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tport, err := io.ReadAll(s)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tmyExpectedPort, err := myExpectedDialOutAddr.ValueForProtocol(ma.P_UDP)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, myExpectedPort, string(port))\n\t\t\t})\n\t\t}\n\t}\n}\n\nfunc TestCircuitBehindWSS(t *testing.T) {\n\trelayTLSConf := getTLSConf(t, net.IPv4(127, 0, 0, 1), time.Now(), time.Now().Add(time.Hour))\n\tserverNameChan := make(chan string, 2) // Channel that returns what server names the client hello specified\n\trelayTLSConf.GetConfigForClient = func(chi *tls.ClientHelloInfo) (*tls.Config, error) {\n\t\tserverNameChan <- chi.ServerName\n\t\treturn relayTLSConf, nil\n\t}\n\n\trelay, err := New(\n\t\tEnableRelayService(),\n\t\tForceReachabilityPublic(),\n\t\tTransport(websocket.New, websocket.WithTLSConfig(relayTLSConf)),\n\t\tListenAddrStrings(\"/ip4/127.0.0.1/tcp/0/wss\"),\n\t)\n\trequire.NoError(t, err)\n\tdefer relay.Close()\n\n\trelayAddrPort, _ := relay.Addrs()[0].ValueForProtocol(ma.P_TCP)\n\trelayAddrWithSNIString := fmt.Sprintf(\n\t\t\"/dns4/localhost/tcp/%s/wss\", relayAddrPort,\n\t)\n\trelayAddrWithSNI := []ma.Multiaddr{ma.StringCast(relayAddrWithSNIString)}\n\n\th, err := New(\n\t\tNoListenAddrs,\n\t\tEnableRelay(),\n\t\tTransport(websocket.New, websocket.WithTLSClientConfig(&tls.Config{InsecureSkipVerify: true})),\n\t\tForceReachabilityPrivate())\n\trequire.NoError(t, err)\n\tdefer h.Close()\n\n\tpeerBehindRelay, err := New(\n\t\tNoListenAddrs,\n\t\tTransport(websocket.New, websocket.WithTLSClientConfig(&tls.Config{InsecureSkipVerify: true})),\n\t\tEnableRelay(),\n\t\tEnableAutoRelayWithStaticRelays([]peer.AddrInfo{{ID: relay.ID(), Addrs: relayAddrWithSNI}}),\n\t\tForceReachabilityPrivate())\n\trequire.NoError(t, err)\n\tdefer peerBehindRelay.Close()\n\n\trequire.Equal(t,\n\t\t\"localhost\",\n\t\t<-serverNameChan, // The server connects to the relay\n\t)\n\n\t// Connect to the peer behind the relay\n\th.Connect(context.Background(), peer.AddrInfo{\n\t\tID: peerBehindRelay.ID(),\n\t\tAddrs: []ma.Multiaddr{ma.StringCast(\n\t\t\tfmt.Sprintf(\"%s/p2p/%s/p2p-circuit\", relayAddrWithSNIString, relay.ID()),\n\t\t)},\n\t})\n\trequire.NoError(t, err)\n\n\trequire.Equal(t,\n\t\t\"localhost\",\n\t\t<-serverNameChan, // The client connects to the relay and sends the SNI\n\t)\n}\n\n// getTLSConf is a helper to generate a self-signed TLS config\nfunc getTLSConf(t *testing.T, ip net.IP, start, end time.Time) *tls.Config {\n\tt.Helper()\n\tcertTempl := &x509.Certificate{\n\t\tSerialNumber:          big.NewInt(1234),\n\t\tSubject:               pkix.Name{Organization: []string{\"websocket\"}},\n\t\tNotBefore:             start,\n\t\tNotAfter:              end,\n\t\tIsCA:                  true,\n\t\tExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},\n\t\tKeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,\n\t\tBasicConstraintsValid: true,\n\t\tIPAddresses:           []net.IP{ip},\n\t}\n\tpriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)\n\trequire.NoError(t, err)\n\tcaBytes, err := x509.CreateCertificate(rand.Reader, certTempl, certTempl, &priv.PublicKey, priv)\n\trequire.NoError(t, err)\n\tcert, err := x509.ParseCertificate(caBytes)\n\trequire.NoError(t, err)\n\treturn &tls.Config{\n\t\tCertificates: []tls.Certificate{{\n\t\t\tCertificate: [][]byte{cert.Raw},\n\t\t\tPrivateKey:  priv,\n\t\t\tLeaf:        cert,\n\t\t}},\n\t}\n}\n\nfunc TestSharedTCPAddr(t *testing.T) {\n\th, err := New(\n\t\tShareTCPListener(),\n\t\tTransport(tcp.NewTCPTransport),\n\t\tTransport(websocket.New),\n\t\tListenAddrStrings(\"/ip4/0.0.0.0/tcp/8188\"),\n\t\tListenAddrStrings(\"/ip4/0.0.0.0/tcp/8188/ws\"),\n\t)\n\trequire.NoError(t, err)\n\tdefer h.Close()\n\tsawTCP := false\n\tsawWS := false\n\tfor _, addr := range h.Addrs() {\n\t\tif strings.HasSuffix(addr.String(), \"/tcp/8188\") {\n\t\t\tsawTCP = true\n\t\t}\n\t\tif strings.HasSuffix(addr.String(), \"/tcp/8188/ws\") {\n\t\t\tsawWS = true\n\t\t}\n\t}\n\trequire.True(t, sawTCP)\n\trequire.True(t, sawWS)\n\n\t_, err = New(\n\t\tShareTCPListener(),\n\t\tTransport(tcp.NewTCPTransport),\n\t\tTransport(websocket.New),\n\t\tPrivateNetwork(pnet.PSK([]byte{1, 2, 3})),\n\t)\n\trequire.ErrorContains(t, err, \"cannot use shared TCP listener with PSK\")\n}\n\nfunc TestCustomTCPDialer(t *testing.T) {\n\texpectedErr := errors.New(\"custom dialer called, but not implemented\")\n\tcustomDialer := func(_ ma.Multiaddr) (tcp.ContextDialer, error) {\n\t\t// Normally a user would implement this by returning a custom dialer\n\t\t// Here, we just test that this is called.\n\t\treturn nil, expectedErr\n\t}\n\n\th, err := New(\n\t\tTransport(tcp.NewTCPTransport, tcp.WithDialerForAddr(customDialer)),\n\t)\n\trequire.NoError(t, err)\n\tdefer h.Close()\n\n\tvar randID peer.ID\n\tpriv, _, err := crypto.GenerateKeyPair(crypto.Ed25519, 256)\n\trequire.NoError(t, err)\n\trandID, err = peer.IDFromPrivateKey(priv)\n\trequire.NoError(t, err)\n\n\terr = h.Connect(context.Background(), peer.AddrInfo{\n\t\tID: randID,\n\t\t// This won't actually be dialed since we return an error above\n\t\tAddrs: []ma.Multiaddr{ma.StringCast(\"/ip4/1.2.3.4/tcp/4\")},\n\t})\n\trequire.ErrorContains(t, err, expectedErr.Error())\n}\n\nfunc TestBasicHostInterfaceAssertion(t *testing.T) {\n\tmockRouter := &mockPeerRouting{}\n\th, err := New(\n\t\tNoListenAddrs,\n\t\tRouting(func(host.Host) (routing.PeerRouting, error) { return mockRouter, nil }),\n\t\tDisableRelay(),\n\t)\n\trequire.NoError(t, err)\n\tdefer h.Close()\n\n\trequire.NotNil(t, h)\n\trequire.NotEmpty(t, h.ID())\n\n\t_, ok := h.(interface{ AllAddrs() []ma.Multiaddr })\n\trequire.True(t, ok)\n}\n\nfunc BenchmarkAllAddrs(b *testing.B) {\n\th, err := New()\n\n\taddrsHost := h.(interface{ AllAddrs() []ma.Multiaddr })\n\trequire.NoError(b, err)\n\tdefer h.Close()\n\n\tb.ReportAllocs()\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\taddrsHost.AllAddrs()\n\t}\n}\n\nfunc TestConnAs(t *testing.T) {\n\ttype testCase struct {\n\t\tname       string\n\t\tlistenAddr string\n\t\ttestAs     func(t *testing.T, c network.Conn)\n\t}\n\n\ttestCases := []testCase{\n\t\t{\n\t\t\t\"QUIC\",\n\t\t\t\"/ip4/0.0.0.0/udp/0/quic-v1\",\n\t\t\tfunc(t *testing.T, c network.Conn) {\n\t\t\t\tvar quicConn *quicgo.Conn\n\t\t\t\trequire.True(t, c.As(&quicConn))\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"TCP+Yamux\",\n\t\t\t\"/ip4/0.0.0.0/tcp/0\",\n\t\t\tfunc(t *testing.T, c network.Conn) {\n\t\t\t\tvar yamuxSession *yamux.Session\n\t\t\t\trequire.True(t, c.As(&yamuxSession))\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"WebRTC\",\n\t\t\t\"/ip4/0.0.0.0/udp/0/webrtc-direct\",\n\t\t\tfunc(t *testing.T, c network.Conn) {\n\t\t\t\tvar webrtcPC *webrtc.PeerConnection\n\t\t\t\trequire.True(t, c.As(&webrtcPC))\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"WebTransport Session\",\n\t\t\t\"/ip4/0.0.0.0/udp/0/quic-v1/webtransport\",\n\t\t\tfunc(t *testing.T, c network.Conn) {\n\t\t\t\tvar s *wtgo.Session\n\t\t\t\trequire.True(t, c.As(&s))\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"WebTransport QUIC Conn\",\n\t\t\t\"/ip4/0.0.0.0/udp/0/quic-v1/webtransport\",\n\t\t\tfunc(t *testing.T, c network.Conn) {\n\t\t\t\tvar quicConn *quicgo.Conn\n\t\t\t\trequire.True(t, c.As(&quicConn))\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\th1, err := New(ListenAddrStrings(\n\t\t\t\ttc.listenAddr,\n\t\t\t))\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer h1.Close()\n\t\t\th2, err := New(ListenAddrStrings(\n\t\t\t\ttc.listenAddr,\n\t\t\t))\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer h2.Close()\n\t\t\terr = h1.Connect(context.Background(), peer.AddrInfo{\n\t\t\t\tID:    h2.ID(),\n\t\t\t\tAddrs: h2.Addrs(),\n\t\t\t})\n\t\t\trequire.NoError(t, err)\n\t\t\tc := h1.Network().ConnsToPeer(h2.ID())[0]\n\t\t\ttc.testAs(t, c)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "limits.go",
    "content": "package libp2p\n\nimport (\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/autonat\"\n\trcmgr \"github.com/libp2p/go-libp2p/p2p/host/resource-manager\"\n\tcircuit \"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/proto\"\n\trelayv2 \"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/relay\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/holepunch\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/identify\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/ping\"\n)\n\n// SetDefaultServiceLimits sets the default limits for bundled libp2p services\nfunc SetDefaultServiceLimits(config *rcmgr.ScalingLimitConfig) {\n\t// identify\n\tconfig.AddServiceLimit(\n\t\tidentify.ServiceName,\n\t\trcmgr.BaseLimit{StreamsInbound: 64, StreamsOutbound: 64, Streams: 128, Memory: 4 << 20},\n\t\trcmgr.BaseLimitIncrease{StreamsInbound: 64, StreamsOutbound: 64, Streams: 128, Memory: 4 << 20},\n\t)\n\tconfig.AddServicePeerLimit(\n\t\tidentify.ServiceName,\n\t\trcmgr.BaseLimit{StreamsInbound: 16, StreamsOutbound: 16, Streams: 32, Memory: 1 << 20},\n\t\trcmgr.BaseLimitIncrease{},\n\t)\n\tfor _, id := range [...]protocol.ID{identify.ID, identify.IDPush} {\n\t\tconfig.AddProtocolLimit(\n\t\t\tid,\n\t\t\trcmgr.BaseLimit{StreamsInbound: 64, StreamsOutbound: 64, Streams: 128, Memory: 4 << 20},\n\t\t\trcmgr.BaseLimitIncrease{StreamsInbound: 64, StreamsOutbound: 64, Streams: 128, Memory: 4 << 20},\n\t\t)\n\t\tconfig.AddProtocolPeerLimit(\n\t\t\tid,\n\t\t\trcmgr.BaseLimit{StreamsInbound: 16, StreamsOutbound: 16, Streams: 32, Memory: 32 * (256<<20 + 16<<10)},\n\t\t\trcmgr.BaseLimitIncrease{},\n\t\t)\n\t}\n\n\t//  ping\n\taddServiceAndProtocolLimit(config,\n\t\tping.ServiceName, ping.ID,\n\t\trcmgr.BaseLimit{StreamsInbound: 64, StreamsOutbound: 64, Streams: 64, Memory: 4 << 20},\n\t\trcmgr.BaseLimitIncrease{StreamsInbound: 64, StreamsOutbound: 64, Streams: 64, Memory: 4 << 20},\n\t)\n\taddServicePeerAndProtocolPeerLimit(\n\t\tconfig,\n\t\tping.ServiceName, ping.ID,\n\t\trcmgr.BaseLimit{StreamsInbound: 2, StreamsOutbound: 3, Streams: 4, Memory: 32 * (256<<20 + 16<<10)},\n\t\trcmgr.BaseLimitIncrease{},\n\t)\n\n\t// autonat\n\taddServiceAndProtocolLimit(config,\n\t\tautonat.ServiceName, autonat.AutoNATProto,\n\t\trcmgr.BaseLimit{StreamsInbound: 64, StreamsOutbound: 64, Streams: 64, Memory: 4 << 20},\n\t\trcmgr.BaseLimitIncrease{StreamsInbound: 4, StreamsOutbound: 4, Streams: 4, Memory: 2 << 20},\n\t)\n\taddServicePeerAndProtocolPeerLimit(\n\t\tconfig,\n\t\tautonat.ServiceName, autonat.AutoNATProto,\n\t\trcmgr.BaseLimit{StreamsInbound: 2, StreamsOutbound: 2, Streams: 2, Memory: 1 << 20},\n\t\trcmgr.BaseLimitIncrease{},\n\t)\n\n\t// holepunch\n\taddServiceAndProtocolLimit(config,\n\t\tholepunch.ServiceName, holepunch.Protocol,\n\t\trcmgr.BaseLimit{StreamsInbound: 32, StreamsOutbound: 32, Streams: 64, Memory: 4 << 20},\n\t\trcmgr.BaseLimitIncrease{StreamsInbound: 8, StreamsOutbound: 8, Streams: 16, Memory: 4 << 20},\n\t)\n\taddServicePeerAndProtocolPeerLimit(config,\n\t\tholepunch.ServiceName, holepunch.Protocol,\n\t\trcmgr.BaseLimit{StreamsInbound: 2, StreamsOutbound: 2, Streams: 2, Memory: 1 << 20},\n\t\trcmgr.BaseLimitIncrease{},\n\t)\n\n\t// relay/v2\n\tconfig.AddServiceLimit(\n\t\trelayv2.ServiceName,\n\t\trcmgr.BaseLimit{StreamsInbound: 256, StreamsOutbound: 256, Streams: 256, Memory: 16 << 20},\n\t\trcmgr.BaseLimitIncrease{StreamsInbound: 256, StreamsOutbound: 256, Streams: 256, Memory: 16 << 20},\n\t)\n\tconfig.AddServicePeerLimit(\n\t\trelayv2.ServiceName,\n\t\trcmgr.BaseLimit{StreamsInbound: 64, StreamsOutbound: 64, Streams: 64, Memory: 1 << 20},\n\t\trcmgr.BaseLimitIncrease{},\n\t)\n\n\t// circuit protocols, both client and service\n\tfor _, proto := range [...]protocol.ID{circuit.ProtoIDv2Hop, circuit.ProtoIDv2Stop} {\n\t\tconfig.AddProtocolLimit(\n\t\t\tproto,\n\t\t\trcmgr.BaseLimit{StreamsInbound: 640, StreamsOutbound: 640, Streams: 640, Memory: 16 << 20},\n\t\t\trcmgr.BaseLimitIncrease{StreamsInbound: 640, StreamsOutbound: 640, Streams: 640, Memory: 16 << 20},\n\t\t)\n\t\tconfig.AddProtocolPeerLimit(\n\t\t\tproto,\n\t\t\trcmgr.BaseLimit{StreamsInbound: 128, StreamsOutbound: 128, Streams: 128, Memory: 32 << 20},\n\t\t\trcmgr.BaseLimitIncrease{},\n\t\t)\n\t}\n}\n\nfunc addServiceAndProtocolLimit(config *rcmgr.ScalingLimitConfig, service string, proto protocol.ID, limit rcmgr.BaseLimit, increase rcmgr.BaseLimitIncrease) {\n\tconfig.AddServiceLimit(service, limit, increase)\n\tconfig.AddProtocolLimit(proto, limit, increase)\n}\n\nfunc addServicePeerAndProtocolPeerLimit(config *rcmgr.ScalingLimitConfig, service string, proto protocol.ID, limit rcmgr.BaseLimit, increase rcmgr.BaseLimitIncrease) {\n\tconfig.AddServicePeerLimit(service, limit, increase)\n\tconfig.AddProtocolPeerLimit(proto, limit, increase)\n}\n"
  },
  {
    "path": "options.go",
    "content": "package libp2p\n\n// This file contains all libp2p configuration options (except the defaults,\n// those are in defaults.go).\n\nimport (\n\t\"crypto/rand\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/config\"\n\t\"github.com/libp2p/go-libp2p/core/connmgr\"\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/metrics\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\t\"github.com/libp2p/go-libp2p/core/pnet\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\t\"github.com/libp2p/go-libp2p/core/transport\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/autorelay\"\n\tbhost \"github.com/libp2p/go-libp2p/p2p/host/basic\"\n\t\"github.com/libp2p/go-libp2p/p2p/net/swarm\"\n\ttptu \"github.com/libp2p/go-libp2p/p2p/net/upgrader\"\n\trelayv2 \"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/relay\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/holepunch\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/quicreuse\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"go.uber.org/fx\"\n)\n\n// ListenAddrStrings configures libp2p to listen on the given (unparsed)\n// addresses.\nfunc ListenAddrStrings(s ...string) Option {\n\treturn func(cfg *Config) error {\n\t\tfor _, addrstr := range s {\n\t\t\ta, err := ma.NewMultiaddr(addrstr)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tcfg.ListenAddrs = append(cfg.ListenAddrs, a)\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// ListenAddrs configures libp2p to listen on the given addresses.\nfunc ListenAddrs(addrs ...ma.Multiaddr) Option {\n\treturn func(cfg *Config) error {\n\t\tcfg.ListenAddrs = append(cfg.ListenAddrs, addrs...)\n\t\treturn nil\n\t}\n}\n\n// Security configures libp2p to use the given security transport (or transport\n// constructor).\n//\n// Name is the protocol name.\n//\n// The transport can be a constructed security.Transport or a function taking\n// any subset of this libp2p node's:\n// * Public key\n// * Private key\n// * Peer ID\n// * Host\n// * Network\n// * Peerstore\nfunc Security(name string, constructor any) Option {\n\treturn func(cfg *Config) error {\n\t\tif cfg.Insecure {\n\t\t\treturn fmt.Errorf(\"cannot use security transports with an insecure libp2p configuration\")\n\t\t}\n\t\tcfg.SecurityTransports = append(cfg.SecurityTransports, config.Security{ID: protocol.ID(name), Constructor: constructor})\n\t\treturn nil\n\t}\n}\n\n// NoSecurity is an option that completely disables all transport security.\n// It's incompatible with all other transport security protocols.\nvar NoSecurity Option = func(cfg *Config) error {\n\tif len(cfg.SecurityTransports) > 0 {\n\t\treturn fmt.Errorf(\"cannot use security transports with an insecure libp2p configuration\")\n\t}\n\tcfg.Insecure = true\n\treturn nil\n}\n\n// Muxer configures libp2p to use the given stream multiplexer.\n// name is the protocol name.\nfunc Muxer(name string, muxer network.Multiplexer) Option {\n\treturn func(cfg *Config) error {\n\t\tcfg.Muxers = append(cfg.Muxers, tptu.StreamMuxer{Muxer: muxer, ID: protocol.ID(name)})\n\t\treturn nil\n\t}\n}\n\nfunc QUICReuse(constructor any, opts ...quicreuse.Option) Option {\n\treturn func(cfg *Config) error {\n\t\ttag := `group:\"quicreuseopts\"`\n\t\ttyp := reflect.ValueOf(constructor).Type()\n\t\tnumParams := typ.NumIn()\n\t\tisVariadic := typ.IsVariadic()\n\n\t\tif !isVariadic && len(opts) > 0 {\n\t\t\treturn errors.New(\"QUICReuse constructor doesn't take any options\")\n\t\t}\n\n\t\tvar params []string\n\t\tif isVariadic && len(opts) > 0 {\n\t\t\t// If there are options, apply the tag.\n\t\t\t// Since options are variadic, they have to be the last argument of the constructor.\n\t\t\tparams = make([]string, numParams)\n\t\t\tparams[len(params)-1] = tag\n\t\t}\n\n\t\tcfg.QUICReuse = append(cfg.QUICReuse, fx.Provide(fx.Annotate(constructor, fx.ParamTags(params...))))\n\t\tfor _, opt := range opts {\n\t\t\tcfg.QUICReuse = append(cfg.QUICReuse, fx.Supply(fx.Annotate(opt, fx.ResultTags(tag))))\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// Transport configures libp2p to use the given transport (or transport\n// constructor).\n//\n// The transport can be a constructed transport.Transport or a function taking\n// any subset of this libp2p node's:\n// * Transport Upgrader (*tptu.Upgrader)\n// * Host\n// * Stream muxer (muxer.Transport)\n// * Security transport (security.Transport)\n// * Private network protector (pnet.Protector)\n// * Peer ID\n// * Private Key\n// * Public Key\n// * Address filter (filter.Filter)\n// * Peerstore\nfunc Transport(constructor any, opts ...any) Option {\n\treturn func(cfg *Config) error {\n\t\t// generate a random identifier, so that fx can associate the constructor with its options\n\t\tb := make([]byte, 8)\n\t\trand.Read(b)\n\t\tid := binary.BigEndian.Uint64(b)\n\n\t\ttag := fmt.Sprintf(`group:\"transportopt_%d\"`, id)\n\n\t\ttyp := reflect.ValueOf(constructor).Type()\n\t\tnumParams := typ.NumIn()\n\t\tisVariadic := typ.IsVariadic()\n\n\t\tif !isVariadic && len(opts) > 0 {\n\t\t\treturn errors.New(\"transport constructor doesn't take any options\")\n\t\t}\n\t\tif isVariadic && numParams >= 1 {\n\t\t\tparamType := typ.In(numParams - 1).Elem()\n\t\t\tfor _, opt := range opts {\n\t\t\t\tif typ := reflect.TypeOf(opt); !typ.AssignableTo(paramType) {\n\t\t\t\t\treturn fmt.Errorf(\"transport option of type %s not assignable to %s\", typ, paramType)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tvar params []string\n\t\tif isVariadic && len(opts) > 0 {\n\t\t\t// If there are transport options, apply the tag.\n\t\t\t// Since options are variadic, they have to be the last argument of the constructor.\n\t\t\tparams = make([]string, numParams)\n\t\t\tparams[len(params)-1] = tag\n\t\t}\n\n\t\tcfg.Transports = append(cfg.Transports, fx.Provide(\n\t\t\tfx.Annotate(\n\t\t\t\tconstructor,\n\t\t\t\tfx.ParamTags(params...),\n\t\t\t\tfx.As(new(transport.Transport)),\n\t\t\t\tfx.ResultTags(`group:\"transport\"`),\n\t\t\t),\n\t\t))\n\t\tfor _, opt := range opts {\n\t\t\tcfg.Transports = append(cfg.Transports, fx.Supply(\n\t\t\t\tfx.Annotate(\n\t\t\t\t\topt,\n\t\t\t\t\tfx.ResultTags(tag),\n\t\t\t\t),\n\t\t\t))\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// Peerstore configures libp2p to use the given peerstore.\nfunc Peerstore(ps peerstore.Peerstore) Option {\n\treturn func(cfg *Config) error {\n\t\tif cfg.Peerstore != nil {\n\t\t\treturn fmt.Errorf(\"cannot specify multiple peerstore options\")\n\t\t}\n\n\t\tcfg.Peerstore = ps\n\t\treturn nil\n\t}\n}\n\n// PrivateNetwork configures libp2p to use the given private network protector.\nfunc PrivateNetwork(psk pnet.PSK) Option {\n\treturn func(cfg *Config) error {\n\t\tif cfg.PSK != nil {\n\t\t\treturn fmt.Errorf(\"cannot specify multiple private network options\")\n\t\t}\n\n\t\tcfg.PSK = psk\n\t\treturn nil\n\t}\n}\n\n// BandwidthReporter configures libp2p to use the given bandwidth reporter.\nfunc BandwidthReporter(rep metrics.Reporter) Option {\n\treturn func(cfg *Config) error {\n\t\tif cfg.Reporter != nil {\n\t\t\treturn fmt.Errorf(\"cannot specify multiple bandwidth reporter options\")\n\t\t}\n\n\t\tcfg.Reporter = rep\n\t\treturn nil\n\t}\n}\n\n// Identity configures libp2p to use the given private key to identify itself.\nfunc Identity(sk crypto.PrivKey) Option {\n\treturn func(cfg *Config) error {\n\t\tif cfg.PeerKey != nil {\n\t\t\treturn fmt.Errorf(\"cannot specify multiple identities\")\n\t\t}\n\n\t\tcfg.PeerKey = sk\n\t\treturn nil\n\t}\n}\n\n// ConnectionManager configures libp2p to use the given connection manager.\n//\n// The current \"standard\" connection manager lives in github.com/libp2p/go-libp2p-connmgr. See\n// https://pkg.go.dev/github.com/libp2p/go-libp2p-connmgr?utm_source=godoc#NewConnManager.\nfunc ConnectionManager(connman connmgr.ConnManager) Option {\n\treturn func(cfg *Config) error {\n\t\tif cfg.ConnManager != nil {\n\t\t\treturn fmt.Errorf(\"cannot specify multiple connection managers\")\n\t\t}\n\t\tcfg.ConnManager = connman\n\t\treturn nil\n\t}\n}\n\n// AddrsFactory configures libp2p to use the given address factory.\nfunc AddrsFactory(factory config.AddrsFactory) Option {\n\treturn func(cfg *Config) error {\n\t\tif cfg.AddrsFactory != nil {\n\t\t\treturn fmt.Errorf(\"cannot specify multiple address factories\")\n\t\t}\n\t\tcfg.AddrsFactory = factory\n\t\treturn nil\n\t}\n}\n\n// EnableRelay configures libp2p to enable the relay transport.\n// This option only configures libp2p to accept inbound connections from relays\n// and make outbound connections_through_ relays when requested by the remote peer.\n// This option supports both circuit v1 and v2 connections.\n// (default: enabled)\nfunc EnableRelay() Option {\n\treturn func(cfg *Config) error {\n\t\tcfg.RelayCustom = true\n\t\tcfg.Relay = true\n\t\treturn nil\n\t}\n}\n\n// DisableRelay configures libp2p to disable the relay transport.\nfunc DisableRelay() Option {\n\treturn func(cfg *Config) error {\n\t\tcfg.RelayCustom = true\n\t\tcfg.Relay = false\n\t\treturn nil\n\t}\n}\n\n// EnableRelayService configures libp2p to run a circuit v2 relay,\n// if we detect that we're publicly reachable.\nfunc EnableRelayService(opts ...relayv2.Option) Option {\n\treturn func(cfg *Config) error {\n\t\tcfg.EnableRelayService = true\n\t\tcfg.RelayServiceOpts = opts\n\t\treturn nil\n\t}\n}\n\n// EnableAutoRelay configures libp2p to enable the AutoRelay subsystem.\n//\n// Dependencies:\n//   - Relay (enabled by default)\n//   - Either:\n//     1. A list of static relays\n//     2. A PeerSource function that provides a chan of relays. See `autorelay.WithPeerSource`\n//\n// This subsystem performs automatic address rewriting to advertise relay addresses when it\n// detects that the node is publicly unreachable (e.g. behind a NAT).\n//\n// Deprecated: Use EnableAutoRelayWithStaticRelays or EnableAutoRelayWithPeerSource\nfunc EnableAutoRelay(opts ...autorelay.Option) Option {\n\treturn func(cfg *Config) error {\n\t\tcfg.EnableAutoRelay = true\n\t\tcfg.AutoRelayOpts = opts\n\t\treturn nil\n\t}\n}\n\n// EnableAutoRelayWithStaticRelays configures libp2p to enable the AutoRelay subsystem using\n// the provided relays as relay candidates.\n// This subsystem performs automatic address rewriting to advertise relay addresses when it\n// detects that the node is publicly unreachable (e.g. behind a NAT).\nfunc EnableAutoRelayWithStaticRelays(static []peer.AddrInfo, opts ...autorelay.Option) Option {\n\treturn func(cfg *Config) error {\n\t\tcfg.EnableAutoRelay = true\n\t\tcfg.AutoRelayOpts = append([]autorelay.Option{autorelay.WithStaticRelays(static)}, opts...)\n\t\treturn nil\n\t}\n}\n\n// EnableAutoRelayWithPeerSource configures libp2p to enable the AutoRelay\n// subsystem using the provided PeerSource callback to get more relay\n// candidates.  This subsystem performs automatic address rewriting to advertise\n// relay addresses when it detects that the node is publicly unreachable (e.g.\n// behind a NAT).\nfunc EnableAutoRelayWithPeerSource(peerSource autorelay.PeerSource, opts ...autorelay.Option) Option {\n\treturn func(cfg *Config) error {\n\t\tcfg.EnableAutoRelay = true\n\t\tcfg.AutoRelayOpts = append([]autorelay.Option{autorelay.WithPeerSource(peerSource)}, opts...)\n\t\treturn nil\n\t}\n}\n\n// ForceReachabilityPublic overrides automatic reachability detection in the AutoNAT subsystem,\n// forcing the local node to believe it is reachable externally.\nfunc ForceReachabilityPublic() Option {\n\treturn func(cfg *Config) error {\n\t\tpublic := network.ReachabilityPublic\n\t\tcfg.AutoNATConfig.ForceReachability = &public\n\t\treturn nil\n\t}\n}\n\n// ForceReachabilityPrivate overrides automatic reachability detection in the AutoNAT subsystem,\n// forceing the local node to believe it is behind a NAT and not reachable externally.\nfunc ForceReachabilityPrivate() Option {\n\treturn func(cfg *Config) error {\n\t\tprivate := network.ReachabilityPrivate\n\t\tcfg.AutoNATConfig.ForceReachability = &private\n\t\treturn nil\n\t}\n}\n\n// EnableNATService configures libp2p to provide a service to peers for determining\n// their reachability status. When enabled, the host will attempt to dial back\n// to peers, and then tell them if it was successful in making such connections.\nfunc EnableNATService() Option {\n\treturn func(cfg *Config) error {\n\t\tcfg.AutoNATConfig.EnableService = true\n\t\treturn nil\n\t}\n}\n\n// AutoNATServiceRateLimit changes the default rate limiting configured in helping\n// other peers determine their reachability status. When set, the host will limit\n// the number of requests it responds to in each 60 second period to the set\n// numbers. A value of '0' disables throttling.\nfunc AutoNATServiceRateLimit(global, perPeer int, interval time.Duration) Option {\n\treturn func(cfg *Config) error {\n\t\tcfg.AutoNATConfig.ThrottleGlobalLimit = global\n\t\tcfg.AutoNATConfig.ThrottlePeerLimit = perPeer\n\t\tcfg.AutoNATConfig.ThrottleInterval = interval\n\t\treturn nil\n\t}\n}\n\n// ConnectionGater configures libp2p to use the given ConnectionGater\n// to actively reject inbound/outbound connections based on the lifecycle stage\n// of the connection.\n//\n// For more information, refer to go-libp2p/core.ConnectionGater.\nfunc ConnectionGater(cg connmgr.ConnectionGater) Option {\n\treturn func(cfg *Config) error {\n\t\tif cfg.ConnectionGater != nil {\n\t\t\treturn errors.New(\"cannot configure multiple connection gaters, or cannot configure both Filters and ConnectionGater\")\n\t\t}\n\t\tcfg.ConnectionGater = cg\n\t\treturn nil\n\t}\n}\n\n// ResourceManager configures libp2p to use the given ResourceManager.\n// When using the p2p/host/resource-manager implementation of the ResourceManager interface,\n// it is recommended to set limits for libp2p protocol by calling SetDefaultServiceLimits.\nfunc ResourceManager(rcmgr network.ResourceManager) Option {\n\treturn func(cfg *Config) error {\n\t\tif cfg.ResourceManager != nil {\n\t\t\treturn errors.New(\"cannot configure multiple resource managers\")\n\t\t}\n\t\tcfg.ResourceManager = rcmgr\n\t\treturn nil\n\t}\n}\n\n// NATPortMap configures libp2p to use the default NATManager. The default\n// NATManager will attempt to open a port in your network's firewall using UPnP.\nfunc NATPortMap() Option {\n\treturn NATManager(bhost.NewNATManager)\n}\n\n// NATManager will configure libp2p to use the requested NATManager. This\n// function should be passed a NATManager *constructor* that takes a libp2p Network.\nfunc NATManager(nm config.NATManagerC) Option {\n\treturn func(cfg *Config) error {\n\t\tif cfg.NATManager != nil {\n\t\t\treturn fmt.Errorf(\"cannot specify multiple NATManagers\")\n\t\t}\n\t\tcfg.NATManager = nm\n\t\treturn nil\n\t}\n}\n\n// Ping will configure libp2p to support the ping service; enable by default.\nfunc Ping(enable bool) Option {\n\treturn func(cfg *Config) error {\n\t\tcfg.DisablePing = !enable\n\t\treturn nil\n\t}\n}\n\n// Routing will configure libp2p to use routing.\nfunc Routing(rt config.RoutingC) Option {\n\treturn func(cfg *Config) error {\n\t\tif cfg.Routing != nil {\n\t\t\treturn fmt.Errorf(\"cannot specify multiple routing options\")\n\t\t}\n\t\tcfg.Routing = rt\n\t\treturn nil\n\t}\n}\n\n// NoListenAddrs will configure libp2p to not listen by default.\n//\n// This will both clear any configured listen addrs and prevent libp2p from\n// applying the default listen address option. It also disables relay, unless the\n// user explicitly specifies with an option, as the transport creates an implicit\n// listen address that would make the node dialable through any relay it was connected to.\nvar NoListenAddrs = func(cfg *Config) error {\n\tcfg.ListenAddrs = []ma.Multiaddr{}\n\tif !cfg.RelayCustom {\n\t\tcfg.RelayCustom = true\n\t\tcfg.Relay = false\n\t}\n\treturn nil\n}\n\n// NoTransports will configure libp2p to not enable any transports.\n//\n// This will both clear any configured transports (specified in prior libp2p\n// options) and prevent libp2p from applying the default transports.\nvar NoTransports = func(cfg *Config) error {\n\tcfg.Transports = []fx.Option{}\n\treturn nil\n}\n\n// ProtocolVersion sets the protocolVersion string required by the\n// libp2p Identify protocol.\nfunc ProtocolVersion(s string) Option {\n\treturn func(cfg *Config) error {\n\t\tcfg.ProtocolVersion = s\n\t\treturn nil\n\t}\n}\n\n// UserAgent sets the libp2p user-agent sent along with the identify protocol\nfunc UserAgent(userAgent string) Option {\n\treturn func(cfg *Config) error {\n\t\tcfg.UserAgent = userAgent\n\t\treturn nil\n\t}\n}\n\n// MultiaddrResolver sets the libp2p dns resolver\nfunc MultiaddrResolver(rslv network.MultiaddrDNSResolver) Option {\n\treturn func(cfg *Config) error {\n\t\tcfg.MultiaddrResolver = rslv\n\t\treturn nil\n\t}\n}\n\n// Experimental\n// EnableHolePunching enables NAT traversal by enabling NATT'd peers to both initiate and respond to hole punching attempts\n// to create direct/NAT-traversed connections with other peers. (default: disabled)\n//\n// Dependencies:\n//   - Relay (enabled by default)\n//\n// This subsystem performs two functions:\n//\n//  1. On receiving an inbound Relay connection, it attempts to create a direct connection with the remote peer\n//     by initiating and co-ordinating a hole punch over the Relayed connection.\n//  2. If a peer sees a request to co-ordinate a hole punch on an outbound Relay connection,\n//     it will participate in the hole-punch to create a direct connection with the remote peer.\n//\n// If the hole punch is successful, all new streams will thereafter be created on the hole-punched connection.\n// The Relayed connection will eventually be closed after a grace period.\n//\n// All existing indefinite long-lived streams on the Relayed connection will have to re-opened on the hole-punched connection by the user.\n// Users can make use of the `Connected`/`Disconnected` notifications emitted by the Network for this purpose.\n//\n// It is not mandatory but nice to also enable the `AutoRelay` option (See `EnableAutoRelay`)\n// so the peer can discover and connect to Relay servers  if it discovers that it is NATT'd and has private reachability via AutoNAT.\n// This will then enable it to advertise Relay addresses which can be used to accept inbound Relay connections to then co-ordinate\n// a hole punch.\n//\n// If `EnableAutoRelay` is configured and the user is confident that the peer has private reachability/is NATT'd,\n// the `ForceReachabilityPrivate` option can be configured to short-circuit reachability discovery via AutoNAT\n// so the peer can immediately start connecting to Relay servers.\n//\n// If `EnableAutoRelay` is configured, the `StaticRelays` option can be used to configure a static set of Relay servers\n// for `AutoRelay` to connect to so that it does not need to discover Relay servers via Routing.\nfunc EnableHolePunching(opts ...holepunch.Option) Option {\n\treturn func(cfg *Config) error {\n\t\tcfg.EnableHolePunching = true\n\t\tcfg.HolePunchingOptions = opts\n\t\treturn nil\n\t}\n}\n\nfunc WithDialTimeout(t time.Duration) Option {\n\treturn func(cfg *Config) error {\n\t\tif t <= 0 {\n\t\t\treturn errors.New(\"dial timeout needs to be non-negative\")\n\t\t}\n\t\tcfg.DialTimeout = t\n\t\treturn nil\n\t}\n}\n\n// DisableMetrics configures libp2p to disable prometheus metrics\nfunc DisableMetrics() Option {\n\treturn func(cfg *Config) error {\n\t\tcfg.DisableMetrics = true\n\t\treturn nil\n\t}\n}\n\n// PrometheusRegisterer configures libp2p to use reg as the Registerer for all metrics subsystems\nfunc PrometheusRegisterer(reg prometheus.Registerer) Option {\n\treturn func(cfg *Config) error {\n\t\tif cfg.DisableMetrics {\n\t\t\treturn errors.New(\"cannot set registerer when metrics are disabled\")\n\t\t}\n\t\tif cfg.PrometheusRegisterer != nil {\n\t\t\treturn errors.New(\"registerer already set\")\n\t\t}\n\t\tif reg == nil {\n\t\t\treturn errors.New(\"registerer cannot be nil\")\n\t\t}\n\t\tcfg.PrometheusRegisterer = reg\n\t\treturn nil\n\t}\n}\n\n// DialRanker configures libp2p to use d as the dial ranker. To enable smart\n// dialing use `swarm.DefaultDialRanker`. use `swarm.NoDelayDialRanker` to\n// disable smart dialing.\n//\n// Deprecated: use SwarmOpts(swarm.WithDialRanker(d)) instead\nfunc DialRanker(d network.DialRanker) Option {\n\treturn func(cfg *Config) error {\n\t\tif cfg.DialRanker != nil {\n\t\t\treturn errors.New(\"dial ranker already configured\")\n\t\t}\n\t\tcfg.DialRanker = d\n\t\treturn nil\n\t}\n}\n\n// SwarmOpts configures libp2p to use swarm with opts\nfunc SwarmOpts(opts ...swarm.Option) Option {\n\treturn func(cfg *Config) error {\n\t\tcfg.SwarmOpts = opts\n\t\treturn nil\n\t}\n}\n\n// DisableIdentifyAddressDiscovery disables address discovery using peer provided observed addresses\n// in identify. If you know your public addresses upfront, the recommended way is to use\n// AddressFactory to provide the external adddress to the host and use this option to disable\n// discovery from identify.\nfunc DisableIdentifyAddressDiscovery() Option {\n\treturn func(cfg *Config) error {\n\t\tcfg.DisableIdentifyAddressDiscovery = true\n\t\treturn nil\n\t}\n}\n\n// EnableAutoNATv2 enables autonat v2\nfunc EnableAutoNATv2() Option {\n\treturn func(cfg *Config) error {\n\t\tcfg.EnableAutoNATv2 = true\n\t\treturn nil\n\t}\n}\n\n// UDPBlackHoleSuccessCounter configures libp2p to use f as the black hole filter for UDP addrs\nfunc UDPBlackHoleSuccessCounter(f *swarm.BlackHoleSuccessCounter) Option {\n\treturn func(cfg *Config) error {\n\t\tcfg.UDPBlackHoleSuccessCounter = f\n\t\tcfg.CustomUDPBlackHoleSuccessCounter = true\n\t\treturn nil\n\t}\n}\n\n// IPv6BlackHoleSuccessCounter configures libp2p to use f as the black hole filter for IPv6 addrs\nfunc IPv6BlackHoleSuccessCounter(f *swarm.BlackHoleSuccessCounter) Option {\n\treturn func(cfg *Config) error {\n\t\tcfg.IPv6BlackHoleSuccessCounter = f\n\t\tcfg.CustomIPv6BlackHoleSuccessCounter = true\n\t\treturn nil\n\t}\n}\n\n// WithFxOption adds a user provided fx.Option to the libp2p constructor.\n// Experimental: This option is subject to change or removal.\nfunc WithFxOption(opts ...fx.Option) Option {\n\treturn func(cfg *Config) error {\n\t\tcfg.UserFxOptions = append(cfg.UserFxOptions, opts...)\n\t\treturn nil\n\t}\n}\n\n// ShareTCPListener shares the same listen address between TCP and Websocket\n// transports. This lets both transports use the same TCP port.\n//\n// Currently this behavior is Opt-in. In a future release this will be the\n// default, and this option will be removed.\nfunc ShareTCPListener() Option {\n\treturn func(cfg *Config) error {\n\t\tcfg.ShareTCPListener = true\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "options_filter.go",
    "content": "package libp2p\n\nimport (\n\t\"github.com/libp2p/go-libp2p/core/connmgr\"\n\t\"github.com/libp2p/go-libp2p/core/control\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\n// filtersConnectionGater is an adapter that turns multiaddr.Filter into a\n// connmgr.ConnectionGater.\ntype filtersConnectionGater ma.Filters\n\nvar _ connmgr.ConnectionGater = (*filtersConnectionGater)(nil)\n\nfunc (f *filtersConnectionGater) InterceptAddrDial(_ peer.ID, addr ma.Multiaddr) (allow bool) {\n\treturn !(*ma.Filters)(f).AddrBlocked(addr)\n}\n\nfunc (f *filtersConnectionGater) InterceptPeerDial(_ peer.ID) (allow bool) {\n\treturn true\n}\n\nfunc (f *filtersConnectionGater) InterceptAccept(connAddr network.ConnMultiaddrs) (allow bool) {\n\treturn !(*ma.Filters)(f).AddrBlocked(connAddr.RemoteMultiaddr())\n}\n\nfunc (f *filtersConnectionGater) InterceptSecured(_ network.Direction, _ peer.ID, connAddr network.ConnMultiaddrs) (allow bool) {\n\treturn !(*ma.Filters)(f).AddrBlocked(connAddr.RemoteMultiaddr())\n}\n\nfunc (f *filtersConnectionGater) InterceptUpgraded(_ network.Conn) (allow bool, reason control.DisconnectReason) {\n\treturn true, 0\n}\n"
  },
  {
    "path": "p2p/canonicallog/canonicallog.go",
    "content": "package canonicallog\n\nimport (\n\t\"context\"\n\t\"log/slog\"\n\t\"math/rand\"\n\t\"net\"\n\t\"os\"\n\t\"runtime\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n\t\"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\nvar log = slog.New(\n\tslog.NewTextHandler(\n\t\tos.Stderr,\n\t\t&slog.HandlerOptions{\n\t\t\tLevel:     logging.ConfigFromEnv().LevelForSystem(\"canonical-log\"),\n\t\t\tAddSource: true}))\n\n// logWithSkip logs at level with AddSource pointing to the caller `skip` frames up\n// from *this* function’s caller (so skip=0 => the immediate caller of logWithSkip).\nfunc logWithSkip(ctx context.Context, l *slog.Logger, level slog.Level, skip int, msg string, args ...any) {\n\tif !l.Enabled(ctx, level) {\n\t\treturn\n\t}\n\n\tvar pcs [1]uintptr\n\t// +2 to skip runtime.Callers and logWithSkip itself.\n\truntime.Callers(skip+2, pcs[:])\n\n\tr := slog.NewRecord(time.Now(), level, msg, pcs[0])\n\tr.Add(args...)\n\t_ = l.Handler().Handle(ctx, r)\n}\n\n// LogMisbehavingPeer is the canonical way to log a misbehaving peer.\n// Protocols should use this to identify a misbehaving peer to allow the end\n// user to easily identify these nodes across protocols and libp2p.\nfunc LogMisbehavingPeer(p peer.ID, peerAddr multiaddr.Multiaddr, component string, err error, msg string) {\n\tlogWithSkip(context.Background(), log, slog.LevelWarn, 1, \"CANONICAL_MISBEHAVING_PEER\",\n\t\t\"peer\", p,\n\t\t\"addr\", peerAddr,\n\t\t\"component\", component,\n\t\t\"err\", err,\n\t\t\"msg\", msg)\n}\n\n// LogMisbehavingPeerNetAddr is the canonical way to log a misbehaving peer.\n// Protocols should use this to identify a misbehaving peer to allow the end\n// user to easily identify these nodes across protocols and libp2p.\nfunc LogMisbehavingPeerNetAddr(p peer.ID, peerAddr net.Addr, component string, originalErr error, msg string) {\n\tma, err := manet.FromNetAddr(peerAddr)\n\tif err != nil {\n\t\tlogWithSkip(context.Background(), log, slog.LevelWarn, 1, \"CANONICAL_MISBEHAVING_PEER\",\n\t\t\t\"peer\", p,\n\t\t\t\"net_addr\", peerAddr.String(),\n\t\t\t\"component\", component,\n\t\t\t\"err\", originalErr,\n\t\t\t\"msg\", msg)\n\t\treturn\n\t}\n\n\tLogMisbehavingPeer(p, ma, component, originalErr, msg)\n}\n\n// LogPeerStatus logs any useful information about a peer. It takes in a sample\n// rate and will only log one in every sampleRate messages (randomly). This is\n// useful in surfacing events that are normal in isolation, but may be abnormal\n// in large quantities. For example, a successful connection from an IP address\n// is normal. 10,000 connections from that same IP address is not normal. libp2p\n// itself does nothing besides emitting this log. Hook this up to another tool\n// like fail2ban to action on the log.\nfunc LogPeerStatus(sampleRate int, p peer.ID, peerAddr multiaddr.Multiaddr, keyVals ...string) {\n\tif rand.Intn(sampleRate) == 0 {\n\t\targs := []any{\n\t\t\t\"peer\", p,\n\t\t\t\"addr\", peerAddr.String(),\n\t\t\t\"sample_rate\", sampleRate,\n\t\t}\n\t\t// Add the additional key-value pairs\n\t\tfor _, kv := range keyVals {\n\t\t\targs = append(args, kv)\n\t\t}\n\t\tlogWithSkip(context.Background(), log, slog.LevelInfo, 1, \"CANONICAL_PEER_STATUS\", args...)\n\t}\n}\n"
  },
  {
    "path": "p2p/canonicallog/canonicallog_test.go",
    "content": "package canonicallog\n\nimport (\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p/core/test\"\n\n\t\"github.com/multiformats/go-multiaddr\"\n)\n\nfunc TestLogs(t *testing.T) {\n\toriginalLogger := log\n\tdefer func() {\n\t\tlog = originalLogger\n\t}()\n\t// Override to print debug logs\n\tlog = slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelInfo, AddSource: true}))\n\n\tLogMisbehavingPeer(test.RandPeerIDFatal(t), multiaddr.StringCast(\"/ip4/1.2.3.4\"), \"somecomponent\", fmt.Errorf(\"something\"), \"hi\")\n\n\tnetAddr := &net.TCPAddr{IP: net.ParseIP(\"127.0.0.1\"), Port: 80}\n\tLogMisbehavingPeerNetAddr(test.RandPeerIDFatal(t), netAddr, \"somecomponent\", fmt.Errorf(\"something\"), \"hello \\\"world\\\"\")\n\n\tLogPeerStatus(1, test.RandPeerIDFatal(t), multiaddr.StringCast(\"/ip4/1.2.3.4\"), \"extra\", \"info\")\n}\n"
  },
  {
    "path": "p2p/discovery/backoff/backoff.go",
    "content": "package backoff\n\nimport (\n\t\"math\"\n\t\"math/rand\"\n\t\"sync\"\n\t\"time\"\n\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n)\n\nvar log = logging.Logger(\"discovery-backoff\")\n\ntype BackoffFactory func() BackoffStrategy\n\n// BackoffStrategy describes how backoff will be implemented. BackoffStrategies are stateful.\ntype BackoffStrategy interface {\n\t// Delay calculates how long the next backoff duration should be, given the prior calls to Delay\n\tDelay() time.Duration\n\t// Reset clears the internal state of the BackoffStrategy\n\tReset()\n}\n\n// Jitter implementations taken roughly from https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/\n\n// Jitter must return a duration between min and max. Min must be lower than, or equal to, max.\ntype Jitter func(duration, min, max time.Duration, rng *rand.Rand) time.Duration\n\n// FullJitter returns a random number, uniformly chosen from the range [min, boundedDur].\n// boundedDur is the duration bounded between min and max.\nfunc FullJitter(duration, min, max time.Duration, rng *rand.Rand) time.Duration {\n\tif duration <= min {\n\t\treturn min\n\t}\n\n\tnormalizedDur := boundedDuration(duration, min, max) - min\n\n\treturn boundedDuration(time.Duration(rng.Int63n(int64(normalizedDur)))+min, min, max)\n}\n\n// NoJitter returns the duration bounded between min and max\nfunc NoJitter(duration, min, max time.Duration, _ *rand.Rand) time.Duration {\n\treturn boundedDuration(duration, min, max)\n}\n\ntype randomizedBackoff struct {\n\tmin time.Duration\n\tmax time.Duration\n\trng *rand.Rand\n}\n\nfunc (b *randomizedBackoff) BoundedDelay(duration time.Duration) time.Duration {\n\treturn boundedDuration(duration, b.min, b.max)\n}\n\nfunc boundedDuration(d, min, max time.Duration) time.Duration {\n\tif d < min {\n\t\treturn min\n\t}\n\tif d > max {\n\t\treturn max\n\t}\n\treturn d\n}\n\ntype attemptBackoff struct {\n\tattempt int\n\tjitter  Jitter\n\trandomizedBackoff\n}\n\nfunc (b *attemptBackoff) Reset() {\n\tb.attempt = 0\n}\n\n// NewFixedBackoff creates a BackoffFactory with a constant backoff duration\nfunc NewFixedBackoff(delay time.Duration) BackoffFactory {\n\treturn func() BackoffStrategy {\n\t\treturn &fixedBackoff{delay: delay}\n\t}\n}\n\ntype fixedBackoff struct {\n\tdelay time.Duration\n}\n\nfunc (b *fixedBackoff) Delay() time.Duration {\n\treturn b.delay\n}\n\nfunc (b *fixedBackoff) Reset() {}\n\n// NewPolynomialBackoff creates a BackoffFactory with backoff of the form c0*x^0, c1*x^1, ...cn*x^n where x is the attempt number\n// jitter is the function for adding randomness around the backoff\n// timeUnits are the units of time the polynomial is evaluated in\n// polyCoefs is the array of polynomial coefficients from [c0, c1, ... cn]\nfunc NewPolynomialBackoff(min, max time.Duration, jitter Jitter,\n\ttimeUnits time.Duration, polyCoefs []float64, rngSrc rand.Source) BackoffFactory {\n\trng := rand.New(&lockedSource{src: rngSrc})\n\treturn func() BackoffStrategy {\n\t\treturn &polynomialBackoff{\n\t\t\tattemptBackoff: attemptBackoff{\n\t\t\t\trandomizedBackoff: randomizedBackoff{\n\t\t\t\t\tmin: min,\n\t\t\t\t\tmax: max,\n\t\t\t\t\trng: rng,\n\t\t\t\t},\n\t\t\t\tjitter: jitter,\n\t\t\t},\n\t\t\ttimeUnits: timeUnits,\n\t\t\tpoly:      polyCoefs,\n\t\t}\n\t}\n}\n\ntype polynomialBackoff struct {\n\tattemptBackoff\n\ttimeUnits time.Duration\n\tpoly      []float64\n}\n\nfunc (b *polynomialBackoff) Delay() time.Duration {\n\tvar polySum float64\n\tswitch len(b.poly) {\n\tcase 0:\n\t\treturn 0\n\tcase 1:\n\t\tpolySum = b.poly[0]\n\tdefault:\n\t\tpolySum = b.poly[0]\n\t\texp := 1\n\t\tattempt := b.attempt\n\t\tb.attempt++\n\n\t\tfor _, c := range b.poly[1:] {\n\t\t\texp *= attempt\n\t\t\tpolySum += float64(exp) * c\n\t\t}\n\t}\n\treturn b.jitter(time.Duration(float64(b.timeUnits)*polySum), b.min, b.max, b.rng)\n}\n\n// NewExponentialBackoff creates a BackoffFactory with backoff of the form base^x + offset where x is the attempt number\n// jitter is the function for adding randomness around the backoff\n// timeUnits are the units of time the base^x is evaluated in\nfunc NewExponentialBackoff(min, max time.Duration, jitter Jitter,\n\ttimeUnits time.Duration, base float64, offset time.Duration, rngSrc rand.Source) BackoffFactory {\n\trng := rand.New(&lockedSource{src: rngSrc})\n\treturn func() BackoffStrategy {\n\t\treturn &exponentialBackoff{\n\t\t\tattemptBackoff: attemptBackoff{\n\t\t\t\trandomizedBackoff: randomizedBackoff{\n\t\t\t\t\tmin: min,\n\t\t\t\t\tmax: max,\n\t\t\t\t\trng: rng,\n\t\t\t\t},\n\t\t\t\tjitter: jitter,\n\t\t\t},\n\t\t\ttimeUnits: timeUnits,\n\t\t\tbase:      base,\n\t\t\toffset:    offset,\n\t\t}\n\t}\n}\n\ntype exponentialBackoff struct {\n\tattemptBackoff\n\ttimeUnits time.Duration\n\tbase      float64\n\toffset    time.Duration\n}\n\nfunc (b *exponentialBackoff) Delay() time.Duration {\n\tattempt := b.attempt\n\tb.attempt++\n\treturn b.jitter(\n\t\ttime.Duration(math.Pow(b.base, float64(attempt))*float64(b.timeUnits))+b.offset, b.min, b.max, b.rng)\n}\n\n// NewExponentialDecorrelatedJitter creates a BackoffFactory with backoff of the roughly of the form base^x where x is the attempt number.\n// Delays start at the minimum duration and after each attempt delay = rand(min, delay * base), bounded by the max\n// See https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/ for more information\nfunc NewExponentialDecorrelatedJitter(min, max time.Duration, base float64, rngSrc rand.Source) BackoffFactory {\n\trng := rand.New(&lockedSource{src: rngSrc})\n\treturn func() BackoffStrategy {\n\t\treturn &exponentialDecorrelatedJitter{\n\t\t\trandomizedBackoff: randomizedBackoff{\n\t\t\t\tmin: min,\n\t\t\t\tmax: max,\n\t\t\t\trng: rng,\n\t\t\t},\n\t\t\tbase: base,\n\t\t}\n\t}\n}\n\ntype exponentialDecorrelatedJitter struct {\n\trandomizedBackoff\n\tbase      float64\n\tlastDelay time.Duration\n}\n\nfunc (b *exponentialDecorrelatedJitter) Delay() time.Duration {\n\tif b.lastDelay < b.min {\n\t\tb.lastDelay = b.min\n\t\treturn b.lastDelay\n\t}\n\n\tnextMax := int64(float64(b.lastDelay) * b.base)\n\tb.lastDelay = boundedDuration(time.Duration(b.rng.Int63n(nextMax-int64(b.min)))+b.min, b.min, b.max)\n\treturn b.lastDelay\n}\n\nfunc (b *exponentialDecorrelatedJitter) Reset() { b.lastDelay = 0 }\n\ntype lockedSource struct {\n\tlk  sync.Mutex\n\tsrc rand.Source\n}\n\nfunc (r *lockedSource) Int63() (n int64) {\n\tr.lk.Lock()\n\tn = r.src.Int63()\n\tr.lk.Unlock()\n\treturn\n}\n\nfunc (r *lockedSource) Seed(seed int64) {\n\tr.lk.Lock()\n\tr.src.Seed(seed)\n\tr.lk.Unlock()\n}\n"
  },
  {
    "path": "p2p/discovery/backoff/backoff_test.go",
    "content": "package backoff\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n\t\"testing\"\n\t\"time\"\n\n\t\"golang.org/x/sync/errgroup\"\n)\n\nfunc checkDelay(bkf BackoffStrategy, expected time.Duration, t *testing.T) {\n\tt.Helper()\n\tif calculated := bkf.Delay(); calculated != expected {\n\t\tt.Fatalf(\"expected %v, got %v\", expected, calculated)\n\t}\n}\n\nfunc TestFixedBackoff(t *testing.T) {\n\tstartDelay := time.Second\n\tdelay := startDelay\n\n\tbkf := NewFixedBackoff(delay)\n\tb1 := bkf()\n\tb2 := bkf()\n\n\tif b1.Delay() != startDelay || b2.Delay() != startDelay {\n\t\tt.Fatal(\"incorrect delay time\")\n\t}\n\n\tif b1.Delay() != startDelay {\n\t\tt.Fatal(\"backoff is stateful\")\n\t}\n\n\tif b1.Reset(); b1.Delay() != startDelay {\n\t\tt.Fatalf(\"Reset does something\")\n\t}\n}\n\nfunc TestPolynomialBackoff(t *testing.T) {\n\tbkf := NewPolynomialBackoff(time.Second, time.Second*33, NoJitter, time.Second, []float64{0.5, 2, 3}, rand.NewSource(0))\n\tb1 := bkf()\n\tb2 := bkf()\n\n\tif b1.Delay() != time.Second || b2.Delay() != time.Second {\n\t\tt.Fatal(\"incorrect delay time\")\n\t}\n\n\tcheckDelay(b1, time.Millisecond*5500, t)\n\tcheckDelay(b1, time.Millisecond*16500, t)\n\tcheckDelay(b1, time.Millisecond*33000, t)\n\tcheckDelay(b2, time.Millisecond*5500, t)\n\n\tb1.Reset()\n\tb1.Delay()\n\tcheckDelay(b1, time.Millisecond*5500, t)\n}\n\nfunc TestExponentialBackoff(t *testing.T) {\n\tbkf := NewExponentialBackoff(time.Millisecond*650, time.Second*7, NoJitter, time.Second, 1.5, -time.Millisecond*400, rand.NewSource(0))\n\tb1 := bkf()\n\tb2 := bkf()\n\n\tif b1.Delay() != time.Millisecond*650 || b2.Delay() != time.Millisecond*650 {\n\t\tt.Fatal(\"incorrect delay time\")\n\t}\n\n\tcheckDelay(b1, time.Millisecond*1100, t)\n\tcheckDelay(b1, time.Millisecond*1850, t)\n\tcheckDelay(b1, time.Millisecond*2975, t)\n\tcheckDelay(b1, time.Microsecond*4662500, t)\n\tcheckDelay(b1, time.Second*7, t)\n\tcheckDelay(b2, time.Millisecond*1100, t)\n\n\tb1.Reset()\n\tb1.Delay()\n\tcheckDelay(b1, time.Millisecond*1100, t)\n}\n\nfunc minMaxJitterTest(jitter Jitter, t *testing.T) {\n\trng := rand.New(rand.NewSource(0))\n\tif jitter(time.Nanosecond, time.Hour*10, time.Hour*20, rng) < time.Hour*10 {\n\t\tt.Fatal(\"Min not working\")\n\t}\n\tif jitter(time.Hour, time.Nanosecond, time.Nanosecond*10, rng) > time.Nanosecond*10 {\n\t\tt.Fatal(\"Max not working\")\n\t}\n}\n\nfunc TestNoJitter(t *testing.T) {\n\tminMaxJitterTest(NoJitter, t)\n\tfor i := range 10 {\n\t\texpected := time.Second * time.Duration(i)\n\t\tif calculated := NoJitter(expected, time.Duration(0), time.Second*100, nil); calculated != expected {\n\t\t\tt.Fatalf(\"expected %v, got %v\", expected, calculated)\n\t\t}\n\t}\n}\n\nfunc TestFullJitter(t *testing.T) {\n\trng := rand.New(rand.NewSource(0))\n\tminMaxJitterTest(FullJitter, t)\n\tconst numBuckets = 51\n\tconst multiplier = 10\n\tconst threshold = 20\n\n\thistogram := make([]int, numBuckets)\n\n\tfor range (numBuckets - 1) * multiplier {\n\t\tstarted := time.Nanosecond * 50\n\t\tcalculated := FullJitter(started, 0, 100, rng)\n\t\thistogram[calculated]++\n\t}\n\n\tfor _, count := range histogram {\n\t\tif count > threshold {\n\t\t\tt.Fatal(\"jitter is not close to evenly spread\")\n\t\t}\n\t}\n\n\tif histogram[numBuckets-1] > 0 {\n\t\tt.Fatal(\"jitter increased overall time\")\n\t}\n}\n\nfunc TestManyBackoffFactory(t *testing.T) {\n\trngSource := rand.NewSource(0)\n\tconcurrent := 10\n\n\tt.Run(\"Exponential\", func(_ *testing.T) {\n\t\ttestManyBackoffFactoryHelper(concurrent,\n\t\t\tNewExponentialBackoff(time.Millisecond*650, time.Second*7, FullJitter, time.Second, 1.5, -time.Millisecond*400, rngSource),\n\t\t)\n\t})\n\tt.Run(\"Polynomial\", func(_ *testing.T) {\n\t\ttestManyBackoffFactoryHelper(concurrent,\n\t\t\tNewPolynomialBackoff(time.Second, time.Second*33, NoJitter, time.Second, []float64{0.5, 2, 3}, rngSource),\n\t\t)\n\t})\n\tt.Run(\"Fixed\", func(_ *testing.T) {\n\t\ttestManyBackoffFactoryHelper(concurrent,\n\t\t\tNewFixedBackoff(time.Second),\n\t\t)\n\t})\n}\n\nfunc testManyBackoffFactoryHelper(concurrent int, bkf BackoffFactory) {\n\tbackoffCh := make(chan BackoffStrategy, concurrent)\n\n\terrGrp := errgroup.Group{}\n\tfor range concurrent {\n\t\terrGrp.Go(func() (err error) {\n\t\t\tdefer func() {\n\t\t\t\tif r := recover(); r != nil {\n\t\t\t\t\terr = fmt.Errorf(\"panic %v\", r)\n\t\t\t\t}\n\t\t\t}()\n\t\t\tbackoffCh <- bkf()\n\t\t\treturn\n\t\t})\n\t}\n\tif err := errGrp.Wait(); err != nil {\n\t\tpanic(err)\n\t}\n\tclose(backoffCh)\n\n\terrGrp = errgroup.Group{}\n\tfor b := range backoffCh {\n\t\tbackoff := b\n\t\terrGrp.Go(func() (err error) {\n\t\t\tdefer func() {\n\t\t\t\tif r := recover(); r != nil {\n\t\t\t\t\terr = fmt.Errorf(\"panic %v\", r)\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\tfor range 5 {\n\t\t\t\tfor range 10 {\n\t\t\t\t\tbackoff.Delay()\n\t\t\t\t}\n\t\t\t\tbackoff.Reset()\n\t\t\t}\n\t\t\treturn\n\t\t})\n\t}\n\n\tif err := errGrp.Wait(); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "p2p/discovery/backoff/backoffcache.go",
    "content": "package backoff\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/discovery\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\n// BackoffDiscovery is an implementation of discovery that caches peer data and attenuates repeated queries\ntype BackoffDiscovery struct {\n\tdisc         discovery.Discovery\n\tstratFactory BackoffFactory\n\tpeerCache    map[string]*backoffCache\n\tpeerCacheMux sync.RWMutex\n\n\tparallelBufSz int\n\treturnedBufSz int\n\n\tclock clock\n}\n\ntype BackoffDiscoveryOption func(*BackoffDiscovery) error\n\nfunc NewBackoffDiscovery(disc discovery.Discovery, stratFactory BackoffFactory, opts ...BackoffDiscoveryOption) (discovery.Discovery, error) {\n\tb := &BackoffDiscovery{\n\t\tdisc:         disc,\n\t\tstratFactory: stratFactory,\n\t\tpeerCache:    make(map[string]*backoffCache),\n\n\t\tparallelBufSz: 32,\n\t\treturnedBufSz: 32,\n\n\t\tclock: realClock{},\n\t}\n\n\tfor _, opt := range opts {\n\t\tif err := opt(b); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn b, nil\n}\n\n// WithBackoffDiscoverySimultaneousQueryBufferSize sets the buffer size for the channels between the main FindPeers query\n// for a given namespace and all simultaneous FindPeers queries for the namespace\nfunc WithBackoffDiscoverySimultaneousQueryBufferSize(size int) BackoffDiscoveryOption {\n\treturn func(b *BackoffDiscovery) error {\n\t\tif size < 0 {\n\t\t\treturn fmt.Errorf(\"cannot set size to be smaller than 0\")\n\t\t}\n\t\tb.parallelBufSz = size\n\t\treturn nil\n\t}\n}\n\n// WithBackoffDiscoveryReturnedChannelSize sets the size of the buffer to be used during a FindPeer query.\n// Note: This does not apply if the query occurs during the backoff time\nfunc WithBackoffDiscoveryReturnedChannelSize(size int) BackoffDiscoveryOption {\n\treturn func(b *BackoffDiscovery) error {\n\t\tif size < 0 {\n\t\t\treturn fmt.Errorf(\"cannot set size to be smaller than 0\")\n\t\t}\n\t\tb.returnedBufSz = size\n\t\treturn nil\n\t}\n}\n\ntype clock interface {\n\tNow() time.Time\n}\n\ntype realClock struct{}\n\nfunc (c realClock) Now() time.Time {\n\treturn time.Now()\n}\n\ntype backoffCache struct {\n\t// strat is assigned on creation and not written to\n\tstrat BackoffStrategy\n\n\tmux          sync.Mutex // guards writes to all following fields\n\tnextDiscover time.Time\n\tprevPeers    map[peer.ID]peer.AddrInfo\n\tpeers        map[peer.ID]peer.AddrInfo\n\tsendingChs   map[chan peer.AddrInfo]int\n\tongoing      bool\n\n\tclock clock\n}\n\nfunc (d *BackoffDiscovery) Advertise(ctx context.Context, ns string, opts ...discovery.Option) (time.Duration, error) {\n\treturn d.disc.Advertise(ctx, ns, opts...)\n}\n\nfunc (d *BackoffDiscovery) FindPeers(ctx context.Context, ns string, opts ...discovery.Option) (<-chan peer.AddrInfo, error) {\n\t// Get options\n\tvar options discovery.Options\n\terr := options.Apply(opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Get cached peers\n\td.peerCacheMux.RLock()\n\tc, ok := d.peerCache[ns]\n\td.peerCacheMux.RUnlock()\n\n\t/*\n\t\tOverall plan:\n\t\tIf it's time to look for peers, look for peers, then return them\n\t\tIf it's not time then return cache\n\t\tIf it's time to look for peers, but we have already started looking. Get up to speed with ongoing request\n\t*/\n\n\t// Setup cache if we don't have one yet\n\tif !ok {\n\t\tpc := &backoffCache{\n\t\t\tnextDiscover: time.Time{},\n\t\t\tprevPeers:    make(map[peer.ID]peer.AddrInfo),\n\t\t\tpeers:        make(map[peer.ID]peer.AddrInfo),\n\t\t\tsendingChs:   make(map[chan peer.AddrInfo]int),\n\t\t\tstrat:        d.stratFactory(),\n\t\t\tclock:        d.clock,\n\t\t}\n\n\t\td.peerCacheMux.Lock()\n\t\tc, ok = d.peerCache[ns]\n\n\t\tif !ok {\n\t\t\td.peerCache[ns] = pc\n\t\t\tc = pc\n\t\t}\n\n\t\td.peerCacheMux.Unlock()\n\t}\n\n\tc.mux.Lock()\n\tdefer c.mux.Unlock()\n\n\ttimeExpired := d.clock.Now().After(c.nextDiscover)\n\n\t// If it's not yet time to search again and no searches are in progress then return cached peers\n\tif !(timeExpired || c.ongoing) {\n\t\tchLen := options.Limit\n\n\t\tif chLen == 0 {\n\t\t\tchLen = len(c.prevPeers)\n\t\t} else if chLen > len(c.prevPeers) {\n\t\t\tchLen = len(c.prevPeers)\n\t\t}\n\t\tpch := make(chan peer.AddrInfo, chLen)\n\t\tfor _, ai := range c.prevPeers {\n\t\t\tselect {\n\t\t\tcase pch <- ai:\n\t\t\tdefault:\n\t\t\t\t// skip if we have asked for a lower limit than the number of peers known\n\t\t\t}\n\t\t}\n\t\tclose(pch)\n\t\treturn pch, nil\n\t}\n\n\t// If a request is not already in progress setup a dispatcher channel for dispatching incoming peers\n\tif !c.ongoing {\n\t\tpch, err := d.disc.FindPeers(ctx, ns, opts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tc.ongoing = true\n\t\tgo findPeerDispatcher(ctx, c, pch)\n\t}\n\n\t// Setup receiver channel for receiving peers from ongoing requests\n\tevtCh := make(chan peer.AddrInfo, d.parallelBufSz)\n\tpch := make(chan peer.AddrInfo, d.returnedBufSz)\n\trcvPeers := make([]peer.AddrInfo, 0, 32)\n\tfor _, ai := range c.peers {\n\t\trcvPeers = append(rcvPeers, ai)\n\t}\n\tc.sendingChs[evtCh] = options.Limit\n\n\tgo findPeerReceiver(ctx, pch, evtCh, rcvPeers)\n\n\treturn pch, nil\n}\n\nfunc findPeerDispatcher(ctx context.Context, c *backoffCache, pch <-chan peer.AddrInfo) {\n\tdefer func() {\n\t\tc.mux.Lock()\n\n\t\t// If the peer addresses have changed reset the backoff\n\t\tif checkUpdates(c.prevPeers, c.peers) {\n\t\t\tc.strat.Reset()\n\t\t\tc.prevPeers = c.peers\n\t\t}\n\t\tc.nextDiscover = c.clock.Now().Add(c.strat.Delay())\n\n\t\tc.ongoing = false\n\t\tc.peers = make(map[peer.ID]peer.AddrInfo)\n\n\t\tfor ch := range c.sendingChs {\n\t\t\tclose(ch)\n\t\t}\n\t\tc.sendingChs = make(map[chan peer.AddrInfo]int)\n\t\tc.mux.Unlock()\n\t}()\n\n\tfor {\n\t\tselect {\n\t\tcase ai, ok := <-pch:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tc.mux.Lock()\n\n\t\t\t// If we receive the same peer multiple times return the address union\n\t\t\tvar sendAi peer.AddrInfo\n\t\t\tif prevAi, ok := c.peers[ai.ID]; ok {\n\t\t\t\tif combinedAi := mergeAddrInfos(prevAi, ai); combinedAi != nil {\n\t\t\t\t\tsendAi = *combinedAi\n\t\t\t\t} else {\n\t\t\t\t\tc.mux.Unlock()\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tsendAi = ai\n\t\t\t}\n\n\t\t\tc.peers[ai.ID] = sendAi\n\n\t\t\tfor ch, rem := range c.sendingChs {\n\t\t\t\tif rem > 0 {\n\t\t\t\t\tch <- sendAi\n\t\t\t\t\tc.sendingChs[ch] = rem - 1\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tc.mux.Unlock()\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc findPeerReceiver(ctx context.Context, pch, evtCh chan peer.AddrInfo, rcvPeers []peer.AddrInfo) {\n\tdefer close(pch)\n\n\tfor {\n\t\tselect {\n\t\tcase ai, ok := <-evtCh:\n\t\t\tif ok {\n\t\t\t\trcvPeers = append(rcvPeers, ai)\n\n\t\t\t\tsentAll := true\n\t\t\tsendPeers:\n\t\t\t\tfor i, p := range rcvPeers {\n\t\t\t\t\tselect {\n\t\t\t\t\tcase pch <- p:\n\t\t\t\t\tdefault:\n\t\t\t\t\t\trcvPeers = rcvPeers[i:]\n\t\t\t\t\t\tsentAll = false\n\t\t\t\t\t\tbreak sendPeers\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif sentAll {\n\t\t\t\t\trcvPeers = []peer.AddrInfo{}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfor _, p := range rcvPeers {\n\t\t\t\t\tselect {\n\t\t\t\t\tcase pch <- p:\n\t\t\t\t\tcase <-ctx.Done():\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc mergeAddrInfos(prevAi, newAi peer.AddrInfo) *peer.AddrInfo {\n\tseen := make(map[string]struct{}, len(prevAi.Addrs))\n\tcombinedAddrs := make([]ma.Multiaddr, 0, len(prevAi.Addrs))\n\taddAddrs := func(addrs []ma.Multiaddr) {\n\t\tfor _, addr := range addrs {\n\t\t\tif _, ok := seen[addr.String()]; ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tseen[addr.String()] = struct{}{}\n\t\t\tcombinedAddrs = append(combinedAddrs, addr)\n\t\t}\n\t}\n\taddAddrs(prevAi.Addrs)\n\taddAddrs(newAi.Addrs)\n\n\tif len(combinedAddrs) > len(prevAi.Addrs) {\n\t\tcombinedAi := &peer.AddrInfo{ID: prevAi.ID, Addrs: combinedAddrs}\n\t\treturn combinedAi\n\t}\n\treturn nil\n}\n\nfunc checkUpdates(orig, update map[peer.ID]peer.AddrInfo) bool {\n\tif len(orig) != len(update) {\n\t\treturn true\n\t}\n\tfor p, ai := range update {\n\t\tif prevAi, ok := orig[p]; ok {\n\t\t\tif combinedAi := mergeAddrInfos(prevAi, ai); combinedAi != nil {\n\t\t\t\treturn true\n\t\t\t}\n\t\t} else {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "p2p/discovery/backoff/backoffcache_test.go",
    "content": "package backoff\n\nimport (\n\t\"context\"\n\t\"math/rand\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/p2p/discovery/mocks\"\n\tbhost \"github.com/libp2p/go-libp2p/p2p/host/blank\"\n\tswarmt \"github.com/libp2p/go-libp2p/p2p/net/swarm/testing\"\n\n\t\"github.com/libp2p/go-libp2p/core/discovery\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\n\tmockClock \"github.com/benbjohnson/clock\"\n)\n\ntype delayedDiscovery struct {\n\tdisc  discovery.Discovery\n\tdelay time.Duration\n\tclock *mockClock.Mock\n}\n\nfunc (d *delayedDiscovery) Advertise(ctx context.Context, ns string, opts ...discovery.Option) (time.Duration, error) {\n\treturn d.disc.Advertise(ctx, ns, opts...)\n}\n\nfunc (d *delayedDiscovery) FindPeers(ctx context.Context, ns string, opts ...discovery.Option) (<-chan peer.AddrInfo, error) {\n\tdch, err := d.disc.FindPeers(ctx, ns, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tch := make(chan peer.AddrInfo, 32)\n\tdoneCh := make(chan struct{})\n\tgo func() {\n\t\tdefer close(ch)\n\t\tdefer close(doneCh)\n\t\tfor ai := range dch {\n\t\t\tch <- ai\n\t\t\td.clock.Sleep(d.delay)\n\t\t}\n\t}()\n\n\t// Tick the clock forward to advance the sleep above\n\tgo func() {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-doneCh:\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t\td.clock.Add(d.delay)\n\t\t\t}\n\t\t}\n\t}()\n\n\treturn ch, nil\n}\n\nfunc assertNumPeers(t *testing.T, ctx context.Context, d discovery.Discovery, ns string, count int) {\n\tt.Helper()\n\tassertNumPeersWithLimit(t, ctx, d, ns, 10, count)\n}\n\nfunc assertNumPeersWithLimit(t *testing.T, ctx context.Context, d discovery.Discovery, ns string, limit int, count int) {\n\tt.Helper()\n\tpeerCh, err := d.FindPeers(ctx, ns, discovery.Limit(limit))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tpeerset := make(map[peer.ID]struct{})\n\tfor p := range peerCh {\n\t\tpeerset[p.ID] = struct{}{}\n\t}\n\n\tif len(peerset) != count {\n\t\tt.Fatalf(\"Was supposed to find %d, found %d instead\", count, len(peerset))\n\t}\n}\n\n// withClock lets you override the default time.Now() call. Useful for tests.\nfunc withClock(c clock) BackoffDiscoveryOption {\n\treturn func(b *BackoffDiscovery) error {\n\t\tb.clock = c\n\t\treturn nil\n\t}\n}\n\nfunc TestBackoffDiscoverySingleBackoff(t *testing.T) {\n\tctx := t.Context()\n\n\tclock := mockClock.NewMock()\n\tdiscServer := mocks.NewDiscoveryServer(clock)\n\n\th1 := bhost.NewBlankHost(swarmt.GenSwarm(t))\n\tdefer h1.Close()\n\th2 := bhost.NewBlankHost(swarmt.GenSwarm(t))\n\tdefer h2.Close()\n\td1 := mocks.NewDiscoveryClient(h1, discServer)\n\td2 := mocks.NewDiscoveryClient(h2, discServer)\n\n\tbkf := NewExponentialBackoff(\n\t\ttime.Millisecond*100,\n\t\ttime.Second*10,\n\t\tNoJitter,\n\t\ttime.Millisecond*100,\n\t\t2.5,\n\t\t0,\n\t\trand.NewSource(0),\n\t)\n\tdCache, err := NewBackoffDiscovery(d1, bkf, withClock(clock))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tconst ns = \"test\"\n\n\t// try adding a peer then find it\n\td1.Advertise(ctx, ns, discovery.TTL(time.Hour))\n\t// Advance clock by one step\n\tclock.Add(1)\n\tassertNumPeers(t, ctx, dCache, ns, 1)\n\n\t// add a new peer and make sure it is still hidden by the caching layer\n\td2.Advertise(ctx, ns, discovery.TTL(time.Hour))\n\t// Advance clock by one step\n\tclock.Add(1)\n\tassertNumPeers(t, ctx, dCache, ns, 1)\n\n\t// wait for cache to expire and check for the new peer\n\tclock.Add(time.Millisecond * 110)\n\tassertNumPeers(t, ctx, dCache, ns, 2)\n}\n\nfunc TestBackoffDiscoveryMultipleBackoff(t *testing.T) {\n\tclock := mockClock.NewMock()\n\tctx := t.Context()\n\n\tdiscServer := mocks.NewDiscoveryServer(clock)\n\n\th1 := bhost.NewBlankHost(swarmt.GenSwarm(t))\n\tdefer h1.Close()\n\th2 := bhost.NewBlankHost(swarmt.GenSwarm(t))\n\tdefer h2.Close()\n\td1 := mocks.NewDiscoveryClient(h1, discServer)\n\td2 := mocks.NewDiscoveryClient(h2, discServer)\n\n\t// Startup delay is 0ms. First backoff after finding data is 100ms, second backoff is 250ms.\n\tbkf := NewExponentialBackoff(\n\t\ttime.Millisecond*100,\n\t\ttime.Second*10,\n\t\tNoJitter,\n\t\ttime.Millisecond*100,\n\t\t2.5,\n\t\t0,\n\t\trand.NewSource(0),\n\t)\n\tdCache, err := NewBackoffDiscovery(d1, bkf, withClock(clock))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tconst ns = \"test\"\n\n\t// try adding a peer then find it\n\td1.Advertise(ctx, ns, discovery.TTL(time.Hour))\n\t// Advance clock by one step\n\tclock.Add(1)\n\tassertNumPeers(t, ctx, dCache, ns, 1)\n\n\t// wait a little to make sure the extra request doesn't modify the backoff\n\tclock.Add(time.Millisecond * 50) // 50 < 100\n\tassertNumPeers(t, ctx, dCache, ns, 1)\n\n\t// wait for backoff to expire and check if we increase it\n\tclock.Add(time.Millisecond * 60) // 50+60 > 100\n\tassertNumPeers(t, ctx, dCache, ns, 1)\n\n\td2.Advertise(ctx, ns, discovery.TTL(time.Millisecond*400))\n\n\tclock.Add(time.Millisecond * 150) // 150 < 250\n\tassertNumPeers(t, ctx, dCache, ns, 1)\n\n\tclock.Add(time.Millisecond * 150) // 150 + 150 > 250\n\tassertNumPeers(t, ctx, dCache, ns, 2)\n\n\t// check that the backoff has been reset\n\t// also checks that we can decrease our peer count (i.e. not just growing a set)\n\tclock.Add(time.Millisecond * 110) // 110 > 100, also 150+150+110>400\n\tassertNumPeers(t, ctx, dCache, ns, 1)\n}\n\nfunc TestBackoffDiscoverySimultaneousQuery(t *testing.T) {\n\tctx := t.Context()\n\n\tclock := mockClock.NewMock()\n\tdiscServer := mocks.NewDiscoveryServer(clock)\n\n\t// Testing with n larger than most internal buffer sizes (32)\n\tn := 40\n\tadvertisers := make([]discovery.Discovery, n)\n\n\tfor i := range n {\n\t\th := bhost.NewBlankHost(swarmt.GenSwarm(t))\n\t\tdefer h.Close()\n\t\tadvertisers[i] = mocks.NewDiscoveryClient(h, discServer)\n\t}\n\n\td1 := &delayedDiscovery{advertisers[0], time.Millisecond * 10, clock}\n\n\tbkf := NewFixedBackoff(time.Millisecond * 200)\n\tdCache, err := NewBackoffDiscovery(d1, bkf, withClock(clock))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tconst ns = \"test\"\n\n\tfor _, a := range advertisers {\n\t\tif _, err := a.Advertise(ctx, ns, discovery.TTL(time.Hour)); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n\t// Advance clock by one step\n\tclock.Add(1)\n\n\tch1, err := dCache.FindPeers(ctx, ns)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t<-ch1\n\tch2, err := dCache.FindPeers(ctx, ns)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tszCh2 := 0\n\tfor ai := range ch2 {\n\t\t_ = ai\n\t\tszCh2++\n\t}\n\n\tszCh1 := 1\n\tfor range ch1 {\n\t\tszCh1++\n\t}\n\n\tif szCh1 != n && szCh2 != n {\n\t\tt.Fatalf(\"Channels returned %d, %d elements instead of %d\", szCh1, szCh2, n)\n\t}\n}\n\nfunc TestBackoffDiscoveryCacheCapacity(t *testing.T) {\n\tctx := t.Context()\n\n\tclock := mockClock.NewMock()\n\tdiscServer := mocks.NewDiscoveryServer(clock)\n\n\t// Testing with n larger than most internal buffer sizes (32)\n\tn := 40\n\tadvertisers := make([]discovery.Discovery, n)\n\n\tfor i := range n {\n\t\th := bhost.NewBlankHost(swarmt.GenSwarm(t))\n\t\tdefer h.Close()\n\t\tadvertisers[i] = mocks.NewDiscoveryClient(h, discServer)\n\t}\n\n\th1 := bhost.NewBlankHost(swarmt.GenSwarm(t))\n\td1 := mocks.NewDiscoveryClient(h1, discServer)\n\n\tdiscoveryInterval := time.Millisecond * 10\n\n\tbkf := NewFixedBackoff(discoveryInterval)\n\tdCache, err := NewBackoffDiscovery(d1, bkf, withClock(clock))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tconst ns = \"test\"\n\n\t// add speers\n\tfor i := range n {\n\t\tadvertisers[i].Advertise(ctx, ns, discovery.TTL(time.Hour))\n\t}\n\t// Advance clock by one step\n\tclock.Add(1)\n\n\t// Request all peers, all will be present\n\tassertNumPeersWithLimit(t, ctx, dCache, ns, n, n)\n\n\t// Request peers with a lower limit\n\tassertNumPeersWithLimit(t, ctx, dCache, ns, n-1, n-1)\n\n\t// Wait a little time but don't allow cache to expire\n\tclock.Add(discoveryInterval / 10)\n\n\t// Request peers with a lower limit this time using cache\n\t// Here we are testing that the cache logic does not block when there are more peers known than the limit requested\n\t// See https://github.com/libp2p/go-libp2p-discovery/issues/67\n\tassertNumPeersWithLimit(t, ctx, dCache, ns, n-1, n-1)\n\n\t// Wait for next discovery so next request will bypass cache\n\tclock.Add(time.Millisecond * 100)\n\n\t// Ask for all peers again\n\tassertNumPeersWithLimit(t, ctx, dCache, ns, n, n)\n}\n"
  },
  {
    "path": "p2p/discovery/backoff/backoffconnector.go",
    "content": "package backoff\n\nimport (\n\t\"context\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\n\tlru \"github.com/hashicorp/golang-lru/v2\"\n)\n\n// BackoffConnector is a utility to connect to peers, but only if we have not recently tried connecting to them already\ntype BackoffConnector struct {\n\tcache      *lru.TwoQueueCache[peer.ID, *connCacheData]\n\thost       host.Host\n\tconnTryDur time.Duration\n\tbackoff    BackoffFactory\n\tmux        sync.Mutex\n}\n\n// NewBackoffConnector creates a utility to connect to peers, but only if we have not recently tried connecting to them already\n// cacheSize is the size of a TwoQueueCache\n// connectionTryDuration is how long we attempt to connect to a peer before giving up\n// backoff describes the strategy used to decide how long to backoff after previously attempting to connect to a peer\nfunc NewBackoffConnector(h host.Host, cacheSize int, connectionTryDuration time.Duration, backoff BackoffFactory) (*BackoffConnector, error) {\n\tcache, err := lru.New2Q[peer.ID, *connCacheData](cacheSize)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &BackoffConnector{\n\t\tcache:      cache,\n\t\thost:       h,\n\t\tconnTryDur: connectionTryDuration,\n\t\tbackoff:    backoff,\n\t}, nil\n}\n\ntype connCacheData struct {\n\tnextTry time.Time\n\tstrat   BackoffStrategy\n}\n\n// Connect attempts to connect to the peers passed in by peerCh. Will not connect to peers if they are within the backoff period.\n// As Connect will attempt to dial peers as soon as it learns about them, the caller should try to keep the number,\n// and rate, of inbound peers manageable.\nfunc (c *BackoffConnector) Connect(ctx context.Context, peerCh <-chan peer.AddrInfo) {\n\tfor {\n\t\tselect {\n\t\tcase pi, ok := <-peerCh:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif pi.ID == c.host.ID() || pi.ID == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tc.mux.Lock()\n\t\t\tvar cachedPeer *connCacheData\n\t\t\tif tv, ok := c.cache.Get(pi.ID); ok {\n\t\t\t\tnow := time.Now()\n\t\t\t\tif now.Before(tv.nextTry) {\n\t\t\t\t\tc.mux.Unlock()\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\ttv.nextTry = now.Add(tv.strat.Delay())\n\t\t\t} else {\n\t\t\t\tcachedPeer = &connCacheData{strat: c.backoff()}\n\t\t\t\tcachedPeer.nextTry = time.Now().Add(cachedPeer.strat.Delay())\n\t\t\t\tc.cache.Add(pi.ID, cachedPeer)\n\t\t\t}\n\t\t\tc.mux.Unlock()\n\n\t\t\tgo func(pi peer.AddrInfo) {\n\t\t\t\tctx, cancel := context.WithTimeout(ctx, c.connTryDur)\n\t\t\t\tdefer cancel()\n\n\t\t\t\terr := c.host.Connect(ctx, pi)\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Debug(\"Error connecting to pubsub peer\", \"peer\", pi.ID, \"err\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}(pi)\n\n\t\tcase <-ctx.Done():\n\t\t\tlog.Info(\"discovery: backoff connector context error\", \"err\", ctx.Err())\n\t\t\treturn\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "p2p/discovery/backoff/backoffconnector_test.go",
    "content": "package backoff\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\tbhost \"github.com/libp2p/go-libp2p/p2p/host/blank\"\n\tswarmt \"github.com/libp2p/go-libp2p/p2p/net/swarm/testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype maxDialHost struct {\n\thost.Host\n\n\tmux            sync.Mutex\n\ttimesDialed    map[peer.ID]int\n\tmaxTimesToDial map[peer.ID]int\n}\n\nfunc (h *maxDialHost) Connect(ctx context.Context, ai peer.AddrInfo) error {\n\tpid := ai.ID\n\n\th.mux.Lock()\n\tdefer h.mux.Unlock()\n\tnumDials := h.timesDialed[pid]\n\tnumDials += 1\n\th.timesDialed[pid] = numDials\n\n\tif maxDials, ok := h.maxTimesToDial[pid]; ok && numDials > maxDials {\n\t\treturn fmt.Errorf(\"should not be dialing peer %s\", pid.String())\n\t}\n\n\treturn h.Host.Connect(ctx, ai)\n}\n\nfunc getNetHosts(t *testing.T, n int) []host.Host {\n\tout := make([]host.Host, 0, n)\n\n\tfor range n {\n\t\tnetw := swarmt.GenSwarm(t)\n\t\th := bhost.NewBlankHost(netw)\n\t\tt.Cleanup(func() { h.Close() })\n\t\tout = append(out, h)\n\t}\n\n\treturn out\n}\n\nfunc loadCh(peers []host.Host) <-chan peer.AddrInfo {\n\tch := make(chan peer.AddrInfo, len(peers))\n\tfor _, p := range peers {\n\t\tch <- p.Peerstore().PeerInfo(p.ID())\n\t}\n\tclose(ch)\n\treturn ch\n}\n\nfunc TestBackoffConnector(t *testing.T) {\n\thosts := getNetHosts(t, 5)\n\tprimary := &maxDialHost{\n\t\tHost:        hosts[0],\n\t\ttimesDialed: make(map[peer.ID]int),\n\t\tmaxTimesToDial: map[peer.ID]int{\n\t\t\thosts[1].ID(): 1,\n\t\t\thosts[2].ID(): 2,\n\t\t},\n\t}\n\n\tbc, err := NewBackoffConnector(primary, 10, time.Minute, NewFixedBackoff(250*time.Millisecond))\n\trequire.NoError(t, err)\n\n\tbc.Connect(context.Background(), loadCh(hosts))\n\trequire.Eventually(t, func() bool { return len(primary.Network().Peers()) == len(hosts)-1 }, 3*time.Second, 10*time.Millisecond)\n\n\ttime.Sleep(100 * time.Millisecond) // give connection attempts time to complete (relevant when using multiple transports)\n\tfor _, c := range primary.Network().Conns() {\n\t\tc.Close()\n\t}\n\trequire.Eventually(t, func() bool { return len(primary.Network().Peers()) == 0 }, 3*time.Second, 10*time.Millisecond)\n\n\tbc.Connect(context.Background(), loadCh(hosts))\n\trequire.Empty(t, primary.Network().Peers(), \"shouldn't be connected to any peers\")\n\n\ttime.Sleep(time.Millisecond * 500)\n\tbc.Connect(context.Background(), loadCh(hosts))\n\trequire.Eventually(t, func() bool { return len(primary.Network().Peers()) == len(hosts)-2 }, 3*time.Second, 10*time.Millisecond)\n\t// make sure we actually don't connect to host 1 any more\n\ttime.Sleep(100 * time.Millisecond)\n\trequire.Len(t, primary.Network().Peers(), len(hosts)-2, \"wrong number of connections\")\n}\n"
  },
  {
    "path": "p2p/discovery/mdns/mdns.go",
    "content": "package mdns\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\t\"math/rand\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\n\t\"github.com/libp2p/zeroconf/v2\"\n\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\nconst (\n\tServiceName   = \"_p2p._udp\"\n\tmdnsDomain    = \"local\"\n\tdnsaddrPrefix = \"dnsaddr=\"\n)\n\nvar log = logging.Logger(\"mdns\")\n\ntype Service interface {\n\tStart() error\n\tio.Closer\n}\n\ntype Notifee interface {\n\tHandlePeerFound(peer.AddrInfo)\n}\n\ntype mdnsService struct {\n\thost        host.Host\n\tserviceName string\n\tpeerName    string\n\n\t// The context is canceled when Close() is called.\n\tctx       context.Context\n\tctxCancel context.CancelFunc\n\n\tresolverWG sync.WaitGroup\n\tserver     *zeroconf.Server\n\n\tnotifee Notifee\n}\n\nfunc NewMdnsService(host host.Host, serviceName string, notifee Notifee) *mdnsService {\n\tif serviceName == \"\" {\n\t\tserviceName = ServiceName\n\t}\n\ts := &mdnsService{\n\t\thost:        host,\n\t\tserviceName: serviceName,\n\t\tpeerName:    randomString(32 + rand.Intn(32)), // generate a random string between 32 and 63 characters long\n\t\tnotifee:     notifee,\n\t}\n\ts.ctx, s.ctxCancel = context.WithCancel(context.Background())\n\treturn s\n}\n\nfunc (s *mdnsService) Start() error {\n\tif err := s.startServer(); err != nil {\n\t\treturn err\n\t}\n\ts.startResolver(s.ctx)\n\treturn nil\n}\n\nfunc (s *mdnsService) Close() error {\n\ts.ctxCancel()\n\tif s.server != nil {\n\t\ts.server.Shutdown()\n\t}\n\ts.resolverWG.Wait()\n\treturn nil\n}\n\n// We don't really care about the IP addresses, but the spec (and various routers / firewalls) require us\n// to send A and AAAA records.\nfunc (s *mdnsService) getIPs(addrs []ma.Multiaddr) ([]string, error) {\n\tvar ip4, ip6 string\n\tfor _, addr := range addrs {\n\t\tfirst, _ := ma.SplitFirst(addr)\n\t\tif first == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif ip4 == \"\" && first.Protocol().Code == ma.P_IP4 {\n\t\t\tip4 = first.Value()\n\t\t} else if ip6 == \"\" && first.Protocol().Code == ma.P_IP6 {\n\t\t\tip6 = first.Value()\n\t\t}\n\t}\n\tips := make([]string, 0, 2)\n\tif ip4 != \"\" {\n\t\tips = append(ips, ip4)\n\t}\n\tif ip6 != \"\" {\n\t\tips = append(ips, ip6)\n\t}\n\tif len(ips) == 0 {\n\t\treturn nil, errors.New(\"didn't find any IP addresses\")\n\t}\n\treturn ips, nil\n}\n\n// containsUnsuitableProtocol returns true if the multiaddr includes protocols\n// that are not suitable for mDNS advertisement:\n//   - Circuit relay (requires intermediary, not direct LAN connectivity)\n//   - Browser transports: WebTransport, WebRTC, WebSocket (browsers don't use mDNS)\nfunc containsUnsuitableProtocol(addr ma.Multiaddr) bool {\n\tfound := false\n\tma.ForEach(addr, func(c ma.Component) bool {\n\t\tswitch c.Protocol().Code {\n\t\tcase ma.P_CIRCUIT,\n\t\t\tma.P_WEBTRANSPORT,\n\t\t\tma.P_WEBRTC,\n\t\t\tma.P_WEBRTC_DIRECT,\n\t\t\tma.P_P2P_WEBRTC_DIRECT,\n\t\t\tma.P_WS,\n\t\t\tma.P_WSS:\n\t\t\tfound = true\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t})\n\treturn found\n}\n\n// isSuitableForMDNS returns true for multiaddrs that should be advertised\n// via mDNS.\n//\n// For an address to be suitable:\n//  1. It must start with /ip4, /ip6, or a .local DNS name. The .local TLD is\n//     reserved for mDNS (RFC 6762) and resolved via multicast, not unicast DNS.\n//     Non-.local DNS names are filtered out as they require external DNS.\n//  2. It must not use circuit relay or browser-only transports (WebTransport,\n//     WebRTC, WebSocket) because these are not useful for direct LAN discovery.\n//\n// Filtering reduces mDNS packet size, helping stay within the recommended\n// 1500-byte limit per RFC 6762. See: https://github.com/libp2p/go-libp2p/issues/3415\nfunc isSuitableForMDNS(addr ma.Multiaddr) bool {\n\tif addr == nil {\n\t\treturn false\n\t}\n\n\tfirst, _ := ma.SplitFirst(addr)\n\tif first == nil {\n\t\treturn false\n\t}\n\n\t// Check the addressing scheme\n\tswitch first.Protocol().Code {\n\tcase ma.P_IP4, ma.P_IP6:\n\t\t// Direct IP addresses are always suitable for LAN discovery\n\tcase ma.P_DNS, ma.P_DNS4, ma.P_DNS6, ma.P_DNSADDR:\n\t\t// DNS names are only suitable if they're in the .local TLD,\n\t\t// which is resolved via mDNS (RFC 6762), not unicast DNS.\n\t\tif !strings.HasSuffix(strings.ToLower(first.Value()), \".local\") {\n\t\t\treturn false\n\t\t}\n\tdefault:\n\t\treturn false\n\t}\n\n\treturn !containsUnsuitableProtocol(addr)\n}\n\nfunc (s *mdnsService) startServer() error {\n\tinterfaceAddrs, err := s.host.Network().InterfaceListenAddresses()\n\tif err != nil {\n\t\treturn err\n\t}\n\taddrs, err := peer.AddrInfoToP2pAddrs(&peer.AddrInfo{\n\t\tID:    s.host.ID(),\n\t\tAddrs: interfaceAddrs,\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\t// Build TXT records for addresses suitable for mDNS advertisement.\n\tvar txts []string\n\tfor _, addr := range addrs {\n\t\tif isSuitableForMDNS(addr) {\n\t\t\ttxts = append(txts, dnsaddrPrefix+addr.String())\n\t\t}\n\t}\n\n\tips, err := s.getIPs(addrs)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tserver, err := zeroconf.RegisterProxy(\n\t\ts.peerName,\n\t\ts.serviceName,\n\t\tmdnsDomain,\n\t\t4001, // we have to pass in a port number here, but libp2p only uses the TXT records\n\t\ts.peerName,\n\t\tips,\n\t\ttxts,\n\t\tnil,\n\t)\n\tif err != nil {\n\t\treturn err\n\t}\n\ts.server = server\n\treturn nil\n}\n\nfunc (s *mdnsService) startResolver(ctx context.Context) {\n\ts.resolverWG.Add(2)\n\tentryChan := make(chan *zeroconf.ServiceEntry, 1000)\n\tgo func() {\n\t\tdefer s.resolverWG.Done()\n\t\tfor entry := range entryChan {\n\t\t\t// We only care about the TXT records.\n\t\t\t// Ignore A, AAAA and PTR.\n\t\t\taddrs := make([]ma.Multiaddr, 0, len(entry.Text)) // assume that all TXT records are dnsaddrs\n\t\t\tfor _, s := range entry.Text {\n\t\t\t\tif !strings.HasPrefix(s, dnsaddrPrefix) {\n\t\t\t\t\tlog.Debug(\"missing dnsaddr prefix\")\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\taddr, err := ma.NewMultiaddr(s[len(dnsaddrPrefix):])\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Debug(\"failed to parse multiaddr\", \"err\", err)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\taddrs = append(addrs, addr)\n\t\t\t}\n\t\t\tinfos, err := peer.AddrInfosFromP2pAddrs(addrs...)\n\t\t\tif err != nil {\n\t\t\t\tlog.Debug(\"failed to get peer info\", \"err\", err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tfor _, info := range infos {\n\t\t\t\tif info.ID == s.host.ID() {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tgo s.notifee.HandlePeerFound(info)\n\t\t\t}\n\t\t}\n\t}()\n\tgo func() {\n\t\tdefer s.resolverWG.Done()\n\t\tif err := zeroconf.Browse(ctx, s.serviceName, mdnsDomain, entryChan); err != nil {\n\t\t\tlog.Debug(\"zeroconf browsing failed\", \"err\", err)\n\t\t}\n\t}()\n}\n\nfunc randomString(l int) string {\n\tconst alphabet = \"abcdefghijklmnopqrstuvwxyz0123456789\"\n\ts := make([]byte, 0, l)\n\tfor range l {\n\t\ts = append(s, alphabet[rand.Intn(len(alphabet))])\n\t}\n\treturn string(s)\n}\n"
  },
  {
    "path": "p2p/discovery/mdns/mdns_test.go",
    "content": "package mdns\n\nimport (\n\t\"os\"\n\t\"runtime\"\n\t\"slices\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc setupMDNS(t *testing.T, notifee Notifee) peer.ID {\n\tt.Helper()\n\thost, err := libp2p.New(libp2p.ListenAddrStrings(\"/ip4/127.0.0.1/tcp/0\"))\n\trequire.NoError(t, err)\n\ts := NewMdnsService(host, \"\", notifee)\n\trequire.NoError(t, s.Start())\n\tt.Cleanup(func() {\n\t\thost.Close()\n\t\ts.Close()\n\t})\n\treturn host.ID()\n}\n\ntype notif struct {\n\tmutex sync.Mutex\n\tinfos []peer.AddrInfo\n}\n\nvar _ Notifee = &notif{}\n\nfunc (n *notif) HandlePeerFound(info peer.AddrInfo) {\n\tn.mutex.Lock()\n\tn.infos = append(n.infos, info)\n\tn.mutex.Unlock()\n}\n\nfunc (n *notif) GetPeers() []peer.AddrInfo {\n\tn.mutex.Lock()\n\tdefer n.mutex.Unlock()\n\tinfos := make([]peer.AddrInfo, 0, len(n.infos))\n\tinfos = append(infos, n.infos...)\n\treturn infos\n}\n\nfunc TestOtherDiscovery(t *testing.T) {\n\tif runtime.GOOS != \"linux\" && os.Getenv(\"CI\") != \"\" {\n\t\tt.Skip(\"this test is flaky on CI outside of linux\")\n\t}\n\n\tconst n = 4\n\n\tnotifs := make([]*notif, n)\n\thostIDs := make([]peer.ID, n)\n\tfor i := range n {\n\t\tnotif := &notif{}\n\t\tnotifs[i] = notif\n\t\thostIDs[i] = setupMDNS(t, notif)\n\t}\n\n\tcontainsAllHostIDs := func(ids []peer.ID, currentHostID peer.ID) bool {\n\t\tfor _, id := range hostIDs {\n\t\t\tvar found bool\n\t\t\tif currentHostID == id {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif slices.Contains(ids, id) {\n\t\t\t\tfound = true\n\t\t\t}\n\t\t\tif !found {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\t}\n\n\tassert.Eventuallyf(\n\t\tt,\n\t\tfunc() bool {\n\t\t\tfor i, notif := range notifs {\n\t\t\t\tinfos := notif.GetPeers()\n\t\t\t\tids := make([]peer.ID, 0, len(infos))\n\t\t\t\tfor _, info := range infos {\n\t\t\t\t\tids = append(ids, info.ID)\n\t\t\t\t}\n\t\t\t\tif !containsAllHostIDs(ids, hostIDs[i]) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true\n\t\t},\n\t\t5*time.Second,\n\t\t100*time.Millisecond,\n\t\t\"expected peers to find each other\",\n\t)\n}\n\nfunc TestIsSuitableForMDNS(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\taddr     string\n\t\texpected bool\n\t}{\n\t\t// IP addresses with native transports - suitable for mDNS\n\t\t{\"tcp\", \"/ip4/192.168.1.1/tcp/4001\", true},\n\t\t{\"quic-v1\", \"/ip4/192.168.1.2/udp/4001/quic-v1\", true},\n\t\t{\"tcp-ipv6\", \"/ip6/fe80::1/tcp/4001\", true},\n\t\t{\"quic-v1-ipv6\", \"/ip6/fe80::2/udp/4001/quic-v1\", true},\n\n\t\t// Browser transports - NOT suitable for mDNS\n\t\t// (browsers don't use mDNS for peer discovery)\n\t\t{\"webtransport\", \"/ip4/192.168.1.1/udp/4001/quic-v1/webtransport\", false},\n\t\t{\"webrtc\", \"/ip4/192.168.1.1/udp/4001/webrtc/certhash/uEiAkH5a4DPGKUuOBjYw0CgwjLa2R_RF71v86aVxlqdKNOQ\", false},\n\t\t{\"webrtc-direct\", \"/ip4/192.168.1.1/udp/4001/webrtc-direct\", false},\n\t\t{\"ws\", \"/ip4/192.168.1.1/tcp/4001/ws\", false},\n\t\t{\"wss\", \"/ip4/192.168.1.1/tcp/443/wss\", false},\n\n\t\t// .local DNS names - suitable for mDNS\n\t\t// (.local TLD is resolved via mDNS per RFC 6762)\n\t\t{\"dns-local\", \"/dns/myhost.local/tcp/4001\", true},\n\t\t{\"dns4-local\", \"/dns4/myhost.local/tcp/4001\", true},\n\t\t{\"dns6-local\", \"/dns6/myhost.local/tcp/4001\", true},\n\t\t{\"dnsaddr-local\", \"/dnsaddr/myhost.local/tcp/4001\", true},\n\t\t{\"dns-local-mixed-case\", \"/dns4/MyHost.LOCAL/tcp/4001\", true},\n\n\t\t// Non-.local DNS names - NOT suitable for mDNS\n\t\t// (require unicast DNS resolution, not mDNS)\n\t\t{\"dns4-public\", \"/dns4/example.com/tcp/4001\", false},\n\t\t{\"dns6-public\", \"/dns6/example.com/tcp/4001\", false},\n\t\t{\"dnsaddr-public\", \"/dnsaddr/example.com/tcp/4001\", false},\n\t\t{\"dns-local-suffix-not-tld\", \"/dns4/notlocal.com/tcp/4001\", false},\n\t\t{\"dns-fake-local\", \"/dns4/local.example.com/tcp/4001\", false},\n\t\t{\"libp2p-direct\", \"/dns4/192-0-2-1.k51qzi5uqu5dgutdk6i1ynyzgkqngpha5xpgia3a5qqp4jsh0u4csozksxel3r.libp2p.direct/tcp/30895/tls/ws\", false},\n\n\t\t// Circuit relay addresses - NOT suitable for mDNS\n\t\t// (require relay node, not direct LAN connectivity)\n\t\t{\"circuit-relay\", \"/ip4/198.51.100.1/tcp/4001/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN/p2p-circuit/p2p/12D3KooWGzBXWNvHpLALvz3jhwdCF6kfv9MfhMn9CuS2MBD2GpSy\", false},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\taddr, err := ma.NewMultiaddr(tc.addr)\n\t\t\trequire.NoError(t, err)\n\t\t\tgot := isSuitableForMDNS(addr)\n\t\t\tassert.Equal(t, tc.expected, got, \"isSuitableForMDNS(%s)\", tc.addr)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "p2p/discovery/mocks/mocks.go",
    "content": "package mocks\n\nimport (\n\t\"context\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/discovery\"\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n)\n\ntype clock interface {\n\tNow() time.Time\n}\n\ntype MockDiscoveryServer struct {\n\tmx    sync.Mutex\n\tdb    map[string]map[peer.ID]*discoveryRegistration\n\tclock clock\n}\n\ntype discoveryRegistration struct {\n\tinfo       peer.AddrInfo\n\texpiration time.Time\n}\n\nfunc NewDiscoveryServer(clock clock) *MockDiscoveryServer {\n\treturn &MockDiscoveryServer{\n\t\tdb:    make(map[string]map[peer.ID]*discoveryRegistration),\n\t\tclock: clock,\n\t}\n}\n\nfunc (s *MockDiscoveryServer) Advertise(ns string, info peer.AddrInfo, ttl time.Duration) (time.Duration, error) {\n\ts.mx.Lock()\n\tdefer s.mx.Unlock()\n\n\tpeers, ok := s.db[ns]\n\tif !ok {\n\t\tpeers = make(map[peer.ID]*discoveryRegistration)\n\t\ts.db[ns] = peers\n\t}\n\tpeers[info.ID] = &discoveryRegistration{info, s.clock.Now().Add(ttl)}\n\treturn ttl, nil\n}\n\nfunc (s *MockDiscoveryServer) FindPeers(ns string, limit int) (<-chan peer.AddrInfo, error) {\n\ts.mx.Lock()\n\tdefer s.mx.Unlock()\n\n\tpeers, ok := s.db[ns]\n\tif !ok || len(peers) == 0 {\n\t\temptyCh := make(chan peer.AddrInfo)\n\t\tclose(emptyCh)\n\t\treturn emptyCh, nil\n\t}\n\n\tcount := len(peers)\n\tif limit != 0 && count > limit {\n\t\tcount = limit\n\t}\n\n\titerTime := s.clock.Now()\n\tch := make(chan peer.AddrInfo, count)\n\tnumSent := 0\n\tfor p, reg := range peers {\n\t\tif numSent == count {\n\t\t\tbreak\n\t\t}\n\t\tif iterTime.After(reg.expiration) {\n\t\t\tdelete(peers, p)\n\t\t\tcontinue\n\t\t}\n\n\t\tnumSent++\n\t\tch <- reg.info\n\t}\n\tclose(ch)\n\n\treturn ch, nil\n}\n\ntype MockDiscoveryClient struct {\n\thost   host.Host\n\tserver *MockDiscoveryServer\n}\n\nfunc NewDiscoveryClient(h host.Host, server *MockDiscoveryServer) *MockDiscoveryClient {\n\treturn &MockDiscoveryClient{\n\t\thost:   h,\n\t\tserver: server,\n\t}\n}\n\nfunc (d *MockDiscoveryClient) Advertise(_ context.Context, ns string, opts ...discovery.Option) (time.Duration, error) {\n\tvar options discovery.Options\n\terr := options.Apply(opts...)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn d.server.Advertise(ns, *host.InfoFromHost(d.host), options.Ttl)\n}\n\nfunc (d *MockDiscoveryClient) FindPeers(_ context.Context, ns string, opts ...discovery.Option) (<-chan peer.AddrInfo, error) {\n\tvar options discovery.Options\n\terr := options.Apply(opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn d.server.FindPeers(ns, options.Limit)\n}\n"
  },
  {
    "path": "p2p/discovery/routing/routing.go",
    "content": "package routing\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/discovery\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/routing\"\n\n\t\"github.com/ipfs/go-cid\"\n\tmh \"github.com/multiformats/go-multihash\"\n)\n\n// RoutingDiscovery is an implementation of discovery using ContentRouting.\n// Namespaces are translated to Cids using the SHA256 hash.\ntype RoutingDiscovery struct {\n\trouting.ContentRouting\n}\n\nfunc NewRoutingDiscovery(router routing.ContentRouting) *RoutingDiscovery {\n\treturn &RoutingDiscovery{router}\n}\n\nfunc (d *RoutingDiscovery) Advertise(ctx context.Context, ns string, opts ...discovery.Option) (time.Duration, error) {\n\tvar options discovery.Options\n\terr := options.Apply(opts...)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tttl := options.Ttl\n\tif ttl == 0 || ttl > 3*time.Hour {\n\t\t// the DHT provider record validity is 24hrs, but it is recommended to republish at least every 6hrs\n\t\t// we go one step further and republish every 3hrs\n\t\tttl = 3 * time.Hour\n\t}\n\n\tcid, err := nsToCid(ns)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\t// this context requires a timeout; it determines how long the DHT looks for\n\t// closest peers to the key/CID before it goes on to provide the record to them.\n\t// Not setting a timeout here will make the DHT wander forever.\n\tpctx, cancel := context.WithTimeout(ctx, 60*time.Second)\n\tdefer cancel()\n\n\terr = d.Provide(pctx, cid, true)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn ttl, nil\n}\n\nfunc (d *RoutingDiscovery) FindPeers(ctx context.Context, ns string, opts ...discovery.Option) (<-chan peer.AddrInfo, error) {\n\toptions := discovery.Options{\n\t\tLimit: 100, // default limit if not specified in options\n\t}\n\terr := options.Apply(opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcid, err := nsToCid(ns)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn d.FindProvidersAsync(ctx, cid, options.Limit), nil\n}\n\nfunc nsToCid(ns string) (cid.Cid, error) {\n\th, err := mh.Sum([]byte(ns), mh.SHA2_256, -1)\n\tif err != nil {\n\t\treturn cid.Undef, err\n\t}\n\n\treturn cid.NewCidV1(cid.Raw, h), nil\n}\n\nfunc NewDiscoveryRouting(disc discovery.Discovery, opts ...discovery.Option) *DiscoveryRouting {\n\treturn &DiscoveryRouting{disc, opts}\n}\n\ntype DiscoveryRouting struct {\n\tdiscovery.Discovery\n\topts []discovery.Option\n}\n\nfunc (r *DiscoveryRouting) Provide(ctx context.Context, c cid.Cid, bcast bool) error {\n\tif !bcast {\n\t\treturn nil\n\t}\n\n\t_, err := r.Advertise(ctx, cidToNs(c), r.opts...)\n\treturn err\n}\n\nfunc (r *DiscoveryRouting) FindProvidersAsync(ctx context.Context, c cid.Cid, limit int) <-chan peer.AddrInfo {\n\tch, _ := r.FindPeers(ctx, cidToNs(c), append([]discovery.Option{discovery.Limit(limit)}, r.opts...)...)\n\treturn ch\n}\n\nfunc cidToNs(c cid.Cid) string {\n\treturn \"/provider/\" + c.String()\n}\n"
  },
  {
    "path": "p2p/discovery/routing/routing_test.go",
    "content": "package routing\n\nimport (\n\t\"context\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/benbjohnson/clock\"\n\t\"github.com/libp2p/go-libp2p/p2p/discovery/mocks\"\n\t\"github.com/libp2p/go-libp2p/p2p/discovery/util\"\n\tbhost \"github.com/libp2p/go-libp2p/p2p/host/blank\"\n\tswarmt \"github.com/libp2p/go-libp2p/p2p/net/swarm/testing\"\n\n\t\"github.com/ipfs/go-cid\"\n\t\"github.com/libp2p/go-libp2p/core/discovery\"\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n)\n\ntype mockRoutingTable struct {\n\tmx        sync.Mutex\n\tproviders map[string]map[peer.ID]peer.AddrInfo\n}\n\ntype mockRouting struct {\n\th   host.Host\n\ttab *mockRoutingTable\n}\n\nfunc NewMockRoutingTable() *mockRoutingTable {\n\treturn &mockRoutingTable{providers: make(map[string]map[peer.ID]peer.AddrInfo)}\n}\n\nfunc NewMockRouting(h host.Host, tab *mockRoutingTable) *mockRouting {\n\treturn &mockRouting{h: h, tab: tab}\n}\n\nfunc (m *mockRouting) Provide(_ context.Context, cid cid.Cid, _ bool) error {\n\tm.tab.mx.Lock()\n\tdefer m.tab.mx.Unlock()\n\n\tpmap, ok := m.tab.providers[cid.String()]\n\tif !ok {\n\t\tpmap = make(map[peer.ID]peer.AddrInfo)\n\t\tm.tab.providers[cid.String()] = pmap\n\t}\n\n\tpmap[m.h.ID()] = peer.AddrInfo{ID: m.h.ID(), Addrs: m.h.Addrs()}\n\n\treturn nil\n}\n\nfunc (m *mockRouting) FindProvidersAsync(ctx context.Context, cid cid.Cid, _ int) <-chan peer.AddrInfo {\n\tch := make(chan peer.AddrInfo)\n\tgo func() {\n\t\tdefer close(ch)\n\t\tm.tab.mx.Lock()\n\t\tdefer m.tab.mx.Unlock()\n\n\t\tpmap, ok := m.tab.providers[cid.String()]\n\t\tif !ok {\n\t\t\treturn\n\t\t}\n\n\t\tfor _, pi := range pmap {\n\t\t\tselect {\n\t\t\tcase ch <- pi:\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\n\treturn ch\n}\n\nfunc TestRoutingDiscovery(t *testing.T) {\n\tctx := t.Context()\n\n\th1 := bhost.NewBlankHost(swarmt.GenSwarm(t))\n\th2 := bhost.NewBlankHost(swarmt.GenSwarm(t))\n\n\tmtab := NewMockRoutingTable()\n\tmr1 := NewMockRouting(h1, mtab)\n\tmr2 := NewMockRouting(h2, mtab)\n\n\td1 := NewRoutingDiscovery(mr1)\n\td2 := NewRoutingDiscovery(mr2)\n\n\t_, err := d1.Advertise(ctx, \"/test\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tpis, err := util.FindPeers(ctx, d2, \"/test\", discovery.Limit(20))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif len(pis) != 1 {\n\t\tt.Fatalf(\"Expected 1 peer, got %d\", len(pis))\n\t}\n\n\tpi := pis[0]\n\tif pi.ID != h1.ID() {\n\t\tt.Fatalf(\"Unexpected peer: %s\", pi.ID)\n\t}\n}\n\nfunc TestDiscoveryRouting(t *testing.T) {\n\tctx := t.Context()\n\n\th1 := bhost.NewBlankHost(swarmt.GenSwarm(t))\n\th2 := bhost.NewBlankHost(swarmt.GenSwarm(t))\n\n\tclock := clock.NewMock()\n\tdserver := mocks.NewDiscoveryServer(clock)\n\td1 := mocks.NewDiscoveryClient(h1, dserver)\n\td2 := mocks.NewDiscoveryClient(h2, dserver)\n\n\tr1 := NewDiscoveryRouting(d1, discovery.TTL(time.Hour))\n\tr2 := NewDiscoveryRouting(d2, discovery.TTL(time.Hour))\n\n\tc, err := nsToCid(\"/test\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif err := r1.Provide(ctx, c, true); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tpch := r2.FindProvidersAsync(ctx, c, 20)\n\n\tallAIs := make([]peer.AddrInfo, 0, len(pch))\n\tfor ai := range pch {\n\t\tallAIs = append(allAIs, ai)\n\t}\n\n\tif len(allAIs) != 1 {\n\t\tt.Fatalf(\"Expected 1 peer, got %d\", len(allAIs))\n\t}\n\n\tai := allAIs[0]\n\tif ai.ID != h1.ID() {\n\t\tt.Fatalf(\"Unexpected peer: %s\", ai.ID)\n\t}\n}\n"
  },
  {
    "path": "p2p/discovery/util/util.go",
    "content": "package util\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/discovery\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n)\n\nvar log = logging.Logger(\"discovery-util\")\n\n// FindPeers is a utility function that synchronously collects peers from a Discoverer.\nfunc FindPeers(ctx context.Context, d discovery.Discoverer, ns string, opts ...discovery.Option) ([]peer.AddrInfo, error) {\n\n\tch, err := d.FindPeers(ctx, ns, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tres := make([]peer.AddrInfo, 0, len(ch))\n\tfor pi := range ch {\n\t\tres = append(res, pi)\n\t}\n\n\treturn res, nil\n}\n\n// Advertise is a utility function that persistently advertises a service through an Advertiser.\nfunc Advertise(ctx context.Context, a discovery.Advertiser, ns string, opts ...discovery.Option) {\n\tgo func() {\n\t\tfor {\n\t\t\tttl, err := a.Advertise(ctx, ns, opts...)\n\t\t\tif err != nil {\n\t\t\t\tlog.Debug(\"Error advertising\", \"namespace\", ns, \"err\", err)\n\t\t\t\tif ctx.Err() != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tselect {\n\t\t\t\tcase <-time.After(2 * time.Minute):\n\t\t\t\t\tcontinue\n\t\t\t\tcase <-ctx.Done():\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\twait := 7 * ttl / 8\n\t\t\tselect {\n\t\t\tcase <-time.After(wait):\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n}\n"
  },
  {
    "path": "p2p/host/autonat/autonat.go",
    "content": "package autonat\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"slices\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/event\"\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/eventbus\"\n\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\nvar log = logging.Logger(\"autonat\")\n\nconst maxConfidence = 3\n\n// AmbientAutoNAT is the implementation of ambient NAT autodiscovery\ntype AmbientAutoNAT struct {\n\thost host.Host\n\n\t*config\n\n\tctx               context.Context\n\tctxCancel         context.CancelFunc // is closed when Close is called\n\tbackgroundRunning chan struct{}      // is closed when the background go routine exits\n\n\tinboundConn   chan network.Conn\n\tdialResponses chan error\n\t// Used when testing the autonat service\n\tobservations chan network.Reachability\n\t// status is an autoNATResult reflecting current status.\n\tstatus atomic.Pointer[network.Reachability]\n\t// Reflects the confidence on of the NATStatus being private, as a single\n\t// dialback may fail for reasons unrelated to NAT.\n\t// If it is <3, then multiple autoNAT peers may be contacted for dialback\n\t// If only a single autoNAT peer is known, then the confidence increases\n\t// for each failure until it reaches 3.\n\tconfidence    int\n\tlastInbound   time.Time\n\tlastProbe     time.Time\n\trecentProbes  map[peer.ID]time.Time\n\tpendingProbes int\n\tourAddrs      map[string]struct{}\n\n\tservice *autoNATService\n\n\temitReachabilityChanged event.Emitter\n\tsubscriber              event.Subscription\n}\n\n// StaticAutoNAT is a simple AutoNAT implementation when a single NAT status is desired.\ntype StaticAutoNAT struct {\n\thost         host.Host\n\treachability network.Reachability\n\tservice      *autoNATService\n}\n\n// New creates a new NAT autodiscovery system attached to a host\nfunc New(h host.Host, options ...Option) (AutoNAT, error) {\n\tvar err error\n\tconf := new(config)\n\tconf.host = h\n\tconf.dialPolicy.host = h\n\n\tif err = defaults(conf); err != nil {\n\t\treturn nil, err\n\t}\n\tif conf.addressFunc == nil {\n\t\tif aa, ok := h.(interface{ AllAddrs() []ma.Multiaddr }); ok {\n\t\t\tconf.addressFunc = aa.AllAddrs\n\t\t} else {\n\t\t\tconf.addressFunc = h.Addrs\n\t\t}\n\t}\n\n\tfor _, o := range options {\n\t\tif err = o(conf); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\temitReachabilityChanged, _ := h.EventBus().Emitter(new(event.EvtLocalReachabilityChanged), eventbus.Stateful)\n\n\tvar service *autoNATService\n\tif (!conf.forceReachability || conf.reachability == network.ReachabilityPublic) && conf.dialer != nil {\n\t\tservice, err = newAutoNATService(conf)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tservice.Enable()\n\t}\n\n\tif conf.forceReachability {\n\t\temitReachabilityChanged.Emit(event.EvtLocalReachabilityChanged{Reachability: conf.reachability})\n\n\t\treturn &StaticAutoNAT{\n\t\t\thost:         h,\n\t\t\treachability: conf.reachability,\n\t\t\tservice:      service,\n\t\t}, nil\n\t}\n\n\tctx, cancel := context.WithCancel(context.Background())\n\tas := &AmbientAutoNAT{\n\t\tctx:               ctx,\n\t\tctxCancel:         cancel,\n\t\tbackgroundRunning: make(chan struct{}),\n\t\thost:              h,\n\t\tconfig:            conf,\n\t\tinboundConn:       make(chan network.Conn, 5),\n\t\tdialResponses:     make(chan error, 1),\n\t\tobservations:      make(chan network.Reachability, 1),\n\n\t\temitReachabilityChanged: emitReachabilityChanged,\n\t\tservice:                 service,\n\t\trecentProbes:            make(map[peer.ID]time.Time),\n\t\tourAddrs:                make(map[string]struct{}),\n\t}\n\treachability := network.ReachabilityUnknown\n\tas.status.Store(&reachability)\n\n\tsubscriber, err := as.host.EventBus().Subscribe(\n\t\t[]any{new(event.EvtLocalAddressesUpdated), new(event.EvtPeerIdentificationCompleted)},\n\t\teventbus.Name(\"autonat\"),\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tas.subscriber = subscriber\n\n\tgo as.background()\n\n\treturn as, nil\n}\n\n// Status returns the AutoNAT observed reachability status.\nfunc (as *AmbientAutoNAT) Status() network.Reachability {\n\ts := as.status.Load()\n\treturn *s\n}\n\nfunc (as *AmbientAutoNAT) emitStatus() {\n\tstatus := *as.status.Load()\n\tas.emitReachabilityChanged.Emit(event.EvtLocalReachabilityChanged{Reachability: status})\n\tif as.metricsTracer != nil {\n\t\tas.metricsTracer.ReachabilityStatus(status)\n\t}\n}\n\nfunc ipInList(candidate ma.Multiaddr, list []ma.Multiaddr) bool {\n\tcandidateIP, _ := manet.ToIP(candidate)\n\tfor _, i := range list {\n\t\tif ip, err := manet.ToIP(i); err == nil && ip.Equal(candidateIP) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (as *AmbientAutoNAT) background() {\n\tdefer close(as.backgroundRunning)\n\t// wait a bit for the node to come online and establish some connections\n\t// before starting autodetection\n\tdelay := as.config.bootDelay\n\n\tsubChan := as.subscriber.Out()\n\tdefer as.subscriber.Close()\n\tdefer as.emitReachabilityChanged.Close()\n\n\t// Fallback timer to update address in case EvtLocalAddressesUpdated is not emitted.\n\t// TODO: The event not emitting properly is a bug. This is a workaround.\n\taddrChangeTicker := time.NewTicker(30 * time.Minute)\n\tdefer addrChangeTicker.Stop()\n\n\ttimer := time.NewTimer(delay)\n\tdefer timer.Stop()\n\ttimerRunning := true\n\tforceProbe := false\n\tfor {\n\t\tselect {\n\t\tcase conn := <-as.inboundConn:\n\t\t\tlocalAddrs := as.host.Addrs()\n\t\t\tif manet.IsPublicAddr(conn.RemoteMultiaddr()) &&\n\t\t\t\t!ipInList(conn.RemoteMultiaddr(), localAddrs) {\n\t\t\t\tas.lastInbound = time.Now()\n\t\t\t}\n\t\tcase <-addrChangeTicker.C:\n\t\t\t// schedule a new probe if addresses have changed\n\t\tcase e := <-subChan:\n\t\t\tswitch e := e.(type) {\n\t\t\tcase event.EvtPeerIdentificationCompleted:\n\t\t\t\tif proto, err := as.host.Peerstore().SupportsProtocols(e.Peer, AutoNATProto); err == nil && len(proto) > 0 {\n\t\t\t\t\tforceProbe = true\n\t\t\t\t}\n\t\t\tcase event.EvtLocalAddressesUpdated:\n\t\t\t\t// schedule a new probe if addresses have changed\n\t\t\tdefault:\n\t\t\t\tlog.Error(\"unknown event type\", \"event_type\", fmt.Sprintf(\"%T\", e))\n\t\t\t}\n\t\tcase obs := <-as.observations:\n\t\t\tas.recordObservation(obs)\n\t\t\tcontinue\n\t\tcase err, ok := <-as.dialResponses:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tas.pendingProbes--\n\t\t\tif IsDialRefused(err) {\n\t\t\t\tforceProbe = true\n\t\t\t} else {\n\t\t\t\tas.handleDialResponse(err)\n\t\t\t}\n\t\tcase <-timer.C:\n\t\t\ttimerRunning = false\n\t\t\tforceProbe = false\n\t\t\t// Update the last probe time. We use it to ensure\n\t\t\t// that we don't spam the peerstore.\n\t\t\tas.lastProbe = time.Now()\n\t\t\tpeer := as.getPeerToProbe()\n\t\t\tas.tryProbe(peer)\n\t\tcase <-as.ctx.Done():\n\t\t\treturn\n\t\t}\n\t\t// On address update, reduce confidence from maximum so that we schedule\n\t\t// the next probe sooner\n\t\thasNewAddr := as.checkAddrs()\n\t\tif hasNewAddr && as.confidence == maxConfidence {\n\t\t\tas.confidence--\n\t\t}\n\n\t\tif timerRunning && !timer.Stop() {\n\t\t\t<-timer.C\n\t\t}\n\t\ttimer.Reset(as.scheduleProbe(forceProbe))\n\t\ttimerRunning = true\n\t}\n}\n\nfunc (as *AmbientAutoNAT) checkAddrs() (hasNewAddr bool) {\n\tcurrentAddrs := as.addressFunc()\n\thasNewAddr = slices.ContainsFunc(currentAddrs, func(a ma.Multiaddr) bool {\n\t\t_, ok := as.ourAddrs[string(a.Bytes())]\n\t\treturn !ok\n\t})\n\tclear(as.ourAddrs)\n\tfor _, a := range currentAddrs {\n\t\tif !manet.IsPublicAddr(a) {\n\t\t\tcontinue\n\t\t}\n\t\tas.ourAddrs[string(a.Bytes())] = struct{}{}\n\t}\n\treturn hasNewAddr\n}\n\n// scheduleProbe calculates when the next probe should be scheduled for.\nfunc (as *AmbientAutoNAT) scheduleProbe(forceProbe bool) time.Duration {\n\tnow := time.Now()\n\tcurrentStatus := *as.status.Load()\n\tnextProbeAfter := as.config.refreshInterval\n\treceivedInbound := as.lastInbound.After(as.lastProbe)\n\tswitch {\n\tcase forceProbe && currentStatus == network.ReachabilityUnknown:\n\t\t// retry very quicky if forceProbe is true *and* we don't know our reachability\n\t\t// limit all peers fetch from peerstore to 1 per second.\n\t\tnextProbeAfter = 2 * time.Second\n\tcase currentStatus == network.ReachabilityUnknown,\n\t\tas.confidence < maxConfidence,\n\t\tcurrentStatus != network.ReachabilityPublic && receivedInbound:\n\t\t// Retry quickly in case:\n\t\t// 1. Our reachability is Unknown\n\t\t// 2. We don't have enough confidence in our reachability.\n\t\t// 3. We're private but we received an inbound connection.\n\t\tnextProbeAfter = as.config.retryInterval\n\tcase currentStatus == network.ReachabilityPublic && receivedInbound:\n\t\t// We are public and we received an inbound connection recently,\n\t\t// wait a little longer\n\t\tnextProbeAfter *= 2\n\t\tnextProbeAfter = min(nextProbeAfter, maxRefreshInterval)\n\t}\n\tnextProbeTime := as.lastProbe.Add(nextProbeAfter)\n\tif nextProbeTime.Before(now) {\n\t\tnextProbeTime = now\n\t}\n\tif as.metricsTracer != nil {\n\t\tas.metricsTracer.NextProbeTime(nextProbeTime)\n\t}\n\n\treturn nextProbeTime.Sub(now)\n}\n\n// handleDialResponse updates the current status based on dial response.\nfunc (as *AmbientAutoNAT) handleDialResponse(dialErr error) {\n\tvar observation network.Reachability\n\tswitch {\n\tcase dialErr == nil:\n\t\tobservation = network.ReachabilityPublic\n\tcase IsDialError(dialErr):\n\t\tobservation = network.ReachabilityPrivate\n\tdefault:\n\t\tobservation = network.ReachabilityUnknown\n\t}\n\n\tas.recordObservation(observation)\n}\n\n// recordObservation updates NAT status and confidence\nfunc (as *AmbientAutoNAT) recordObservation(observation network.Reachability) {\n\n\tcurrentStatus := *as.status.Load()\n\n\tif observation == network.ReachabilityPublic {\n\t\tchanged := false\n\t\tif currentStatus != network.ReachabilityPublic {\n\t\t\t// Aggressively switch to public from other states ignoring confidence\n\t\t\tlog.Debug(\"NAT status is public\")\n\n\t\t\t// we are flipping our NATStatus, so confidence drops to 0\n\t\t\tas.confidence = 0\n\t\t\tif as.service != nil {\n\t\t\t\tas.service.Enable()\n\t\t\t}\n\t\t\tchanged = true\n\t\t} else if as.confidence < maxConfidence {\n\t\t\tas.confidence++\n\t\t}\n\t\tas.status.Store(&observation)\n\t\tif changed {\n\t\t\tas.emitStatus()\n\t\t}\n\t} else if observation == network.ReachabilityPrivate {\n\t\tif currentStatus != network.ReachabilityPrivate {\n\t\t\tif as.confidence > 0 {\n\t\t\t\tas.confidence--\n\t\t\t} else {\n\t\t\t\tlog.Debug(\"NAT status is private\")\n\n\t\t\t\t// we are flipping our NATStatus, so confidence drops to 0\n\t\t\t\tas.confidence = 0\n\t\t\t\tas.status.Store(&observation)\n\t\t\t\tif as.service != nil {\n\t\t\t\t\tas.service.Disable()\n\t\t\t\t}\n\t\t\t\tas.emitStatus()\n\t\t\t}\n\t\t} else if as.confidence < maxConfidence {\n\t\t\tas.confidence++\n\t\t\tas.status.Store(&observation)\n\t\t}\n\t} else if as.confidence > 0 {\n\t\t// don't just flip to unknown, reduce confidence first\n\t\tas.confidence--\n\t} else {\n\t\tlog.Debug(\"NAT status is unknown\")\n\t\tas.status.Store(&observation)\n\t\tif currentStatus != network.ReachabilityUnknown {\n\t\t\tif as.service != nil {\n\t\t\t\tas.service.Enable()\n\t\t\t}\n\t\t\tas.emitStatus()\n\t\t}\n\t}\n\tif as.metricsTracer != nil {\n\t\tas.metricsTracer.ReachabilityStatusConfidence(as.confidence)\n\t}\n}\n\nfunc (as *AmbientAutoNAT) tryProbe(p peer.ID) {\n\tif p == \"\" || as.pendingProbes > 5 {\n\t\treturn\n\t}\n\tinfo := as.host.Peerstore().PeerInfo(p)\n\tas.recentProbes[p] = time.Now()\n\tas.pendingProbes++\n\tgo as.probe(&info)\n}\n\nfunc (as *AmbientAutoNAT) probe(pi *peer.AddrInfo) {\n\tcli := NewAutoNATClient(as.host, as.config.addressFunc, as.metricsTracer)\n\tctx, cancel := context.WithTimeout(as.ctx, as.config.requestTimeout)\n\tdefer cancel()\n\n\terr := cli.DialBack(ctx, pi.ID)\n\tlog.Debug(\"Dialback through peer completed\", \"peer\", pi.ID, \"err\", err)\n\n\tselect {\n\tcase as.dialResponses <- err:\n\tcase <-as.ctx.Done():\n\t\treturn\n\t}\n}\n\nfunc (as *AmbientAutoNAT) getPeerToProbe() peer.ID {\n\tpeers := as.host.Network().Peers()\n\tif len(peers) == 0 {\n\t\treturn \"\"\n\t}\n\n\t// clean old probes\n\tfixedNow := time.Now()\n\tfor k, v := range as.recentProbes {\n\t\tif fixedNow.Sub(v) > as.throttlePeerPeriod {\n\t\t\tdelete(as.recentProbes, k)\n\t\t}\n\t}\n\n\t// Shuffle peers\n\tfor n := len(peers); n > 0; n-- {\n\t\trandIndex := rand.Intn(n)\n\t\tpeers[n-1], peers[randIndex] = peers[randIndex], peers[n-1]\n\t}\n\n\tfor _, p := range peers {\n\t\tinfo := as.host.Peerstore().PeerInfo(p)\n\t\t// Exclude peers which don't support the autonat protocol.\n\t\tif proto, err := as.host.Peerstore().SupportsProtocols(p, AutoNATProto); len(proto) == 0 || err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tif as.config.dialPolicy.skipPeer(info.Addrs) {\n\t\t\tcontinue\n\t\t}\n\t\treturn p\n\t}\n\n\treturn \"\"\n}\n\nfunc (as *AmbientAutoNAT) Close() error {\n\tas.ctxCancel()\n\tif as.service != nil {\n\t\treturn as.service.Close()\n\t}\n\t<-as.backgroundRunning\n\treturn nil\n}\n\n// Status returns the AutoNAT observed reachability status.\nfunc (s *StaticAutoNAT) Status() network.Reachability {\n\treturn s.reachability\n}\n\nfunc (s *StaticAutoNAT) Close() error {\n\tif s.service != nil {\n\t\treturn s.service.Close()\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "p2p/host/autonat/autonat_test.go",
    "content": "package autonat\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/event\"\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/autonat/pb\"\n\tbhost \"github.com/libp2p/go-libp2p/p2p/host/blank\"\n\tswarmt \"github.com/libp2p/go-libp2p/p2p/net/swarm/testing\"\n\n\t\"github.com/libp2p/go-msgio/pbio\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\n// these are mock service implementations for testing\nfunc makeAutoNATServicePrivate(t *testing.T) host.Host {\n\th := bhost.NewBlankHost(swarmt.GenSwarm(t))\n\th.SetStreamHandler(AutoNATProto, sayPrivateStreamHandler(t))\n\treturn h\n}\n\nfunc sayPrivateStreamHandler(t *testing.T) network.StreamHandler {\n\treturn func(s network.Stream) {\n\t\tdefer s.Close()\n\t\tr := pbio.NewDelimitedReader(s, network.MessageSizeMax)\n\t\tif err := r.ReadMsg(&pb.Message{}); err != nil {\n\t\t\tt.Error(err)\n\t\t\treturn\n\t\t}\n\t\tw := pbio.NewDelimitedWriter(s)\n\t\tres := pb.Message{\n\t\t\tType:         pb.Message_DIAL_RESPONSE.Enum(),\n\t\t\tDialResponse: newDialResponseError(pb.Message_E_DIAL_ERROR, \"dial failed\"),\n\t\t}\n\t\tw.WriteMsg(&res)\n\t}\n}\n\nfunc makeAutoNATRefuseDialRequest(t *testing.T, done chan struct{}) host.Host {\n\th := bhost.NewBlankHost(swarmt.GenSwarm(t))\n\th.SetStreamHandler(AutoNATProto, sayRefusedStreamHandler(t, done))\n\treturn h\n}\n\nfunc sayRefusedStreamHandler(t *testing.T, done chan struct{}) network.StreamHandler {\n\treturn func(s network.Stream) {\n\t\tdefer s.Close()\n\t\tr := pbio.NewDelimitedReader(s, network.MessageSizeMax)\n\t\tif err := r.ReadMsg(&pb.Message{}); err != nil {\n\t\t\t// ignore error if the test has completed\n\t\t\tselect {\n\t\t\tcase _, ok := <-done:\n\t\t\t\tif !ok {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t}\n\t\t\tt.Error(err)\n\t\t\treturn\n\t\t}\n\t\tw := pbio.NewDelimitedWriter(s)\n\t\tres := pb.Message{\n\t\t\tType:         pb.Message_DIAL_RESPONSE.Enum(),\n\t\t\tDialResponse: newDialResponseError(pb.Message_E_DIAL_REFUSED, \"dial refused\"),\n\t\t}\n\t\tw.WriteMsg(&res)\n\t}\n}\n\nfunc makeAutoNATServicePublic(t *testing.T) host.Host {\n\th := bhost.NewBlankHost(swarmt.GenSwarm(t))\n\th.SetStreamHandler(AutoNATProto, func(s network.Stream) {\n\t\tdefer s.Close()\n\t\tr := pbio.NewDelimitedReader(s, network.MessageSizeMax)\n\t\tif err := r.ReadMsg(&pb.Message{}); err != nil {\n\t\t\tt.Error(err)\n\t\t\treturn\n\t\t}\n\t\tw := pbio.NewDelimitedWriter(s)\n\t\tres := pb.Message{\n\t\t\tType:         pb.Message_DIAL_RESPONSE.Enum(),\n\t\t\tDialResponse: newDialResponseOK(s.Conn().RemoteMultiaddr()),\n\t\t}\n\t\tw.WriteMsg(&res)\n\t})\n\treturn h\n}\n\nfunc makeAutoNAT(t *testing.T, ash host.Host) (host.Host, AutoNAT) {\n\th := bhost.NewBlankHost(swarmt.GenSwarm(t))\n\th.Peerstore().AddAddrs(ash.ID(), ash.Addrs(), time.Minute)\n\th.Peerstore().AddProtocols(ash.ID(), AutoNATProto)\n\ta, _ := New(h, WithSchedule(100*time.Millisecond, time.Second), WithoutStartupDelay())\n\ta.(*AmbientAutoNAT).config.dialPolicy.allowSelfDials = true\n\ta.(*AmbientAutoNAT).config.throttlePeerPeriod = 100 * time.Millisecond\n\treturn h, a\n}\n\nfunc identifyAsServer(server, recip host.Host) {\n\trecip.Peerstore().AddAddrs(server.ID(), server.Addrs(), time.Minute)\n\trecip.Peerstore().AddProtocols(server.ID(), AutoNATProto)\n\n}\n\nfunc connect(t *testing.T, a, b host.Host) {\n\tpinfo := peer.AddrInfo{ID: a.ID(), Addrs: a.Addrs()}\n\terr := b.Connect(context.Background(), pinfo)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc expectEvent(t *testing.T, s event.Subscription, expected network.Reachability, timeout time.Duration) {\n\tt.Helper()\n\tselect {\n\tcase e := <-s.Out():\n\t\tev, ok := e.(event.EvtLocalReachabilityChanged)\n\t\tif !ok || ev.Reachability != expected {\n\t\t\tt.Fatal(\"got wrong event type from the bus\")\n\t\t}\n\n\tcase <-time.After(timeout):\n\t\tt.Fatal(\"failed to get the reachability event from the bus\")\n\t}\n}\n\n// tests\nfunc TestAutoNATPrivate(t *testing.T) {\n\ths := makeAutoNATServicePrivate(t)\n\tdefer hs.Close()\n\thc, an := makeAutoNAT(t, hs)\n\tdefer hc.Close()\n\tdefer an.Close()\n\n\t// subscribe to AutoNat events\n\ts, err := hc.EventBus().Subscribe(&event.EvtLocalReachabilityChanged{})\n\tif err != nil {\n\t\tt.Fatalf(\"failed to subscribe to event EvtLocalReachabilityChanged, err=%s\", err)\n\t}\n\n\tstatus := an.Status()\n\tif status != network.ReachabilityUnknown {\n\t\tt.Fatalf(\"unexpected NAT status: %d\", status)\n\t}\n\n\tconnect(t, hs, hc)\n\texpectEvent(t, s, network.ReachabilityPrivate, 3*time.Second)\n}\n\nfunc TestAutoNATPublic(t *testing.T) {\n\ths := makeAutoNATServicePublic(t)\n\tdefer hs.Close()\n\thc, an := makeAutoNAT(t, hs)\n\tdefer hc.Close()\n\tdefer an.Close()\n\n\t// subscribe to AutoNat events\n\ts, err := hc.EventBus().Subscribe(&event.EvtLocalReachabilityChanged{})\n\tif err != nil {\n\t\tt.Fatalf(\"failed to subscribe to event EvtLocalReachabilityChanged, err=%s\", err)\n\t}\n\n\tstatus := an.Status()\n\tif status != network.ReachabilityUnknown {\n\t\tt.Fatalf(\"unexpected NAT status: %d\", status)\n\t}\n\n\tconnect(t, hs, hc)\n\texpectEvent(t, s, network.ReachabilityPublic, 3*time.Second)\n}\n\nfunc TestAutoNATPublictoPrivate(t *testing.T) {\n\ths := makeAutoNATServicePublic(t)\n\tdefer hs.Close()\n\thc, an := makeAutoNAT(t, hs)\n\tdefer hc.Close()\n\tdefer an.Close()\n\n\t// subscribe to AutoNat events\n\ts, err := hc.EventBus().Subscribe(&event.EvtLocalReachabilityChanged{})\n\tif err != nil {\n\t\tt.Fatalf(\"failed to subscribe to event EvtLocalReachabilityChanged, err=%s\", err)\n\t}\n\n\tif status := an.Status(); status != network.ReachabilityUnknown {\n\t\tt.Fatalf(\"unexpected NAT status: %d\", status)\n\t}\n\n\tconnect(t, hs, hc)\n\texpectEvent(t, s, network.ReachabilityPublic, 3*time.Second)\n\n\ths.SetStreamHandler(AutoNATProto, sayPrivateStreamHandler(t))\n\thps := makeAutoNATServicePrivate(t)\n\tconnect(t, hps, hc)\n\tidentifyAsServer(hps, hc)\n\n\texpectEvent(t, s, network.ReachabilityPrivate, 3*time.Second)\n}\n\nfunc TestAutoNATIncomingEvents(t *testing.T) {\n\ths := makeAutoNATServicePrivate(t)\n\tdefer hs.Close()\n\thc, ani := makeAutoNAT(t, hs)\n\tdefer hc.Close()\n\tdefer ani.Close()\n\tan := ani.(*AmbientAutoNAT)\n\n\tstatus := an.Status()\n\tif status != network.ReachabilityUnknown {\n\t\tt.Fatalf(\"unexpected NAT status: %d\", status)\n\t}\n\n\tconnect(t, hs, hc)\n\n\tem, _ := hc.EventBus().Emitter(&event.EvtPeerIdentificationCompleted{})\n\tem.Emit(event.EvtPeerIdentificationCompleted{Peer: hs.ID()})\n\n\trequire.Eventually(t, func() bool {\n\t\treturn an.Status() != network.ReachabilityUnknown\n\t}, 5*time.Second, 100*time.Millisecond, \"Expected probe due to identification of autonat service\")\n}\n\nfunc TestAutoNATDialRefused(t *testing.T) {\n\ths := makeAutoNATServicePublic(t)\n\tdefer hs.Close()\n\thc, an := makeAutoNAT(t, hs)\n\tdefer hc.Close()\n\tdefer an.Close()\n\n\t// subscribe to AutoNat events\n\ts, err := hc.EventBus().Subscribe(&event.EvtLocalReachabilityChanged{})\n\tif err != nil {\n\t\tt.Fatalf(\"failed to subscribe to event EvtLocalReachabilityChanged, err=%s\", err)\n\t}\n\n\tif status := an.Status(); status != network.ReachabilityUnknown {\n\t\tt.Fatalf(\"unexpected NAT status: %d\", status)\n\t}\n\n\tconnect(t, hs, hc)\n\texpectEvent(t, s, network.ReachabilityPublic, 10*time.Second)\n\n\tdone := make(chan struct{})\n\ths.SetStreamHandler(AutoNATProto, sayRefusedStreamHandler(t, done))\n\thps := makeAutoNATRefuseDialRequest(t, done)\n\tconnect(t, hps, hc)\n\tidentifyAsServer(hps, hc)\n\n\trequire.Never(t, func() bool {\n\t\treturn an.Status() != network.ReachabilityPublic\n\t}, 3*time.Second, 1*time.Second, \"Expected probe to not change reachability from public\")\n\tclose(done)\n}\n\nfunc recordObservation(an *AmbientAutoNAT, status network.Reachability) {\n\tan.observations <- status\n}\n\nfunc TestAutoNATObservationRecording(t *testing.T) {\n\ths := makeAutoNATServicePublic(t)\n\tdefer hs.Close()\n\thc, ani := makeAutoNAT(t, hs)\n\tdefer hc.Close()\n\tdefer ani.Close()\n\tan := ani.(*AmbientAutoNAT)\n\n\ts, err := hc.EventBus().Subscribe(&event.EvtLocalReachabilityChanged{})\n\tif err != nil {\n\t\tt.Fatalf(\"failed to subscribe to event EvtLocalRoutabilityPublic, err=%s\", err)\n\t}\n\n\texpectStatus := func(expected network.Reachability, msg string, args ...any) {\n\t\trequire.EventuallyWithTf(t, func(collect *assert.CollectT) {\n\t\t\tassert.Equal(collect, expected, an.Status())\n\t\t}, 2*time.Second, 100*time.Millisecond, msg, args...)\n\t}\n\n\trecordObservation(an, network.ReachabilityPublic)\n\texpectStatus(network.ReachabilityPublic, \"failed to transition to public.\")\n\texpectEvent(t, s, network.ReachabilityPublic, 3*time.Second)\n\n\t// a single recording should have confidence still at 0, and transition to private quickly.\n\trecordObservation(an, network.ReachabilityPrivate)\n\texpectStatus(network.ReachabilityPrivate, \"failed to transition to private.\")\n\n\texpectEvent(t, s, network.ReachabilityPrivate, 3*time.Second)\n\n\t// stronger public confidence should be harder to undo.\n\trecordObservation(an, network.ReachabilityPublic)\n\trecordObservation(an, network.ReachabilityPublic)\n\texpectStatus(network.ReachabilityPublic, \"failed to transition to public.\")\n\texpectEvent(t, s, network.ReachabilityPublic, 3*time.Second)\n\n\trecordObservation(an, network.ReachabilityPrivate)\n\texpectStatus(network.ReachabilityPublic, \"too-extreme private transition.\")\n\n\t// Don't emit events if reachability hasn't changed\n\trecordObservation(an, network.ReachabilityPublic)\n\texpectStatus(network.ReachabilityPublic, \"reachability should stay public\")\n\tselect {\n\tcase <-s.Out():\n\t\tt.Fatal(\"received event without state transition\")\n\tcase <-time.After(300 * time.Millisecond):\n\t}\n}\n\nfunc TestStaticNat(t *testing.T) {\n\t_, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\n\th := bhost.NewBlankHost(swarmt.GenSwarm(t))\n\tdefer h.Close()\n\ts, _ := h.EventBus().Subscribe(&event.EvtLocalReachabilityChanged{})\n\n\tnat, err := New(h, WithReachability(network.ReachabilityPrivate))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif nat.Status() != network.ReachabilityPrivate {\n\t\tt.Fatalf(\"should be private\")\n\t}\n\texpectEvent(t, s, network.ReachabilityPrivate, 3*time.Second)\n}\n"
  },
  {
    "path": "p2p/host/autonat/client.go",
    "content": "package autonat\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/autonat/pb\"\n\n\t\"github.com/libp2p/go-msgio/pbio\"\n)\n\n// NewAutoNATClient creates a fresh instance of an AutoNATClient\n// If addrFunc is nil, h.Addrs will be used\nfunc NewAutoNATClient(h host.Host, addrFunc AddrFunc, mt MetricsTracer) Client {\n\tif addrFunc == nil {\n\t\taddrFunc = h.Addrs\n\t}\n\treturn &client{h: h, addrFunc: addrFunc, mt: mt}\n}\n\ntype client struct {\n\th        host.Host\n\taddrFunc AddrFunc\n\tmt       MetricsTracer\n}\n\n// DialBack asks peer p to dial us back on all addresses returned by the addrFunc.\n// It blocks until we've received a response from the peer.\n//\n// Note: A returned error Message_E_DIAL_ERROR does not imply that the server\n// actually performed a dial attempt. Servers that run a version < v0.20.0 also\n// return Message_E_DIAL_ERROR if the dial was skipped due to the dialPolicy.\nfunc (c *client) DialBack(ctx context.Context, p peer.ID) error {\n\ts, err := c.h.NewStream(ctx, p, AutoNATProto)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif err := s.Scope().SetService(ServiceName); err != nil {\n\t\tlog.Debug(\"error attaching stream to autonat service\", \"err\", err)\n\t\ts.Reset()\n\t\treturn err\n\t}\n\n\tif err := s.Scope().ReserveMemory(maxMsgSize, network.ReservationPriorityAlways); err != nil {\n\t\tlog.Debug(\"error reserving memory for autonat stream\", \"err\", err)\n\t\ts.Reset()\n\t\treturn err\n\t}\n\tdefer s.Scope().ReleaseMemory(maxMsgSize)\n\n\tdeadline := time.Now().Add(streamTimeout)\n\tif ctxDeadline, ok := ctx.Deadline(); ok {\n\t\tif ctxDeadline.Before(deadline) {\n\t\t\tdeadline = ctxDeadline\n\t\t}\n\t}\n\n\ts.SetDeadline(deadline)\n\t// Might as well just reset the stream. Once we get to this point, we\n\t// don't care about being nice.\n\tdefer s.Close()\n\n\tr := pbio.NewDelimitedReader(s, maxMsgSize)\n\tw := pbio.NewDelimitedWriter(s)\n\n\treq := newDialMessage(peer.AddrInfo{ID: c.h.ID(), Addrs: c.addrFunc()})\n\tif err := w.WriteMsg(req); err != nil {\n\t\ts.Reset()\n\t\treturn err\n\t}\n\n\tvar res pb.Message\n\tif err := r.ReadMsg(&res); err != nil {\n\t\ts.Reset()\n\t\treturn err\n\t}\n\tif res.GetType() != pb.Message_DIAL_RESPONSE {\n\t\ts.Reset()\n\t\treturn fmt.Errorf(\"unexpected response: %s\", res.GetType().String())\n\t}\n\n\tstatus := res.GetDialResponse().GetStatus()\n\tif c.mt != nil {\n\t\tc.mt.ReceivedDialResponse(status)\n\t}\n\tswitch status {\n\tcase pb.Message_OK:\n\t\treturn nil\n\tdefault:\n\t\treturn Error{Status: status, Text: res.GetDialResponse().GetStatusText()}\n\t}\n}\n\n// Error wraps errors signalled by AutoNAT services\ntype Error struct {\n\tStatus pb.Message_ResponseStatus\n\tText   string\n}\n\nfunc (e Error) Error() string {\n\treturn fmt.Sprintf(\"AutoNAT error: %s (%s)\", e.Text, e.Status.String())\n}\n\n// IsDialError returns true if the error was due to a dial back failure\nfunc (e Error) IsDialError() bool {\n\treturn e.Status == pb.Message_E_DIAL_ERROR\n}\n\n// IsDialRefused returns true if the error was due to a refusal to dial back\nfunc (e Error) IsDialRefused() bool {\n\treturn e.Status == pb.Message_E_DIAL_REFUSED\n}\n\n// IsDialError returns true if the AutoNAT peer signalled an error dialing back\nfunc IsDialError(e error) bool {\n\tae, ok := e.(Error)\n\treturn ok && ae.IsDialError()\n}\n\n// IsDialRefused returns true if the AutoNAT peer signalled refusal to dial back\nfunc IsDialRefused(e error) bool {\n\tae, ok := e.(Error)\n\treturn ok && ae.IsDialRefused()\n}\n"
  },
  {
    "path": "p2p/host/autonat/dialpolicy.go",
    "content": "package autonat\n\nimport (\n\t\"net\"\n\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\ntype dialPolicy struct {\n\tallowSelfDials bool\n\thost           host.Host\n}\n\n// skipDial indicates that a multiaddress isn't worth attempted dialing.\n// The same logic is used when the autonat client is considering if\n// a remote peer is worth using as a server, and when the server is\n// considering if a requested client is worth dialing back.\nfunc (d *dialPolicy) skipDial(addr ma.Multiaddr) bool {\n\t// skip relay addresses\n\t_, err := addr.ValueForProtocol(ma.P_CIRCUIT)\n\tif err == nil {\n\t\treturn true\n\t}\n\n\tif d.allowSelfDials {\n\t\treturn false\n\t}\n\n\t// skip private network (unroutable) addresses\n\tif !manet.IsPublicAddr(addr) {\n\t\treturn true\n\t}\n\tcandidateIP, err := manet.ToIP(addr)\n\tif err != nil {\n\t\treturn true\n\t}\n\n\t// Skip dialing addresses we believe are the local node's\n\tfor _, localAddr := range d.host.Addrs() {\n\t\tlocalIP, err := manet.ToIP(localAddr)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tif localIP.Equal(candidateIP) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n// skipPeer indicates that the collection of multiaddresses representing a peer\n// isn't worth attempted dialing. If one of the addresses matches an address\n// we believe is ours, we exclude the peer, even if there are other valid\n// public addresses in the list.\nfunc (d *dialPolicy) skipPeer(addrs []ma.Multiaddr) bool {\n\tlocalAddrs := d.host.Addrs()\n\tlocalHosts := make([]net.IP, 0)\n\tfor _, lAddr := range localAddrs {\n\t\tif _, err := lAddr.ValueForProtocol(ma.P_CIRCUIT); err != nil && manet.IsPublicAddr(lAddr) {\n\t\t\tlIP, err := manet.ToIP(lAddr)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tlocalHosts = append(localHosts, lIP)\n\t\t}\n\t}\n\n\t// if a public IP of the peer is one of ours: skip the peer.\n\tgoodPublic := false\n\tfor _, addr := range addrs {\n\t\tif _, err := addr.ValueForProtocol(ma.P_CIRCUIT); err != nil && manet.IsPublicAddr(addr) {\n\t\t\taIP, err := manet.ToIP(addr)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfor _, lIP := range localHosts {\n\t\t\t\tif lIP.Equal(aIP) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t}\n\t\t\tgoodPublic = true\n\t\t}\n\t}\n\n\tif d.allowSelfDials {\n\t\treturn false\n\t}\n\n\treturn !goodPublic\n}\n"
  },
  {
    "path": "p2p/host/autonat/dialpolicy_test.go",
    "content": "package autonat\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net\"\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/transport\"\n\tblankhost \"github.com/libp2p/go-libp2p/p2p/host/blank\"\n\tswarmt \"github.com/libp2p/go-libp2p/p2p/net/swarm/testing\"\n\n\t\"github.com/multiformats/go-multiaddr\"\n)\n\nfunc makeMA(a string) multiaddr.Multiaddr {\n\taddr, err := multiaddr.NewMultiaddr(a)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn addr\n}\n\ntype mockT struct {\n\tctx    context.Context\n\tcancel context.CancelFunc\n\taddr   multiaddr.Multiaddr\n}\n\nfunc (m *mockT) Dial(_ context.Context, _ multiaddr.Multiaddr, _ peer.ID) (transport.CapableConn, error) {\n\treturn nil, nil\n}\nfunc (m *mockT) CanDial(_ multiaddr.Multiaddr) bool { return true }\nfunc (m *mockT) Listen(_ multiaddr.Multiaddr) (transport.Listener, error) {\n\treturn &mockL{m.ctx, m.cancel, m.addr}, nil\n}\nfunc (m *mockT) Protocols() []int { return []int{multiaddr.P_IP4} }\nfunc (m *mockT) Proxy() bool      { return false }\nfunc (m *mockT) String() string   { return \"mock-tcp-ipv4\" }\n\ntype mockL struct {\n\tctx    context.Context\n\tcancel context.CancelFunc\n\taddr   multiaddr.Multiaddr\n}\n\nfunc (l *mockL) Accept() (transport.CapableConn, error) {\n\t<-l.ctx.Done()\n\treturn nil, errors.New(\"expected in mocked test\")\n}\nfunc (l *mockL) Close() error                   { l.cancel(); return nil }\nfunc (l *mockL) Addr() net.Addr                 { return nil }\nfunc (l *mockL) Multiaddr() multiaddr.Multiaddr { return l.addr }\n\nfunc TestSkipDial(t *testing.T) {\n\tctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\n\ts := swarmt.GenSwarm(t)\n\tdefer s.Close()\n\td := dialPolicy{host: blankhost.NewBlankHost(s)}\n\tif d.skipDial(makeMA(\"/ip4/8.8.8.8\")) != false {\n\t\tt.Fatal(\"failed dialing a valid public addr\")\n\t}\n\n\tif d.skipDial(makeMA(\"/ip6/2607:f8b0:400a::1\")) != false {\n\t\tt.Fatal(\"failed dialing a valid public addr\")\n\t}\n\n\tif d.skipDial(makeMA(\"/ip4/192.168.0.1\")) != true {\n\t\tt.Fatal(\"didn't skip dialing an internal addr\")\n\t}\n\n\ts.AddTransport(&mockT{ctx, cancel, makeMA(\"/ip4/8.8.8.8\")})\n\terr := s.AddListenAddr(makeMA(\"/ip4/8.8.8.8\"))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif d.skipDial(makeMA(\"/ip4/8.8.8.8\")) != true {\n\t\tt.Fatal(\"failed dialing a valid host address\")\n\t}\n}\n\nfunc TestSkipPeer(t *testing.T) {\n\tctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\n\ts := swarmt.GenSwarm(t)\n\tdefer s.Close()\n\n\td := dialPolicy{host: blankhost.NewBlankHost(s)}\n\tif d.skipPeer([]multiaddr.Multiaddr{makeMA(\"/ip4/8.8.8.8\")}) != false {\n\t\tt.Fatal(\"failed dialing a valid public addr\")\n\t}\n\tif d.skipPeer([]multiaddr.Multiaddr{makeMA(\"/ip4/8.8.8.8\"), makeMA(\"/ip4/192.168.0.1\")}) != false {\n\t\tt.Fatal(\"failed dialing a valid public addr\")\n\t}\n\tif d.skipPeer([]multiaddr.Multiaddr{makeMA(\"/ip4/192.168.0.1\")}) != true {\n\t\tt.Fatal(\"succeeded with no public addr\")\n\t}\n\n\ts.AddTransport(&mockT{ctx, cancel, makeMA(\"/ip4/8.8.8.8\")})\n\terr := s.AddListenAddr(makeMA(\"/ip4/8.8.8.8\"))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif d.skipPeer([]multiaddr.Multiaddr{makeMA(\"/ip4/8.8.8.8\"), makeMA(\"/ip4/192.168.0.1\")}) != true {\n\t\tt.Fatal(\"succeeded dialing host address\")\n\t}\n\tif d.skipPeer([]multiaddr.Multiaddr{makeMA(\"/ip4/8.8.8.8\"), makeMA(\"/ip4/9.9.9.9\")}) != true {\n\t\tt.Fatal(\"succeeded dialing host address when other public\")\n\t}\n\tif d.skipPeer([]multiaddr.Multiaddr{makeMA(\"/ip4/9.9.9.9\")}) != false {\n\t\tt.Fatal(\"succeeded dialing host address when other public\")\n\t}\n}\n\nfunc TestSkipLocalPeer(t *testing.T) {\n\tctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\n\ts := swarmt.GenSwarm(t)\n\tdefer s.Close()\n\n\td := dialPolicy{host: blankhost.NewBlankHost(s)}\n\ts.AddTransport(&mockT{ctx, cancel, makeMA(\"/ip4/192.168.0.1\")})\n\terr := s.AddListenAddr(makeMA(\"/ip4/192.168.0.1\"))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif d.skipPeer([]multiaddr.Multiaddr{makeMA(\"/ip4/8.8.8.8\")}) != false {\n\t\tt.Fatal(\"failed dialing a valid public addr\")\n\t}\n\tif d.skipPeer([]multiaddr.Multiaddr{makeMA(\"/ip4/8.8.8.8\"), makeMA(\"/ip4/192.168.0.1\")}) != false {\n\t\tt.Fatal(\"failed dialing a valid public addr\")\n\t}\n\tif d.skipPeer([]multiaddr.Multiaddr{makeMA(\"/ip4/192.168.0.1\")}) != true {\n\t\tt.Fatal(\"succeeded with no public addr\")\n\t}\n}\n"
  },
  {
    "path": "p2p/host/autonat/interface.go",
    "content": "package autonat\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\n// AutoNAT is the interface for NAT autodiscovery\ntype AutoNAT interface {\n\t// Status returns the current NAT status\n\tStatus() network.Reachability\n\tio.Closer\n}\n\n// Client is a stateless client interface to AutoNAT peers\ntype Client interface {\n\t// DialBack requests from a peer providing AutoNAT services to test dial back\n\t// and report the address on a successful connection.\n\tDialBack(ctx context.Context, p peer.ID) error\n}\n\n// AddrFunc is a function returning the candidate addresses for the local host.\ntype AddrFunc func() []ma.Multiaddr\n\n// Option is an Autonat option for configuration\ntype Option func(*config) error\n"
  },
  {
    "path": "p2p/host/autonat/metrics.go",
    "content": "package autonat\n\nimport (\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/autonat/pb\"\n\t\"github.com/libp2p/go-libp2p/p2p/metricshelper\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n)\n\nconst metricNamespace = \"libp2p_autonat\"\n\nvar (\n\treachabilityStatus = prometheus.NewGauge(\n\t\tprometheus.GaugeOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"reachability_status\",\n\t\t\tHelp:      \"Current node reachability\",\n\t\t},\n\t)\n\treachabilityStatusConfidence = prometheus.NewGauge(\n\t\tprometheus.GaugeOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"reachability_status_confidence\",\n\t\t\tHelp:      \"Node reachability status confidence\",\n\t\t},\n\t)\n\treceivedDialResponseTotal = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"received_dial_response_total\",\n\t\t\tHelp:      \"Count of dial responses for client\",\n\t\t},\n\t\t[]string{\"response_status\"},\n\t)\n\toutgoingDialResponseTotal = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"outgoing_dial_response_total\",\n\t\t\tHelp:      \"Count of dial responses for server\",\n\t\t},\n\t\t[]string{\"response_status\"},\n\t)\n\toutgoingDialRefusedTotal = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"outgoing_dial_refused_total\",\n\t\t\tHelp:      \"Count of dial requests refused by server\",\n\t\t},\n\t\t[]string{\"refusal_reason\"},\n\t)\n\tnextProbeTimestamp = prometheus.NewGauge(\n\t\tprometheus.GaugeOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"next_probe_timestamp\",\n\t\t\tHelp:      \"Time of next probe\",\n\t\t},\n\t)\n\tcollectors = []prometheus.Collector{\n\t\treachabilityStatus,\n\t\treachabilityStatusConfidence,\n\t\treceivedDialResponseTotal,\n\t\toutgoingDialResponseTotal,\n\t\toutgoingDialRefusedTotal,\n\t\tnextProbeTimestamp,\n\t}\n)\n\ntype MetricsTracer interface {\n\tReachabilityStatus(status network.Reachability)\n\tReachabilityStatusConfidence(confidence int)\n\tReceivedDialResponse(status pb.Message_ResponseStatus)\n\tOutgoingDialResponse(status pb.Message_ResponseStatus)\n\tOutgoingDialRefused(reason string)\n\tNextProbeTime(t time.Time)\n}\n\nfunc getResponseStatus(status pb.Message_ResponseStatus) string {\n\tvar s string\n\tswitch status {\n\tcase pb.Message_OK:\n\t\ts = \"ok\"\n\tcase pb.Message_E_DIAL_ERROR:\n\t\ts = \"dial error\"\n\tcase pb.Message_E_DIAL_REFUSED:\n\t\ts = \"dial refused\"\n\tcase pb.Message_E_BAD_REQUEST:\n\t\ts = \"bad request\"\n\tcase pb.Message_E_INTERNAL_ERROR:\n\t\ts = \"internal error\"\n\tdefault:\n\t\ts = \"unknown\"\n\t}\n\treturn s\n}\n\nconst (\n\trate_limited     = \"rate limited\"\n\tdial_blocked     = \"dial blocked\"\n\tno_valid_address = \"no valid address\"\n)\n\ntype metricsTracer struct{}\n\nvar _ MetricsTracer = &metricsTracer{}\n\ntype metricsTracerSetting struct {\n\treg prometheus.Registerer\n}\n\ntype MetricsTracerOption func(*metricsTracerSetting)\n\nfunc WithRegisterer(reg prometheus.Registerer) MetricsTracerOption {\n\treturn func(s *metricsTracerSetting) {\n\t\tif reg != nil {\n\t\t\ts.reg = reg\n\t\t}\n\t}\n}\n\nfunc NewMetricsTracer(opts ...MetricsTracerOption) MetricsTracer {\n\tsetting := &metricsTracerSetting{reg: prometheus.DefaultRegisterer}\n\tfor _, opt := range opts {\n\t\topt(setting)\n\t}\n\tmetricshelper.RegisterCollectors(setting.reg, collectors...)\n\treturn &metricsTracer{}\n}\n\nfunc (mt *metricsTracer) ReachabilityStatus(status network.Reachability) {\n\treachabilityStatus.Set(float64(status))\n}\n\nfunc (mt *metricsTracer) ReachabilityStatusConfidence(confidence int) {\n\treachabilityStatusConfidence.Set(float64(confidence))\n}\n\nfunc (mt *metricsTracer) ReceivedDialResponse(status pb.Message_ResponseStatus) {\n\ttags := metricshelper.GetStringSlice()\n\tdefer metricshelper.PutStringSlice(tags)\n\t*tags = append(*tags, getResponseStatus(status))\n\treceivedDialResponseTotal.WithLabelValues(*tags...).Inc()\n}\n\nfunc (mt *metricsTracer) OutgoingDialResponse(status pb.Message_ResponseStatus) {\n\ttags := metricshelper.GetStringSlice()\n\tdefer metricshelper.PutStringSlice(tags)\n\t*tags = append(*tags, getResponseStatus(status))\n\toutgoingDialResponseTotal.WithLabelValues(*tags...).Inc()\n}\n\nfunc (mt *metricsTracer) OutgoingDialRefused(reason string) {\n\ttags := metricshelper.GetStringSlice()\n\tdefer metricshelper.PutStringSlice(tags)\n\t*tags = append(*tags, reason)\n\toutgoingDialRefusedTotal.WithLabelValues(*tags...).Inc()\n}\n\nfunc (mt *metricsTracer) NextProbeTime(t time.Time) {\n\tnextProbeTimestamp.Set(float64(t.Unix()))\n}\n"
  },
  {
    "path": "p2p/host/autonat/metrics_test.go",
    "content": "//go:build nocover\n\npackage autonat\n\nimport (\n\t\"math/rand\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/autonat/pb\"\n)\n\nfunc BenchmarkReachabilityStatus(b *testing.B) {\n\tb.ReportAllocs()\n\tmt := NewMetricsTracer()\n\tfor i := 0; i < b.N; i++ {\n\t\tmt.ReachabilityStatus(network.Reachability(i % 3))\n\t}\n}\n\nfunc BenchmarkClientDialResponse(b *testing.B) {\n\tb.ReportAllocs()\n\tmt := NewMetricsTracer()\n\tstatuses := []pb.Message_ResponseStatus{\n\t\tpb.Message_OK, pb.Message_E_DIAL_ERROR, pb.Message_E_DIAL_REFUSED, pb.Message_E_BAD_REQUEST}\n\tfor i := 0; i < b.N; i++ {\n\t\tmt.ReceivedDialResponse(statuses[i%len(statuses)])\n\t}\n}\n\nfunc BenchmarkServerDialResponse(b *testing.B) {\n\tb.ReportAllocs()\n\tmt := NewMetricsTracer()\n\tstatuses := []pb.Message_ResponseStatus{\n\t\tpb.Message_OK, pb.Message_E_DIAL_ERROR, pb.Message_E_DIAL_REFUSED, pb.Message_E_BAD_REQUEST}\n\tfor i := 0; i < b.N; i++ {\n\t\tmt.OutgoingDialResponse(statuses[i%len(statuses)])\n\t}\n}\n\nfunc BenchmarkServerDialRefused(b *testing.B) {\n\tb.ReportAllocs()\n\tmt := NewMetricsTracer()\n\tfor i := 0; i < b.N; i++ {\n\t\tmt.OutgoingDialRefused(rate_limited)\n\t}\n}\n\nfunc TestMetricsNoAllocNoCover(t *testing.T) {\n\tmt := NewMetricsTracer()\n\tstatuses := []network.Reachability{\n\t\tnetwork.ReachabilityPublic,\n\t\tnetwork.ReachabilityPrivate,\n\t\tnetwork.ReachabilityUnknown,\n\t}\n\trespStatuses := []pb.Message_ResponseStatus{\n\t\tpb.Message_OK,\n\t\tpb.Message_E_BAD_REQUEST,\n\t\tpb.Message_E_DIAL_REFUSED,\n\t\tpb.Message_E_INTERNAL_ERROR,\n\t}\n\treasons := []string{\n\t\trate_limited,\n\t\t\"bad request\",\n\t\t\"no valid address\",\n\t}\n\ttests := map[string]func(){\n\t\t\"ReachabilityStatus\":           func() { mt.ReachabilityStatus(statuses[rand.Intn(len(statuses))]) },\n\t\t\"ReachabilityStatusConfidence\": func() { mt.ReachabilityStatusConfidence(rand.Intn(4)) },\n\t\t\"ReceivedDialResponse\":         func() { mt.ReceivedDialResponse(respStatuses[rand.Intn(len(respStatuses))]) },\n\t\t\"OutgoingDialResponse\":         func() { mt.OutgoingDialResponse(respStatuses[rand.Intn(len(respStatuses))]) },\n\t\t\"OutgoingDialRefused\":          func() { mt.OutgoingDialRefused(reasons[rand.Intn(len(reasons))]) },\n\t\t\"NextProbeTime\":                func() { mt.NextProbeTime(time.Now()) },\n\t}\n\tfor method, f := range tests {\n\t\tallocs := testing.AllocsPerRun(1000, f)\n\t\tif allocs > 0 {\n\t\t\tt.Fatalf(\"%s alloc test failed expected 0 received %0.2f\", method, allocs)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "p2p/host/autonat/notify.go",
    "content": "package autonat\n\nimport (\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\nvar _ network.Notifiee = (*AmbientAutoNAT)(nil)\n\n// Listen is part of the network.Notifiee interface\nfunc (as *AmbientAutoNAT) Listen(_ network.Network, _ ma.Multiaddr) {}\n\n// ListenClose is part of the network.Notifiee interface\nfunc (as *AmbientAutoNAT) ListenClose(_ network.Network, _ ma.Multiaddr) {}\n\n// Connected is part of the network.Notifiee interface\nfunc (as *AmbientAutoNAT) Connected(_ network.Network, c network.Conn) {\n\tif c.Stat().Direction == network.DirInbound &&\n\t\tmanet.IsPublicAddr(c.RemoteMultiaddr()) {\n\t\tselect {\n\t\tcase as.inboundConn <- c:\n\t\tdefault:\n\t\t}\n\t}\n}\n\n// Disconnected is part of the network.Notifiee interface\nfunc (as *AmbientAutoNAT) Disconnected(_ network.Network, _ network.Conn) {}\n"
  },
  {
    "path": "p2p/host/autonat/options.go",
    "content": "package autonat\n\nimport (\n\t\"errors\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n)\n\n// config holds configurable options for the autonat subsystem.\ntype config struct {\n\thost host.Host\n\n\taddressFunc       AddrFunc\n\tdialPolicy        dialPolicy\n\tdialer            network.Network\n\tforceReachability bool\n\treachability      network.Reachability\n\tmetricsTracer     MetricsTracer\n\n\t// client\n\tbootDelay          time.Duration\n\tretryInterval      time.Duration\n\trefreshInterval    time.Duration\n\trequestTimeout     time.Duration\n\tthrottlePeerPeriod time.Duration\n\n\t// server\n\tdialTimeout         time.Duration\n\tmaxPeerAddresses    int\n\tthrottleGlobalMax   int\n\tthrottlePeerMax     int\n\tthrottleResetPeriod time.Duration\n\tthrottleResetJitter time.Duration\n}\n\nvar defaults = func(c *config) error {\n\tc.bootDelay = 15 * time.Second\n\tc.retryInterval = 90 * time.Second\n\tc.refreshInterval = 15 * time.Minute\n\tc.requestTimeout = 30 * time.Second\n\tc.throttlePeerPeriod = 90 * time.Second\n\n\tc.dialTimeout = 15 * time.Second\n\tc.maxPeerAddresses = 16\n\tc.throttleGlobalMax = 30\n\tc.throttlePeerMax = 3\n\tc.throttleResetPeriod = 1 * time.Minute\n\tc.throttleResetJitter = 15 * time.Second\n\treturn nil\n}\n\nconst maxRefreshInterval = 24 * time.Hour\n\n// EnableService specifies that AutoNAT should be allowed to run a NAT service to help\n// other peers determine their own NAT status. The provided Network should not be the\n// default network/dialer of the host passed to `New`, as the NAT system will need to\n// make parallel connections, and as such will modify both the associated peerstore\n// and terminate connections of this dialer. The dialer provided\n// should be compatible (TCP/UDP) however with the transports of the libp2p network.\nfunc EnableService(dialer network.Network) Option {\n\treturn func(c *config) error {\n\t\tif dialer == c.host.Network() || dialer.Peerstore() == c.host.Peerstore() {\n\t\t\treturn errors.New(\"dialer should not be that of the host\")\n\t\t}\n\t\tc.dialer = dialer\n\t\treturn nil\n\t}\n}\n\n// WithReachability overrides autonat to simply report an over-ridden reachability\n// status.\nfunc WithReachability(reachability network.Reachability) Option {\n\treturn func(c *config) error {\n\t\tc.forceReachability = true\n\t\tc.reachability = reachability\n\t\treturn nil\n\t}\n}\n\n// UsingAddresses allows overriding which Addresses the AutoNAT client believes\n// are \"its own\". Useful for testing, or for more exotic port-forwarding\n// scenarios where the host may be listening on different ports than it wants\n// to externally advertise or verify connectability on.\nfunc UsingAddresses(addrFunc AddrFunc) Option {\n\treturn func(c *config) error {\n\t\tif addrFunc == nil {\n\t\t\treturn errors.New(\"invalid address function supplied\")\n\t\t}\n\t\tc.addressFunc = addrFunc\n\t\treturn nil\n\t}\n}\n\n// WithSchedule configures how aggressively probes will be made to verify the\n// address of the host. retryInterval indicates how often probes should be made\n// when the host lacks confidence about its address, while refreshInterval\n// is the schedule of periodic probes when the host believes it knows its\n// steady-state reachability.\nfunc WithSchedule(retryInterval, refreshInterval time.Duration) Option {\n\treturn func(c *config) error {\n\t\tc.retryInterval = retryInterval\n\t\tc.refreshInterval = refreshInterval\n\t\treturn nil\n\t}\n}\n\n// WithoutStartupDelay removes the initial delay the NAT subsystem typically\n// uses as a buffer for ensuring that connectivity and guesses as to the hosts\n// local interfaces have settled down during startup.\nfunc WithoutStartupDelay() Option {\n\treturn func(c *config) error {\n\t\tc.bootDelay = 1\n\t\treturn nil\n\t}\n}\n\n// WithoutThrottling indicates that this autonat service should not place\n// restrictions on how many peers it is willing to help when acting as\n// a server.\nfunc WithoutThrottling() Option {\n\treturn func(c *config) error {\n\t\tc.throttleGlobalMax = 0\n\t\treturn nil\n\t}\n}\n\n// WithThrottling specifies how many peers (`amount`) it is willing to help\n// ever `interval` amount of time when acting as a server.\nfunc WithThrottling(amount int, interval time.Duration) Option {\n\treturn func(c *config) error {\n\t\tc.throttleGlobalMax = amount\n\t\tc.throttleResetPeriod = interval\n\t\tc.throttleResetJitter = interval / 4\n\t\treturn nil\n\t}\n}\n\n// WithPeerThrottling specifies a limit for the maximum number of IP checks\n// this node will provide to an individual peer in each `interval`.\nfunc WithPeerThrottling(amount int) Option {\n\treturn func(c *config) error {\n\t\tc.throttlePeerMax = amount\n\t\treturn nil\n\t}\n}\n\n// WithMetricsTracer uses mt to track autonat metrics\nfunc WithMetricsTracer(mt MetricsTracer) Option {\n\treturn func(c *config) error {\n\t\tc.metricsTracer = mt\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "p2p/host/autonat/pb/autonat.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.6\n// \tprotoc        v5.29.2\n// source: p2p/host/autonat/pb/autonat.proto\n\npackage pb\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Message_MessageType int32\n\nconst (\n\tMessage_DIAL          Message_MessageType = 0\n\tMessage_DIAL_RESPONSE Message_MessageType = 1\n)\n\n// Enum value maps for Message_MessageType.\nvar (\n\tMessage_MessageType_name = map[int32]string{\n\t\t0: \"DIAL\",\n\t\t1: \"DIAL_RESPONSE\",\n\t}\n\tMessage_MessageType_value = map[string]int32{\n\t\t\"DIAL\":          0,\n\t\t\"DIAL_RESPONSE\": 1,\n\t}\n)\n\nfunc (x Message_MessageType) Enum() *Message_MessageType {\n\tp := new(Message_MessageType)\n\t*p = x\n\treturn p\n}\n\nfunc (x Message_MessageType) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (Message_MessageType) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_p2p_host_autonat_pb_autonat_proto_enumTypes[0].Descriptor()\n}\n\nfunc (Message_MessageType) Type() protoreflect.EnumType {\n\treturn &file_p2p_host_autonat_pb_autonat_proto_enumTypes[0]\n}\n\nfunc (x Message_MessageType) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Do not use.\nfunc (x *Message_MessageType) UnmarshalJSON(b []byte) error {\n\tnum, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)\n\tif err != nil {\n\t\treturn err\n\t}\n\t*x = Message_MessageType(num)\n\treturn nil\n}\n\n// Deprecated: Use Message_MessageType.Descriptor instead.\nfunc (Message_MessageType) EnumDescriptor() ([]byte, []int) {\n\treturn file_p2p_host_autonat_pb_autonat_proto_rawDescGZIP(), []int{0, 0}\n}\n\ntype Message_ResponseStatus int32\n\nconst (\n\tMessage_OK               Message_ResponseStatus = 0\n\tMessage_E_DIAL_ERROR     Message_ResponseStatus = 100\n\tMessage_E_DIAL_REFUSED   Message_ResponseStatus = 101\n\tMessage_E_BAD_REQUEST    Message_ResponseStatus = 200\n\tMessage_E_INTERNAL_ERROR Message_ResponseStatus = 300\n)\n\n// Enum value maps for Message_ResponseStatus.\nvar (\n\tMessage_ResponseStatus_name = map[int32]string{\n\t\t0:   \"OK\",\n\t\t100: \"E_DIAL_ERROR\",\n\t\t101: \"E_DIAL_REFUSED\",\n\t\t200: \"E_BAD_REQUEST\",\n\t\t300: \"E_INTERNAL_ERROR\",\n\t}\n\tMessage_ResponseStatus_value = map[string]int32{\n\t\t\"OK\":               0,\n\t\t\"E_DIAL_ERROR\":     100,\n\t\t\"E_DIAL_REFUSED\":   101,\n\t\t\"E_BAD_REQUEST\":    200,\n\t\t\"E_INTERNAL_ERROR\": 300,\n\t}\n)\n\nfunc (x Message_ResponseStatus) Enum() *Message_ResponseStatus {\n\tp := new(Message_ResponseStatus)\n\t*p = x\n\treturn p\n}\n\nfunc (x Message_ResponseStatus) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (Message_ResponseStatus) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_p2p_host_autonat_pb_autonat_proto_enumTypes[1].Descriptor()\n}\n\nfunc (Message_ResponseStatus) Type() protoreflect.EnumType {\n\treturn &file_p2p_host_autonat_pb_autonat_proto_enumTypes[1]\n}\n\nfunc (x Message_ResponseStatus) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Do not use.\nfunc (x *Message_ResponseStatus) UnmarshalJSON(b []byte) error {\n\tnum, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)\n\tif err != nil {\n\t\treturn err\n\t}\n\t*x = Message_ResponseStatus(num)\n\treturn nil\n}\n\n// Deprecated: Use Message_ResponseStatus.Descriptor instead.\nfunc (Message_ResponseStatus) EnumDescriptor() ([]byte, []int) {\n\treturn file_p2p_host_autonat_pb_autonat_proto_rawDescGZIP(), []int{0, 1}\n}\n\ntype Message struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tType          *Message_MessageType   `protobuf:\"varint,1,opt,name=type,enum=autonat.pb.Message_MessageType\" json:\"type,omitempty\"`\n\tDial          *Message_Dial          `protobuf:\"bytes,2,opt,name=dial\" json:\"dial,omitempty\"`\n\tDialResponse  *Message_DialResponse  `protobuf:\"bytes,3,opt,name=dialResponse\" json:\"dialResponse,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Message) Reset() {\n\t*x = Message{}\n\tmi := &file_p2p_host_autonat_pb_autonat_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Message) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Message) ProtoMessage() {}\n\nfunc (x *Message) ProtoReflect() protoreflect.Message {\n\tmi := &file_p2p_host_autonat_pb_autonat_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Message.ProtoReflect.Descriptor instead.\nfunc (*Message) Descriptor() ([]byte, []int) {\n\treturn file_p2p_host_autonat_pb_autonat_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Message) GetType() Message_MessageType {\n\tif x != nil && x.Type != nil {\n\t\treturn *x.Type\n\t}\n\treturn Message_DIAL\n}\n\nfunc (x *Message) GetDial() *Message_Dial {\n\tif x != nil {\n\t\treturn x.Dial\n\t}\n\treturn nil\n}\n\nfunc (x *Message) GetDialResponse() *Message_DialResponse {\n\tif x != nil {\n\t\treturn x.DialResponse\n\t}\n\treturn nil\n}\n\ntype Message_PeerInfo struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tId            []byte                 `protobuf:\"bytes,1,opt,name=id\" json:\"id,omitempty\"`\n\tAddrs         [][]byte               `protobuf:\"bytes,2,rep,name=addrs\" json:\"addrs,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Message_PeerInfo) Reset() {\n\t*x = Message_PeerInfo{}\n\tmi := &file_p2p_host_autonat_pb_autonat_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Message_PeerInfo) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Message_PeerInfo) ProtoMessage() {}\n\nfunc (x *Message_PeerInfo) ProtoReflect() protoreflect.Message {\n\tmi := &file_p2p_host_autonat_pb_autonat_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Message_PeerInfo.ProtoReflect.Descriptor instead.\nfunc (*Message_PeerInfo) Descriptor() ([]byte, []int) {\n\treturn file_p2p_host_autonat_pb_autonat_proto_rawDescGZIP(), []int{0, 0}\n}\n\nfunc (x *Message_PeerInfo) GetId() []byte {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn nil\n}\n\nfunc (x *Message_PeerInfo) GetAddrs() [][]byte {\n\tif x != nil {\n\t\treturn x.Addrs\n\t}\n\treturn nil\n}\n\ntype Message_Dial struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tPeer          *Message_PeerInfo      `protobuf:\"bytes,1,opt,name=peer\" json:\"peer,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Message_Dial) Reset() {\n\t*x = Message_Dial{}\n\tmi := &file_p2p_host_autonat_pb_autonat_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Message_Dial) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Message_Dial) ProtoMessage() {}\n\nfunc (x *Message_Dial) ProtoReflect() protoreflect.Message {\n\tmi := &file_p2p_host_autonat_pb_autonat_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Message_Dial.ProtoReflect.Descriptor instead.\nfunc (*Message_Dial) Descriptor() ([]byte, []int) {\n\treturn file_p2p_host_autonat_pb_autonat_proto_rawDescGZIP(), []int{0, 1}\n}\n\nfunc (x *Message_Dial) GetPeer() *Message_PeerInfo {\n\tif x != nil {\n\t\treturn x.Peer\n\t}\n\treturn nil\n}\n\ntype Message_DialResponse struct {\n\tstate         protoimpl.MessageState  `protogen:\"open.v1\"`\n\tStatus        *Message_ResponseStatus `protobuf:\"varint,1,opt,name=status,enum=autonat.pb.Message_ResponseStatus\" json:\"status,omitempty\"`\n\tStatusText    *string                 `protobuf:\"bytes,2,opt,name=statusText\" json:\"statusText,omitempty\"`\n\tAddr          []byte                  `protobuf:\"bytes,3,opt,name=addr\" json:\"addr,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Message_DialResponse) Reset() {\n\t*x = Message_DialResponse{}\n\tmi := &file_p2p_host_autonat_pb_autonat_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Message_DialResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Message_DialResponse) ProtoMessage() {}\n\nfunc (x *Message_DialResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_p2p_host_autonat_pb_autonat_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Message_DialResponse.ProtoReflect.Descriptor instead.\nfunc (*Message_DialResponse) Descriptor() ([]byte, []int) {\n\treturn file_p2p_host_autonat_pb_autonat_proto_rawDescGZIP(), []int{0, 2}\n}\n\nfunc (x *Message_DialResponse) GetStatus() Message_ResponseStatus {\n\tif x != nil && x.Status != nil {\n\t\treturn *x.Status\n\t}\n\treturn Message_OK\n}\n\nfunc (x *Message_DialResponse) GetStatusText() string {\n\tif x != nil && x.StatusText != nil {\n\t\treturn *x.StatusText\n\t}\n\treturn \"\"\n}\n\nfunc (x *Message_DialResponse) GetAddr() []byte {\n\tif x != nil {\n\t\treturn x.Addr\n\t}\n\treturn nil\n}\n\nvar File_p2p_host_autonat_pb_autonat_proto protoreflect.FileDescriptor\n\nconst file_p2p_host_autonat_pb_autonat_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"!p2p/host/autonat/pb/autonat.proto\\x12\\n\" +\n\t\"autonat.pb\\\"\\xb5\\x04\\n\" +\n\t\"\\aMessage\\x123\\n\" +\n\t\"\\x04type\\x18\\x01 \\x01(\\x0e2\\x1f.autonat.pb.Message.MessageTypeR\\x04type\\x12,\\n\" +\n\t\"\\x04dial\\x18\\x02 \\x01(\\v2\\x18.autonat.pb.Message.DialR\\x04dial\\x12D\\n\" +\n\t\"\\fdialResponse\\x18\\x03 \\x01(\\v2 .autonat.pb.Message.DialResponseR\\fdialResponse\\x1a0\\n\" +\n\t\"\\bPeerInfo\\x12\\x0e\\n\" +\n\t\"\\x02id\\x18\\x01 \\x01(\\fR\\x02id\\x12\\x14\\n\" +\n\t\"\\x05addrs\\x18\\x02 \\x03(\\fR\\x05addrs\\x1a8\\n\" +\n\t\"\\x04Dial\\x120\\n\" +\n\t\"\\x04peer\\x18\\x01 \\x01(\\v2\\x1c.autonat.pb.Message.PeerInfoR\\x04peer\\x1a~\\n\" +\n\t\"\\fDialResponse\\x12:\\n\" +\n\t\"\\x06status\\x18\\x01 \\x01(\\x0e2\\\".autonat.pb.Message.ResponseStatusR\\x06status\\x12\\x1e\\n\" +\n\t\"\\n\" +\n\t\"statusText\\x18\\x02 \\x01(\\tR\\n\" +\n\t\"statusText\\x12\\x12\\n\" +\n\t\"\\x04addr\\x18\\x03 \\x01(\\fR\\x04addr\\\"*\\n\" +\n\t\"\\vMessageType\\x12\\b\\n\" +\n\t\"\\x04DIAL\\x10\\x00\\x12\\x11\\n\" +\n\t\"\\rDIAL_RESPONSE\\x10\\x01\\\"i\\n\" +\n\t\"\\x0eResponseStatus\\x12\\x06\\n\" +\n\t\"\\x02OK\\x10\\x00\\x12\\x10\\n\" +\n\t\"\\fE_DIAL_ERROR\\x10d\\x12\\x12\\n\" +\n\t\"\\x0eE_DIAL_REFUSED\\x10e\\x12\\x12\\n\" +\n\t\"\\rE_BAD_REQUEST\\x10\\xc8\\x01\\x12\\x15\\n\" +\n\t\"\\x10E_INTERNAL_ERROR\\x10\\xac\\x02B1Z/github.com/libp2p/go-libp2p/p2p/host/autonat/pb\"\n\nvar (\n\tfile_p2p_host_autonat_pb_autonat_proto_rawDescOnce sync.Once\n\tfile_p2p_host_autonat_pb_autonat_proto_rawDescData []byte\n)\n\nfunc file_p2p_host_autonat_pb_autonat_proto_rawDescGZIP() []byte {\n\tfile_p2p_host_autonat_pb_autonat_proto_rawDescOnce.Do(func() {\n\t\tfile_p2p_host_autonat_pb_autonat_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_p2p_host_autonat_pb_autonat_proto_rawDesc), len(file_p2p_host_autonat_pb_autonat_proto_rawDesc)))\n\t})\n\treturn file_p2p_host_autonat_pb_autonat_proto_rawDescData\n}\n\nvar file_p2p_host_autonat_pb_autonat_proto_enumTypes = make([]protoimpl.EnumInfo, 2)\nvar file_p2p_host_autonat_pb_autonat_proto_msgTypes = make([]protoimpl.MessageInfo, 4)\nvar file_p2p_host_autonat_pb_autonat_proto_goTypes = []any{\n\t(Message_MessageType)(0),     // 0: autonat.pb.Message.MessageType\n\t(Message_ResponseStatus)(0),  // 1: autonat.pb.Message.ResponseStatus\n\t(*Message)(nil),              // 2: autonat.pb.Message\n\t(*Message_PeerInfo)(nil),     // 3: autonat.pb.Message.PeerInfo\n\t(*Message_Dial)(nil),         // 4: autonat.pb.Message.Dial\n\t(*Message_DialResponse)(nil), // 5: autonat.pb.Message.DialResponse\n}\nvar file_p2p_host_autonat_pb_autonat_proto_depIdxs = []int32{\n\t0, // 0: autonat.pb.Message.type:type_name -> autonat.pb.Message.MessageType\n\t4, // 1: autonat.pb.Message.dial:type_name -> autonat.pb.Message.Dial\n\t5, // 2: autonat.pb.Message.dialResponse:type_name -> autonat.pb.Message.DialResponse\n\t3, // 3: autonat.pb.Message.Dial.peer:type_name -> autonat.pb.Message.PeerInfo\n\t1, // 4: autonat.pb.Message.DialResponse.status:type_name -> autonat.pb.Message.ResponseStatus\n\t5, // [5:5] is the sub-list for method output_type\n\t5, // [5:5] is the sub-list for method input_type\n\t5, // [5:5] is the sub-list for extension type_name\n\t5, // [5:5] is the sub-list for extension extendee\n\t0, // [0:5] is the sub-list for field type_name\n}\n\nfunc init() { file_p2p_host_autonat_pb_autonat_proto_init() }\nfunc file_p2p_host_autonat_pb_autonat_proto_init() {\n\tif File_p2p_host_autonat_pb_autonat_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_p2p_host_autonat_pb_autonat_proto_rawDesc), len(file_p2p_host_autonat_pb_autonat_proto_rawDesc)),\n\t\t\tNumEnums:      2,\n\t\t\tNumMessages:   4,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_p2p_host_autonat_pb_autonat_proto_goTypes,\n\t\tDependencyIndexes: file_p2p_host_autonat_pb_autonat_proto_depIdxs,\n\t\tEnumInfos:         file_p2p_host_autonat_pb_autonat_proto_enumTypes,\n\t\tMessageInfos:      file_p2p_host_autonat_pb_autonat_proto_msgTypes,\n\t}.Build()\n\tFile_p2p_host_autonat_pb_autonat_proto = out.File\n\tfile_p2p_host_autonat_pb_autonat_proto_goTypes = nil\n\tfile_p2p_host_autonat_pb_autonat_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "p2p/host/autonat/pb/autonat.proto",
    "content": "syntax = \"proto2\";\n\npackage autonat.pb;\n\noption go_package = \"github.com/libp2p/go-libp2p/p2p/host/autonat/pb\";\n\nmessage Message {\n  enum MessageType {\n    DIAL          = 0;\n    DIAL_RESPONSE = 1;\n  }\n\n  enum ResponseStatus {\n    OK               = 0;\n    E_DIAL_ERROR     = 100;\n    E_DIAL_REFUSED   = 101;\n    E_BAD_REQUEST    = 200;\n    E_INTERNAL_ERROR = 300;\n  }\n\n  message PeerInfo {\n    optional bytes id = 1;\n    repeated bytes addrs = 2;\n  }\n\n  message Dial {\n    optional PeerInfo peer = 1;\n  }\n\n  message DialResponse {\n    optional ResponseStatus status = 1;\n    optional string statusText = 2;\n    optional bytes addr = 3;\n  }\n\n  optional MessageType type = 1;\n  optional Dial dial = 2;\n  optional DialResponse dialResponse = 3;\n}\n"
  },
  {
    "path": "p2p/host/autonat/proto.go",
    "content": "package autonat\n\nimport (\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/autonat/pb\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\n// AutoNATProto identifies the autonat service protocol\nconst AutoNATProto = \"/libp2p/autonat/1.0.0\"\n\nfunc newDialMessage(pi peer.AddrInfo) *pb.Message {\n\tmsg := new(pb.Message)\n\tmsg.Type = pb.Message_DIAL.Enum()\n\tmsg.Dial = new(pb.Message_Dial)\n\tmsg.Dial.Peer = new(pb.Message_PeerInfo)\n\tmsg.Dial.Peer.Id = []byte(pi.ID)\n\tmsg.Dial.Peer.Addrs = make([][]byte, len(pi.Addrs))\n\tfor i, addr := range pi.Addrs {\n\t\tmsg.Dial.Peer.Addrs[i] = addr.Bytes()\n\t}\n\n\treturn msg\n}\n\nfunc newDialResponseOK(addr ma.Multiaddr) *pb.Message_DialResponse {\n\tdr := new(pb.Message_DialResponse)\n\tdr.Status = pb.Message_OK.Enum()\n\tdr.Addr = addr.Bytes()\n\treturn dr\n}\n\nfunc newDialResponseError(status pb.Message_ResponseStatus, text string) *pb.Message_DialResponse {\n\tdr := new(pb.Message_DialResponse)\n\tdr.Status = status.Enum()\n\tdr.StatusText = &text\n\treturn dr\n}\n"
  },
  {
    "path": "p2p/host/autonat/svc.go",
    "content": "package autonat\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"math/rand\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/autonat/pb\"\n\n\t\"github.com/libp2p/go-msgio/pbio\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\nvar streamTimeout = 60 * time.Second\n\nconst (\n\tServiceName = \"libp2p.autonat\"\n\n\tmaxMsgSize = 4096\n)\n\n// AutoNATService provides NAT autodetection services to other peers\ntype autoNATService struct {\n\tinstanceLock      sync.Mutex\n\tinstance          context.CancelFunc\n\tbackgroundRunning chan struct{} // closed when background exits\n\n\tconfig *config\n\n\t// rate limiter\n\tmx         sync.Mutex\n\treqs       map[peer.ID]int\n\tglobalReqs int\n}\n\n// NewAutoNATService creates a new AutoNATService instance attached to a host\nfunc newAutoNATService(c *config) (*autoNATService, error) {\n\tif c.dialer == nil {\n\t\treturn nil, errors.New(\"cannot create NAT service without a network\")\n\t}\n\treturn &autoNATService{\n\t\tconfig: c,\n\t\treqs:   make(map[peer.ID]int),\n\t}, nil\n}\n\nfunc (as *autoNATService) handleStream(s network.Stream) {\n\tif err := s.Scope().SetService(ServiceName); err != nil {\n\t\tlog.Debug(\"error attaching stream to autonat service\", \"err\", err)\n\t\ts.Reset()\n\t\treturn\n\t}\n\n\tif err := s.Scope().ReserveMemory(maxMsgSize, network.ReservationPriorityAlways); err != nil {\n\t\tlog.Debug(\"error reserving memory for autonat stream\", \"err\", err)\n\t\ts.Reset()\n\t\treturn\n\t}\n\tdefer s.Scope().ReleaseMemory(maxMsgSize)\n\n\ts.SetDeadline(time.Now().Add(streamTimeout))\n\tdefer s.Close()\n\n\tpid := s.Conn().RemotePeer()\n\tlog.Debug(\"New stream from peer\", \"peer\", pid)\n\n\tr := pbio.NewDelimitedReader(s, maxMsgSize)\n\tw := pbio.NewDelimitedWriter(s)\n\n\tvar req pb.Message\n\tvar res pb.Message\n\n\terr := r.ReadMsg(&req)\n\tif err != nil {\n\t\tlog.Debug(\"Error reading message\", \"peer\", pid, \"err\", err)\n\t\ts.Reset()\n\t\treturn\n\t}\n\n\tt := req.GetType()\n\tif t != pb.Message_DIAL {\n\t\tlog.Debug(\"Unexpected message\", \"peer\", pid, \"message_type\", t.String(), \"expected_type\", pb.Message_DIAL.String())\n\t\ts.Reset()\n\t\treturn\n\t}\n\n\tdr := as.handleDial(pid, s.Conn().RemoteMultiaddr(), req.GetDial().GetPeer())\n\tres.Type = pb.Message_DIAL_RESPONSE.Enum()\n\tres.DialResponse = dr\n\n\terr = w.WriteMsg(&res)\n\tif err != nil {\n\t\tlog.Debug(\"Error writing response\", \"peer\", pid, \"err\", err)\n\t\ts.Reset()\n\t\treturn\n\t}\n\tif as.config.metricsTracer != nil {\n\t\tas.config.metricsTracer.OutgoingDialResponse(res.GetDialResponse().GetStatus())\n\t}\n}\n\nfunc (as *autoNATService) handleDial(p peer.ID, obsaddr ma.Multiaddr, mpi *pb.Message_PeerInfo) *pb.Message_DialResponse {\n\tif mpi == nil {\n\t\treturn newDialResponseError(pb.Message_E_BAD_REQUEST, \"missing peer info\")\n\t}\n\n\tmpid := mpi.GetId()\n\tif mpid != nil {\n\t\tmp, err := peer.IDFromBytes(mpid)\n\t\tif err != nil {\n\t\t\treturn newDialResponseError(pb.Message_E_BAD_REQUEST, \"bad peer id\")\n\t\t}\n\n\t\tif mp != p {\n\t\t\treturn newDialResponseError(pb.Message_E_BAD_REQUEST, \"peer id mismatch\")\n\t\t}\n\t}\n\n\taddrs := make([]ma.Multiaddr, 0, as.config.maxPeerAddresses)\n\tseen := make(map[string]struct{})\n\n\t// Don't even try to dial peers with blocked remote addresses. In order to dial a peer, we\n\t// need to know their public IP address, and it needs to be different from our public IP\n\t// address.\n\tif as.config.dialPolicy.skipDial(obsaddr) {\n\t\tif as.config.metricsTracer != nil {\n\t\t\tas.config.metricsTracer.OutgoingDialRefused(dial_blocked)\n\t\t}\n\t\t// Note: versions < v0.20.0 return Message_E_DIAL_ERROR here, thus we can not rely on this error code.\n\t\treturn newDialResponseError(pb.Message_E_DIAL_REFUSED, \"refusing to dial peer with blocked observed address\")\n\t}\n\n\t// Determine the peer's IP address.\n\thostIP, _ := ma.SplitFirst(obsaddr)\n\tswitch hostIP.Protocol().Code {\n\tcase ma.P_IP4, ma.P_IP6:\n\tdefault:\n\t\t// This shouldn't be possible as we should skip all addresses that don't include\n\t\t// public IP addresses.\n\t\treturn newDialResponseError(pb.Message_E_INTERNAL_ERROR, \"expected an IP address\")\n\t}\n\n\t// add observed addr to the list of addresses to dial\n\taddrs = append(addrs, obsaddr)\n\tseen[obsaddr.String()] = struct{}{}\n\n\tfor _, maddr := range mpi.GetAddrs() {\n\t\taddr, err := ma.NewMultiaddrBytes(maddr)\n\t\tif err != nil {\n\t\t\tlog.Debug(\"Error parsing multiaddr\", \"err\", err)\n\t\t\tcontinue\n\t\t}\n\n\t\t// For security reasons, we _only_ dial the observed IP address.\n\t\t// Replace other IP addresses with the observed one so we can still try the\n\t\t// requested ports/transports.\n\t\tif ip, rest := ma.SplitFirst(addr); !ip.Equal(hostIP) {\n\t\t\t// Make sure it's an IP address\n\t\t\tswitch ip.Protocol().Code {\n\t\t\tcase ma.P_IP4, ma.P_IP6:\n\t\t\tdefault:\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\taddr = hostIP.Multiaddr()\n\t\t\tif len(rest) > 0 {\n\t\t\t\taddr = addr.Encapsulate(rest)\n\t\t\t}\n\t\t}\n\n\t\t// Make sure we're willing to dial the rest of the address (e.g., not a circuit\n\t\t// address).\n\t\tif as.config.dialPolicy.skipDial(addr) {\n\t\t\tcontinue\n\t\t}\n\n\t\tstr := addr.String()\n\t\t_, ok := seen[str]\n\t\tif ok {\n\t\t\tcontinue\n\t\t}\n\n\t\taddrs = append(addrs, addr)\n\t\tseen[str] = struct{}{}\n\n\t\tif len(addrs) >= as.config.maxPeerAddresses {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif len(addrs) == 0 {\n\t\tif as.config.metricsTracer != nil {\n\t\t\tas.config.metricsTracer.OutgoingDialRefused(no_valid_address)\n\t\t}\n\t\t// Note: versions < v0.20.0 return Message_E_DIAL_ERROR here, thus we can not rely on this error code.\n\t\treturn newDialResponseError(pb.Message_E_DIAL_REFUSED, \"no dialable addresses\")\n\t}\n\n\treturn as.doDial(peer.AddrInfo{ID: p, Addrs: addrs})\n}\n\nfunc (as *autoNATService) doDial(pi peer.AddrInfo) *pb.Message_DialResponse {\n\t// rate limit check\n\tas.mx.Lock()\n\tcount := as.reqs[pi.ID]\n\tif count >= as.config.throttlePeerMax || (as.config.throttleGlobalMax > 0 &&\n\t\tas.globalReqs >= as.config.throttleGlobalMax) {\n\t\tas.mx.Unlock()\n\t\tif as.config.metricsTracer != nil {\n\t\t\tas.config.metricsTracer.OutgoingDialRefused(rate_limited)\n\t\t}\n\t\treturn newDialResponseError(pb.Message_E_DIAL_REFUSED, \"too many dials\")\n\t}\n\tas.reqs[pi.ID] = count + 1\n\tas.globalReqs++\n\tas.mx.Unlock()\n\n\tctx, cancel := context.WithTimeout(context.Background(), as.config.dialTimeout)\n\tdefer cancel()\n\n\tas.config.dialer.Peerstore().ClearAddrs(pi.ID)\n\n\tas.config.dialer.Peerstore().AddAddrs(pi.ID, pi.Addrs, peerstore.TempAddrTTL)\n\n\tdefer func() {\n\t\tas.config.dialer.Peerstore().ClearAddrs(pi.ID)\n\t\tas.config.dialer.Peerstore().RemovePeer(pi.ID)\n\t}()\n\n\tconn, err := as.config.dialer.DialPeer(ctx, pi.ID)\n\tif err != nil {\n\t\tlog.Debug(\"error dialing peer\", \"peer\", pi.ID, \"err\", err)\n\t\t// wait for the context to timeout to avoid leaking timing information\n\t\t// this renders the service ineffective as a port scanner\n\t\t<-ctx.Done()\n\t\treturn newDialResponseError(pb.Message_E_DIAL_ERROR, \"dial failed\")\n\t}\n\n\tra := conn.RemoteMultiaddr()\n\tas.config.dialer.ClosePeer(pi.ID)\n\treturn newDialResponseOK(ra)\n}\n\n// Enable the autoNAT service if it is not running.\nfunc (as *autoNATService) Enable() {\n\tas.instanceLock.Lock()\n\tdefer as.instanceLock.Unlock()\n\tif as.instance != nil {\n\t\treturn\n\t}\n\tctx, cancel := context.WithCancel(context.Background())\n\tas.instance = cancel\n\tas.backgroundRunning = make(chan struct{})\n\tas.config.host.SetStreamHandler(AutoNATProto, as.handleStream)\n\n\tgo as.background(ctx)\n}\n\n// Disable the autoNAT service if it is running.\nfunc (as *autoNATService) Disable() {\n\tas.instanceLock.Lock()\n\tdefer as.instanceLock.Unlock()\n\tif as.instance != nil {\n\t\tas.config.host.RemoveStreamHandler(AutoNATProto)\n\t\tas.instance()\n\t\tas.instance = nil\n\t\t<-as.backgroundRunning\n\t}\n}\n\nfunc (as *autoNATService) Close() error {\n\tas.Disable()\n\treturn as.config.dialer.Close()\n}\n\nfunc (as *autoNATService) background(ctx context.Context) {\n\tdefer close(as.backgroundRunning)\n\n\ttimer := time.NewTimer(as.config.throttleResetPeriod)\n\tdefer timer.Stop()\n\n\tfor {\n\t\tselect {\n\t\tcase <-timer.C:\n\t\t\tas.mx.Lock()\n\t\t\tas.reqs = make(map[peer.ID]int)\n\t\t\tas.globalReqs = 0\n\t\t\tas.mx.Unlock()\n\t\t\tjitter := rand.Float32() * float32(as.config.throttleResetJitter)\n\t\t\ttimer.Reset(as.config.throttleResetPeriod + time.Duration(int64(jitter)))\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "p2p/host/autonat/svc_test.go",
    "content": "package autonat\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/event\"\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\tbhost \"github.com/libp2p/go-libp2p/p2p/host/blank\"\n\tswarmt \"github.com/libp2p/go-libp2p/p2p/net/swarm/testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc makeAutoNATConfig(t *testing.T) *config {\n\th := bhost.NewBlankHost(swarmt.GenSwarm(t))\n\tdh := bhost.NewBlankHost(swarmt.GenSwarm(t))\n\tc := config{host: h, dialer: dh.Network()}\n\t_ = defaults(&c)\n\tc.forceReachability = true\n\tc.dialPolicy.allowSelfDials = true\n\treturn &c\n}\n\nfunc makeAutoNATService(t *testing.T, c *config) *autoNATService {\n\tas, err := newAutoNATService(c)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tas.Enable()\n\n\treturn as\n}\n\nfunc makeAutoNATClient(t *testing.T) (host.Host, Client) {\n\th := bhost.NewBlankHost(swarmt.GenSwarm(t))\n\tcli := NewAutoNATClient(h, nil, nil)\n\treturn h, cli\n}\n\n// Note: these tests assume that the host has only private network addresses!\nfunc TestAutoNATServiceDialRefused(t *testing.T) {\n\tctx := t.Context()\n\n\tc := makeAutoNATConfig(t)\n\tdefer c.host.Close()\n\tdefer c.dialer.Close()\n\n\tc.dialTimeout = 1 * time.Second\n\tc.dialPolicy.allowSelfDials = false\n\t_ = makeAutoNATService(t, c)\n\thc, ac := makeAutoNATClient(t)\n\tdefer hc.Close()\n\tconnect(t, c.host, hc)\n\n\terr := ac.DialBack(ctx, c.host.ID())\n\tif err == nil {\n\t\tt.Fatal(\"Dial back succeeded unexpectedly!\")\n\t}\n\n\tif !IsDialRefused(err) {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestAutoNATServiceDialSuccess(t *testing.T) {\n\tctx := t.Context()\n\n\tc := makeAutoNATConfig(t)\n\tdefer c.host.Close()\n\tdefer c.dialer.Close()\n\n\t_ = makeAutoNATService(t, c)\n\n\thc, ac := makeAutoNATClient(t)\n\tdefer hc.Close()\n\tconnect(t, c.host, hc)\n\n\terr := ac.DialBack(ctx, c.host.ID())\n\tif err != nil {\n\t\tt.Fatalf(\"Dial back failed: %s\", err.Error())\n\t}\n}\n\nfunc TestAutoNATServiceDialRateLimiter(t *testing.T) {\n\tctx := t.Context()\n\n\tc := makeAutoNATConfig(t)\n\tdefer c.host.Close()\n\tdefer c.dialer.Close()\n\n\tc.dialTimeout = 200 * time.Millisecond\n\tc.throttleResetPeriod = 200 * time.Millisecond\n\tc.throttleResetJitter = 0\n\tc.throttlePeerMax = 1\n\t_ = makeAutoNATService(t, c)\n\n\thc, ac := makeAutoNATClient(t)\n\tdefer hc.Close()\n\tconnect(t, c.host, hc)\n\n\terr := ac.DialBack(ctx, c.host.ID())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = ac.DialBack(ctx, c.host.ID())\n\tif err == nil {\n\t\tt.Fatal(\"Dial back succeeded unexpectedly!\")\n\t}\n\n\tif !IsDialRefused(err) {\n\t\tt.Fatal(err)\n\t}\n\n\ttime.Sleep(400 * time.Millisecond)\n\n\terr = ac.DialBack(ctx, c.host.ID())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestAutoNATServiceGlobalLimiter(t *testing.T) {\n\tctx := t.Context()\n\n\tc := makeAutoNATConfig(t)\n\tdefer c.host.Close()\n\tdefer c.dialer.Close()\n\n\tc.dialTimeout = time.Second\n\tc.throttleResetPeriod = 10 * time.Second\n\tc.throttleResetJitter = 0\n\tc.throttlePeerMax = 1\n\tc.throttleGlobalMax = 5\n\t_ = makeAutoNATService(t, c)\n\n\ths := c.host\n\n\tfor range 5 {\n\t\thc, ac := makeAutoNATClient(t)\n\t\tconnect(t, hs, hc)\n\n\t\terr := ac.DialBack(ctx, hs.ID())\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n\n\thc, ac := makeAutoNATClient(t)\n\tdefer hc.Close()\n\tconnect(t, hs, hc)\n\terr := ac.DialBack(ctx, hs.ID())\n\tif err == nil {\n\t\tt.Fatal(\"Dial back succeeded unexpectedly!\")\n\t}\n\n\tif !IsDialRefused(err) {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestAutoNATServiceRateLimitJitter(t *testing.T) {\n\tc := makeAutoNATConfig(t)\n\tdefer c.host.Close()\n\tdefer c.dialer.Close()\n\n\tdur := 100 * time.Millisecond\n\tif os.Getenv(\"CI\") != \"\" {\n\t\tdur = 200 * time.Millisecond\n\t}\n\n\tc.throttleResetPeriod = dur\n\tc.throttleResetJitter = dur\n\tc.throttleGlobalMax = 1\n\tsvc := makeAutoNATService(t, c)\n\tsvc.mx.Lock()\n\tsvc.globalReqs = 1\n\tsvc.mx.Unlock()\n\n\trequire.Eventually(t, func() bool {\n\t\tsvc.mx.Lock()\n\t\tdefer svc.mx.Unlock()\n\t\treturn svc.globalReqs == 0\n\t}, dur*5/2, 10*time.Millisecond, \"reset of rate limiter occurred slower than expected\")\n}\n\nfunc TestAutoNATServiceStartup(t *testing.T) {\n\th := bhost.NewBlankHost(swarmt.GenSwarm(t))\n\tdefer h.Close()\n\tdh := bhost.NewBlankHost(swarmt.GenSwarm(t))\n\tdefer dh.Close()\n\tan, err := New(h, EnableService(dh.Network()))\n\tan.(*AmbientAutoNAT).config.dialPolicy.allowSelfDials = true\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\thc, ac := makeAutoNATClient(t)\n\tconnect(t, h, hc)\n\n\terr = ac.DialBack(context.Background(), h.ID())\n\tif err != nil {\n\t\tt.Fatal(\"autonat service be active in unknown mode.\")\n\t}\n\n\tsub, _ := h.EventBus().Subscribe(new(event.EvtLocalReachabilityChanged))\n\n\tanc := an.(*AmbientAutoNAT)\n\tanc.recordObservation(network.ReachabilityPublic)\n\n\t<-sub.Out()\n\n\terr = ac.DialBack(context.Background(), h.ID())\n\tif err != nil {\n\t\tt.Fatalf(\"autonat should be active, was %v\", err)\n\t}\n\tif an.Status() != network.ReachabilityPublic {\n\t\tt.Fatalf(\"autonat should report public, but didn't\")\n\t}\n}\n"
  },
  {
    "path": "p2p/host/autonat/test/autonat_test.go",
    "content": "// This separate testing package helps to resolve a circular dependency potentially\n// being created between libp2p and libp2p-autonat\npackage autonattest\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p\"\n\t\"github.com/libp2p/go-libp2p/core/event\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/autonat\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestAutonatRoundtrip(t *testing.T) {\n\tt.Skip(\"this test doesn't work\")\n\n\t// 3 hosts are used: [client] and [service + dialback dialer]\n\tclient, err := libp2p.New(libp2p.ListenAddrStrings(\"/ip4/127.0.0.1/tcp/0\"), libp2p.EnableNATService())\n\trequire.NoError(t, err)\n\tservice, err := libp2p.New(libp2p.ListenAddrStrings(\"/ip4/127.0.0.1/tcp/0\"))\n\trequire.NoError(t, err)\n\tdialback, err := libp2p.New(libp2p.NoListenAddrs)\n\trequire.NoError(t, err)\n\tif _, err := autonat.New(service, autonat.EnableService(dialback.Network())); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tclient.Peerstore().AddAddrs(service.ID(), service.Addrs(), time.Hour)\n\trequire.NoError(t, client.Connect(context.Background(), service.Peerstore().PeerInfo(service.ID())))\n\n\tcSub, err := client.EventBus().Subscribe(new(event.EvtLocalReachabilityChanged))\n\trequire.NoError(t, err)\n\tdefer cSub.Close()\n\n\tselect {\n\tcase stat := <-cSub.Out():\n\t\tif stat == network.ReachabilityUnknown {\n\t\t\tt.Fatalf(\"After status update, client did not know its status\")\n\t\t}\n\tcase <-time.After(30 * time.Second):\n\t\tt.Fatal(\"sub timed out.\")\n\t}\n}\n"
  },
  {
    "path": "p2p/host/autonat/test/dummy.go",
    "content": "package autonattest\n\n// needed so that go test ./... doesn't error\n"
  },
  {
    "path": "p2p/host/autorelay/addrsplosion.go",
    "content": "package autorelay\n\nimport (\n\t\"encoding/binary\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\n// This function cleans up a relay's address set to remove private addresses and curtail\n// addrsplosion.\n// TODO: Remove this, we don't need this. The current method tries to select the\n// best address for the relay. Instead we should rely on the addresses provided by the\n// relay in response to the reservation request.\nfunc cleanupAddressSet(addrs []ma.Multiaddr) []ma.Multiaddr {\n\tvar public, private []ma.Multiaddr\n\n\tfor _, a := range addrs {\n\t\tif isRelayAddr(a) {\n\t\t\tcontinue\n\t\t}\n\n\t\tif manet.IsPublicAddr(a) {\n\t\t\tpublic = append(public, a)\n\t\t\tcontinue\n\t\t}\n\n\t\t// discard unroutable addrs\n\t\tif manet.IsPrivateAddr(a) {\n\t\t\tprivate = append(private, a)\n\t\t}\n\t}\n\n\tif !hasAddrsplosion(public) {\n\t\treturn public\n\t}\n\n\treturn sanitizeAddrsplodedSet(public, private)\n}\n\nfunc isRelayAddr(a ma.Multiaddr) bool {\n\tisRelay := false\n\n\tma.ForEach(a, func(c ma.Component) bool {\n\t\tswitch c.Protocol().Code {\n\t\tcase ma.P_CIRCUIT:\n\t\t\tisRelay = true\n\t\t\treturn false\n\t\tdefault:\n\t\t\treturn true\n\t\t}\n\t})\n\n\treturn isRelay\n}\n\n// we have addrsplosion if for some protocol we advertise multiple ports on\n// the same base address.\nfunc hasAddrsplosion(addrs []ma.Multiaddr) bool {\n\taset := make(map[string]int)\n\n\tfor _, a := range addrs {\n\t\tkey, port := addrKeyAndPort(a)\n\t\txport, ok := aset[key]\n\t\tif ok && port != xport {\n\t\t\treturn true\n\t\t}\n\t\taset[key] = port\n\t}\n\n\treturn false\n}\n\nfunc addrKeyAndPort(a ma.Multiaddr) (string, int) {\n\tvar (\n\t\tkey  string\n\t\tport int\n\t)\n\n\tma.ForEach(a, func(c ma.Component) bool {\n\t\tswitch c.Protocol().Code {\n\t\tcase ma.P_TCP, ma.P_UDP:\n\t\t\tport = int(binary.BigEndian.Uint16(c.RawValue()))\n\t\t\tkey += \"/\" + c.Protocol().Name\n\t\tdefault:\n\t\t\tval := c.Value()\n\t\t\tif val == \"\" {\n\t\t\t\tval = c.Protocol().Name\n\t\t\t}\n\t\t\tkey += \"/\" + val\n\t\t}\n\t\treturn true\n\t})\n\n\treturn key, port\n}\n\n// clean up addrsplosion\n// the following heuristic is used:\n//   - for each base address/protocol combination, if there are multiple ports advertised then\n//     only accept the default port if present.\n//   - If the default port is not present, we check for non-standard ports by tracking\n//     private port bindings if present.\n//   - If there is no default or private port binding, then we can't infer the correct\n//     port and give up and return all addrs (for that base address)\nfunc sanitizeAddrsplodedSet(public, private []ma.Multiaddr) []ma.Multiaddr {\n\ttype portAndAddr struct {\n\t\taddr ma.Multiaddr\n\t\tport int\n\t}\n\n\tprivports := make(map[int]struct{})\n\tpubaddrs := make(map[string][]portAndAddr)\n\n\tfor _, a := range private {\n\t\t_, port := addrKeyAndPort(a)\n\t\tprivports[port] = struct{}{}\n\t}\n\n\tfor _, a := range public {\n\t\tkey, port := addrKeyAndPort(a)\n\t\tpubaddrs[key] = append(pubaddrs[key], portAndAddr{addr: a, port: port})\n\t}\n\n\tvar result []ma.Multiaddr\n\tfor _, pas := range pubaddrs {\n\t\tif len(pas) == 1 {\n\t\t\t// it's not addrsploded\n\t\t\tresult = append(result, pas[0].addr)\n\t\t\tcontinue\n\t\t}\n\n\t\thaveAddr := false\n\t\tfor _, pa := range pas {\n\t\t\tif _, ok := privports[pa.port]; ok {\n\t\t\t\t// it matches a privately bound port, use it\n\t\t\t\tresult = append(result, pa.addr)\n\t\t\t\thaveAddr = true\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif pa.port == 4001 || pa.port == 4002 {\n\t\t\t\t// it's a default port, use it\n\t\t\t\tresult = append(result, pa.addr)\n\t\t\t\thaveAddr = true\n\t\t\t}\n\t\t}\n\n\t\tif !haveAddr {\n\t\t\t// we weren't able to select a port; bite the bullet and use them all\n\t\t\tfor _, pa := range pas {\n\t\t\t\tresult = append(result, pa.addr)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn result\n}\n"
  },
  {
    "path": "p2p/host/autorelay/addrsplosion_test.go",
    "content": "package autorelay\n\nimport (\n\t\"testing\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmatest \"github.com/multiformats/go-multiaddr/matest\"\n)\n\nfunc TestCleanupAddrs(t *testing.T) {\n\tt.Run(\"with no addrplosion\", func(t *testing.T) {\n\t\taddrs := makeAddrList(\n\t\t\t\"/ip4/127.0.0.1/tcp/4001\",\n\t\t\t\"/ip4/127.0.0.1/udp/4002/quic-v1\",\n\t\t\t\"/ip4/1.2.3.4/tcp/4001\",\n\t\t\t\"/ip4/1.2.3.4/udp/4002/quic-v1\",\n\t\t\t\"/dnsaddr/somedomain.com/tcp/4002/ws\",\n\t\t)\n\t\tclean := makeAddrList(\n\t\t\t\"/ip4/1.2.3.4/tcp/4001\",\n\t\t\t\"/ip4/1.2.3.4/udp/4002/quic-v1\",\n\t\t\t\"/dnsaddr/somedomain.com/tcp/4002/ws\",\n\t\t)\n\t\tmatest.AssertMultiaddrsMatch(t, clean, cleanupAddressSet(addrs))\n\t})\n\n\tt.Run(\"with default port\", func(t *testing.T) {\n\t\t// test with default port addrspolosion\n\t\taddrs := makeAddrList(\n\t\t\t\"/ip4/127.0.0.1/tcp/4001\",\n\t\t\t\"/ip4/1.2.3.4/tcp/4001\",\n\t\t\t\"/ip4/1.2.3.4/tcp/33333\",\n\t\t\t\"/ip4/1.2.3.4/tcp/33334\",\n\t\t\t\"/ip4/1.2.3.4/tcp/33335\",\n\t\t\t\"/ip4/1.2.3.4/udp/4002/quic-v1\",\n\t\t)\n\t\tclean := makeAddrList(\n\t\t\t\"/ip4/1.2.3.4/tcp/4001\",\n\t\t\t\"/ip4/1.2.3.4/udp/4002/quic-v1\",\n\t\t)\n\t\tmatest.AssertMultiaddrsMatch(t, clean, cleanupAddressSet(addrs))\n\t})\n\n\tt.Run(\"with default port, but no private addrs\", func(t *testing.T) {\n\t\t// test with default port addrsplosion but no private addrs\n\t\taddrs := makeAddrList(\n\t\t\t\"/ip4/1.2.3.4/tcp/4001\",\n\t\t\t\"/ip4/1.2.3.4/tcp/33333\",\n\t\t\t\"/ip4/1.2.3.4/tcp/33334\",\n\t\t\t\"/ip4/1.2.3.4/tcp/33335\",\n\t\t\t\"/ip4/1.2.3.4/udp/4002/quic-v1\",\n\t\t)\n\t\tclean := makeAddrList(\n\t\t\t\"/ip4/1.2.3.4/tcp/4001\",\n\t\t\t\"/ip4/1.2.3.4/udp/4002/quic-v1\",\n\t\t)\n\t\tmatest.AssertMultiaddrsMatch(t, clean, cleanupAddressSet(addrs))\n\t})\n\n\tt.Run(\"with non-standard port\", func(t *testing.T) {\n\t\taddrs := makeAddrList(\n\t\t\t\"/ip4/127.0.0.1/tcp/12345\",\n\t\t\t\"/ip4/1.2.3.4/tcp/12345\",\n\t\t\t\"/ip4/1.2.3.4/tcp/33333\",\n\t\t\t\"/ip4/1.2.3.4/tcp/33334\",\n\t\t\t\"/ip4/1.2.3.4/tcp/33335\",\n\t\t)\n\t\tclean := makeAddrList(\n\t\t\t\"/ip4/1.2.3.4/tcp/12345\",\n\t\t)\n\t\tif !matest.AssertEqualMultiaddrs(t, clean, cleanupAddressSet(addrs)) {\n\t\t\tt.Log(\"cleaned up set doesn't match expected\")\n\t\t}\n\t})\n\n\tt.Run(\"with a clean address set\", func(t *testing.T) {\n\t\t// test with a squeaky clean address set\n\t\taddrs := makeAddrList(\n\t\t\t\"/ip4/1.2.3.4/tcp/4001\",\n\t\t\t\"/ip4/1.2.3.4/udp/4001/quic-v1\",\n\t\t)\n\t\tmatest.AssertMultiaddrsMatch(t, addrs, cleanupAddressSet(addrs))\n\t})\n}\n\nfunc makeAddrList(strs ...string) []ma.Multiaddr {\n\tresult := make([]ma.Multiaddr, 0, len(strs))\n\tfor _, s := range strs {\n\t\tresult = append(result, ma.StringCast(s))\n\t}\n\treturn result\n}\n"
  },
  {
    "path": "p2p/host/autorelay/autorelay.go",
    "content": "package autorelay\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"sync\"\n\n\t\"github.com/libp2p/go-libp2p/core/event\"\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/eventbus\"\n\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n)\n\nvar log = logging.Logger(\"autorelay\")\n\ntype AutoRelay struct {\n\trefCount  sync.WaitGroup\n\tctx       context.Context\n\tctxCancel context.CancelFunc\n\n\tmx     sync.Mutex\n\tstatus network.Reachability\n\n\trelayFinder *relayFinder\n\n\thost host.Host\n\n\tmetricsTracer MetricsTracer\n}\n\nfunc NewAutoRelay(host host.Host, opts ...Option) (*AutoRelay, error) {\n\tr := &AutoRelay{\n\t\thost:   host,\n\t\tstatus: network.ReachabilityUnknown,\n\t}\n\tconf := defaultConfig\n\tfor _, opt := range opts {\n\t\tif err := opt(&conf); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tr.ctx, r.ctxCancel = context.WithCancel(context.Background())\n\trf, err := newRelayFinder(host, &conf)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create autorelay: %w\", err)\n\t}\n\tr.relayFinder = rf\n\tr.metricsTracer = &wrappedMetricsTracer{conf.metricsTracer}\n\n\treturn r, nil\n}\n\nfunc (r *AutoRelay) Start() {\n\tr.refCount.Add(1)\n\tgo func() {\n\t\tdefer r.refCount.Done()\n\t\tr.background()\n\t}()\n}\n\nfunc (r *AutoRelay) background() {\n\tsubReachability, err := r.host.EventBus().Subscribe(new(event.EvtLocalReachabilityChanged), eventbus.Name(\"autorelay (background)\"))\n\tif err != nil {\n\t\tlog.Debug(\"failed to subscribe to the EvtLocalReachabilityChanged\")\n\t\treturn\n\t}\n\tdefer subReachability.Close()\n\n\tfor {\n\t\tselect {\n\t\tcase <-r.ctx.Done():\n\t\t\treturn\n\t\tcase ev, ok := <-subReachability.Out():\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevt := ev.(event.EvtLocalReachabilityChanged)\n\t\t\tswitch evt.Reachability {\n\t\t\tcase network.ReachabilityPrivate, network.ReachabilityUnknown:\n\t\t\t\terr := r.relayFinder.Start()\n\t\t\t\tif errors.Is(err, errAlreadyRunning) {\n\t\t\t\t\tlog.Debug(\"tried to start already running relay finder\")\n\t\t\t\t} else if err != nil {\n\t\t\t\t\tlog.Error(\"failed to start relay finder\", \"err\", err)\n\t\t\t\t} else {\n\t\t\t\t\tr.metricsTracer.RelayFinderStatus(true)\n\t\t\t\t}\n\t\t\tcase network.ReachabilityPublic:\n\t\t\t\tr.relayFinder.Stop()\n\t\t\t\tr.metricsTracer.RelayFinderStatus(false)\n\t\t\t}\n\t\t\tr.mx.Lock()\n\t\t\tr.status = evt.Reachability\n\t\t\tr.mx.Unlock()\n\t\t}\n\t}\n}\n\nfunc (r *AutoRelay) Close() error {\n\tr.ctxCancel()\n\terr := r.relayFinder.Stop()\n\tr.refCount.Wait()\n\treturn err\n}\n"
  },
  {
    "path": "p2p/host/autorelay/autorelay_test.go",
    "content": "package autorelay_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"slices\"\n\t\"strings\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p\"\n\t\"github.com/libp2p/go-libp2p/core/event\"\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/test\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/autorelay\"\n\tcircuitv2_proto \"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/proto\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nconst protoIDv2 = circuitv2_proto.ProtoIDv2Hop\n\ntype mockClock struct {\n\t*test.MockClock\n}\n\nfunc (c mockClock) InstantTimer(when time.Time) autorelay.InstantTimer {\n\treturn c.MockClock.InstantTimer(when)\n}\n\nfunc newMockClock() mockClock {\n\treturn mockClock{MockClock: test.NewMockClock()}\n}\n\nvar _ autorelay.ClockWithInstantTimer = mockClock{}\n\nfunc numRelays(h host.Host) int {\n\treturn len(usedRelays(h))\n}\n\nfunc usedRelays(h host.Host) []peer.ID {\n\tm := make(map[peer.ID]struct{})\n\tfor _, addr := range h.Addrs() {\n\t\taddr, comp := ma.SplitLast(addr)\n\t\tif comp.Protocol().Code != ma.P_CIRCUIT { // not a relay addr\n\t\t\tcontinue\n\t\t}\n\t\t_, comp = ma.SplitLast(addr)\n\t\tif comp.Protocol().Code != ma.P_P2P {\n\t\t\tpanic(\"expected p2p component\")\n\t\t}\n\t\tid, err := peer.Decode(comp.Value())\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tm[id] = struct{}{}\n\t}\n\tpeers := make([]peer.ID, 0, len(m))\n\tfor id := range m {\n\t\tpeers = append(peers, id)\n\t}\n\treturn peers\n}\n\nfunc newPrivateNode(t *testing.T, peerSource func(context.Context, int) <-chan peer.AddrInfo,\n\topts ...autorelay.Option) host.Host {\n\tt.Helper()\n\th, err := libp2p.New(\n\t\tlibp2p.ForceReachabilityPrivate(),\n\t\tlibp2p.EnableAutoRelayWithPeerSource(peerSource, opts...),\n\t)\n\trequire.NoError(t, err)\n\treturn h\n}\n\nfunc newPrivateNodeWithStaticRelays(t *testing.T, static []peer.AddrInfo, opts ...autorelay.Option) host.Host {\n\tt.Helper()\n\th, err := libp2p.New(\n\t\tlibp2p.ForceReachabilityPrivate(),\n\t\tlibp2p.EnableAutoRelayWithStaticRelays(static, opts...),\n\t)\n\trequire.NoError(t, err)\n\treturn h\n}\n\nfunc newRelay(t *testing.T) host.Host {\n\tt.Helper()\n\th, err := libp2p.New(\n\t\tlibp2p.DisableRelay(),\n\t\tlibp2p.EnableRelayService(),\n\t\tlibp2p.ForceReachabilityPublic(),\n\t\tlibp2p.AddrsFactory(func(addrs []ma.Multiaddr) []ma.Multiaddr {\n\t\t\tfor i, addr := range addrs {\n\t\t\t\tsaddr := addr.String()\n\t\t\t\tif strings.HasPrefix(saddr, \"/ip4/127.0.0.1/\") {\n\t\t\t\t\taddrNoIP := strings.TrimPrefix(saddr, \"/ip4/127.0.0.1\")\n\t\t\t\t\t// .internal is classified as a public address as users\n\t\t\t\t\t// are free to map this dns to a public ip address for\n\t\t\t\t\t// use within a LAN\n\t\t\t\t\taddrs[i] = ma.StringCast(\"/dns/libp2p.internal\" + addrNoIP)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn addrs\n\t\t}),\n\t)\n\trequire.NoError(t, err)\n\trequire.Eventually(t, func() bool {\n\t\treturn slices.Contains(h.Mux().Protocols(), protoIDv2)\n\t}, time.Second, 10*time.Millisecond)\n\treturn h\n}\n\nfunc TestSingleCandidate(t *testing.T) {\n\tvar counter int\n\th := newPrivateNode(t,\n\t\tfunc(_ context.Context, num int) <-chan peer.AddrInfo {\n\t\t\tcounter++\n\t\t\trequire.Equal(t, 1, num)\n\t\t\tpeerChan := make(chan peer.AddrInfo, num)\n\t\t\tdefer close(peerChan)\n\t\t\tr := newRelay(t)\n\t\t\tt.Cleanup(func() { r.Close() })\n\t\t\tpeerChan <- peer.AddrInfo{ID: r.ID(), Addrs: r.Addrs()}\n\t\t\treturn peerChan\n\t\t},\n\t\tautorelay.WithMaxCandidates(1),\n\t\tautorelay.WithNumRelays(99999),\n\t\tautorelay.WithBootDelay(0),\n\t\tautorelay.WithMinInterval(time.Hour),\n\t)\n\tdefer h.Close()\n\n\trequire.Eventually(t, func() bool { return numRelays(h) > 0 }, 10*time.Second, 100*time.Millisecond)\n\t// test that we don't add any more relays\n\trequire.Never(t, func() bool { return numRelays(h) > 1 }, 200*time.Millisecond, 50*time.Millisecond)\n\trequire.Equal(t, 1, counter, \"expected the peer source callback to only have been called once\")\n}\n\nfunc TestSingleRelay(t *testing.T) {\n\tconst numCandidates = 3\n\tvar called bool\n\tpeerChan := make(chan peer.AddrInfo, numCandidates)\n\tfor range numCandidates {\n\t\tr := newRelay(t)\n\t\tt.Cleanup(func() { r.Close() })\n\t\tpeerChan <- peer.AddrInfo{ID: r.ID(), Addrs: r.Addrs()}\n\t}\n\tclose(peerChan)\n\n\th := newPrivateNode(t,\n\t\tfunc(_ context.Context, num int) <-chan peer.AddrInfo {\n\t\t\trequire.False(t, called, \"expected the peer source callback to only have been called once\")\n\t\t\tcalled = true\n\t\t\trequire.Equal(t, numCandidates, num)\n\t\t\treturn peerChan\n\t\t},\n\t\tautorelay.WithMaxCandidates(numCandidates),\n\t\tautorelay.WithNumRelays(1),\n\t\tautorelay.WithBootDelay(0),\n\t\tautorelay.WithMinInterval(time.Hour),\n\t)\n\tdefer h.Close()\n\n\trequire.Eventually(t, func() bool { return numRelays(h) > 0 }, 5*time.Second, 100*time.Millisecond)\n\t// test that we don't add any more relays\n\trequire.Never(t, func() bool { return numRelays(h) > 1 }, 200*time.Millisecond, 50*time.Millisecond)\n}\n\nfunc TestWaitForCandidates(t *testing.T) {\n\tpeerChan := make(chan peer.AddrInfo)\n\th := newPrivateNode(t,\n\t\tfunc(context.Context, int) <-chan peer.AddrInfo { return peerChan },\n\t\tautorelay.WithMinCandidates(2),\n\t\tautorelay.WithNumRelays(1),\n\t\tautorelay.WithBootDelay(time.Hour),\n\t\tautorelay.WithMinInterval(time.Hour),\n\t)\n\tdefer h.Close()\n\n\tr1 := newRelay(t)\n\tt.Cleanup(func() { r1.Close() })\n\tpeerChan <- peer.AddrInfo{ID: r1.ID(), Addrs: r1.Addrs()}\n\n\t// make sure we don't add any relays yet\n\t// We need to wait until we have at least 2 candidates before we connect.\n\trequire.Never(t, func() bool { return numRelays(h) > 0 }, 200*time.Millisecond, 50*time.Millisecond)\n\n\tr2 := newRelay(t)\n\tt.Cleanup(func() { r2.Close() })\n\tpeerChan <- peer.AddrInfo{ID: r2.ID(), Addrs: r2.Addrs()}\n\trequire.Eventually(t, func() bool { return numRelays(h) > 0 }, 10*time.Second, 100*time.Millisecond)\n}\n\nfunc TestBackoff(t *testing.T) {\n\tconst backoff = 20 * time.Second\n\tcl := newMockClock()\n\tr, err := libp2p.New(\n\t\tlibp2p.DisableRelay(),\n\t\tlibp2p.ForceReachabilityPublic(),\n\t\tlibp2p.AddrsFactory(func(addrs []ma.Multiaddr) []ma.Multiaddr {\n\t\t\tfor i, addr := range addrs {\n\t\t\t\tsaddr := addr.String()\n\t\t\t\tif strings.HasPrefix(saddr, \"/ip4/127.0.0.1/\") {\n\t\t\t\t\taddrNoIP := strings.TrimPrefix(saddr, \"/ip4/127.0.0.1\")\n\t\t\t\t\taddrs[i] = ma.StringCast(\"/dns4/localhost\" + addrNoIP)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn addrs\n\t\t}),\n\t)\n\trequire.NoError(t, err)\n\tdefer r.Close()\n\tvar reservations atomic.Int32\n\tr.SetStreamHandler(protoIDv2, func(str network.Stream) {\n\t\tdefer reservations.Add(1)\n\t\tstr.Close()\n\t})\n\n\tvar counter atomic.Int32\n\th := newPrivateNode(t,\n\t\tfunc(context.Context, int) <-chan peer.AddrInfo {\n\t\t\t// always return the same node, and make sure we don't try to connect to it too frequently\n\t\t\tcounter.Add(1)\n\t\t\tpeerChan := make(chan peer.AddrInfo, 1)\n\t\t\tpeerChan <- peer.AddrInfo{ID: r.ID(), Addrs: r.Addrs()}\n\t\t\tclose(peerChan)\n\t\t\treturn peerChan\n\t\t},\n\t\tautorelay.WithNumRelays(1),\n\t\tautorelay.WithBootDelay(0),\n\t\tautorelay.WithBackoff(backoff),\n\t\tautorelay.WithMinCandidates(1),\n\t\tautorelay.WithMaxCandidateAge(1),\n\t\tautorelay.WithClock(cl),\n\t\tautorelay.WithMinInterval(0),\n\t)\n\tdefer h.Close()\n\n\trequire.Eventually(t, func() bool {\n\t\treturn reservations.Load() == 1\n\t}, 3*time.Second, 20*time.Millisecond, \"reservations load should be 1\")\n\t// We need to wait\n\n\tcl.AdvanceBy(1) // Increment the time a little so we can make another peer source call\n\trequire.Eventually(t, func() bool {\n\t\t// The reservation will fail, and autorelay will ask the peer source for\n\t\t// more candidates.  Wait until it does so, this way we know that client\n\t\t// knows the relay connection has failed before we advance the time.\n\t\treturn counter.Load() > 1\n\t}, 2*time.Second, 100*time.Millisecond, \"counter load should be 2\")\n\n\t// make sure we don't add any relays yet\n\tfor range 2 {\n\t\tcl.AdvanceBy(backoff / 3)\n\t\trequire.Equal(t, 1, int(reservations.Load()))\n\t}\n\tcl.AdvanceBy(backoff)\n\trequire.Eventually(t, func() bool {\n\t\treturn reservations.Load() == 2\n\t}, 3*time.Second, 100*time.Millisecond, \"reservations load should be 2\")\n\trequire.Less(t, int(counter.Load()), 10) // just make sure we're not busy-looping\n\trequire.Equal(t, 2, int(reservations.Load()))\n}\n\nfunc TestStaticRelays(t *testing.T) {\n\tconst numStaticRelays = 3\n\tstaticRelays := make([]peer.AddrInfo, 0, numStaticRelays)\n\tfor range numStaticRelays {\n\t\tr := newRelay(t)\n\t\tt.Cleanup(func() { r.Close() })\n\t\tstaticRelays = append(staticRelays, peer.AddrInfo{ID: r.ID(), Addrs: r.Addrs()})\n\t}\n\n\th := newPrivateNodeWithStaticRelays(t,\n\t\tstaticRelays,\n\t\tautorelay.WithNumRelays(1),\n\t)\n\tdefer h.Close()\n\n\trequire.Eventually(t, func() bool { return numRelays(h) > 0 }, 10*time.Second, 50*time.Millisecond)\n}\n\nfunc TestConnectOnDisconnect(t *testing.T) {\n\tconst num = 3\n\tpeerChan := make(chan peer.AddrInfo, num)\n\trelays := make([]host.Host, 0, num)\n\tfor range 3 {\n\t\tr := newRelay(t)\n\t\tt.Cleanup(func() { r.Close() })\n\t\tpeerChan <- peer.AddrInfo{ID: r.ID(), Addrs: r.Addrs()}\n\t\trelays = append(relays, r)\n\t}\n\th := newPrivateNode(t,\n\t\tfunc(context.Context, int) <-chan peer.AddrInfo { return peerChan },\n\t\tautorelay.WithMinCandidates(1),\n\t\tautorelay.WithMaxCandidates(num),\n\t\tautorelay.WithNumRelays(1),\n\t\tautorelay.WithBootDelay(0),\n\t\tautorelay.WithMinInterval(time.Hour),\n\t)\n\tdefer h.Close()\n\n\trequire.Eventually(t, func() bool { return numRelays(h) > 0 }, 10*time.Second, 100*time.Millisecond)\n\trelaysInUse := usedRelays(h)\n\trequire.Len(t, relaysInUse, 1)\n\toldRelay := relaysInUse[0]\n\n\tfor _, r := range relays {\n\t\tif r.ID() == oldRelay {\n\t\t\tr.Close()\n\t\t}\n\t}\n\trequire.EventuallyWithT(t, func(collect *assert.CollectT) {\n\t\trelaysInUse = usedRelays(h)\n\t\trequire.Len(collect, relaysInUse, 1)\n\t\tassert.NotEqualf(collect, oldRelay, relaysInUse[0], \"old relay should not be used again\")\n\t}, 10*time.Second, 100*time.Millisecond)\n}\n\nfunc TestMaxAge(t *testing.T) {\n\tcl := newMockClock()\n\n\tconst num = 4\n\tpeerChan1 := make(chan peer.AddrInfo, num)\n\tpeerChan2 := make(chan peer.AddrInfo, num)\n\trelays1 := make([]host.Host, 0, num)\n\trelays2 := make([]host.Host, 0, num)\n\tfor range num {\n\t\tr1 := newRelay(t)\n\t\tt.Cleanup(func() { r1.Close() })\n\t\tpeerChan1 <- peer.AddrInfo{ID: r1.ID(), Addrs: r1.Addrs()}\n\t\trelays1 = append(relays1, r1)\n\t\tr2 := newRelay(t)\n\t\tt.Cleanup(func() { r2.Close() })\n\t\trelays2 = append(relays2, r2)\n\t}\n\tclose(peerChan1)\n\tpeerChans := make(chan chan peer.AddrInfo, 2)\n\tpeerChans <- peerChan1\n\tpeerChans <- peerChan2\n\tclose(peerChans)\n\n\th := newPrivateNode(t,\n\t\tfunc(context.Context, int) <-chan peer.AddrInfo {\n\t\t\tc, ok := <-peerChans\n\t\t\tif !ok {\n\t\t\t\tt.Fatal(\"unexpected call to PeerSource\")\n\t\t\t}\n\t\t\treturn c\n\t\t},\n\t\tautorelay.WithNumRelays(1),\n\t\tautorelay.WithMaxCandidates(100),\n\t\tautorelay.WithBootDelay(0),\n\t\tautorelay.WithMaxCandidateAge(20*time.Minute),\n\t\tautorelay.WithClock(cl),\n\t\tautorelay.WithMinInterval(30*time.Second),\n\t)\n\tdefer h.Close()\n\n\trequire.Eventually(t, func() bool {\n\t\treturn numRelays(h) > 0\n\t}, 10*time.Second, 100*time.Millisecond)\n\trelays := usedRelays(h)\n\trequire.Len(t, relays, 1)\n\n\tcl.AdvanceBy(time.Minute)\n\trequire.Eventually(t, func() bool {\n\t\treturn len(peerChans) == 0\n\t}, 10*time.Second, 100*time.Millisecond)\n\n\tcl.AdvanceBy(10 * time.Minute)\n\tfor _, r := range relays2 {\n\t\tpeerChan2 <- peer.AddrInfo{ID: r.ID(), Addrs: r.Addrs()}\n\t}\n\tcl.AdvanceBy(11 * time.Minute)\n\n\trequire.Eventually(t, func() bool {\n\t\trelays = usedRelays(h)\n\t\treturn len(relays) == 1\n\t}, 10*time.Second, 100*time.Millisecond)\n\n\t// by now the 3 relays should have been garbage collected\n\t// And we should only be using a single relay. Lets close it.\n\tvar oldRelay peer.ID\n\tfor _, r := range relays1 {\n\t\tif r.ID() == relays[0] {\n\t\t\toldRelay = r.ID()\n\t\t\tr.Close()\n\t\t}\n\t}\n\trequire.NotEmpty(t, oldRelay)\n\n\trequire.Eventually(t, func() bool {\n\t\trelays = usedRelays(h)\n\t\tif len(relays) != 1 {\n\t\t\treturn false\n\t\t}\n\t\treturn relays[0] != oldRelay\n\t}, 10*time.Second, 100*time.Millisecond)\n\n\trequire.Len(t, relays, 1)\n\tids := make([]peer.ID, 0, len(relays2))\n\tfor _, r := range relays2 {\n\t\tids = append(ids, r.ID())\n\t}\n\n\trequire.Eventually(t, func() bool {\n\t\tif slices.Contains(ids, relays[0]) {\n\t\t\treturn true\n\t\t}\n\t\tfmt.Println(\"waiting for\", ids, \"to contain\", relays[0])\n\t\treturn false\n\t}, 3*time.Second, 100*time.Millisecond)\n\trequire.Contains(t, ids, relays[0])\n}\n\nfunc TestReconnectToStaticRelays(t *testing.T) {\n\tcl := newMockClock()\n\tconst numStaticRelays = 1\n\tstaticRelays := make([]peer.AddrInfo, 0, numStaticRelays)\n\trelays := make([]host.Host, 0, numStaticRelays)\n\tfor range numStaticRelays {\n\t\tr := newRelay(t)\n\t\tt.Cleanup(func() { r.Close() })\n\t\trelays = append(relays, r)\n\t\tstaticRelays = append(staticRelays, peer.AddrInfo{ID: r.ID(), Addrs: r.Addrs()})\n\t}\n\n\th := newPrivateNodeWithStaticRelays(t,\n\t\tstaticRelays,\n\t\tautorelay.WithClock(cl),\n\t\tautorelay.WithBackoff(30*time.Minute),\n\t)\n\tdefer h.Close()\n\n\tcl.AdvanceBy(time.Minute)\n\trequire.Eventually(t, func() bool {\n\t\treturn numRelays(h) == 1\n\t}, 10*time.Second, 100*time.Millisecond)\n\n\trelaysInUse := usedRelays(h)\n\toldRelay := relaysInUse[0]\n\tfor _, r := range relays {\n\t\tif r.ID() == oldRelay {\n\t\t\tr.Network().ClosePeer(h.ID())\n\t\t}\n\t}\n\trequire.Eventually(t, func() bool {\n\t\treturn numRelays(h) == 0\n\t}, 10*time.Second, 100*time.Millisecond)\n\n\tcl.AdvanceBy(time.Hour)\n\trequire.Eventually(t, func() bool {\n\t\treturn numRelays(h) == 1\n\t}, 10*time.Second, 100*time.Millisecond)\n}\n\nfunc TestMinInterval(t *testing.T) {\n\tcl := newMockClock()\n\th := newPrivateNode(t,\n\t\tfunc(context.Context, int) <-chan peer.AddrInfo {\n\t\t\tpeerChan := make(chan peer.AddrInfo, 1)\n\t\t\tdefer close(peerChan)\n\t\t\tr1 := newRelay(t)\n\t\t\tt.Cleanup(func() { r1.Close() })\n\t\t\tpeerChan <- peer.AddrInfo{ID: r1.ID(), Addrs: r1.Addrs()}\n\t\t\treturn peerChan\n\t\t},\n\t\tautorelay.WithClock(cl),\n\t\tautorelay.WithMinCandidates(2),\n\t\tautorelay.WithNumRelays(1),\n\t\tautorelay.WithBootDelay(time.Hour),\n\t\tautorelay.WithMinInterval(500*time.Millisecond),\n\t)\n\tdefer h.Close()\n\n\tcl.AdvanceBy(400 * time.Millisecond)\n\t// The second call to peerSource should happen after 1 second\n\trequire.Never(t, func() bool { return numRelays(h) > 0 }, 500*time.Millisecond, 100*time.Millisecond)\n\tcl.AdvanceBy(600 * time.Millisecond)\n\trequire.Eventually(t, func() bool { return numRelays(h) > 0 }, 3*time.Second, 100*time.Millisecond)\n}\n\nfunc TestNoBusyLoop0MinInterval(t *testing.T) {\n\tvar calledTimes uint64\n\tcl := newMockClock()\n\th := newPrivateNode(t,\n\t\tfunc(context.Context, int) <-chan peer.AddrInfo {\n\t\t\tatomic.AddUint64(&calledTimes, 1)\n\t\t\tpeerChan := make(chan peer.AddrInfo, 1)\n\t\t\tdefer close(peerChan)\n\t\t\tr1 := newRelay(t)\n\t\t\tt.Cleanup(func() { r1.Close() })\n\t\t\tpeerChan <- peer.AddrInfo{ID: r1.ID(), Addrs: r1.Addrs()}\n\t\t\treturn peerChan\n\t\t},\n\t\tautorelay.WithClock(cl),\n\t\tautorelay.WithMinCandidates(1),\n\t\tautorelay.WithMaxCandidates(1),\n\t\tautorelay.WithNumRelays(0),\n\t\tautorelay.WithBootDelay(time.Hour),\n\t\tautorelay.WithMinInterval(time.Millisecond),\n\t)\n\tdefer h.Close()\n\n\trequire.Never(t, func() bool {\n\t\tcl.AdvanceBy(time.Second)\n\t\tval := atomic.LoadUint64(&calledTimes)\n\t\treturn val >= 2\n\t}, 500*time.Millisecond, 100*time.Millisecond)\n\tval := atomic.LoadUint64(&calledTimes)\n\trequire.Less(t, val, uint64(2))\n}\nfunc TestAutoRelayAddrsEvent(t *testing.T) {\n\tcl := newMockClock()\n\trelays := []host.Host{newRelay(t), newRelay(t), newRelay(t), newRelay(t), newRelay(t)}\n\tt.Cleanup(func() {\n\t\tfor _, r := range relays {\n\t\t\tr.Close()\n\t\t}\n\t})\n\n\trelayIDFromP2PAddr := func(a ma.Multiaddr) peer.ID {\n\t\tr, c := ma.SplitLast(a)\n\t\tif c.Protocol().Code != ma.P_CIRCUIT {\n\t\t\treturn \"\"\n\t\t}\n\t\tif id, err := peer.IDFromP2PAddr(r); err == nil {\n\t\t\treturn id\n\t\t}\n\t\treturn \"\"\n\t}\n\n\tcheckAddrsContainsPeersAsRelay := func(addrs []ma.Multiaddr, peers ...peer.ID) bool {\n\t\tfor _, p := range peers {\n\t\t\tif !slices.ContainsFunc(addrs, func(a ma.Multiaddr) bool { return relayIDFromP2PAddr(a) == p }) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\t}\n\tpeerChan := make(chan peer.AddrInfo, 5)\n\th := newPrivateNode(t,\n\t\tfunc(context.Context, int) <-chan peer.AddrInfo {\n\t\t\treturn peerChan\n\t\t},\n\t\tautorelay.WithClock(cl),\n\t\tautorelay.WithMinCandidates(1),\n\t\tautorelay.WithMaxCandidates(10),\n\t\tautorelay.WithNumRelays(5),\n\t\tautorelay.WithBootDelay(1*time.Second),\n\t\tautorelay.WithMinInterval(time.Hour),\n\t)\n\tdefer h.Close()\n\n\tsub, err := h.EventBus().Subscribe(new(event.EvtAutoRelayAddrsUpdated))\n\trequire.NoError(t, err)\n\n\tpeerChan <- peer.AddrInfo{ID: relays[0].ID(), Addrs: relays[0].Addrs()}\n\tcl.AdvanceBy(time.Second)\n\n\trequire.EventuallyWithT(t, func(collect *assert.CollectT) {\n\t\te := <-sub.Out()\n\t\tevt := e.(event.EvtAutoRelayAddrsUpdated)\n\t\tif !checkAddrsContainsPeersAsRelay(evt.RelayAddrs, relays[0].ID()) {\n\t\t\tcollect.Errorf(\"expected %s to be in %v\", relays[0].ID(), evt.RelayAddrs)\n\t\t}\n\t\tif checkAddrsContainsPeersAsRelay(evt.RelayAddrs, relays[1].ID()) {\n\t\t\tcollect.Errorf(\"expected %s to not be in %v\", relays[1].ID(), evt.RelayAddrs)\n\t\t}\n\t}, 5*time.Second, 50*time.Millisecond)\n\tfor _, r := range relays[1:] {\n\t\tpeerChan <- peer.AddrInfo{ID: r.ID(), Addrs: r.Addrs()}\n\t}\n\trequire.EventuallyWithT(t, func(c *assert.CollectT) {\n\t\te := <-sub.Out()\n\t\tevt := e.(event.EvtAutoRelayAddrsUpdated)\n\t\trelayIds := []peer.ID{}\n\t\tfor _, r := range relays[1:] {\n\t\t\trelayIds = append(relayIds, r.ID())\n\t\t}\n\t\tif !checkAddrsContainsPeersAsRelay(evt.RelayAddrs, relayIds...) {\n\t\t\tc.Errorf(\"expected %s to be in %v\", relayIds, evt.RelayAddrs)\n\t\t}\n\t}, 5*time.Second, 50*time.Millisecond)\n\tselect {\n\tcase e := <-sub.Out():\n\t\tt.Fatal(\"expected no more events after all reservations obtained; got: \", e.(event.EvtAutoRelayAddrsUpdated))\n\tcase <-time.After(1 * time.Second):\n\t}\n}\n"
  },
  {
    "path": "p2p/host/autorelay/metrics.go",
    "content": "package autorelay\n\nimport (\n\t\"errors\"\n\n\t\"github.com/libp2p/go-libp2p/p2p/metricshelper\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/client\"\n\tpbv2 \"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/pb\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n)\n\nconst metricNamespace = \"libp2p_autorelay\"\n\nvar (\n\tstatus = prometheus.NewGauge(prometheus.GaugeOpts{\n\t\tNamespace: metricNamespace,\n\t\tName:      \"status\",\n\t\tHelp:      \"relay finder active\",\n\t})\n\treservationsOpenedTotal = prometheus.NewCounter(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"reservations_opened_total\",\n\t\t\tHelp:      \"Reservations Opened\",\n\t\t},\n\t)\n\treservationsClosedTotal = prometheus.NewCounter(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"reservations_closed_total\",\n\t\t\tHelp:      \"Reservations Closed\",\n\t\t},\n\t)\n\treservationRequestsOutcomeTotal = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"reservation_requests_outcome_total\",\n\t\t\tHelp:      \"Reservation Request Outcome\",\n\t\t},\n\t\t[]string{\"request_type\", \"outcome\"},\n\t)\n\n\trelayAddressesUpdatedTotal = prometheus.NewCounter(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"relay_addresses_updated_total\",\n\t\t\tHelp:      \"Relay Addresses Updated Count\",\n\t\t},\n\t)\n\trelayAddressesCount = prometheus.NewGauge(\n\t\tprometheus.GaugeOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"relay_addresses_count\",\n\t\t\tHelp:      \"Relay Addresses Count\",\n\t\t},\n\t)\n\n\tcandidatesCircuitV2SupportTotal = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"candidates_circuit_v2_support_total\",\n\t\t\tHelp:      \"Candidates supporting circuit v2\",\n\t\t},\n\t\t[]string{\"support\"},\n\t)\n\tcandidatesTotal = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"candidates_total\",\n\t\t\tHelp:      \"Candidates Total\",\n\t\t},\n\t\t[]string{\"type\"},\n\t)\n\tcandLoopState = prometheus.NewGauge(\n\t\tprometheus.GaugeOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"candidate_loop_state\",\n\t\t\tHelp:      \"Candidate Loop State\",\n\t\t},\n\t)\n\n\tscheduledWorkTime = prometheus.NewGaugeVec(\n\t\tprometheus.GaugeOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"scheduled_work_time\",\n\t\t\tHelp:      \"Scheduled Work Times\",\n\t\t},\n\t\t[]string{\"work_type\"},\n\t)\n\n\tdesiredReservations = prometheus.NewGauge(\n\t\tprometheus.GaugeOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"desired_reservations\",\n\t\t\tHelp:      \"Desired Reservations\",\n\t\t},\n\t)\n\n\tcollectors = []prometheus.Collector{\n\t\tstatus,\n\t\treservationsOpenedTotal,\n\t\treservationsClosedTotal,\n\t\treservationRequestsOutcomeTotal,\n\t\trelayAddressesUpdatedTotal,\n\t\trelayAddressesCount,\n\t\tcandidatesCircuitV2SupportTotal,\n\t\tcandidatesTotal,\n\t\tcandLoopState,\n\t\tscheduledWorkTime,\n\t\tdesiredReservations,\n\t}\n)\n\ntype candidateLoopState int\n\nconst (\n\tpeerSourceRateLimited candidateLoopState = iota\n\twaitingOnPeerChan\n\twaitingForTrigger\n\tstopped\n)\n\n// MetricsTracer is the interface for tracking metrics for autorelay\ntype MetricsTracer interface {\n\tRelayFinderStatus(isActive bool)\n\n\tReservationEnded(cnt int)\n\tReservationOpened(cnt int)\n\tReservationRequestFinished(isRefresh bool, err error)\n\n\tRelayAddressCount(int)\n\tRelayAddressUpdated()\n\n\tCandidateChecked(supportsCircuitV2 bool)\n\tCandidateAdded(cnt int)\n\tCandidateRemoved(cnt int)\n\tCandidateLoopState(state candidateLoopState)\n\n\tScheduledWorkUpdated(scheduledWork *scheduledWorkTimes)\n\n\tDesiredReservations(int)\n}\n\ntype metricsTracer struct{}\n\nvar _ MetricsTracer = &metricsTracer{}\n\ntype metricsTracerSetting struct {\n\treg prometheus.Registerer\n}\n\ntype MetricsTracerOption func(*metricsTracerSetting)\n\nfunc WithRegisterer(reg prometheus.Registerer) MetricsTracerOption {\n\treturn func(s *metricsTracerSetting) {\n\t\tif reg != nil {\n\t\t\ts.reg = reg\n\t\t}\n\t}\n}\n\nfunc NewMetricsTracer(opts ...MetricsTracerOption) MetricsTracer {\n\tsetting := &metricsTracerSetting{reg: prometheus.DefaultRegisterer}\n\tfor _, opt := range opts {\n\t\topt(setting)\n\t}\n\tmetricshelper.RegisterCollectors(setting.reg, collectors...)\n\n\t// Initialise these counters to 0 otherwise the first reservation requests aren't handled\n\t// correctly when using promql increase function\n\treservationRequestsOutcomeTotal.WithLabelValues(\"refresh\", \"success\")\n\treservationRequestsOutcomeTotal.WithLabelValues(\"new\", \"success\")\n\tcandidatesCircuitV2SupportTotal.WithLabelValues(\"yes\")\n\tcandidatesCircuitV2SupportTotal.WithLabelValues(\"no\")\n\treturn &metricsTracer{}\n}\n\nfunc (mt *metricsTracer) RelayFinderStatus(isActive bool) {\n\tif isActive {\n\t\tstatus.Set(1)\n\t} else {\n\t\tstatus.Set(0)\n\t}\n}\n\nfunc (mt *metricsTracer) ReservationEnded(cnt int) {\n\treservationsClosedTotal.Add(float64(cnt))\n}\n\nfunc (mt *metricsTracer) ReservationOpened(cnt int) {\n\treservationsOpenedTotal.Add(float64(cnt))\n}\n\nfunc (mt *metricsTracer) ReservationRequestFinished(isRefresh bool, err error) {\n\ttags := metricshelper.GetStringSlice()\n\tdefer metricshelper.PutStringSlice(tags)\n\n\tif isRefresh {\n\t\t*tags = append(*tags, \"refresh\")\n\t} else {\n\t\t*tags = append(*tags, \"new\")\n\t}\n\t*tags = append(*tags, getReservationRequestStatus(err))\n\treservationRequestsOutcomeTotal.WithLabelValues(*tags...).Inc()\n\n\tif !isRefresh && err == nil {\n\t\treservationsOpenedTotal.Inc()\n\t}\n}\n\nfunc (mt *metricsTracer) RelayAddressUpdated() {\n\trelayAddressesUpdatedTotal.Inc()\n}\n\nfunc (mt *metricsTracer) RelayAddressCount(cnt int) {\n\trelayAddressesCount.Set(float64(cnt))\n}\n\nfunc (mt *metricsTracer) CandidateChecked(supportsCircuitV2 bool) {\n\ttags := metricshelper.GetStringSlice()\n\tdefer metricshelper.PutStringSlice(tags)\n\tif supportsCircuitV2 {\n\t\t*tags = append(*tags, \"yes\")\n\t} else {\n\t\t*tags = append(*tags, \"no\")\n\t}\n\tcandidatesCircuitV2SupportTotal.WithLabelValues(*tags...).Inc()\n}\n\nfunc (mt *metricsTracer) CandidateAdded(cnt int) {\n\ttags := metricshelper.GetStringSlice()\n\tdefer metricshelper.PutStringSlice(tags)\n\t*tags = append(*tags, \"added\")\n\tcandidatesTotal.WithLabelValues(*tags...).Add(float64(cnt))\n}\n\nfunc (mt *metricsTracer) CandidateRemoved(cnt int) {\n\ttags := metricshelper.GetStringSlice()\n\tdefer metricshelper.PutStringSlice(tags)\n\t*tags = append(*tags, \"removed\")\n\tcandidatesTotal.WithLabelValues(*tags...).Add(float64(cnt))\n}\n\nfunc (mt *metricsTracer) CandidateLoopState(state candidateLoopState) {\n\tcandLoopState.Set(float64(state))\n}\n\nfunc (mt *metricsTracer) ScheduledWorkUpdated(scheduledWork *scheduledWorkTimes) {\n\ttags := metricshelper.GetStringSlice()\n\tdefer metricshelper.PutStringSlice(tags)\n\n\t*tags = append(*tags, \"allowed peer source call\")\n\tscheduledWorkTime.WithLabelValues(*tags...).Set(float64(scheduledWork.nextAllowedCallToPeerSource.Unix()))\n\t*tags = (*tags)[:0]\n\n\t*tags = append(*tags, \"reservation refresh\")\n\tscheduledWorkTime.WithLabelValues(*tags...).Set(float64(scheduledWork.nextRefresh.Unix()))\n\t*tags = (*tags)[:0]\n\n\t*tags = append(*tags, \"clear backoff\")\n\tscheduledWorkTime.WithLabelValues(*tags...).Set(float64(scheduledWork.nextBackoff.Unix()))\n\t*tags = (*tags)[:0]\n\n\t*tags = append(*tags, \"old candidate check\")\n\tscheduledWorkTime.WithLabelValues(*tags...).Set(float64(scheduledWork.nextOldCandidateCheck.Unix()))\n}\n\nfunc (mt *metricsTracer) DesiredReservations(cnt int) {\n\tdesiredReservations.Set(float64(cnt))\n}\n\nfunc getReservationRequestStatus(err error) string {\n\tif err == nil {\n\t\treturn \"success\"\n\t}\n\n\tstatus := \"err other\"\n\tvar re client.ReservationError\n\tif errors.As(err, &re) {\n\t\tswitch re.Status {\n\t\tcase pbv2.Status_CONNECTION_FAILED:\n\t\t\treturn \"connection failed\"\n\t\tcase pbv2.Status_MALFORMED_MESSAGE:\n\t\t\treturn \"malformed message\"\n\t\tcase pbv2.Status_RESERVATION_REFUSED:\n\t\t\treturn \"reservation refused\"\n\t\tcase pbv2.Status_PERMISSION_DENIED:\n\t\t\treturn \"permission denied\"\n\t\tcase pbv2.Status_RESOURCE_LIMIT_EXCEEDED:\n\t\t\treturn \"resource limit exceeded\"\n\t\t}\n\t}\n\treturn status\n}\n\n// wrappedMetricsTracer wraps MetricsTracer and ignores all calls when mt is nil\ntype wrappedMetricsTracer struct {\n\tmt MetricsTracer\n}\n\nvar _ MetricsTracer = &wrappedMetricsTracer{}\n\nfunc (mt *wrappedMetricsTracer) RelayFinderStatus(isActive bool) {\n\tif mt.mt != nil {\n\t\tmt.mt.RelayFinderStatus(isActive)\n\t}\n}\n\nfunc (mt *wrappedMetricsTracer) ReservationEnded(cnt int) {\n\tif mt.mt != nil {\n\t\tmt.mt.ReservationEnded(cnt)\n\t}\n}\n\nfunc (mt *wrappedMetricsTracer) ReservationOpened(cnt int) {\n\tif mt.mt != nil {\n\t\tmt.mt.ReservationOpened(cnt)\n\t}\n}\n\nfunc (mt *wrappedMetricsTracer) ReservationRequestFinished(isRefresh bool, err error) {\n\tif mt.mt != nil {\n\t\tmt.mt.ReservationRequestFinished(isRefresh, err)\n\t}\n}\n\nfunc (mt *wrappedMetricsTracer) RelayAddressUpdated() {\n\tif mt.mt != nil {\n\t\tmt.mt.RelayAddressUpdated()\n\t}\n}\n\nfunc (mt *wrappedMetricsTracer) RelayAddressCount(cnt int) {\n\tif mt.mt != nil {\n\t\tmt.mt.RelayAddressCount(cnt)\n\t}\n}\n\nfunc (mt *wrappedMetricsTracer) CandidateChecked(supportsCircuitV2 bool) {\n\tif mt.mt != nil {\n\t\tmt.mt.CandidateChecked(supportsCircuitV2)\n\t}\n}\n\nfunc (mt *wrappedMetricsTracer) CandidateAdded(cnt int) {\n\tif mt.mt != nil {\n\t\tmt.mt.CandidateAdded(cnt)\n\t}\n}\n\nfunc (mt *wrappedMetricsTracer) CandidateRemoved(cnt int) {\n\tif mt.mt != nil {\n\t\tmt.mt.CandidateRemoved(cnt)\n\t}\n}\n\nfunc (mt *wrappedMetricsTracer) ScheduledWorkUpdated(scheduledWork *scheduledWorkTimes) {\n\tif mt.mt != nil {\n\t\tmt.mt.ScheduledWorkUpdated(scheduledWork)\n\t}\n}\n\nfunc (mt *wrappedMetricsTracer) DesiredReservations(cnt int) {\n\tif mt.mt != nil {\n\t\tmt.mt.DesiredReservations(cnt)\n\t}\n}\n\nfunc (mt *wrappedMetricsTracer) CandidateLoopState(state candidateLoopState) {\n\tif mt.mt != nil {\n\t\tmt.mt.CandidateLoopState(state)\n\t}\n}\n"
  },
  {
    "path": "p2p/host/autorelay/metrics_noalloc_test.go",
    "content": "//go:build nocover\n\npackage autorelay\n\nimport (\n\t\"math/rand\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/client\"\n\tpbv2 \"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/pb\"\n)\n\nfunc getRandScheduledWork() scheduledWorkTimes {\n\trandTime := func() time.Time {\n\t\treturn time.Now().Add(time.Duration(rand.Intn(10)) * time.Second)\n\t}\n\treturn scheduledWorkTimes{\n\t\tleastFrequentInterval:       0,\n\t\tnextRefresh:                 randTime(),\n\t\tnextBackoff:                 randTime(),\n\t\tnextOldCandidateCheck:       randTime(),\n\t\tnextAllowedCallToPeerSource: randTime(),\n\t}\n}\n\nfunc TestMetricsNoAllocNoCover(t *testing.T) {\n\tscheduledWork := []scheduledWorkTimes{}\n\tfor i := 0; i < 10; i++ {\n\t\tscheduledWork = append(scheduledWork, getRandScheduledWork())\n\t}\n\terrs := []error{\n\t\tclient.ReservationError{Status: pbv2.Status_MALFORMED_MESSAGE},\n\t\tclient.ReservationError{Status: pbv2.Status_MALFORMED_MESSAGE},\n\t\tnil,\n\t}\n\ttr := NewMetricsTracer()\n\ttests := map[string]func(){\n\t\t\"RelayFinderStatus\":          func() { tr.RelayFinderStatus(rand.Intn(2) == 1) },\n\t\t\"ReservationEnded\":           func() { tr.ReservationEnded(rand.Intn(10)) },\n\t\t\"ReservationRequestFinished\": func() { tr.ReservationRequestFinished(rand.Intn(2) == 1, errs[rand.Intn(len(errs))]) },\n\t\t\"RelayAddressCount\":          func() { tr.RelayAddressCount(rand.Intn(10)) },\n\t\t\"RelayAddressUpdated\":        func() { tr.RelayAddressUpdated() },\n\t\t\"ReservationOpened\":          func() { tr.ReservationOpened(rand.Intn(10)) },\n\t\t\"CandidateChecked\":           func() { tr.CandidateChecked(rand.Intn(2) == 1) },\n\t\t\"CandidateAdded\":             func() { tr.CandidateAdded(rand.Intn(10)) },\n\t\t\"CandidateRemoved\":           func() { tr.CandidateRemoved(rand.Intn(10)) },\n\t\t\"ScheduledWorkUpdated\":       func() { tr.ScheduledWorkUpdated(&scheduledWork[rand.Intn(len(scheduledWork))]) },\n\t\t\"DesiredReservations\":        func() { tr.DesiredReservations(rand.Intn(10)) },\n\t\t\"CandidateLoopState\":         func() { tr.CandidateLoopState(candidateLoopState(rand.Intn(10))) },\n\t}\n\tfor method, f := range tests {\n\t\tallocs := testing.AllocsPerRun(1000, f)\n\n\t\tif allocs > 0 {\n\t\t\tt.Fatalf(\"Alloc Test: %s, got: %0.2f, expected: 0 allocs\", method, allocs)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "p2p/host/autorelay/options.go",
    "content": "package autorelay\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n)\n\n// AutoRelay will call this function when it needs new candidates because it is\n// not connected to the desired number of relays or we get disconnected from one\n// of the relays. Implementations must send *at most* numPeers, and close the\n// channel when they don't intend to provide any more peers. AutoRelay will not\n// call the callback again until the channel is closed. Implementations should\n// send new peers, but may send peers they sent before. AutoRelay implements a\n// per-peer backoff (see WithBackoff). See WithMinInterval for setting the\n// minimum interval between calls to the callback. The context.Context passed\n// may be canceled when AutoRelay feels satisfied, it will be canceled when the\n// node is shutting down. If the context is canceled you MUST close the output\n// channel at some point.\ntype PeerSource func(ctx context.Context, num int) <-chan peer.AddrInfo\n\ntype config struct {\n\tclock      ClockWithInstantTimer\n\tpeerSource PeerSource\n\t// minimum interval used to call the peerSource callback\n\tminInterval time.Duration\n\t// see WithMinCandidates\n\tminCandidates int\n\t// see WithMaxCandidates\n\tmaxCandidates int\n\t// Delay until we obtain reservations with relays, if we have less than minCandidates candidates.\n\t// See WithBootDelay.\n\tbootDelay time.Duration\n\t// backoff is the time we wait after failing to obtain a reservation with a candidate\n\tbackoff time.Duration\n\t// Number of relays we strive to obtain a reservation with.\n\tdesiredRelays int\n\t// see WithMaxCandidateAge\n\tmaxCandidateAge  time.Duration\n\tsetMinCandidates bool\n\t// see WithMetricsTracer\n\tmetricsTracer MetricsTracer\n}\n\nvar defaultConfig = config{\n\tclock:           RealClock{},\n\tminCandidates:   4,\n\tmaxCandidates:   20,\n\tbootDelay:       3 * time.Minute,\n\tbackoff:         time.Hour,\n\tdesiredRelays:   2,\n\tmaxCandidateAge: 30 * time.Minute,\n\tminInterval:     30 * time.Second,\n}\n\nvar (\n\terrAlreadyHavePeerSource = errors.New(\"can only use a single WithPeerSource or WithStaticRelays\")\n)\n\ntype Option func(*config) error\n\nfunc WithStaticRelays(static []peer.AddrInfo) Option {\n\treturn func(c *config) error {\n\t\tif c.peerSource != nil {\n\t\t\treturn errAlreadyHavePeerSource\n\t\t}\n\n\t\tWithPeerSource(func(_ context.Context, numPeers int) <-chan peer.AddrInfo {\n\t\t\tif len(static) < numPeers {\n\t\t\t\tnumPeers = len(static)\n\t\t\t}\n\t\t\tc := make(chan peer.AddrInfo, numPeers)\n\t\t\tdefer close(c)\n\n\t\t\tfor i := 0; i < numPeers; i++ {\n\t\t\t\tc <- static[i]\n\t\t\t}\n\t\t\treturn c\n\t\t})(c)\n\t\tWithMinCandidates(len(static))(c)\n\t\tWithMaxCandidates(len(static))(c)\n\t\tWithNumRelays(len(static))(c)\n\n\t\treturn nil\n\t}\n}\n\n// WithPeerSource defines a callback for AutoRelay to query for more relay candidates.\nfunc WithPeerSource(f PeerSource) Option {\n\treturn func(c *config) error {\n\t\tif c.peerSource != nil {\n\t\t\treturn errAlreadyHavePeerSource\n\t\t}\n\t\tc.peerSource = f\n\t\treturn nil\n\t}\n}\n\n// WithNumRelays sets the number of relays we strive to obtain reservations with.\nfunc WithNumRelays(n int) Option {\n\treturn func(c *config) error {\n\t\tc.desiredRelays = n\n\t\treturn nil\n\t}\n}\n\n// WithMaxCandidates sets the number of relay candidates that we buffer.\nfunc WithMaxCandidates(n int) Option {\n\treturn func(c *config) error {\n\t\tc.maxCandidates = n\n\t\tif c.minCandidates > n {\n\t\t\tc.minCandidates = n\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// WithMinCandidates sets the minimum number of relay candidates we collect before to get a reservation\n// with any of them (unless we've been running for longer than the boot delay).\n// This is to make sure that we don't just randomly connect to the first candidate that we discover.\nfunc WithMinCandidates(n int) Option {\n\treturn func(c *config) error {\n\t\tif n > c.maxCandidates {\n\t\t\tn = c.maxCandidates\n\t\t}\n\t\tc.minCandidates = n\n\t\tc.setMinCandidates = true\n\t\treturn nil\n\t}\n}\n\n// WithBootDelay set the boot delay for finding relays.\n// We won't attempt any reservation if we've have less than a minimum number of candidates.\n// This prevents us to connect to the \"first best\" relay, and allows us to carefully select the relay.\n// However, in case we haven't found enough relays after the boot delay, we use what we have.\nfunc WithBootDelay(d time.Duration) Option {\n\treturn func(c *config) error {\n\t\tc.bootDelay = d\n\t\treturn nil\n\t}\n}\n\n// WithBackoff sets the time we wait after failing to obtain a reservation with a candidate.\nfunc WithBackoff(d time.Duration) Option {\n\treturn func(c *config) error {\n\t\tc.backoff = d\n\t\treturn nil\n\t}\n}\n\n// WithMaxCandidateAge sets the maximum age of a candidate.\n// When we are connected to the desired number of relays, we don't ask the peer source for new candidates.\n// This can lead to AutoRelay's candidate list becoming outdated, and means we won't be able\n// to quickly establish a new relay connection if our existing connection breaks, if all the candidates\n// have become stale.\nfunc WithMaxCandidateAge(d time.Duration) Option {\n\treturn func(c *config) error {\n\t\tc.maxCandidateAge = d\n\t\treturn nil\n\t}\n}\n\n// InstantTimer is a timer that triggers at some instant rather than some duration\ntype InstantTimer interface {\n\tReset(d time.Time) bool\n\tStop() bool\n\tCh() <-chan time.Time\n}\n\n// ClockWithInstantTimer is a clock that can create timers that trigger at some\n// instant rather than some duration\ntype ClockWithInstantTimer interface {\n\tNow() time.Time\n\tSince(t time.Time) time.Duration\n\tInstantTimer(when time.Time) InstantTimer\n}\n\ntype RealTimer struct{ t *time.Timer }\n\nvar _ InstantTimer = (*RealTimer)(nil)\n\nfunc (t RealTimer) Ch() <-chan time.Time {\n\treturn t.t.C\n}\n\nfunc (t RealTimer) Reset(d time.Time) bool {\n\treturn t.t.Reset(time.Until(d))\n}\n\nfunc (t RealTimer) Stop() bool {\n\treturn t.t.Stop()\n}\n\ntype RealClock struct{}\n\nvar _ ClockWithInstantTimer = RealClock{}\n\nfunc (RealClock) Now() time.Time {\n\treturn time.Now()\n}\nfunc (RealClock) Since(t time.Time) time.Duration {\n\treturn time.Since(t)\n}\nfunc (RealClock) InstantTimer(when time.Time) InstantTimer {\n\tt := time.NewTimer(time.Until(when))\n\treturn &RealTimer{t}\n}\n\nfunc WithClock(cl ClockWithInstantTimer) Option {\n\treturn func(c *config) error {\n\t\tc.clock = cl\n\t\treturn nil\n\t}\n}\n\n// WithMinInterval sets the minimum interval after which peerSource callback will be called for more\n// candidates even if AutoRelay needs new candidates.\nfunc WithMinInterval(interval time.Duration) Option {\n\treturn func(c *config) error {\n\t\tc.minInterval = interval\n\t\treturn nil\n\t}\n}\n\n// WithMetricsTracer configures autorelay to use mt to track metrics\nfunc WithMetricsTracer(mt MetricsTracer) Option {\n\treturn func(c *config) error {\n\t\tc.metricsTracer = mt\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "p2p/host/autorelay/relay_finder.go",
    "content": "package autorelay\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"slices\"\n\t\"sync\"\n\t\"time\"\n\n\t\"golang.org/x/sync/errgroup\"\n\n\t\"github.com/libp2p/go-libp2p/core/event\"\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/eventbus\"\n\tcircuitv2 \"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/client\"\n\tcircuitv2_proto \"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/proto\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/identify\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\nconst protoIDv2 = circuitv2_proto.ProtoIDv2Hop\n\n// Terminology:\n// Candidate: Once we connect to a node and it supports relay protocol,\n// we call it a candidate, and consider using it as a relay.\n//\n// Relay: Out of the list candidates, the ones we have a reservation with.\n// Currently, we just randomly select a candidate, but we can employ more sophisticated\n// selection strategies here (e.g. by facotring in the RTT).\n\nconst (\n\trsvpRefreshInterval = time.Minute\n\trsvpExpirationSlack = 2 * time.Minute\n\n\tautorelayTag  = \"autorelay\"\n\tmaxRelayAddrs = 100\n)\n\ntype candidate struct {\n\tadded           time.Time\n\tsupportsRelayV2 bool\n\tai              peer.AddrInfo\n}\n\n// relayFinder is a Host that uses relays for connectivity when a NAT is detected.\ntype relayFinder struct {\n\tbootTime time.Time\n\thost     host.Host\n\n\tconf *config\n\n\trefCount sync.WaitGroup\n\n\tctxCancel   context.CancelFunc\n\tctxCancelMx sync.Mutex\n\n\tpeerSource PeerSource\n\n\tcandidateFound             chan struct{} // receives every time we find a new relay candidate\n\tcandidateMx                sync.Mutex\n\tcandidates                 map[peer.ID]*candidate\n\tbackoff                    map[peer.ID]time.Time\n\tmaybeConnectToRelayTrigger chan struct{} // cap: 1\n\t// Any time _something_ happens that might cause us to need new candidates.\n\t// This could be\n\t// * the disconnection of a relay\n\t// * the failed attempt to obtain a reservation with a current candidate\n\t// * a candidate is deleted due to its age\n\tmaybeRequestNewCandidates chan struct{} // cap: 1.\n\n\trelayReservationUpdated chan struct{}\n\n\trelayMx sync.Mutex\n\trelays  map[peer.ID]*circuitv2.Reservation\n\n\tcircuitAddrs []ma.Multiaddr\n\n\t// A channel that triggers a run of `runScheduledWork`.\n\ttriggerRunScheduledWork chan struct{}\n\tmetricsTracer           MetricsTracer\n\n\temitter event.Emitter\n}\n\nvar errAlreadyRunning = errors.New(\"relayFinder already running\")\n\nfunc newRelayFinder(host host.Host, conf *config) (*relayFinder, error) {\n\tif conf.peerSource == nil {\n\t\tpanic(\"Can not create a new relayFinder. Need a Peer Source fn or a list of static relays. Refer to the documentation around `libp2p.EnableAutoRelay`\")\n\t}\n\n\temitter, err := host.EventBus().Emitter(new(event.EvtAutoRelayAddrsUpdated), eventbus.Stateful)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &relayFinder{\n\t\tbootTime:                   conf.clock.Now(),\n\t\thost:                       host,\n\t\tconf:                       conf,\n\t\tpeerSource:                 conf.peerSource,\n\t\tcandidates:                 make(map[peer.ID]*candidate),\n\t\tbackoff:                    make(map[peer.ID]time.Time),\n\t\tcandidateFound:             make(chan struct{}, 1),\n\t\tmaybeConnectToRelayTrigger: make(chan struct{}, 1),\n\t\tmaybeRequestNewCandidates:  make(chan struct{}, 1),\n\t\ttriggerRunScheduledWork:    make(chan struct{}, 1),\n\t\trelays:                     make(map[peer.ID]*circuitv2.Reservation),\n\t\trelayReservationUpdated:    make(chan struct{}, 1),\n\t\tmetricsTracer:              &wrappedMetricsTracer{conf.metricsTracer},\n\t\temitter:                    emitter,\n\t}, nil\n}\n\ntype scheduledWorkTimes struct {\n\tleastFrequentInterval       time.Duration\n\tnextRefresh                 time.Time\n\tnextBackoff                 time.Time\n\tnextOldCandidateCheck       time.Time\n\tnextAllowedCallToPeerSource time.Time\n}\n\nfunc (rf *relayFinder) cleanupDisconnectedPeers(ctx context.Context) {\n\tsubConnectedness, err := rf.host.EventBus().Subscribe(new(event.EvtPeerConnectednessChanged), eventbus.Name(\"autorelay (relay finder)\"), eventbus.BufSize(32))\n\tif err != nil {\n\t\tlog.Error(\"failed to subscribe to the EvtPeerConnectednessChanged\")\n\t\treturn\n\t}\n\tdefer subConnectedness.Close()\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase ev, ok := <-subConnectedness.Out():\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevt := ev.(event.EvtPeerConnectednessChanged)\n\t\t\tif evt.Connectedness != network.NotConnected {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tpush := false\n\n\t\t\trf.relayMx.Lock()\n\t\t\tif rf.usingRelay(evt.Peer) { // we were disconnected from a relay\n\t\t\t\tlog.Debug(\"disconnected from relay\", \"peer\", evt.Peer)\n\t\t\t\tdelete(rf.relays, evt.Peer)\n\t\t\t\trf.notifyMaybeConnectToRelay()\n\t\t\t\trf.notifyMaybeNeedNewCandidates()\n\t\t\t\tpush = true\n\t\t\t}\n\t\t\trf.relayMx.Unlock()\n\n\t\t\tif push {\n\t\t\t\trf.notifyRelayReservationUpdated()\n\t\t\t\trf.metricsTracer.ReservationEnded(1)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (rf *relayFinder) background(ctx context.Context) {\n\tpeerSourceRateLimiter := make(chan struct{}, 1)\n\trf.refCount.Add(1)\n\tgo func() {\n\t\tdefer rf.refCount.Done()\n\t\trf.findNodes(ctx, peerSourceRateLimiter)\n\t}()\n\n\trf.refCount.Add(1)\n\tgo func() {\n\t\tdefer rf.refCount.Done()\n\t\trf.handleNewCandidates(ctx)\n\t}()\n\n\tnow := rf.conf.clock.Now()\n\tbootDelayTimer := rf.conf.clock.InstantTimer(now.Add(rf.conf.bootDelay))\n\tdefer bootDelayTimer.Stop()\n\n\t// This is the least frequent event. It's our fallback timer if we don't have any other work to do.\n\tleastFrequentInterval := rf.conf.minInterval\n\t// Check if leastFrequentInterval is 0 to avoid busy looping\n\tif rf.conf.backoff > leastFrequentInterval || leastFrequentInterval == 0 {\n\t\tleastFrequentInterval = rf.conf.backoff\n\t}\n\tif rf.conf.maxCandidateAge > leastFrequentInterval || leastFrequentInterval == 0 {\n\t\tleastFrequentInterval = rf.conf.maxCandidateAge\n\t}\n\tif rsvpRefreshInterval > leastFrequentInterval || leastFrequentInterval == 0 {\n\t\tleastFrequentInterval = rsvpRefreshInterval\n\t}\n\n\tscheduledWork := &scheduledWorkTimes{\n\t\tleastFrequentInterval:       leastFrequentInterval,\n\t\tnextRefresh:                 now.Add(rsvpRefreshInterval),\n\t\tnextBackoff:                 now.Add(rf.conf.backoff),\n\t\tnextOldCandidateCheck:       now.Add(rf.conf.maxCandidateAge),\n\t\tnextAllowedCallToPeerSource: now.Add(-time.Second), // allow immediately\n\t}\n\n\tworkTimer := rf.conf.clock.InstantTimer(rf.runScheduledWork(ctx, now, scheduledWork, peerSourceRateLimiter))\n\tdefer workTimer.Stop()\n\n\tgo rf.cleanupDisconnectedPeers(ctx)\n\n\t// update addrs on starting the relay finder.\n\trf.updateAddrs()\n\tfor {\n\t\tselect {\n\t\tcase <-rf.candidateFound:\n\t\t\trf.notifyMaybeConnectToRelay()\n\t\tcase <-bootDelayTimer.Ch():\n\t\t\trf.notifyMaybeConnectToRelay()\n\t\tcase <-rf.relayReservationUpdated:\n\t\t\trf.updateAddrs()\n\t\tcase now := <-workTimer.Ch():\n\t\t\t// Note: `now` is not guaranteed to be the current time. It's the time\n\t\t\t// that the timer was fired. This is okay because we'll schedule\n\t\t\t// future work at a specific time.\n\t\t\tnextTime := rf.runScheduledWork(ctx, now, scheduledWork, peerSourceRateLimiter)\n\t\t\tworkTimer.Reset(nextTime)\n\t\tcase <-rf.triggerRunScheduledWork:\n\t\t\t// Ignore the next time because we aren't scheduling any future work here\n\t\t\t_ = rf.runScheduledWork(ctx, rf.conf.clock.Now(), scheduledWork, peerSourceRateLimiter)\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (rf *relayFinder) updateAddrs() {\n\toldAddrs := rf.circuitAddrs\n\trf.circuitAddrs = rf.getCircuitAddrs()\n\n\tif areSortedAddrsDifferent(rf.circuitAddrs, oldAddrs) {\n\t\tlog.Debug(\"relay addresses updated\", \"addrs\", rf.circuitAddrs)\n\t\trf.metricsTracer.RelayAddressUpdated()\n\t\trf.metricsTracer.RelayAddressCount(len(rf.circuitAddrs))\n\t\tif err := rf.emitter.Emit(event.EvtAutoRelayAddrsUpdated{RelayAddrs: slices.Clone(rf.circuitAddrs)}); err != nil {\n\t\t\tlog.Error(\"failed to emit event.EvtAutoRelayAddrs with RelayAddrs\", \"addrs\", rf.circuitAddrs, \"err\", err)\n\t\t}\n\t}\n}\n\n// This function returns the p2p-circuit addrs for the host.\n// The returned addresses are of the form <relay's-addr>/p2p/<relay's-id>/p2p-circuit.\nfunc (rf *relayFinder) getCircuitAddrs() []ma.Multiaddr {\n\trf.relayMx.Lock()\n\tdefer rf.relayMx.Unlock()\n\n\traddrs := make([]ma.Multiaddr, 0, 4*len(rf.relays)+4)\n\tfor p := range rf.relays {\n\t\taddrs := cleanupAddressSet(rf.host.Peerstore().Addrs(p))\n\t\tcircuit := ma.StringCast(fmt.Sprintf(\"/p2p/%s/p2p-circuit\", p))\n\t\tfor _, addr := range addrs {\n\t\t\tpub := addr.Encapsulate(circuit)\n\t\t\traddrs = append(raddrs, pub)\n\t\t}\n\t}\n\n\t// Sort the addresses. We depend on this order for checking diffs to send address update events.\n\tslices.SortStableFunc(raddrs, func(a, b ma.Multiaddr) int { return bytes.Compare(a.Bytes(), b.Bytes()) })\n\tif len(raddrs) > maxRelayAddrs {\n\t\traddrs = raddrs[:maxRelayAddrs]\n\t}\n\treturn raddrs\n}\n\nfunc (rf *relayFinder) runScheduledWork(ctx context.Context, now time.Time, scheduledWork *scheduledWorkTimes, peerSourceRateLimiter chan<- struct{}) time.Time {\n\tnextTime := now.Add(scheduledWork.leastFrequentInterval)\n\n\tif now.After(scheduledWork.nextRefresh) {\n\t\tscheduledWork.nextRefresh = now.Add(rsvpRefreshInterval)\n\t\tif rf.refreshReservations(ctx, now) {\n\t\t\trf.notifyRelayReservationUpdated()\n\t\t}\n\t}\n\n\tif now.After(scheduledWork.nextBackoff) {\n\t\tscheduledWork.nextBackoff = rf.clearBackoff(now)\n\t}\n\n\tif now.After(scheduledWork.nextOldCandidateCheck) {\n\t\tscheduledWork.nextOldCandidateCheck = rf.clearOldCandidates(now)\n\t}\n\n\tif now.After(scheduledWork.nextAllowedCallToPeerSource) {\n\t\tselect {\n\t\tcase peerSourceRateLimiter <- struct{}{}:\n\t\t\tscheduledWork.nextAllowedCallToPeerSource = now.Add(rf.conf.minInterval)\n\t\t\tif scheduledWork.nextAllowedCallToPeerSource.Before(nextTime) {\n\t\t\t\tnextTime = scheduledWork.nextAllowedCallToPeerSource\n\t\t\t}\n\t\tdefault:\n\t\t}\n\t} else {\n\t\t// We still need to schedule this work if it's sooner than nextTime\n\t\tif scheduledWork.nextAllowedCallToPeerSource.Before(nextTime) {\n\t\t\tnextTime = scheduledWork.nextAllowedCallToPeerSource\n\t\t}\n\t}\n\n\t// Find the next time we need to run scheduled work.\n\tif scheduledWork.nextRefresh.Before(nextTime) {\n\t\tnextTime = scheduledWork.nextRefresh\n\t}\n\tif scheduledWork.nextBackoff.Before(nextTime) {\n\t\tnextTime = scheduledWork.nextBackoff\n\t}\n\tif scheduledWork.nextOldCandidateCheck.Before(nextTime) {\n\t\tnextTime = scheduledWork.nextOldCandidateCheck\n\t}\n\tif nextTime.Equal(now) {\n\t\t// Only happens in CI with a mock clock\n\t\tnextTime = nextTime.Add(1) // avoids an infinite loop\n\t}\n\n\trf.metricsTracer.ScheduledWorkUpdated(scheduledWork)\n\n\treturn nextTime\n}\n\n// clearOldCandidates clears old candidates from the map. Returns the next time\n// to run this function.\nfunc (rf *relayFinder) clearOldCandidates(now time.Time) time.Time {\n\t// If we don't have any candidates, we should run this again in rf.conf.maxCandidateAge.\n\tnextTime := now.Add(rf.conf.maxCandidateAge)\n\n\tvar deleted bool\n\trf.candidateMx.Lock()\n\tdefer rf.candidateMx.Unlock()\n\tfor id, cand := range rf.candidates {\n\t\texpiry := cand.added.Add(rf.conf.maxCandidateAge)\n\t\tif expiry.After(now) {\n\t\t\tif expiry.Before(nextTime) {\n\t\t\t\tnextTime = expiry\n\t\t\t}\n\t\t} else {\n\t\t\tlog.Debug(\"deleting candidate due to age\", \"peer\", id)\n\t\t\tdeleted = true\n\t\t\trf.removeCandidate(id)\n\t\t}\n\t}\n\tif deleted {\n\t\trf.notifyMaybeNeedNewCandidates()\n\t}\n\n\treturn nextTime\n}\n\n// clearBackoff clears old backoff entries from the map. Returns the next time\n// to run this function.\nfunc (rf *relayFinder) clearBackoff(now time.Time) time.Time {\n\tnextTime := now.Add(rf.conf.backoff)\n\n\trf.candidateMx.Lock()\n\tdefer rf.candidateMx.Unlock()\n\tfor id, t := range rf.backoff {\n\t\texpiry := t.Add(rf.conf.backoff)\n\t\tif expiry.After(now) {\n\t\t\tif expiry.Before(nextTime) {\n\t\t\t\tnextTime = expiry\n\t\t\t}\n\t\t} else {\n\t\t\tlog.Debug(\"removing backoff for node\", \"peer\", id)\n\t\t\tdelete(rf.backoff, id)\n\t\t}\n\t}\n\n\treturn nextTime\n}\n\n// findNodes accepts nodes from the channel and tests if they support relaying.\n// It is run on both public and private nodes.\n// It garbage collects old entries, so that nodes doesn't overflow.\n// This makes sure that as soon as we need to find relay candidates, we have them available.\n// peerSourceRateLimiter is used to limit how often we call the peer source.\nfunc (rf *relayFinder) findNodes(ctx context.Context, peerSourceRateLimiter <-chan struct{}) {\n\tvar peerChan <-chan peer.AddrInfo\n\tvar wg sync.WaitGroup\n\tfor {\n\t\trf.candidateMx.Lock()\n\t\tnumCandidates := len(rf.candidates)\n\t\trf.candidateMx.Unlock()\n\n\t\tif peerChan == nil && numCandidates < rf.conf.minCandidates {\n\t\t\trf.metricsTracer.CandidateLoopState(peerSourceRateLimited)\n\n\t\t\tselect {\n\t\t\tcase <-peerSourceRateLimiter:\n\t\t\t\tpeerChan = rf.peerSource(ctx, rf.conf.maxCandidates)\n\t\t\t\tselect {\n\t\t\t\tcase rf.triggerRunScheduledWork <- struct{}{}:\n\t\t\t\tdefault:\n\t\t\t\t}\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tif peerChan == nil {\n\t\t\trf.metricsTracer.CandidateLoopState(waitingForTrigger)\n\t\t} else {\n\t\t\trf.metricsTracer.CandidateLoopState(waitingOnPeerChan)\n\t\t}\n\n\t\tselect {\n\t\tcase <-rf.maybeRequestNewCandidates:\n\t\t\tcontinue\n\t\tcase pi, ok := <-peerChan:\n\t\t\tif !ok {\n\t\t\t\twg.Wait()\n\t\t\t\tpeerChan = nil\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tlog.Debug(\"found node\", \"peer\", pi.ID)\n\t\t\trf.candidateMx.Lock()\n\t\t\tnumCandidates := len(rf.candidates)\n\t\t\tbackoffStart, isOnBackoff := rf.backoff[pi.ID]\n\t\t\trf.candidateMx.Unlock()\n\t\t\tif isOnBackoff {\n\t\t\t\tlog.Debug(\"skipping node that we recently failed to obtain a reservation with\", \"peer\", pi.ID, \"last_attempt\", rf.conf.clock.Since(backoffStart))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif numCandidates >= rf.conf.maxCandidates {\n\t\t\t\tlog.Debug(\"skipping node. Already have enough candidates\", \"peer\", pi.ID, \"candidate_count\", numCandidates, \"max_candidates\", rf.conf.maxCandidates)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\trf.refCount.Add(1)\n\t\t\twg.Add(1)\n\t\t\tgo func() {\n\t\t\t\tdefer rf.refCount.Done()\n\t\t\t\tdefer wg.Done()\n\t\t\t\tif added := rf.handleNewNode(ctx, pi); added {\n\t\t\t\t\trf.notifyNewCandidate()\n\t\t\t\t}\n\t\t\t}()\n\t\tcase <-ctx.Done():\n\t\t\trf.metricsTracer.CandidateLoopState(stopped)\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (rf *relayFinder) notifyMaybeConnectToRelay() {\n\tselect {\n\tcase rf.maybeConnectToRelayTrigger <- struct{}{}:\n\tdefault:\n\t}\n}\n\nfunc (rf *relayFinder) notifyMaybeNeedNewCandidates() {\n\tselect {\n\tcase rf.maybeRequestNewCandidates <- struct{}{}:\n\tdefault:\n\t}\n}\n\nfunc (rf *relayFinder) notifyNewCandidate() {\n\tselect {\n\tcase rf.candidateFound <- struct{}{}:\n\tdefault:\n\t}\n}\n\nfunc (rf *relayFinder) notifyRelayReservationUpdated() {\n\tselect {\n\tcase rf.relayReservationUpdated <- struct{}{}:\n\tdefault:\n\t}\n}\n\n// handleNewNode tests if a peer supports circuit v2.\n// This method is only run on private nodes.\n// If a peer does, it is added to the candidates map.\n// Note that just supporting the protocol doesn't guarantee that we can also obtain a reservation.\nfunc (rf *relayFinder) handleNewNode(ctx context.Context, pi peer.AddrInfo) (added bool) {\n\trf.relayMx.Lock()\n\trelayInUse := rf.usingRelay(pi.ID)\n\trf.relayMx.Unlock()\n\tif relayInUse {\n\t\treturn false\n\t}\n\n\tctx, cancel := context.WithTimeout(ctx, 20*time.Second)\n\tdefer cancel()\n\tsupportsV2, err := rf.tryNode(ctx, pi)\n\tif err != nil {\n\t\tlog.Debug(\"node not accepted as a candidate\", \"peer\", pi.ID, \"err\", err)\n\t\tif err == errProtocolNotSupported {\n\t\t\trf.metricsTracer.CandidateChecked(false)\n\t\t}\n\t\treturn false\n\t}\n\trf.metricsTracer.CandidateChecked(true)\n\n\trf.candidateMx.Lock()\n\tif len(rf.candidates) > rf.conf.maxCandidates {\n\t\trf.candidateMx.Unlock()\n\t\treturn false\n\t}\n\tlog.Debug(\"node supports relay protocol\", \"peer\", pi.ID, \"supports_circuit_v2\", supportsV2)\n\trf.addCandidate(&candidate{\n\t\tadded:           rf.conf.clock.Now(),\n\t\tai:              pi,\n\t\tsupportsRelayV2: supportsV2,\n\t})\n\trf.candidateMx.Unlock()\n\treturn true\n}\n\nvar errProtocolNotSupported = errors.New(\"doesn't speak circuit v2\")\n\n// tryNode checks if a peer actually supports either circuit v2.\n// It does not modify any internal state.\nfunc (rf *relayFinder) tryNode(ctx context.Context, pi peer.AddrInfo) (supportsRelayV2 bool, err error) {\n\tif err := rf.host.Connect(ctx, pi); err != nil {\n\t\treturn false, fmt.Errorf(\"error connecting to relay %s: %w\", pi.ID, err)\n\t}\n\n\tconns := rf.host.Network().ConnsToPeer(pi.ID)\n\tfor _, conn := range conns {\n\t\tif isRelayAddr(conn.RemoteMultiaddr()) {\n\t\t\treturn false, errors.New(\"not a public node\")\n\t\t}\n\t}\n\n\t// wait for identify to complete in at least one conn so that we can check the supported protocols\n\thi, ok := rf.host.(interface{ IDService() identify.IDService })\n\tif !ok {\n\t\t// if we don't have identify, assume the peer supports relay.\n\t\treturn true, nil\n\t}\n\tready := make(chan struct{}, 1)\n\tfor _, conn := range conns {\n\t\tgo func(conn network.Conn) {\n\t\t\tselect {\n\t\t\tcase <-hi.IDService().IdentifyWait(conn):\n\t\t\t\tselect {\n\t\t\t\tcase ready <- struct{}{}:\n\t\t\t\tdefault:\n\t\t\t\t}\n\t\t\tcase <-ctx.Done():\n\t\t\t}\n\t\t}(conn)\n\t}\n\n\tselect {\n\tcase <-ready:\n\tcase <-ctx.Done():\n\t\treturn false, ctx.Err()\n\t}\n\n\tprotos, err := rf.host.Peerstore().SupportsProtocols(pi.ID, protoIDv2)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"error checking relay protocol support for peer %s: %w\", pi.ID, err)\n\t}\n\tif len(protos) == 0 {\n\t\treturn false, errProtocolNotSupported\n\t}\n\treturn true, nil\n}\n\n// When a new node that could be a relay is found, we receive a notification on the maybeConnectToRelayTrigger chan.\n// This function makes sure that we only run one instance of maybeConnectToRelay at once, and buffers\n// exactly one more trigger event to run maybeConnectToRelay.\nfunc (rf *relayFinder) handleNewCandidates(ctx context.Context) {\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase <-rf.maybeConnectToRelayTrigger:\n\t\t\trf.maybeConnectToRelay(ctx)\n\t\t}\n\t}\n}\n\nfunc (rf *relayFinder) maybeConnectToRelay(ctx context.Context) {\n\trf.relayMx.Lock()\n\tnumRelays := len(rf.relays)\n\trf.relayMx.Unlock()\n\t// We're already connected to our desired number of relays. Nothing to do here.\n\tif numRelays == rf.conf.desiredRelays {\n\t\treturn\n\t}\n\n\trf.candidateMx.Lock()\n\tif len(rf.relays) == 0 && len(rf.candidates) < rf.conf.minCandidates && rf.conf.clock.Since(rf.bootTime) < rf.conf.bootDelay {\n\t\t// During the startup phase, we don't want to connect to the first candidate that we find.\n\t\t// Instead, we wait until we've found at least minCandidates, and then select the best of those.\n\t\t// However, if that takes too long (longer than bootDelay), we still go ahead.\n\t\trf.candidateMx.Unlock()\n\t\treturn\n\t}\n\tif len(rf.candidates) == 0 {\n\t\trf.candidateMx.Unlock()\n\t\treturn\n\t}\n\tcandidates := rf.selectCandidates()\n\trf.candidateMx.Unlock()\n\n\t// We now iterate over the candidates, attempting (sequentially) to get reservations with them, until\n\t// we reach the desired number of relays.\n\tfor _, cand := range candidates {\n\t\tid := cand.ai.ID\n\t\trf.relayMx.Lock()\n\t\tusingRelay := rf.usingRelay(id)\n\t\trf.relayMx.Unlock()\n\t\tif usingRelay {\n\t\t\trf.candidateMx.Lock()\n\t\t\trf.removeCandidate(id)\n\t\t\trf.candidateMx.Unlock()\n\t\t\trf.notifyMaybeNeedNewCandidates()\n\t\t\tcontinue\n\t\t}\n\t\trsvp, err := rf.connectToRelay(ctx, cand)\n\t\tif err != nil {\n\t\t\tlog.Debug(\"failed to connect to relay\", \"relay_peer\", id, \"err\", err)\n\t\t\trf.notifyMaybeNeedNewCandidates()\n\t\t\trf.metricsTracer.ReservationRequestFinished(false, err)\n\t\t\tcontinue\n\t\t}\n\t\tlog.Debug(\"adding new relay\", \"relay_peer\", id)\n\t\trf.relayMx.Lock()\n\t\trf.relays[id] = rsvp\n\t\tnumRelays := len(rf.relays)\n\t\trf.relayMx.Unlock()\n\t\trf.notifyMaybeNeedNewCandidates()\n\n\t\trf.host.ConnManager().Protect(id, autorelayTag) // protect the connection\n\n\t\trf.notifyRelayReservationUpdated()\n\n\t\trf.metricsTracer.ReservationRequestFinished(false, nil)\n\n\t\tif numRelays >= rf.conf.desiredRelays {\n\t\t\tbreak\n\t\t}\n\t}\n}\n\nfunc (rf *relayFinder) connectToRelay(ctx context.Context, cand *candidate) (*circuitv2.Reservation, error) {\n\tid := cand.ai.ID\n\n\tctx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\tdefer cancel()\n\n\tvar rsvp *circuitv2.Reservation\n\n\t// make sure we're still connected.\n\tif rf.host.Network().Connectedness(id) != network.Connected {\n\t\tif err := rf.host.Connect(ctx, cand.ai); err != nil {\n\t\t\trf.candidateMx.Lock()\n\t\t\trf.removeCandidate(cand.ai.ID)\n\t\t\trf.candidateMx.Unlock()\n\t\t\treturn nil, fmt.Errorf(\"failed to connect: %w\", err)\n\t\t}\n\t}\n\n\trf.candidateMx.Lock()\n\trf.backoff[id] = rf.conf.clock.Now()\n\trf.candidateMx.Unlock()\n\tvar err error\n\tif cand.supportsRelayV2 {\n\t\trsvp, err = circuitv2.Reserve(ctx, rf.host, cand.ai)\n\t\tif err != nil {\n\t\t\terr = fmt.Errorf(\"failed to reserve slot: %w\", err)\n\t\t}\n\t}\n\trf.candidateMx.Lock()\n\trf.removeCandidate(id)\n\trf.candidateMx.Unlock()\n\treturn rsvp, err\n}\n\nfunc (rf *relayFinder) refreshReservations(ctx context.Context, now time.Time) bool {\n\trf.relayMx.Lock()\n\n\t// find reservations about to expire and refresh them in parallel\n\tg := new(errgroup.Group)\n\tfor p, rsvp := range rf.relays {\n\t\tif now.Add(rsvpExpirationSlack).Before(rsvp.Expiration) {\n\t\t\tcontinue\n\t\t}\n\n\t\tp := p\n\t\tg.Go(func() error {\n\t\t\terr := rf.refreshRelayReservation(ctx, p)\n\t\t\trf.metricsTracer.ReservationRequestFinished(true, err)\n\t\t\treturn err\n\t\t})\n\t}\n\trf.relayMx.Unlock()\n\n\terr := g.Wait()\n\treturn err != nil\n}\n\nfunc (rf *relayFinder) refreshRelayReservation(ctx context.Context, p peer.ID) error {\n\trsvp, err := circuitv2.Reserve(ctx, rf.host, peer.AddrInfo{ID: p})\n\n\trf.relayMx.Lock()\n\tif err != nil {\n\t\tlog.Debug(\"failed to refresh relay slot reservation\", \"relay_peer\", p, \"err\", err)\n\t\t_, exists := rf.relays[p]\n\t\tdelete(rf.relays, p)\n\t\t// unprotect the connection\n\t\trf.host.ConnManager().Unprotect(p, autorelayTag)\n\t\trf.relayMx.Unlock()\n\t\tif exists {\n\t\t\trf.metricsTracer.ReservationEnded(1)\n\t\t}\n\t\treturn err\n\t}\n\n\tlog.Debug(\"refreshed relay slot reservation\", \"relay_peer\", p)\n\trf.relays[p] = rsvp\n\trf.relayMx.Unlock()\n\treturn nil\n}\n\n// usingRelay returns if we're currently using the given relay.\nfunc (rf *relayFinder) usingRelay(p peer.ID) bool {\n\t_, ok := rf.relays[p]\n\treturn ok\n}\n\n// addCandidates adds a candidate to the candidates set. Assumes caller holds candidateMx mutex\nfunc (rf *relayFinder) addCandidate(cand *candidate) {\n\t_, exists := rf.candidates[cand.ai.ID]\n\trf.candidates[cand.ai.ID] = cand\n\tif !exists {\n\t\trf.metricsTracer.CandidateAdded(1)\n\t}\n}\n\nfunc (rf *relayFinder) removeCandidate(id peer.ID) {\n\t_, exists := rf.candidates[id]\n\tif exists {\n\t\tdelete(rf.candidates, id)\n\t\trf.metricsTracer.CandidateRemoved(1)\n\t}\n}\n\n// selectCandidates returns an ordered slice of relay candidates.\n// Callers should attempt to obtain reservations with the candidates in this order.\nfunc (rf *relayFinder) selectCandidates() []*candidate {\n\tnow := rf.conf.clock.Now()\n\tcandidates := make([]*candidate, 0, len(rf.candidates))\n\tfor _, cand := range rf.candidates {\n\t\tif cand.added.Add(rf.conf.maxCandidateAge).After(now) {\n\t\t\tcandidates = append(candidates, cand)\n\t\t}\n\t}\n\n\t// TODO: better relay selection strategy; this just selects random relays,\n\t// but we should probably use ping latency as the selection metric\n\trand.Shuffle(len(candidates), func(i, j int) {\n\t\tcandidates[i], candidates[j] = candidates[j], candidates[i]\n\t})\n\treturn candidates\n}\n\nfunc (rf *relayFinder) Start() error {\n\trf.ctxCancelMx.Lock()\n\tdefer rf.ctxCancelMx.Unlock()\n\tif rf.ctxCancel != nil {\n\t\treturn errAlreadyRunning\n\t}\n\tlog.Debug(\"starting relay finder\")\n\n\trf.initMetrics()\n\n\tctx, cancel := context.WithCancel(context.Background())\n\trf.ctxCancel = cancel\n\trf.refCount.Add(1)\n\tgo func() {\n\t\tdefer rf.refCount.Done()\n\t\trf.background(ctx)\n\t}()\n\treturn nil\n}\n\nfunc (rf *relayFinder) Stop() error {\n\trf.ctxCancelMx.Lock()\n\tdefer rf.ctxCancelMx.Unlock()\n\tlog.Debug(\"stopping relay finder\")\n\tif rf.ctxCancel != nil {\n\t\trf.ctxCancel()\n\t}\n\trf.refCount.Wait()\n\trf.ctxCancel = nil\n\n\trf.resetMetrics()\n\treturn nil\n}\n\nfunc (rf *relayFinder) initMetrics() {\n\trf.metricsTracer.DesiredReservations(rf.conf.desiredRelays)\n\n\trf.relayMx.Lock()\n\trf.metricsTracer.ReservationOpened(len(rf.relays))\n\trf.relayMx.Unlock()\n\n\trf.candidateMx.Lock()\n\trf.metricsTracer.CandidateAdded(len(rf.candidates))\n\trf.candidateMx.Unlock()\n}\n\nfunc (rf *relayFinder) resetMetrics() {\n\trf.relayMx.Lock()\n\trf.metricsTracer.ReservationEnded(len(rf.relays))\n\trf.relayMx.Unlock()\n\n\trf.candidateMx.Lock()\n\trf.metricsTracer.CandidateRemoved(len(rf.candidates))\n\trf.candidateMx.Unlock()\n\n\trf.metricsTracer.RelayAddressCount(0)\n\trf.metricsTracer.ScheduledWorkUpdated(&scheduledWorkTimes{})\n}\n\nfunc areSortedAddrsDifferent(a, b []ma.Multiaddr) bool {\n\tif len(a) != len(b) {\n\t\treturn true\n\t}\n\tfor i, aa := range a {\n\t\tif !aa.Equal(b[i]) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "p2p/host/basic/addrs_manager.go",
    "content": "package basichost\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"slices\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/event\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\t\"github.com/libp2p/go-libp2p/core/record\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/eventbus\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n)\n\nconst maxObservedAddrsPerListenAddr = 3\n\n// addrChangeTickrInterval is the interval to recompute host addrs.\nvar addrChangeTickrInterval = 5 * time.Second\n\nconst maxPeerRecordSize = 8 * 1024 // 8k to be compatible with identify's limit\n\n// addrStore is a minimal interface for storing peer addresses\ntype addrStore interface {\n\tSetAddrs(peer.ID, []ma.Multiaddr, time.Duration)\n}\n\n// ObservedAddrsManager maps our local listen addrs to externally observed addrs.\ntype ObservedAddrsManager interface {\n\tAddrs(minObservers int) []ma.Multiaddr\n\tAddrsFor(local ma.Multiaddr) []ma.Multiaddr\n}\n\ntype hostAddrs struct {\n\taddrs            []ma.Multiaddr\n\tlocalAddrs       []ma.Multiaddr\n\treachableAddrs   []ma.Multiaddr\n\tunreachableAddrs []ma.Multiaddr\n\tunknownAddrs     []ma.Multiaddr\n\trelayAddrs       []ma.Multiaddr\n}\n\ntype addrsManager struct {\n\tbus                      event.Bus\n\tnatManager               NATManager\n\taddrsFactory             AddrsFactory\n\tlistenAddrs              func() []ma.Multiaddr\n\taddCertHashes            func([]ma.Multiaddr) []ma.Multiaddr\n\tobservedAddrsManager     ObservedAddrsManager\n\tinterfaceAddrs           *interfaceAddrsCache\n\taddrsReachabilityTracker *addrsReachabilityTracker\n\n\t// triggerAddrsUpdateChan is used to trigger an addresses update.\n\ttriggerAddrsUpdateChan chan chan struct{}\n\t// started is used to check whether the addrsManager has started.\n\tstarted atomic.Bool\n\t// triggerReachabilityUpdate is notified when reachable addrs are updated.\n\ttriggerReachabilityUpdate chan struct{}\n\n\thostReachability atomic.Pointer[network.Reachability]\n\n\taddrsMx      sync.RWMutex\n\tcurrentAddrs hostAddrs\n\n\tsignKey           crypto.PrivKey\n\taddrStore         addrStore\n\tsignedRecordStore peerstore.CertifiedAddrBook\n\thostID            peer.ID\n\n\twg        sync.WaitGroup\n\tctx       context.Context\n\tctxCancel context.CancelFunc\n}\n\nfunc newAddrsManager(\n\tbus event.Bus,\n\tnatmgr NATManager,\n\taddrsFactory AddrsFactory,\n\tlistenAddrs func() []ma.Multiaddr,\n\taddCertHashes func([]ma.Multiaddr) []ma.Multiaddr,\n\tobservedAddrsManager ObservedAddrsManager,\n\tclient autonatv2Client,\n\tenableMetrics bool,\n\tregisterer prometheus.Registerer,\n\tdisableSignedPeerRecord bool,\n\tsignKey crypto.PrivKey,\n\taddrStore addrStore,\n\thostID peer.ID,\n) (*addrsManager, error) {\n\tctx, cancel := context.WithCancel(context.Background())\n\tas := &addrsManager{\n\t\tbus:                       bus,\n\t\tlistenAddrs:               listenAddrs,\n\t\taddCertHashes:             addCertHashes,\n\t\tobservedAddrsManager:      observedAddrsManager,\n\t\tnatManager:                natmgr,\n\t\taddrsFactory:              addrsFactory,\n\t\ttriggerAddrsUpdateChan:    make(chan chan struct{}, 1),\n\t\ttriggerReachabilityUpdate: make(chan struct{}, 1),\n\t\tinterfaceAddrs:            &interfaceAddrsCache{},\n\t\tsignKey:                   signKey,\n\t\taddrStore:                 addrStore,\n\t\thostID:                    hostID,\n\t\tctx:                       ctx,\n\t\tctxCancel:                 cancel,\n\t}\n\tunknownReachability := network.ReachabilityUnknown\n\tas.hostReachability.Store(&unknownReachability)\n\n\tif !disableSignedPeerRecord {\n\t\tvar ok bool\n\t\tas.signedRecordStore, ok = as.addrStore.(peerstore.CertifiedAddrBook)\n\t\tif !ok {\n\t\t\treturn nil, errors.New(\"peerstore doesn't implement CertifiedAddrBook interface\")\n\t\t}\n\t}\n\n\tif client != nil {\n\t\tvar metricsTracker MetricsTracker\n\t\tif enableMetrics {\n\t\t\tmetricsTracker = newMetricsTracker(withRegisterer(registerer))\n\t\t}\n\t\tas.addrsReachabilityTracker = newAddrsReachabilityTracker(client, as.triggerReachabilityUpdate, nil, metricsTracker)\n\t}\n\treturn as, nil\n}\n\nfunc (a *addrsManager) Start() error {\n\tif a.addrsReachabilityTracker != nil {\n\t\terr := a.addrsReachabilityTracker.Start()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error starting addrs reachability tracker: %s\", err)\n\t\t}\n\t}\n\tif err := a.startBackgroundWorker(); err != nil {\n\t\treturn fmt.Errorf(\"error starting background worker: %s\", err)\n\t}\n\n\t// this ensures that listens concurrent with Start are reflected correctly after Start exits.\n\ta.started.Store(true)\n\ta.updateAddrsSync()\n\treturn nil\n}\n\nfunc (a *addrsManager) Close() {\n\ta.ctxCancel()\n\tif a.natManager != nil {\n\t\terr := a.natManager.Close()\n\t\tif err != nil {\n\t\t\tlog.Warn(\"error closing natmgr\", \"err\", err)\n\t\t}\n\t}\n\tif a.addrsReachabilityTracker != nil {\n\t\terr := a.addrsReachabilityTracker.Close()\n\t\tif err != nil {\n\t\t\tlog.Warn(\"error closing addrs reachability tracker\", \"err\", err)\n\t\t}\n\t}\n\ta.wg.Wait()\n}\n\nfunc (a *addrsManager) NetNotifee() network.Notifiee {\n\treturn &network.NotifyBundle{\n\t\tListenF:      func(network.Network, ma.Multiaddr) { a.updateAddrsSync() },\n\t\tListenCloseF: func(network.Network, ma.Multiaddr) { a.updateAddrsSync() },\n\t}\n}\n\nfunc (a *addrsManager) updateAddrsSync() {\n\t// This prevents a deadlock where addrs updates before starting the manager are ignored\n\tif !a.started.Load() {\n\t\treturn\n\t}\n\tch := make(chan struct{})\n\tselect {\n\tcase a.triggerAddrsUpdateChan <- ch:\n\t\tselect {\n\t\tcase <-ch:\n\t\tcase <-a.ctx.Done():\n\t\t}\n\tcase <-a.ctx.Done():\n\t}\n}\n\nfunc (a *addrsManager) startBackgroundWorker() (retErr error) {\n\tautoRelayAddrsSub, err := a.bus.Subscribe(new(event.EvtAutoRelayAddrsUpdated), eventbus.Name(\"addrs-manager autorelay sub\"))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error subscribing to auto relay addrs: %s\", err)\n\t}\n\tmc := multiCloser{autoRelayAddrsSub}\n\tautonatReachabilitySub, err := a.bus.Subscribe(new(event.EvtLocalReachabilityChanged), eventbus.Name(\"addrs-manager autonatv1 sub\"))\n\tif err != nil {\n\t\treturn errors.Join(\n\t\t\tfmt.Errorf(\"error subscribing to autonat reachability: %s\", err),\n\t\t\tmc.Close(),\n\t\t)\n\t}\n\tmc = append(mc, autonatReachabilitySub)\n\n\temitter, err := a.bus.Emitter(new(event.EvtHostReachableAddrsChanged), eventbus.Stateful)\n\tif err != nil {\n\t\treturn errors.Join(\n\t\t\tfmt.Errorf(\"error creating reachability subscriber: %s\", err),\n\t\t\tmc.Close(),\n\t\t)\n\t}\n\tmc = append(mc, emitter)\n\n\tlocalAddrsEmitter, err := a.bus.Emitter(new(event.EvtLocalAddressesUpdated), eventbus.Stateful)\n\tif err != nil {\n\t\treturn errors.Join(\n\t\t\tfmt.Errorf(\"error creating local addrs emitter: %s\", err),\n\t\t\tmc.Close(),\n\t\t)\n\t}\n\n\ta.wg.Add(1)\n\tgo a.background(autoRelayAddrsSub, autonatReachabilitySub, emitter, localAddrsEmitter)\n\treturn nil\n}\n\nfunc (a *addrsManager) background(\n\tautoRelayAddrsSub,\n\tautonatReachabilitySub event.Subscription,\n\temitter event.Emitter,\n\tlocalAddrsEmitter event.Emitter,\n) {\n\tdefer a.wg.Done()\n\tdefer func() {\n\t\terr := autoRelayAddrsSub.Close()\n\t\tif err != nil {\n\t\t\tlog.Warn(\"error closing auto relay addrs sub\", \"err\", err)\n\t\t}\n\t\terr = autonatReachabilitySub.Close()\n\t\tif err != nil {\n\t\t\tlog.Warn(\"error closing autonat reachability sub\", \"err\", err)\n\t\t}\n\t\terr = emitter.Close()\n\t\tif err != nil {\n\t\t\tlog.Warn(\"error closing host reachability emitter\", \"err\", err)\n\t\t}\n\t\terr = localAddrsEmitter.Close()\n\t\tif err != nil {\n\t\t\tlog.Warn(\"error closing local addrs emitter\", \"err\", err)\n\t\t}\n\t}()\n\n\tvar relayAddrs []ma.Multiaddr\n\t// update relay addrs in case we're private\n\tselect {\n\tcase e := <-autoRelayAddrsSub.Out():\n\t\tif evt, ok := e.(event.EvtAutoRelayAddrsUpdated); ok {\n\t\t\trelayAddrs = slices.Clone(evt.RelayAddrs)\n\t\t}\n\tdefault:\n\t}\n\n\tselect {\n\tcase e := <-autonatReachabilitySub.Out():\n\t\tif evt, ok := e.(event.EvtLocalReachabilityChanged); ok {\n\t\t\ta.hostReachability.Store(&evt.Reachability)\n\t\t}\n\tdefault:\n\t}\n\n\tticker := time.NewTicker(addrChangeTickrInterval)\n\tdefer ticker.Stop()\n\tvar previousAddrs hostAddrs\n\tnotifCh := make(chan struct{})\n\tfor {\n\t\tselect {\n\t\tcase <-ticker.C:\n\t\tcase notifCh = <-a.triggerAddrsUpdateChan:\n\t\tcase <-a.triggerReachabilityUpdate:\n\t\tcase e := <-autoRelayAddrsSub.Out():\n\t\t\tif evt, ok := e.(event.EvtAutoRelayAddrsUpdated); ok {\n\t\t\t\trelayAddrs = slices.Clone(evt.RelayAddrs)\n\t\t\t}\n\t\tcase e := <-autonatReachabilitySub.Out():\n\t\t\tif evt, ok := e.(event.EvtLocalReachabilityChanged); ok {\n\t\t\t\ta.hostReachability.Store(&evt.Reachability)\n\t\t\t}\n\t\tcase <-a.ctx.Done():\n\t\t\treturn\n\t\t}\n\n\t\tcurrAddrs := a.updateAddrs(previousAddrs, relayAddrs)\n\t\tif notifCh != nil {\n\t\t\tclose(notifCh)\n\t\t\tnotifCh = nil\n\t\t}\n\t\ta.notifyAddrsUpdated(emitter, localAddrsEmitter, previousAddrs, currAddrs)\n\t\tpreviousAddrs = currAddrs\n\t}\n}\n\n// updateAddrs updates the addresses of the host and returns the new updated\n// addrs. This must only be called from the background goroutine or from the Start method otherwise\n// we may end up with stale addrs.\nfunc (a *addrsManager) updateAddrs(prevHostAddrs hostAddrs, relayAddrs []ma.Multiaddr) hostAddrs {\n\tlocalAddrs := a.getLocalAddrs()\n\tvar currReachableAddrs, currUnreachableAddrs, currUnknownAddrs []ma.Multiaddr\n\tif a.addrsReachabilityTracker != nil {\n\t\tcurrReachableAddrs, currUnreachableAddrs, currUnknownAddrs = a.getConfirmedAddrs(localAddrs)\n\t}\n\trelayAddrs = slices.Clone(relayAddrs)\n\tcurrAddrs := a.getDialableAddrs(localAddrs, currReachableAddrs, currUnreachableAddrs, relayAddrs)\n\tcurrAddrs = a.applyAddrsFactory(currAddrs)\n\n\tif areAddrsDifferent(prevHostAddrs.addrs, currAddrs) {\n\t\t_, _, removed := diffAddrs(prevHostAddrs.addrs, currAddrs)\n\t\ta.updatePeerStore(currAddrs, removed)\n\t}\n\ta.addrsMx.Lock()\n\ta.currentAddrs = hostAddrs{\n\t\taddrs:            append(a.currentAddrs.addrs[:0], currAddrs...),\n\t\tlocalAddrs:       append(a.currentAddrs.localAddrs[:0], localAddrs...),\n\t\treachableAddrs:   append(a.currentAddrs.reachableAddrs[:0], currReachableAddrs...),\n\t\tunreachableAddrs: append(a.currentAddrs.unreachableAddrs[:0], currUnreachableAddrs...),\n\t\tunknownAddrs:     append(a.currentAddrs.unknownAddrs[:0], currUnknownAddrs...),\n\t\trelayAddrs:       append(a.currentAddrs.relayAddrs[:0], relayAddrs...),\n\t}\n\ta.addrsMx.Unlock()\n\n\treturn hostAddrs{\n\t\tlocalAddrs:       localAddrs,\n\t\taddrs:            currAddrs,\n\t\treachableAddrs:   currReachableAddrs,\n\t\tunreachableAddrs: currUnreachableAddrs,\n\t\tunknownAddrs:     currUnknownAddrs,\n\t\trelayAddrs:       relayAddrs,\n\t}\n}\n\n// updatePeerStore updates the peer store for the host\nfunc (a *addrsManager) updatePeerStore(currentAddrs []ma.Multiaddr, removedAddrs []ma.Multiaddr) {\n\t// update host addresses in the peer store\n\ta.addrStore.SetAddrs(a.hostID, currentAddrs, peerstore.PermanentAddrTTL)\n\ta.addrStore.SetAddrs(a.hostID, removedAddrs, 0)\n\n\tvar sr *record.Envelope\n\t// Our addresses have changed.\n\t// store the signed peer record in the peer store.\n\tif a.signedRecordStore != nil {\n\t\tvar err error\n\t\t// add signed peer record to the event\n\t\t// in case of an error drop this event.\n\t\tsr, err = a.makeSignedPeerRecord(currentAddrs)\n\t\tif err != nil {\n\t\t\tlog.Error(\"error creating a signed peer record from the set of current addresses\", \"err\", err)\n\t\t\treturn\n\t\t}\n\t\tif _, err := a.signedRecordStore.ConsumePeerRecord(sr, peerstore.PermanentAddrTTL); err != nil {\n\t\t\tlog.Error(\"failed to persist signed peer record in peer store\", \"err\", err)\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (a *addrsManager) notifyAddrsUpdated(emitter event.Emitter, localAddrsEmitter event.Emitter, previous, current hostAddrs) {\n\tif areAddrsDifferent(previous.localAddrs, current.localAddrs) {\n\t\tlog.Debug(\"host local addresses updated\", \"addrs\", current.localAddrs)\n\t\tif a.addrsReachabilityTracker != nil {\n\t\t\ta.addrsReachabilityTracker.UpdateAddrs(current.localAddrs)\n\t\t}\n\t}\n\tif areAddrsDifferent(previous.addrs, current.addrs) {\n\t\tlog.Debug(\"host addresses updated\", \"addrs\", current.localAddrs)\n\t\ta.emitLocalAddrsUpdated(localAddrsEmitter, current.addrs, previous.addrs)\n\t}\n\n\t// We *must* send both reachability changed and addrs changed events from the\n\t// same goroutine to ensure correct ordering\n\t// Consider the events:\n\t// \t- addr x discovered\n\t// \t- addr x is reachable\n\t// \t- addr x removed\n\t// We must send these events in the same order. It'll be confusing for consumers\n\t// if the reachable event is received after the addr removed event.\n\tif areAddrsDifferent(previous.reachableAddrs, current.reachableAddrs) ||\n\t\tareAddrsDifferent(previous.unreachableAddrs, current.unreachableAddrs) ||\n\t\tareAddrsDifferent(previous.unknownAddrs, current.unknownAddrs) {\n\t\tlog.Debug(\"host reachable addrs updated\",\n\t\t\t\"reachable\", current.reachableAddrs,\n\t\t\t\"unreachable\", current.unreachableAddrs,\n\t\t\t\"unknown\", current.unknownAddrs)\n\t\tif err := emitter.Emit(event.EvtHostReachableAddrsChanged{\n\t\t\tReachable:   slices.Clone(current.reachableAddrs),\n\t\t\tUnreachable: slices.Clone(current.unreachableAddrs),\n\t\t\tUnknown:     slices.Clone(current.unknownAddrs),\n\t\t}); err != nil {\n\t\t\tlog.Error(\"error sending host reachable addrs changed event\", \"err\", err)\n\t\t}\n\t}\n}\n\n// Addrs returns the node's dialable addresses both public and private.\n// If autorelay is enabled and node reachability is private, it returns\n// the node's relay addresses and private network addresses.\nfunc (a *addrsManager) Addrs() []ma.Multiaddr {\n\ta.addrsMx.RLock()\n\taddrs := a.getDialableAddrs(a.currentAddrs.localAddrs, a.currentAddrs.reachableAddrs, a.currentAddrs.unreachableAddrs, a.currentAddrs.relayAddrs)\n\ta.addrsMx.RUnlock()\n\t// don't hold the lock while applying addrs factory\n\treturn a.applyAddrsFactory(addrs)\n}\n\n// getDialableAddrs returns the node's dialable addrs. Doesn't mutate any argument.\nfunc (a *addrsManager) getDialableAddrs(localAddrs, reachableAddrs, unreachableAddrs, relayAddrs []ma.Multiaddr) []ma.Multiaddr {\n\t// remove known unreachable addrs\n\taddrs := removeInSource(slices.Clone(localAddrs), unreachableAddrs)\n\t// If we have no confirmed reachable addresses, add the relay addresses\n\tif a.addrsReachabilityTracker != nil {\n\t\tif len(reachableAddrs) == 0 {\n\t\t\taddrs = append(addrs, relayAddrs...)\n\t\t}\n\t} else {\n\t\trch := a.hostReachability.Load()\n\t\t// If we're only using autonatv1, remove public addrs and add relay addrs\n\t\tif len(relayAddrs) > 0 && rch != nil && *rch == network.ReachabilityPrivate {\n\t\t\taddrs = slices.DeleteFunc(addrs, manet.IsPublicAddr)\n\t\t\taddrs = append(addrs, relayAddrs...)\n\t\t}\n\t}\n\treturn addrs\n}\n\nfunc (a *addrsManager) applyAddrsFactory(addrs []ma.Multiaddr) []ma.Multiaddr {\n\taf := a.addrsFactory(addrs)\n\t// Copy to our slice in case addrsFactory returns its own same slice always.\n\taddrs = append(addrs[:0], af...)\n\t// Add certhashes for the addresses provided by the user via address factory.\n\taddrs = a.addCertHashes(ma.Unique(addrs))\n\tslices.SortFunc(addrs, func(a, b ma.Multiaddr) int { return a.Compare(b) })\n\treturn addrs\n}\n\n// HolePunchAddrs returns all the host's direct public addresses, reachable or unreachable,\n// suitable for hole punching.\nfunc (a *addrsManager) HolePunchAddrs() []ma.Multiaddr {\n\taddrs := a.DirectAddrs()\n\taddrs = slices.Clone(a.addrsFactory(addrs))\n\t// AllAddrs may ignore observed addresses in favour of NAT mappings.\n\t// Use both for hole punching.\n\tif a.observedAddrsManager != nil {\n\t\t// For holepunching, include all the best addresses we know even ones with only 1 observer.\n\t\taddrs = append(addrs, a.observedAddrsManager.Addrs(1)...)\n\t}\n\taddrs = ma.Unique(addrs)\n\treturn slices.DeleteFunc(addrs, func(a ma.Multiaddr) bool { return !manet.IsPublicAddr(a) })\n}\n\n// DirectAddrs returns all the addresses the host is listening on except circuit addresses.\nfunc (a *addrsManager) DirectAddrs() []ma.Multiaddr {\n\ta.addrsMx.RLock()\n\tdefer a.addrsMx.RUnlock()\n\treturn slices.Clone(a.currentAddrs.localAddrs)\n}\n\n// ConfirmedAddrs returns all addresses of the host that are reachable from the internet\nfunc (a *addrsManager) ConfirmedAddrs() (reachable []ma.Multiaddr, unreachable []ma.Multiaddr, unknown []ma.Multiaddr) {\n\ta.addrsMx.RLock()\n\tdefer a.addrsMx.RUnlock()\n\treturn slices.Clone(a.currentAddrs.reachableAddrs), slices.Clone(a.currentAddrs.unreachableAddrs), slices.Clone(a.currentAddrs.unknownAddrs)\n}\n\nfunc (a *addrsManager) getConfirmedAddrs(localAddrs []ma.Multiaddr) (reachableAddrs, unreachableAddrs, unknownAddrs []ma.Multiaddr) {\n\treachableAddrs, unreachableAddrs, unknownAddrs = a.addrsReachabilityTracker.ConfirmedAddrs()\n\treturn removeNotInSource(reachableAddrs, localAddrs), removeNotInSource(unreachableAddrs, localAddrs), removeNotInSource(unknownAddrs, localAddrs)\n}\n\nvar p2pCircuitAddr = ma.StringCast(\"/p2p-circuit\")\n\nfunc (a *addrsManager) getLocalAddrs() []ma.Multiaddr {\n\tlistenAddrs := a.listenAddrs()\n\tif len(listenAddrs) == 0 {\n\t\treturn nil\n\t}\n\n\tfinalAddrs := make([]ma.Multiaddr, 0, 8)\n\tfinalAddrs = a.appendInterfaceAddrs(finalAddrs, listenAddrs)\n\tif a.natManager != nil {\n\t\tfinalAddrs = a.appendNATAddrs(finalAddrs, listenAddrs)\n\t}\n\tif a.observedAddrsManager != nil {\n\t\tfinalAddrs = a.appendObservedAddrs(finalAddrs, listenAddrs, a.interfaceAddrs.All())\n\t}\n\n\t// Remove \"/p2p-circuit\" addresses from the list.\n\t// The p2p-circuit listener reports its address as just /p2p-circuit. This is\n\t// useless for dialing. Users need to manage their circuit addresses themselves,\n\t// or use AutoRelay.\n\tfinalAddrs = slices.DeleteFunc(finalAddrs, func(a ma.Multiaddr) bool {\n\t\treturn a.Equal(p2pCircuitAddr)\n\t})\n\n\t// Remove any unspecified address from the list\n\tfinalAddrs = slices.DeleteFunc(finalAddrs, func(a ma.Multiaddr) bool {\n\t\treturn manet.IsIPUnspecified(a)\n\t})\n\n\t// Add certhashes for /webrtc-direct, /webtransport, etc addresses discovered\n\t// using identify.\n\tfinalAddrs = a.addCertHashes(finalAddrs)\n\tfinalAddrs = ma.Unique(finalAddrs)\n\tslices.SortFunc(finalAddrs, func(a, b ma.Multiaddr) int { return a.Compare(b) })\n\treturn finalAddrs\n}\n\n// appendInterfaceAddrs resolves any unspecified listen addresses to all interface addresses\n// and appends them to `dst`.\nfunc (a *addrsManager) appendInterfaceAddrs(dst []ma.Multiaddr, listenAddrs []ma.Multiaddr) []ma.Multiaddr {\n\tif resolved, err := manet.ResolveUnspecifiedAddresses(listenAddrs, a.interfaceAddrs.All()); err != nil {\n\t\tlog.Warn(\"failed to resolve listen addrs\", \"err\", err)\n\t} else {\n\t\tdst = append(dst, resolved...)\n\t}\n\treturn dst\n}\n\n// appendNATAddrs appends the NAT-ed addrs for the listenAddrs. For unspecified listen addrs it appends the\n// public address for all the interfaces.\n// Inferring WebTransport from QUIC depends on the observed address manager.\nfunc (a *addrsManager) appendNATAddrs(dst []ma.Multiaddr, listenAddrs []ma.Multiaddr) []ma.Multiaddr {\n\tfor _, listenAddr := range listenAddrs {\n\t\tnatAddr := a.natManager.GetMapping(listenAddr)\n\t\tif natAddr != nil {\n\t\t\tdst = append(dst, natAddr)\n\t\t}\n\t}\n\treturn dst\n}\n\nfunc (a *addrsManager) appendObservedAddrs(dst []ma.Multiaddr, listenAddrs, ifaceAddrs []ma.Multiaddr) []ma.Multiaddr {\n\t// Add it for all the listenAddr first.\n\t// listenAddr maybe unspecified. That's okay as connections on UDP transports\n\t// will have the unspecified address as the local address.\n\tfor _, la := range listenAddrs {\n\t\tobsAddrs := a.observedAddrsManager.AddrsFor(la)\n\t\tif len(obsAddrs) > maxObservedAddrsPerListenAddr {\n\t\t\tobsAddrs = obsAddrs[:maxObservedAddrsPerListenAddr]\n\t\t}\n\t\tdst = append(dst, obsAddrs...)\n\t}\n\n\t// if it can be resolved into more addresses, add them too\n\tresolved, err := manet.ResolveUnspecifiedAddresses(listenAddrs, ifaceAddrs)\n\tif err != nil {\n\t\tlog.Warn(\"failed to resolve listen addr\", \"listen_addr\", listenAddrs, \"iface_addrs\", ifaceAddrs, \"err\", err)\n\t\treturn dst\n\t}\n\tfor _, addr := range resolved {\n\t\tobsAddrs := a.observedAddrsManager.AddrsFor(addr)\n\t\tif len(obsAddrs) > maxObservedAddrsPerListenAddr {\n\t\t\tobsAddrs = obsAddrs[:maxObservedAddrsPerListenAddr]\n\t\t}\n\t\tdst = append(dst, obsAddrs...)\n\t}\n\treturn dst\n}\n\n// makeSignedPeerRecord creates a signed peer record for the given addresses\nfunc (a *addrsManager) makeSignedPeerRecord(addrs []ma.Multiaddr) (*record.Envelope, error) {\n\tif a.signKey == nil {\n\t\treturn nil, errors.New(\"signKey is nil\")\n\t}\n\t// Limit the length of currentAddrs to ensure that our signed peer records aren't rejected\n\tpeerRecordSize := 64 // HostID\n\tk, err := a.signKey.Raw()\n\tvar nk int\n\tif err == nil {\n\t\tnk = len(k)\n\t} else {\n\t\tnk = 1024 // In case of error, use a large enough value.\n\t}\n\tpeerRecordSize += 2 * nk // 1 for signature, 1 for public key\n\t// we want the final address list to be small for keeping the signed peer record in size\n\taddrs = trimHostAddrList(addrs, maxPeerRecordSize-peerRecordSize-256) // 256 B of buffer\n\trec := peer.PeerRecordFromAddrInfo(peer.AddrInfo{\n\t\tID:    a.hostID,\n\t\tAddrs: addrs,\n\t})\n\treturn record.Seal(rec, a.signKey)\n}\n\n// emitLocalAddrsUpdated emits an EvtLocalAddressesUpdated event and updates the addresses in the peerstore.\nfunc (a *addrsManager) emitLocalAddrsUpdated(emitter event.Emitter, currentAddrs []ma.Multiaddr, lastAddrs []ma.Multiaddr) {\n\tadded, maintained, removed := diffAddrs(lastAddrs, currentAddrs)\n\tif len(added) == 0 && len(removed) == 0 {\n\t\treturn\n\t}\n\n\tvar sr *record.Envelope\n\tif a.signedRecordStore != nil {\n\t\tsr = a.signedRecordStore.GetPeerRecord(a.hostID)\n\t}\n\n\tevt := &event.EvtLocalAddressesUpdated{\n\t\tDiffs:            true,\n\t\tCurrent:          make([]event.UpdatedAddress, 0, len(currentAddrs)),\n\t\tRemoved:          make([]event.UpdatedAddress, 0, len(removed)),\n\t\tSignedPeerRecord: sr,\n\t}\n\n\tfor _, addr := range maintained {\n\t\tevt.Current = append(evt.Current, event.UpdatedAddress{\n\t\t\tAddress: addr,\n\t\t\tAction:  event.Maintained,\n\t\t})\n\t}\n\n\tfor _, addr := range added {\n\t\tevt.Current = append(evt.Current, event.UpdatedAddress{\n\t\t\tAddress: addr,\n\t\t\tAction:  event.Added,\n\t\t})\n\t}\n\n\tfor _, addr := range removed {\n\t\tevt.Removed = append(evt.Removed, event.UpdatedAddress{\n\t\t\tAddress: addr,\n\t\t\tAction:  event.Removed,\n\t\t})\n\t}\n\n\t// emit addr change event\n\tif err := emitter.Emit(*evt); err != nil {\n\t\tlog.Warn(\"error emitting event for updated addrs\", \"err\", err)\n\t}\n}\n\nfunc areAddrsDifferent(prev, current []ma.Multiaddr) bool {\n\t// TODO: make the sorted nature of ma.Unique a guarantee in multiaddrs\n\tprev = ma.Unique(prev)\n\tcurrent = ma.Unique(current)\n\tif len(prev) != len(current) {\n\t\treturn true\n\t}\n\tslices.SortFunc(prev, func(a, b ma.Multiaddr) int { return a.Compare(b) })\n\tslices.SortFunc(current, func(a, b ma.Multiaddr) int { return a.Compare(b) })\n\tfor i := range prev {\n\t\tif !prev[i].Equal(current[i]) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// diffAddrs diffs prev and current addrs and returns added, maintained, and removed addrs.\n// Both prev and current are expected to be sorted using ma.Compare()\nfunc diffAddrs(prev, current []ma.Multiaddr) (added, maintained, removed []ma.Multiaddr) {\n\ti, j := 0, 0\n\tfor i < len(prev) && j < len(current) {\n\t\tcmp := prev[i].Compare(current[j])\n\t\tswitch {\n\t\tcase cmp < 0:\n\t\t\t// prev < current\n\t\t\tremoved = append(removed, prev[i])\n\t\t\ti++\n\t\tcase cmp > 0:\n\t\t\t// current < prev\n\t\t\tadded = append(added, current[j])\n\t\t\tj++\n\t\tdefault:\n\t\t\tmaintained = append(maintained, current[j])\n\t\t\ti++\n\t\t\tj++\n\t\t}\n\t}\n\t// All remaining current addresses are added\n\tadded = append(added, current[j:]...)\n\n\t// All remaining previous addresses are removed\n\tremoved = append(removed, prev[i:]...)\n\treturn\n}\n\n// trimHostAddrList trims the address list to fit within the maximum size\nfunc trimHostAddrList(addrs []ma.Multiaddr, maxSize int) []ma.Multiaddr {\n\ttotalSize := 0\n\tfor _, a := range addrs {\n\t\ttotalSize += len(a.Bytes())\n\t}\n\tif totalSize <= maxSize {\n\t\treturn addrs\n\t}\n\n\tscore := func(addr ma.Multiaddr) int {\n\t\tvar res int\n\t\tif manet.IsPublicAddr(addr) {\n\t\t\tres |= 1 << 12\n\t\t} else if !manet.IsIPLoopback(addr) {\n\t\t\tres |= 1 << 11\n\t\t}\n\t\tvar protocolWeight int\n\t\tma.ForEach(addr, func(c ma.Component) bool {\n\t\t\tswitch c.Protocol().Code {\n\t\t\tcase ma.P_QUIC_V1:\n\t\t\t\tprotocolWeight = 5\n\t\t\tcase ma.P_TCP:\n\t\t\t\tprotocolWeight = 4\n\t\t\tcase ma.P_WSS:\n\t\t\t\tprotocolWeight = 3\n\t\t\tcase ma.P_WEBTRANSPORT:\n\t\t\t\tprotocolWeight = 2\n\t\t\tcase ma.P_WEBRTC_DIRECT:\n\t\t\t\tprotocolWeight = 1\n\t\t\tcase ma.P_P2P:\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn true\n\t\t})\n\t\tres |= 1 << protocolWeight\n\t\treturn res\n\t}\n\n\tslices.SortStableFunc(addrs, func(a, b ma.Multiaddr) int {\n\t\treturn score(b) - score(a) // b-a for reverse order\n\t})\n\ttotalSize = 0\n\tfor i, a := range addrs {\n\t\ttotalSize += len(a.Bytes())\n\t\tif totalSize > maxSize {\n\t\t\taddrs = addrs[:i]\n\t\t\tbreak\n\t\t}\n\t}\n\treturn addrs\n}\n\nconst interfaceAddrsCacheTTL = time.Minute\n\ntype interfaceAddrsCache struct {\n\tmx          sync.RWMutex\n\tall         []ma.Multiaddr\n\tlastUpdated time.Time\n}\n\nfunc (i *interfaceAddrsCache) All() []ma.Multiaddr {\n\ti.mx.RLock()\n\tif time.Now().After(i.lastUpdated.Add(interfaceAddrsCacheTTL)) {\n\t\ti.mx.RUnlock()\n\t\treturn i.update()\n\t}\n\tdefer i.mx.RUnlock()\n\treturn i.all\n}\n\nfunc (i *interfaceAddrsCache) update() []ma.Multiaddr {\n\ti.mx.Lock()\n\tdefer i.mx.Unlock()\n\tif !time.Now().After(i.lastUpdated.Add(interfaceAddrsCacheTTL)) {\n\t\treturn i.all\n\t}\n\ti.updateUnlocked()\n\ti.lastUpdated = time.Now()\n\treturn i.all\n}\n\nfunc (i *interfaceAddrsCache) updateUnlocked() {\n\ti.all = nil\n\n\tifaceAddrs, err := manet.InterfaceMultiaddrs()\n\tif err != nil {\n\t\t// This usually shouldn't happen, but we could be in some kind\n\t\t// of funky restricted environment.\n\t\tlog.Error(\"failed to resolve local interface addresses\", \"err\", err)\n\t\ti.all = []ma.Multiaddr{manet.IP4Loopback, manet.IP6Loopback}\n\t\treturn\n\t}\n\n\t// remove link local ipv6 addresses\n\ti.all = slices.DeleteFunc(ifaceAddrs, manet.IsIP6LinkLocal)\n}\n\n// removeNotInSource removes items from addrs that are not present in source.\n// Modifies the addrs slice in place\n// addrs and source must be sorted using multiaddr.Compare.\nfunc removeNotInSource(addrs, source []ma.Multiaddr) []ma.Multiaddr {\n\tj := 0\n\t// mark entries not in source as nil\n\tfor i, a := range addrs {\n\t\t// move right as long as a > source[j]\n\t\tfor j < len(source) && a.Compare(source[j]) > 0 {\n\t\t\tj++\n\t\t}\n\t\t// a is not in source if we've reached the end, or a is lesser\n\t\tif j == len(source) || a.Compare(source[j]) < 0 {\n\t\t\taddrs[i] = nil\n\t\t}\n\t\t// a is in source, nothing to do\n\t}\n\t// Move all the nils to the end.\n\t// j is the current element, i is lowest index of a nil element.\n\t// At the end of every iteration all elements from i to j are nil.\n\ti := 0\n\tfor j := range len(addrs) {\n\t\tif addrs[j] != nil {\n\t\t\taddrs[i], addrs[j] = addrs[j], addrs[i]\n\t\t\ti++\n\t\t}\n\t}\n\treturn addrs[:i]\n}\n\n// removeInSource removes items from addrs that are present in source.\n// Modifies the addrs slice in place\n// addrs and source must be sorted using multiaddr.Compare.\nfunc removeInSource(addrs, source []ma.Multiaddr) []ma.Multiaddr {\n\tj := 0\n\t// mark entries in source as nil\n\tfor i, a := range addrs {\n\t\t// move right in source as long as a > source[j]\n\t\tfor j < len(source) && a.Compare(source[j]) > 0 {\n\t\t\tj++\n\t\t}\n\t\t// a is in source,  mark nil\n\t\tif j < len(source) && a.Compare(source[j]) == 0 {\n\t\t\taddrs[i] = nil\n\t\t}\n\t}\n\t// Move all the nils to the end.\n\t// j is the current element, i is lowest index of a nil element.\n\t// At the end of every iteration all elements from i to j are nil.\n\ti := 0\n\tfor j := range len(addrs) {\n\t\tif addrs[j] != nil {\n\t\t\taddrs[i], addrs[j] = addrs[j], addrs[i]\n\t\t\ti++\n\t\t}\n\t}\n\treturn addrs[:i]\n}\n\ntype multiCloser []io.Closer\n\nfunc (mc *multiCloser) Close() error {\n\tvar errs []error\n\tfor _, closer := range *mc {\n\t\tif err := closer.Close(); err != nil {\n\t\t\tvar closerName string\n\t\t\tif named, ok := closer.(interface{ Name() string }); ok {\n\t\t\t\tcloserName = named.Name()\n\t\t\t} else {\n\t\t\t\tcloserName = fmt.Sprintf(\"%T\", closer)\n\t\t\t}\n\t\t\terrs = append(errs, fmt.Errorf(\"error closing %s: %w\", closerName, err))\n\t\t}\n\t}\n\treturn errors.Join(errs...)\n}\n"
  },
  {
    "path": "p2p/host/basic/addrs_manager_test.go",
    "content": "package basichost\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"fmt\"\n\t\"slices\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/event\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/eventbus\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoremem\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/autonatv2\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/multiformats/go-multiaddr/matest\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype mockNatManager struct {\n\tGetMappingFunc func(addr ma.Multiaddr) ma.Multiaddr\n}\n\nfunc (*mockNatManager) Close() error {\n\treturn nil\n}\n\nfunc (m *mockNatManager) GetMapping(addr ma.Multiaddr) ma.Multiaddr {\n\tif m.GetMappingFunc == nil {\n\t\treturn nil\n\t}\n\treturn m.GetMappingFunc(addr)\n}\n\nfunc (*mockNatManager) HasDiscoveredNAT() bool {\n\treturn true\n}\n\nvar _ NATManager = &mockNatManager{}\n\ntype mockObservedAddrs struct {\n\tAddrsFunc    func() []ma.Multiaddr\n\tAddrsForFunc func(ma.Multiaddr) []ma.Multiaddr\n}\n\nfunc (m *mockObservedAddrs) Addrs(int) []ma.Multiaddr { return m.AddrsFunc() }\n\nfunc (m *mockObservedAddrs) AddrsFor(local ma.Multiaddr) []ma.Multiaddr { return m.AddrsForFunc(local) }\n\nvar _ ObservedAddrsManager = &mockObservedAddrs{}\n\ntype addrStoreArgs struct {\n\tAddrStore               addrStore\n\tSignKey                 crypto.PrivKey\n\tHostID                  peer.ID\n\tDisableSignedPeerRecord bool\n}\n\ntype addrsManagerArgs struct {\n\tNATManager           NATManager\n\tAddrsFactory         AddrsFactory\n\tObservedAddrsManager ObservedAddrsManager\n\tListenAddrs          func() []ma.Multiaddr\n\tAddCertHashes        func([]ma.Multiaddr) []ma.Multiaddr\n\tAutoNATClient        autonatv2Client\n\tBus                  event.Bus\n\tAddrStoreArgs        addrStoreArgs\n}\n\ntype addrsManagerTestCase struct {\n\t*addrsManager\n\tPushRelay        func(relayAddrs []ma.Multiaddr)\n\tPushReachability func(rch network.Reachability)\n}\n\nfunc newAddrsManagerTestCase(tb testing.TB, args addrsManagerArgs) addrsManagerTestCase {\n\teb := args.Bus\n\tif eb == nil {\n\t\teb = eventbus.NewBus()\n\t}\n\tif args.AddrsFactory == nil {\n\t\targs.AddrsFactory = func(addrs []ma.Multiaddr) []ma.Multiaddr { return addrs }\n\t}\n\n\taddCertHashes := func(addrs []ma.Multiaddr) []ma.Multiaddr {\n\t\treturn addrs\n\t}\n\tif args.AddCertHashes != nil {\n\t\taddCertHashes = args.AddCertHashes\n\t}\n\tsignKey := args.AddrStoreArgs.SignKey\n\taddrStore := args.AddrStoreArgs.AddrStore\n\tpid := args.AddrStoreArgs.HostID\n\tif args.AddrStoreArgs == (addrStoreArgs{}) {\n\t\tvar err error\n\t\tsignKey, _, err = crypto.GenerateEd25519Key(rand.Reader)\n\t\trequire.NoError(tb, err)\n\t\taddrStore, err = pstoremem.NewPeerstore()\n\t\trequire.NoError(tb, err)\n\t\tpid, err = peer.IDFromPrivateKey(signKey)\n\t\trequire.NoError(tb, err)\n\t}\n\tam, err := newAddrsManager(\n\t\teb,\n\t\targs.NATManager,\n\t\targs.AddrsFactory,\n\t\targs.ListenAddrs,\n\t\taddCertHashes,\n\t\targs.ObservedAddrsManager,\n\t\targs.AutoNATClient,\n\t\ttrue,\n\t\tprometheus.DefaultRegisterer,\n\t\tfalse,\n\t\tsignKey,\n\t\taddrStore,\n\t\tpid,\n\t)\n\trequire.NoError(tb, err)\n\n\trequire.NoError(tb, am.Start())\n\traEm, err := eb.Emitter(new(event.EvtAutoRelayAddrsUpdated), eventbus.Stateful)\n\trequire.NoError(tb, err)\n\n\trchEm, err := eb.Emitter(new(event.EvtLocalReachabilityChanged), eventbus.Stateful)\n\trequire.NoError(tb, err)\n\n\ttb.Cleanup(am.Close)\n\treturn addrsManagerTestCase{\n\t\taddrsManager: am,\n\t\tPushRelay: func(relayAddrs []ma.Multiaddr) {\n\t\t\terr := raEm.Emit(event.EvtAutoRelayAddrsUpdated{RelayAddrs: relayAddrs})\n\t\t\trequire.NoError(tb, err)\n\t\t},\n\t\tPushReachability: func(rch network.Reachability) {\n\t\t\terr := rchEm.Emit(event.EvtLocalReachabilityChanged{Reachability: rch})\n\t\t\trequire.NoError(tb, err)\n\t\t},\n\t}\n}\n\nfunc TestAddrsManager(t *testing.T) {\n\tlhquic := ma.StringCast(\"/ip4/127.0.0.1/udp/1/quic-v1\")\n\tlhtcp := ma.StringCast(\"/ip4/127.0.0.1/tcp/1\")\n\n\tpublicQUIC := ma.StringCast(\"/ip4/1.2.3.4/udp/1/quic-v1\")\n\tpublicQUIC2 := ma.StringCast(\"/ip4/1.2.3.4/udp/2/quic-v1\")\n\tpublicTCP := ma.StringCast(\"/ip4/1.2.3.4/tcp/1\")\n\tprivQUIC := ma.StringCast(\"/ip4/100.100.100.101/udp/1/quic-v1\")\n\n\tt.Run(\"only nat\", func(t *testing.T) {\n\t\tam := newAddrsManagerTestCase(t, addrsManagerArgs{\n\t\t\tNATManager: &mockNatManager{\n\t\t\t\tGetMappingFunc: func(addr ma.Multiaddr) ma.Multiaddr {\n\t\t\t\t\tif _, err := addr.ValueForProtocol(ma.P_UDP); err == nil {\n\t\t\t\t\t\treturn publicQUIC\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tListenAddrs: func() []ma.Multiaddr { return []ma.Multiaddr{lhquic, lhtcp} },\n\t\t})\n\t\tam.updateAddrsSync()\n\t\trequire.EventuallyWithT(t, func(collect *assert.CollectT) {\n\t\t\texpected := []ma.Multiaddr{publicQUIC, lhquic, lhtcp}\n\t\t\tassert.ElementsMatch(collect, am.Addrs(), expected, \"%s\\n%s\", am.Addrs(), expected)\n\t\t}, 5*time.Second, 50*time.Millisecond)\n\t})\n\n\tt.Run(\"nat and observed addrs\", func(t *testing.T) {\n\t\t// nat mapping for udp, observed addrs for tcp\n\t\tam := newAddrsManagerTestCase(t, addrsManagerArgs{\n\t\t\tNATManager: &mockNatManager{\n\t\t\t\tGetMappingFunc: func(addr ma.Multiaddr) ma.Multiaddr {\n\t\t\t\t\tif _, err := addr.ValueForProtocol(ma.P_UDP); err == nil {\n\t\t\t\t\t\treturn privQUIC\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tObservedAddrsManager: &mockObservedAddrs{\n\t\t\t\tAddrsForFunc: func(addr ma.Multiaddr) []ma.Multiaddr {\n\t\t\t\t\tif _, err := addr.ValueForProtocol(ma.P_TCP); err == nil {\n\t\t\t\t\t\treturn []ma.Multiaddr{publicTCP}\n\t\t\t\t\t}\n\t\t\t\t\tif _, err := addr.ValueForProtocol(ma.P_UDP); err == nil {\n\t\t\t\t\t\treturn []ma.Multiaddr{publicQUIC2}\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tListenAddrs: func() []ma.Multiaddr { return []ma.Multiaddr{lhquic, lhtcp} },\n\t\t})\n\t\trequire.EventuallyWithT(t, func(collect *assert.CollectT) {\n\t\t\texpected := []ma.Multiaddr{lhquic, lhtcp, privQUIC, publicTCP, publicQUIC2}\n\t\t\tassert.ElementsMatch(collect, am.Addrs(), expected, \"%s\\n%s\", am.Addrs(), expected)\n\t\t}, 5*time.Second, 50*time.Millisecond)\n\t})\n\n\tt.Run(\"nat returns unspecified addr\", func(t *testing.T) {\n\t\tquicPort1 := ma.StringCast(\"/ip4/3.3.3.3/udp/1/quic-v1\")\n\t\t// port from nat, IP from observed addr\n\t\tam := newAddrsManagerTestCase(t, addrsManagerArgs{\n\t\t\tNATManager: &mockNatManager{\n\t\t\t\tGetMappingFunc: func(addr ma.Multiaddr) ma.Multiaddr {\n\t\t\t\t\tif addr.Equal(lhquic) {\n\t\t\t\t\t\treturn ma.StringCast(\"/ip4/0.0.0.0/udp/2/quic-v1\")\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tObservedAddrsManager: &mockObservedAddrs{\n\t\t\t\tAddrsForFunc: func(addr ma.Multiaddr) []ma.Multiaddr {\n\t\t\t\t\tif addr.Equal(lhquic) {\n\t\t\t\t\t\treturn []ma.Multiaddr{quicPort1}\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tListenAddrs: func() []ma.Multiaddr { return []ma.Multiaddr{lhquic} },\n\t\t})\n\t\texpected := []ma.Multiaddr{lhquic, quicPort1}\n\t\trequire.EventuallyWithT(t, func(collect *assert.CollectT) {\n\t\t\tassert.ElementsMatch(collect, am.Addrs(), expected, \"%s\\n%s\", am.Addrs(), expected)\n\t\t}, 5*time.Second, 50*time.Millisecond)\n\t})\n\tt.Run(\"only observed addrs\", func(t *testing.T) {\n\t\tam := newAddrsManagerTestCase(t, addrsManagerArgs{\n\t\t\tObservedAddrsManager: &mockObservedAddrs{\n\t\t\t\tAddrsForFunc: func(addr ma.Multiaddr) []ma.Multiaddr {\n\t\t\t\t\tif addr.Equal(lhtcp) {\n\t\t\t\t\t\treturn []ma.Multiaddr{publicTCP}\n\t\t\t\t\t}\n\t\t\t\t\tif addr.Equal(lhquic) {\n\t\t\t\t\t\treturn []ma.Multiaddr{publicQUIC}\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tListenAddrs: func() []ma.Multiaddr { return []ma.Multiaddr{lhquic, lhtcp} },\n\t\t})\n\t\tam.updateAddrsSync()\n\t\texpected := []ma.Multiaddr{lhquic, lhtcp, publicTCP, publicQUIC}\n\t\trequire.EventuallyWithT(t, func(collect *assert.CollectT) {\n\t\t\tassert.ElementsMatch(collect, am.Addrs(), expected, \"%s\\n%s\", am.Addrs(), expected)\n\t\t}, 5*time.Second, 50*time.Millisecond)\n\t})\n\n\tt.Run(\"observed addrs limit\", func(t *testing.T) {\n\t\tquicAddrs := []ma.Multiaddr{\n\t\t\tma.StringCast(\"/ip4/1.2.3.4/udp/1/quic-v1\"),\n\t\t\tma.StringCast(\"/ip4/1.2.3.4/udp/2/quic-v1\"),\n\t\t\tma.StringCast(\"/ip4/1.2.3.4/udp/3/quic-v1\"),\n\t\t\tma.StringCast(\"/ip4/1.2.3.4/udp/4/quic-v1\"),\n\t\t\tma.StringCast(\"/ip4/1.2.3.4/udp/5/quic-v1\"),\n\t\t\tma.StringCast(\"/ip4/1.2.3.4/udp/6/quic-v1\"),\n\t\t\tma.StringCast(\"/ip4/1.2.3.4/udp/7/quic-v1\"),\n\t\t\tma.StringCast(\"/ip4/1.2.3.4/udp/8/quic-v1\"),\n\t\t\tma.StringCast(\"/ip4/1.2.3.4/udp/9/quic-v1\"),\n\t\t\tma.StringCast(\"/ip4/1.2.3.4/udp/10/quic-v1\"),\n\t\t}\n\t\tam := newAddrsManagerTestCase(t, addrsManagerArgs{\n\t\t\tObservedAddrsManager: &mockObservedAddrs{\n\t\t\t\tAddrsForFunc: func(_ ma.Multiaddr) []ma.Multiaddr {\n\t\t\t\t\treturn quicAddrs\n\t\t\t\t},\n\t\t\t},\n\t\t\tListenAddrs: func() []ma.Multiaddr { return []ma.Multiaddr{lhquic} },\n\t\t})\n\t\tam.updateAddrsSync()\n\t\texpected := []ma.Multiaddr{lhquic}\n\t\texpected = append(expected, quicAddrs[:maxObservedAddrsPerListenAddr]...)\n\t\trequire.EventuallyWithT(t, func(collect *assert.CollectT) {\n\t\t\tmatest.AssertMultiaddrsMatch(collect, expected, am.Addrs())\n\t\t}, 2*time.Second, 50*time.Millisecond)\n\t})\n\tt.Run(\"public addrs removed when private\", func(t *testing.T) {\n\t\tam := newAddrsManagerTestCase(t, addrsManagerArgs{\n\t\t\tObservedAddrsManager: &mockObservedAddrs{\n\t\t\t\tAddrsForFunc: func(_ ma.Multiaddr) []ma.Multiaddr {\n\t\t\t\t\treturn []ma.Multiaddr{publicQUIC}\n\t\t\t\t},\n\t\t\t},\n\t\t\tListenAddrs: func() []ma.Multiaddr { return []ma.Multiaddr{lhquic, lhtcp} },\n\t\t})\n\n\t\t// remove public addrs\n\t\tam.PushReachability(network.ReachabilityPrivate)\n\t\trelayAddr := ma.StringCast(\"/ip4/1.2.3.4/udp/1/quic-v1/p2p/QmdXGaeGiVA745XorV1jr11RHxB9z4fqykm6xCUPX1aTJo/p2p-circuit\")\n\t\tam.PushRelay([]ma.Multiaddr{relayAddr})\n\n\t\texpectedAddrs := []ma.Multiaddr{relayAddr, lhquic, lhtcp}\n\t\texpectedAllAddrs := []ma.Multiaddr{publicQUIC, lhquic, lhtcp}\n\t\trequire.EventuallyWithT(t, func(collect *assert.CollectT) {\n\t\t\tassert.ElementsMatch(collect, am.Addrs(), expectedAddrs, \"%s\\n%s\", am.Addrs(), expectedAddrs)\n\t\t\tassert.ElementsMatch(collect, am.DirectAddrs(), expectedAllAddrs, \"%s\\n%s\", am.DirectAddrs(), expectedAllAddrs)\n\t\t}, 5*time.Second, 50*time.Millisecond)\n\n\t\t// add public addrs\n\t\tam.PushReachability(network.ReachabilityPublic)\n\n\t\texpectedAddrs = expectedAllAddrs\n\t\trequire.EventuallyWithT(t, func(collect *assert.CollectT) {\n\t\t\tassert.ElementsMatch(collect, am.Addrs(), expectedAddrs, \"%s\\n%s\", am.Addrs(), expectedAddrs)\n\t\t\tassert.ElementsMatch(collect, am.DirectAddrs(), expectedAllAddrs, \"%s\\n%s\", am.DirectAddrs(), expectedAllAddrs)\n\t\t}, 5*time.Second, 50*time.Millisecond)\n\t})\n\n\tt.Run(\"addrs factory gets relay addrs\", func(t *testing.T) {\n\t\trelayAddr := ma.StringCast(\"/ip4/1.2.3.4/udp/1/quic-v1/p2p/QmdXGaeGiVA745XorV1jr11RHxB9z4fqykm6xCUPX1aTJo/p2p-circuit\")\n\t\tpublicQUIC2 := ma.StringCast(\"/ip4/1.2.3.4/udp/2/quic-v1\")\n\t\tam := newAddrsManagerTestCase(t, addrsManagerArgs{\n\t\t\tAddrsFactory: func(addrs []ma.Multiaddr) []ma.Multiaddr {\n\t\t\t\tfor _, a := range addrs {\n\t\t\t\t\tif a.Equal(relayAddr) {\n\t\t\t\t\t\treturn []ma.Multiaddr{publicQUIC2}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tObservedAddrsManager: &mockObservedAddrs{\n\t\t\t\tAddrsForFunc: func(_ ma.Multiaddr) []ma.Multiaddr {\n\t\t\t\t\treturn []ma.Multiaddr{publicQUIC}\n\t\t\t\t},\n\t\t\t},\n\t\t\tListenAddrs: func() []ma.Multiaddr { return []ma.Multiaddr{lhquic, lhtcp} },\n\t\t})\n\t\tam.PushReachability(network.ReachabilityPrivate)\n\t\tam.PushRelay([]ma.Multiaddr{relayAddr})\n\n\t\texpectedAddrs := []ma.Multiaddr{publicQUIC2}\n\t\texpectedAllAddrs := []ma.Multiaddr{publicQUIC, lhquic, lhtcp}\n\t\trequire.EventuallyWithT(t, func(collect *assert.CollectT) {\n\t\t\tassert.ElementsMatch(collect, am.Addrs(), expectedAddrs, \"%s\\n%s\", am.Addrs(), expectedAddrs)\n\t\t\tassert.ElementsMatch(collect, am.DirectAddrs(), expectedAllAddrs, \"%s\\n%s\", am.DirectAddrs(), expectedAllAddrs)\n\t\t}, 5*time.Second, 50*time.Millisecond)\n\t})\n\n\tt.Run(\"updates addresses on signaling\", func(t *testing.T) {\n\t\tupdateChan := make(chan struct{})\n\t\tam := newAddrsManagerTestCase(t, addrsManagerArgs{\n\t\t\tAddrsFactory: func(_ []ma.Multiaddr) []ma.Multiaddr {\n\t\t\t\tselect {\n\t\t\t\tcase <-updateChan:\n\t\t\t\t\treturn []ma.Multiaddr{publicQUIC}\n\t\t\t\tdefault:\n\t\t\t\t\treturn []ma.Multiaddr{publicTCP}\n\t\t\t\t}\n\t\t\t},\n\t\t\tListenAddrs: func() []ma.Multiaddr { return []ma.Multiaddr{lhquic, lhtcp} },\n\t\t})\n\t\trequire.Contains(t, am.Addrs(), publicTCP)\n\t\trequire.NotContains(t, am.Addrs(), publicQUIC)\n\t\tclose(updateChan)\n\t\tam.updateAddrsSync()\n\t\trequire.EventuallyWithT(t, func(collect *assert.CollectT) {\n\t\t\tassert.Contains(collect, am.Addrs(), publicQUIC)\n\t\t\tassert.NotContains(collect, am.Addrs(), publicTCP)\n\t\t}, 1*time.Second, 50*time.Millisecond)\n\t})\n\n\tt.Run(\"addrs factory depends on confirmed addrs\", func(t *testing.T) {\n\t\tvar amp atomic.Pointer[addrsManager]\n\t\tq1 := ma.StringCast(\"/ip4/1.1.1.1/udp/1/quic-v1\")\n\t\taddrsFactory := func(_ []ma.Multiaddr) []ma.Multiaddr {\n\t\t\tif amp.Load() == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\t// r is empty as there's no reachability tracker\n\t\t\tr, _, _ := amp.Load().ConfirmedAddrs()\n\t\t\treturn append(r, q1)\n\t\t}\n\t\tam := newAddrsManagerTestCase(t, addrsManagerArgs{\n\t\t\tAddrsFactory: addrsFactory,\n\t\t\tListenAddrs:  func() []ma.Multiaddr { return []ma.Multiaddr{lhquic, lhtcp} },\n\t\t})\n\t\tamp.Store(am.addrsManager)\n\t\tam.updateAddrsSync()\n\t\tmatest.AssertEqualMultiaddrs(t, []ma.Multiaddr{q1}, am.Addrs())\n\t})\n}\n\nfunc TestAddrsManagerReachabilityEvent(t *testing.T) {\n\tpublicQUIC, _ := ma.NewMultiaddr(\"/ip4/1.2.3.4/udp/1234/quic-v1\")\n\tpublicQUIC2, _ := ma.NewMultiaddr(\"/ip4/1.2.3.4/udp/1235/quic-v1\")\n\tpublicTCP, _ := ma.NewMultiaddr(\"/ip4/1.2.3.4/tcp/1234\")\n\n\tbus := eventbus.NewBus()\n\n\tsub, err := bus.Subscribe(new(event.EvtHostReachableAddrsChanged))\n\trequire.NoError(t, err)\n\tdefer sub.Close()\n\n\tam := newAddrsManagerTestCase(t, addrsManagerArgs{\n\t\tBus: bus,\n\t\t// currently they aren't being passed to the reachability tracker\n\t\tListenAddrs: func() []ma.Multiaddr { return []ma.Multiaddr{publicQUIC, publicQUIC2, publicTCP} },\n\t\tAutoNATClient: mockAutoNATClient{\n\t\t\tF: func(_ context.Context, reqs []autonatv2.Request) (autonatv2.Result, error) {\n\t\t\t\tif reqs[0].Addr.Equal(publicQUIC) {\n\t\t\t\t\treturn autonatv2.Result{Addr: reqs[0].Addr, Idx: 0, Reachability: network.ReachabilityPublic}, nil\n\t\t\t\t} else if reqs[0].Addr.Equal(publicQUIC2) {\n\t\t\t\t\treturn autonatv2.Result{Addr: reqs[0].Addr, Idx: 0, Reachability: network.ReachabilityPrivate}, nil\n\t\t\t\t}\n\t\t\t\treturn autonatv2.Result{Addr: reqs[0].Addr, Idx: 0, Reachability: network.ReachabilityUnknown, AllAddrsRefused: true}, nil\n\t\t\t},\n\t\t},\n\t})\n\n\tinitialUnknownAddrs := []ma.Multiaddr{publicQUIC, publicTCP, publicQUIC2}\n\n\t// First event: all addresses are initially unknown\n\tselect {\n\tcase e := <-sub.Out():\n\t\tevt := e.(event.EvtHostReachableAddrsChanged)\n\t\trequire.Empty(t, evt.Reachable)\n\t\trequire.Empty(t, evt.Unreachable)\n\t\trequire.ElementsMatch(t, initialUnknownAddrs, evt.Unknown)\n\tcase <-time.After(5 * time.Second):\n\t\tt.Fatal(\"expected initial event for reachability change\")\n\t}\n\n\t// Wait for probes to complete and addresses to be classified\n\treachableAddrs := []ma.Multiaddr{publicQUIC}\n\tunreachableAddrs := []ma.Multiaddr{publicQUIC2}\n\tunknownAddrs := []ma.Multiaddr{publicTCP}\n\tselect {\n\tcase e := <-sub.Out():\n\t\tevt := e.(event.EvtHostReachableAddrsChanged)\n\t\tmatest.AssertMultiaddrsMatch(t, reachableAddrs, evt.Reachable)\n\t\tmatest.AssertMultiaddrsMatch(t, unreachableAddrs, evt.Unreachable)\n\t\tmatest.AssertMultiaddrsMatch(t, unknownAddrs, evt.Unknown)\n\t\treachable, unreachable, unknown := am.ConfirmedAddrs()\n\t\tmatest.AssertMultiaddrsMatch(t, reachableAddrs, reachable)\n\t\tmatest.AssertMultiaddrsMatch(t, unreachableAddrs, unreachable)\n\t\tmatest.AssertMultiaddrsMatch(t, unknownAddrs, unknown)\n\t\t// unreachable addrs should be removed\n\t\tmatest.AssertMultiaddrsMatch(t, []ma.Multiaddr{publicQUIC, publicTCP}, am.Addrs())\n\tcase <-time.After(5 * time.Second):\n\t\tt.Fatal(\"expected final event for reachability change after probing\")\n\t}\n}\n\nfunc TestAddrsManagerPeerstoreUpdated(t *testing.T) {\n\tquic1 := ma.StringCast(\"/ip4/1.2.3.4/udp/1234/quic-v1\")\n\tquic2 := ma.StringCast(\"/ip4/1.2.3.5/udp/1/quic-v1\")\n\n\tpstore, err := pstoremem.NewPeerstore()\n\trequire.NoError(t, err)\n\tcab, _ := peerstore.GetCertifiedAddrBook(pstore)\n\tsignKey, _, err := crypto.GenerateEd25519Key(rand.Reader)\n\trequire.NoError(t, err)\n\tpid, err := peer.IDFromPrivateKey(signKey)\n\trequire.NoError(t, err)\n\n\tvar update atomic.Bool\n\tam := newAddrsManagerTestCase(t, addrsManagerArgs{\n\t\tListenAddrs: func() []ma.Multiaddr { return nil },\n\t\tAddrsFactory: func([]ma.Multiaddr) []ma.Multiaddr {\n\t\t\tif !update.Load() {\n\t\t\t\treturn []ma.Multiaddr{quic1}\n\t\t\t}\n\t\t\treturn []ma.Multiaddr{quic2}\n\t\t},\n\t\tAddrStoreArgs: addrStoreArgs{\n\t\t\tAddrStore: pstore,\n\t\t\tHostID:    pid,\n\t\t\tSignKey:   signKey,\n\t\t},\n\t})\n\tdefer am.Close()\n\tmatest.AssertEqualMultiaddrs(t, []ma.Multiaddr{quic1}, pstore.Addrs(pid))\n\tev := cab.GetPeerRecord(pid)\n\tpr := peerRecordFromEnvelope(t, ev)\n\trequire.Equal(t, pr.Addrs, []ma.Multiaddr{quic1})\n\tupdate.Store(true)\n\tam.updateAddrsSync()\n\tmatest.AssertEqualMultiaddrs(t, []ma.Multiaddr{quic2}, pstore.Addrs(pid))\n\tev = cab.GetPeerRecord(pid)\n\tpr = peerRecordFromEnvelope(t, ev)\n\trequire.Equal(t, pr.Addrs, []ma.Multiaddr{quic2})\n\n}\n\nfunc TestRemoveIfNotInSource(t *testing.T) {\n\taddrs := make([]ma.Multiaddr, 0, 10)\n\tfor i := range 10 {\n\t\taddrs = append(addrs, ma.StringCast(fmt.Sprintf(\"/ip4/1.2.3.4/tcp/%d\", i)))\n\t}\n\tslices.SortFunc(addrs, func(a, b ma.Multiaddr) int { return a.Compare(b) })\n\tcases := []struct {\n\t\taddrs    []ma.Multiaddr\n\t\tsource   []ma.Multiaddr\n\t\texpected []ma.Multiaddr\n\t}{\n\t\t{},\n\t\t{addrs: slices.Clone(addrs[:5]), source: nil, expected: nil},\n\t\t{addrs: nil, source: addrs, expected: nil},\n\t\t{addrs: []ma.Multiaddr{addrs[0]}, source: []ma.Multiaddr{addrs[0]}, expected: []ma.Multiaddr{addrs[0]}},\n\t\t{addrs: slices.Clone(addrs), source: []ma.Multiaddr{addrs[0]}, expected: []ma.Multiaddr{addrs[0]}},\n\t\t{addrs: slices.Clone(addrs), source: slices.Clone(addrs[5:]), expected: slices.Clone(addrs[5:])},\n\t\t{addrs: slices.Clone(addrs[:5]), source: []ma.Multiaddr{addrs[0], addrs[2], addrs[8]}, expected: []ma.Multiaddr{addrs[0], addrs[2]}},\n\t}\n\tfor i, tc := range cases {\n\t\tt.Run(fmt.Sprintf(\"%d\", i), func(t *testing.T) {\n\t\t\taddrs := removeNotInSource(tc.addrs, tc.source)\n\t\t\trequire.ElementsMatch(t, tc.expected, addrs, \"%s\\n%s\", tc.expected, tc.addrs)\n\t\t})\n\t}\n}\n\nfunc BenchmarkAreAddrsDifferent(b *testing.B) {\n\tvar addrs [10]ma.Multiaddr\n\tfor i := range len(addrs) {\n\t\taddrs[i] = ma.StringCast(fmt.Sprintf(\"/ip4/1.1.1.%d/tcp/1\", i))\n\t}\n\tb.Run(\"areAddrsDifferent\", func(b *testing.B) {\n\t\tb.ReportAllocs()\n\t\tb.ResetTimer()\n\t\tfor i := 0; i < b.N; i++ {\n\t\t\tareAddrsDifferent(addrs[:], addrs[:])\n\t\t}\n\t})\n}\n\nfunc BenchmarkRemoveIfNotInSource(b *testing.B) {\n\tvar addrs [10]ma.Multiaddr\n\tfor i := range len(addrs) {\n\t\taddrs[i] = ma.StringCast(fmt.Sprintf(\"/ip4/1.1.1.%d/tcp/1\", i))\n\t}\n\tb.ReportAllocs()\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tremoveNotInSource(slices.Clone(addrs[:5]), addrs[:])\n\t}\n}\n"
  },
  {
    "path": "p2p/host/basic/addrs_metrics.go",
    "content": "package basichost\n\nimport (\n\t\"maps\"\n\n\t\"github.com/libp2p/go-libp2p/p2p/metricshelper\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\n\t\"github.com/prometheus/client_golang/prometheus\"\n)\n\nconst metricNamespace = \"libp2p_host_addrs\"\n\nvar (\n\treachableAddrs = prometheus.NewGaugeVec(\n\t\tprometheus.GaugeOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"reachable\",\n\t\t\tHelp:      \"Number of reachable addresses by transport\",\n\t\t},\n\t\t[]string{\"ipv\", \"transport\"},\n\t)\n\tunreachableAddrs = prometheus.NewGaugeVec(\n\t\tprometheus.GaugeOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"unreachable\",\n\t\t\tHelp:      \"Number of unreachable addresses by transport\",\n\t\t},\n\t\t[]string{\"ipv\", \"transport\"},\n\t)\n\tunknownAddrs = prometheus.NewGaugeVec(\n\t\tprometheus.GaugeOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"unknown\",\n\t\t\tHelp:      \"Number of addresses with unknown reachability by transport\",\n\t\t},\n\t\t[]string{\"ipv\", \"transport\"},\n\t)\n\tcollectors = []prometheus.Collector{\n\t\treachableAddrs,\n\t\tunreachableAddrs,\n\t\tunknownAddrs,\n\t}\n)\n\n// MetricsTracker tracks autonatv2 reachability metrics\ntype MetricsTracker interface {\n\t// ConfirmedAddrsChanged updates metrics with current address reachability status\n\tConfirmedAddrsChanged(reachable, unreachable, unknown []ma.Multiaddr)\n\t// ReachabilityTrackerClosed updated metrics on host close\n\tReachabilityTrackerClosed()\n}\n\ntype metricsTracker struct {\n\tprevReachableCounts   map[metricKey]int\n\tprevUnreachableCounts map[metricKey]int\n\tprevUnknownCounts     map[metricKey]int\n\tcurrentReachable      map[metricKey]int\n\tcurrentUnreachable    map[metricKey]int\n\tcurrentUnknown        map[metricKey]int\n}\n\nvar _ MetricsTracker = &metricsTracker{}\n\ntype metricsTrackerSetting struct {\n\treg prometheus.Registerer\n}\n\ntype metricsTrackerOption func(*metricsTrackerSetting)\n\n// withRegisterer sets the prometheus registerer for the metrics\nfunc withRegisterer(reg prometheus.Registerer) metricsTrackerOption {\n\treturn func(s *metricsTrackerSetting) {\n\t\tif reg != nil {\n\t\t\ts.reg = reg\n\t\t}\n\t}\n}\n\ntype metricKey struct {\n\tipv       string\n\ttransport string\n}\n\n// newMetricsTracker creates a new metrics tracker for autonatv2\nfunc newMetricsTracker(opts ...metricsTrackerOption) MetricsTracker {\n\tsetting := &metricsTrackerSetting{reg: prometheus.DefaultRegisterer}\n\tfor _, opt := range opts {\n\t\topt(setting)\n\t}\n\tmetricshelper.RegisterCollectors(setting.reg, collectors...)\n\treturn &metricsTracker{\n\t\tprevReachableCounts:   make(map[metricKey]int),\n\t\tprevUnreachableCounts: make(map[metricKey]int),\n\t\tprevUnknownCounts:     make(map[metricKey]int),\n\t\tcurrentReachable:      make(map[metricKey]int),\n\t\tcurrentUnreachable:    make(map[metricKey]int),\n\t\tcurrentUnknown:        make(map[metricKey]int),\n\t}\n}\n\nfunc (t *metricsTracker) ReachabilityTrackerClosed() {\n\tresetMetric(reachableAddrs, t.currentReachable, t.prevReachableCounts)\n\tresetMetric(unreachableAddrs, t.currentUnreachable, t.prevUnreachableCounts)\n\tresetMetric(unknownAddrs, t.currentUnknown, t.prevUnknownCounts)\n}\n\n// ConfirmedAddrsChanged updates the metrics with current address reachability counts by transport\nfunc (t *metricsTracker) ConfirmedAddrsChanged(reachable, unreachable, unknown []ma.Multiaddr) {\n\tupdateMetric(reachableAddrs, reachable, t.currentReachable, t.prevReachableCounts)\n\tupdateMetric(unreachableAddrs, unreachable, t.currentUnreachable, t.prevUnreachableCounts)\n\tupdateMetric(unknownAddrs, unknown, t.currentUnknown, t.prevUnknownCounts)\n}\n\nfunc updateMetric(metric *prometheus.GaugeVec, addrs []ma.Multiaddr, current map[metricKey]int, prev map[metricKey]int) {\n\tclear(prev)\n\tmaps.Copy(prev, current)\n\tclear(current)\n\tfor _, addr := range addrs {\n\t\ttransport := metricshelper.GetTransport(addr)\n\t\tipv := metricshelper.GetIPVersion(addr)\n\t\tkey := metricKey{ipv: ipv, transport: transport}\n\t\tcurrent[key]++\n\t}\n\n\ttags := metricshelper.GetStringSlice()\n\tdefer metricshelper.PutStringSlice(tags)\n\n\tfor k, v := range current {\n\t\t*tags = append(*tags, k.ipv, k.transport)\n\t\tmetric.WithLabelValues(*tags...).Set(float64(v))\n\t\t*tags = (*tags)[:0]\n\t}\n\tfor k := range prev {\n\t\tif _, ok := current[k]; ok {\n\t\t\tcontinue\n\t\t}\n\t\t*tags = append(*tags, k.ipv, k.transport)\n\t\tmetric.WithLabelValues(*tags...).Set(0)\n\t\t*tags = (*tags)[:0]\n\t}\n}\n\nfunc resetMetric(metric *prometheus.GaugeVec, current map[metricKey]int, prev map[metricKey]int) {\n\ttags := metricshelper.GetStringSlice()\n\tdefer metricshelper.PutStringSlice(tags)\n\tfor k := range current {\n\t\t*tags = append(*tags, k.ipv, k.transport)\n\t\tmetric.WithLabelValues(*tags...).Set(0)\n\t\t*tags = (*tags)[:0]\n\t}\n\tclear(current)\n\tclear(prev)\n}\n"
  },
  {
    "path": "p2p/host/basic/addrs_metrics_test.go",
    "content": "//go:build nocover\n\npackage basichost\n\nimport (\n\t\"math/rand\"\n\t\"testing\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n)\n\nfunc TestMetricsNoAllocNoCover(t *testing.T) {\n\taddrs := []ma.Multiaddr{\n\t\tma.StringCast(\"/ip4/1.2.3.4/tcp/1\"),\n\t\tma.StringCast(\"/ip4/1.2.3.4/tcp/2\"),\n\t\tma.StringCast(\"/ip4/1.2.3.4/udp/2345/quic\"),\n\t\tma.StringCast(\"/ip4/1.2.3.4/udp/2346/webrtc-direct\"),\n\t\tma.StringCast(\"/ip4/1.2.3.4/tcp/80/ws\"),\n\t\tma.StringCast(\"/ip4/1.2.3.4/tcp/443/wss\"),\n\t\tma.StringCast(\"/ip4/1.2.3.4/udp/443/quic-v1/webtransport\"),\n\t}\n\n\trandAddrs := func() []ma.Multiaddr {\n\t\tn := rand.Intn(len(addrs))\n\t\tk := n + rand.Intn(len(addrs)-n)\n\t\treturn addrs[n:k]\n\t}\n\n\tmt := newMetricsTracker(withRegisterer(prometheus.DefaultRegisterer))\n\ttests := map[string]func(){\n\t\t\"ConfirmedAddrsChanged\": func() {\n\t\t\tmt.ConfirmedAddrsChanged(randAddrs(), randAddrs(), randAddrs())\n\t\t},\n\t\t\"ReachabilityTrackerClosed\": func() {\n\t\t\tmt.ReachabilityTrackerClosed()\n\t\t},\n\t}\n\n\tfor method, f := range tests {\n\t\tallocs := testing.AllocsPerRun(1000, f)\n\t\tif allocs > 0 {\n\t\t\tt.Fatalf(\"Alloc Test: %s, got: %0.2f, expected: 0 allocs\", method, allocs)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "p2p/host/basic/addrs_reachability_tracker.go",
    "content": "package basichost\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"slices\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/benbjohnson/clock\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/autonatv2\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\ntype autonatv2Client interface {\n\tGetReachability(ctx context.Context, reqs []autonatv2.Request) (autonatv2.Result, error)\n}\n\nconst (\n\n\t// maxAddrsPerRequest is the maximum number of addresses to probe in a single request\n\tmaxAddrsPerRequest = 10\n\t// maxTrackedAddrs is the maximum number of addresses to track\n\t// 10 addrs per transport for 5 transports\n\tmaxTrackedAddrs = 50\n\t// defaultMaxConcurrency is the default number of concurrent workers for reachability checks\n\tdefaultMaxConcurrency = 5\n\t// newAddrsProbeDelay is the delay before probing new addr's reachability.\n\tnewAddrsProbeDelay = 1 * time.Second\n)\n\n// addrsReachabilityTracker tracks reachability for addresses.\n// Use UpdateAddrs to provide addresses for tracking reachability.\n// reachabilityUpdateCh is notified when reachability for any of the tracked address changes.\ntype addrsReachabilityTracker struct {\n\tctx    context.Context\n\tcancel context.CancelFunc\n\twg     sync.WaitGroup\n\n\tclient autonatv2Client\n\t// reachabilityUpdateCh is used to notify when reachability may have changed\n\treachabilityUpdateCh chan struct{}\n\tmaxConcurrency       int\n\tnewAddrsProbeDelay   time.Duration\n\tprobeManager         *probeManager\n\tnewAddrs             chan []ma.Multiaddr\n\tclock                clock.Clock\n\tmetricsTracker       MetricsTracker\n\n\tmx               sync.Mutex\n\treachableAddrs   []ma.Multiaddr\n\tunreachableAddrs []ma.Multiaddr\n\tunknownAddrs     []ma.Multiaddr\n}\n\n// newAddrsReachabilityTracker returns a new addrsReachabilityTracker.\n// reachabilityUpdateCh is notified when reachability for any of the tracked address changes.\nfunc newAddrsReachabilityTracker(client autonatv2Client, reachabilityUpdateCh chan struct{}, cl clock.Clock, metricsTracker MetricsTracker) *addrsReachabilityTracker {\n\tctx, cancel := context.WithCancel(context.Background())\n\tif cl == nil {\n\t\tcl = clock.New()\n\t}\n\treturn &addrsReachabilityTracker{\n\t\tctx:                  ctx,\n\t\tcancel:               cancel,\n\t\tclient:               client,\n\t\treachabilityUpdateCh: reachabilityUpdateCh,\n\t\tprobeManager:         newProbeManager(cl.Now),\n\t\tnewAddrsProbeDelay:   newAddrsProbeDelay,\n\t\tmaxConcurrency:       defaultMaxConcurrency,\n\t\tnewAddrs:             make(chan []ma.Multiaddr, 1),\n\t\tclock:                cl,\n\t\tmetricsTracker:       metricsTracker,\n\t}\n}\n\nfunc (r *addrsReachabilityTracker) UpdateAddrs(addrs []ma.Multiaddr) {\n\tselect {\n\tcase r.newAddrs <- slices.Clone(addrs):\n\tcase <-r.ctx.Done():\n\t}\n}\n\nfunc (r *addrsReachabilityTracker) ConfirmedAddrs() (reachableAddrs, unreachableAddrs, unknownAddrs []ma.Multiaddr) {\n\tr.mx.Lock()\n\tdefer r.mx.Unlock()\n\treturn slices.Clone(r.reachableAddrs), slices.Clone(r.unreachableAddrs), slices.Clone(r.unknownAddrs)\n}\n\nfunc (r *addrsReachabilityTracker) Start() error {\n\tr.wg.Add(1)\n\tgo r.background()\n\treturn nil\n}\n\nfunc (r *addrsReachabilityTracker) Close() error {\n\tr.cancel()\n\tr.wg.Wait()\n\treturn nil\n}\n\nconst (\n\t// defaultReachabilityRefreshInterval is the default interval to refresh reachability.\n\t// In steady state, we check for any required probes every refresh interval.\n\t// This doesn't mean we'll probe for any particular address, only that we'll check\n\t// if any address needs to be probed.\n\tdefaultReachabilityRefreshInterval = 5 * time.Minute\n\t// maxBackoffInterval is the maximum back off in case we're unable to probe for reachability.\n\t// We may be unable to confirm addresses in case there are no valid peers with autonatv2\n\t// or the autonatv2 subsystem is consistently erroring.\n\tmaxBackoffInterval = 5 * time.Minute\n\t// backoffStartInterval is the initial back off in case we're unable to probe for reachability.\n\tbackoffStartInterval = 5 * time.Second\n)\n\nfunc (r *addrsReachabilityTracker) background() {\n\tdefer r.wg.Done()\n\n\t// probeTicker is used to trigger probes at regular intervals\n\tprobeTicker := r.clock.Ticker(defaultReachabilityRefreshInterval)\n\tdefer probeTicker.Stop()\n\n\t// probeTimer is used to trigger probes at specific times\n\tprobeTimer := r.clock.Timer(time.Duration(math.MaxInt64))\n\tdefer probeTimer.Stop()\n\tnextProbeTime := time.Time{}\n\n\tvar task reachabilityTask\n\tvar backoffInterval time.Duration\n\tvar currReachable, currUnreachable, currUnknown, prevReachable, prevUnreachable, prevUnknown []ma.Multiaddr\n\tfor {\n\t\tselect {\n\t\tcase <-probeTicker.C:\n\t\t\t// don't start a probe if we have a scheduled probe\n\t\t\tif task.BackoffCh == nil && nextProbeTime.IsZero() {\n\t\t\t\ttask = r.refreshReachability()\n\t\t\t}\n\t\tcase <-probeTimer.C:\n\t\t\tif task.BackoffCh == nil {\n\t\t\t\ttask = r.refreshReachability()\n\t\t\t}\n\t\t\tnextProbeTime = time.Time{}\n\t\tcase backoff := <-task.BackoffCh:\n\t\t\ttask = reachabilityTask{}\n\t\t\t// On completion, start the next probe immediately, or wait for backoff.\n\t\t\t// In case there are no further probes, the reachability tracker will return an empty task,\n\t\t\t// which hangs forever. Eventually, we'll refresh again when the ticker fires.\n\t\t\tif backoff {\n\t\t\t\tbackoffInterval = newBackoffInterval(backoffInterval)\n\t\t\t} else {\n\t\t\t\tbackoffInterval = -1 * time.Second // negative to trigger next probe immediately\n\t\t\t}\n\t\t\tnextProbeTime = r.clock.Now().Add(backoffInterval)\n\t\tcase addrs := <-r.newAddrs:\n\t\t\tif task.BackoffCh != nil { // cancel running task.\n\t\t\t\ttask.Cancel()\n\t\t\t\t<-task.BackoffCh // ignore backoff from cancelled task\n\t\t\t\ttask = reachabilityTask{}\n\t\t\t}\n\t\t\tr.updateTrackedAddrs(addrs)\n\t\t\tnewAddrsNextTime := r.clock.Now().Add(r.newAddrsProbeDelay)\n\t\t\tif nextProbeTime.Before(newAddrsNextTime) {\n\t\t\t\tnextProbeTime = newAddrsNextTime\n\t\t\t}\n\t\tcase <-r.ctx.Done():\n\t\t\tif task.BackoffCh != nil {\n\t\t\t\ttask.Cancel()\n\t\t\t\t<-task.BackoffCh\n\t\t\t\ttask = reachabilityTask{}\n\t\t\t}\n\t\t\tif r.metricsTracker != nil {\n\t\t\t\tr.metricsTracker.ReachabilityTrackerClosed()\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tcurrReachable, currUnreachable, currUnknown = r.appendConfirmedAddrs(currReachable[:0], currUnreachable[:0], currUnknown[:0])\n\t\tif areAddrsDifferent(prevReachable, currReachable) || areAddrsDifferent(prevUnreachable, currUnreachable) || areAddrsDifferent(prevUnknown, currUnknown) {\n\t\t\tif r.metricsTracker != nil {\n\t\t\t\tr.metricsTracker.ConfirmedAddrsChanged(currReachable, currUnreachable, currUnknown)\n\t\t\t}\n\t\t\tr.notify()\n\t\t}\n\t\tprevReachable = append(prevReachable[:0], currReachable...)\n\t\tprevUnreachable = append(prevUnreachable[:0], currUnreachable...)\n\t\tprevUnknown = append(prevUnknown[:0], currUnknown...)\n\t\tif !nextProbeTime.IsZero() {\n\t\t\tprobeTimer.Reset(nextProbeTime.Sub(r.clock.Now()))\n\t\t}\n\t}\n}\n\nfunc newBackoffInterval(current time.Duration) time.Duration {\n\tif current <= 0 {\n\t\treturn backoffStartInterval\n\t}\n\tcurrent *= 2\n\tif current > maxBackoffInterval {\n\t\treturn maxBackoffInterval\n\t}\n\treturn current\n}\n\nfunc (r *addrsReachabilityTracker) appendConfirmedAddrs(reachable, unreachable, unknown []ma.Multiaddr) (reachableAddrs, unreachableAddrs, unknownAddrs []ma.Multiaddr) {\n\treachable, unreachable, unknown = r.probeManager.AppendConfirmedAddrs(reachable, unreachable, unknown)\n\tr.mx.Lock()\n\tr.reachableAddrs = append(r.reachableAddrs[:0], reachable...)\n\tr.unreachableAddrs = append(r.unreachableAddrs[:0], unreachable...)\n\tr.unknownAddrs = append(r.unknownAddrs[:0], unknown...)\n\tr.mx.Unlock()\n\n\treturn reachable, unreachable, unknown\n}\n\nfunc (r *addrsReachabilityTracker) notify() {\n\tselect {\n\tcase r.reachabilityUpdateCh <- struct{}{}:\n\tdefault:\n\t}\n}\n\nfunc (r *addrsReachabilityTracker) updateTrackedAddrs(addrs []ma.Multiaddr) {\n\taddrs = slices.DeleteFunc(addrs, func(a ma.Multiaddr) bool {\n\t\treturn !manet.IsPublicAddr(a)\n\t})\n\tif len(addrs) > maxTrackedAddrs {\n\t\tlog.Error(\"too many addresses for addrs reachability tracker; dropping some\", \"total\", len(addrs), \"max\", maxTrackedAddrs, \"dropping\", len(addrs)-maxTrackedAddrs)\n\t\taddrs = addrs[:maxTrackedAddrs]\n\t}\n\tr.probeManager.UpdateAddrs(addrs)\n}\n\ntype probe = []autonatv2.Request\n\nconst probeTimeout = 30 * time.Second\n\n// reachabilityTask is a task to refresh reachability.\n// Waiting on the zero value blocks forever.\ntype reachabilityTask struct {\n\tCancel context.CancelFunc\n\t// BackoffCh returns whether the caller should backoff before\n\t// refreshing reachability\n\tBackoffCh chan bool\n}\n\nfunc (r *addrsReachabilityTracker) refreshReachability() reachabilityTask {\n\tif len(r.probeManager.GetProbe()) == 0 {\n\t\treturn reachabilityTask{}\n\t}\n\tresCh := make(chan bool, 1)\n\tctx, cancel := context.WithTimeout(r.ctx, 5*time.Minute)\n\tr.wg.Add(1)\n\t// We run probes provided by addrsTracker. It stops probing when any\n\t// of the following happens:\n\t// - there are no more probes to run\n\t// - context is completed\n\t// - there are too many consecutive failures from the client\n\t// - the client has no valid peers to probe\n\tgo func() {\n\t\tdefer r.wg.Done()\n\t\tdefer cancel()\n\t\tclient := &errCountingClient{autonatv2Client: r.client, MaxConsecutiveErrors: maxConsecutiveErrors}\n\t\tvar backoff atomic.Bool\n\t\tvar wg sync.WaitGroup\n\t\twg.Add(r.maxConcurrency)\n\t\tfor range r.maxConcurrency {\n\t\t\tgo func() {\n\t\t\t\tdefer wg.Done()\n\t\t\t\tfor {\n\t\t\t\t\tif ctx.Err() != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\treqs := r.probeManager.GetProbe()\n\t\t\t\t\tif len(reqs) == 0 {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tr.probeManager.MarkProbeInProgress(reqs)\n\t\t\t\t\trctx, cancel := context.WithTimeout(ctx, probeTimeout)\n\t\t\t\t\tres, err := client.GetReachability(rctx, reqs)\n\t\t\t\t\tcancel()\n\t\t\t\t\tr.probeManager.CompleteProbe(reqs, res, err)\n\t\t\t\t\tif isErrorPersistent(err) {\n\t\t\t\t\t\tbackoff.Store(true)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}()\n\t\t}\n\t\twg.Wait()\n\t\tresCh <- backoff.Load()\n\t}()\n\treturn reachabilityTask{Cancel: cancel, BackoffCh: resCh}\n}\n\nvar errTooManyConsecutiveFailures = errors.New(\"too many consecutive failures\")\n\n// errCountingClient counts errors from autonatv2Client and wraps the errors in response with a\n// errTooManyConsecutiveFailures in case of persistent failures from autonatv2 module.\ntype errCountingClient struct {\n\tautonatv2Client\n\tMaxConsecutiveErrors int\n\tmx                   sync.Mutex\n\tconsecutiveErrors    int\n}\n\nfunc (c *errCountingClient) GetReachability(ctx context.Context, reqs probe) (autonatv2.Result, error) {\n\tres, err := c.autonatv2Client.GetReachability(ctx, reqs)\n\tc.mx.Lock()\n\tdefer c.mx.Unlock()\n\tif err != nil && !errors.Is(err, context.Canceled) { // ignore canceled errors, they're not errors from autonatv2\n\t\tc.consecutiveErrors++\n\t\tif c.consecutiveErrors > c.MaxConsecutiveErrors {\n\t\t\terr = fmt.Errorf(\"%w:%w\", errTooManyConsecutiveFailures, err)\n\t\t}\n\t\tif errors.Is(err, autonatv2.ErrPrivateAddrs) {\n\t\t\tlog.Error(\"private IP addr in autonatv2 request\", \"err\", err)\n\t\t}\n\t} else {\n\t\tc.consecutiveErrors = 0\n\t}\n\treturn res, err\n}\n\nconst maxConsecutiveErrors = 20\n\n// isErrorPersistent returns whether the error will repeat on future probes for a while\nfunc isErrorPersistent(err error) bool {\n\tif err == nil {\n\t\treturn false\n\t}\n\treturn errors.Is(err, autonatv2.ErrPrivateAddrs) || errors.Is(err, autonatv2.ErrNoPeers) ||\n\t\terrors.Is(err, errTooManyConsecutiveFailures)\n}\n\nconst (\n\t// recentProbeInterval is the interval to probe addresses that have been refused\n\t// these are generally addresses with newer transports for which we don't have many peers\n\t// capable of dialing the transport\n\trecentProbeInterval = 10 * time.Minute\n\t// maxConsecutiveRefusals is the maximum number of consecutive refusals for an address after which\n\t// we wait for `recentProbeInterval` before probing again\n\tmaxConsecutiveRefusals = 5\n\t// maxRecentDialsPerAddr is the maximum number of dials on an address before we stop probing for the address.\n\t// This is used to prevent infinite probing of an address whose status is indeterminate for any reason.\n\tmaxRecentDialsPerAddr = 10\n\t// confidence is the absolute difference between the number of successes and failures for an address\n\t// targetConfidence is the confidence threshold for an address after which we wait for `maxProbeInterval`\n\t// before probing again.\n\ttargetConfidence = 3\n\t// minConfidence is the confidence threshold for an address to be considered reachable or unreachable\n\t// confidence is the absolute difference between the number of successes and failures for an address\n\tminConfidence = 2\n\t// maxRecentDialsWindow is the maximum number of recent probe results to consider for a single address\n\t//\n\t// +2 allows for 1 invalid probe result. Consider a string of successes, after which we have a single failure\n\t// and then a success(...S S S S F S). The confidence in the targetConfidence window  will be equal to\n\t// targetConfidence, the last F and S cancel each other, and we won't probe again for maxProbeInterval.\n\tmaxRecentDialsWindow = targetConfidence + 2\n\t// highConfidenceAddrProbeInterval is the maximum interval between probes for an address\n\thighConfidenceAddrProbeInterval = 1 * time.Hour\n\t// highConfidenceSecondaryAddrProbeInterval is the maximum interval between probes for an address\n\thighConfidenceSecondaryAddrProbeInterval = 3 * time.Hour\n\t// maxProbeResultTTL is the maximum time to keep probe results for a primary address\n\tmaxProbeResultTTL = maxRecentDialsWindow * highConfidenceAddrProbeInterval\n)\n\n// probeManager tracks reachability for a set of addresses by periodically probing reachability with autonatv2.\n// A Probe is a list of addresses which can be tested for reachability with autonatv2.\n// This struct decides the priority order of addresses for testing reachability, and throttles in case there have\n// been too many probes for an address in the `ProbeInterval`.\n//\n// Use the `runProbes` function to execute the probes with an autonatv2 client.\ntype probeManager struct {\n\tnow func() time.Time\n\n\tmx                    sync.Mutex\n\tinProgressProbes      map[string]int // addr -> count\n\tinProgressProbesTotal int\n\tstatuses              map[string]*addrStatus\n\tprimaryAddrs          []ma.Multiaddr\n\tsecondaryAddrs        []ma.Multiaddr\n}\n\n// newProbeManager creates a new probe manager.\nfunc newProbeManager(now func() time.Time) *probeManager {\n\treturn &probeManager{\n\t\tstatuses:         make(map[string]*addrStatus),\n\t\tinProgressProbes: make(map[string]int),\n\t\tnow:              now,\n\t}\n}\n\n// AppendConfirmedAddrs appends the current confirmed reachable and unreachable addresses.\nfunc (m *probeManager) AppendConfirmedAddrs(reachable, unreachable, unknown []ma.Multiaddr) (reachableAddrs, unreachableAddrs, unknownAddrs []ma.Multiaddr) {\n\tm.mx.Lock()\n\tdefer m.mx.Unlock()\n\n\tfor _, a := range m.primaryAddrs {\n\t\ts := m.statuses[string(a.Bytes())]\n\t\ts.RemoveBefore(m.now().Add(-maxProbeResultTTL)) // cleanup stale results\n\t\tswitch s.Reachability() {\n\t\tcase network.ReachabilityPublic:\n\t\t\treachable = append(reachable, a)\n\t\tcase network.ReachabilityPrivate:\n\t\t\tunreachable = append(unreachable, a)\n\t\tcase network.ReachabilityUnknown:\n\t\t\tunknown = append(unknown, a)\n\t\t}\n\t}\n\n\tfor _, a := range m.secondaryAddrs {\n\t\ts := m.statuses[string(a.Bytes())]\n\t\ts.RemoveBefore(m.now().Add(-maxProbeResultTTL)) // cleanup stale results\n\t\tswitch s.Reachability() {\n\t\tcase network.ReachabilityPublic:\n\t\t\treachable = append(reachable, a)\n\t\tcase network.ReachabilityPrivate:\n\t\t\tunreachable = append(unreachable, a)\n\t\tcase network.ReachabilityUnknown:\n\t\t\tunknown = append(unknown, a)\n\t\t}\n\t}\n\treturn reachable, unreachable, unknown\n}\n\n// UpdateAddrs updates the tracked addrs\nfunc (m *probeManager) UpdateAddrs(addrs []ma.Multiaddr) {\n\tm.mx.Lock()\n\tdefer m.mx.Unlock()\n\n\tslices.SortFunc(addrs, func(a, b ma.Multiaddr) int { return a.Compare(b) })\n\tstatuses := make(map[string]*addrStatus, len(addrs))\n\tfor _, addr := range addrs {\n\t\tk := string(addr.Bytes())\n\t\tif _, ok := m.statuses[k]; !ok {\n\t\t\tstatuses[k] = &addrStatus{Addr: addr}\n\t\t} else {\n\t\t\tstatuses[k] = m.statuses[k]\n\t\t\t// our addresses have changed, we may have removed the primary address\n\t\t\tstatuses[k].primary = nil\n\t\t}\n\t}\n\tassignPrimaryAddrs(statuses)\n\tm.primaryAddrs = m.primaryAddrs[:0]\n\tm.secondaryAddrs = m.secondaryAddrs[:0]\n\tfor _, a := range addrs {\n\t\tif statuses[string(a.Bytes())].primary == nil {\n\t\t\tm.primaryAddrs = append(m.primaryAddrs, a)\n\t\t} else {\n\t\t\tm.secondaryAddrs = append(m.secondaryAddrs, a)\n\t\t}\n\t}\n\tm.statuses = statuses\n}\n\n// GetProbe returns the next probe. Returns zero value in case there are no more probes.\n// Probes that are run against an autonatv2 client should be marked in progress with\n// `MarkProbeInProgress` before running.\nfunc (m *probeManager) GetProbe() probe {\n\tm.mx.Lock()\n\tdefer m.mx.Unlock()\n\n\t/*\n\t\t - First, select the first address for the probe. The assumption is that this is the\n\t\t\taddress which will be dialled.\n\t\t - Then, we fill the rest of the addresses in the probe while trying to ensure diversity.\n\t*/\n\tnow := m.now()\n\t// first check if the probe's first address is a primary address\n\tidx, ok := m.getFirstProbeAddrIdx(m.primaryAddrs, now)\n\tvar reqs probe\n\tif ok {\n\t\treqs = make(probe, 0, maxAddrsPerRequest)\n\t\treqs = append(reqs, autonatv2.Request{Addr: m.primaryAddrs[idx], SendDialData: true})\n\t\treqs = m.appendRequestsToProbe(reqs, m.primaryAddrs, idx, true, now)\n\t\treqs = m.appendRequestsToProbe(reqs, m.secondaryAddrs, 0, false, now)\n\t} else {\n\t\t// no primary addresses available, try secondary.\n\t\tidx, ok := m.getFirstProbeAddrIdx(m.secondaryAddrs, now)\n\t\tif !ok {\n\t\t\treturn nil\n\t\t}\n\t\treqs = make(probe, 0, maxAddrsPerRequest)\n\t\treqs = append(reqs, autonatv2.Request{Addr: m.secondaryAddrs[idx], SendDialData: true})\n\t\treqs = m.appendRequestsToProbe(reqs, m.primaryAddrs, 0, false, now)\n\t\treqs = m.appendRequestsToProbe(reqs, m.secondaryAddrs, idx, true, now)\n\t}\n\n\tif len(reqs) >= maxAddrsPerRequest {\n\t\treqs = reqs[:maxAddrsPerRequest]\n\t}\n\treturn reqs\n}\n\n// getFirstProbeAddrIdx returns the idx of the probe's first address\nfunc (m *probeManager) getFirstProbeAddrIdx(addrs []ma.Multiaddr, now time.Time) (int, bool) {\n\tfor i, a := range addrs {\n\t\ts := m.statuses[string(a.Bytes())]\n\t\tpc := s.RequiredProbeCount(now)\n\t\tif pc == 0 || m.inProgressProbes[string(addrs[i].Bytes())] >= pc {\n\t\t\tcontinue\n\t\t}\n\t\treturn i, true\n\t}\n\treturn -1, false\n}\n\n// appendRequestsToProbe appends requests to `reqs` after the first address has been determined\nfunc (m *probeManager) appendRequestsToProbe(reqs probe, addrs []ma.Multiaddr, st int, skipStart bool, now time.Time) probe {\n\tn := len(addrs)\n\tfor j := range n {\n\t\tk := (j + st) % n // We start from index: st\n\t\tif skipStart && k == st {\n\t\t\tcontinue\n\t\t}\n\t\ts := m.statuses[string(addrs[k].Bytes())]\n\t\tpc := s.RequiredProbeCount(now)\n\t\tif pc == 0 {\n\t\t\tcontinue\n\t\t}\n\t\treqs = append(reqs, autonatv2.Request{Addr: addrs[k], SendDialData: true})\n\t\tif len(reqs) >= maxAddrsPerRequest {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn reqs\n}\n\n// MarkProbeInProgress should be called when a probe is started.\n// All in progress probes *MUST* be completed with `CompleteProbe`\nfunc (m *probeManager) MarkProbeInProgress(reqs probe) {\n\tif len(reqs) == 0 {\n\t\treturn\n\t}\n\tm.mx.Lock()\n\tdefer m.mx.Unlock()\n\tm.inProgressProbes[string(reqs[0].Addr.Bytes())]++\n\tm.inProgressProbesTotal++\n}\n\n// InProgressProbes returns the number of probes that are currently in progress.\nfunc (m *probeManager) InProgressProbes() int {\n\tm.mx.Lock()\n\tdefer m.mx.Unlock()\n\treturn m.inProgressProbesTotal\n}\n\n// CompleteProbe should be called when a probe completes.\nfunc (m *probeManager) CompleteProbe(reqs probe, res autonatv2.Result, err error) {\n\tnow := m.now()\n\n\tif len(reqs) == 0 {\n\t\t// should never happen\n\t\treturn\n\t}\n\n\tm.mx.Lock()\n\tdefer m.mx.Unlock()\n\n\t// decrement in-progress count for the first address\n\tfirstAddrKey := string(reqs[0].Addr.Bytes())\n\tm.inProgressProbes[firstAddrKey]--\n\tif m.inProgressProbes[firstAddrKey] <= 0 {\n\t\tdelete(m.inProgressProbes, firstAddrKey)\n\t}\n\tm.inProgressProbesTotal--\n\n\t// nothing to do if the request errored.\n\tif err != nil {\n\t\treturn\n\t}\n\n\t// Consider only first address as refused. This increases the number of\n\t// refused probes, but refused probes are cheap for a server as no dials are made.\n\tif res.AllAddrsRefused {\n\t\tif s, ok := m.statuses[firstAddrKey]; ok {\n\t\t\ts.AddRefusal(now)\n\t\t}\n\t\treturn\n\t}\n\tdialAddrKey := string(res.Addr.Bytes())\n\tif dialAddrKey != firstAddrKey {\n\t\tif s, ok := m.statuses[firstAddrKey]; ok {\n\t\t\ts.AddRefusal(now)\n\t\t}\n\t}\n\n\t// record the result for the dialed address\n\tif s, ok := m.statuses[dialAddrKey]; ok {\n\t\ts.AddOutcome(now, res.Reachability, maxRecentDialsWindow)\n\t}\n}\n\ntype dialOutcome struct {\n\tSuccess bool\n\tAt      time.Time\n}\n\ntype addrStatus struct {\n\tAddr                ma.Multiaddr\n\tprimary             *addrStatus\n\tlastRefusalTime     time.Time\n\tconsecutiveRefusals int\n\tdialTimes           []time.Time\n\toutcomes            []dialOutcome\n}\n\nfunc (s *addrStatus) Reachability() network.Reachability {\n\trch, _, _ := s.reachabilityAndCounts()\n\treturn rch\n}\n\nfunc (s *addrStatus) RequiredProbeCount(now time.Time) int {\n\t// Secondary addresses inherit reachability from their confirmed-public primary.\n\t// If the primary is ReachabilityPublic, the port is confirmed open at the\n\t// network level, so the secondary is also reachable (they share the socket).\n\t//\n\t// If the primary is ReachabilityPrivate, we still probe the secondary because\n\t// Private is a weaker signal - it could indicate:\n\t//   - Port genuinely blocked (secondary will also fail)\n\t//   - Protocol-specific issues with the primary (secondary might work)\n\t// The cost of extra probes when truly firewalled is low (quick failures).\n\tif s.primary != nil && s.primary.Reachability() == network.ReachabilityPublic {\n\t\treturn 0\n\t}\n\n\tif s.consecutiveRefusals >= maxConsecutiveRefusals {\n\t\tif now.Sub(s.lastRefusalTime) < recentProbeInterval {\n\t\t\treturn 0\n\t\t}\n\t\t// reset every `recentProbeInterval`\n\t\ts.lastRefusalTime = time.Time{}\n\t\ts.consecutiveRefusals = 0\n\t}\n\n\t// Don't probe if we have probed too many times recently\n\trd := s.recentDialCount(now)\n\tif rd >= maxRecentDialsPerAddr {\n\t\treturn 0\n\t}\n\n\treturn s.requiredProbeCountForConfirmation(now)\n}\n\nfunc (s *addrStatus) requiredProbeCountForConfirmation(now time.Time) int {\n\treachability, successes, failures := s.reachabilityAndCounts()\n\tconfidence := successes - failures\n\tif confidence < 0 {\n\t\tconfidence = -confidence\n\t}\n\tcnt := targetConfidence - confidence\n\tif cnt > 0 {\n\t\treturn cnt\n\t}\n\t// we have enough confirmations; check if we should refresh\n\n\t// Should never happen. The confidence logic above should require a few probes.\n\tif len(s.outcomes) == 0 {\n\t\treturn 0\n\t}\n\tlastOutcome := s.outcomes[len(s.outcomes)-1]\n\t// If the last probe result is old, we need to retest\n\tif d := now.Sub(lastOutcome.At); (s.primary == nil && d > highConfidenceAddrProbeInterval) ||\n\t\t(d > highConfidenceSecondaryAddrProbeInterval) {\n\t\treturn 1\n\t}\n\t// if the last probe result was different from reachability, probe again.\n\tswitch reachability {\n\tcase network.ReachabilityPublic:\n\t\tif !lastOutcome.Success {\n\t\t\treturn 1\n\t\t}\n\tcase network.ReachabilityPrivate:\n\t\tif lastOutcome.Success {\n\t\t\treturn 1\n\t\t}\n\tdefault:\n\t\t// this should never happen\n\t\treturn 1\n\t}\n\treturn 0\n}\n\nfunc (s *addrStatus) AddRefusal(now time.Time) {\n\ts.lastRefusalTime = now\n\ts.consecutiveRefusals++\n}\n\nfunc (s *addrStatus) AddOutcome(at time.Time, rch network.Reachability, windowSize int) {\n\ts.lastRefusalTime = time.Time{}\n\ts.consecutiveRefusals = 0\n\n\ts.dialTimes = append(s.dialTimes, at)\n\tfor i, t := range s.dialTimes {\n\t\tif at.Sub(t) < recentProbeInterval {\n\t\t\ts.dialTimes = slices.Delete(s.dialTimes, 0, i)\n\t\t\tbreak\n\t\t}\n\t}\n\n\ts.RemoveBefore(at.Add(-maxProbeResultTTL)) // remove old outcomes\n\tsuccess := false\n\tswitch rch {\n\tcase network.ReachabilityPublic:\n\t\tsuccess = true\n\tcase network.ReachabilityPrivate:\n\t\tsuccess = false\n\tdefault:\n\t\treturn // don't store the outcome if reachability is unknown\n\t}\n\ts.outcomes = append(s.outcomes, dialOutcome{At: at, Success: success})\n\tif len(s.outcomes) > windowSize {\n\t\ts.outcomes = slices.Delete(s.outcomes, 0, len(s.outcomes)-windowSize)\n\t}\n}\n\n// RemoveBefore removes outcomes before t\nfunc (s *addrStatus) RemoveBefore(t time.Time) {\n\tend := 0\n\tfor ; end < len(s.outcomes); end++ {\n\t\tif !s.outcomes[end].At.Before(t) {\n\t\t\tbreak\n\t\t}\n\t}\n\ts.outcomes = slices.Delete(s.outcomes, 0, end)\n}\n\nfunc (s *addrStatus) recentDialCount(now time.Time) int {\n\tcnt := 0\n\tfor _, t := range slices.Backward(s.dialTimes) {\n\t\tif now.Sub(t) > recentProbeInterval {\n\t\t\tbreak\n\t\t}\n\t\tcnt++\n\t}\n\treturn cnt\n}\n\nfunc (s *addrStatus) reachabilityAndCounts() (rch network.Reachability, successes int, failures int) {\n\tfor _, r := range s.outcomes {\n\t\tif r.Success {\n\t\t\tsuccesses++\n\t\t} else {\n\t\t\tfailures++\n\t\t}\n\t}\n\tif s.primary != nil {\n\t\tprch, _, _ := s.primary.reachabilityAndCounts()\n\t\tif prch == network.ReachabilityPublic {\n\t\t\t// Secondary transports inherit Public reachability from their primary.\n\t\t\t//\n\t\t\t// This is important because not all AutoNAT v2 server implementations\n\t\t\t// support all secondary transports. As the Amino DHT gained a more\n\t\t\t// diverse set of node implementations (2025 Q4), we observed false\n\t\t\t// negatives: secondary addresses being marked unreachable when probing\n\t\t\t// peers simply didn't support the protocol, not because the port was\n\t\t\t// actually blocked.\n\t\t\t//\n\t\t\t// This handles shared-listener configurations where multiple\n\t\t\t// protocols share the same network socket:\n\t\t\t//\n\t\t\t//   TCP-based (libp2p.ShareTCPListener):\n\t\t\t//     Primary:   /ip4/.../tcp/port\n\t\t\t//     Secondary: /ip4/.../tcp/port/tls/sni/*.libp2p.direct/ws\n\t\t\t//     TCP and Secure WebSocket share the same TCP listener.\n\t\t\t//\n\t\t\t//   UDP/QUIC-based (quicreuse.ConnManager):\n\t\t\t//     Primary:   /ip4/.../udp/port/quic-v1\n\t\t\t//     Secondary: /ip4/.../udp/port/quic-v1/webtransport\n\t\t\t//     Secondary: /ip4/.../udp/port/webrtc-direct\n\t\t\t//     QUIC, WebTransport, and WebRTC share the same UDP socket.\n\t\t\t//\n\t\t\t// AutoNAT v2 probe failures for secondary protocols typically\n\t\t\t// indicate protocol incompatibility at the probing peer, not\n\t\t\t// port unreachability:\n\t\t\t//\n\t\t\t//   - Secure WebSocket: Probing peer may not support WebSockets,\n\t\t\t//     or TLS handshake fails because the certificate isn't\n\t\t\t//     provisioned yet (AutoTLS still obtaining cert).\n\t\t\t//   - WebTransport: Probing peer supports QUIC but not HTTP/3.\n\t\t\t//   - WebRTC: Probing peer supports QUIC but not DTLS-SRTP.\n\t\t\t//\n\t\t\t// Since the primary confirms the port is network-reachable, we\n\t\t\t// inherit that status. Protocol-level failures don't indicate\n\t\t\t// the address is unreachable to peers that DO support the protocol.\n\t\t\treturn network.ReachabilityPublic, successes, failures\n\t\t}\n\t\t// If primary is Private or Unknown, we don't inherit - the secondary\n\t\t// builds its own status through probing. This is more conservative:\n\t\t// Private could indicate protocol-specific issues rather than port\n\t\t// unreachability, so we give the secondary a chance to prove itself.\n\t}\n\tif successes-failures >= minConfidence {\n\t\treturn network.ReachabilityPublic, successes, failures\n\t}\n\tif failures-successes >= minConfidence {\n\t\treturn network.ReachabilityPrivate, successes, failures\n\t}\n\treturn network.ReachabilityUnknown, successes, failures\n}\n\nvar errNotTW = errors.New(\"not a thinwaist address\")\n\nfunc thinWaistPart(a ma.Multiaddr) (ma.Multiaddr, error) {\n\tif len(a) < 2 {\n\t\treturn nil, errNotTW\n\t}\n\tif c0, c1 := a[0].Code(), a[1].Code(); (c0 != ma.P_IP4 && c0 != ma.P_IP6) || (c1 != ma.P_TCP && c1 != ma.P_UDP) {\n\t\treturn nil, errNotTW\n\t}\n\treturn a[:2], nil\n}\n\nfunc assignPrimaryAddrs(statuses map[string]*addrStatus) {\n\ttwMap := make(map[string][]ma.Multiaddr, len(statuses))\n\tfor _, s := range statuses {\n\t\ttwp, err := thinWaistPart(s.Addr)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\ttwMap[string(twp.Bytes())] = append(twMap[string(twp.Bytes())], s.Addr)\n\t}\n\n\tscore := func(a ma.Multiaddr) int {\n\t\tscore := 0\n\t\tfor _, p := range a {\n\t\t\tswitch p.Code() {\n\t\t\tcase ma.P_QUIC_V1, ma.P_TCP:\n\t\t\t\tscore += 1\n\t\t\tcase ma.P_WEBTRANSPORT:\n\t\t\t\tscore += 1 << 1\n\t\t\tcase ma.P_WEBRTC:\n\t\t\t\tscore += 1 << 2\n\t\t\tcase ma.P_WS, ma.P_WSS:\n\t\t\t\tscore += 1 << 3\n\t\t\t}\n\t\t}\n\t\tif score == 0 {\n\t\t\treturn 1 << 20\n\t\t}\n\t\treturn score\n\t}\n\tfor _, addrs := range twMap {\n\t\tif len(addrs) <= 1 {\n\t\t\tcontinue\n\t\t}\n\t\tslices.SortFunc(addrs, func(a, b ma.Multiaddr) int {\n\t\t\treturn score(a) - score(b)\n\t\t})\n\t\tprimary := addrs[0]\n\t\tps := statuses[string(primary.Bytes())]\n\t\tfor _, a := range addrs[1:] {\n\t\t\ts := statuses[string(a.Bytes())]\n\t\t\ts.primary = ps\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "p2p/host/basic/addrs_reachability_tracker_test.go",
    "content": "package basichost\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"net\"\n\t\"net/netip\"\n\t\"slices\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/benbjohnson/clock\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/autonatv2\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/multiformats/go-multiaddr/matest\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestProbeManager(t *testing.T) {\n\tpub1 := ma.StringCast(\"/ip4/1.1.1.1/tcp/1\")\n\tpub2 := ma.StringCast(\"/ip4/1.1.1.2/tcp/1\")\n\tpub3 := ma.StringCast(\"/ip4/1.1.1.3/tcp/1\")\n\n\tcl := clock.NewMock()\n\n\tnextProbe := func(pm *probeManager) []autonatv2.Request {\n\t\treqs := pm.GetProbe()\n\t\tif len(reqs) != 0 {\n\t\t\tpm.MarkProbeInProgress(reqs)\n\t\t}\n\t\treturn reqs\n\t}\n\n\tmakeNewProbeManager := func(addrs []ma.Multiaddr) *probeManager {\n\t\tpm := newProbeManager(cl.Now)\n\t\tpm.UpdateAddrs(addrs)\n\t\treturn pm\n\t}\n\n\tt.Run(\"addrs updates\", func(t *testing.T) {\n\t\tpm := newProbeManager(cl.Now)\n\t\tpm.UpdateAddrs([]ma.Multiaddr{pub1, pub2})\n\t\tfor {\n\t\t\treqs := nextProbe(pm)\n\t\t\tif len(reqs) == 0 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tpm.CompleteProbe(reqs, autonatv2.Result{Addr: reqs[0].Addr, Idx: 0, Reachability: network.ReachabilityPublic}, nil)\n\t\t}\n\t\treachable, _, _ := pm.AppendConfirmedAddrs(nil, nil, nil)\n\t\trequire.Equal(t, reachable, []ma.Multiaddr{pub1, pub2})\n\t\tpm.UpdateAddrs([]ma.Multiaddr{pub3})\n\n\t\treachable, _, _ = pm.AppendConfirmedAddrs(nil, nil, nil)\n\t\trequire.Empty(t, reachable)\n\t\trequire.Len(t, pm.statuses, 1)\n\t})\n\n\tt.Run(\"inprogress\", func(t *testing.T) {\n\t\tpm := makeNewProbeManager([]ma.Multiaddr{pub1, pub2})\n\t\treqs1 := pm.GetProbe()\n\t\treqs2 := pm.GetProbe()\n\t\trequire.Equal(t, reqs1, reqs2)\n\t\tfor range targetConfidence {\n\t\t\treqs := nextProbe(pm)\n\t\t\trequire.Equal(t, reqs, []autonatv2.Request{{Addr: pub1, SendDialData: true}, {Addr: pub2, SendDialData: true}})\n\t\t}\n\t\tfor range targetConfidence {\n\t\t\treqs := nextProbe(pm)\n\t\t\trequire.Equal(t, reqs, []autonatv2.Request{{Addr: pub2, SendDialData: true}, {Addr: pub1, SendDialData: true}})\n\t\t}\n\t\treqs := pm.GetProbe()\n\t\trequire.Empty(t, reqs)\n\t})\n\n\tt.Run(\"refusals\", func(t *testing.T) {\n\t\tpm := makeNewProbeManager([]ma.Multiaddr{pub1, pub2})\n\t\tvar probes [][]autonatv2.Request\n\t\tfor range targetConfidence {\n\t\t\treqs := nextProbe(pm)\n\t\t\trequire.Equal(t, reqs, []autonatv2.Request{{Addr: pub1, SendDialData: true}, {Addr: pub2, SendDialData: true}})\n\t\t\tprobes = append(probes, reqs)\n\t\t}\n\t\t// first one refused second one successful\n\t\tfor _, p := range probes {\n\t\t\tpm.CompleteProbe(p, autonatv2.Result{Addr: pub2, Idx: 1, Reachability: network.ReachabilityPublic}, nil)\n\t\t}\n\t\t// the second address is validated!\n\t\tprobes = nil\n\t\tfor range targetConfidence {\n\t\t\treqs := nextProbe(pm)\n\t\t\trequire.Equal(t, reqs, []autonatv2.Request{{Addr: pub1, SendDialData: true}})\n\t\t\tprobes = append(probes, reqs)\n\t\t}\n\t\treqs := pm.GetProbe()\n\t\trequire.Empty(t, reqs)\n\t\tfor _, p := range probes {\n\t\t\tpm.CompleteProbe(p, autonatv2.Result{AllAddrsRefused: true}, nil)\n\t\t}\n\t\t// all requests refused; no more probes for too many refusals\n\t\treqs = pm.GetProbe()\n\t\trequire.Empty(t, reqs)\n\n\t\tcl.Add(recentProbeInterval)\n\t\treqs = pm.GetProbe()\n\t\trequire.Equal(t, reqs, []autonatv2.Request{{Addr: pub1, SendDialData: true}})\n\t})\n\n\tt.Run(\"successes\", func(t *testing.T) {\n\t\tpm := makeNewProbeManager([]ma.Multiaddr{pub1, pub2})\n\t\tfor range 2 {\n\t\t\tfor range targetConfidence {\n\t\t\t\treqs := nextProbe(pm)\n\t\t\t\tpm.CompleteProbe(reqs, autonatv2.Result{Addr: reqs[0].Addr, Idx: 0, Reachability: network.ReachabilityPublic}, nil)\n\t\t\t}\n\t\t}\n\t\t// all addrs confirmed\n\t\treqs := pm.GetProbe()\n\t\trequire.Empty(t, reqs)\n\n\t\tcl.Add(highConfidenceAddrProbeInterval + time.Millisecond)\n\t\treqs = nextProbe(pm)\n\t\trequire.Equal(t, reqs, []autonatv2.Request{{Addr: pub1, SendDialData: true}, {Addr: pub2, SendDialData: true}})\n\t\treqs = nextProbe(pm)\n\t\trequire.Equal(t, reqs, []autonatv2.Request{{Addr: pub2, SendDialData: true}, {Addr: pub1, SendDialData: true}})\n\t})\n\n\tt.Run(\"throttling on indeterminate reachability\", func(t *testing.T) {\n\t\tpm := makeNewProbeManager([]ma.Multiaddr{pub1, pub2})\n\t\treachability := network.ReachabilityPublic\n\t\tnextReachability := func() network.Reachability {\n\t\t\tif reachability == network.ReachabilityPublic {\n\t\t\t\treachability = network.ReachabilityPrivate\n\t\t\t} else {\n\t\t\t\treachability = network.ReachabilityPublic\n\t\t\t}\n\t\t\treturn reachability\n\t\t}\n\t\t// both addresses are indeterminate\n\t\tfor range 2 * maxRecentDialsPerAddr {\n\t\t\treqs := nextProbe(pm)\n\t\t\tpm.CompleteProbe(reqs, autonatv2.Result{Addr: reqs[0].Addr, Idx: 0, Reachability: nextReachability()}, nil)\n\t\t}\n\t\treqs := pm.GetProbe()\n\t\trequire.Empty(t, reqs)\n\n\t\tcl.Add(recentProbeInterval + time.Millisecond)\n\t\treqs = pm.GetProbe()\n\t\trequire.Equal(t, reqs, []autonatv2.Request{{Addr: pub1, SendDialData: true}, {Addr: pub2, SendDialData: true}})\n\t\tfor range 2 * maxRecentDialsPerAddr {\n\t\t\treqs := nextProbe(pm)\n\t\t\tpm.CompleteProbe(reqs, autonatv2.Result{Addr: reqs[0].Addr, Idx: 0, Reachability: nextReachability()}, nil)\n\t\t}\n\t\treqs = pm.GetProbe()\n\t\trequire.Empty(t, reqs)\n\t})\n\n\tt.Run(\"reachabilityUpdate\", func(t *testing.T) {\n\t\tpm := makeNewProbeManager([]ma.Multiaddr{pub1, pub2})\n\t\tfor range 2 * targetConfidence {\n\t\t\treqs := nextProbe(pm)\n\t\t\tif reqs[0].Addr.Equal(pub1) {\n\t\t\t\tpm.CompleteProbe(reqs, autonatv2.Result{Addr: pub1, Idx: 0, Reachability: network.ReachabilityPublic}, nil)\n\t\t\t} else {\n\t\t\t\tpm.CompleteProbe(reqs, autonatv2.Result{Addr: pub2, Idx: 0, Reachability: network.ReachabilityPrivate}, nil)\n\t\t\t}\n\t\t}\n\n\t\treachable, unreachable, _ := pm.AppendConfirmedAddrs(nil, nil, nil)\n\t\trequire.Equal(t, reachable, []ma.Multiaddr{pub1})\n\t\trequire.Equal(t, unreachable, []ma.Multiaddr{pub2})\n\t})\n\tt.Run(\"expiry\", func(t *testing.T) {\n\t\tpm := makeNewProbeManager([]ma.Multiaddr{pub1})\n\t\tfor range 2 * targetConfidence {\n\t\t\treqs := nextProbe(pm)\n\t\t\tpm.CompleteProbe(reqs, autonatv2.Result{Addr: pub1, Idx: 0, Reachability: network.ReachabilityPublic}, nil)\n\t\t}\n\n\t\treachable, unreachable, _ := pm.AppendConfirmedAddrs(nil, nil, nil)\n\t\trequire.Equal(t, reachable, []ma.Multiaddr{pub1})\n\t\trequire.Empty(t, unreachable)\n\n\t\tcl.Add(maxProbeResultTTL + 1*time.Second)\n\t\treachable, unreachable, _ = pm.AppendConfirmedAddrs(nil, nil, nil)\n\t\trequire.Empty(t, reachable)\n\t\trequire.Empty(t, unreachable)\n\t})\n\n\tt.Run(\"primary secondary\", func(t *testing.T) {\n\t\tquic := ma.StringCast(\"/ip4/1.1.1.1/udp/1/quic-v1\")\n\t\twebrtc := ma.StringCast(\"/ip4/1.1.1.1/udp/1/webrtc-direct\")\n\t\ttcp := ma.StringCast(\"/ip4/1.1.1.1/tcp/1\")\n\t\twebsocket := ma.StringCast(\"/ip4/1.1.1.1/tcp/1/ws\")\n\t\tpm := makeNewProbeManager([]ma.Multiaddr{tcp, websocket, webrtc, quic})\n\n\t\textractAddrs := func(reqs probe) []ma.Multiaddr {\n\t\t\tvar res []ma.Multiaddr\n\t\t\tfor _, r := range reqs {\n\t\t\t\tres = append(res, r.Addr)\n\t\t\t}\n\t\t\treturn res\n\t\t}\n\n\t\t// Conservative inheritance: secondaries only inherit Public from primary.\n\t\t// If primary is Private, secondaries still get probed (Private could be\n\t\t// protocol-specific, not port-level).\n\t\t//\n\t\t// TCP gets Public results - websocket inherits Public (skips probing)\n\t\t// This is the AutoTLS use case: TCP works, WSS inherits reachability.\n\t\tfor i := range targetConfidence {\n\t\t\treqs := nextProbe(pm)\n\t\t\tif i < minConfidence {\n\t\t\t\t// TCP not yet confirmed Public, all 4 addresses need probing\n\t\t\t\tmatest.AssertEqualMultiaddrs(t, []ma.Multiaddr{tcp, quic, websocket, webrtc}, extractAddrs(reqs))\n\t\t\t} else {\n\t\t\t\t// TCP confirmed Public, websocket inherits - only 3 addresses\n\t\t\t\tmatest.AssertEqualMultiaddrs(t, []ma.Multiaddr{tcp, quic, webrtc}, extractAddrs(reqs))\n\t\t\t}\n\t\t\tpm.CompleteProbe(reqs, autonatv2.Result{Addr: tcp, Idx: 0, Reachability: network.ReachabilityPublic}, nil)\n\t\t}\n\n\t\t// QUIC gets Private results - webrtc still needs probing (no inheritance)\n\t\t// This tests the conservative behavior: Private doesn't propagate.\n\t\tfor range targetConfidence {\n\t\t\treqs := nextProbe(pm)\n\t\t\t// websocket already inherited from tcp, but webrtc doesn't inherit Private\n\t\t\tmatest.AssertEqualMultiaddrs(t, []ma.Multiaddr{quic, webrtc}, extractAddrs(reqs))\n\t\t\tpm.CompleteProbe(reqs, autonatv2.Result{Addr: quic, Idx: 0, Reachability: network.ReachabilityPrivate}, nil)\n\t\t}\n\n\t\t// webrtc still needs probing (doesn't inherit Private from quic)\n\t\t// Give webrtc its own Private status through probing\n\t\tfor range targetConfidence {\n\t\t\treqs := nextProbe(pm)\n\t\t\tmatest.AssertEqualMultiaddrs(t, []ma.Multiaddr{webrtc}, extractAddrs(reqs))\n\t\t\tpm.CompleteProbe(reqs, autonatv2.Result{Addr: webrtc, Idx: 0, Reachability: network.ReachabilityPrivate}, nil)\n\t\t}\n\n\t\treqs := nextProbe(pm)\n\t\trequire.Empty(t, reqs)\n\n\t\treachable, unreachable, _ := pm.AppendConfirmedAddrs(nil, nil, nil)\n\t\t// websocket inherits Public from tcp (confirmed reachable port) - AutoTLS case\n\t\t// webrtc has its own Private status (probed independently)\n\t\tmatest.AssertMultiaddrsMatch(t, []ma.Multiaddr{tcp, websocket}, reachable)\n\t\tmatest.AssertMultiaddrsMatch(t, []ma.Multiaddr{quic, webrtc}, unreachable)\n\n\t\t// After highConfidenceAddrProbeInterval (1h), only primaries need refresh.\n\t\t// websocket inherits from tcp (Public), webrtc has longer refresh interval (3h).\n\t\tfor range 2 {\n\t\t\tcl.Add(highConfidenceAddrProbeInterval + 1*time.Millisecond)\n\t\t\treqs := nextProbe(pm)\n\t\t\t// Only tcp and quic need refresh; websocket inherits, webrtc has 3h interval\n\t\t\tmatest.AssertEqualMultiaddrs(t, []ma.Multiaddr{tcp, quic}, extractAddrs(reqs))\n\t\t\tpm.CompleteProbe(reqs, autonatv2.Result{Addr: tcp, Idx: 0, Reachability: network.ReachabilityPublic}, nil)\n\t\t\treqs = nextProbe(pm)\n\t\t\tmatest.AssertEqualMultiaddrs(t, []ma.Multiaddr{quic}, extractAddrs(reqs))\n\t\t\tpm.CompleteProbe(reqs, autonatv2.Result{Addr: quic, Idx: 0, Reachability: network.ReachabilityPrivate}, nil)\n\n\t\t\treqs = nextProbe(pm)\n\t\t\trequire.Empty(t, reqs)\n\t\t}\n\n\t\treachable, unreachable, _ = pm.AppendConfirmedAddrs(nil, nil, nil)\n\t\tmatest.AssertMultiaddrsMatch(t, reachable, []ma.Multiaddr{tcp, websocket})\n\t\tmatest.AssertMultiaddrsMatch(t, unreachable, []ma.Multiaddr{quic, webrtc})\n\n\t\t// After highConfidenceSecondaryAddrProbeInterval (3h), webrtc needs refresh too.\n\t\t// We've advanced 2h+2ms, need to reach 3h+ for webrtc's refresh.\n\t\t// Also need to exceed 1h since last tcp/quic refresh for them to need refresh.\n\t\tcl.Add(highConfidenceSecondaryAddrProbeInterval - 2*highConfidenceAddrProbeInterval + 1*time.Millisecond)\n\t\treqs = nextProbe(pm)\n\t\t// tcp, quic, and webrtc need refresh; websocket still inherits from tcp\n\t\tmatest.AssertEqualMultiaddrs(t, []ma.Multiaddr{tcp, quic, webrtc}, extractAddrs(reqs))\n\t\tpm.CompleteProbe(reqs, autonatv2.Result{Addr: tcp, Idx: 0, Reachability: network.ReachabilityPublic}, nil)\n\t\treqs = nextProbe(pm)\n\t\tmatest.AssertEqualMultiaddrs(t, []ma.Multiaddr{quic, webrtc}, extractAddrs(reqs))\n\t\tpm.CompleteProbe(reqs, autonatv2.Result{Addr: quic, Idx: 0, Reachability: network.ReachabilityPrivate}, nil)\n\t\treqs = nextProbe(pm)\n\t\tmatest.AssertEqualMultiaddrs(t, []ma.Multiaddr{webrtc}, extractAddrs(reqs))\n\t\tpm.CompleteProbe(reqs, autonatv2.Result{Addr: webrtc, Idx: 0, Reachability: network.ReachabilityPrivate}, nil)\n\n\t\treqs = nextProbe(pm)\n\t\trequire.Empty(t, reqs)\n\t})\n}\n\ntype mockAutoNATClient struct {\n\tF func(context.Context, []autonatv2.Request) (autonatv2.Result, error)\n}\n\nfunc (m mockAutoNATClient) GetReachability(ctx context.Context, reqs []autonatv2.Request) (autonatv2.Result, error) {\n\treturn m.F(ctx, reqs)\n}\n\nvar _ autonatv2Client = mockAutoNATClient{}\n\nfunc TestAddrsReachabilityTracker(t *testing.T) {\n\tpub1 := ma.StringCast(\"/ip4/1.1.1.1/tcp/1\")\n\tpub2 := ma.StringCast(\"/ip4/1.1.1.2/tcp/1\")\n\tpub3 := ma.StringCast(\"/ip4/1.1.1.3/tcp/1\")\n\tpri := ma.StringCast(\"/ip4/192.168.1.1/tcp/1\")\n\n\tassertFirstEvent := func(t *testing.T, tr *addrsReachabilityTracker, addrs []ma.Multiaddr) {\n\t\tselect {\n\t\tcase <-tr.reachabilityUpdateCh:\n\t\tcase <-time.After(200 * time.Millisecond):\n\t\t\tt.Fatal(\"expected first event quickly\")\n\t\t}\n\t\treachable, unreachable, unknown := tr.ConfirmedAddrs()\n\t\trequire.Empty(t, reachable)\n\t\trequire.Empty(t, unreachable)\n\t\trequire.ElementsMatch(t, unknown, addrs, \"%s %s\", unknown, addrs)\n\t}\n\n\tnewTracker := func(cli mockAutoNATClient, cl clock.Clock) *addrsReachabilityTracker {\n\t\tctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n\t\tif cl == nil {\n\t\t\tcl = clock.New()\n\t\t}\n\t\ttr := &addrsReachabilityTracker{\n\t\t\tctx:                  ctx,\n\t\t\tcancel:               cancel,\n\t\t\tclient:               cli,\n\t\t\tnewAddrs:             make(chan []ma.Multiaddr, 1),\n\t\t\treachabilityUpdateCh: make(chan struct{}, 1),\n\t\t\tmaxConcurrency:       3,\n\t\t\tnewAddrsProbeDelay:   0 * time.Second,\n\t\t\tprobeManager:         newProbeManager(cl.Now),\n\t\t\tclock:                cl,\n\t\t}\n\t\terr := tr.Start()\n\t\trequire.NoError(t, err)\n\t\tt.Cleanup(func() {\n\t\t\terr := tr.Close()\n\t\t\tassert.NoError(t, err)\n\t\t})\n\t\treturn tr\n\t}\n\n\tt.Run(\"simple\", func(t *testing.T) {\n\t\t// pub1 reachable, pub2 unreachable, pub3 ignored\n\t\tmockClient := mockAutoNATClient{\n\t\t\tF: func(_ context.Context, reqs []autonatv2.Request) (autonatv2.Result, error) {\n\t\t\t\tfor i, req := range reqs {\n\t\t\t\t\tif req.Addr.Equal(pub1) {\n\t\t\t\t\t\treturn autonatv2.Result{Addr: pub1, Idx: i, Reachability: network.ReachabilityPublic}, nil\n\t\t\t\t\t} else if req.Addr.Equal(pub2) {\n\t\t\t\t\t\treturn autonatv2.Result{Addr: pub2, Idx: i, Reachability: network.ReachabilityPrivate}, nil\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn autonatv2.Result{AllAddrsRefused: true}, nil\n\t\t\t},\n\t\t}\n\t\ttr := newTracker(mockClient, nil)\n\t\ttr.UpdateAddrs([]ma.Multiaddr{pub2, pub1, pri})\n\t\tassertFirstEvent(t, tr, []ma.Multiaddr{pub1, pub2})\n\n\t\tselect {\n\t\tcase <-tr.reachabilityUpdateCh:\n\t\tcase <-time.After(2 * time.Second):\n\t\t\tt.Fatal(\"expected reachability update\")\n\t\t}\n\t\treachable, unreachable, unknown := tr.ConfirmedAddrs()\n\t\trequire.Equal(t, reachable, []ma.Multiaddr{pub1}, \"%s %s\", reachable, pub1)\n\t\trequire.Equal(t, unreachable, []ma.Multiaddr{pub2}, \"%s %s\", unreachable, pub2)\n\t\trequire.Empty(t, unknown)\n\n\t\ttr.UpdateAddrs([]ma.Multiaddr{pub3, pub1, pub2, pri})\n\t\tselect {\n\t\tcase <-tr.reachabilityUpdateCh:\n\t\tcase <-time.After(2 * time.Second):\n\t\t\tt.Fatal(\"expected reachability update\")\n\t\t}\n\t\treachable, unreachable, unknown = tr.ConfirmedAddrs()\n\t\tt.Logf(\"Second probe - Reachable: %v, Unreachable: %v, Unknown: %v\", reachable, unreachable, unknown)\n\t\trequire.Equal(t, reachable, []ma.Multiaddr{pub1}, \"%s %s\", reachable, pub1)\n\t\trequire.Equal(t, unreachable, []ma.Multiaddr{pub2}, \"%s %s\", unreachable, pub2)\n\t\trequire.Equal(t, unknown, []ma.Multiaddr{pub3}, \"%s %s\", unknown, pub3)\n\t})\n\n\tt.Run(\"confirmed addrs ordering\", func(t *testing.T) {\n\t\tmockClient := mockAutoNATClient{\n\t\t\tF: func(_ context.Context, reqs []autonatv2.Request) (autonatv2.Result, error) {\n\t\t\t\treturn autonatv2.Result{Addr: reqs[0].Addr, Idx: 0, Reachability: network.ReachabilityPublic}, nil\n\t\t\t},\n\t\t}\n\t\ttr := newTracker(mockClient, nil)\n\t\tvar addrs []ma.Multiaddr\n\t\tfor i := range 10 {\n\t\t\taddrs = append(addrs, ma.StringCast(fmt.Sprintf(\"/ip4/1.1.1.1/tcp/%d\", i)))\n\t\t}\n\t\tslices.SortFunc(addrs, func(a, b ma.Multiaddr) int { return -a.Compare(b) }) // sort in reverse order\n\t\ttr.UpdateAddrs(addrs)\n\t\tassertFirstEvent(t, tr, addrs)\n\n\t\tselect {\n\t\tcase <-tr.reachabilityUpdateCh:\n\t\tcase <-time.After(2 * time.Second):\n\t\t\tt.Fatal(\"expected reachability update\")\n\t\t}\n\t\treachable, unreachable, _ := tr.ConfirmedAddrs()\n\t\trequire.Empty(t, unreachable)\n\n\t\torderedAddrs := slices.Clone(addrs)\n\t\tslices.Reverse(orderedAddrs)\n\t\trequire.Equal(t, reachable, orderedAddrs, \"%s %s\", reachable, addrs)\n\t})\n\n\tt.Run(\"backoff\", func(t *testing.T) {\n\t\tnotify := make(chan struct{}, 1)\n\t\tdrainNotify := func() bool {\n\t\t\tfound := false\n\t\t\tfor {\n\t\t\t\tselect {\n\t\t\t\tcase <-notify:\n\t\t\t\t\tfound = true\n\t\t\t\tdefault:\n\t\t\t\t\treturn found\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tvar allow atomic.Bool\n\t\tmockClient := mockAutoNATClient{\n\t\t\tF: func(_ context.Context, reqs []autonatv2.Request) (autonatv2.Result, error) {\n\t\t\t\tselect {\n\t\t\t\tcase notify <- struct{}{}:\n\t\t\t\tdefault:\n\t\t\t\t}\n\t\t\t\tif !allow.Load() {\n\t\t\t\t\treturn autonatv2.Result{}, autonatv2.ErrNoPeers\n\t\t\t\t}\n\t\t\t\tif reqs[0].Addr.Equal(pub1) {\n\t\t\t\t\treturn autonatv2.Result{Addr: pub1, Idx: 0, Reachability: network.ReachabilityPublic}, nil\n\t\t\t\t}\n\t\t\t\treturn autonatv2.Result{AllAddrsRefused: true}, nil\n\t\t\t},\n\t\t}\n\n\t\tcl := clock.NewMock()\n\t\ttr := newTracker(mockClient, cl)\n\n\t\t// update addrs and wait for initial checks\n\t\ttr.UpdateAddrs([]ma.Multiaddr{pub1})\n\t\tassertFirstEvent(t, tr, []ma.Multiaddr{pub1})\n\t\t// need to update clock after the background goroutine processes the new addrs\n\t\ttime.Sleep(100 * time.Millisecond)\n\t\tcl.Add(1)\n\t\ttime.Sleep(100 * time.Millisecond)\n\t\trequire.True(t, drainNotify()) // check that we did receive probes\n\n\t\tbackoffInterval := backoffStartInterval\n\t\tfor range 4 {\n\t\t\tdrainNotify()\n\t\t\tcl.Add(backoffInterval / 2)\n\t\t\tselect {\n\t\t\tcase <-notify:\n\t\t\t\tt.Fatal(\"unexpected call\")\n\t\t\tcase <-time.After(50 * time.Millisecond):\n\t\t\t}\n\t\t\tcl.Add(backoffInterval/2 + 1) // +1 to push it slightly over the backoff interval\n\t\t\tbackoffInterval *= 2\n\t\t\tselect {\n\t\t\tcase <-notify:\n\t\t\tcase <-time.After(1 * time.Second):\n\t\t\t\tt.Fatal(\"expected probe\")\n\t\t\t}\n\t\t\treachable, unreachable, _ := tr.ConfirmedAddrs()\n\t\t\trequire.Empty(t, reachable)\n\t\t\trequire.Empty(t, unreachable)\n\t\t}\n\t\tallow.Store(true)\n\t\tdrainNotify()\n\t\tcl.Add(backoffInterval + 1)\n\t\tselect {\n\t\tcase <-tr.reachabilityUpdateCh:\n\t\tcase <-time.After(1 * time.Second):\n\t\t\tt.Fatal(\"unexpected reachability update\")\n\t\t}\n\t\treachable, unreachable, _ := tr.ConfirmedAddrs()\n\t\trequire.Equal(t, reachable, []ma.Multiaddr{pub1})\n\t\trequire.Empty(t, unreachable)\n\t})\n\n\tt.Run(\"event update\", func(t *testing.T) {\n\t\t// allow minConfidence probes to pass\n\t\tcalled := make(chan struct{}, minConfidence)\n\t\tnotify := make(chan struct{})\n\t\tmockClient := mockAutoNATClient{\n\t\t\tF: func(_ context.Context, _ []autonatv2.Request) (autonatv2.Result, error) {\n\t\t\t\tselect {\n\t\t\t\tcase called <- struct{}{}:\n\t\t\t\t\tnotify <- struct{}{}\n\t\t\t\t\treturn autonatv2.Result{Addr: pub1, Idx: 0, Reachability: network.ReachabilityPublic}, nil\n\t\t\t\tdefault:\n\t\t\t\t\treturn autonatv2.Result{AllAddrsRefused: true}, nil\n\t\t\t\t}\n\t\t\t},\n\t\t}\n\n\t\ttr := newTracker(mockClient, nil)\n\t\ttr.UpdateAddrs([]ma.Multiaddr{pub1})\n\t\tassertFirstEvent(t, tr, []ma.Multiaddr{pub1})\n\n\t\tfor range minConfidence {\n\t\t\tselect {\n\t\t\tcase <-notify:\n\t\t\tcase <-time.After(1 * time.Second):\n\t\t\t\tt.Fatal(\"expected call to autonat client\")\n\t\t\t}\n\t\t}\n\t\tselect {\n\t\tcase <-tr.reachabilityUpdateCh:\n\t\t\treachable, unreachable, _ := tr.ConfirmedAddrs()\n\t\t\trequire.Equal(t, reachable, []ma.Multiaddr{pub1})\n\t\t\trequire.Empty(t, unreachable)\n\t\tcase <-time.After(1 * time.Second):\n\t\t\tt.Fatal(\"expected reachability update\")\n\t\t}\n\t\ttr.UpdateAddrs([]ma.Multiaddr{pub1}) // same addrs shouldn't get update\n\t\tselect {\n\t\tcase <-tr.reachabilityUpdateCh:\n\t\t\tt.Fatal(\"didn't expect reachability update\")\n\t\tcase <-time.After(100 * time.Millisecond):\n\t\t}\n\t\ttr.UpdateAddrs([]ma.Multiaddr{pub2})\n\t\tselect {\n\t\tcase <-tr.reachabilityUpdateCh:\n\t\t\treachable, unreachable, _ := tr.ConfirmedAddrs()\n\t\t\trequire.Empty(t, reachable)\n\t\t\trequire.Empty(t, unreachable)\n\t\tcase <-time.After(1 * time.Second):\n\t\t\tt.Fatal(\"expected reachability update\")\n\t\t}\n\t})\n\n\tt.Run(\"refresh after reset interval\", func(t *testing.T) {\n\t\tnotify := make(chan struct{}, 1)\n\t\tdrainNotify := func() bool {\n\t\t\tfound := false\n\t\t\tfor {\n\t\t\t\tselect {\n\t\t\t\tcase <-notify:\n\t\t\t\t\tfound = true\n\t\t\t\tdefault:\n\t\t\t\t\treturn found\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tmockClient := mockAutoNATClient{\n\t\t\tF: func(_ context.Context, reqs []autonatv2.Request) (autonatv2.Result, error) {\n\t\t\t\tselect {\n\t\t\t\tcase notify <- struct{}{}:\n\t\t\t\tdefault:\n\t\t\t\t}\n\t\t\t\tif reqs[0].Addr.Equal(pub1) {\n\t\t\t\t\treturn autonatv2.Result{Addr: pub1, Idx: 0, Reachability: network.ReachabilityPublic}, nil\n\t\t\t\t}\n\t\t\t\treturn autonatv2.Result{AllAddrsRefused: true}, nil\n\t\t\t},\n\t\t}\n\n\t\tcl := clock.NewMock()\n\t\ttr := newTracker(mockClient, cl)\n\n\t\t// update addrs and wait for initial checks\n\t\ttr.UpdateAddrs([]ma.Multiaddr{pub1})\n\t\tassertFirstEvent(t, tr, []ma.Multiaddr{pub1})\n\t\t// need to update clock after the background goroutine processes the new addrs\n\t\ttime.Sleep(100 * time.Millisecond)\n\t\tcl.Add(1)\n\t\ttime.Sleep(100 * time.Millisecond)\n\t\trequire.True(t, drainNotify()) // check that we did receive probes\n\t\tcl.Add(highConfidenceAddrProbeInterval / 2)\n\t\tselect {\n\t\tcase <-notify:\n\t\t\tt.Fatal(\"unexpected call\")\n\t\tcase <-time.After(50 * time.Millisecond):\n\t\t}\n\n\t\tcl.Add(highConfidenceAddrProbeInterval/2 + defaultReachabilityRefreshInterval) // defaultResetInterval for the next probe time\n\t\tselect {\n\t\tcase <-notify:\n\t\tcase <-time.After(1 * time.Second):\n\t\t\tt.Fatal(\"expected probe\")\n\t\t}\n\t})\n}\n\nfunc TestRefreshReachability(t *testing.T) {\n\tpub1 := ma.StringCast(\"/ip4/1.1.1.1/tcp/1\")\n\tpub2 := ma.StringCast(\"/ip4/1.1.1.1/tcp/2\")\n\tctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)\n\tdefer cancel()\n\n\tnewTracker := func(client autonatv2Client, pm *probeManager) *addrsReachabilityTracker {\n\t\treturn &addrsReachabilityTracker{\n\t\t\tprobeManager:   pm,\n\t\t\tclient:         client,\n\t\t\tclock:          clock.New(),\n\t\t\tmaxConcurrency: 3,\n\t\t\tctx:            ctx,\n\t\t\tcancel:         cancel,\n\t\t}\n\t}\n\tt.Run(\"backoff on ErrNoValidPeers\", func(t *testing.T) {\n\t\tmockClient := mockAutoNATClient{\n\t\t\tF: func(_ context.Context, _ []autonatv2.Request) (autonatv2.Result, error) {\n\t\t\t\treturn autonatv2.Result{}, autonatv2.ErrNoPeers\n\t\t\t},\n\t\t}\n\n\t\taddrTracker := newProbeManager(time.Now)\n\t\taddrTracker.UpdateAddrs([]ma.Multiaddr{pub1})\n\t\tr := newTracker(mockClient, addrTracker)\n\t\tres := r.refreshReachability()\n\t\trequire.True(t, <-res.BackoffCh)\n\t\trequire.Equal(t, addrTracker.InProgressProbes(), 0)\n\t})\n\n\tt.Run(\"returns backoff on errTooManyConsecutiveFailures\", func(t *testing.T) {\n\t\t// Create a client that always returns ErrDialRefused\n\t\tmockClient := mockAutoNATClient{\n\t\t\tF: func(_ context.Context, _ []autonatv2.Request) (autonatv2.Result, error) {\n\t\t\t\treturn autonatv2.Result{}, errors.New(\"test error\")\n\t\t\t},\n\t\t}\n\n\t\tpm := newProbeManager(time.Now)\n\t\tpm.UpdateAddrs([]ma.Multiaddr{pub1})\n\t\tr := newTracker(mockClient, pm)\n\t\tresult := r.refreshReachability()\n\t\trequire.True(t, <-result.BackoffCh)\n\t\trequire.Equal(t, pm.InProgressProbes(), 0)\n\t})\n\n\tt.Run(\"quits on cancellation\", func(t *testing.T) {\n\t\tctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)\n\t\tblock := make(chan struct{})\n\t\tmockClient := mockAutoNATClient{\n\t\t\tF: func(_ context.Context, _ []autonatv2.Request) (autonatv2.Result, error) {\n\t\t\t\tblock <- struct{}{}\n\t\t\t\treturn autonatv2.Result{}, nil\n\t\t\t},\n\t\t}\n\n\t\tpm := newProbeManager(time.Now)\n\t\tpm.UpdateAddrs([]ma.Multiaddr{pub1})\n\t\tr := &addrsReachabilityTracker{\n\t\t\tctx:          ctx,\n\t\t\tcancel:       cancel,\n\t\t\tclient:       mockClient,\n\t\t\tprobeManager: pm,\n\t\t\tclock:        clock.New(),\n\t\t}\n\t\tvar wg sync.WaitGroup\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tresult := r.refreshReachability()\n\t\t\tassert.False(t, <-result.BackoffCh)\n\t\t\tassert.Equal(t, pm.InProgressProbes(), 0)\n\t\t}()\n\n\t\tcancel()\n\t\ttime.Sleep(50 * time.Millisecond) // wait for the cancellation to be processed\n\n\touter:\n\t\tfor range defaultMaxConcurrency {\n\t\t\tselect {\n\t\t\tcase <-block:\n\t\t\tdefault:\n\t\t\t\tbreak outer\n\t\t\t}\n\t\t}\n\t\tselect {\n\t\tcase <-block:\n\t\t\tt.Fatal(\"expected no more requests\")\n\t\tcase <-time.After(50 * time.Millisecond):\n\t\t}\n\t\twg.Wait()\n\t})\n\n\tt.Run(\"handles refusals\", func(t *testing.T) {\n\t\tpub1, _ := ma.NewMultiaddr(\"/ip4/1.1.1.1/tcp/1\")\n\n\t\tmockClient := mockAutoNATClient{\n\t\t\tF: func(_ context.Context, reqs []autonatv2.Request) (autonatv2.Result, error) {\n\t\t\t\tfor i, req := range reqs {\n\t\t\t\t\tif req.Addr.Equal(pub1) {\n\t\t\t\t\t\treturn autonatv2.Result{Addr: pub1, Idx: i, Reachability: network.ReachabilityPublic}, nil\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn autonatv2.Result{AllAddrsRefused: true}, nil\n\t\t\t},\n\t\t}\n\n\t\tpm := newProbeManager(time.Now)\n\t\tpm.UpdateAddrs([]ma.Multiaddr{pub2, pub1})\n\t\tr := newTracker(mockClient, pm)\n\n\t\tresult := r.refreshReachability()\n\t\trequire.False(t, <-result.BackoffCh)\n\n\t\treachable, unreachable, _ := pm.AppendConfirmedAddrs(nil, nil, nil)\n\t\trequire.Equal(t, reachable, []ma.Multiaddr{pub1})\n\t\trequire.Empty(t, unreachable)\n\t\trequire.Equal(t, pm.InProgressProbes(), 0)\n\t})\n\n\tt.Run(\"handles completions\", func(t *testing.T) {\n\t\tmockClient := mockAutoNATClient{\n\t\t\tF: func(_ context.Context, reqs []autonatv2.Request) (autonatv2.Result, error) {\n\t\t\t\tfor i, req := range reqs {\n\t\t\t\t\tif req.Addr.Equal(pub1) {\n\t\t\t\t\t\treturn autonatv2.Result{Addr: pub1, Idx: i, Reachability: network.ReachabilityPublic}, nil\n\t\t\t\t\t}\n\t\t\t\t\tif req.Addr.Equal(pub2) {\n\t\t\t\t\t\treturn autonatv2.Result{Addr: pub2, Idx: i, Reachability: network.ReachabilityPrivate}, nil\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn autonatv2.Result{AllAddrsRefused: true}, nil\n\t\t\t},\n\t\t}\n\t\tpm := newProbeManager(time.Now)\n\t\tpm.UpdateAddrs([]ma.Multiaddr{pub2, pub1})\n\t\tr := newTracker(mockClient, pm)\n\t\tresult := r.refreshReachability()\n\t\trequire.False(t, <-result.BackoffCh)\n\n\t\treachable, unreachable, _ := pm.AppendConfirmedAddrs(nil, nil, nil)\n\t\trequire.Equal(t, reachable, []ma.Multiaddr{pub1})\n\t\trequire.Equal(t, unreachable, []ma.Multiaddr{pub2})\n\t\trequire.Equal(t, pm.InProgressProbes(), 0)\n\t})\n}\n\nfunc TestAddrStatusProbeCount(t *testing.T) {\n\tcases := []struct {\n\t\tinputs             string\n\t\twantRequiredProbes int\n\t\twantReachability   network.Reachability\n\t}{\n\t\t{\n\t\t\tinputs:             \"\",\n\t\t\twantRequiredProbes: 3,\n\t\t\twantReachability:   network.ReachabilityUnknown,\n\t\t},\n\t\t{\n\t\t\tinputs:             \"S\",\n\t\t\twantRequiredProbes: 2,\n\t\t\twantReachability:   network.ReachabilityUnknown,\n\t\t},\n\t\t{\n\t\t\tinputs:             \"SS\",\n\t\t\twantRequiredProbes: 1,\n\t\t\twantReachability:   network.ReachabilityPublic,\n\t\t},\n\t\t{\n\t\t\tinputs:             \"SSS\",\n\t\t\twantRequiredProbes: 0,\n\t\t\twantReachability:   network.ReachabilityPublic,\n\t\t},\n\t\t{\n\t\t\tinputs:             \"SSSSSSSF\",\n\t\t\twantRequiredProbes: 1,\n\t\t\twantReachability:   network.ReachabilityPublic,\n\t\t},\n\t\t{\n\t\t\tinputs:             \"SFSFSSSS\",\n\t\t\twantRequiredProbes: 0,\n\t\t\twantReachability:   network.ReachabilityPublic,\n\t\t},\n\t\t{\n\t\t\tinputs:             \"SSSSSFSF\",\n\t\t\twantRequiredProbes: 2,\n\t\t\twantReachability:   network.ReachabilityUnknown,\n\t\t},\n\t\t{\n\t\t\tinputs:             \"FF\",\n\t\t\twantRequiredProbes: 1,\n\t\t\twantReachability:   network.ReachabilityPrivate,\n\t\t},\n\t}\n\tfor _, c := range cases {\n\t\tt.Run(c.inputs, func(t *testing.T) {\n\t\t\tnow := time.Time{}.Add(1 * time.Second)\n\t\t\tao := addrStatus{}\n\t\t\tfor _, r := range c.inputs {\n\t\t\t\tif r == 'S' {\n\t\t\t\t\tao.AddOutcome(now, network.ReachabilityPublic, 5)\n\t\t\t\t} else {\n\t\t\t\t\tao.AddOutcome(now, network.ReachabilityPrivate, 5)\n\t\t\t\t}\n\t\t\t\tnow = now.Add(1 * time.Second)\n\t\t\t}\n\t\t\trequire.Equal(t, ao.RequiredProbeCount(now), c.wantRequiredProbes)\n\t\t\trequire.Equal(t, ao.Reachability(), c.wantReachability)\n\t\t\tif c.wantRequiredProbes == 0 {\n\t\t\t\tnow = now.Add(highConfidenceAddrProbeInterval + 10*time.Microsecond)\n\t\t\t\trequire.Equal(t, ao.RequiredProbeCount(now), 1)\n\t\t\t}\n\n\t\t\tnow = now.Add(1 * time.Second)\n\t\t\tao.RemoveBefore(now)\n\t\t\trequire.Len(t, ao.outcomes, 0)\n\t\t})\n\t}\n}\n\nfunc TestAssignPrimaryAddress(t *testing.T) {\n\twebTransport1 := ma.StringCast(\"/ip4/127.0.0.1/udp/1/quic-v1/webtransport\")\n\tquic1 := ma.StringCast(\"/ip4/127.0.0.1/udp/1/quic-v1\")\n\twebRTC1 := ma.StringCast(\"/ip4/127.0.0.1/udp/1/webrtc-direct\")\n\n\twebTransport2 := ma.StringCast(\"/ip4/127.0.0.1/udp/2/quic-v1/webtransport\")\n\tquic2 := ma.StringCast(\"/ip4/127.0.0.1/udp/2/quic-v1\")\n\twebRTC2 := ma.StringCast(\"/ip4/127.0.0.1/udp/2/webrtc-direct\")\n\n\ttcp1 := ma.StringCast(\"/ip4/127.0.0.1/tcp/1\")\n\tws1 := ma.StringCast(\"/ip4/127.0.0.1/tcp/1/ws\")\n\n\ttests := [][]struct{ secondary, primary ma.Multiaddr }{\n\t\t{\n\t\t\t{webTransport1, quic1},\n\t\t\t{webRTC1, quic1},\n\t\t},\n\t\t{\n\t\t\t{webTransport1, quic1},\n\t\t\t{webRTC1, quic1},\n\t\t\t{webTransport2, quic2},\n\t\t\t{webRTC2, quic2},\n\t\t},\n\t\t{\n\t\t\t{webTransport1, quic1},\n\t\t\t{webRTC1, quic1},\n\t\t\t{webTransport2, quic2},\n\t\t\t{webRTC2, quic2},\n\t\t\t{ws1, tcp1},\n\t\t},\n\t\t{\n\t\t\t{webTransport1, nil},\n\t\t\t{quic2, nil},\n\t\t\t{ws1, nil},\n\t\t},\n\t}\n\tfor i, tt := range tests {\n\t\tt.Run(fmt.Sprintf(\"%d\", i), func(t *testing.T) {\n\t\t\tstatuses := make(map[string]*addrStatus)\n\t\t\tfor _, p := range tt {\n\t\t\t\tif p.primary != nil {\n\t\t\t\t\tstatuses[string(p.primary.Bytes())] = &addrStatus{Addr: p.primary}\n\t\t\t\t}\n\t\t\t\tstatuses[string(p.secondary.Bytes())] = &addrStatus{Addr: p.secondary}\n\t\t\t}\n\t\t\tassignPrimaryAddrs(statuses)\n\t\t\tfor _, p := range tt {\n\t\t\t\tif p.primary != nil {\n\t\t\t\t\trequire.Nil(t, statuses[string(p.primary.Bytes())].primary)\n\t\t\t\t\trequire.Equal(t, statuses[string(p.secondary.Bytes())].primary, statuses[string(p.primary.Bytes())])\n\t\t\t\t} else {\n\t\t\t\t\trequire.Nil(t, statuses[string(p.secondary.Bytes())].primary)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkAddrTracker(b *testing.B) {\n\tcl := clock.NewMock()\n\tt := newProbeManager(cl.Now)\n\n\taddrs := make([]ma.Multiaddr, 20)\n\tfor i := range addrs {\n\t\taddrs[i] = ma.StringCast(fmt.Sprintf(\"/ip4/1.1.1.1/tcp/%d\", rand.Intn(1000)))\n\t}\n\tt.UpdateAddrs(addrs)\n\tb.ReportAllocs()\n\tb.ResetTimer()\n\tp := t.GetProbe()\n\tfor i := 0; i < b.N; i++ {\n\t\tpp := t.GetProbe()\n\t\tif len(pp) == 0 {\n\t\t\tpp = p\n\t\t}\n\t\tt.MarkProbeInProgress(pp)\n\t\tt.CompleteProbe(pp, autonatv2.Result{Addr: pp[0].Addr, Idx: 0, Reachability: network.ReachabilityPublic}, nil)\n\t}\n}\n\nfunc FuzzAddrsReachabilityTracker(f *testing.F) {\n\ttype autonatv2Response struct {\n\t\tResult autonatv2.Result\n\t\tErr    error\n\t}\n\n\tnewMockClient := func(b []byte) mockAutoNATClient {\n\t\tcount := 0\n\t\treturn mockAutoNATClient{\n\t\t\tF: func(_ context.Context, reqs []autonatv2.Request) (autonatv2.Result, error) {\n\t\t\t\tif len(b) == 0 {\n\t\t\t\t\treturn autonatv2.Result{}, nil\n\t\t\t\t}\n\t\t\t\tcount = (count + 1) % len(b)\n\t\t\t\tif b[count]%3 == 0 {\n\t\t\t\t\t// some address confirmed\n\t\t\t\t\tc1 := (count + 1) % len(b)\n\t\t\t\t\tc2 := (count + 2) % len(b)\n\t\t\t\t\trch := network.Reachability(b[c1] % 3)\n\t\t\t\t\tn := int(b[c2]) % len(reqs)\n\t\t\t\t\treturn autonatv2.Result{\n\t\t\t\t\t\tAddr:         reqs[n].Addr,\n\t\t\t\t\t\tIdx:          n,\n\t\t\t\t\t\tReachability: rch,\n\t\t\t\t\t}, nil\n\t\t\t\t}\n\t\t\t\toutcomes := []autonatv2Response{\n\t\t\t\t\t{Result: autonatv2.Result{AllAddrsRefused: true}},\n\t\t\t\t\t{Err: errors.New(\"test error\")},\n\t\t\t\t\t{Err: autonatv2.ErrPrivateAddrs},\n\t\t\t\t\t{Err: autonatv2.ErrNoPeers},\n\t\t\t\t\t{Result: autonatv2.Result{}, Err: nil},\n\t\t\t\t\t{Result: autonatv2.Result{Addr: reqs[0].Addr, Idx: 0, Reachability: network.ReachabilityPublic}},\n\t\t\t\t\t{Result: autonatv2.Result{\n\t\t\t\t\t\tAddr:            reqs[0].Addr,\n\t\t\t\t\t\tIdx:             0,\n\t\t\t\t\t\tReachability:    network.ReachabilityPublic,\n\t\t\t\t\t\tAllAddrsRefused: true,\n\t\t\t\t\t}},\n\t\t\t\t\t{Result: autonatv2.Result{\n\t\t\t\t\t\tAddr:            reqs[0].Addr,\n\t\t\t\t\t\tIdx:             len(reqs) - 1, // invalid idx\n\t\t\t\t\t\tReachability:    network.ReachabilityPublic,\n\t\t\t\t\t\tAllAddrsRefused: false,\n\t\t\t\t\t}},\n\t\t\t\t}\n\t\t\t\toutcome := outcomes[int(b[count])%len(outcomes)]\n\t\t\t\treturn outcome.Result, outcome.Err\n\t\t\t},\n\t\t}\n\t}\n\n\t// TODO: Move this to go-multiaddrs\n\tgetProto := func(protos []byte) ma.Multiaddr {\n\t\tprotoType := 0\n\t\tif len(protos) > 0 {\n\t\t\tprotoType = int(protos[0])\n\t\t}\n\n\t\tport1, port2 := 0, 0\n\t\tif len(protos) > 1 {\n\t\t\tport1 = int(protos[1])\n\t\t}\n\t\tif len(protos) > 2 {\n\t\t\tport2 = int(protos[2])\n\t\t}\n\t\tprotoTemplates := []string{\n\t\t\t\"/tcp/%d/\",\n\t\t\t\"/udp/%d/\",\n\t\t\t\"/udp/%d/quic-v1/\",\n\t\t\t\"/udp/%d/quic-v1/tcp/%d\",\n\t\t\t\"/udp/%d/quic-v1/webtransport/\",\n\t\t\t\"/udp/%d/webrtc/\",\n\t\t\t\"/udp/%d/webrtc-direct/\",\n\t\t\t\"/unix/hello/\",\n\t\t}\n\t\ts := protoTemplates[protoType%len(protoTemplates)]\n\t\tport1 %= (1 << 16)\n\t\tif strings.Count(s, \"%d\") == 1 {\n\t\t\treturn ma.StringCast(fmt.Sprintf(s, port1))\n\t\t}\n\t\tport2 %= (1 << 16)\n\t\treturn ma.StringCast(fmt.Sprintf(s, port1, port2))\n\t}\n\n\tgetIP := func(ips []byte) ma.Multiaddr {\n\t\tipType := 0\n\t\tif len(ips) > 0 {\n\t\t\tipType = int(ips[0])\n\t\t}\n\t\tips = ips[1:]\n\t\tvar x, y int64\n\t\tsplit := min(len(ips), 128/8)\n\t\tvar b [8]byte\n\t\tcopy(b[:], ips[:split])\n\t\tx = int64(binary.LittleEndian.Uint64(b[:]))\n\t\tclear(b[:])\n\t\tcopy(b[:], ips[split:])\n\t\ty = int64(binary.LittleEndian.Uint64(b[:]))\n\n\t\tvar ip netip.Addr\n\t\tswitch ipType % 3 {\n\t\tcase 0:\n\t\t\tip = netip.AddrFrom4([4]byte{byte(x), byte(x >> 8), byte(x >> 16), byte(x >> 24)})\n\t\t\treturn ma.StringCast(fmt.Sprintf(\"/ip4/%s/\", ip))\n\t\tcase 1:\n\t\t\tpubIP := net.ParseIP(\"2005::\") // Public IP address\n\t\t\tx := int64(binary.LittleEndian.Uint64(pubIP[0:8]))\n\t\t\tip = netip.AddrFrom16([16]byte{\n\t\t\t\tbyte(x), byte(x >> 8), byte(x >> 16), byte(x >> 24),\n\t\t\t\tbyte(x >> 32), byte(x >> 40), byte(x >> 48), byte(x >> 56),\n\t\t\t\tbyte(y), byte(y >> 8), byte(y >> 16), byte(y >> 24),\n\t\t\t\tbyte(y >> 32), byte(y >> 40), byte(y >> 48), byte(y >> 56),\n\t\t\t})\n\t\t\treturn ma.StringCast(fmt.Sprintf(\"/ip6/%s/\", ip))\n\t\tdefault:\n\t\t\tip := netip.AddrFrom16([16]byte{\n\t\t\t\tbyte(x), byte(x >> 8), byte(x >> 16), byte(x >> 24),\n\t\t\t\tbyte(x >> 32), byte(x >> 40), byte(x >> 48), byte(x >> 56),\n\t\t\t\tbyte(y), byte(y >> 8), byte(y >> 16), byte(y >> 24),\n\t\t\t\tbyte(y >> 32), byte(y >> 40), byte(y >> 48), byte(y >> 56),\n\t\t\t})\n\t\t\treturn ma.StringCast(fmt.Sprintf(\"/ip6/%s/\", ip))\n\t\t}\n\t}\n\n\tgetAddr := func(addrType int, ips, protos []byte) ma.Multiaddr {\n\t\tswitch addrType % 4 {\n\t\tcase 0:\n\t\t\treturn getIP(ips).Encapsulate(getProto(protos))\n\t\tcase 1:\n\t\t\treturn getProto(protos)\n\t\tcase 2:\n\t\t\treturn nil\n\t\tdefault:\n\t\t\treturn getIP(ips).Encapsulate(getProto(protos))\n\t\t}\n\t}\n\n\tgetDNSAddr := func(hostNameBytes, protos []byte) ma.Multiaddr {\n\t\thostName := strings.ReplaceAll(string(hostNameBytes), \"\\\\\", \"\")\n\t\thostName = strings.ReplaceAll(hostName, \"/\", \"\")\n\t\tif hostName == \"\" {\n\t\t\thostName = \"localhost\"\n\t\t}\n\t\tdnsType := 0\n\t\tif len(hostNameBytes) > 0 {\n\t\t\tdnsType = int(hostNameBytes[0])\n\t\t}\n\t\tdnsProtos := []string{\"dns\", \"dns4\", \"dns6\", \"dnsaddr\"}\n\t\tda := ma.StringCast(fmt.Sprintf(\"/%s/%s/\", dnsProtos[dnsType%len(dnsProtos)], hostName))\n\t\treturn da.Encapsulate(getProto(protos))\n\t}\n\n\tconst maxAddrs = 1000\n\tgetAddrs := func(numAddrs int, ips, protos, hostNames []byte) []ma.Multiaddr {\n\t\tif len(ips) == 0 || len(protos) == 0 || len(hostNames) == 0 {\n\t\t\treturn nil\n\t\t}\n\t\tnumAddrs = ((numAddrs % maxAddrs) + maxAddrs) % maxAddrs\n\t\taddrs := make([]ma.Multiaddr, numAddrs)\n\t\tipIdx := 0\n\t\tprotoIdx := 0\n\t\tfor i := range numAddrs {\n\t\t\taddrs[i] = getAddr(i, ips[ipIdx:], protos[protoIdx:])\n\t\t\tipIdx = (ipIdx + 1) % len(ips)\n\t\t\tprotoIdx = (protoIdx + 1) % len(protos)\n\t\t}\n\t\tmaxDNSAddrs := 10\n\t\tprotoIdx = 0\n\t\tfor i := 0; i < len(hostNames) && i < maxDNSAddrs; i += 2 {\n\t\t\ted := min(i+2, len(hostNames))\n\t\t\taddrs = append(addrs, getDNSAddr(hostNames[i:ed], protos[protoIdx:]))\n\t\t\tprotoIdx = (protoIdx + 1) % len(protos)\n\t\t}\n\t\treturn addrs\n\t}\n\n\tcl := clock.NewMock()\n\tf.Fuzz(func(t *testing.T, numAddrs int, ips, protos, hostNames, autonatResponses []byte) {\n\t\ttr := newAddrsReachabilityTracker(newMockClient(autonatResponses), nil, cl, nil)\n\t\trequire.NoError(t, tr.Start())\n\t\ttr.UpdateAddrs(getAddrs(numAddrs, ips, protos, hostNames))\n\n\t\t// fuzz tests need to finish in 10 seconds for some reason\n\t\t// https://github.com/golang/go/issues/48157\n\t\t// https://github.com/golang/go/commit/5d24203c394e6b64c42a9f69b990d94cb6c8aad4#diff-4e3b9481b8794eb058998e2bec389d3db7a23c54e67ac0f7259a3a5d2c79fd04R474-R483\n\t\tconst maxIters = 20\n\t\tfor range maxIters {\n\t\t\tcl.Add(5 * time.Minute)\n\t\t\ttime.Sleep(100 * time.Millisecond)\n\t\t}\n\t\trequire.NoError(t, tr.Close())\n\t})\n}\n"
  },
  {
    "path": "p2p/host/basic/basic_host.go",
    "content": "package basichost\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log/slog\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/connmgr\"\n\t\"github.com/libp2p/go-libp2p/core/event\"\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/autonat\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/eventbus\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/pstoremanager\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/relaysvc\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/autonatv2\"\n\trelayv2 \"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/relay\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/holepunch\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/identify\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/ping\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmsmux \"github.com/multiformats/go-multistream\"\n)\n\nvar log = logging.Logger(\"basichost\")\n\nvar (\n\t// DefaultNegotiationTimeout is the default value for HostOpts.NegotiationTimeout.\n\tDefaultNegotiationTimeout = 10 * time.Second\n\n\t// DefaultAddrsFactory is the default value for HostOpts.AddrsFactory.\n\tDefaultAddrsFactory = func(addrs []ma.Multiaddr) []ma.Multiaddr { return addrs }\n)\n\n// AddrsFactory functions can be passed to New in order to override\n// addresses returned by Addrs.\ntype AddrsFactory func([]ma.Multiaddr) []ma.Multiaddr\n\n// BasicHost is the basic implementation of the host.Host interface. This\n// particular host implementation:\n//   - uses a protocol muxer to mux per-protocol streams\n//   - uses an identity service to send + receive node information\n//   - uses a nat service to establish NAT port mappings\ntype BasicHost struct {\n\tctx       context.Context\n\tctxCancel context.CancelFunc\n\t// ensures we shutdown ONLY once\n\tcloseSync sync.Once\n\t// keep track of resources we need to wait on before shutting down\n\trefCount sync.WaitGroup\n\n\tnetwork      network.Network\n\tpsManager    *pstoremanager.PeerstoreManager\n\tmux          *msmux.MultistreamMuxer[protocol.ID]\n\tids          identify.IDService\n\thps          *holepunch.Service\n\tpings        *ping.PingService\n\tcmgr         connmgr.ConnManager\n\teventbus     event.Bus\n\trelayManager *relaysvc.RelayManager\n\n\tnegtimeout time.Duration\n\n\temitters struct {\n\t\tevtLocalProtocolsUpdated event.Emitter\n\t}\n\n\tautoNATMx sync.RWMutex\n\tautoNat   autonat.AutoNAT\n\n\tautonatv2      *autonatv2.AutoNAT\n\taddressManager *addrsManager\n}\n\nvar _ host.Host = (*BasicHost)(nil)\n\n// HostOpts holds options that can be passed to NewHost in order to\n// customize construction of the *BasicHost.\ntype HostOpts struct {\n\t// EventBus sets the event bus. Will construct a new event bus if omitted.\n\tEventBus event.Bus\n\n\t// MultistreamMuxer is essential for the *BasicHost and will use a sensible default value if omitted.\n\tMultistreamMuxer *msmux.MultistreamMuxer[protocol.ID]\n\n\t// NegotiationTimeout determines the read and write timeouts when negotiating\n\t// protocols for streams. If 0 or omitted, it will use\n\t// DefaultNegotiationTimeout. If below 0, timeouts on streams will be\n\t// deactivated.\n\tNegotiationTimeout time.Duration\n\n\t// AddrsFactory holds a function which can be used to override or filter the result of Addrs.\n\t// If omitted, there's no override or filtering, and the results of Addrs and AllAddrs are the same.\n\tAddrsFactory AddrsFactory\n\n\t// NATManager takes care of setting NAT port mappings, and discovering external addresses.\n\t// If omitted, this will simply be disabled.\n\tNATManager func(network.Network) NATManager\n\n\t// ConnManager is a libp2p connection manager\n\tConnManager connmgr.ConnManager\n\n\t// EnablePing indicates whether to instantiate the ping service\n\tEnablePing bool\n\n\t// EnableRelayService enables the circuit v2 relay (if we're publicly reachable).\n\tEnableRelayService bool\n\t// RelayServiceOpts are options for the circuit v2 relay.\n\tRelayServiceOpts []relayv2.Option\n\n\t// UserAgent sets the user-agent for the host.\n\tUserAgent string\n\n\t// ProtocolVersion sets the protocol version for the host.\n\tProtocolVersion string\n\n\t// DisableSignedPeerRecord disables the generation of Signed Peer Records on this host.\n\tDisableSignedPeerRecord bool\n\n\t// EnableHolePunching enables the peer to initiate/respond to hole punching attempts for NAT traversal.\n\tEnableHolePunching bool\n\t// HolePunchingOptions are options for the hole punching service\n\tHolePunchingOptions []holepunch.Option\n\n\t// EnableMetrics enables the metrics subsystems\n\tEnableMetrics bool\n\t// PrometheusRegisterer is the PrometheusRegisterer used for metrics\n\tPrometheusRegisterer prometheus.Registerer\n\t// AutoNATv2MetricsTracker tracks AutoNATv2 address reachability metrics\n\tAutoNATv2MetricsTracker MetricsTracker\n\n\t// ObservedAddrsManager maps our local listen addresses to external publicly observed addresses.\n\tObservedAddrsManager ObservedAddrsManager\n\n\tAutoNATv2 *autonatv2.AutoNAT\n}\n\n// NewHost constructs a new *BasicHost and activates it by attaching its stream and connection handlers to the given inet.Network.\nfunc NewHost(n network.Network, opts *HostOpts) (*BasicHost, error) {\n\tif opts == nil {\n\t\topts = &HostOpts{}\n\t}\n\tif opts.EventBus == nil {\n\t\topts.EventBus = eventbus.NewBus()\n\t}\n\n\tpsManager, err := pstoremanager.NewPeerstoreManager(n.Peerstore(), opts.EventBus, n)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\thostCtx, cancel := context.WithCancel(context.Background())\n\th := &BasicHost{\n\t\tnetwork:    n,\n\t\tpsManager:  psManager,\n\t\tmux:        msmux.NewMultistreamMuxer[protocol.ID](),\n\t\tnegtimeout: DefaultNegotiationTimeout,\n\t\teventbus:   opts.EventBus,\n\t\tctx:        hostCtx,\n\t\tctxCancel:  cancel,\n\t}\n\n\tif h.emitters.evtLocalProtocolsUpdated, err = h.eventbus.Emitter(&event.EvtLocalProtocolsUpdated{}, eventbus.Stateful); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif opts.MultistreamMuxer != nil {\n\t\th.mux = opts.MultistreamMuxer\n\t}\n\n\tidOpts := []identify.Option{\n\t\tidentify.UserAgent(opts.UserAgent),\n\t\tidentify.ProtocolVersion(opts.ProtocolVersion),\n\t}\n\n\t// we can't set this as a default above because it depends on the *BasicHost.\n\tif opts.DisableSignedPeerRecord {\n\t\tidOpts = append(idOpts, identify.DisableSignedPeerRecord())\n\t}\n\tif opts.EnableMetrics {\n\t\tidOpts = append(idOpts,\n\t\t\tidentify.WithMetricsTracer(\n\t\t\t\tidentify.NewMetricsTracer(identify.WithRegisterer(opts.PrometheusRegisterer))))\n\t}\n\n\th.ids, err = identify.NewIDService(h, idOpts...)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create Identify service: %s\", err)\n\t}\n\n\taddrFactory := DefaultAddrsFactory\n\tif opts.AddrsFactory != nil {\n\t\taddrFactory = opts.AddrsFactory\n\t}\n\n\tvar natmgr NATManager\n\tif opts.NATManager != nil {\n\t\tnatmgr = opts.NATManager(h.Network())\n\t}\n\n\tif opts.AutoNATv2 != nil {\n\t\th.autonatv2 = opts.AutoNATv2\n\t}\n\n\tvar autonatv2Client autonatv2Client // avoid typed nil errors\n\tif h.autonatv2 != nil {\n\t\tautonatv2Client = h.autonatv2\n\t}\n\n\t// Create addCertHashes function with interface assertion for swarm\n\taddCertHashesFunc := func(addrs []ma.Multiaddr) []ma.Multiaddr {\n\t\treturn addrs\n\t}\n\tif swarm, ok := h.Network().(interface {\n\t\tAddCertHashes(addrs []ma.Multiaddr) []ma.Multiaddr\n\t}); ok {\n\t\taddCertHashesFunc = swarm.AddCertHashes\n\t}\n\n\th.addressManager, err = newAddrsManager(\n\t\th.eventbus,\n\t\tnatmgr,\n\t\taddrFactory,\n\t\th.Network().ListenAddresses,\n\t\taddCertHashesFunc,\n\t\topts.ObservedAddrsManager,\n\t\tautonatv2Client,\n\t\topts.EnableMetrics,\n\t\topts.PrometheusRegisterer,\n\t\topts.DisableSignedPeerRecord,\n\t\th.Peerstore().PrivKey(h.ID()),\n\t\th.Peerstore(),\n\t\th.ID(),\n\t)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create address service: %w\", err)\n\t}\n\n\tif opts.EnableHolePunching {\n\t\tif opts.EnableMetrics {\n\t\t\thpOpts := []holepunch.Option{\n\t\t\t\tholepunch.WithMetricsTracer(holepunch.NewMetricsTracer(holepunch.WithRegisterer(opts.PrometheusRegisterer)))}\n\t\t\topts.HolePunchingOptions = append(hpOpts, opts.HolePunchingOptions...)\n\n\t\t}\n\t\th.hps, err = holepunch.NewService(h, h.ids, h.addressManager.HolePunchAddrs, opts.HolePunchingOptions...)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to create hole punch service: %w\", err)\n\t\t}\n\t}\n\n\tif uint64(opts.NegotiationTimeout) != 0 {\n\t\th.negtimeout = opts.NegotiationTimeout\n\t}\n\n\tif opts.ConnManager == nil {\n\t\th.cmgr = &connmgr.NullConnMgr{}\n\t} else {\n\t\th.cmgr = opts.ConnManager\n\t\tn.Notify(h.cmgr.Notifee())\n\t}\n\n\tif opts.EnableRelayService {\n\t\tif opts.EnableMetrics {\n\t\t\t// Prefer explicitly provided metrics tracer\n\t\t\tmetricsOpt := []relayv2.Option{\n\t\t\t\trelayv2.WithMetricsTracer(\n\t\t\t\t\trelayv2.NewMetricsTracer(relayv2.WithRegisterer(opts.PrometheusRegisterer)))}\n\t\t\topts.RelayServiceOpts = append(metricsOpt, opts.RelayServiceOpts...)\n\t\t}\n\t\th.relayManager = relaysvc.NewRelayManager(h, opts.RelayServiceOpts...)\n\t}\n\n\tif opts.EnablePing {\n\t\th.pings = ping.NewPingService(h)\n\t}\n\n\tn.SetStreamHandler(h.newStreamHandler)\n\n\treturn h, nil\n}\n\n// Start starts background tasks in the host\n// TODO: Return error and handle it in the caller?\nfunc (h *BasicHost) Start() {\n\th.psManager.Start()\n\tif h.autonatv2 != nil {\n\t\terr := h.autonatv2.Start(h)\n\t\tif err != nil {\n\t\t\tlog.Error(\"autonat v2 failed to start\", \"err\", err)\n\t\t}\n\t}\n\t// register to be notified when the network's listen addrs change,\n\t// so we can update our address set and push events if needed\n\th.Network().Notify(h.addressManager.NetNotifee())\n\tif err := h.addressManager.Start(); err != nil {\n\t\tlog.Error(\"address service failed to start\", \"err\", err)\n\t}\n\n\th.ids.Start()\n}\n\n// newStreamHandler is the remote-opened stream handler for network.Network\n// TODO: this feels a bit wonky\nfunc (h *BasicHost) newStreamHandler(s network.Stream) {\n\tbefore := time.Now()\n\n\tif h.negtimeout > 0 {\n\t\tif err := s.SetDeadline(time.Now().Add(h.negtimeout)); err != nil {\n\t\t\tlog.Debug(\"setting stream deadline\", \"err\", err)\n\t\t\ts.Reset()\n\t\t\treturn\n\t\t}\n\t}\n\n\tprotoID, handle, err := h.Mux().Negotiate(s)\n\ttook := time.Since(before)\n\tif err != nil {\n\t\tif err == io.EOF {\n\t\t\tlvl := slog.LevelDebug\n\t\t\tif took > time.Second*10 {\n\t\t\t\tlvl = slog.LevelWarn\n\t\t\t}\n\t\t\tlog.Log(context.Background(), lvl, \"protocol EOF\", \"remote_peer\", s.Conn().RemotePeer(), \"duration\", took)\n\t\t} else {\n\t\t\tlog.Debug(\"protocol mux failed\", \"err\", err, \"duration\", took, \"stream_id\", s.ID(), \"remote_peer\", s.Conn().RemotePeer(), \"remote_multiaddr\", s.Conn().RemoteMultiaddr())\n\t\t}\n\t\ts.ResetWithError(network.StreamProtocolNegotiationFailed)\n\t\treturn\n\t}\n\n\tif h.negtimeout > 0 {\n\t\tif err := s.SetDeadline(time.Time{}); err != nil {\n\t\t\tlog.Debug(\"resetting stream deadline\", \"err\", err)\n\t\t\ts.Reset()\n\t\t\treturn\n\t\t}\n\t}\n\n\tif err := s.SetProtocol(protoID); err != nil {\n\t\tlog.Debug(\"error setting stream protocol\", \"err\", err)\n\t\ts.ResetWithError(network.StreamResourceLimitExceeded)\n\t\treturn\n\t}\n\n\tlog.Debug(\"negotiated\", \"protocol\", protoID, \"duration\", took)\n\n\thandle(protoID, s)\n}\n\n// ID returns the (local) peer.ID associated with this Host\nfunc (h *BasicHost) ID() peer.ID {\n\treturn h.Network().LocalPeer()\n}\n\n// Peerstore returns the Host's repository of Peer Addresses and Keys.\nfunc (h *BasicHost) Peerstore() peerstore.Peerstore {\n\treturn h.Network().Peerstore()\n}\n\n// Network returns the Network interface of the Host\nfunc (h *BasicHost) Network() network.Network {\n\treturn h.network\n}\n\n// Mux returns the Mux multiplexing incoming streams to protocol handlers\nfunc (h *BasicHost) Mux() protocol.Switch {\n\treturn h.mux\n}\n\n// IDService returns\nfunc (h *BasicHost) IDService() identify.IDService {\n\treturn h.ids\n}\n\nfunc (h *BasicHost) EventBus() event.Bus {\n\treturn h.eventbus\n}\n\n// SetStreamHandler sets the protocol handler on the Host's Mux.\n// This is equivalent to:\n//\n//\thost.Mux().SetHandler(proto, handler)\n//\n// (Thread-safe)\nfunc (h *BasicHost) SetStreamHandler(pid protocol.ID, handler network.StreamHandler) {\n\th.Mux().AddHandler(pid, func(_ protocol.ID, rwc io.ReadWriteCloser) error {\n\t\tis := rwc.(network.Stream)\n\t\thandler(is)\n\t\treturn nil\n\t})\n\th.emitters.evtLocalProtocolsUpdated.Emit(event.EvtLocalProtocolsUpdated{\n\t\tAdded: []protocol.ID{pid},\n\t})\n}\n\n// SetStreamHandlerMatch sets the protocol handler on the Host's Mux\n// using a matching function to do protocol comparisons\nfunc (h *BasicHost) SetStreamHandlerMatch(pid protocol.ID, m func(protocol.ID) bool, handler network.StreamHandler) {\n\th.Mux().AddHandlerWithFunc(pid, m, func(_ protocol.ID, rwc io.ReadWriteCloser) error {\n\t\tis := rwc.(network.Stream)\n\t\thandler(is)\n\t\treturn nil\n\t})\n\th.emitters.evtLocalProtocolsUpdated.Emit(event.EvtLocalProtocolsUpdated{\n\t\tAdded: []protocol.ID{pid},\n\t})\n}\n\n// RemoveStreamHandler returns ..\nfunc (h *BasicHost) RemoveStreamHandler(pid protocol.ID) {\n\th.Mux().RemoveHandler(pid)\n\th.emitters.evtLocalProtocolsUpdated.Emit(event.EvtLocalProtocolsUpdated{\n\t\tRemoved: []protocol.ID{pid},\n\t})\n}\n\n// NewStream opens a new stream to given peer p, and writes a p2p/protocol\n// header with given protocol.ID. If there is no connection to p, attempts\n// to create one. If ProtocolID is \"\", writes no header.\n// (Thread-safe)\nfunc (h *BasicHost) NewStream(ctx context.Context, p peer.ID, pids ...protocol.ID) (str network.Stream, strErr error) {\n\tif _, ok := ctx.Deadline(); !ok {\n\t\tif h.negtimeout > 0 {\n\t\t\tvar cancel context.CancelFunc\n\t\t\tctx, cancel = context.WithTimeout(ctx, h.negtimeout)\n\t\t\tdefer cancel()\n\t\t}\n\t}\n\n\t// If the caller wants to prevent the host from dialing, it should use the NoDial option.\n\tif nodial, _ := network.GetNoDial(ctx); !nodial {\n\t\terr := h.Connect(ctx, peer.AddrInfo{ID: p})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\ts, err := h.Network().NewStream(network.WithNoDial(ctx, \"already dialed\"), p)\n\tif err != nil {\n\t\t// TODO: It would be nicer to get the actual error from the swarm,\n\t\t// but this will require some more work.\n\t\tif errors.Is(err, network.ErrNoConn) {\n\t\t\treturn nil, errors.New(\"connection failed\")\n\t\t}\n\t\treturn nil, fmt.Errorf(\"failed to open stream: %w\", err)\n\t}\n\tdefer func() {\n\t\tif strErr != nil && s != nil {\n\t\t\ts.ResetWithError(network.StreamProtocolNegotiationFailed)\n\t\t}\n\t}()\n\n\t// Wait for any in-progress identifies on the connection to finish. This\n\t// is faster than negotiating.\n\t//\n\t// If the other side doesn't support identify, that's fine. This will\n\t// just be a no-op.\n\tselect {\n\tcase <-h.ids.IdentifyWait(s.Conn()):\n\tcase <-ctx.Done():\n\t\treturn nil, fmt.Errorf(\"identify failed to complete: %w\", ctx.Err())\n\t}\n\n\tpref, err := h.preferredProtocol(p, pids)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif pref != \"\" {\n\t\tif err := s.SetProtocol(pref); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tlzcon := msmux.NewMSSelect(s, pref)\n\t\treturn &streamWrapper{\n\t\t\tStream: s,\n\t\t\trw:     lzcon,\n\t\t}, nil\n\t}\n\n\t// Negotiate the protocol in the background, obeying the context.\n\tvar selected protocol.ID\n\terrCh := make(chan error, 1)\n\tgo func() {\n\t\tselected, err = msmux.SelectOneOf(pids, s)\n\t\terrCh <- err\n\t}()\n\tselect {\n\tcase err = <-errCh:\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to negotiate protocol: %w\", err)\n\t\t}\n\tcase <-ctx.Done():\n\t\ts.ResetWithError(network.StreamProtocolNegotiationFailed)\n\t\t// wait for `SelectOneOf` to error out because of resetting the stream.\n\t\t<-errCh\n\t\treturn nil, fmt.Errorf(\"failed to negotiate protocol: %w\", ctx.Err())\n\t}\n\n\tif err := s.SetProtocol(selected); err != nil {\n\t\ts.ResetWithError(network.StreamResourceLimitExceeded)\n\t\treturn nil, err\n\t}\n\t_ = h.Peerstore().AddProtocols(p, selected) // adding the protocol to the peerstore isn't critical\n\treturn s, nil\n}\n\nfunc (h *BasicHost) preferredProtocol(p peer.ID, pids []protocol.ID) (protocol.ID, error) {\n\tsupported, err := h.Peerstore().SupportsProtocols(p, pids...)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tvar out protocol.ID\n\tif len(supported) > 0 {\n\t\tout = supported[0]\n\t}\n\treturn out, nil\n}\n\n// Connect ensures there is a connection between this host and the peer with\n// given peer.ID. If there is not an active connection, Connect will issue a\n// h.Network.Dial, and block until a connection is open, or an error is returned.\n// Connect will absorb the addresses in pi into its internal peerstore.\n// It will also resolve any /dns4, /dns6, and /dnsaddr addresses.\nfunc (h *BasicHost) Connect(ctx context.Context, pi peer.AddrInfo) error {\n\t// absorb addresses into peerstore\n\th.Peerstore().AddAddrs(pi.ID, pi.Addrs, peerstore.TempAddrTTL)\n\n\tforceDirect, _ := network.GetForceDirectDial(ctx)\n\tcanUseLimitedConn, _ := network.GetAllowLimitedConn(ctx)\n\tif !forceDirect {\n\t\tconnectedness := h.Network().Connectedness(pi.ID)\n\t\tif connectedness == network.Connected || (canUseLimitedConn && connectedness == network.Limited) {\n\t\t\treturn nil\n\t\t}\n\t}\n\n\treturn h.dialPeer(ctx, pi.ID)\n}\n\n// dialPeer opens a connection to peer, and makes sure to identify\n// the connection once it has been opened.\nfunc (h *BasicHost) dialPeer(ctx context.Context, p peer.ID) error {\n\tlog.Debug(\"host dialing peer\", \"source_peer\", h.ID(), \"destination_peer\", p)\n\tc, err := h.Network().DialPeer(ctx, p)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to dial: %w\", err)\n\t}\n\n\t// TODO: Consider removing this? On one hand, it's nice because we can\n\t// assume that things like the agent version are usually set when this\n\t// returns. On the other hand, we don't _really_ need to wait for this.\n\t//\n\t// This is mostly here to preserve existing behavior.\n\tselect {\n\tcase <-h.ids.IdentifyWait(c):\n\tcase <-ctx.Done():\n\t\treturn fmt.Errorf(\"identify failed to complete: %w\", ctx.Err())\n\t}\n\n\tlog.Debug(\"host finished dialing peer\", \"source_peer\", h.ID(), \"destination_peer\", p)\n\treturn nil\n}\n\nfunc (h *BasicHost) ConnManager() connmgr.ConnManager {\n\treturn h.cmgr\n}\n\n// Addrs returns listening addresses.\n// When used with AutoRelay, and if the host is not publicly reachable,\n// this will not have the host's direct public addresses, it'll only have\n// the relay addresses and private addresses.\nfunc (h *BasicHost) Addrs() []ma.Multiaddr {\n\treturn h.addressManager.Addrs()\n}\n\n// AllAddrs returns all the addresses the host is listening on except circuit addresses.\nfunc (h *BasicHost) AllAddrs() []ma.Multiaddr {\n\treturn h.addressManager.DirectAddrs()\n}\n\n// ConfirmedAddrs returns all addresses of the host grouped by their reachability\n// as verified by autonatv2.\n//\n// Experimental: This API may change in the future without deprecation.\n//\n// Requires AutoNATv2 to be enabled.\nfunc (h *BasicHost) ConfirmedAddrs() (reachable []ma.Multiaddr, unreachable []ma.Multiaddr, unknown []ma.Multiaddr) {\n\treturn h.addressManager.ConfirmedAddrs()\n}\n\n// SetAutoNat sets the autonat service for the host.\nfunc (h *BasicHost) SetAutoNat(a autonat.AutoNAT) {\n\th.autoNATMx.Lock()\n\tdefer h.autoNATMx.Unlock()\n\tif h.autoNat == nil {\n\t\th.autoNat = a\n\t}\n}\n\n// GetAutoNat returns the host's AutoNAT service, if AutoNAT is enabled.\n//\n// Deprecated: Use `BasicHost.Reachability` to get the host's reachability.\nfunc (h *BasicHost) GetAutoNat() autonat.AutoNAT {\n\th.autoNATMx.Lock()\n\tdefer h.autoNATMx.Unlock()\n\treturn h.autoNat\n}\n\n// Reachability returns the host's reachability status.\nfunc (h *BasicHost) Reachability() network.Reachability {\n\treturn *h.addressManager.hostReachability.Load()\n}\n\n// Close shuts down the Host's services (network, etc).\nfunc (h *BasicHost) Close() error {\n\th.closeSync.Do(func() {\n\t\th.ctxCancel()\n\t\tif h.cmgr != nil {\n\t\t\th.cmgr.Close()\n\t\t}\n\n\t\tif h.ids != nil {\n\t\t\th.ids.Close()\n\t\t}\n\t\tif h.autoNat != nil {\n\t\t\th.autoNat.Close()\n\t\t}\n\t\tif h.relayManager != nil {\n\t\t\th.relayManager.Close()\n\t\t}\n\t\tif h.hps != nil {\n\t\t\th.hps.Close()\n\t\t}\n\t\tif h.autonatv2 != nil {\n\t\t\th.autonatv2.Close()\n\t\t}\n\n\t\t_ = h.emitters.evtLocalProtocolsUpdated.Close()\n\n\t\tif err := h.network.Close(); err != nil {\n\t\t\tlog.Error(\"swarm close failed\", \"err\", err)\n\t\t}\n\n\t\th.addressManager.Close()\n\t\th.psManager.Close()\n\t\tif h.Peerstore() != nil {\n\t\t\th.Peerstore().Close()\n\t\t}\n\n\t\th.refCount.Wait()\n\n\t\tif h.Network().ResourceManager() != nil {\n\t\t\th.Network().ResourceManager().Close()\n\t\t}\n\t})\n\n\treturn nil\n}\n\ntype streamWrapper struct {\n\tnetwork.Stream\n\trw io.ReadWriteCloser\n}\n\nfunc (s *streamWrapper) Read(b []byte) (int, error) {\n\treturn s.rw.Read(b)\n}\n\nfunc (s *streamWrapper) Write(b []byte) (int, error) {\n\treturn s.rw.Write(b)\n}\n\nfunc (s *streamWrapper) Close() error {\n\t// Set a read deadline to prevent Close() from blocking indefinitely\n\t// waiting for the multistream-select handshake to complete.\n\t// This can happen when the remote peer is slow or unresponsive.\n\t// See: https://github.com/multiformats/go-multistream/issues/47\n\t_ = s.Stream.SetReadDeadline(time.Now().Add(DefaultNegotiationTimeout))\n\treturn s.rw.Close()\n}\n\nfunc (s *streamWrapper) CloseWrite() error {\n\t// Flush the handshake before closing, but ignore the error. The other\n\t// end may have closed their side for reading.\n\t//\n\t// If something is wrong with the stream, the user will get on error on\n\t// read instead.\n\tif flusher, ok := s.rw.(interface{ Flush() error }); ok {\n\t\t_ = flusher.Flush()\n\t}\n\treturn s.Stream.CloseWrite()\n}\n"
  },
  {
    "path": "p2p/host/basic/basic_host_synctest_test.go",
    "content": "//go:build go1.25\n\npackage basichost_test\n\nimport (\n\t\"testing\"\n\t\"testing/synctest\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\tbasichost \"github.com/libp2p/go-libp2p/p2p/host/basic\"\n\t\"github.com/libp2p/go-libp2p/x/simlibp2p\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\n// TestStreamCloseDoesNotHangOnUnresponsivePeer verifies that stream.Close()\n// returns within DefaultNegotiationTimeout even when the remote peer never\n// completes the multistream handshake. Without the read deadline fix in\n// streamWrapper.Close(), this would hang indefinitely.\nfunc TestStreamCloseDoesNotHangOnUnresponsivePeer_synctest(t *testing.T) {\n\tsynctest.Test(t, func(t *testing.T) {\n\t\tctx := t.Context()\n\n\t\th1, h2 := simlibp2p.GetBasicHostPair(t)\n\t\tdefer h1.Close()\n\t\tdefer h2.Close()\n\n\t\tconst testProto = \"/test/hang\"\n\n\t\t// Manually add protocol to peerstore so h1 thinks h2 supports it.\n\t\t// This makes NewStream use lazy multistream (skipping negotiation until Close).\n\t\th1.Peerstore().AddProtocols(h2.ID(), testProto)\n\n\t\t// h2 accepts streams at the network level but never responds to\n\t\t// multistream protocol negotiation, simulating an unresponsive peer.\n\t\th2.Network().SetStreamHandler(func(s network.Stream) {\n\t\t\t// Read incoming data but never write back - simulates unresponsive peer\n\t\t\tbuf := make([]byte, 1024)\n\t\t\tfor {\n\t\t\t\t_, err := s.Read(buf)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\n\t\t// Open stream to h2 - uses lazy multistream because protocol is \"known\"\n\t\ts, err := h1.NewStream(ctx, h2.ID(), testProto)\n\t\trequire.NoError(t, err)\n\n\t\t// Trigger the lazy handshake by writing data.\n\t\t// The write succeeds (buffered), but the read handshake will block\n\t\t// because h2 never sends a response.\n\t\t_, err = s.Write([]byte(\"trigger handshake\"))\n\t\trequire.NoError(t, err)\n\n\t\t// Close() should return within DefaultNegotiationTimeout because the fix\n\t\t// sets a read deadline before calling the underlying Close().\n\t\t// Without the fix, this would hang indefinitely.\n\t\telapsedCh := make(chan time.Duration)\n\t\tgo func() {\n\t\t\tstart := time.Now()\n\t\t\t_ = s.Close()\n\t\t\telapsedCh <- time.Since(start)\n\t\t}()\n\n\t\tmaxExpected := basichost.DefaultNegotiationTimeout\n\t\tvar elapsed time.Duration\n\t\tselect {\n\t\tcase elapsed = <-elapsedCh:\n\t\tcase <-time.After(maxExpected + time.Second):\n\t\t\tt.Fatal(\"timeout waiting for Close()\")\n\t\t}\n\n\t\trequire.Equal(t, elapsed, maxExpected,\n\t\t\t\"Close() took %v, expected < %v (DefaultNegotiationTimeout + margin)\", elapsed, maxExpected)\n\n\t\tt.Logf(\"Close() returned in %v (limit: %v)\", elapsed, maxExpected)\n\t})\n}\n"
  },
  {
    "path": "p2p/host/basic/basic_host_test.go",
    "content": "package basichost\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"io\"\n\t\"reflect\"\n\t\"slices\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p-testing/race\"\n\t\"github.com/libp2p/go-libp2p/core/event\"\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\t\"github.com/libp2p/go-libp2p/core/record\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/autonat\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/eventbus\"\n\tswarmt \"github.com/libp2p/go-libp2p/p2p/net/swarm/testing\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/identify\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/multiformats/go-multiaddr/matest\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestHostDoubleClose(t *testing.T) {\n\th1, err := NewHost(swarmt.GenSwarm(t), nil)\n\trequire.NoError(t, err)\n\th1.Close()\n\th1.Close()\n}\n\nfunc TestHostSimple(t *testing.T) {\n\tctx := context.Background()\n\th1, err := NewHost(swarmt.GenSwarm(t), nil)\n\trequire.NoError(t, err)\n\tdefer h1.Close()\n\th1.Start()\n\th2, err := NewHost(swarmt.GenSwarm(t), nil)\n\trequire.NoError(t, err)\n\n\tdefer h2.Close()\n\th2.Start()\n\n\th2pi := h2.Peerstore().PeerInfo(h2.ID())\n\trequire.NoError(t, h1.Connect(ctx, h2pi))\n\n\tpiper, pipew := io.Pipe()\n\th2.SetStreamHandler(protocol.TestingID, func(s network.Stream) {\n\t\tdefer s.Close()\n\t\tw := io.MultiWriter(s, pipew)\n\t\tio.Copy(w, s) // mirror everything\n\t})\n\n\ts, err := h1.NewStream(ctx, h2pi.ID, protocol.TestingID)\n\trequire.NoError(t, err)\n\n\t// write to the stream\n\tbuf1 := []byte(\"abcdefghijkl\")\n\t_, err = s.Write(buf1)\n\trequire.NoError(t, err)\n\n\t// get it from the stream (echoed)\n\tbuf2 := make([]byte, len(buf1))\n\t_, err = io.ReadFull(s, buf2)\n\trequire.NoError(t, err)\n\trequire.Equal(t, buf1, buf2)\n\n\t// get it from the pipe (tee)\n\tbuf3 := make([]byte, len(buf1))\n\t_, err = io.ReadFull(piper, buf3)\n\trequire.NoError(t, err)\n\trequire.Equal(t, buf1, buf3)\n}\n\nfunc TestMultipleClose(t *testing.T) {\n\th, err := NewHost(swarmt.GenSwarm(t), nil)\n\trequire.NoError(t, err)\n\n\trequire.NoError(t, h.Close())\n\trequire.NoError(t, h.Close())\n\th2, err := NewHost(swarmt.GenSwarm(t), nil)\n\trequire.NoError(t, err)\n\tdefer h2.Close()\n\trequire.Error(t, h.Connect(context.Background(), peer.AddrInfo{ID: h2.ID(), Addrs: h2.Addrs()}))\n\th.Network().Peerstore().AddAddrs(h2.ID(), h2.Addrs(), peerstore.PermanentAddrTTL)\n\t_, err = h.NewStream(context.Background(), h2.ID())\n\trequire.Error(t, err)\n\trequire.Empty(t, h.Addrs())\n\trequire.Empty(t, h.AllAddrs())\n}\n\nfunc TestSignedPeerRecordWithNoListenAddrs(t *testing.T) {\n\th, err := NewHost(swarmt.GenSwarm(t, swarmt.OptDialOnly), nil)\n\trequire.NoError(t, err)\n\tdefer h.Close()\n\th.Start()\n\n\trequire.Empty(t, h.Addrs(), \"expected no listen addrs\")\n\t// now add a listen addr\n\trequire.NoError(t, h.Network().Listen(ma.StringCast(\"/ip4/0.0.0.0/tcp/0\")))\n\trequire.NotEmpty(t, h.Addrs(), \"expected at least 1 listen addr\")\n\n\tcab, ok := peerstore.GetCertifiedAddrBook(h.Peerstore())\n\tif !ok {\n\t\tt.Fatalf(\"peerstore doesn't support certified addrs\")\n\t}\n\t// the signed record with the new addr is added async\n\tvar env *record.Envelope\n\trequire.Eventually(t, func() bool {\n\t\tenv = cab.GetPeerRecord(h.ID())\n\t\treturn env != nil\n\t}, 500*time.Millisecond, 10*time.Millisecond)\n\trec, err := env.Record()\n\trequire.NoError(t, err)\n\trequire.NotEmpty(t, rec.(*peer.PeerRecord).Addrs)\n}\n\nfunc TestProtocolHandlerEvents(t *testing.T) {\n\th, err := NewHost(swarmt.GenSwarm(t), nil)\n\trequire.NoError(t, err)\n\tdefer h.Close()\n\n\tsub, err := h.EventBus().Subscribe(&event.EvtLocalProtocolsUpdated{}, eventbus.BufSize(16))\n\trequire.NoError(t, err)\n\tdefer sub.Close()\n\n\t// the identify service adds new protocol handlers shortly after the host\n\t// starts. this helps us filter those events out, since they're unrelated\n\t// to the test.\n\tisIdentify := func(evt event.EvtLocalProtocolsUpdated) bool {\n\t\tfor _, p := range evt.Added {\n\t\t\tif p == identify.ID || p == identify.IDPush {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\treturn false\n\t}\n\n\tnextEvent := func() event.EvtLocalProtocolsUpdated {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase evt := <-sub.Out():\n\t\t\t\tnext := evt.(event.EvtLocalProtocolsUpdated)\n\t\t\t\tif isIdentify(next) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treturn next\n\t\t\tcase <-time.After(5 * time.Second):\n\t\t\t\tt.Fatal(\"event not received in 5 seconds\")\n\t\t\t}\n\t\t}\n\t}\n\n\tassert := func(added, removed []protocol.ID) {\n\t\tnext := nextEvent()\n\t\tif !reflect.DeepEqual(added, next.Added) {\n\t\t\tt.Errorf(\"expected added: %v; received: %v\", added, next.Added)\n\t\t}\n\t\tif !reflect.DeepEqual(removed, next.Removed) {\n\t\t\tt.Errorf(\"expected removed: %v; received: %v\", removed, next.Removed)\n\t\t}\n\t}\n\n\th.SetStreamHandler(protocol.TestingID, func(_ network.Stream) {})\n\tassert([]protocol.ID{protocol.TestingID}, nil)\n\th.SetStreamHandler(\"foo\", func(_ network.Stream) {})\n\tassert([]protocol.ID{\"foo\"}, nil)\n\th.RemoveStreamHandler(protocol.TestingID)\n\tassert(nil, []protocol.ID{protocol.TestingID})\n}\n\nfunc TestHostAddrsFactory(t *testing.T) {\n\tmaddr := ma.StringCast(\"/ip4/1.2.3.4/tcp/1234\")\n\taddrsFactory := func(_ []ma.Multiaddr) []ma.Multiaddr {\n\t\treturn []ma.Multiaddr{maddr}\n\t}\n\n\th, err := NewHost(swarmt.GenSwarm(t), &HostOpts{AddrsFactory: addrsFactory})\n\trequire.NoError(t, err)\n\tdefer h.Close()\n\n\taddrs := h.Addrs()\n\tif len(addrs) != 1 {\n\t\tt.Fatalf(\"expected 1 addr, got %+v\", addrs)\n\t}\n\tif !addrs[0].Equal(maddr) {\n\t\tt.Fatalf(\"expected %s, got %s\", maddr.String(), addrs[0].String())\n\t}\n\n\tautoNat, err := autonat.New(h, autonat.WithReachability(network.ReachabilityPublic))\n\tif err != nil {\n\t\tt.Fatalf(\"should be able to attach autonat: %v\", err)\n\t}\n\th.SetAutoNat(autoNat)\n\taddrs = h.Addrs()\n\tif len(addrs) != 1 {\n\t\tt.Fatalf(\"didn't expect change in returned addresses.\")\n\t}\n}\n\nfunc TestAllAddrs(t *testing.T) {\n\t// no listen addrs\n\th, err := NewHost(swarmt.GenSwarm(t, swarmt.OptDialOnly), nil)\n\trequire.NoError(t, err)\n\th.Start()\n\tdefer h.Close()\n\trequire.Nil(t, h.AllAddrs())\n\n\t// listen on loopback\n\tladdr := ma.StringCast(\"/ip4/127.0.0.1/tcp/0\")\n\trequire.NoError(t, h.Network().Listen(laddr))\n\trequire.Len(t, h.AllAddrs(), 1)\n\tfirstAddr := h.AllAddrs()[0]\n\trequire.Equal(t, \"/ip4/127.0.0.1\", ma.Split(firstAddr)[0].String())\n\n\t// listen on IPv4 0.0.0.0\n\trequire.NoError(t, h.Network().Listen(ma.StringCast(\"/ip4/0.0.0.0/tcp/0\")))\n\t// should contain more addresses than just the one from last time\n\trequire.Greater(t, len(h.AllAddrs()), 1)\n\t// Should still contain the original addr.\n\trequire.True(t, ma.Contains(h.AllAddrs(), firstAddr), \"should still contain the original addr\")\n}\n\nfunc TestAllAddrsUnique(t *testing.T) {\n\tif race.WithRace() {\n\t\tt.Skip(\"updates addrChangeTickrInterval which might be racy\")\n\t}\n\toldInterval := addrChangeTickrInterval\n\taddrChangeTickrInterval = 100 * time.Millisecond\n\tdefer func() {\n\t\taddrChangeTickrInterval = oldInterval\n\t}()\n\tsendNewAddrs := make(chan struct{})\n\topts := HostOpts{\n\t\tAddrsFactory: func(_ []ma.Multiaddr) []ma.Multiaddr {\n\t\t\tselect {\n\t\t\tcase <-sendNewAddrs:\n\t\t\t\treturn []ma.Multiaddr{\n\t\t\t\t\tma.StringCast(\"/ip4/1.2.3.4/tcp/1\"),\n\t\t\t\t\tma.StringCast(\"/ip4/1.2.3.4/tcp/1\"),\n\t\t\t\t\tma.StringCast(\"/ip4/1.2.3.4/tcp/1\"),\n\t\t\t\t\tma.StringCast(\"/ip4/1.2.3.4/udp/1/quic-v1\"),\n\t\t\t\t\tma.StringCast(\"/ip4/1.2.3.4/udp/1/quic-v1\"),\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t},\n\t}\n\t// no listen addrs\n\th, err := NewHost(swarmt.GenSwarm(t, swarmt.OptDialOnly), &opts)\n\trequire.NoError(t, err)\n\tdefer h.Close()\n\th.Start()\n\n\tsub, err := h.EventBus().Subscribe(&event.EvtLocalAddressesUpdated{})\n\trequire.NoError(t, err)\n\tout := make(chan int)\n\tdone := make(chan struct{})\n\tgo func() {\n\t\tcnt := 0\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-sub.Out():\n\t\t\t\tcnt++\n\t\t\tcase <-done:\n\t\t\t\tout <- cnt\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\tclose(sendNewAddrs)\n\trequire.Len(t, h.Addrs(), 2)\n\tmatest.AssertEqualMultiaddrs(t, []ma.Multiaddr{ma.StringCast(\"/ip4/1.2.3.4/tcp/1\"), ma.StringCast(\"/ip4/1.2.3.4/udp/1/quic-v1\")}, h.Addrs())\n\ttime.Sleep(2*addrChangeTickrInterval + 1*time.Second) // the background loop runs every 5 seconds. Wait for 2x that time.\n\tclose(done)\n\tcnt := <-out\n\trequire.Equal(t, 1, cnt)\n}\n\n// getHostPair gets a new pair of hosts.\n// The first host initiates the connection to the second host.\nfunc getHostPair(t *testing.T) (host.Host, host.Host) {\n\tt.Helper()\n\n\th1, err := NewHost(swarmt.GenSwarm(t), nil)\n\trequire.NoError(t, err)\n\th1.Start()\n\th2, err := NewHost(swarmt.GenSwarm(t), nil)\n\trequire.NoError(t, err)\n\th2.Start()\n\n\tctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)\n\tdefer cancel()\n\th2pi := h2.Peerstore().PeerInfo(h2.ID())\n\trequire.NoError(t, h1.Connect(ctx, h2pi))\n\treturn h1, h2\n}\n\nfunc assertWait(t *testing.T, c chan protocol.ID, exp protocol.ID) {\n\tt.Helper()\n\tselect {\n\tcase proto := <-c:\n\t\tif proto != exp {\n\t\t\tt.Fatalf(\"should have connected on %s, got %s\", exp, proto)\n\t\t}\n\tcase <-time.After(time.Second * 5):\n\t\tt.Fatal(\"timeout waiting for stream\")\n\t}\n}\n\nfunc TestHostProtoPreference(t *testing.T) {\n\th1, h2 := getHostPair(t)\n\tdefer h1.Close()\n\tdefer h2.Close()\n\n\tconst (\n\t\tprotoOld   = \"/testing\"\n\t\tprotoNew   = \"/testing/1.1.0\"\n\t\tprotoMinor = \"/testing/1.2.0\"\n\t)\n\n\tconnectedOn := make(chan protocol.ID)\n\thandler := func(s network.Stream) {\n\t\tconnectedOn <- s.Protocol()\n\t\ts.Close()\n\t}\n\n\t// Prevent pushing identify information so this test works.\n\th1.RemoveStreamHandler(identify.IDPush)\n\n\th2.SetStreamHandler(protoOld, handler)\n\n\ts, err := h1.NewStream(context.Background(), h2.ID(), protoMinor, protoNew, protoOld)\n\trequire.NoError(t, err)\n\n\t// force the lazy negotiation to complete\n\t_, err = s.Write(nil)\n\trequire.NoError(t, err)\n\n\tassertWait(t, connectedOn, protoOld)\n\ts.Close()\n\n\th2.SetStreamHandlerMatch(protoMinor, func(protocol.ID) bool { return true }, handler)\n\t// remembered preference will be chosen first, even when the other side newly supports it\n\ts2, err := h1.NewStream(context.Background(), h2.ID(), protoMinor, protoNew, protoOld)\n\trequire.NoError(t, err)\n\n\t// required to force 'lazy' handshake\n\t_, err = s2.Write([]byte(\"hello\"))\n\trequire.NoError(t, err)\n\n\tassertWait(t, connectedOn, protoOld)\n\ts2.Close()\n\n\ts3, err := h1.NewStream(context.Background(), h2.ID(), protoMinor)\n\trequire.NoError(t, err)\n\n\t// Force a lazy handshake as we may have received a protocol update by this point.\n\t_, err = s3.Write([]byte(\"hello\"))\n\trequire.NoError(t, err)\n\n\tassertWait(t, connectedOn, protoMinor)\n\ts3.Close()\n}\n\nfunc TestHostProtoMismatch(t *testing.T) {\n\tctx := t.Context()\n\n\th1, h2 := getHostPair(t)\n\tdefer h1.Close()\n\tdefer h2.Close()\n\n\th1.SetStreamHandler(\"/super\", func(s network.Stream) {\n\t\tt.Error(\"shouldnt get here\")\n\t\ts.Reset()\n\t})\n\n\t_, err := h2.NewStream(ctx, h1.ID(), \"/foo\", \"/bar\", \"/baz/1.0.0\")\n\tif err == nil {\n\t\tt.Fatal(\"expected new stream to fail\")\n\t}\n}\n\nfunc TestHostProtoPreknowledge(t *testing.T) {\n\th1, err := NewHost(swarmt.GenSwarm(t, swarmt.OptDialOnly), nil)\n\trequire.NoError(t, err)\n\tdefer h1.Close()\n\n\th2, err := NewHost(swarmt.GenSwarm(t, swarmt.OptDisableTCP), nil)\n\trequire.NoError(t, err)\n\tdefer h2.Close()\n\n\tconn := make(chan protocol.ID)\n\thandler := func(s network.Stream) {\n\t\tconn <- s.Protocol()\n\t\ts.Close()\n\t}\n\n\th2.SetStreamHandler(\"/super\", handler)\n\n\th1.Start()\n\th2.Start()\n\n\t// Prevent pushing identify information so this test actually _uses_ the super protocol.\n\th1.RemoveStreamHandler(identify.IDPush)\n\n\th2pi := h2.Peerstore().PeerInfo(h2.ID())\n\t// Filter to only 1 address so that we don't have to think about parallel\n\t// connections in this test\n\th2pi.Addrs = h2pi.Addrs[:1]\n\trequire.NoError(t, h1.Connect(context.Background(), h2pi))\n\n\t// This test implicitly relies on 1 connection. If a background identify\n\t// completes after we set the stream handler below things break\n\trequire.Len(t, h1.Network().ConnsToPeer(h2.ID()), 1)\n\n\t// wait for identify handshake to finish completely\n\tselect {\n\tcase <-h1.ids.IdentifyWait(h1.Network().ConnsToPeer(h2.ID())[0]):\n\tcase <-time.After(time.Second * 5):\n\t\tt.Fatal(\"timed out waiting for identify\")\n\t}\n\n\tselect {\n\tcase <-h2.ids.IdentifyWait(h2.Network().ConnsToPeer(h1.ID())[0]):\n\tcase <-time.After(time.Second * 5):\n\t\tt.Fatal(\"timed out waiting for identify\")\n\t}\n\n\th2.SetStreamHandler(\"/foo\", handler)\n\n\trequire.Never(t, func() bool {\n\t\tprotos, err := h1.Peerstore().GetProtocols(h2.ID())\n\t\trequire.NoError(t, err)\n\t\treturn slices.Contains(protos, \"/foo\")\n\t}, time.Second, 100*time.Millisecond)\n\n\ts, err := h1.NewStream(context.Background(), h2.ID(), \"/foo\", \"/bar\", \"/super\")\n\trequire.NoError(t, err)\n\n\tselect {\n\tcase p := <-conn:\n\t\tt.Fatal(\"shouldn't have gotten connection yet, we should have a lazy stream: \", p)\n\tcase <-time.After(time.Millisecond * 50):\n\t}\n\n\t_, err = s.Read(nil)\n\trequire.NoError(t, err)\n\tassertWait(t, conn, \"/super\")\n\n\ts.Close()\n}\n\nfunc TestNewDialOld(t *testing.T) {\n\tctx := t.Context()\n\n\th1, h2 := getHostPair(t)\n\tdefer h1.Close()\n\tdefer h2.Close()\n\n\tconnectedOn := make(chan protocol.ID)\n\th2.SetStreamHandler(\"/testing\", func(s network.Stream) {\n\t\tconnectedOn <- s.Protocol()\n\t\ts.Close()\n\t})\n\n\ts, err := h1.NewStream(ctx, h2.ID(), \"/testing/1.0.0\", \"/testing\")\n\trequire.NoError(t, err)\n\n\t// force the lazy negotiation to complete\n\t_, err = s.Write(nil)\n\trequire.NoError(t, err)\n\tassertWait(t, connectedOn, \"/testing\")\n\n\trequire.Equal(t, s.Protocol(), protocol.ID(\"/testing\"), \"should have gotten /testing\")\n}\n\nfunc TestNewStreamResolve(t *testing.T) {\n\th1, err := NewHost(swarmt.GenSwarm(t), nil)\n\trequire.NoError(t, err)\n\th1.Start()\n\th2, err := NewHost(swarmt.GenSwarm(t), nil)\n\trequire.NoError(t, err)\n\th2.Start()\n\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second)\n\tdefer cancel()\n\n\t// Get the tcp port that h2 is listening on.\n\th2pi := h2.Peerstore().PeerInfo(h2.ID())\n\tvar dialAddr string\n\tconst tcpPrefix = \"/ip4/127.0.0.1/tcp/\"\n\tfor _, addr := range h2pi.Addrs {\n\t\taddrStr := addr.String()\n\t\tif strings.HasPrefix(addrStr, tcpPrefix) {\n\t\t\tport := addrStr[len(tcpPrefix):]\n\t\t\tdialAddr = \"/dns4/localhost/tcp/\" + port\n\t\t\tbreak\n\t\t}\n\t}\n\tassert.NotEqual(t, \"\", dialAddr)\n\n\t// Add the DNS multiaddr to h1's peerstore.\n\tmaddr, err := ma.NewMultiaddr(dialAddr)\n\trequire.NoError(t, err)\n\th1.Peerstore().AddAddr(h2.ID(), maddr, time.Second)\n\n\tconnectedOn := make(chan protocol.ID)\n\th2.SetStreamHandler(\"/testing\", func(s network.Stream) {\n\t\tconnectedOn <- s.Protocol()\n\t\ts.Close()\n\t})\n\n\t// NewStream will make a new connection using the DNS address in h1's\n\t// peerstore.\n\ts, err := h1.NewStream(ctx, h2.ID(), \"/testing/1.0.0\", \"/testing\")\n\trequire.NoError(t, err)\n\n\t// force the lazy negotiation to complete\n\t_, err = s.Write(nil)\n\trequire.NoError(t, err)\n\tassertWait(t, connectedOn, \"/testing\")\n}\n\nfunc TestProtoDowngrade(t *testing.T) {\n\tctx := t.Context()\n\n\th1, h2 := getHostPair(t)\n\tdefer h1.Close()\n\tdefer h2.Close()\n\n\tconnectedOn := make(chan protocol.ID)\n\th2.SetStreamHandler(\"/testing/1.0.0\", func(s network.Stream) {\n\t\tdefer s.Close()\n\t\tresult, err := io.ReadAll(s)\n\t\tassert.NoError(t, err)\n\t\tassert.Equal(t, \"bar\", string(result))\n\t\tconnectedOn <- s.Protocol()\n\t})\n\n\ts, err := h1.NewStream(ctx, h2.ID(), \"/testing/1.0.0\", \"/testing\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, s.Protocol(), protocol.ID(\"/testing/1.0.0\"), \"should have gotten /testing/1.0.0, got %s\", s.Protocol())\n\n\t_, err = s.Write([]byte(\"bar\"))\n\trequire.NoError(t, err)\n\trequire.NoError(t, s.CloseWrite())\n\n\tassertWait(t, connectedOn, \"/testing/1.0.0\")\n\trequire.NoError(t, s.Close())\n\n\th1.Network().ClosePeer(h2.ID())\n\th2.RemoveStreamHandler(\"/testing/1.0.0\")\n\th2.SetStreamHandler(\"/testing\", func(s network.Stream) {\n\t\tdefer s.Close()\n\t\tresult, err := io.ReadAll(s)\n\t\tassert.NoError(t, err)\n\t\tassert.Equal(t, \"foo\", string(result))\n\t\tconnectedOn <- s.Protocol()\n\t})\n\n\t// Give us a second to update our protocol list. This happens async through the event bus.\n\t// This is _almost_ instantaneous, but this test fails once every ~1k runs without this.\n\ttime.Sleep(time.Millisecond)\n\n\th2pi := h2.Peerstore().PeerInfo(h2.ID())\n\trequire.NoError(t, h1.Connect(ctx, h2pi))\n\n\ts2, err := h1.NewStream(ctx, h2.ID(), \"/testing/1.0.0\", \"/testing\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, s2.Protocol(), protocol.ID(\"/testing\"), \"should have gotten /testing, got %s, %s\", s.Protocol(), s.Conn())\n\n\t_, err = s2.Write([]byte(\"foo\"))\n\trequire.NoError(t, err)\n\trequire.NoError(t, s2.CloseWrite())\n\n\tassertWait(t, connectedOn, \"/testing\")\n}\n\nfunc TestAddrChangeImmediatelyIfAddressNonEmpty(t *testing.T) {\n\tctx := context.Background()\n\ttaddrs := []ma.Multiaddr{ma.StringCast(\"/ip4/1.2.3.4/tcp/1234\")}\n\n\th, err := NewHost(swarmt.GenSwarm(t), &HostOpts{AddrsFactory: func(_ []ma.Multiaddr) []ma.Multiaddr {\n\t\treturn taddrs\n\t}})\n\trequire.NoError(t, err)\n\tdefer h.Close()\n\n\tsub, err := h.EventBus().Subscribe(&event.EvtLocalAddressesUpdated{})\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tdefer sub.Close()\n\th.Start()\n\n\texpected := event.EvtLocalAddressesUpdated{\n\t\tDiffs: true,\n\t\tCurrent: []event.UpdatedAddress{\n\t\t\t{Action: event.Added, Address: ma.StringCast(\"/ip4/1.2.3.4/tcp/1234\")},\n\t\t},\n\t\tRemoved: []event.UpdatedAddress{}}\n\n\t// assert we get expected event\n\tevt := waitForAddrChangeEvent(ctx, sub, t)\n\tif !updatedAddrEventsEqual(expected, evt) {\n\t\tt.Errorf(\"change events not equal: \\n\\texpected: %v \\n\\tactual: %v\", expected, evt)\n\t}\n\n\t// assert it's on the signed record\n\trc := peerRecordFromEnvelope(t, evt.SignedPeerRecord)\n\tmatest.AssertEqualMultiaddrs(t, taddrs, rc.Addrs)\n\n\t// assert it's in the peerstore\n\tev := h.Peerstore().(peerstore.CertifiedAddrBook).GetPeerRecord(h.ID())\n\trequire.NotNil(t, ev)\n\trc = peerRecordFromEnvelope(t, ev)\n\tmatest.AssertEqualMultiaddrs(t, taddrs, rc.Addrs)\n}\n\nfunc TestStatefulAddrEvents(t *testing.T) {\n\th, err := NewHost(swarmt.GenSwarm(t), nil)\n\trequire.NoError(t, err)\n\th.Start()\n\tdefer h.Close()\n\n\tsub, err := h.EventBus().Subscribe(&event.EvtLocalAddressesUpdated{}, eventbus.BufSize(10))\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tdefer sub.Close()\n\n\tselect {\n\tcase v := <-sub.Out():\n\t\tassert.NotNil(t, v)\n\tcase <-time.After(time.Second * 5):\n\t\tt.Error(\"timed out waiting for event\")\n\t}\n}\n\nfunc TestHostAddrChangeDetection(t *testing.T) {\n\t// This test uses the address factory to provide several\n\t// sets of listen addresses for the host. It advances through\n\t// the sets by changing the currentAddrSet index var below.\n\taddrSets := [][]ma.Multiaddr{\n\t\t{},\n\t\t{ma.StringCast(\"/ip4/1.2.3.4/tcp/1234\")},\n\t\t{ma.StringCast(\"/ip4/1.2.3.4/tcp/1234\"), ma.StringCast(\"/ip4/2.3.4.5/tcp/1234\")},\n\t\t{ma.StringCast(\"/ip4/2.3.4.5/tcp/1234\"), ma.StringCast(\"/ip4/3.4.5.6/tcp/4321\")},\n\t}\n\n\t// The events we expect the host to emit when SignalAddressChange is called\n\t// and the changes between addr sets are detected\n\texpectedEvents := []event.EvtLocalAddressesUpdated{\n\t\t{\n\t\t\tDiffs: true,\n\t\t\tCurrent: []event.UpdatedAddress{\n\t\t\t\t{Action: event.Added, Address: ma.StringCast(\"/ip4/1.2.3.4/tcp/1234\")},\n\t\t\t},\n\t\t\tRemoved: []event.UpdatedAddress{},\n\t\t},\n\t\t{\n\t\t\tDiffs: true,\n\t\t\tCurrent: []event.UpdatedAddress{\n\t\t\t\t{Action: event.Maintained, Address: ma.StringCast(\"/ip4/1.2.3.4/tcp/1234\")},\n\t\t\t\t{Action: event.Added, Address: ma.StringCast(\"/ip4/2.3.4.5/tcp/1234\")},\n\t\t\t},\n\t\t\tRemoved: []event.UpdatedAddress{},\n\t\t},\n\t\t{\n\t\t\tDiffs: true,\n\t\t\tCurrent: []event.UpdatedAddress{\n\t\t\t\t{Action: event.Added, Address: ma.StringCast(\"/ip4/3.4.5.6/tcp/4321\")},\n\t\t\t\t{Action: event.Maintained, Address: ma.StringCast(\"/ip4/2.3.4.5/tcp/1234\")},\n\t\t\t},\n\t\t\tRemoved: []event.UpdatedAddress{\n\t\t\t\t{Action: event.Removed, Address: ma.StringCast(\"/ip4/1.2.3.4/tcp/1234\")},\n\t\t\t},\n\t\t},\n\t}\n\n\tvar lk sync.Mutex\n\tcurrentAddrSet := 0\n\taddrsFactory := func(_ []ma.Multiaddr) []ma.Multiaddr {\n\t\tlk.Lock()\n\t\tdefer lk.Unlock()\n\t\treturn addrSets[currentAddrSet]\n\t}\n\n\tctx := context.Background()\n\th, err := NewHost(swarmt.GenSwarm(t), &HostOpts{AddrsFactory: addrsFactory})\n\trequire.NoError(t, err)\n\th.Start()\n\tdefer h.Close()\n\n\tsub, err := h.EventBus().Subscribe(&event.EvtLocalAddressesUpdated{}, eventbus.BufSize(10))\n\trequire.NoError(t, err)\n\tdefer sub.Close()\n\n\t// wait for the host background thread to start\n\ttime.Sleep(1 * time.Second)\n\t// host should start with no addrs (addrSet 0)\n\taddrs := h.Addrs()\n\tif len(addrs) != 0 {\n\t\tt.Fatalf(\"expected 0 addrs, got %d\", len(addrs))\n\t}\n\n\t// change addr, signal and assert event\n\tfor i := 1; i < len(addrSets); i++ {\n\t\tlk.Lock()\n\t\tcurrentAddrSet = i\n\t\tlk.Unlock()\n\t\th.addressManager.updateAddrsSync()\n\t\tevt := waitForAddrChangeEvent(ctx, sub, t)\n\t\tif !updatedAddrEventsEqual(expectedEvents[i-1], evt) {\n\t\t\tt.Errorf(\"change events not equal: \\n\\texpected: %v \\n\\tactual: %v\", expectedEvents[i-1], evt)\n\t\t}\n\n\t\t// assert it's on the signed record\n\t\trc := peerRecordFromEnvelope(t, evt.SignedPeerRecord)\n\t\tmatest.AssertMultiaddrsMatch(t, addrSets[i], rc.Addrs)\n\n\t\t// assert it's in the peerstore\n\t\tev := h.Peerstore().(peerstore.CertifiedAddrBook).GetPeerRecord(h.ID())\n\t\trequire.NotNil(t, ev)\n\t\trc = peerRecordFromEnvelope(t, ev)\n\t\tmatest.AssertMultiaddrsMatch(t, addrSets[i], rc.Addrs)\n\t}\n}\n\nfunc TestNegotiationCancel(t *testing.T) {\n\tctx := t.Context()\n\n\th1, h2 := getHostPair(t)\n\tdefer h1.Close()\n\tdefer h2.Close()\n\n\t// pre-negotiation so we can make the negotiation hang.\n\th2.Network().SetStreamHandler(func(s network.Stream) {\n\t\t<-ctx.Done() // wait till the test is done.\n\t\ts.Reset()\n\t})\n\n\tctx2, cancel2 := context.WithCancel(ctx)\n\tdefer cancel2()\n\n\terrCh := make(chan error, 1)\n\tgo func() {\n\t\ts, err := h1.NewStream(ctx2, h2.ID(), \"/testing\")\n\t\tif s != nil {\n\t\t\terrCh <- fmt.Errorf(\"expected to fail negotiation\")\n\t\t\treturn\n\t\t}\n\t\terrCh <- err\n\t}()\n\tselect {\n\tcase err := <-errCh:\n\t\tt.Fatal(err)\n\tcase <-time.After(10 * time.Millisecond):\n\t\t// ok, hung.\n\t}\n\tcancel2()\n\n\tselect {\n\tcase err := <-errCh:\n\t\trequire.ErrorIs(t, err, context.Canceled)\n\tcase <-time.After(500 * time.Millisecond):\n\t\t// failed to cancel\n\t\tt.Fatal(\"expected negotiation to be canceled\")\n\t}\n}\n\nfunc waitForAddrChangeEvent(ctx context.Context, sub event.Subscription, t *testing.T) event.EvtLocalAddressesUpdated {\n\tt.Helper()\n\tfor {\n\t\tselect {\n\t\tcase evt, more := <-sub.Out():\n\t\t\tif !more {\n\t\t\t\tt.Fatal(\"channel should not be closed\")\n\t\t\t}\n\t\t\treturn evt.(event.EvtLocalAddressesUpdated)\n\t\tcase <-ctx.Done():\n\t\t\tt.Fatal(\"context should not have cancelled\")\n\t\tcase <-time.After(5 * time.Second):\n\t\t\tt.Fatal(\"timed out waiting for address change event\")\n\t\t}\n\t}\n}\n\n// updatedAddrsEqual is a helper to check whether two lists of\n// event.UpdatedAddress have the same contents, ignoring ordering.\nfunc updatedAddrsEqual(a, b []event.UpdatedAddress) bool {\n\tif len(a) != len(b) {\n\t\treturn false\n\t}\n\n\t// We can't use an UpdatedAddress directly as a map key, since\n\t// Multiaddr is an interface, and go won't know how to compare\n\t// for equality. So we convert to this little struct, which\n\t// stores the multiaddr as a string.\n\ttype ua struct {\n\t\taction  event.AddrAction\n\t\taddrStr string\n\t}\n\taSet := make(map[ua]struct{})\n\tfor _, addr := range a {\n\t\tk := ua{action: addr.Action, addrStr: string(addr.Address.Bytes())}\n\t\taSet[k] = struct{}{}\n\t}\n\tfor _, addr := range b {\n\t\tk := ua{action: addr.Action, addrStr: string(addr.Address.Bytes())}\n\t\t_, ok := aSet[k]\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n// updatedAddrEventsEqual is a helper to check whether two\n// event.EvtLocalAddressesUpdated are equal, ignoring the ordering of\n// addresses in the inner lists.\nfunc updatedAddrEventsEqual(a, b event.EvtLocalAddressesUpdated) bool {\n\treturn a.Diffs == b.Diffs &&\n\t\tupdatedAddrsEqual(a.Current, b.Current) &&\n\t\tupdatedAddrsEqual(a.Removed, b.Removed)\n}\n\nfunc peerRecordFromEnvelope(t *testing.T, ev *record.Envelope) *peer.PeerRecord {\n\tt.Helper()\n\trec, err := ev.Record()\n\tif err != nil {\n\t\tt.Fatalf(\"error getting PeerRecord from event: %v\", err)\n\t\treturn nil\n\t}\n\tpeerRec, ok := rec.(*peer.PeerRecord)\n\tif !ok {\n\t\tt.Fatalf(\"wrong type for peer record\")\n\t\treturn nil\n\t}\n\treturn peerRec\n}\n\nfunc TestTrimHostAddrList(t *testing.T) {\n\ttype testCase struct {\n\t\tname      string\n\t\tin        []ma.Multiaddr\n\t\tthreshold int\n\t\tout       []ma.Multiaddr\n\t}\n\n\ttcpPublic := ma.StringCast(\"/ip4/1.1.1.1/tcp/1\")\n\tquicPublic := ma.StringCast(\"/ip4/1.1.1.1/udp/1/quic-v1\")\n\n\ttcpPrivate := ma.StringCast(\"/ip4/192.168.1.1/tcp/1\")\n\tquicPrivate := ma.StringCast(\"/ip4/192.168.1.1/udp/1/quic-v1\")\n\n\ttcpLocal := ma.StringCast(\"/ip4/127.0.0.1/tcp/1\")\n\tquicLocal := ma.StringCast(\"/ip4/127.0.0.1/udp/1/quic-v1\")\n\n\ttestCases := []testCase{\n\t\t{\n\t\t\tname:      \"Public preferred over private\",\n\t\t\tin:        []ma.Multiaddr{tcpPublic, quicPrivate},\n\t\t\tthreshold: len(tcpLocal.Bytes()),\n\t\t\tout:       []ma.Multiaddr{tcpPublic},\n\t\t},\n\t\t{\n\t\t\tname:      \"Public and private preffered over local\",\n\t\t\tin:        []ma.Multiaddr{tcpPublic, tcpPrivate, quicLocal},\n\t\t\tthreshold: len(tcpPublic.Bytes()) + len(tcpPrivate.Bytes()),\n\t\t\tout:       []ma.Multiaddr{tcpPublic, tcpPrivate},\n\t\t},\n\t\t{\n\t\t\tname:      \"quic preferred over tcp\",\n\t\t\tin:        []ma.Multiaddr{tcpPublic, quicPublic},\n\t\t\tthreshold: len(quicPublic.Bytes()),\n\t\t\tout:       []ma.Multiaddr{quicPublic},\n\t\t},\n\t\t{\n\t\t\tname:      \"no filtering on large threshold\",\n\t\t\tin:        []ma.Multiaddr{tcpPublic, quicPublic, quicLocal, tcpLocal, tcpPrivate},\n\t\t\tthreshold: 10000,\n\t\t\tout:       []ma.Multiaddr{tcpPublic, quicPublic, quicLocal, tcpLocal, tcpPrivate},\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := trimHostAddrList(tc.in, tc.threshold)\n\t\t\trequire.ElementsMatch(t, got, tc.out)\n\t\t})\n\t}\n}\n\nfunc TestHostTimeoutNewStream(t *testing.T) {\n\th1, err := NewHost(swarmt.GenSwarm(t), nil)\n\trequire.NoError(t, err)\n\th1.Start()\n\tdefer h1.Close()\n\n\tconst proto = \"/testing\"\n\th2 := swarmt.GenSwarm(t)\n\n\th2.SetStreamHandler(func(s network.Stream) {\n\t\t// First message is multistream header. Just echo it\n\t\tmsHeader := []byte(\"\\x19/multistream/1.0.0\\n\")\n\t\t_, err := s.Read(msHeader)\n\t\tassert.NoError(t, err)\n\t\t_, err = s.Write(msHeader)\n\t\tassert.NoError(t, err)\n\n\t\tbuf := make([]byte, 1024)\n\t\tn, err := s.Read(buf)\n\t\tassert.NoError(t, err)\n\n\t\tmsgLen, varintN := binary.Uvarint(buf[:n])\n\t\tbuf = buf[varintN:]\n\t\tproto := buf[:int(msgLen)]\n\t\tif string(proto) == \"/ipfs/id/1.0.0\\n\" {\n\t\t\t// Signal we don't support identify\n\t\t\tna := []byte(\"na\\n\")\n\t\t\tn := binary.PutUvarint(buf, uint64(len(na)))\n\t\t\tcopy(buf[n:], na)\n\n\t\t\t_, err = s.Write(buf[:int(n)+len(na)])\n\t\t\tassert.NoError(t, err)\n\t\t} else {\n\t\t\t// Stall\n\t\t\ttime.Sleep(5 * time.Second)\n\t\t}\n\t\tt.Log(\"Resetting\")\n\t\ts.Reset()\n\t})\n\n\terr = h1.Connect(context.Background(), peer.AddrInfo{\n\t\tID:    h2.LocalPeer(),\n\t\tAddrs: h2.ListenAddresses(),\n\t})\n\trequire.NoError(t, err)\n\n\t// No context passed in, fallback to negtimeout\n\th1.negtimeout = time.Second\n\t_, err = h1.NewStream(context.Background(), h2.LocalPeer(), proto)\n\trequire.Error(t, err)\n\trequire.ErrorContains(t, err, \"context deadline exceeded\")\n}\n"
  },
  {
    "path": "p2p/host/basic/mock_nat_test.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github.com/libp2p/go-libp2p/p2p/host/basic (interfaces: NAT)\n//\n// Generated by this command:\n//\n//\tmockgen -build_flags=-tags=gomock -package basichost -destination mock_nat_test.go github.com/libp2p/go-libp2p/p2p/host/basic NAT\n//\n\n// Package basichost is a generated GoMock package.\npackage basichost\n\nimport (\n\tcontext \"context\"\n\tnetip \"net/netip\"\n\treflect \"reflect\"\n\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockNAT is a mock of NAT interface.\ntype MockNAT struct {\n\tctrl     *gomock.Controller\n\trecorder *MockNATMockRecorder\n\tisgomock struct{}\n}\n\n// MockNATMockRecorder is the mock recorder for MockNAT.\ntype MockNATMockRecorder struct {\n\tmock *MockNAT\n}\n\n// NewMockNAT creates a new mock instance.\nfunc NewMockNAT(ctrl *gomock.Controller) *MockNAT {\n\tmock := &MockNAT{ctrl: ctrl}\n\tmock.recorder = &MockNATMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockNAT) EXPECT() *MockNATMockRecorder {\n\treturn m.recorder\n}\n\n// AddMapping mocks base method.\nfunc (m *MockNAT) AddMapping(ctx context.Context, protocol string, port int) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"AddMapping\", ctx, protocol, port)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// AddMapping indicates an expected call of AddMapping.\nfunc (mr *MockNATMockRecorder) AddMapping(ctx, protocol, port any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"AddMapping\", reflect.TypeOf((*MockNAT)(nil).AddMapping), ctx, protocol, port)\n}\n\n// Close mocks base method.\nfunc (m *MockNAT) Close() error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Close\")\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Close indicates an expected call of Close.\nfunc (mr *MockNATMockRecorder) Close() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Close\", reflect.TypeOf((*MockNAT)(nil).Close))\n}\n\n// GetMapping mocks base method.\nfunc (m *MockNAT) GetMapping(protocol string, port int) (netip.AddrPort, bool) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetMapping\", protocol, port)\n\tret0, _ := ret[0].(netip.AddrPort)\n\tret1, _ := ret[1].(bool)\n\treturn ret0, ret1\n}\n\n// GetMapping indicates an expected call of GetMapping.\nfunc (mr *MockNATMockRecorder) GetMapping(protocol, port any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetMapping\", reflect.TypeOf((*MockNAT)(nil).GetMapping), protocol, port)\n}\n\n// RemoveMapping mocks base method.\nfunc (m *MockNAT) RemoveMapping(ctx context.Context, protocol string, port int) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RemoveMapping\", ctx, protocol, port)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// RemoveMapping indicates an expected call of RemoveMapping.\nfunc (mr *MockNATMockRecorder) RemoveMapping(ctx, protocol, port any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RemoveMapping\", reflect.TypeOf((*MockNAT)(nil).RemoveMapping), ctx, protocol, port)\n}\n"
  },
  {
    "path": "p2p/host/basic/mocks.go",
    "content": "//go:build gomock || generate\n\npackage basichost\n\n//go:generate sh -c \"go run go.uber.org/mock/mockgen -build_flags=\\\"-tags=gomock\\\" -package basichost -destination mock_nat_test.go github.com/libp2p/go-libp2p/p2p/host/basic NAT\"\ntype NAT nat\n"
  },
  {
    "path": "p2p/host/basic/natmgr.go",
    "content": "package basichost\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net\"\n\t\"net/netip\"\n\t\"strconv\"\n\t\"sync\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\tinat \"github.com/libp2p/go-libp2p/p2p/net/nat\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\n// NATManager is a simple interface to manage NAT devices.\n// It listens Listen and ListenClose notifications from the network.Network,\n// and tries to obtain port mappings for those.\ntype NATManager interface {\n\tGetMapping(ma.Multiaddr) ma.Multiaddr\n\tHasDiscoveredNAT() bool\n\tio.Closer\n}\n\n// NewNATManager creates a NAT manager.\nfunc NewNATManager(net network.Network) NATManager {\n\treturn newNATManager(net)\n}\n\ntype entry struct {\n\tprotocol string\n\tport     int\n}\n\ntype nat interface {\n\tAddMapping(ctx context.Context, protocol string, port int) error\n\tRemoveMapping(ctx context.Context, protocol string, port int) error\n\tGetMapping(protocol string, port int) (netip.AddrPort, bool)\n\tio.Closer\n}\n\n// so we can mock it in tests\nvar discoverNAT = func(ctx context.Context) (nat, error) { return inat.DiscoverNAT(ctx) }\n\n// natManager takes care of adding + removing port mappings to the nat.\n// Initialized with the host if it has a NATPortMap option enabled.\n// natManager receives signals from the network, and check on nat mappings:\n//   - natManager listens to the network and adds or closes port mappings\n//     as the network signals Listen() or ListenClose().\n//   - closing the natManager closes the nat and its mappings.\ntype natManager struct {\n\tnet   network.Network\n\tnatMx sync.RWMutex\n\tnat   nat\n\n\tsyncFlag chan struct{} // cap: 1\n\n\ttracked map[entry]bool // the bool is only used in doSync and has no meaning outside of that function\n\n\trefCount  sync.WaitGroup\n\tctx       context.Context\n\tctxCancel context.CancelFunc\n}\n\nfunc newNATManager(net network.Network) *natManager {\n\tctx, cancel := context.WithCancel(context.Background())\n\tnmgr := &natManager{\n\t\tnet:       net,\n\t\tsyncFlag:  make(chan struct{}, 1),\n\t\tctx:       ctx,\n\t\tctxCancel: cancel,\n\t\ttracked:   make(map[entry]bool),\n\t}\n\tnmgr.refCount.Add(1)\n\tgo nmgr.background(ctx)\n\treturn nmgr\n}\n\n// Close closes the natManager, closing the underlying nat\n// and unregistering from network events.\nfunc (nmgr *natManager) Close() error {\n\tnmgr.ctxCancel()\n\tnmgr.refCount.Wait()\n\treturn nil\n}\n\nfunc (nmgr *natManager) HasDiscoveredNAT() bool {\n\tnmgr.natMx.RLock()\n\tdefer nmgr.natMx.RUnlock()\n\treturn nmgr.nat != nil\n}\n\nfunc (nmgr *natManager) background(ctx context.Context) {\n\tdefer nmgr.refCount.Done()\n\n\tdefer func() {\n\t\tnmgr.natMx.Lock()\n\t\tdefer nmgr.natMx.Unlock()\n\n\t\tif nmgr.nat != nil {\n\t\t\tnmgr.nat.Close()\n\t\t}\n\t}()\n\n\tdiscoverCtx, cancel := context.WithTimeout(ctx, inat.DiscoveryTimeout)\n\tdefer cancel()\n\tnatInstance, err := discoverNAT(discoverCtx)\n\tif err != nil {\n\t\tlog.Info(\"DiscoverNAT error:\", \"err\", err)\n\t\treturn\n\t}\n\n\tnmgr.natMx.Lock()\n\tnmgr.nat = natInstance\n\tnmgr.natMx.Unlock()\n\n\t// sign natManager up for network notifications\n\t// we need to sign up here to avoid missing some notifs\n\t// before the NAT has been found.\n\tnmgr.net.Notify((*nmgrNetNotifiee)(nmgr))\n\tdefer nmgr.net.StopNotify((*nmgrNetNotifiee)(nmgr))\n\n\tnmgr.doSync() // sync one first.\n\tfor {\n\t\tselect {\n\t\tcase <-nmgr.syncFlag:\n\t\t\tnmgr.doSync() // sync when our listen addresses change.\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (nmgr *natManager) sync() {\n\tselect {\n\tcase nmgr.syncFlag <- struct{}{}:\n\tdefault:\n\t}\n}\n\n// doSync syncs the current NAT mappings, removing any outdated mappings and adding any\n// new mappings.\nfunc (nmgr *natManager) doSync() {\n\tfor e := range nmgr.tracked {\n\t\tnmgr.tracked[e] = false\n\t}\n\tvar newAddresses []entry\n\tfor _, maddr := range nmgr.net.ListenAddresses() {\n\t\t// Strip the IP\n\t\tmaIP, rest := ma.SplitFirst(maddr)\n\t\tif maIP == nil || len(rest) == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tswitch maIP.Protocol().Code {\n\t\tcase ma.P_IP6, ma.P_IP4:\n\t\tdefault:\n\t\t\tcontinue\n\t\t}\n\n\t\t// Only bother if we're listening on an unicast / unspecified IP.\n\t\tip := net.IP(maIP.RawValue())\n\t\tif !ip.IsGlobalUnicast() && !ip.IsUnspecified() {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Extract the port/protocol\n\t\tproto, _ := ma.SplitFirst(rest)\n\t\tif proto == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tvar protocol string\n\t\tswitch proto.Protocol().Code {\n\t\tcase ma.P_TCP:\n\t\t\tprotocol = \"tcp\"\n\t\tcase ma.P_UDP:\n\t\t\tprotocol = \"udp\"\n\t\tdefault:\n\t\t\tcontinue\n\t\t}\n\t\tport, err := strconv.ParseUint(proto.Value(), 10, 16)\n\t\tif err != nil {\n\t\t\t// bug in multiaddr\n\t\t\tpanic(err)\n\t\t}\n\t\te := entry{protocol: protocol, port: int(port)}\n\t\tif _, ok := nmgr.tracked[e]; ok {\n\t\t\tnmgr.tracked[e] = true\n\t\t} else {\n\t\t\tnewAddresses = append(newAddresses, e)\n\t\t}\n\t}\n\n\tvar wg sync.WaitGroup\n\tdefer wg.Wait()\n\n\t// Close old mappings\n\tfor e, v := range nmgr.tracked {\n\t\tif !v {\n\t\t\tnmgr.nat.RemoveMapping(nmgr.ctx, e.protocol, e.port)\n\t\t\tdelete(nmgr.tracked, e)\n\t\t}\n\t}\n\n\t// Create new mappings.\n\tfor _, e := range newAddresses {\n\t\tif err := nmgr.nat.AddMapping(nmgr.ctx, e.protocol, e.port); err != nil {\n\t\t\tlog.Error(\"failed to port-map\", \"protocol\", e.protocol, \"port\", e.port, \"err\", err)\n\t\t}\n\t\tnmgr.tracked[e] = false\n\t}\n}\n\nfunc (nmgr *natManager) GetMapping(addr ma.Multiaddr) ma.Multiaddr {\n\tnmgr.natMx.Lock()\n\tdefer nmgr.natMx.Unlock()\n\n\tif nmgr.nat == nil { // NAT not yet initialized\n\t\treturn nil\n\t}\n\n\tvar found bool\n\tvar proto int // ma.P_TCP or ma.P_UDP\n\ttransport, rest := ma.SplitFunc(addr, func(c ma.Component) bool {\n\t\tif found {\n\t\t\treturn true\n\t\t}\n\t\tproto = c.Protocol().Code\n\t\tfound = proto == ma.P_TCP || proto == ma.P_UDP\n\t\treturn false\n\t})\n\tif !manet.IsThinWaist(transport) {\n\t\treturn nil\n\t}\n\n\tnaddr, err := manet.ToNetAddr(transport)\n\tif err != nil {\n\t\tlog.Error(\"error parsing net multiaddr\", \"addr\", transport, \"err\", err)\n\t\treturn nil\n\t}\n\n\tvar (\n\t\tip       net.IP\n\t\tport     int\n\t\tprotocol string\n\t)\n\tswitch naddr := naddr.(type) {\n\tcase *net.TCPAddr:\n\t\tip = naddr.IP\n\t\tport = naddr.Port\n\t\tprotocol = \"tcp\"\n\tcase *net.UDPAddr:\n\t\tip = naddr.IP\n\t\tport = naddr.Port\n\t\tprotocol = \"udp\"\n\tdefault:\n\t\treturn nil\n\t}\n\n\tif !ip.IsGlobalUnicast() && !ip.IsUnspecified() {\n\t\t// We only map global unicast & unspecified addresses ports, not broadcast, multicast, etc.\n\t\treturn nil\n\t}\n\n\textAddr, ok := nmgr.nat.GetMapping(protocol, port)\n\tif !ok {\n\t\treturn nil\n\t}\n\n\tvar mappedAddr net.Addr\n\tswitch naddr.(type) {\n\tcase *net.TCPAddr:\n\t\tmappedAddr = net.TCPAddrFromAddrPort(extAddr)\n\tcase *net.UDPAddr:\n\t\tmappedAddr = net.UDPAddrFromAddrPort(extAddr)\n\t}\n\tmappedMaddr, err := manet.FromNetAddr(mappedAddr)\n\tif err != nil {\n\t\tlog.Error(\"mapped addr can't be turned into a multiaddr\", \"addr\", mappedAddr, \"err\", err)\n\t\treturn nil\n\t}\n\textMaddr := mappedMaddr\n\tif rest != nil {\n\t\textMaddr = ma.Join(extMaddr, rest)\n\t}\n\treturn extMaddr\n}\n\ntype nmgrNetNotifiee natManager\n\nfunc (nn *nmgrNetNotifiee) natManager() *natManager                       { return (*natManager)(nn) }\nfunc (nn *nmgrNetNotifiee) Listen(network.Network, ma.Multiaddr)          { nn.natManager().sync() }\nfunc (nn *nmgrNetNotifiee) ListenClose(_ network.Network, _ ma.Multiaddr) { nn.natManager().sync() }\nfunc (nn *nmgrNetNotifiee) Connected(network.Network, network.Conn)       {}\nfunc (nn *nmgrNetNotifiee) Disconnected(network.Network, network.Conn)    {}\n"
  },
  {
    "path": "p2p/host/basic/natmgr_test.go",
    "content": "package basichost\n\nimport (\n\t\"context\"\n\t\"net/netip\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\n\tswarmt \"github.com/libp2p/go-libp2p/p2p/net/swarm/testing\"\n\n\t\"go.uber.org/mock/gomock\"\n)\n\nfunc setupMockNAT(t *testing.T) (mockNAT *MockNAT, reset func()) {\n\tt.Helper()\n\tctrl := gomock.NewController(t)\n\tmockNAT = NewMockNAT(ctrl)\n\torigDiscoverNAT := discoverNAT\n\tdiscoverNAT = func(_ context.Context) (nat, error) { return mockNAT, nil }\n\treturn mockNAT, func() {\n\t\tdiscoverNAT = origDiscoverNAT\n\t\tctrl.Finish()\n\t}\n}\n\nfunc TestMapping(t *testing.T) {\n\tmockNAT, reset := setupMockNAT(t)\n\tdefer reset()\n\n\tsw := swarmt.GenSwarm(t)\n\tdefer sw.Close()\n\tm := newNATManager(sw)\n\trequire.Eventually(t, func() bool {\n\t\tm.natMx.Lock()\n\t\tdefer m.natMx.Unlock()\n\t\treturn m.nat != nil\n\t}, time.Second, time.Millisecond)\n\texternalAddr := netip.AddrPortFrom(netip.AddrFrom4([4]byte{1, 2, 3, 4}), 4321)\n\t// pretend that we have a TCP mapping\n\tmockNAT.EXPECT().GetMapping(\"tcp\", 1234).Return(externalAddr, true)\n\trequire.Equal(t, \"/ip4/1.2.3.4/tcp/4321\", m.GetMapping(ma.StringCast(\"/ip4/0.0.0.0/tcp/1234\")).String())\n\n\t// pretend that we have a QUIC mapping\n\tmockNAT.EXPECT().GetMapping(\"udp\", 1234).Return(externalAddr, true)\n\trequire.Equal(t, \"/ip4/1.2.3.4/udp/4321/quic-v1\", m.GetMapping(ma.StringCast(\"/ip4/0.0.0.0/udp/1234/quic-v1\")).String())\n\n\t// pretend that there's no mapping\n\tmockNAT.EXPECT().GetMapping(\"tcp\", 1234).Return(netip.AddrPort{}, false)\n\trequire.Nil(t, m.GetMapping(ma.StringCast(\"/ip4/0.0.0.0/tcp/1234\")))\n\n\t// make sure this works for WebSocket addresses as well\n\tmockNAT.EXPECT().GetMapping(\"tcp\", 1234).Return(externalAddr, true)\n\trequire.Equal(t, \"/ip4/1.2.3.4/tcp/4321/ws\", m.GetMapping(ma.StringCast(\"/ip4/0.0.0.0/tcp/1234/ws\")).String())\n\n\t// make sure this works for WebTransport addresses as well\n\tmockNAT.EXPECT().GetMapping(\"udp\", 1234).Return(externalAddr, true)\n\trequire.Equal(t, \"/ip4/1.2.3.4/udp/4321/quic-v1/webtransport\", m.GetMapping(ma.StringCast(\"/ip4/0.0.0.0/udp/1234/quic-v1/webtransport\")).String())\n}\n\nfunc TestAddAndRemoveListeners(t *testing.T) {\n\tmockNAT, reset := setupMockNAT(t)\n\tdefer reset()\n\n\tsw := swarmt.GenSwarm(t)\n\tdefer sw.Close()\n\tm := newNATManager(sw)\n\trequire.Eventually(t, func() bool {\n\t\tm.natMx.Lock()\n\t\tdefer m.natMx.Unlock()\n\t\treturn m.nat != nil\n\t}, time.Second, time.Millisecond)\n\n\tadded := make(chan struct{}, 1)\n\t// add a TCP listener\n\tmockNAT.EXPECT().AddMapping(gomock.Any(), \"tcp\", 1234).Do(func(context.Context, string, int) { added <- struct{}{} })\n\trequire.NoError(t, sw.Listen(ma.StringCast(\"/ip4/0.0.0.0/tcp/1234\")))\n\tselect {\n\tcase <-added:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"didn't receive call to AddMapping\")\n\t}\n\n\t// add a QUIC listener\n\tmockNAT.EXPECT().AddMapping(gomock.Any(), \"udp\", 1234).Do(func(context.Context, string, int) { added <- struct{}{} })\n\trequire.NoError(t, sw.Listen(ma.StringCast(\"/ip4/0.0.0.0/udp/1234/quic-v1\")))\n\tselect {\n\tcase <-added:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"didn't receive call to AddMapping\")\n\t}\n\n\t// remove the QUIC listener\n\tmockNAT.EXPECT().RemoveMapping(gomock.Any(), \"udp\", 1234).Do(func(context.Context, string, int) { added <- struct{}{} })\n\tsw.ListenClose(ma.StringCast(\"/ip4/0.0.0.0/udp/1234/quic-v1\"))\n\tselect {\n\tcase <-added:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"didn't receive call to RemoveMapping\")\n\t}\n\n\t// test shutdown\n\tmockNAT.EXPECT().RemoveMapping(gomock.Any(), \"tcp\", 1234).MaxTimes(1)\n\tmockNAT.EXPECT().Close().MaxTimes(1)\n}\n"
  },
  {
    "path": "p2p/host/blank/blank.go",
    "content": "package blankhost\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/libp2p/go-libp2p/core/connmgr\"\n\t\"github.com/libp2p/go-libp2p/core/event\"\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\t\"github.com/libp2p/go-libp2p/core/record\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/eventbus\"\n\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmstream \"github.com/multiformats/go-multistream\"\n)\n\nvar log = logging.Logger(\"blankhost\")\n\n// BlankHost is the thinnest implementation of the host.Host interface\ntype BlankHost struct {\n\tn        network.Network\n\tmux      *mstream.MultistreamMuxer[protocol.ID]\n\tcmgr     connmgr.ConnManager\n\teventbus event.Bus\n\temitters struct {\n\t\tevtLocalProtocolsUpdated event.Emitter\n\t}\n}\n\ntype config struct {\n\tcmgr     connmgr.ConnManager\n\teventBus event.Bus\n}\n\ntype Option = func(cfg *config)\n\nfunc WithConnectionManager(cmgr connmgr.ConnManager) Option {\n\treturn func(cfg *config) {\n\t\tcfg.cmgr = cmgr\n\t}\n}\n\nfunc WithEventBus(eventBus event.Bus) Option {\n\treturn func(cfg *config) {\n\t\tcfg.eventBus = eventBus\n\t}\n}\n\nfunc NewBlankHost(n network.Network, options ...Option) *BlankHost {\n\tcfg := config{\n\t\tcmgr: &connmgr.NullConnMgr{},\n\t}\n\tfor _, opt := range options {\n\t\topt(&cfg)\n\t}\n\n\tbh := &BlankHost{\n\t\tn:        n,\n\t\tcmgr:     cfg.cmgr,\n\t\tmux:      mstream.NewMultistreamMuxer[protocol.ID](),\n\t\teventbus: cfg.eventBus,\n\t}\n\tif bh.eventbus == nil {\n\t\tbh.eventbus = eventbus.NewBus(eventbus.WithMetricsTracer(eventbus.NewMetricsTracer()))\n\t}\n\n\t// subscribe the connection manager to network notifications (has no effect with NullConnMgr)\n\tn.Notify(bh.cmgr.Notifee())\n\n\tvar err error\n\tif bh.emitters.evtLocalProtocolsUpdated, err = bh.eventbus.Emitter(&event.EvtLocalProtocolsUpdated{}); err != nil {\n\t\treturn nil\n\t}\n\n\tn.SetStreamHandler(bh.newStreamHandler)\n\n\t// persist a signed peer record for self to the peerstore.\n\tif err := bh.initSignedRecord(); err != nil {\n\t\tlog.Error(\"error creating blank host\", \"err\", err)\n\t\treturn nil\n\t}\n\n\treturn bh\n}\n\nfunc (bh *BlankHost) initSignedRecord() error {\n\tcab, ok := peerstore.GetCertifiedAddrBook(bh.n.Peerstore())\n\tif !ok {\n\t\tlog.Error(\"peerstore does not support signed records\")\n\t\treturn errors.New(\"peerstore does not support signed records\")\n\t}\n\trec := peer.PeerRecordFromAddrInfo(peer.AddrInfo{ID: bh.ID(), Addrs: bh.Addrs()})\n\tev, err := record.Seal(rec, bh.Peerstore().PrivKey(bh.ID()))\n\tif err != nil {\n\t\tlog.Error(\"failed to create signed record for self\", \"err\", err)\n\t\treturn fmt.Errorf(\"failed to create signed record for self, err=%s\", err)\n\t}\n\t_, err = cab.ConsumePeerRecord(ev, peerstore.PermanentAddrTTL)\n\tif err != nil {\n\t\tlog.Error(\"failed to persist signed record to peerstore\", \"err\", err)\n\t\treturn fmt.Errorf(\"failed to persist signed record for self, err=%s\", err)\n\t}\n\treturn err\n}\n\nvar _ host.Host = (*BlankHost)(nil)\n\nfunc (bh *BlankHost) Addrs() []ma.Multiaddr {\n\taddrs, err := bh.n.InterfaceListenAddresses()\n\tif err != nil {\n\t\tlog.Debug(\"error retrieving network interface addrs\", \"err\", err)\n\t\treturn nil\n\t}\n\n\treturn addrs\n}\n\nfunc (bh *BlankHost) Close() error {\n\treturn bh.n.Close()\n}\n\nfunc (bh *BlankHost) Connect(ctx context.Context, ai peer.AddrInfo) error {\n\t// absorb addresses into peerstore\n\tbh.Peerstore().AddAddrs(ai.ID, ai.Addrs, peerstore.TempAddrTTL)\n\n\tcs := bh.n.ConnsToPeer(ai.ID)\n\tif len(cs) > 0 {\n\t\treturn nil\n\t}\n\n\t_, err := bh.Network().DialPeer(ctx, ai.ID)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to dial: %w\", err)\n\t}\n\treturn err\n}\n\nfunc (bh *BlankHost) Peerstore() peerstore.Peerstore {\n\treturn bh.n.Peerstore()\n}\n\nfunc (bh *BlankHost) ID() peer.ID {\n\treturn bh.n.LocalPeer()\n}\n\nfunc (bh *BlankHost) NewStream(ctx context.Context, p peer.ID, protos ...protocol.ID) (network.Stream, error) {\n\ts, err := bh.n.NewStream(ctx, p)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to open stream: %w\", err)\n\t}\n\n\tselected, err := mstream.SelectOneOf(protos, s)\n\tif err != nil {\n\t\ts.Reset()\n\t\treturn nil, fmt.Errorf(\"failed to negotiate protocol: %w\", err)\n\t}\n\n\ts.SetProtocol(selected)\n\tbh.Peerstore().AddProtocols(p, selected)\n\n\treturn s, nil\n}\n\nfunc (bh *BlankHost) RemoveStreamHandler(pid protocol.ID) {\n\tbh.Mux().RemoveHandler(pid)\n\tbh.emitters.evtLocalProtocolsUpdated.Emit(event.EvtLocalProtocolsUpdated{\n\t\tRemoved: []protocol.ID{pid},\n\t})\n}\n\nfunc (bh *BlankHost) SetStreamHandler(pid protocol.ID, handler network.StreamHandler) {\n\tbh.Mux().AddHandler(pid, func(p protocol.ID, rwc io.ReadWriteCloser) error {\n\t\tis := rwc.(network.Stream)\n\t\tis.SetProtocol(p)\n\t\thandler(is)\n\t\treturn nil\n\t})\n\tbh.emitters.evtLocalProtocolsUpdated.Emit(event.EvtLocalProtocolsUpdated{\n\t\tAdded: []protocol.ID{pid},\n\t})\n}\n\nfunc (bh *BlankHost) SetStreamHandlerMatch(pid protocol.ID, m func(protocol.ID) bool, handler network.StreamHandler) {\n\tbh.Mux().AddHandlerWithFunc(pid, m, func(p protocol.ID, rwc io.ReadWriteCloser) error {\n\t\tis := rwc.(network.Stream)\n\t\tis.SetProtocol(p)\n\t\thandler(is)\n\t\treturn nil\n\t})\n\tbh.emitters.evtLocalProtocolsUpdated.Emit(event.EvtLocalProtocolsUpdated{\n\t\tAdded: []protocol.ID{pid},\n\t})\n}\n\n// newStreamHandler is the remote-opened stream handler for network.Network\nfunc (bh *BlankHost) newStreamHandler(s network.Stream) {\n\tprotoID, handle, err := bh.Mux().Negotiate(s)\n\tif err != nil {\n\t\tlog.Info(\"protocol negotiation failed\", \"err\", err)\n\t\ts.Reset()\n\t\treturn\n\t}\n\n\ts.SetProtocol(protoID)\n\n\thandle(protoID, s)\n}\n\n// TODO: i'm not sure this really needs to be here\nfunc (bh *BlankHost) Mux() protocol.Switch {\n\treturn bh.mux\n}\n\n// TODO: also not sure this fits... Might be better ways around this (leaky abstractions)\nfunc (bh *BlankHost) Network() network.Network {\n\treturn bh.n\n}\n\nfunc (bh *BlankHost) ConnManager() connmgr.ConnManager {\n\treturn bh.cmgr\n}\n\nfunc (bh *BlankHost) EventBus() event.Bus {\n\treturn bh.eventbus\n}\n"
  },
  {
    "path": "p2p/host/eventbus/basic.go",
    "content": "package eventbus\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/event\"\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n)\n\nvar log = logging.Logger(\"eventbus\")\n\nconst slowConsumerWarningTimeout = time.Second\n\n// /////////////////////\n// BUS\n\n// basicBus is a type-based event delivery system\ntype basicBus struct {\n\tlk            sync.RWMutex\n\tnodes         map[reflect.Type]*node\n\twildcard      *wildcardNode\n\tmetricsTracer MetricsTracer\n}\n\nvar _ event.Bus = (*basicBus)(nil)\n\ntype emitter struct {\n\tn             *node\n\tw             *wildcardNode\n\ttyp           reflect.Type\n\tclosed        atomic.Bool\n\tdropper       func(reflect.Type)\n\tmetricsTracer MetricsTracer\n}\n\nfunc (e *emitter) Emit(evt any) error {\n\tif e.closed.Load() {\n\t\treturn fmt.Errorf(\"emitter is closed\")\n\t}\n\n\te.n.emit(evt)\n\te.w.emit(evt)\n\n\tif e.metricsTracer != nil {\n\t\te.metricsTracer.EventEmitted(e.typ)\n\t}\n\treturn nil\n}\n\nfunc (e *emitter) Close() error {\n\tif !e.closed.CompareAndSwap(false, true) {\n\t\treturn fmt.Errorf(\"closed an emitter more than once\")\n\t}\n\tif e.n.nEmitters.Add(-1) == 0 {\n\t\te.dropper(e.typ)\n\t}\n\treturn nil\n}\n\nfunc NewBus(opts ...Option) event.Bus {\n\tbus := &basicBus{\n\t\tnodes:    map[reflect.Type]*node{},\n\t\twildcard: &wildcardNode{},\n\t}\n\tfor _, opt := range opts {\n\t\topt(bus)\n\t}\n\treturn bus\n}\n\nfunc (b *basicBus) withNode(typ reflect.Type, cb func(*node), async func(*node)) {\n\tb.lk.Lock()\n\n\tn, ok := b.nodes[typ]\n\tif !ok {\n\t\tn = newNode(typ, b.metricsTracer)\n\t\tb.nodes[typ] = n\n\t}\n\n\tn.lk.Lock()\n\tb.lk.Unlock()\n\n\tcb(n)\n\n\tif async == nil {\n\t\tn.lk.Unlock()\n\t} else {\n\t\tgo func() {\n\t\t\tdefer n.lk.Unlock()\n\t\t\tasync(n)\n\t\t}()\n\t}\n}\n\nfunc (b *basicBus) tryDropNode(typ reflect.Type) {\n\tb.lk.Lock()\n\tn, ok := b.nodes[typ]\n\tif !ok { // already dropped\n\t\tb.lk.Unlock()\n\t\treturn\n\t}\n\n\tn.lk.Lock()\n\tif n.nEmitters.Load() > 0 || len(n.sinks) > 0 {\n\t\tn.lk.Unlock()\n\t\tb.lk.Unlock()\n\t\treturn // still in use\n\t}\n\tn.lk.Unlock()\n\n\tdelete(b.nodes, typ)\n\tb.lk.Unlock()\n}\n\ntype wildcardSub struct {\n\tch            chan any\n\tw             *wildcardNode\n\tmetricsTracer MetricsTracer\n\tname          string\n\tcloseOnce     sync.Once\n}\n\nfunc (w *wildcardSub) Out() <-chan any {\n\treturn w.ch\n}\n\nfunc (w *wildcardSub) Close() error {\n\tw.closeOnce.Do(func() {\n\t\tw.w.removeSink(w.ch)\n\t\tif w.metricsTracer != nil {\n\t\t\tw.metricsTracer.RemoveSubscriber(reflect.TypeOf(event.WildcardSubscription))\n\t\t}\n\t})\n\n\treturn nil\n}\n\nfunc (w *wildcardSub) Name() string {\n\treturn w.name\n}\n\ntype namedSink struct {\n\tname string\n\tch   chan any\n}\n\ntype sub struct {\n\tch            chan any\n\tnodes         []*node\n\tdropper       func(reflect.Type)\n\tmetricsTracer MetricsTracer\n\tname          string\n\tcloseOnce     sync.Once\n}\n\nfunc (s *sub) Name() string {\n\treturn s.name\n}\n\nfunc (s *sub) Out() <-chan any {\n\treturn s.ch\n}\n\nfunc (s *sub) Close() error {\n\tgo func() {\n\t\t// drain the event channel, will return when closed and drained.\n\t\t// this is necessary to unblock publishes to this channel.\n\t\tfor range s.ch {\n\t\t}\n\t}()\n\ts.closeOnce.Do(func() {\n\t\tfor _, n := range s.nodes {\n\t\t\tn.lk.Lock()\n\n\t\t\tfor i := 0; i < len(n.sinks); i++ {\n\t\t\t\tif n.sinks[i].ch == s.ch {\n\t\t\t\t\tn.sinks[i], n.sinks[len(n.sinks)-1] = n.sinks[len(n.sinks)-1], nil\n\t\t\t\t\tn.sinks = n.sinks[:len(n.sinks)-1]\n\n\t\t\t\t\tif s.metricsTracer != nil {\n\t\t\t\t\t\ts.metricsTracer.RemoveSubscriber(n.typ)\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttryDrop := len(n.sinks) == 0 && n.nEmitters.Load() == 0\n\n\t\t\tn.lk.Unlock()\n\n\t\t\tif tryDrop {\n\t\t\t\ts.dropper(n.typ)\n\t\t\t}\n\t\t}\n\t\tclose(s.ch)\n\t})\n\treturn nil\n}\n\nvar _ event.Subscription = (*sub)(nil)\n\n// Subscribe creates new subscription. Failing to drain the channel will cause\n// publishers to get blocked. CancelFunc is guaranteed to return after last send\n// to the channel\nfunc (b *basicBus) Subscribe(evtTypes any, opts ...event.SubscriptionOpt) (_ event.Subscription, err error) {\n\tsettings := newSubSettings()\n\tfor _, opt := range opts {\n\t\tif err := opt(&settings); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif evtTypes == event.WildcardSubscription {\n\t\tout := &wildcardSub{\n\t\t\tch:            make(chan any, settings.buffer),\n\t\t\tw:             b.wildcard,\n\t\t\tmetricsTracer: b.metricsTracer,\n\t\t\tname:          settings.name,\n\t\t}\n\t\tb.wildcard.addSink(&namedSink{ch: out.ch, name: out.name})\n\t\treturn out, nil\n\t}\n\n\ttypes, ok := evtTypes.([]any)\n\tif !ok {\n\t\ttypes = []any{evtTypes}\n\t}\n\n\tif len(types) > 1 {\n\t\tfor _, t := range types {\n\t\t\tif t == event.WildcardSubscription {\n\t\t\t\treturn nil, fmt.Errorf(\"wildcard subscriptions must be started separately\")\n\t\t\t}\n\t\t}\n\t}\n\n\tout := &sub{\n\t\tch:    make(chan any, settings.buffer),\n\t\tnodes: make([]*node, len(types)),\n\n\t\tdropper:       b.tryDropNode,\n\t\tmetricsTracer: b.metricsTracer,\n\t\tname:          settings.name,\n\t}\n\n\tfor _, etyp := range types {\n\t\tif reflect.TypeOf(etyp).Kind() != reflect.Pointer {\n\t\t\treturn nil, errors.New(\"subscribe called with non-pointer type\")\n\t\t}\n\t}\n\n\tfor i, etyp := range types {\n\t\ttyp := reflect.TypeOf(etyp)\n\n\t\tb.withNode(typ.Elem(), func(n *node) {\n\t\t\tn.sinks = append(n.sinks, &namedSink{ch: out.ch, name: out.name})\n\t\t\tout.nodes[i] = n\n\t\t\tif b.metricsTracer != nil {\n\t\t\t\tb.metricsTracer.AddSubscriber(typ.Elem())\n\t\t\t}\n\t\t}, func(n *node) {\n\t\t\tif n.keepLast {\n\t\t\t\tl := n.last\n\t\t\t\tif l == nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tout.ch <- l\n\t\t\t}\n\t\t})\n\t}\n\n\treturn out, nil\n}\n\n// Emitter creates new emitter\n//\n// eventType accepts typed nil pointers, and uses the type information to\n// select output type\n//\n// Example:\n// emit, err := eventbus.Emitter(new(EventT))\n// defer emit.Close() // MUST call this after being done with the emitter\n//\n// emit(EventT{})\nfunc (b *basicBus) Emitter(evtType any, opts ...event.EmitterOpt) (e event.Emitter, err error) {\n\tif evtType == event.WildcardSubscription {\n\t\treturn nil, fmt.Errorf(\"illegal emitter for wildcard subscription\")\n\t}\n\n\tvar settings emitterSettings\n\tfor _, opt := range opts {\n\t\tif err := opt(&settings); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\ttyp := reflect.TypeOf(evtType)\n\tif typ.Kind() != reflect.Pointer {\n\t\treturn nil, errors.New(\"emitter called with non-pointer type\")\n\t}\n\ttyp = typ.Elem()\n\n\tb.withNode(typ, func(n *node) {\n\t\tn.nEmitters.Add(1)\n\t\tn.keepLast = n.keepLast || settings.makeStateful\n\t\te = &emitter{n: n, typ: typ, dropper: b.tryDropNode, w: b.wildcard, metricsTracer: b.metricsTracer}\n\t}, nil)\n\treturn\n}\n\n// GetAllEventTypes returns all the event types that this bus has emitters\n// or subscribers for.\nfunc (b *basicBus) GetAllEventTypes() []reflect.Type {\n\tb.lk.RLock()\n\tdefer b.lk.RUnlock()\n\n\ttypes := make([]reflect.Type, 0, len(b.nodes))\n\tfor t := range b.nodes {\n\t\ttypes = append(types, t)\n\t}\n\treturn types\n}\n\n// /////////////////////\n// NODE\n\ntype wildcardNode struct {\n\tsync.RWMutex\n\tnSinks        atomic.Int32\n\tsinks         []*namedSink\n\tmetricsTracer MetricsTracer\n\n\tslowConsumerTimer *time.Timer\n}\n\nfunc (n *wildcardNode) addSink(sink *namedSink) {\n\tn.nSinks.Add(1) // ok to do outside the lock\n\tn.Lock()\n\tn.sinks = append(n.sinks, sink)\n\tn.Unlock()\n\n\tif n.metricsTracer != nil {\n\t\tn.metricsTracer.AddSubscriber(reflect.TypeOf(event.WildcardSubscription))\n\t}\n}\n\nfunc (n *wildcardNode) removeSink(ch chan any) {\n\tgo func() {\n\t\t// drain the event channel, will return when closed and drained.\n\t\t// this is necessary to unblock publishes to this channel.\n\t\tfor range ch {\n\t\t}\n\t}()\n\tn.nSinks.Add(-1) // ok to do outside the lock\n\tn.Lock()\n\tfor i := 0; i < len(n.sinks); i++ {\n\t\tif n.sinks[i].ch == ch {\n\t\t\tn.sinks[i], n.sinks[len(n.sinks)-1] = n.sinks[len(n.sinks)-1], nil\n\t\t\tn.sinks = n.sinks[:len(n.sinks)-1]\n\t\t\tbreak\n\t\t}\n\t}\n\tn.Unlock()\n}\n\nvar wildcardType = reflect.TypeOf(event.WildcardSubscription)\n\nfunc (n *wildcardNode) emit(evt any) {\n\tif n.nSinks.Load() == 0 {\n\t\treturn\n\t}\n\n\tn.RLock()\n\tfor _, sink := range n.sinks {\n\n\t\t// Sending metrics before sending on channel allows us to\n\t\t// record channel full events before blocking\n\t\tsendSubscriberMetrics(n.metricsTracer, sink)\n\n\t\tselect {\n\t\tcase sink.ch <- evt:\n\t\tdefault:\n\t\t\tslowConsumerTimer := emitAndLogError(n.slowConsumerTimer, wildcardType, evt, sink)\n\t\t\tdefer func() {\n\t\t\t\tn.Lock()\n\t\t\t\tn.slowConsumerTimer = slowConsumerTimer\n\t\t\t\tn.Unlock()\n\t\t\t}()\n\t\t}\n\t}\n\tn.RUnlock()\n}\n\ntype node struct {\n\t// Note: make sure to NEVER lock basicBus.lk when this lock is held\n\tlk sync.Mutex\n\n\ttyp reflect.Type\n\n\t// emitter ref count\n\tnEmitters atomic.Int32\n\n\tkeepLast bool\n\tlast     any\n\n\tsinks         []*namedSink\n\tmetricsTracer MetricsTracer\n\n\tslowConsumerTimer *time.Timer\n}\n\nfunc newNode(typ reflect.Type, metricsTracer MetricsTracer) *node {\n\treturn &node{\n\t\ttyp:           typ,\n\t\tmetricsTracer: metricsTracer,\n\t}\n}\n\nfunc (n *node) emit(evt any) {\n\ttyp := reflect.TypeOf(evt)\n\tif typ != n.typ {\n\t\tpanic(fmt.Sprintf(\"Emit called with wrong type. expected: %s, got: %s\", n.typ, typ))\n\t}\n\n\tn.lk.Lock()\n\tif n.keepLast {\n\t\tn.last = evt\n\t}\n\n\tfor _, sink := range n.sinks {\n\n\t\t// Sending metrics before sending on channel allows us to\n\t\t// record channel full events before blocking\n\t\tsendSubscriberMetrics(n.metricsTracer, sink)\n\t\tselect {\n\t\tcase sink.ch <- evt:\n\t\tdefault:\n\t\t\tn.slowConsumerTimer = emitAndLogError(n.slowConsumerTimer, n.typ, evt, sink)\n\t\t}\n\t}\n\tn.lk.Unlock()\n}\n\nfunc emitAndLogError(timer *time.Timer, typ reflect.Type, evt any, sink *namedSink) *time.Timer {\n\t// Slow consumer. Log a warning if stalled for the timeout\n\tif timer == nil {\n\t\ttimer = time.NewTimer(slowConsumerWarningTimeout)\n\t} else {\n\t\ttimer.Reset(slowConsumerWarningTimeout)\n\t}\n\n\tselect {\n\tcase sink.ch <- evt:\n\t\tif !timer.Stop() {\n\t\t\t<-timer.C\n\t\t}\n\tcase <-timer.C:\n\t\tlog.Warn(\"subscriber is a slow consumer. This can lead to libp2p stalling and hard to debug issues.\", \"subscriber_name\", sink.name, \"event_type\", typ)\n\t\t// Continue to stall since there's nothing else we can do.\n\t\tsink.ch <- evt\n\t}\n\n\treturn timer\n}\n\nfunc sendSubscriberMetrics(metricsTracer MetricsTracer, sink *namedSink) {\n\tif metricsTracer != nil {\n\t\tmetricsTracer.SubscriberQueueLength(sink.name, len(sink.ch)+1)\n\t\tmetricsTracer.SubscriberQueueFull(sink.name, len(sink.ch)+1 >= cap(sink.ch))\n\t\tmetricsTracer.SubscriberEventQueued(sink.name)\n\t}\n}\n"
  },
  {
    "path": "p2p/host/eventbus/basic_metrics.go",
    "content": "package eventbus\n\nimport (\n\t\"reflect\"\n\t\"strings\"\n\n\t\"github.com/libp2p/go-libp2p/p2p/metricshelper\"\n\n\t\"github.com/prometheus/client_golang/prometheus\"\n)\n\nconst metricNamespace = \"libp2p_eventbus\"\n\nvar (\n\teventsEmitted = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"events_emitted_total\",\n\t\t\tHelp:      \"Events Emitted\",\n\t\t},\n\t\t[]string{\"event\"},\n\t)\n\ttotalSubscribers = prometheus.NewGaugeVec(\n\t\tprometheus.GaugeOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"subscribers_total\",\n\t\t\tHelp:      \"Number of subscribers for an event type\",\n\t\t},\n\t\t[]string{\"event\"},\n\t)\n\tsubscriberQueueLength = prometheus.NewGaugeVec(\n\t\tprometheus.GaugeOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"subscriber_queue_length\",\n\t\t\tHelp:      \"Subscriber queue length\",\n\t\t},\n\t\t[]string{\"subscriber_name\"},\n\t)\n\tsubscriberQueueFull = prometheus.NewGaugeVec(\n\t\tprometheus.GaugeOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"subscriber_queue_full\",\n\t\t\tHelp:      \"Subscriber Queue completely full\",\n\t\t},\n\t\t[]string{\"subscriber_name\"},\n\t)\n\tsubscriberEventQueued = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"subscriber_event_queued\",\n\t\t\tHelp:      \"Event Queued for subscriber\",\n\t\t},\n\t\t[]string{\"subscriber_name\"},\n\t)\n\tcollectors = []prometheus.Collector{\n\t\teventsEmitted,\n\t\ttotalSubscribers,\n\t\tsubscriberQueueLength,\n\t\tsubscriberQueueFull,\n\t\tsubscriberEventQueued,\n\t}\n)\n\n// MetricsTracer tracks metrics for the eventbus subsystem\ntype MetricsTracer interface {\n\n\t// EventEmitted counts the total number of events grouped by event type\n\tEventEmitted(typ reflect.Type)\n\n\t// AddSubscriber adds a subscriber for the event type\n\tAddSubscriber(typ reflect.Type)\n\n\t// RemoveSubscriber removes a subscriber for the event type\n\tRemoveSubscriber(typ reflect.Type)\n\n\t// SubscriberQueueLength is the length of the subscribers channel\n\tSubscriberQueueLength(name string, n int)\n\n\t// SubscriberQueueFull tracks whether a subscribers channel if full\n\tSubscriberQueueFull(name string, isFull bool)\n\n\t// SubscriberEventQueued counts the total number of events grouped by subscriber\n\tSubscriberEventQueued(name string)\n}\n\ntype metricsTracer struct{}\n\nvar _ MetricsTracer = &metricsTracer{}\n\ntype metricsTracerSetting struct {\n\treg prometheus.Registerer\n}\n\ntype MetricsTracerOption func(*metricsTracerSetting)\n\nfunc WithRegisterer(reg prometheus.Registerer) MetricsTracerOption {\n\treturn func(s *metricsTracerSetting) {\n\t\tif reg != nil {\n\t\t\ts.reg = reg\n\t\t}\n\t}\n}\n\nfunc NewMetricsTracer(opts ...MetricsTracerOption) MetricsTracer {\n\tsetting := &metricsTracerSetting{reg: prometheus.DefaultRegisterer}\n\tfor _, opt := range opts {\n\t\topt(setting)\n\t}\n\tmetricshelper.RegisterCollectors(setting.reg, collectors...)\n\treturn &metricsTracer{}\n}\n\nfunc (m *metricsTracer) EventEmitted(typ reflect.Type) {\n\ttags := metricshelper.GetStringSlice()\n\tdefer metricshelper.PutStringSlice(tags)\n\n\t*tags = append(*tags, strings.TrimPrefix(typ.String(), \"event.\"))\n\teventsEmitted.WithLabelValues(*tags...).Inc()\n}\n\nfunc (m *metricsTracer) AddSubscriber(typ reflect.Type) {\n\ttags := metricshelper.GetStringSlice()\n\tdefer metricshelper.PutStringSlice(tags)\n\n\t*tags = append(*tags, strings.TrimPrefix(typ.String(), \"event.\"))\n\ttotalSubscribers.WithLabelValues(*tags...).Inc()\n}\n\nfunc (m *metricsTracer) RemoveSubscriber(typ reflect.Type) {\n\ttags := metricshelper.GetStringSlice()\n\tdefer metricshelper.PutStringSlice(tags)\n\n\t*tags = append(*tags, strings.TrimPrefix(typ.String(), \"event.\"))\n\ttotalSubscribers.WithLabelValues(*tags...).Dec()\n}\n\nfunc (m *metricsTracer) SubscriberQueueLength(name string, n int) {\n\ttags := metricshelper.GetStringSlice()\n\tdefer metricshelper.PutStringSlice(tags)\n\n\t*tags = append(*tags, name)\n\tsubscriberQueueLength.WithLabelValues(*tags...).Set(float64(n))\n}\n\nfunc (m *metricsTracer) SubscriberQueueFull(name string, isFull bool) {\n\ttags := metricshelper.GetStringSlice()\n\tdefer metricshelper.PutStringSlice(tags)\n\n\t*tags = append(*tags, name)\n\tobserver := subscriberQueueFull.WithLabelValues(*tags...)\n\tif isFull {\n\t\tobserver.Set(1)\n\t} else {\n\t\tobserver.Set(0)\n\t}\n}\n\nfunc (m *metricsTracer) SubscriberEventQueued(name string) {\n\ttags := metricshelper.GetStringSlice()\n\tdefer metricshelper.PutStringSlice(tags)\n\n\t*tags = append(*tags, name)\n\tsubscriberEventQueued.WithLabelValues(*tags...).Inc()\n}\n"
  },
  {
    "path": "p2p/host/eventbus/basic_metrics_test.go",
    "content": "//go:build nocover\n\npackage eventbus\n\nimport (\n\t\"math/rand\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p/core/event\"\n)\n\nfunc BenchmarkEventEmitted(b *testing.B) {\n\tb.ReportAllocs()\n\ttypes := []reflect.Type{\n\t\treflect.TypeOf(new(event.EvtLocalAddressesUpdated)),\n\t\treflect.TypeOf(new(event.EvtNATDeviceTypeChanged)),\n\t\treflect.TypeOf(new(event.EvtLocalProtocolsUpdated)),\n\t}\n\tmt := NewMetricsTracer()\n\tfor i := 0; i < b.N; i++ {\n\t\tmt.EventEmitted(types[i%len(types)])\n\t}\n}\n\nfunc BenchmarkSubscriberQueueLength(b *testing.B) {\n\tb.ReportAllocs()\n\tnames := []string{\"s1\", \"s2\", \"s3\", \"s4\"}\n\tmt := NewMetricsTracer()\n\tfor i := 0; i < b.N; i++ {\n\t\tmt.SubscriberQueueLength(names[i%len(names)], i)\n\t}\n}\n\nvar eventTypes = []reflect.Type{\n\treflect.TypeOf(new(event.EvtLocalAddressesUpdated)),\n\treflect.TypeOf(new(event.EvtNATDeviceTypeChanged)),\n\treflect.TypeOf(new(event.EvtLocalProtocolsUpdated)),\n\treflect.TypeOf(new(event.EvtPeerIdentificationCompleted)),\n}\n\nvar names = []string{\n\t\"one\",\n\t\"two\",\n\t\"three\",\n\t\"four\",\n\t\"five\",\n}\n\nfunc TestMetricsNoAllocNoCover(t *testing.T) {\n\tmt := NewMetricsTracer()\n\ttests := map[string]func(){\n\t\t\"EventEmitted\":          func() { mt.EventEmitted(eventTypes[rand.Intn(len(eventTypes))]) },\n\t\t\"AddSubscriber\":         func() { mt.AddSubscriber(eventTypes[rand.Intn(len(eventTypes))]) },\n\t\t\"RemoveSubscriber\":      func() { mt.RemoveSubscriber(eventTypes[rand.Intn(len(eventTypes))]) },\n\t\t\"SubscriberQueueLength\": func() { mt.SubscriberQueueLength(names[rand.Intn(len(names))], rand.Intn(100)) },\n\t\t\"SubscriberQueueFull\":   func() { mt.SubscriberQueueFull(names[rand.Intn(len(names))], rand.Intn(2) == 1) },\n\t\t\"SubscriberEventQueued\": func() { mt.SubscriberEventQueued(names[rand.Intn(len(names))]) },\n\t}\n\tfor method, f := range tests {\n\t\tallocs := testing.AllocsPerRun(1000, f)\n\t\tif allocs > 0 {\n\t\t\tt.Fatalf(\"Alloc Test: %s, got: %0.2f, expected: 0 allocs\", method, allocs)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "p2p/host/eventbus/basic_test.go",
    "content": "package eventbus\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"reflect\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/event\"\n\n\t\"github.com/libp2p/go-libp2p-testing/race\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype (\n\tEventA struct{}\n\tEventB int\n)\n\nfunc getN() int {\n\tn := 50000\n\tif race.WithRace() {\n\t\tn = 1000\n\t}\n\treturn n\n}\n\nfunc (EventA) String() string {\n\treturn \"Oh, Hello\"\n}\n\nfunc TestDefaultSubIsBuffered(t *testing.T) {\n\tbus := NewBus()\n\ts, err := bus.Subscribe(new(EventA))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif cap(s.(*sub).ch) == 0 {\n\t\tt.Fatalf(\"without any options subscribe should be buffered. was %d\", cap(s.(*sub).ch))\n\t}\n}\n\nfunc TestEmit(t *testing.T) {\n\tbus := NewBus()\n\tsub, err := bus.Subscribe(new(EventA))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tgo func() {\n\t\tdefer sub.Close()\n\t\t<-sub.Out()\n\t}()\n\n\tem, err := bus.Emitter(new(EventA))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer em.Close()\n\n\tem.Emit(EventA{})\n}\n\nfunc TestSub(t *testing.T) {\n\tbus := NewBus()\n\tsub, err := bus.Subscribe(new(EventB))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tvar event EventB\n\n\tvar wait sync.WaitGroup\n\twait.Add(1)\n\n\tgo func() {\n\t\tdefer sub.Close()\n\t\tevent = (<-sub.Out()).(EventB)\n\t\twait.Done()\n\t}()\n\n\tem, err := bus.Emitter(new(EventB))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer em.Close()\n\n\tem.Emit(EventB(7))\n\twait.Wait()\n\n\tif event != 7 {\n\t\tt.Error(\"got wrong event\")\n\t}\n}\n\nfunc TestGetAllEventTypes(t *testing.T) {\n\tbus := NewBus()\n\trequire.Empty(t, bus.GetAllEventTypes())\n\n\t// the wildcard subscription should be returned.\n\t_, err := bus.Subscribe(event.WildcardSubscription)\n\trequire.NoError(t, err)\n\n\t_, err = bus.Subscribe(new(EventB))\n\trequire.NoError(t, err)\n\n\tevts := bus.GetAllEventTypes()\n\trequire.Len(t, evts, 1)\n\trequire.Equal(t, reflect.TypeOf((*EventB)(nil)).Elem(), evts[0])\n\n\t_, err = bus.Emitter(new(EventA))\n\trequire.NoError(t, err)\n\n\tevts = bus.GetAllEventTypes()\n\trequire.Len(t, evts, 2)\n\trequire.Contains(t, evts, reflect.TypeOf((*EventB)(nil)).Elem())\n\trequire.Contains(t, evts, reflect.TypeOf((*EventA)(nil)).Elem())\n}\n\nfunc TestEmitNoSubNoBlock(t *testing.T) {\n\tbus := NewBus()\n\n\tem, err := bus.Emitter(new(EventA))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer em.Close()\n\n\tem.Emit(EventA{})\n}\n\ntype mockLogger struct {\n\tmu   sync.Mutex\n\tlogs []string\n}\n\nfunc (m *mockLogger) Write(p []byte) (n int, err error) {\n\tm.mu.Lock()\n\tdefer m.mu.Unlock()\n\tm.logs = append(m.logs, string(p))\n\treturn len(p), nil\n}\n\nfunc (m *mockLogger) Logs() []string {\n\tm.mu.Lock()\n\tdefer m.mu.Unlock()\n\treturn m.logs\n}\n\nfunc (m *mockLogger) Clear() {\n\tm.mu.Lock()\n\tdefer m.mu.Unlock()\n\tm.logs = nil\n}\n\nfunc TestEmitLogsErrorOnStall(t *testing.T) {\n\toldLogger := log\n\tdefer func() {\n\t\tlog = oldLogger\n\t}()\n\tml := mockLogger{}\n\tlog = slog.New(slog.NewTextHandler(&ml, nil))\n\n\tbus1 := NewBus()\n\tbus2 := NewBus()\n\n\teventSub, err := bus1.Subscribe(new(EventA))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\twildcardSub, err := bus2.Subscribe(event.WildcardSubscription)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttestCases := []event.Subscription{eventSub, wildcardSub}\n\teventBuses := []event.Bus{bus1, bus2}\n\n\tfor i, sub := range testCases {\n\t\tbus := eventBuses[i]\n\t\tem, err := bus.Emitter(new(EventA))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer em.Close()\n\n\t\tgo func() {\n\t\t\tfor i := 0; i < subSettingsDefault.buffer+2; i++ {\n\t\t\t\tem.Emit(EventA{})\n\t\t\t}\n\t\t}()\n\n\t\trequire.EventuallyWithT(t, func(collect *assert.CollectT) {\n\t\t\tlogs := ml.Logs()\n\t\t\tfound := false\n\t\t\tfor _, log := range logs {\n\t\t\t\tif strings.Contains(log, \"slow consumer\") {\n\t\t\t\t\tfound = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tassert.True(collect, found, \"expected to find slow consumer log\")\n\t\t}, 3*time.Second, 500*time.Millisecond)\n\t\tml.Clear()\n\n\t\t// Close the subscriber so the worker can finish.\n\t\tsub.Close()\n\t}\n}\n\nfunc TestEmitOnClosed(t *testing.T) {\n\tbus := NewBus()\n\n\tem, err := bus.Emitter(new(EventA))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tem.Close()\n\terr = em.Emit(EventA{})\n\tif err == nil {\n\t\tt.Errorf(\"expected error\")\n\t}\n\tif err.Error() != \"emitter is closed\" {\n\t\tt.Error(\"unexpected message\")\n\t}\n}\n\nfunc TestClosingRaces(t *testing.T) {\n\tsubs := getN()\n\temits := getN()\n\n\tvar wg sync.WaitGroup\n\tvar lk sync.RWMutex\n\tlk.Lock()\n\n\twg.Add(subs + emits)\n\n\tb := NewBus()\n\n\tfor range subs {\n\t\tgo func() {\n\t\t\tlk.RLock()\n\t\t\tdefer lk.RUnlock()\n\n\t\t\tsub, _ := b.Subscribe(new(EventA))\n\t\t\ttime.Sleep(10 * time.Millisecond)\n\t\t\tsub.Close()\n\n\t\t\twg.Done()\n\t\t}()\n\t}\n\tfor range emits {\n\t\tgo func() {\n\t\t\tlk.RLock()\n\t\t\tdefer lk.RUnlock()\n\n\t\t\temit, _ := b.Emitter(new(EventA))\n\t\t\ttime.Sleep(10 * time.Millisecond)\n\t\t\temit.Close()\n\n\t\t\twg.Done()\n\t\t}()\n\t}\n\n\ttime.Sleep(10 * time.Millisecond)\n\tlk.Unlock() // start everything\n\n\twg.Wait()\n\n\tif len(b.(*basicBus).nodes) != 0 {\n\t\tt.Error(\"expected no nodes\")\n\t}\n}\n\nfunc TestSubMany(t *testing.T) {\n\tbus := NewBus()\n\n\tvar r atomic.Int32\n\n\tn := getN()\n\tvar wait sync.WaitGroup\n\tvar ready sync.WaitGroup\n\twait.Add(n)\n\tready.Add(n)\n\n\tfor range n {\n\t\tgo func() {\n\t\t\tsub, err := bus.Subscribe(new(EventB))\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\tdefer sub.Close()\n\n\t\t\tready.Done()\n\t\t\tr.Add(int32((<-sub.Out()).(EventB)))\n\t\t\twait.Done()\n\t\t}()\n\t}\n\n\tem, err := bus.Emitter(new(EventB))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer em.Close()\n\n\tready.Wait()\n\n\tem.Emit(EventB(7))\n\twait.Wait()\n\n\tif int(r.Load()) != 7*n {\n\t\tt.Error(\"got wrong result\")\n\t}\n}\n\nfunc TestWildcardSubscription(t *testing.T) {\n\tbus := NewBus()\n\tsub, err := bus.Subscribe(event.WildcardSubscription)\n\trequire.NoError(t, err)\n\tdefer sub.Close()\n\n\tem1, err := bus.Emitter(new(EventA))\n\trequire.NoError(t, err)\n\tdefer em1.Close()\n\n\tem2, err := bus.Emitter(new(EventB))\n\trequire.NoError(t, err)\n\tdefer em2.Close()\n\n\trequire.NoError(t, em1.Emit(EventA{}))\n\trequire.NoError(t, em2.Emit(EventB(1)))\n\n\tctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)\n\tdefer cancel()\n\tvar evts []any\n\nLOOP:\n\tfor {\n\t\tselect {\n\t\tcase evt := <-sub.Out():\n\t\t\tif evta, ok := evt.(EventA); ok {\n\t\t\t\tevts = append(evts, evta)\n\t\t\t}\n\n\t\t\tif evtb, ok := evt.(EventB); ok {\n\t\t\t\tevts = append(evts, evtb)\n\t\t\t}\n\n\t\t\tif len(evts) == 2 {\n\t\t\t\tbreak LOOP\n\t\t\t}\n\n\t\tcase <-ctx.Done():\n\t\t\tt.Fatalf(\"did not receive events\")\n\t\t}\n\t}\n}\n\nfunc TestManyWildcardSubscriptions(t *testing.T) {\n\tbus := NewBus()\n\tsubs := make([]event.Subscription, 0, 10)\n\tfor range 10 {\n\t\tsub, err := bus.Subscribe(event.WildcardSubscription)\n\t\trequire.NoError(t, err)\n\t\tsubs = append(subs, sub)\n\t}\n\n\tem1, err := bus.Emitter(new(EventA))\n\trequire.NoError(t, err)\n\tdefer em1.Close()\n\n\tem2, err := bus.Emitter(new(EventB))\n\trequire.NoError(t, err)\n\tdefer em2.Close()\n\n\trequire.NoError(t, em1.Emit(EventA{}))\n\trequire.NoError(t, em2.Emit(EventB(1)))\n\n\t// all 10 subscriptions received all 2 events.\n\tfor _, s := range subs {\n\t\trequire.Len(t, s.Out(), 2)\n\t}\n\n\t// close the first five subscriptions.\n\tfor _, s := range subs[:5] {\n\t\trequire.NoError(t, s.Close())\n\t}\n\n\t// emit another 2 events.\n\trequire.NoError(t, em1.Emit(EventA{}))\n\trequire.NoError(t, em2.Emit(EventB(1)))\n\n\t// the first five 0 events because it was closed. The other five\n\t// have 4 events.\n\trequire.EventuallyWithT(t, func(t *assert.CollectT) {\n\t\tfor _, s := range subs[:5] {\n\t\t\trequire.Len(t, s.Out(), 0, \"expected closed subscription to have flushed events\")\n\t\t}\n\t}, 2*time.Second, 100*time.Millisecond)\n\n\tfor _, s := range subs[5:] {\n\t\trequire.Len(t, s.Out(), 4)\n\t}\n\n\t// close them all, the first five will be closed twice (asserts idempotency).\n\tfor _, s := range subs {\n\t\trequire.NoError(t, s.Close())\n\t}\n\n\tfor _, s := range subs {\n\t\trequire.Zero(t, s.(*wildcardSub).w.nSinks.Load())\n\t}\n}\n\nfunc TestWildcardValidations(t *testing.T) {\n\tbus := NewBus()\n\n\t_, err := bus.Subscribe([]any{event.WildcardSubscription, new(EventA), new(EventB)})\n\trequire.Error(t, err)\n\n\t_, err = bus.Emitter(event.WildcardSubscription)\n\trequire.Error(t, err)\n}\n\nfunc TestSubType(t *testing.T) {\n\tbus := NewBus()\n\tsub, err := bus.Subscribe([]any{new(EventA), new(EventB)})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tvar event fmt.Stringer\n\n\tvar wait sync.WaitGroup\n\twait.Add(1)\n\n\tgo func() {\n\t\tdefer sub.Close()\n\t\tevent = (<-sub.Out()).(EventA)\n\t\twait.Done()\n\t}()\n\n\tem, err := bus.Emitter(new(EventA))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer em.Close()\n\n\tem.Emit(EventA{})\n\twait.Wait()\n\n\tif event.String() != \"Oh, Hello\" {\n\t\tt.Error(\"didn't get the correct message\")\n\t}\n}\n\nfunc TestNonStateful(t *testing.T) {\n\tbus := NewBus()\n\tem, err := bus.Emitter(new(EventB))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer em.Close()\n\n\tsub1, err := bus.Subscribe(new(EventB), BufSize(1))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer sub1.Close()\n\n\tselect {\n\tcase <-sub1.Out():\n\t\tt.Fatal(\"didn't expect to get an event\")\n\tdefault:\n\t}\n\n\tem.Emit(EventB(1))\n\n\tselect {\n\tcase e := <-sub1.Out():\n\t\tif e.(EventB) != 1 {\n\t\t\tt.Fatal(\"got wrong event\")\n\t\t}\n\tdefault:\n\t\tt.Fatal(\"expected to get an event\")\n\t}\n\n\tsub2, err := bus.Subscribe(new(EventB), BufSize(1))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer sub2.Close()\n\n\tselect {\n\tcase <-sub2.Out():\n\t\tt.Fatal(\"didn't expect to get an event\")\n\tdefault:\n\t}\n}\n\nfunc TestStateful(t *testing.T) {\n\tbus := NewBus()\n\tem, err := bus.Emitter(new(EventB), Stateful)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer em.Close()\n\n\tem.Emit(EventB(2))\n\n\tsub, err := bus.Subscribe(new(EventB), BufSize(1))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer sub.Close()\n\n\tif (<-sub.Out()).(EventB) != 2 {\n\t\tt.Fatal(\"got wrong event\")\n\t}\n}\n\nfunc TestCloseBlocking(t *testing.T) {\n\tbus := NewBus()\n\tem, err := bus.Emitter(new(EventB))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tsub, err := bus.Subscribe(new(EventB))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tgo func() {\n\t\tem.Emit(EventB(159))\n\t}()\n\n\ttime.Sleep(10 * time.Millisecond) // make sure that emit is blocked\n\n\tsub.Close() // cancel sub\n}\n\nfunc TestSubFailFully(t *testing.T) {\n\tbus := NewBus()\n\tem, err := bus.Emitter(new(EventB))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t_, err = bus.Subscribe([]any{new(EventB), 5})\n\tif err == nil || err.Error() != \"subscribe called with non-pointer type\" {\n\t\tt.Fatal(err)\n\t}\n\n\tdone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(done)\n\t\tem.Emit(EventB(159)) // will hang if sub doesn't fail properly\n\t}()\n\n\tselect {\n\tcase <-done:\n\tcase <-time.After(5 * time.Second):\n\t\tt.Fatal(\"timeout\")\n\t}\n}\n\nfunc TestSubCloseMultiple(t *testing.T) {\n\tbus := NewBus()\n\n\tsub, err := bus.Subscribe([]any{new(EventB)})\n\trequire.NoError(t, err)\n\terr = sub.Close()\n\trequire.NoError(t, err)\n\terr = sub.Close()\n\trequire.NoError(t, err)\n}\n\nfunc testMany(t testing.TB, subs, emits, msgs int, stateful bool) {\n\tif race.WithRace() && subs+emits > 5000 {\n\t\tt.SkipNow()\n\t}\n\n\tbus := NewBus()\n\n\tvar r atomic.Int64\n\n\tvar wait sync.WaitGroup\n\tvar ready sync.WaitGroup\n\twait.Add(subs + emits)\n\tready.Add(subs)\n\n\tfor range subs {\n\t\tgo func() {\n\t\t\tsub, err := bus.Subscribe(new(EventB))\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\tdefer sub.Close()\n\n\t\t\tready.Done()\n\t\t\tfor i := 0; i < emits*msgs; i++ {\n\t\t\t\te, ok := <-sub.Out()\n\t\t\t\tif !ok {\n\t\t\t\t\tpanic(\"wat\")\n\t\t\t\t}\n\t\t\t\tr.Add(int64(e.(EventB)))\n\t\t\t}\n\t\t\twait.Done()\n\t\t}()\n\t}\n\n\tfor range emits {\n\t\tgo func() {\n\t\t\tem, err := bus.Emitter(new(EventB), func(settings any) error {\n\t\t\t\tsettings.(*emitterSettings).makeStateful = stateful\n\t\t\t\treturn nil\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\tdefer em.Close()\n\n\t\t\tready.Wait()\n\n\t\t\tfor range msgs {\n\t\t\t\tem.Emit(EventB(97))\n\t\t\t}\n\n\t\t\twait.Done()\n\t\t}()\n\t}\n\n\twait.Wait()\n\n\tif int(r.Load()) != 97*subs*emits*msgs {\n\t\tt.Fatal(\"got wrong result\")\n\t}\n}\n\nfunc TestBothMany(t *testing.T) {\n\ttestMany(t, 10000, 100, 10, false)\n}\n\ntype benchCase struct {\n\tsubs     int\n\temits    int\n\tstateful bool\n}\n\nfunc (bc benchCase) name() string {\n\treturn fmt.Sprintf(\"subs-%03d/emits-%03d/stateful-%t\", bc.subs, bc.emits, bc.stateful)\n}\n\nfunc genTestCases() []benchCase {\n\tret := make([]benchCase, 0, 200)\n\tfor stateful := range 2 {\n\t\tfor subs := uint(0); subs <= 8; subs = subs + 4 {\n\t\t\tfor emits := uint(0); emits <= 8; emits = emits + 4 {\n\t\t\t\tret = append(ret, benchCase{1 << subs, 1 << emits, stateful == 1})\n\t\t\t}\n\t\t}\n\t}\n\treturn ret\n}\n\nfunc BenchmarkEvents(b *testing.B) {\n\tfor _, bc := range genTestCases() {\n\t\tb.Run(bc.name(), benchMany(bc))\n\t}\n}\n\nfunc benchMany(bc benchCase) func(*testing.B) {\n\treturn func(b *testing.B) {\n\t\tb.ReportAllocs()\n\t\tsubs := bc.subs\n\t\temits := bc.emits\n\t\tstateful := bc.stateful\n\t\tbus := NewBus()\n\t\tvar wait sync.WaitGroup\n\t\tvar ready sync.WaitGroup\n\t\twait.Add(subs + emits)\n\t\tready.Add(subs + emits)\n\n\t\tfor range subs {\n\t\t\tgo func() {\n\t\t\t\tsub, err := bus.Subscribe(new(EventB))\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(err)\n\t\t\t\t}\n\t\t\t\tdefer sub.Close()\n\n\t\t\t\tready.Done()\n\t\t\t\tready.Wait()\n\t\t\t\tfor i := 0; i < (b.N/emits)*emits; i++ {\n\t\t\t\t\t_, ok := <-sub.Out()\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\tpanic(\"wat\")\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\twait.Done()\n\t\t\t}()\n\t\t}\n\n\t\tfor range emits {\n\t\t\tgo func() {\n\t\t\t\tem, err := bus.Emitter(new(EventB), func(settings any) error {\n\t\t\t\t\tsettings.(*emitterSettings).makeStateful = stateful\n\t\t\t\t\treturn nil\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(err)\n\t\t\t\t}\n\t\t\t\tdefer em.Close()\n\n\t\t\t\tready.Done()\n\t\t\t\tready.Wait()\n\n\t\t\t\tfor i := 0; i < b.N/emits; i++ {\n\t\t\t\t\tem.Emit(EventB(97))\n\t\t\t\t}\n\n\t\t\t\twait.Done()\n\t\t\t}()\n\t\t}\n\t\tready.Wait()\n\t\tb.ResetTimer()\n\t\twait.Wait()\n\t}\n}\n\nvar div = 100\n\nfunc BenchmarkSubscribe(b *testing.B) {\n\tb.ReportAllocs()\n\tfor i := 0; i < b.N/div; i++ {\n\t\tbus := NewBus()\n\t\tfor range div {\n\t\t\tbus.Subscribe(new(EventA))\n\t\t}\n\t}\n}\n\nfunc BenchmarkEmitter(b *testing.B) {\n\tb.ReportAllocs()\n\tfor i := 0; i < b.N/div; i++ {\n\t\tbus := NewBus()\n\t\tfor range div {\n\t\t\tbus.Emitter(new(EventA))\n\t\t}\n\t}\n}\n\nfunc BenchmarkSubscribeAndEmitter(b *testing.B) {\n\tb.ReportAllocs()\n\tfor i := 0; i < b.N/div; i++ {\n\t\tbus := NewBus()\n\t\tfor range div {\n\t\t\tbus.Subscribe(new(EventA))\n\t\t\tbus.Emitter(new(EventA))\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "p2p/host/eventbus/opts.go",
    "content": "package eventbus\n\nimport (\n\t\"fmt\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync/atomic\"\n)\n\ntype subSettings struct {\n\tbuffer int\n\tname   string\n}\n\nvar subCnt atomic.Int64\n\nvar subSettingsDefault = subSettings{\n\tbuffer: 16,\n}\n\n// newSubSettings returns the settings for a new subscriber\n// The default naming strategy is sub-<fileName>-L<lineNum>\nfunc newSubSettings() subSettings {\n\tsettings := subSettingsDefault\n\t_, file, line, ok := runtime.Caller(2) // skip=1 is eventbus.Subscriber\n\tif ok {\n\t\tfile = strings.TrimPrefix(file, \"github.com/\")\n\t\t// remove the version number from the path, for example\n\t\t// go-libp2p-package@v0.x.y-some-hash-123/file.go will be shortened go go-libp2p-package/file.go\n\t\tif idx1 := strings.Index(file, \"@\"); idx1 != -1 {\n\t\t\tif idx2 := strings.Index(file[idx1:], \"/\"); idx2 != -1 {\n\t\t\t\tfile = file[:idx1] + file[idx1+idx2:]\n\t\t\t}\n\t\t}\n\t\tsettings.name = fmt.Sprintf(\"%s-L%d\", file, line)\n\t} else {\n\t\tsettings.name = fmt.Sprintf(\"subscriber-%d\", subCnt.Add(1))\n\t}\n\treturn settings\n}\n\nfunc BufSize(n int) func(any) error {\n\treturn func(s any) error {\n\t\ts.(*subSettings).buffer = n\n\t\treturn nil\n\t}\n}\n\nfunc Name(name string) func(any) error {\n\treturn func(s any) error {\n\t\ts.(*subSettings).name = name\n\t\treturn nil\n\t}\n}\n\ntype emitterSettings struct {\n\tmakeStateful bool\n}\n\n// Stateful is an Emitter option which makes the eventbus channel\n// 'remember' last event sent, and when a new subscriber joins the\n// bus, the remembered event is immediately sent to the subscription\n// channel.\n//\n// This allows to provide state tracking for dynamic systems, and/or\n// allows new subscribers to verify that there are Emitters on the channel\nfunc Stateful(s any) error {\n\ts.(*emitterSettings).makeStateful = true\n\treturn nil\n}\n\ntype Option func(*basicBus)\n\nfunc WithMetricsTracer(metricsTracer MetricsTracer) Option {\n\treturn func(bus *basicBus) {\n\t\tbus.metricsTracer = metricsTracer\n\t\tbus.wildcard.metricsTracer = metricsTracer\n\t}\n}\n"
  },
  {
    "path": "p2p/host/observedaddrs/manager.go",
    "content": "package observedaddrs\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net\"\n\t\"slices\"\n\t\"sort\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/event\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n\tbasichost \"github.com/libp2p/go-libp2p/p2p/host/basic\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/eventbus\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\nvar log = logging.Logger(\"observedaddrs\")\n\n// ActivationThresh is the minimum number of observers required for an observed address\n// to be considered valid. We may not advertise this address even if we have these many\n// observations if better observed addresses are available.\nvar ActivationThresh = 4\n\nvar (\n\t// observedAddrManagerWorkerChannelSize defines how many addresses can be enqueued\n\t// for adding to an ObservedAddrManager.\n\tobservedAddrManagerWorkerChannelSize = 16\n\t// natTypeChangeTickrInterval is the interval between two nat device change events.\n\t//\n\t// Computing the NAT type is expensive and the information in the event is not too\n\t// useful, so this interval is long.\n\tnatTypeChangeTickrInterval = 1 * time.Minute\n)\n\nconst maxExternalThinWaistAddrsPerLocalAddr = 3\n\n// thinWaist is a struct that stores the address along with it's thin waist prefix and rest of the multiaddr\ntype thinWaist struct {\n\tAddr, TW, Rest ma.Multiaddr\n}\n\nvar errTW = errors.New(\"not a thinwaist address\")\n\nfunc thinWaistForm(a ma.Multiaddr) (thinWaist, error) {\n\tif len(a) < 2 {\n\t\treturn thinWaist{}, errTW\n\t}\n\tif c0, c1 := a[0].Code(), a[1].Code(); (c0 != ma.P_IP4 && c0 != ma.P_IP6) || (c1 != ma.P_TCP && c1 != ma.P_UDP) {\n\t\treturn thinWaist{}, errTW\n\t}\n\treturn thinWaist{Addr: a, TW: a[:2], Rest: a[2:]}, nil\n}\n\n// getObserver returns the observer for the multiaddress\n// For an IPv4 multiaddress the observer is the IP address\n// For an IPv6 multiaddress the observer is the first /56 prefix of the IP address\nfunc getObserver(a ma.Multiaddr) (string, error) {\n\tip, err := manet.ToIP(a)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif ip4 := ip.To4(); ip4 != nil {\n\t\treturn ip4.String(), nil\n\t}\n\t// Count /56 prefix as a single observer.\n\treturn ip.Mask(net.CIDRMask(56, 128)).String(), nil\n}\n\n// connMultiaddrs provides IsClosed along with network.ConnMultiaddrs. It is easier to mock this than network.Conn\ntype connMultiaddrs interface {\n\tnetwork.ConnMultiaddrs\n\tIsClosed() bool\n}\n\n// observerSetCacheSize is the number of transport sharing the same thinwaist (tcp, ws, wss), (quic, webtransport, webrtc-direct)\n// This is 3 in practice right now, but keep a buffer of few extra elements\nconst observerSetCacheSize = 10\n\n// observerSet is the set of observers who have observed ThinWaistAddr\ntype observerSet struct {\n\tObservedTWAddr ma.Multiaddr\n\tObservedBy     map[string]int\n\n\tmu               sync.RWMutex            // protects following\n\tcachedMultiaddrs map[string]ma.Multiaddr // cache of localMultiaddr rest(addr - thinwaist) => output multiaddr\n}\n\nfunc (s *observerSet) cacheMultiaddr(addr ma.Multiaddr) ma.Multiaddr {\n\tif addr == nil {\n\t\treturn s.ObservedTWAddr\n\t}\n\taddrStr := string(addr.Bytes())\n\ts.mu.RLock()\n\tres, ok := s.cachedMultiaddrs[addrStr]\n\ts.mu.RUnlock()\n\tif ok {\n\t\treturn res\n\t}\n\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\t// Check if some other go routine added this while we were waiting\n\tres, ok = s.cachedMultiaddrs[addrStr]\n\tif ok {\n\t\treturn res\n\t}\n\tif s.cachedMultiaddrs == nil {\n\t\ts.cachedMultiaddrs = make(map[string]ma.Multiaddr, observerSetCacheSize)\n\t}\n\tif len(s.cachedMultiaddrs) == observerSetCacheSize {\n\t\t// remove one entry if we will go over the limit\n\t\tfor k := range s.cachedMultiaddrs {\n\t\t\tdelete(s.cachedMultiaddrs, k)\n\t\t\tbreak\n\t\t}\n\t}\n\ts.cachedMultiaddrs[addrStr] = ma.Join(s.ObservedTWAddr, addr)\n\treturn s.cachedMultiaddrs[addrStr]\n}\n\ntype observation struct {\n\tconn     connMultiaddrs\n\tobserved ma.Multiaddr\n}\n\n// Manager maps connection's local multiaddrs to their externally observable multiaddress\ntype Manager struct {\n\t// Our listen addrs\n\tlistenAddrs func() []ma.Multiaddr\n\t// worker channel for new observations\n\twch chan observation\n\t// eventbus for identify observations\n\teventbus event.Bus\n\n\t// for closing\n\twg         sync.WaitGroup\n\tctx        context.Context\n\tctxCancel  context.CancelFunc\n\tstopNotify func()\n\n\tmu sync.RWMutex\n\t// local thin waist => external thin waist => observerSet\n\texternalAddrs map[string]map[string]*observerSet\n\t// connObservedTWAddrs maps the connection to the last observed thin waist multiaddr on that connection\n\tconnObservedTWAddrs map[connMultiaddrs]ma.Multiaddr\n}\n\nvar _ basichost.ObservedAddrsManager = (*Manager)(nil)\n\n// NewManager returns a new manager using peerstore.OwnObservedAddressTTL as the TTL.\nfunc NewManager(eventbus event.Bus, net network.Network) (*Manager, error) {\n\tlistenAddrs := func() []ma.Multiaddr {\n\t\tla := net.ListenAddresses()\n\t\tila, err := net.InterfaceListenAddresses()\n\t\tif err != nil {\n\t\t\tlog.Warn(\"error getting interface listen addresses\", \"err\", err)\n\t\t}\n\t\treturn append(la, ila...)\n\t}\n\to, err := newManagerWithListenAddrs(eventbus, listenAddrs)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn o, nil\n}\n\n// newManagerWithListenAddrs uses the listenAddrs directly to simplify creation in tests.\nfunc newManagerWithListenAddrs(bus event.Bus, listenAddrs func() []ma.Multiaddr) (*Manager, error) {\n\to := &Manager{\n\t\texternalAddrs:       make(map[string]map[string]*observerSet),\n\t\tconnObservedTWAddrs: make(map[connMultiaddrs]ma.Multiaddr),\n\t\twch:                 make(chan observation, observedAddrManagerWorkerChannelSize),\n\t\tlistenAddrs:         listenAddrs,\n\t\teventbus:            bus,\n\t\tstopNotify:          func() {},\n\t}\n\to.ctx, o.ctxCancel = context.WithCancel(context.Background())\n\treturn o, nil\n}\n\n// Start tracking addrs\nfunc (o *Manager) Start(n network.Network) {\n\tnb := &network.NotifyBundle{\n\t\tDisconnectedF: func(_ network.Network, c network.Conn) {\n\t\t\to.removeConn(c)\n\t\t},\n\t}\n\n\tsub, err := o.eventbus.Subscribe(new(event.EvtPeerIdentificationCompleted), eventbus.Name(\"observed-addrs-manager\"))\n\tif err != nil {\n\t\tlog.Error(\"failed to start observed addrs manager: identify subscription failed.\", \"err\", err)\n\t\treturn\n\t}\n\temitter, err := o.eventbus.Emitter(new(event.EvtNATDeviceTypeChanged), eventbus.Stateful)\n\tif err != nil {\n\t\tlog.Error(\"failed to start observed addrs manager: nat device type changed emitter error.\", \"err\", err)\n\t\tsub.Close()\n\t\treturn\n\t}\n\n\tn.Notify(nb)\n\to.stopNotify = func() {\n\t\tn.StopNotify(nb)\n\t}\n\n\to.wg.Add(2)\n\tgo o.eventHandler(sub, emitter)\n\tgo o.worker()\n}\n\n// AddrsFor return all activated observed addresses associated with the given\n// (resolved) listen address.\nfunc (o *Manager) AddrsFor(addr ma.Multiaddr) (addrs []ma.Multiaddr) {\n\tif addr == nil {\n\t\treturn nil\n\t}\n\to.mu.RLock()\n\tdefer o.mu.RUnlock()\n\ttw, err := thinWaistForm(addr)\n\tif err != nil {\n\t\treturn nil\n\t}\n\n\tobserverSets := o.getTopExternalAddrs(string(tw.TW.Bytes()), ActivationThresh)\n\tres := make([]ma.Multiaddr, 0, len(observerSets))\n\tfor _, s := range observerSets {\n\t\tres = append(res, s.cacheMultiaddr(tw.Rest))\n\t}\n\treturn res\n}\n\n// appendInferredAddrs infers the external address of addresses for the addresses\n// that we are listening on using the thin waist mapping.\n//\n// e.g. If we have observations for a QUIC address on port 9000, and we are\n// listening on the same interface and port 9000 for WebTransport, we can infer\n// the external WebTransport address.\nfunc (o *Manager) appendInferredAddrs(twToObserverSets map[string][]*observerSet, addrs []ma.Multiaddr) []ma.Multiaddr {\n\tif twToObserverSets == nil {\n\t\ttwToObserverSets = make(map[string][]*observerSet)\n\t\tfor localTWStr := range o.externalAddrs {\n\t\t\ttwToObserverSets[localTWStr] = append(twToObserverSets[localTWStr], o.getTopExternalAddrs(localTWStr, ActivationThresh)...)\n\t\t}\n\t}\n\tlAddrs := o.listenAddrs()\n\tseenTWs := make(map[string]struct{})\n\tfor _, a := range lAddrs {\n\t\tif _, ok := seenTWs[string(a.Bytes())]; ok {\n\t\t\t// We've already added this\n\t\t\tcontinue\n\t\t}\n\t\tseenTWs[string(a.Bytes())] = struct{}{}\n\t\tt, err := thinWaistForm(a)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, s := range twToObserverSets[string(t.TW.Bytes())] {\n\t\t\taddrs = append(addrs, s.cacheMultiaddr(t.Rest))\n\t\t}\n\t}\n\treturn addrs\n}\n\n// Addrs return all observed addresses with at least minObservers observers\n// If minObservers <= 0, it will return all addresses with at least ActivationThresh observers.\nfunc (o *Manager) Addrs(minObservers int) []ma.Multiaddr {\n\to.mu.RLock()\n\tdefer o.mu.RUnlock()\n\n\tif minObservers <= 0 {\n\t\tminObservers = ActivationThresh\n\t}\n\n\tm := make(map[string][]*observerSet)\n\tfor localTWStr := range o.externalAddrs {\n\t\tm[localTWStr] = append(m[localTWStr], o.getTopExternalAddrs(localTWStr, minObservers)...)\n\t}\n\taddrs := make([]ma.Multiaddr, 0, maxExternalThinWaistAddrsPerLocalAddr*5) // assume 5 transports\n\taddrs = o.appendInferredAddrs(m, addrs)\n\treturn addrs\n}\n\nfunc (o *Manager) getTopExternalAddrs(localTWStr string, minObservers int) []*observerSet {\n\tobserverSets := make([]*observerSet, 0, len(o.externalAddrs[localTWStr]))\n\tfor _, v := range o.externalAddrs[localTWStr] {\n\t\tif len(v.ObservedBy) >= minObservers {\n\t\t\tobserverSets = append(observerSets, v)\n\t\t}\n\t}\n\tslices.SortFunc(observerSets, func(a, b *observerSet) int {\n\t\tdiff := len(b.ObservedBy) - len(a.ObservedBy)\n\t\tif diff != 0 {\n\t\t\treturn diff\n\t\t}\n\t\t// In case we have elements with equal counts,\n\t\t// keep the address list stable by using the lexicographically smaller address\n\t\treturn a.ObservedTWAddr.Compare(b.ObservedTWAddr)\n\t})\n\t// TODO(sukunrt): Improve this logic. Return only if the addresses have a\n\t// threshold fraction of the maximum observations\n\tn := min(len(observerSets), maxExternalThinWaistAddrsPerLocalAddr)\n\treturn observerSets[:n]\n}\n\nfunc (o *Manager) eventHandler(identifySub event.Subscription, natEmitter event.Emitter) {\n\tdefer o.wg.Done()\n\tnatTypeTicker := time.NewTicker(natTypeChangeTickrInterval)\n\tdefer natTypeTicker.Stop()\n\tvar udpNATType, tcpNATType network.NATDeviceType\n\tfor {\n\t\tselect {\n\t\tcase e := <-identifySub.Out():\n\t\t\tevt := e.(event.EvtPeerIdentificationCompleted)\n\t\t\tselect {\n\t\t\tcase o.wch <- observation{\n\t\t\t\tconn:     evt.Conn,\n\t\t\t\tobserved: evt.ObservedAddr,\n\t\t\t}:\n\t\t\tdefault:\n\t\t\t\tlog.Debug(\"dropping address observation due to full buffer\",\n\t\t\t\t\t\"from\", evt.Conn.RemoteMultiaddr(),\n\t\t\t\t\t\"observed\", evt.ObservedAddr,\n\t\t\t\t)\n\t\t\t}\n\t\tcase <-natTypeTicker.C:\n\t\t\tnewUDPNAT, newTCPNAT := o.getNATType()\n\t\t\tif newUDPNAT != udpNATType {\n\t\t\t\tnatEmitter.Emit(event.EvtNATDeviceTypeChanged{\n\t\t\t\t\tTransportProtocol: network.NATTransportUDP,\n\t\t\t\t\tNatDeviceType:     newUDPNAT,\n\t\t\t\t})\n\t\t\t}\n\t\t\tif newTCPNAT != tcpNATType {\n\t\t\t\tnatEmitter.Emit(event.EvtNATDeviceTypeChanged{\n\t\t\t\t\tTransportProtocol: network.NATTransportTCP,\n\t\t\t\t\tNatDeviceType:     newTCPNAT,\n\t\t\t\t})\n\t\t\t}\n\t\t\tudpNATType, tcpNATType = newUDPNAT, newTCPNAT\n\t\tcase <-o.ctx.Done():\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (o *Manager) worker() {\n\tdefer o.wg.Done()\n\tfor {\n\t\tselect {\n\t\tcase obs := <-o.wch:\n\t\t\to.maybeRecordObservation(obs.conn, obs.observed)\n\t\tcase <-o.ctx.Done():\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (o *Manager) shouldRecordObservation(conn connMultiaddrs, observed ma.Multiaddr) (shouldRecord bool, localTW thinWaist, observedTW thinWaist) {\n\tif conn == nil || observed == nil {\n\t\treturn false, thinWaist{}, thinWaist{}\n\t}\n\t// Ignore observations from loopback nodes. We already know our loopback\n\t// addresses.\n\tif manet.IsIPLoopback(observed) {\n\t\treturn false, thinWaist{}, thinWaist{}\n\t}\n\n\t// Provided by NAT64 peers, these addresses are specific to the peer and not publicly routable\n\tif manet.IsNAT64IPv4ConvertedIPv6Addr(observed) {\n\t\treturn false, thinWaist{}, thinWaist{}\n\t}\n\n\t// Ignore p2p-circuit addresses. These are the observed address of the relay.\n\t// Not useful for us.\n\tif isRelayedAddress(observed) {\n\t\treturn false, thinWaist{}, thinWaist{}\n\t}\n\n\tlocalTW, err := thinWaistForm(conn.LocalMultiaddr())\n\tif err != nil {\n\t\tlog.Info(\"failed to get interface listen addrs\", \"err\", err)\n\t\treturn false, thinWaist{}, thinWaist{}\n\t}\n\n\tlistenAddrs := o.listenAddrs()\n\tfor i, a := range listenAddrs {\n\t\ttw, err := thinWaistForm(a)\n\t\tif err != nil {\n\t\t\tlistenAddrs[i] = nil\n\t\t\tcontinue\n\t\t}\n\t\tlistenAddrs[i] = tw.TW\n\t}\n\n\tif !ma.Contains(listenAddrs, localTW.TW) {\n\t\t// not in our list\n\t\treturn false, thinWaist{}, thinWaist{}\n\t}\n\n\tobservedTW, err = thinWaistForm(observed)\n\tif err != nil {\n\t\treturn false, thinWaist{}, thinWaist{}\n\t}\n\tif !hasConsistentTransport(localTW.TW, observedTW.TW) {\n\t\tlog.Debug(\n\t\t\t\"invalid observed address for local address\",\n\t\t\t\"observed\", observed,\n\t\t\t\"local\", localTW.Addr,\n\t\t)\n\t\treturn false, thinWaist{}, thinWaist{}\n\t}\n\n\treturn true, localTW, observedTW\n}\n\nfunc (o *Manager) maybeRecordObservation(conn connMultiaddrs, observed ma.Multiaddr) {\n\tshouldRecord, localTW, observedTW := o.shouldRecordObservation(conn, observed)\n\tif !shouldRecord {\n\t\treturn\n\t}\n\tlog.Debug(\"added own observed listen addr\", \"conn\", conn, \"observed\", observed)\n\n\to.mu.Lock()\n\tdefer o.mu.Unlock()\n\to.recordObservationUnlocked(conn, localTW, observedTW)\n}\n\nfunc (o *Manager) recordObservationUnlocked(conn connMultiaddrs, localTW, observedTW thinWaist) {\n\tif conn.IsClosed() {\n\t\t// dont record if the connection is already closed. Any previous observations will be removed in\n\t\t// the disconnected callback\n\t\treturn\n\t}\n\tlocalTWStr := string(localTW.TW.Bytes())\n\tobservedTWStr := string(observedTW.TW.Bytes())\n\tobserver, err := getObserver(conn.RemoteMultiaddr())\n\tif err != nil {\n\t\treturn\n\t}\n\n\tprevObservedTWAddr, ok := o.connObservedTWAddrs[conn]\n\tif ok {\n\t\t// we have received the same observation again, nothing to do\n\t\tif prevObservedTWAddr.Equal(observedTW.TW) {\n\t\t\treturn\n\t\t}\n\t\t// if we have a previous entry remove it from externalAddrs\n\t\to.removeExternalAddrsUnlocked(observer, localTWStr, string(prevObservedTWAddr.Bytes()))\n\t}\n\to.connObservedTWAddrs[conn] = observedTW.TW\n\to.addExternalAddrsUnlocked(observedTW.TW, observer, localTWStr, observedTWStr)\n}\n\nfunc (o *Manager) removeExternalAddrsUnlocked(observer, localTWStr, observedTWStr string) {\n\ts, ok := o.externalAddrs[localTWStr][observedTWStr]\n\tif !ok {\n\t\treturn\n\t}\n\ts.ObservedBy[observer]--\n\tif s.ObservedBy[observer] <= 0 {\n\t\tdelete(s.ObservedBy, observer)\n\t}\n\tif len(s.ObservedBy) == 0 {\n\t\tdelete(o.externalAddrs[localTWStr], observedTWStr)\n\t}\n\tif len(o.externalAddrs[localTWStr]) == 0 {\n\t\tdelete(o.externalAddrs, localTWStr)\n\t}\n}\n\nfunc (o *Manager) addExternalAddrsUnlocked(observedTWAddr ma.Multiaddr, observer, localTWStr, observedTWStr string) {\n\ts, ok := o.externalAddrs[localTWStr][observedTWStr]\n\tif !ok {\n\t\ts = &observerSet{\n\t\t\tObservedTWAddr: observedTWAddr,\n\t\t\tObservedBy:     make(map[string]int),\n\t\t}\n\t\tif _, ok := o.externalAddrs[localTWStr]; !ok {\n\t\t\to.externalAddrs[localTWStr] = make(map[string]*observerSet)\n\t\t}\n\t\to.externalAddrs[localTWStr][observedTWStr] = s\n\t}\n\ts.ObservedBy[observer]++\n}\n\nfunc (o *Manager) removeConn(conn connMultiaddrs) {\n\tif conn == nil {\n\t\treturn\n\t}\n\to.mu.Lock()\n\tdefer o.mu.Unlock()\n\n\tobservedTWAddr, ok := o.connObservedTWAddrs[conn]\n\tif !ok {\n\t\treturn\n\t}\n\tdelete(o.connObservedTWAddrs, conn)\n\n\tlocalTW, err := thinWaistForm(conn.LocalMultiaddr())\n\tif err != nil {\n\t\treturn\n\t}\n\n\tobserver, err := getObserver(conn.RemoteMultiaddr())\n\tif err != nil {\n\t\treturn\n\t}\n\n\to.removeExternalAddrsUnlocked(observer, string(localTW.TW.Bytes()), string(observedTWAddr.Bytes()))\n}\n\nfunc (o *Manager) getNATType() (tcpNATType, udpNATType network.NATDeviceType) {\n\to.mu.RLock()\n\tdefer o.mu.RUnlock()\n\n\tvar tcpCounts, udpCounts []int\n\tvar tcpTotal, udpTotal int\n\tfor _, m := range o.externalAddrs {\n\t\tisTCP := false\n\t\tfor _, v := range m {\n\t\t\tfor _, c := range v.ObservedTWAddr {\n\t\t\t\tif c.Code() == ma.P_TCP {\n\t\t\t\t\tisTCP = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfor _, v := range m {\n\t\t\tif isTCP {\n\t\t\t\ttcpCounts = append(tcpCounts, len(v.ObservedBy))\n\t\t\t\ttcpTotal += len(v.ObservedBy)\n\t\t\t} else {\n\t\t\t\tudpCounts = append(udpCounts, len(v.ObservedBy))\n\t\t\t\tudpTotal += len(v.ObservedBy)\n\t\t\t}\n\t\t}\n\t}\n\n\tsort.Sort(sort.Reverse(sort.IntSlice(tcpCounts)))\n\tsort.Sort(sort.Reverse(sort.IntSlice(udpCounts)))\n\n\ttcpTopCounts, udpTopCounts := 0, 0\n\tfor i := 0; i < maxExternalThinWaistAddrsPerLocalAddr && i < len(tcpCounts); i++ {\n\t\ttcpTopCounts += tcpCounts[i]\n\t}\n\tfor i := 0; i < maxExternalThinWaistAddrsPerLocalAddr && i < len(udpCounts); i++ {\n\t\tudpTopCounts += udpCounts[i]\n\t}\n\n\t// If the top elements cover more than 1/2 of all the observations, there's a > 50% chance that\n\t// hole punching based on outputs of observed address manager will succeed\n\t//\n\t// The `3*maxExternalThinWaistAddrsPerLocalAddr` is a magic number, we just want sufficient\n\t// observations to decide about NAT Type\n\tif tcpTotal >= 3*maxExternalThinWaistAddrsPerLocalAddr {\n\t\tif tcpTopCounts >= tcpTotal/2 {\n\t\t\ttcpNATType = network.NATDeviceTypeEndpointIndependent\n\t\t} else {\n\t\t\ttcpNATType = network.NATDeviceTypeEndpointDependent\n\t\t}\n\t}\n\tif udpTotal >= 3*maxExternalThinWaistAddrsPerLocalAddr {\n\t\tif udpTopCounts >= udpTotal/2 {\n\t\t\tudpNATType = network.NATDeviceTypeEndpointIndependent\n\t\t} else {\n\t\t\tudpNATType = network.NATDeviceTypeEndpointDependent\n\t\t}\n\t}\n\treturn\n}\n\nfunc (o *Manager) Close() error {\n\to.stopNotify()\n\to.ctxCancel()\n\to.wg.Wait()\n\treturn nil\n}\n\n// hasConsistentTransport returns true if the thin waist address `aTW` shares the same\n// protocols with `bTW`\nfunc hasConsistentTransport(aTW, bTW ma.Multiaddr) bool {\n\tif len(aTW) != len(bTW) {\n\t\treturn false\n\t}\n\tfor i, a := range aTW {\n\t\tif bTW[i].Code() != a.Code() {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc isRelayedAddress(a ma.Multiaddr) bool {\n\tfor _, c := range a {\n\t\tif c.Code() == ma.P_CIRCUIT {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "p2p/host/observedaddrs/manager_glass_test.go",
    "content": "package observedaddrs\n\n// This test lives in the identify package, not the identify_test package, so it\n// can access internal types.\n\nimport (\n\t\"fmt\"\n\t\"sync/atomic\"\n\t\"testing\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/multiformats/go-multiaddr/matest\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype mockConn struct {\n\tlocal, remote ma.Multiaddr\n\tisClosed      atomic.Bool\n}\n\n// LocalMultiaddr implements connMultiaddrProvider\nfunc (c *mockConn) LocalMultiaddr() ma.Multiaddr {\n\treturn c.local\n}\n\n// RemoteMultiaddr implements connMultiaddrProvider\nfunc (c *mockConn) RemoteMultiaddr() ma.Multiaddr {\n\treturn c.remote\n}\n\nfunc (c *mockConn) Close() {\n\tc.isClosed.Store(true)\n}\n\nfunc (c *mockConn) IsClosed() bool {\n\treturn c.isClosed.Load()\n}\n\nfunc TestShouldRecordObservationWithWebTransport(t *testing.T) {\n\tlistenAddr := ma.StringCast(\"/ip4/0.0.0.0/udp/0/quic-v1/webtransport/certhash/uEgNmb28\")\n\tlistenAddrs := func() []ma.Multiaddr { return []ma.Multiaddr{listenAddr} }\n\n\tc := &mockConn{\n\t\tlocal:  listenAddr,\n\t\tremote: ma.StringCast(\"/ip4/1.2.3.6/udp/1236/quic-v1/webtransport\"),\n\t}\n\tobservedAddr := ma.StringCast(\"/ip4/1.2.3.4/udp/1231/quic-v1/webtransport\")\n\to, err := newManagerWithListenAddrs(nil, listenAddrs)\n\trequire.NoError(t, err)\n\tshouldRecord, _, _ := o.shouldRecordObservation(c, observedAddr)\n\trequire.True(t, shouldRecord)\n}\n\nfunc TestShouldNotRecordObservationWithRelayedAddr(t *testing.T) {\n\tlistenAddr := ma.StringCast(\"/ip4/1.2.3.4/udp/8888/quic-v1/p2p-circuit\")\n\tlistenAddrs := func() []ma.Multiaddr { return []ma.Multiaddr{listenAddr} }\n\n\tc := &mockConn{\n\t\tlocal:  listenAddr,\n\t\tremote: ma.StringCast(\"/ip4/1.2.3.6/udp/1236/quic-v1/p2p-circuit\"),\n\t}\n\tobservedAddr := ma.StringCast(\"/ip4/1.2.3.4/udp/1231/quic-v1/p2p-circuit\")\n\to, err := newManagerWithListenAddrs(nil, listenAddrs)\n\trequire.NoError(t, err)\n\tshouldRecord, _, _ := o.shouldRecordObservation(c, observedAddr)\n\trequire.False(t, shouldRecord)\n}\n\nfunc TestShouldRecordObservationWithNAT64Addr(t *testing.T) {\n\tlistenAddr1 := ma.StringCast(\"/ip4/0.0.0.0/tcp/1234\")\n\tlistenAddr2 := ma.StringCast(\"/ip6/::/tcp/1234\")\n\tlistenAddrs := func() []ma.Multiaddr { return []ma.Multiaddr{listenAddr1, listenAddr2} }\n\tc4 := &mockConn{\n\t\tlocal:  listenAddr1,\n\t\tremote: ma.StringCast(\"/ip4/1.2.3.6/tcp/4321\"),\n\t}\n\tc6 := &mockConn{\n\t\tlocal:  listenAddr2,\n\t\tremote: ma.StringCast(\"/ip6/1::4/tcp/4321\"),\n\t}\n\n\tcases := []struct {\n\t\taddr          ma.Multiaddr\n\t\twant          bool\n\t\tconn          *mockConn\n\t\tfailureReason string\n\t}{\n\t\t{\n\t\t\taddr:          ma.StringCast(\"/ip4/1.2.3.4/tcp/1234\"),\n\t\t\twant:          true,\n\t\t\tfailureReason: \"IPv4 should be observed\",\n\t\t\tconn:          c4,\n\t\t},\n\t\t{\n\t\t\taddr:          ma.StringCast(\"/ip6/1::4/tcp/1234\"),\n\t\t\twant:          true,\n\t\t\tfailureReason: \"public IPv6 address should be observed\",\n\t\t\tconn:          c6,\n\t\t},\n\t\t{\n\t\t\taddr:          ma.StringCast(\"/ip6/64:ff9b::192.0.1.2/tcp/1234\"),\n\t\t\twant:          false,\n\t\t\tfailureReason: \"NAT64 IPv6 address shouldn't be observed\",\n\t\t\tconn:          c6,\n\t\t},\n\t}\n\n\to, err := newManagerWithListenAddrs(nil, listenAddrs)\n\trequire.NoError(t, err)\n\tfor i, tc := range cases {\n\t\tt.Run(fmt.Sprintf(\"%d\", i), func(t *testing.T) {\n\t\t\tif shouldRecord, _, _ := o.shouldRecordObservation(tc.conn, tc.addr); shouldRecord != tc.want {\n\t\t\t\tt.Fatalf(\"%s %s\", tc.addr, tc.failureReason)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestThinWaistForm(t *testing.T) {\n\ttc := []struct {\n\t\tinput string\n\t\ttw    string\n\t\trest  string\n\t\terr   bool\n\t}{{\n\t\tinput: \"/ip4/1.2.3.4/tcp/1\",\n\t\ttw:    \"/ip4/1.2.3.4/tcp/1\",\n\t\trest:  \"\",\n\t}, {\n\t\tinput: \"/ip4/1.2.3.4/tcp/1/ws\",\n\t\ttw:    \"/ip4/1.2.3.4/tcp/1\",\n\t\trest:  \"/ws\",\n\t}, {\n\t\tinput: \"/ip4/127.0.0.1/udp/1/quic-v1\",\n\t\ttw:    \"/ip4/127.0.0.1/udp/1\",\n\t\trest:  \"/quic-v1\",\n\t}, {\n\t\tinput: \"/ip4/1.2.3.4/udp/1/quic-v1/webtransport\",\n\t\ttw:    \"/ip4/1.2.3.4/udp/1\",\n\t\trest:  \"/quic-v1/webtransport\",\n\t}, {\n\t\tinput: \"/ip4/1.2.3.4/\",\n\t\terr:   true,\n\t}, {\n\t\tinput: \"/tcp/1\",\n\t\terr:   true,\n\t}, {\n\t\tinput: \"/ip6/::1/tcp/1\",\n\t\ttw:    \"/ip6/::1/tcp/1\",\n\t\trest:  \"\",\n\t}}\n\tfor i, tt := range tc {\n\t\tt.Run(fmt.Sprintf(\"%d\", i), func(t *testing.T) {\n\t\t\tinputAddr := ma.StringCast(tt.input)\n\t\t\ttw, err := thinWaistForm(inputAddr)\n\t\t\tif tt.err {\n\t\t\t\trequire.Equal(t, tw, thinWaist{})\n\t\t\t\trequire.Error(t, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\twantTW := ma.StringCast(tt.tw)\n\t\t\tvar restTW ma.Multiaddr\n\t\t\tif tt.rest != \"\" {\n\t\t\t\trestTW = ma.StringCast(tt.rest)\n\t\t\t}\n\t\t\tmatest.AssertEqualMultiaddr(t, inputAddr, tw.Addr)\n\t\t\tmatest.AssertEqualMultiaddr(t, wantTW, tw.TW)\n\t\t\tmatest.AssertEqualMultiaddr(t, restTW, tw.Rest)\n\t\t})\n\t}\n\n}\n"
  },
  {
    "path": "p2p/host/observedaddrs/manager_test.go",
    "content": "package observedaddrs\n\nimport (\n\tcrand \"crypto/rand\"\n\t\"fmt\"\n\t\"net\"\n\t\"slices\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/eventbus\"\n\n\tswarmt \"github.com/libp2p/go-libp2p/p2p/net/swarm/testing\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmatest \"github.com/multiformats/go-multiaddr/matest\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc requireAddrsMatch(t *testing.T, a, b []ma.Multiaddr) {\n\tt.Helper()\n\tslices.SortFunc(a, func(a, b ma.Multiaddr) int { return a.Compare(b) })\n\tslices.SortFunc(b, func(a, b ma.Multiaddr) int { return a.Compare(b) })\n\trequireEqualAddrs(t, a, b)\n}\n\nfunc requireEqualAddrs(t *testing.T, a, b []ma.Multiaddr) {\n\tt.Helper()\n\tif len(a) != len(b) {\n\t\tt.Fatalf(\"length mismatch: %d != %d\", len(a), len(b))\n\t}\n\tfor i, addr := range a {\n\t\tif !addr.Equal(b[i]) {\n\t\t\tt.Fatalf(\"addr mismatch: %s != %s\", addr, b[i])\n\t\t}\n\t}\n}\n\nfunc newConn(local, remote ma.Multiaddr) *mockConn {\n\treturn &mockConn{local: local, remote: remote}\n}\n\nfunc TestObservedAddrsManager(t *testing.T) {\n\ttcp4ListenAddr := ma.StringCast(\"/ip4/192.168.1.100/tcp/1\")\n\tquic4ListenAddr := ma.StringCast(\"/ip4/0.0.0.0/udp/1/quic-v1\")\n\twebTransport4ListenAddr := ma.StringCast(\"/ip4/0.0.0.0/udp/1/quic-v1/webtransport/certhash/uEgNmb28\")\n\ttcp6ListenAddr := ma.StringCast(\"/ip6/2004::1/tcp/1\")\n\tquic6ListenAddr := ma.StringCast(\"/ip6/::/udp/1/quic-v1\")\n\twebTransport6ListenAddr := ma.StringCast(\"/ip6/::/udp/1/quic-v1/webtransport/certhash/uEgNmb28\")\n\tnewObservedAddrMgr := func() *Manager {\n\t\tlistenAddrsFunc := func() []ma.Multiaddr {\n\t\t\treturn []ma.Multiaddr{\n\t\t\t\ttcp4ListenAddr, quic4ListenAddr, webTransport4ListenAddr, tcp6ListenAddr, quic6ListenAddr, webTransport6ListenAddr,\n\t\t\t}\n\t\t}\n\t\teb := eventbus.NewBus()\n\t\to, err := newManagerWithListenAddrs(eb, listenAddrsFunc)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\ts := swarmt.GenSwarm(t)\n\t\to.Start(s)\n\t\tt.Cleanup(func() { o.Close() })\n\t\treturn o\n\t}\n\n\tcheckAllEntriesRemoved := func(o *Manager) bool {\n\t\treturn len(o.Addrs(0)) == 0 && len(o.externalAddrs) == 0 && len(o.connObservedTWAddrs) == 0\n\t}\n\n\tgetConns := func(t *testing.T, n int, protocolCode int) []*mockConn {\n\t\tt.Helper()\n\t\tlocalAddrMap := map[int]ma.Multiaddr{\n\t\t\tma.P_TCP:          tcp4ListenAddr,\n\t\t\tma.P_QUIC_V1:      quic4ListenAddr,\n\t\t\tma.P_WEBTRANSPORT: webTransport4ListenAddr,\n\t\t}\n\t\tprotoPartMap := map[int]ma.Multiaddr{\n\t\t\tma.P_TCP:          ma.StringCast(\"/tcp/1\"),\n\t\t\tma.P_QUIC_V1:      ma.StringCast(\"/udp/1/quic-v1\"),\n\t\t\tma.P_WEBTRANSPORT: ma.StringCast(\"/udp/1/quic-v1/webtransport\"),\n\t\t}\n\n\t\tlocalAddr, ok := localAddrMap[protocolCode]\n\t\tif !ok {\n\t\t\tt.Fatalf(\"unknown protocol code: %d\", protocolCode)\n\t\t}\n\t\tprotoPart, ok := protoPartMap[protocolCode]\n\t\tif !ok {\n\t\t\tt.Fatalf(\"unknown protocol code: %d\", protocolCode)\n\t\t}\n\n\t\tconns := make([]*mockConn, 0, n)\n\t\tfor i := range n {\n\t\t\tipPart := ma.StringCast(fmt.Sprintf(\"/ip4/1.2.3.%d\", i))\n\t\t\tconns = append(conns, newConn(localAddr, ma.Join(ipPart, protoPart)))\n\t\t}\n\t\treturn conns\n\t}\n\n\tt.Run(\"Single Observation\", func(t *testing.T) {\n\t\to := newObservedAddrMgr()\n\t\tdefer o.Close()\n\t\tobserved := ma.StringCast(\"/ip4/2.2.2.2/tcp/2\")\n\t\tc1 := newConn(tcp4ListenAddr, ma.StringCast(\"/ip4/1.2.3.1/tcp/1\"))\n\t\tc2 := newConn(tcp4ListenAddr, ma.StringCast(\"/ip4/1.2.3.2/tcp/1\"))\n\t\tc3 := newConn(tcp4ListenAddr, ma.StringCast(\"/ip4/1.2.3.3/tcp/1\"))\n\t\tc4 := newConn(tcp4ListenAddr, ma.StringCast(\"/ip4/1.2.3.4/tcp/1\"))\n\t\to.maybeRecordObservation(c1, observed)\n\t\to.maybeRecordObservation(c2, observed)\n\t\to.maybeRecordObservation(c3, observed)\n\t\to.maybeRecordObservation(c4, observed)\n\t\trequire.Eventually(t, func() bool {\n\t\t\treturn matest.AssertEqualMultiaddrs(t, o.Addrs(0), []ma.Multiaddr{observed})\n\t\t}, 1*time.Second, 100*time.Millisecond)\n\t\to.removeConn(c1)\n\t\to.removeConn(c2)\n\t\to.removeConn(c3)\n\t\to.removeConn(c4)\n\t\trequire.Eventually(t, func() bool {\n\t\t\treturn checkAllEntriesRemoved(o)\n\t\t}, 1*time.Second, 100*time.Millisecond)\n\t})\n\n\tt.Run(\"many observed addrs output size limited\", func(t *testing.T) {\n\t\to := newObservedAddrMgr()\n\t\tdefer o.Close()\n\t\tconns := getConns(t, 40, ma.P_TCP)\n\t\tobservedAddrs := make([]ma.Multiaddr, maxExternalThinWaistAddrsPerLocalAddr*2)\n\t\tfor i := range observedAddrs {\n\t\t\tobservedAddrs[i] = ma.StringCast(fmt.Sprintf(\"/ip4/2.2.2.%d/tcp/2\", i))\n\t\t}\n\t\tfor i, c := range conns {\n\t\t\t// avoid the async nature of Record\n\t\t\to.maybeRecordObservation(c, observedAddrs[i%len(observedAddrs)])\n\t\t}\n\t\trequire.Eventually(t, func() bool {\n\t\t\treturn len(o.Addrs(ActivationThresh)) == maxExternalThinWaistAddrsPerLocalAddr &&\n\t\t\t\tlen(o.AddrsFor(tcp4ListenAddr)) == maxExternalThinWaistAddrsPerLocalAddr\n\t\t}, 1*time.Second, 100*time.Millisecond)\n\t\tfor _, c := range conns {\n\t\t\to.removeConn(c)\n\t\t}\n\t\trequire.Eventually(t, func() bool {\n\t\t\treturn checkAllEntriesRemoved(o)\n\t\t}, 1*time.Second, 100*time.Millisecond)\n\t})\n\n\tt.Run(\"WebTransport inferred from QUIC\", func(t *testing.T) {\n\t\to := newObservedAddrMgr()\n\t\tdefer o.Close()\n\t\tobservedQuic := ma.StringCast(\"/ip4/2.2.2.2/udp/2/quic-v1\")\n\t\tobservedWebTransport := ma.StringCast(\"/ip4/2.2.2.2/udp/2/quic-v1/webtransport/certhash/uEgNmb28\")\n\t\tc1 := newConn(quic4ListenAddr, ma.StringCast(\"/ip4/1.2.3.1/udp/1/quic-v1\"))\n\t\tc2 := newConn(quic4ListenAddr, ma.StringCast(\"/ip4/1.2.3.2/udp/1/quic-v1\"))\n\t\tc3 := newConn(webTransport4ListenAddr, ma.StringCast(\"/ip4/1.2.3.3/udp/1/quic-v1/webtransport\"))\n\t\tc4 := newConn(webTransport4ListenAddr, ma.StringCast(\"/ip4/1.2.3.4/udp/1/quic-v1/webtransport\"))\n\t\to.maybeRecordObservation(c1, observedQuic)\n\t\to.maybeRecordObservation(c2, observedQuic)\n\t\to.maybeRecordObservation(c3, observedWebTransport)\n\t\to.maybeRecordObservation(c4, observedWebTransport)\n\t\trequire.EventuallyWithT(t, func(t *assert.CollectT) {\n\t\t\tmatest.AssertEqualMultiaddrs(t, o.Addrs(0), []ma.Multiaddr{observedQuic, observedWebTransport})\n\t\t}, 1*time.Second, 100*time.Millisecond)\n\t\to.removeConn(c1)\n\t\to.removeConn(c2)\n\t\to.removeConn(c3)\n\t\to.removeConn(c4)\n\t\trequire.Eventually(t, func() bool {\n\t\t\treturn checkAllEntriesRemoved(o)\n\t\t}, 1*time.Second, 100*time.Millisecond)\n\t})\n\n\tt.Run(\"WebTransport inferred from QUIC, with no WebTransport connections\", func(t *testing.T) {\n\t\to := newObservedAddrMgr()\n\t\tdefer o.Close()\n\t\tobservedQuic := ma.StringCast(\"/ip4/2.2.2.2/udp/2/quic-v1\")\n\t\tinferredWebTransport := ma.StringCast(\"/ip4/2.2.2.2/udp/2/quic-v1/webtransport/certhash/uEgNmb28\")\n\t\tc1 := newConn(quic4ListenAddr, ma.StringCast(\"/ip4/1.2.3.1/udp/1/quic-v1\"))\n\t\tc2 := newConn(quic4ListenAddr, ma.StringCast(\"/ip4/1.2.3.2/udp/1/quic-v1\"))\n\t\tc3 := newConn(quic4ListenAddr, ma.StringCast(\"/ip4/1.2.3.3/udp/1/quic-v1\"))\n\t\tc4 := newConn(quic4ListenAddr, ma.StringCast(\"/ip4/1.2.3.4/udp/1/quic-v1\"))\n\t\to.maybeRecordObservation(c1, observedQuic)\n\t\to.maybeRecordObservation(c2, observedQuic)\n\t\to.maybeRecordObservation(c3, observedQuic)\n\t\to.maybeRecordObservation(c4, observedQuic)\n\t\trequire.EventuallyWithT(t, func(t *assert.CollectT) {\n\t\t\tmatest.AssertEqualMultiaddrs(t, o.Addrs(0), []ma.Multiaddr{observedQuic, inferredWebTransport})\n\t\t}, 1*time.Second, 100*time.Millisecond)\n\t\to.removeConn(c1)\n\t\to.removeConn(c2)\n\t\to.removeConn(c3)\n\t\to.removeConn(c4)\n\t\trequire.Eventually(t, func() bool {\n\t\t\treturn checkAllEntriesRemoved(o)\n\t\t}, 1*time.Second, 100*time.Millisecond)\n\t})\n\n\tt.Run(\"SameObservers\", func(t *testing.T) {\n\t\to := newObservedAddrMgr()\n\t\tdefer o.Close()\n\n\t\tobservedQuic := ma.StringCast(\"/ip4/2.2.2.2/udp/2/quic-v1\")\n\t\tinferredWebTransport := ma.StringCast(\"/ip4/2.2.2.2/udp/2/quic-v1/webtransport/certhash/uEgNmb28\")\n\n\t\tconst N = 4 // ActivationThresh\n\t\tvar ob1, ob2 [N]connMultiaddrs\n\t\tfor i := range N {\n\t\t\tob1[i] = newConn(quic4ListenAddr, ma.StringCast(fmt.Sprintf(\"/ip4/1.2.3.%d/udp/1/quic-v1\", i)))\n\t\t\tob2[i] = newConn(quic4ListenAddr, ma.StringCast(fmt.Sprintf(\"/ip4/1.2.3.%d/udp/2/quic-v1\", i)))\n\t\t}\n\t\tfor i := range N - 1 {\n\t\t\to.maybeRecordObservation(ob1[i], observedQuic)\n\t\t\to.maybeRecordObservation(ob2[i], observedQuic)\n\t\t}\n\t\ttime.Sleep(100 * time.Millisecond)\n\t\trequire.Equal(t, o.Addrs(0), []ma.Multiaddr{})\n\n\t\t// We should have a valid address now\n\t\to.maybeRecordObservation(ob1[N-1], observedQuic)\n\t\to.maybeRecordObservation(ob2[N-1], observedQuic)\n\t\trequire.EventuallyWithT(t, func(t *assert.CollectT) {\n\t\t\tmatest.AssertEqualMultiaddrs(t, o.Addrs(0), []ma.Multiaddr{observedQuic, inferredWebTransport})\n\t\t}, 2*time.Second, 100*time.Millisecond)\n\n\t\t// Now disconnect first observer group\n\t\tfor i := range N {\n\t\t\to.removeConn(ob1[i])\n\t\t}\n\t\ttime.Sleep(100 * time.Millisecond)\n\t\tif !matest.AssertEqualMultiaddrs(t, o.Addrs(0), []ma.Multiaddr{observedQuic, inferredWebTransport}) {\n\t\t\tt.Fatalf(\"address removed too earyl %v %v\", o.Addrs(0), observedQuic)\n\t\t}\n\n\t\t// Now disconnect the second group to check cleanup\n\t\tfor i := range N {\n\t\t\to.removeConn(ob2[i])\n\t\t}\n\t\trequire.Eventually(t, func() bool {\n\t\t\treturn checkAllEntriesRemoved(o)\n\t\t}, 2*time.Second, 100*time.Millisecond)\n\t})\n\n\tt.Run(\"SameObserversDifferentAddrs\", func(t *testing.T) {\n\t\to := newObservedAddrMgr()\n\t\tdefer o.Close()\n\n\t\tobservedQuic1 := ma.StringCast(\"/ip4/2.2.2.2/udp/2/quic-v1\")\n\t\tobservedQuic2 := ma.StringCast(\"/ip4/2.2.2.2/udp/3/quic-v1\")\n\t\tinferredWebTransport1 := ma.StringCast(\"/ip4/2.2.2.2/udp/2/quic-v1/webtransport/certhash/uEgNmb28\")\n\t\tinferredWebTransport2 := ma.StringCast(\"/ip4/2.2.2.2/udp/3/quic-v1/webtransport/certhash/uEgNmb28\")\n\n\t\tconst N = 4 // ActivationThresh\n\t\tvar ob1, ob2 [N]connMultiaddrs\n\t\tfor i := range N {\n\t\t\tob1[i] = newConn(quic4ListenAddr, ma.StringCast(fmt.Sprintf(\"/ip4/1.2.3.%d/udp/1/quic-v1\", i)))\n\t\t\tob2[i] = newConn(quic4ListenAddr, ma.StringCast(fmt.Sprintf(\"/ip4/1.2.3.%d/udp/2/quic-v1\", i)))\n\t\t}\n\t\tfor i := range N - 1 {\n\t\t\to.maybeRecordObservation(ob1[i], observedQuic1)\n\t\t\to.maybeRecordObservation(ob2[i], observedQuic2)\n\t\t}\n\t\ttime.Sleep(100 * time.Millisecond)\n\t\trequire.Equal(t, o.Addrs(0), []ma.Multiaddr{})\n\n\t\t// We should have a valid address now\n\t\to.maybeRecordObservation(ob1[N-1], observedQuic1)\n\t\to.maybeRecordObservation(ob2[N-1], observedQuic2)\n\t\trequire.EventuallyWithT(t, func(t *assert.CollectT) {\n\t\t\tmatest.AssertEqualMultiaddrs(t, o.Addrs(0), []ma.Multiaddr{observedQuic1, observedQuic2, inferredWebTransport1, inferredWebTransport2})\n\t\t}, 2*time.Second, 100*time.Millisecond)\n\n\t\t// Now disconnect first observer group\n\t\tfor i := range N {\n\t\t\to.removeConn(ob1[i])\n\t\t}\n\t\ttime.Sleep(100 * time.Millisecond)\n\t\tif !matest.AssertEqualMultiaddrs(t, o.Addrs(0), []ma.Multiaddr{observedQuic2, inferredWebTransport2}) {\n\t\t\tt.Fatalf(\"address removed too early %v %v\", o.Addrs(0), observedQuic2)\n\t\t}\n\n\t\t// Now disconnect the second group to check cleanup\n\t\tfor i := range N {\n\t\t\to.removeConn(ob2[i])\n\t\t}\n\t\trequire.Eventually(t, func() bool {\n\t\t\treturn checkAllEntriesRemoved(o)\n\t\t}, 2*time.Second, 100*time.Millisecond)\n\t})\n\n\tt.Run(\"Old observations discarded\", func(t *testing.T) {\n\t\to := newObservedAddrMgr()\n\t\tdefer o.Close()\n\t\tc1 := newConn(quic4ListenAddr, ma.StringCast(\"/ip4/1.2.3.1/udp/1/quic-v1\"))\n\t\tc2 := newConn(quic4ListenAddr, ma.StringCast(\"/ip4/1.2.3.2/udp/1/quic-v1\"))\n\t\tc3 := newConn(webTransport4ListenAddr, ma.StringCast(\"/ip4/1.2.3.3/udp/1/quic-v1/webtransport\"))\n\t\tc4 := newConn(webTransport4ListenAddr, ma.StringCast(\"/ip4/1.2.3.4/udp/1/quic-v1/webtransport\"))\n\t\tc5 := newConn(quic4ListenAddr, ma.StringCast(\"/ip4/1.2.3.5/udp/1/quic-v1\"))\n\t\tc6 := newConn(quic4ListenAddr, ma.StringCast(\"/ip4/1.2.3.6/udp/1/quic-v1\"))\n\t\tvar observedQuic, observedWebTransport, observedWebTransportWithCertHash ma.Multiaddr\n\t\tfor i := range 10 {\n\t\t\t// Change the IP address in each observation\n\t\t\tobservedQuic = ma.StringCast(fmt.Sprintf(\"/ip4/2.2.2.%d/udp/2/quic-v1\", i))\n\t\t\tobservedWebTransport = ma.StringCast(fmt.Sprintf(\"/ip4/2.2.2.%d/udp/2/quic-v1/webtransport\", i))\n\t\t\tobservedWebTransportWithCertHash = ma.StringCast(fmt.Sprintf(\"/ip4/2.2.2.%d/udp/2/quic-v1/webtransport/certhash/uEgNmb28\", i))\n\t\t\to.maybeRecordObservation(c1, observedQuic)\n\t\t\to.maybeRecordObservation(c2, observedQuic)\n\t\t\to.maybeRecordObservation(c3, observedWebTransport)\n\t\t\to.maybeRecordObservation(c4, observedWebTransport)\n\t\t\to.maybeRecordObservation(c5, observedQuic)\n\t\t\ttime.Sleep(20 * time.Millisecond)\n\t\t}\n\n\t\trequire.EventuallyWithT(t, func(t *assert.CollectT) {\n\t\t\tmatest.AssertEqualMultiaddrs(t, o.Addrs(0), []ma.Multiaddr{observedQuic, observedWebTransportWithCertHash})\n\t\t}, 1*time.Second, 100*time.Millisecond)\n\n\t\ttw, err := thinWaistForm(quic4ListenAddr)\n\t\trequire.NoError(t, err)\n\t\trequire.Less(t, len(o.externalAddrs[string(tw.TW.Bytes())]), 2)\n\n\t\trequireEqualAddrs(t, []ma.Multiaddr{observedWebTransportWithCertHash}, o.AddrsFor(webTransport4ListenAddr))\n\t\trequireEqualAddrs(t, []ma.Multiaddr{observedQuic}, o.AddrsFor(quic4ListenAddr))\n\t\trequireAddrsMatch(t, []ma.Multiaddr{observedQuic, observedWebTransportWithCertHash}, o.Addrs(0))\n\n\t\tfor range 3 {\n\t\t\t// remove non-recorded connection\n\t\t\to.removeConn(c6)\n\t\t}\n\t\trequireEqualAddrs(t, []ma.Multiaddr{observedWebTransportWithCertHash}, o.AddrsFor(webTransport4ListenAddr))\n\t\trequireEqualAddrs(t, []ma.Multiaddr{observedQuic}, o.AddrsFor(quic4ListenAddr))\n\t\trequireAddrsMatch(t, []ma.Multiaddr{observedQuic, observedWebTransportWithCertHash}, o.Addrs(0))\n\n\t\to.removeConn(c1)\n\t\to.removeConn(c2)\n\t\to.removeConn(c3)\n\t\to.removeConn(c4)\n\t\to.removeConn(c5)\n\t\trequire.Eventually(t, func() bool {\n\t\t\treturn checkAllEntriesRemoved(o)\n\t\t}, 1*time.Second, 100*time.Millisecond)\n\t})\n\n\tt.Run(\"WebTransport certhash\", func(t *testing.T) {\n\t\to := newObservedAddrMgr()\n\t\tobservedWebTransport := ma.StringCast(\"/ip4/2.2.2.2/udp/1/quic-v1/webtransport\")\n\t\tobservedWebTransportWithCerthash := ma.StringCast(\"/ip4/2.2.2.2/udp/1/quic-v1/webtransport/certhash/uEgNmb28\")\n\t\tinferredQUIC := ma.StringCast(\"/ip4/2.2.2.2/udp/1/quic-v1\")\n\t\tc1 := newConn(webTransport4ListenAddr, ma.StringCast(\"/ip4/1.2.3.1/udp/1/quic-v1/webtransport\"))\n\t\tc2 := newConn(webTransport4ListenAddr, ma.StringCast(\"/ip4/1.2.3.2/udp/1/quic-v1/webtransport\"))\n\t\tc3 := newConn(webTransport4ListenAddr, ma.StringCast(\"/ip4/1.2.3.3/udp/1/quic-v1/webtransport\"))\n\t\tc4 := newConn(webTransport4ListenAddr, ma.StringCast(\"/ip4/1.2.3.4/udp/1/quic-v1/webtransport\"))\n\t\to.maybeRecordObservation(c1, observedWebTransport)\n\t\to.maybeRecordObservation(c2, observedWebTransport)\n\t\to.maybeRecordObservation(c3, observedWebTransport)\n\t\to.maybeRecordObservation(c4, observedWebTransport)\n\t\trequire.EventuallyWithT(t, func(t *assert.CollectT) {\n\t\t\tmatest.AssertMultiaddrsMatch(t, o.Addrs(0), []ma.Multiaddr{observedWebTransportWithCerthash, inferredQUIC})\n\t\t}, 1*time.Second, 100*time.Millisecond)\n\t\to.removeConn(c1)\n\t\to.removeConn(c2)\n\t\to.removeConn(c3)\n\t\to.removeConn(c4)\n\t\trequire.Eventually(t, func() bool {\n\t\t\treturn checkAllEntriesRemoved(o)\n\t\t}, 1*time.Second, 100*time.Millisecond)\n\t})\n\n\tt.Run(\"getNATType\", func(t *testing.T) {\n\t\to := newObservedAddrMgr()\n\t\tdefer o.Close()\n\n\t\tobservedWebTransport := ma.StringCast(\"/ip4/2.2.2.2/udp/1/quic-v1/webtransport\")\n\t\tvar udpConns [5 * maxExternalThinWaistAddrsPerLocalAddr]connMultiaddrs\n\t\tfor i := range len(udpConns) {\n\t\t\tudpConns[i] = newConn(webTransport4ListenAddr, ma.StringCast(fmt.Sprintf(\"/ip4/1.2.3.%d/udp/1/quic-v1/webtransport\", i)))\n\t\t\to.maybeRecordObservation(udpConns[i], observedWebTransport)\n\t\t\ttime.Sleep(10 * time.Millisecond)\n\t\t}\n\t\trequire.EventuallyWithT(t, func(t *assert.CollectT) {\n\t\t\ttcpNAT, udpNAT := o.getNATType()\n\t\t\trequire.Equal(t, tcpNAT, network.NATDeviceTypeUnknown)\n\t\t\trequire.Equal(t, udpNAT, network.NATDeviceTypeEndpointIndependent)\n\t\t}, 1*time.Second, 100*time.Millisecond)\n\n\t})\n\tt.Run(\"NATTypeSymmetric\", func(t *testing.T) {\n\t\to := newObservedAddrMgr()\n\t\tdefer o.Close()\n\t\tconst N = 100\n\t\tvar tcpConns, quicConns [N]*mockConn\n\t\tfor i := range N {\n\t\t\ttcpConns[i] = newConn(tcp4ListenAddr, ma.StringCast(fmt.Sprintf(\"/ip4/1.2.3.%d/tcp/1\", i)))\n\t\t\tquicConns[i] = newConn(quic4ListenAddr, ma.StringCast(fmt.Sprintf(\"/ip4/1.2.3.%d/udp/1/quic-v1\", i)))\n\t\t}\n\t\tvar observedQuic, observedTCP ma.Multiaddr\n\t\tfor i := range N {\n\t\t\t// ip addr has the form 2.2.<conn-num>.2\n\t\t\tobservedQuic = ma.StringCast(fmt.Sprintf(\"/ip4/2.2.%d.2/udp/2/quic-v1\", i%20))\n\t\t\tobservedTCP = ma.StringCast(fmt.Sprintf(\"/ip4/2.2.%d.2/tcp/2\", i%20))\n\t\t\to.maybeRecordObservation(tcpConns[i], observedTCP)\n\t\t\to.maybeRecordObservation(quicConns[i], observedQuic)\n\t\t\ttime.Sleep(10 * time.Millisecond)\n\t\t}\n\t\t// At this point we have 20 groups with 5 observations for every connection\n\t\t// The output should remain stable\n\t\trequire.EventuallyWithT(t, func(t *assert.CollectT) {\n\t\t\trequire.Equal(t, len(o.Addrs(0)), 3*maxExternalThinWaistAddrsPerLocalAddr)\n\t\t}, 1*time.Second, 100*time.Millisecond)\n\t\taddrs := o.Addrs(0)\n\t\tfor range 10 {\n\t\t\trequire.ElementsMatch(t, o.Addrs(0), addrs, \"%s %s\", o.Addrs(0), addrs)\n\t\t\ttime.Sleep(50 * time.Millisecond)\n\t\t}\n\n\t\ttcpNAT, udpNAT := o.getNATType()\n\t\trequire.Equal(t, tcpNAT, network.NATDeviceTypeEndpointDependent)\n\t\trequire.Equal(t, udpNAT, network.NATDeviceTypeEndpointDependent)\n\n\t\tfor i := range N {\n\t\t\to.removeConn(tcpConns[i])\n\t\t\to.removeConn(quicConns[i])\n\t\t}\n\t\trequire.Eventually(t, func() bool {\n\t\t\treturn checkAllEntriesRemoved(o)\n\t\t}, 1*time.Second, 100*time.Millisecond)\n\t})\n\tt.Run(\"Nil Input\", func(_ *testing.T) {\n\t\to := newObservedAddrMgr()\n\t\tdefer o.Close()\n\t\to.maybeRecordObservation(nil, nil)\n\t\tremoteAddr := ma.StringCast(\"/ip4/1.2.3.4/tcp/1\")\n\t\to.maybeRecordObservation(newConn(tcp4ListenAddr, remoteAddr), nil)\n\t\to.maybeRecordObservation(nil, remoteAddr)\n\t\to.AddrsFor(nil)\n\t\to.removeConn(nil)\n\t})\n\n\tt.Run(\"Many connection many observations IP4 And IP6\", func(t *testing.T) {\n\t\to := newObservedAddrMgr()\n\t\tdefer o.Close()\n\t\tconst N = 100\n\t\tvar tcp4Conns, quic4Conns, webTransport4Conns [N]*mockConn\n\t\tvar tcp6Conns, quic6Conns, webTransport6Conns [N]*mockConn\n\t\tfor i := range N {\n\t\t\ttcp4Conns[i] = newConn(tcp4ListenAddr, ma.StringCast(fmt.Sprintf(\"/ip4/1.2.3.%d/tcp/1\", i)))\n\t\t\tquic4Conns[i] = newConn(quic4ListenAddr, ma.StringCast(fmt.Sprintf(\"/ip4/1.2.3.%d/udp/1/quic-v1\", i)))\n\t\t\twebTransport4Conns[i] = newConn(webTransport4ListenAddr, ma.StringCast(fmt.Sprintf(\"/ip4/1.2.3.%d/udp/1/quic-v1/webtransport\", i)))\n\n\t\t\ttcp6Conns[i] = newConn(tcp6ListenAddr, ma.StringCast(fmt.Sprintf(\"/ip6/20%02x::/tcp/1\", i)))\n\t\t\tquic6Conns[i] = newConn(quic6ListenAddr, ma.StringCast(fmt.Sprintf(\"/ip6/20%02x::/udp/1/quic-v1\", i)))\n\t\t\twebTransport6Conns[i] = newConn(webTransport6ListenAddr, ma.StringCast(fmt.Sprintf(\"/ip6/20%02x::/udp/1/quic-v1/webtransport\", i)))\n\t\t}\n\t\tvar observedQUIC4, observedWebTransport4, observedTCP4 ma.Multiaddr\n\t\tvar observedQUIC6, observedWebTransport6, observedTCP6 ma.Multiaddr\n\t\tfor i := range N {\n\t\t\tfor j := range 5 {\n\t\t\t\t// ip addr has the form 2.2.<conn-num>.<obs-num>\n\t\t\t\tobservedQUIC4 = ma.StringCast(fmt.Sprintf(\"/ip4/2.2.%d.%d/udp/2/quic-v1\", i/10, j))\n\t\t\t\tobservedWebTransport4 = ma.StringCast(fmt.Sprintf(\"/ip4/2.2.%d.%d/udp/2/quic-v1/webtransport\", i/10, j))\n\t\t\t\tobservedTCP4 = ma.StringCast(fmt.Sprintf(\"/ip4/2.2.%d.%d/tcp/2\", i/10, j))\n\n\t\t\t\t// ip addr has the form 20XX::YY\n\t\t\t\tobservedQUIC6 = ma.StringCast(fmt.Sprintf(\"/ip6/20%02x::%02x/udp/2/quic-v1\", i/10, j))\n\t\t\t\tobservedWebTransport6 = ma.StringCast(fmt.Sprintf(\"/ip6/20%02x::%02x/udp/2/quic-v1/webtransport\", i/10, j))\n\t\t\t\tobservedTCP6 = ma.StringCast(fmt.Sprintf(\"/ip6/20%02x::%02x/tcp/2\", i/10, j))\n\n\t\t\t\to.maybeRecordObservation(tcp4Conns[i], observedTCP4)\n\t\t\t\to.maybeRecordObservation(quic4Conns[i], observedQUIC4)\n\t\t\t\to.maybeRecordObservation(webTransport4Conns[i], observedWebTransport4)\n\n\t\t\t\to.maybeRecordObservation(tcp6Conns[i], observedTCP6)\n\t\t\t\to.maybeRecordObservation(quic6Conns[i], observedQUIC6)\n\t\t\t\to.maybeRecordObservation(webTransport6Conns[i], observedWebTransport6)\n\t\t\t}\n\t\t}\n\t\t// At this point we have 10 groups of N / 10 with 10 observations for every connection\n\t\t// The output should remain stable\n\t\trequire.Eventually(t, func() bool {\n\t\t\treturn len(o.Addrs(0)) == 2*3*maxExternalThinWaistAddrsPerLocalAddr\n\t\t}, 1*time.Second, 100*time.Millisecond)\n\t\taddrs := o.Addrs(0)\n\t\tfor range 10 {\n\t\t\trequire.ElementsMatch(t, o.Addrs(0), addrs, \"%s %s\", o.Addrs(0), addrs)\n\t\t\ttime.Sleep(10 * time.Millisecond)\n\t\t}\n\n\t\t// Now we bias a few address counts and check for sorting correctness\n\t\tvar resTCPAddrs, resQuicAddrs, resWebTransportAddrs, resWebTransportWithCertHashAddrs []ma.Multiaddr\n\n\t\tfor i, idx := 0, 0; i < maxExternalThinWaistAddrsPerLocalAddr; i++ {\n\t\t\tresTCPAddrs = append(resTCPAddrs, ma.StringCast(fmt.Sprintf(\"/ip4/2.2.%d.4/tcp/2\", 9-i)))\n\t\t\tresQuicAddrs = append(resQuicAddrs, ma.StringCast(fmt.Sprintf(\"/ip4/2.2.%d.4/udp/2/quic-v1\", 9-i)))\n\t\t\tresWebTransportAddrs = append(resWebTransportAddrs, ma.StringCast(fmt.Sprintf(\"/ip4/2.2.%d.4/udp/2/quic-v1/webtransport\", 9-i)))\n\t\t\tresWebTransportWithCertHashAddrs = append(resWebTransportWithCertHashAddrs, ma.StringCast(fmt.Sprintf(\"/ip4/2.2.%d.4/udp/2/quic-v1/webtransport/certhash/uEgNmb28\", 9-i)))\n\n\t\t\to.maybeRecordObservation(tcp4Conns[i], resTCPAddrs[idx])\n\t\t\to.maybeRecordObservation(quic4Conns[i], resQuicAddrs[idx])\n\t\t\to.maybeRecordObservation(webTransport4Conns[i], resWebTransportAddrs[idx])\n\t\t\tidx++\n\n\t\t\tresTCPAddrs = append(resTCPAddrs, ma.StringCast(fmt.Sprintf(\"/ip6/20%02x::04/tcp/2\", 9-i)))\n\t\t\tresQuicAddrs = append(resQuicAddrs, ma.StringCast(fmt.Sprintf(\"/ip6/20%02x::04/udp/2/quic-v1\", 9-i)))\n\t\t\tresWebTransportAddrs = append(resWebTransportAddrs, ma.StringCast(fmt.Sprintf(\"/ip6/20%02x::04/udp/2/quic-v1/webtransport\", 9-i)))\n\t\t\tresWebTransportWithCertHashAddrs = append(resWebTransportWithCertHashAddrs, ma.StringCast(fmt.Sprintf(\"/ip6/20%02x::04/udp/2/quic-v1/webtransport/certhash/uEgNmb28\", 9-i)))\n\n\t\t\to.maybeRecordObservation(tcp6Conns[i], resTCPAddrs[idx])\n\t\t\to.maybeRecordObservation(quic6Conns[i], resQuicAddrs[idx])\n\t\t\to.maybeRecordObservation(webTransport6Conns[i], resWebTransportAddrs[idx])\n\t\t\tidx++\n\t\t}\n\t\tvar allAddrs []ma.Multiaddr\n\t\tallAddrs = append(allAddrs, resTCPAddrs[:]...)\n\t\tallAddrs = append(allAddrs, resQuicAddrs[:]...)\n\t\tallAddrs = append(allAddrs, resWebTransportWithCertHashAddrs[:]...)\n\t\trequire.EventuallyWithT(t, func(t *assert.CollectT) {\n\t\t\tmatest.AssertMultiaddrsMatch(t, o.Addrs(0), allAddrs)\n\t\t}, 1*time.Second, 100*time.Millisecond)\n\n\t\tfor i := range N {\n\t\t\to.removeConn(tcp4Conns[i])\n\t\t\to.removeConn(quic4Conns[i])\n\t\t\to.removeConn(webTransport4Conns[i])\n\t\t\to.removeConn(tcp6Conns[i])\n\t\t\to.removeConn(quic6Conns[i])\n\t\t\to.removeConn(webTransport6Conns[i])\n\t\t}\n\t\trequire.Eventually(t, func() bool {\n\t\t\treturn checkAllEntriesRemoved(o)\n\t\t}, 1*time.Second, 100*time.Millisecond)\n\t})\n}\n\nfunc genIPMultiaddr(ip6 bool) ma.Multiaddr {\n\tvar ipB [16]byte\n\tcrand.Read(ipB[:])\n\tvar ip net.IP\n\tif ip6 {\n\t\tip = net.IP(ipB[:])\n\t} else {\n\t\tip = net.IP(ipB[:4])\n\t}\n\taddr, _ := manet.FromIP(ip)\n\treturn addr\n}\n\nfunc FuzzObservedAddrsManager(f *testing.F) {\n\tprotos := []string{\n\t\t\"/webrtc-direct\",\n\t\t\"/quic-v1\",\n\t\t\"/quic-v1/webtransport\",\n\t}\n\ttcp4 := ma.StringCast(\"/ip4/192.168.1.100/tcp/1\")\n\tquic4 := ma.StringCast(\"/ip4/0.0.0.0/udp/1/quic-v1\")\n\twt4 := ma.StringCast(\"/ip4/0.0.0.0/udp/1/quic-v1/webtransport/certhash/uEgNmb28\")\n\ttcp6 := ma.StringCast(\"/ip6/1::1/tcp/1\")\n\tquic6 := ma.StringCast(\"/ip6/::/udp/1/quic-v1\")\n\twt6 := ma.StringCast(\"/ip6/::/udp/1/quic-v1/webtransport/certhash/uEgNmb28\")\n\tnewObservedAddrMgr := func() *Manager {\n\t\tlistenAddrs := []ma.Multiaddr{\n\t\t\ttcp4, quic4, wt4, tcp6, quic6, wt6,\n\t\t}\n\t\tlistenAddrsFunc := func() []ma.Multiaddr {\n\t\t\treturn listenAddrs\n\t\t}\n\t\teb := eventbus.NewBus()\n\t\to, err := newManagerWithListenAddrs(eb, listenAddrsFunc)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\treturn o\n\t}\n\n\tf.Fuzz(func(_ *testing.T, port uint16) {\n\t\taddrs := []ma.Multiaddr{genIPMultiaddr(true), genIPMultiaddr(false)}\n\t\tn := len(addrs)\n\t\tfor i := 0; i < n; i++ {\n\t\t\taddrs = append(addrs, addrs[i].Encapsulate(ma.StringCast(fmt.Sprintf(\"/tcp/%d\", port))))\n\t\t\taddrs = append(addrs, addrs[i].Encapsulate(ma.StringCast(fmt.Sprintf(\"/udp/%d\", port))))\n\t\t\taddrs = append(addrs, ma.StringCast(fmt.Sprintf(\"/tcp/%d\", port)))\n\t\t\taddrs = append(addrs, ma.StringCast(fmt.Sprintf(\"/udp/%d\", port)))\n\t\t}\n\t\tn = len(addrs)\n\t\tfor i := 0; i < n; i++ {\n\t\t\tfor j := range protos {\n\t\t\t\tprotoAddr := ma.StringCast(protos[j])\n\t\t\t\taddrs = append(addrs, addrs[i].Encapsulate(protoAddr))\n\t\t\t\taddrs = append(addrs, protoAddr)\n\t\t\t}\n\t\t}\n\t\to := newObservedAddrMgr()\n\t\tdefer o.Close()\n\t\tfor i := 0; i < len(addrs); i++ {\n\t\t\tfor _, l := range o.listenAddrs() {\n\t\t\t\tc := newConn(l, addrs[i])\n\t\t\t\to.maybeRecordObservation(c, addrs[i])\n\t\t\t\to.maybeRecordObservation(c, nil)\n\t\t\t\to.maybeRecordObservation(nil, addrs[i])\n\t\t\t\to.removeConn(c)\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc TestObserver(t *testing.T) {\n\ttests := []struct {\n\t\taddr ma.Multiaddr\n\t\twant string\n\t}{\n\t\t{\n\t\t\taddr: ma.StringCast(\"/ip4/1.2.3.4/tcp/1\"),\n\t\t\twant: \"1.2.3.4\",\n\t\t},\n\t\t{\n\t\t\taddr: ma.StringCast(\"/ip4/192.168.0.1/tcp/1\"),\n\t\t\twant: \"192.168.0.1\",\n\t\t},\n\t\t{\n\t\t\taddr: ma.StringCast(\"/ip6/200::1/udp/1/quic-v1\"),\n\t\t\twant: \"200::\",\n\t\t},\n\t\t{\n\t\t\taddr: ma.StringCast(\"/ip6/::1/udp/1/quic-v1\"),\n\t\t\twant: \"::\",\n\t\t},\n\t}\n\n\tfor i, tc := range tests {\n\t\tt.Run(fmt.Sprintf(\"%d\", i), func(t *testing.T) {\n\t\t\tgot, err := getObserver(tc.addr)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, got, tc.want)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "p2p/host/peerstore/metrics.go",
    "content": "package peerstore\n\nimport (\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n)\n\n// LatencyEWMASmoothing governs the decay of the EWMA (the speed\n// at which it changes). This must be a normalized (0-1) value.\n// 1 is 100% change, 0 is no change.\nvar LatencyEWMASmoothing = 0.1\n\ntype metrics struct {\n\tmutex  sync.RWMutex\n\tlatmap map[peer.ID]time.Duration\n}\n\nfunc NewMetrics() *metrics {\n\treturn &metrics{\n\t\tlatmap: make(map[peer.ID]time.Duration),\n\t}\n}\n\n// RecordLatency records a new latency measurement\nfunc (m *metrics) RecordLatency(p peer.ID, next time.Duration) {\n\tnextf := float64(next)\n\ts := LatencyEWMASmoothing\n\tif s > 1 || s < 0 {\n\t\ts = 0.1 // ignore the knob. it's broken. look, it jiggles.\n\t}\n\n\tm.mutex.Lock()\n\tewma, found := m.latmap[p]\n\tewmaf := float64(ewma)\n\tif !found {\n\t\tm.latmap[p] = next // when no data, just take it as the mean.\n\t} else {\n\t\tnextf = ((1.0 - s) * ewmaf) + (s * nextf)\n\t\tm.latmap[p] = time.Duration(nextf)\n\t}\n\tm.mutex.Unlock()\n}\n\n// LatencyEWMA returns an exponentially-weighted moving avg.\n// of all measurements of a peer's latency.\nfunc (m *metrics) LatencyEWMA(p peer.ID) time.Duration {\n\tm.mutex.RLock()\n\tdefer m.mutex.RUnlock()\n\treturn m.latmap[p]\n}\n\nfunc (m *metrics) RemovePeer(p peer.ID) {\n\tm.mutex.Lock()\n\tdelete(m.latmap, p)\n\tm.mutex.Unlock()\n}\n"
  },
  {
    "path": "p2p/host/peerstore/metrics_test.go",
    "content": "package peerstore\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/test\"\n)\n\nfunc TestLatencyEWMAFun(t *testing.T) {\n\tt.Skip(\"run it for fun\")\n\n\tm := NewMetrics()\n\tid, err := test.RandPeerID()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tmu := 100.0\n\tsig := 10.0\n\tnext := func() time.Duration {\n\t\tmu = (rand.NormFloat64() * sig) + mu\n\t\treturn time.Duration(mu)\n\t}\n\n\tprint := func() {\n\t\tfmt.Printf(\"%3.f %3.f --> %d\\n\", sig, mu, m.LatencyEWMA(id))\n\t}\n\n\tfor {\n\t\ttime.Sleep(200 * time.Millisecond)\n\t\tm.RecordLatency(id, next())\n\t\tprint()\n\t}\n}\n\nfunc TestLatencyEWMA(t *testing.T) {\n\tm := NewMetrics()\n\tid, err := test.RandPeerID()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tconst exp = 100\n\tconst mu = exp\n\tconst sig = 10\n\tnext := func() time.Duration { return time.Duration(rand.Intn(20) - 10 + mu) }\n\n\tfor range 10 {\n\t\tm.RecordLatency(id, next())\n\t}\n\n\tlat := m.LatencyEWMA(id)\n\tdiff := exp - lat\n\tif diff < 0 {\n\t\tdiff = -diff\n\t}\n\tif diff > sig {\n\t\tt.Fatalf(\"latency outside of expected range. expected %d ± %d, got %d\", exp, sig, lat)\n\t}\n}\n"
  },
  {
    "path": "p2p/host/peerstore/peerstore.go",
    "content": "package peerstore\n\nimport (\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\tpstore \"github.com/libp2p/go-libp2p/core/peerstore\"\n)\n\nfunc PeerInfos(ps pstore.Peerstore, peers peer.IDSlice) []peer.AddrInfo {\n\tpi := make([]peer.AddrInfo, len(peers))\n\tfor i, p := range peers {\n\t\tpi[i] = ps.PeerInfo(p)\n\t}\n\treturn pi\n}\n\nfunc PeerInfoIDs(pis []peer.AddrInfo) peer.IDSlice {\n\tps := make(peer.IDSlice, len(pis))\n\tfor i, pi := range pis {\n\t\tps[i] = pi.ID\n\t}\n\treturn ps\n}\n"
  },
  {
    "path": "p2p/host/peerstore/pstoreds/addr_book.go",
    "content": "package pstoreds\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"sort\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\tpstore \"github.com/libp2p/go-libp2p/core/peerstore\"\n\t\"github.com/libp2p/go-libp2p/core/record\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoreds/pb\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoremem\"\n\n\t\"github.com/hashicorp/golang-lru/arc/v2\"\n\tds \"github.com/ipfs/go-datastore\"\n\t\"github.com/ipfs/go-datastore/query\"\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n\tb32 \"github.com/multiformats/go-base32\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\ntype ttlWriteMode int\n\nconst (\n\tttlOverride ttlWriteMode = iota\n\tttlExtend\n)\n\nvar (\n\tlog = logging.Logger(\"peerstore/ds\")\n\n\t// Peer addresses are stored db key pattern:\n\t// /peers/addrs/<b32 peer id no padding>\n\taddrBookBase = ds.NewKey(\"/peers/addrs\")\n)\n\n// addrsRecord decorates the AddrBookRecord with locks and metadata.\ntype addrsRecord struct {\n\tsync.RWMutex\n\t*pb.AddrBookRecord\n\tdirty bool\n}\n\n// flush writes the record to the datastore by calling ds.Put, unless the record is\n// marked for deletion, in which case we call ds.Delete. To be called within a lock.\nfunc (r *addrsRecord) flush(write ds.Write) (err error) {\n\tkey := addrBookBase.ChildString(b32.RawStdEncoding.EncodeToString(r.Id))\n\n\tif len(r.Addrs) == 0 {\n\t\tif err = write.Delete(context.TODO(), key); err == nil {\n\t\t\tr.dirty = false\n\t\t}\n\t\treturn err\n\t}\n\n\tdata, err := proto.Marshal(r)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err = write.Put(context.TODO(), key, data); err != nil {\n\t\treturn err\n\t}\n\t// write succeeded; record is no longer dirty.\n\tr.dirty = false\n\treturn nil\n}\n\n// clean is called on records to perform housekeeping. The return value indicates if the record was changed\n// as a result of this call.\n//\n// clean does the following:\n// * sorts addresses by expiration (soonest expiring first).\n// * removes expired addresses.\n//\n// It short-circuits optimistically when there's nothing to do.\n//\n// clean is called from several points:\n// * when accessing an entry.\n// * when performing periodic GC.\n// * after an entry has been modified (e.g. addresses have been added or removed, TTLs updated, etc.)\n//\n// If the return value is true, the caller should perform a flush immediately to sync the record with the store.\nfunc (r *addrsRecord) clean(now time.Time) (chgd bool) {\n\tnowUnix := now.Unix()\n\taddrsLen := len(r.Addrs)\n\n\tif !r.dirty && !r.hasExpiredAddrs(nowUnix) {\n\t\t// record is not dirty, and we have no expired entries to purge.\n\t\treturn false\n\t}\n\n\tif addrsLen == 0 {\n\t\t// this is a ghost record; let's signal it has to be written.\n\t\t// flush() will take care of doing the deletion.\n\t\treturn true\n\t}\n\n\tif r.dirty && addrsLen > 1 {\n\t\tsort.Slice(r.Addrs, func(i, j int) bool {\n\t\t\treturn r.Addrs[i].Expiry < r.Addrs[j].Expiry\n\t\t})\n\t}\n\n\tr.Addrs = removeExpired(r.Addrs, nowUnix)\n\n\treturn r.dirty || len(r.Addrs) != addrsLen\n}\n\nfunc (r *addrsRecord) hasExpiredAddrs(now int64) bool {\n\tif len(r.Addrs) > 0 && r.Addrs[0].Expiry <= now {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc removeExpired(entries []*pb.AddrBookRecord_AddrEntry, now int64) []*pb.AddrBookRecord_AddrEntry {\n\t// since addresses are sorted by expiration, we find the first\n\t// survivor and split the slice on its index.\n\tpivot := -1\n\tfor i, addr := range entries {\n\t\tif addr.Expiry > now {\n\t\t\tbreak\n\t\t}\n\t\tpivot = i\n\t}\n\n\treturn entries[pivot+1:]\n}\n\n// dsAddrBook is an address book backed by a Datastore with a GC procedure to purge expired entries. It uses an\n// in-memory address stream manager. See the NewAddrBook for more information.\ntype dsAddrBook struct {\n\tctx  context.Context\n\topts Options\n\n\tcache       cache[peer.ID, *addrsRecord]\n\tds          ds.Batching\n\tgc          *dsAddrBookGc\n\tsubsManager *pstoremem.AddrSubManager\n\n\t// controls children goroutine lifetime.\n\tchildrenDone sync.WaitGroup\n\tcancelFn     func()\n\n\tclock clock\n}\n\ntype clock interface {\n\tNow() time.Time\n\tAfter(d time.Duration) <-chan time.Time\n}\n\ntype realclock struct{}\n\nfunc (rc realclock) Now() time.Time {\n\treturn time.Now()\n}\n\nfunc (rc realclock) After(d time.Duration) <-chan time.Time {\n\treturn time.After(d)\n}\n\nvar _ pstore.AddrBook = (*dsAddrBook)(nil)\nvar _ pstore.CertifiedAddrBook = (*dsAddrBook)(nil)\n\n// NewAddrBook initializes a new datastore-backed address book. It serves as a drop-in replacement for pstoremem\n// (memory-backed peerstore), and works with any datastore implementing the ds.Batching interface.\n//\n// Addresses and peer records are serialized into protobuf, storing one datastore entry per peer, along with metadata\n// to control address expiration. To alleviate disk access and serde overhead, we internally use a read/write-through\n// ARC cache, the size of which is adjustable via Options.CacheSize.\n//\n// The user has a choice of two GC algorithms:\n//\n//   - lookahead GC: minimises the amount of full store traversals by maintaining a time-indexed list of entries that\n//     need to be visited within the period specified in Options.GCLookaheadInterval. This is useful in scenarios with\n//     considerable TTL variance, coupled with datastores whose native iterators return entries in lexicographical key\n//     order. Enable this mode by passing a value Options.GCLookaheadInterval > 0. Lookahead windows are jumpy, not\n//     sliding. Purges operate exclusively over the lookahead window with periodicity Options.GCPurgeInterval.\n//\n//   - full-purge GC (default): performs a full visit of the store with periodicity Options.GCPurgeInterval. Useful when\n//     the range of possible TTL values is small and the values themselves are also extreme, e.g. 10 minutes or\n//     permanent, popular values used in other libp2p modules. In this cited case, optimizing with lookahead windows\n//     makes little sense.\nfunc NewAddrBook(ctx context.Context, store ds.Batching, opts Options) (ab *dsAddrBook, err error) {\n\tctx, cancelFn := context.WithCancel(ctx)\n\tab = &dsAddrBook{\n\t\tctx:         ctx,\n\t\tds:          store,\n\t\topts:        opts,\n\t\tcancelFn:    cancelFn,\n\t\tsubsManager: pstoremem.NewAddrSubManager(),\n\t\tclock:       realclock{},\n\t}\n\n\tif opts.Clock != nil {\n\t\tab.clock = opts.Clock\n\t}\n\n\tif opts.CacheSize > 0 {\n\t\tif ab.cache, err = arc.NewARC[peer.ID, *addrsRecord](int(opts.CacheSize)); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t} else {\n\t\tab.cache = new(noopCache[peer.ID, *addrsRecord])\n\t}\n\n\tif ab.gc, err = newAddressBookGc(ctx, ab); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ab, nil\n}\n\nfunc (ab *dsAddrBook) Close() error {\n\tab.cancelFn()\n\tab.childrenDone.Wait()\n\treturn nil\n}\n\n// loadRecord is a read-through fetch. It fetches a record from cache, falling back to the\n// datastore upon a miss, and returning a newly initialized record if the peer doesn't exist.\n//\n// loadRecord calls clean() on an existing record before returning it. If the record changes\n// as a result and the update argument is true, the resulting state is saved in the datastore.\n//\n// If the cache argument is true, the record is inserted in the cache when loaded from the datastore.\nfunc (ab *dsAddrBook) loadRecord(id peer.ID, cache bool, update bool) (pr *addrsRecord, err error) {\n\tif pr, ok := ab.cache.Get(id); ok {\n\t\tpr.Lock()\n\t\tdefer pr.Unlock()\n\n\t\tif pr.clean(ab.clock.Now()) && update {\n\t\t\terr = pr.flush(ab.ds)\n\t\t}\n\t\treturn pr, err\n\t}\n\n\tpr = &addrsRecord{AddrBookRecord: &pb.AddrBookRecord{}}\n\tkey := addrBookBase.ChildString(b32.RawStdEncoding.EncodeToString([]byte(id)))\n\tdata, err := ab.ds.Get(context.TODO(), key)\n\n\tswitch err {\n\tcase ds.ErrNotFound:\n\t\terr = nil\n\t\tpr.Id = []byte(id)\n\tcase nil:\n\t\tif err := proto.Unmarshal(data, pr); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\t// this record is new and local for now (not in cache), so we don't need to lock.\n\t\tif pr.clean(ab.clock.Now()) && update {\n\t\t\terr = pr.flush(ab.ds)\n\t\t}\n\tdefault:\n\t\treturn nil, err\n\t}\n\n\tif cache {\n\t\tab.cache.Add(id, pr)\n\t}\n\treturn pr, err\n}\n\n// AddAddr will add a new address if it's not already in the AddrBook.\nfunc (ab *dsAddrBook) AddAddr(p peer.ID, addr ma.Multiaddr, ttl time.Duration) {\n\tab.AddAddrs(p, []ma.Multiaddr{addr}, ttl)\n}\n\n// AddAddrs will add many new addresses if they're not already in the AddrBook.\nfunc (ab *dsAddrBook) AddAddrs(p peer.ID, addrs []ma.Multiaddr, ttl time.Duration) {\n\tif ttl <= 0 {\n\t\treturn\n\t}\n\taddrs = cleanAddrs(addrs, p)\n\tab.setAddrs(p, addrs, ttl, ttlExtend, false)\n}\n\n// ConsumePeerRecord adds addresses from a signed peer.PeerRecord (contained in\n// a record.Envelope), which will expire after the given TTL.\n// See https://godoc.org/github.com/libp2p/go-libp2p/core/peerstore#CertifiedAddrBook for more details.\nfunc (ab *dsAddrBook) ConsumePeerRecord(recordEnvelope *record.Envelope, ttl time.Duration) (bool, error) {\n\tr, err := recordEnvelope.Record()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\trec, ok := r.(*peer.PeerRecord)\n\tif !ok {\n\t\treturn false, fmt.Errorf(\"envelope did not contain PeerRecord\")\n\t}\n\tif !rec.PeerID.MatchesPublicKey(recordEnvelope.PublicKey) {\n\t\treturn false, fmt.Errorf(\"signing key does not match PeerID in PeerRecord\")\n\t}\n\n\t// ensure that the seq number from envelope is >= any previously received seq no\n\t// update when equal to extend the ttls\n\tif ab.latestPeerRecordSeq(rec.PeerID) > rec.Seq {\n\t\treturn false, nil\n\t}\n\n\taddrs := cleanAddrs(rec.Addrs, rec.PeerID)\n\terr = ab.setAddrs(rec.PeerID, addrs, ttl, ttlExtend, true)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\terr = ab.storeSignedPeerRecord(rec.PeerID, recordEnvelope, rec)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treturn true, nil\n}\n\nfunc (ab *dsAddrBook) latestPeerRecordSeq(p peer.ID) uint64 {\n\tpr, err := ab.loadRecord(p, true, false)\n\tif err != nil {\n\t\t// We ignore the error because we don't want to fail storing a new record in this\n\t\t// case.\n\t\tlog.Error(\"unable to load record\", \"peer\", p, \"err\", err)\n\t\treturn 0\n\t}\n\tpr.RLock()\n\tdefer pr.RUnlock()\n\n\tif len(pr.Addrs) == 0 || pr.CertifiedRecord == nil || len(pr.CertifiedRecord.Raw) == 0 {\n\t\treturn 0\n\t}\n\treturn pr.CertifiedRecord.Seq\n}\n\nfunc (ab *dsAddrBook) storeSignedPeerRecord(p peer.ID, envelope *record.Envelope, rec *peer.PeerRecord) error {\n\tenvelopeBytes, err := envelope.Marshal()\n\tif err != nil {\n\t\treturn err\n\t}\n\t// reload record and add routing state\n\t// this has to be done after we add the addresses, since if\n\t// we try to flush a datastore record with no addresses,\n\t// it will just get deleted\n\tpr, err := ab.loadRecord(p, true, false)\n\tif err != nil {\n\t\treturn err\n\t}\n\tpr.Lock()\n\tdefer pr.Unlock()\n\tpr.CertifiedRecord = &pb.AddrBookRecord_CertifiedRecord{\n\t\tSeq: rec.Seq,\n\t\tRaw: envelopeBytes,\n\t}\n\tpr.dirty = true\n\terr = pr.flush(ab.ds)\n\treturn err\n}\n\n// GetPeerRecord returns a record.Envelope containing a peer.PeerRecord for the\n// given peer id, if one exists.\n// Returns nil if no signed PeerRecord exists for the peer.\nfunc (ab *dsAddrBook) GetPeerRecord(p peer.ID) *record.Envelope {\n\tpr, err := ab.loadRecord(p, true, false)\n\tif err != nil {\n\t\tlog.Error(\"unable to load record for peer\", \"peer\", p, \"err\", err)\n\t\treturn nil\n\t}\n\tpr.RLock()\n\tdefer pr.RUnlock()\n\tif pr.CertifiedRecord == nil || len(pr.CertifiedRecord.Raw) == 0 || len(pr.Addrs) == 0 {\n\t\treturn nil\n\t}\n\tstate, _, err := record.ConsumeEnvelope(pr.CertifiedRecord.Raw, peer.PeerRecordEnvelopeDomain)\n\tif err != nil {\n\t\tlog.Error(\"error unmarshaling stored signed peer record for peer\", \"peer\", p, \"err\", err)\n\t\treturn nil\n\t}\n\treturn state\n}\n\n// SetAddr will add or update the TTL of an address in the AddrBook.\nfunc (ab *dsAddrBook) SetAddr(p peer.ID, addr ma.Multiaddr, ttl time.Duration) {\n\tab.SetAddrs(p, []ma.Multiaddr{addr}, ttl)\n}\n\n// SetAddrs will add or update the TTLs of addresses in the AddrBook.\nfunc (ab *dsAddrBook) SetAddrs(p peer.ID, addrs []ma.Multiaddr, ttl time.Duration) {\n\taddrs = cleanAddrs(addrs, p)\n\tif ttl <= 0 {\n\t\tab.deleteAddrs(p, addrs)\n\t\treturn\n\t}\n\tab.setAddrs(p, addrs, ttl, ttlOverride, false)\n}\n\n// UpdateAddrs will update any addresses for a given peer and TTL combination to\n// have a new TTL.\nfunc (ab *dsAddrBook) UpdateAddrs(p peer.ID, oldTTL time.Duration, newTTL time.Duration) {\n\tpr, err := ab.loadRecord(p, true, false)\n\tif err != nil {\n\t\tlog.Error(\"failed to update ttls for peer\", \"peer\", p, \"err\", err)\n\t\treturn\n\t}\n\n\tpr.Lock()\n\tdefer pr.Unlock()\n\n\tnewExp := ab.clock.Now().Add(newTTL).Unix()\n\tfor _, entry := range pr.Addrs {\n\t\tif entry.Ttl != int64(oldTTL) {\n\t\t\tcontinue\n\t\t}\n\t\tentry.Ttl, entry.Expiry = int64(newTTL), newExp\n\t\tpr.dirty = true\n\t}\n\n\tif pr.clean(ab.clock.Now()) {\n\t\tpr.flush(ab.ds)\n\t}\n}\n\n// Addrs returns all of the non-expired addresses for a given peer.\nfunc (ab *dsAddrBook) Addrs(p peer.ID) []ma.Multiaddr {\n\tpr, err := ab.loadRecord(p, true, true)\n\tif err != nil {\n\t\tlog.Warn(\"failed to load peerstore entry for peer while querying addrs\", \"peer\", p, \"err\", err)\n\t\treturn nil\n\t}\n\n\tpr.RLock()\n\tdefer pr.RUnlock()\n\n\taddrs := make([]ma.Multiaddr, len(pr.Addrs))\n\tfor i, a := range pr.Addrs {\n\t\tvar err error\n\t\taddrs[i], err = ma.NewMultiaddrBytes(a.Addr)\n\t\tif err != nil {\n\t\t\tlog.Warn(\"failed to parse peerstore entry for peer while querying addrs\", \"peer\", p, \"err\", err)\n\t\t\treturn nil\n\t\t}\n\t}\n\treturn addrs\n}\n\n// Peers returns all of the peer IDs for which the AddrBook has addresses.\nfunc (ab *dsAddrBook) PeersWithAddrs() peer.IDSlice {\n\tids, err := uniquePeerIds(ab.ds, addrBookBase, func(result query.Result) string {\n\t\treturn ds.RawKey(result.Key).Name()\n\t})\n\tif err != nil {\n\t\tlog.Error(\"error while retrieving peers with addresses\", \"err\", err)\n\t}\n\treturn ids\n}\n\n// AddrStream returns a channel on which all new addresses discovered for a\n// given peer ID will be published.\nfunc (ab *dsAddrBook) AddrStream(ctx context.Context, p peer.ID) <-chan ma.Multiaddr {\n\tinitial := ab.Addrs(p)\n\treturn ab.subsManager.AddrStream(ctx, p, initial)\n}\n\n// ClearAddrs will delete all known addresses for a peer ID.\nfunc (ab *dsAddrBook) ClearAddrs(p peer.ID) {\n\tab.cache.Remove(p)\n\n\tkey := addrBookBase.ChildString(b32.RawStdEncoding.EncodeToString([]byte(p)))\n\tif err := ab.ds.Delete(context.TODO(), key); err != nil {\n\t\tlog.Error(\"failed to clear addresses for peer\", \"peer\", p, \"err\", err)\n\t}\n}\n\nfunc (ab *dsAddrBook) setAddrs(p peer.ID, addrs []ma.Multiaddr, ttl time.Duration, mode ttlWriteMode, _ bool) (err error) {\n\tif len(addrs) == 0 {\n\t\treturn nil\n\t}\n\n\tpr, err := ab.loadRecord(p, true, false)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to load peerstore entry for peer %s while setting addrs, err: %v\", p, err)\n\t}\n\n\tpr.Lock()\n\tdefer pr.Unlock()\n\n\t// // if we have a signed PeerRecord, ignore attempts to add unsigned addrs\n\t// if !signed && pr.CertifiedRecord != nil {\n\t// \treturn nil\n\t// }\n\n\tnewExp := ab.clock.Now().Add(ttl).Unix()\n\taddrsMap := make(map[string]*pb.AddrBookRecord_AddrEntry, len(pr.Addrs))\n\tfor _, addr := range pr.Addrs {\n\t\taddrsMap[string(addr.Addr)] = addr\n\t}\n\n\tupdateExisting := func(incoming ma.Multiaddr) *pb.AddrBookRecord_AddrEntry {\n\t\texistingEntry := addrsMap[string(incoming.Bytes())]\n\t\tif existingEntry == nil {\n\t\t\treturn nil\n\t\t}\n\n\t\tswitch mode {\n\t\tcase ttlOverride:\n\t\t\texistingEntry.Ttl = int64(ttl)\n\t\t\texistingEntry.Expiry = newExp\n\t\tcase ttlExtend:\n\t\t\tif int64(ttl) > existingEntry.Ttl {\n\t\t\t\texistingEntry.Ttl = int64(ttl)\n\t\t\t}\n\t\t\tif newExp > existingEntry.Expiry {\n\t\t\t\texistingEntry.Expiry = newExp\n\t\t\t}\n\t\tdefault:\n\t\t\tpanic(\"BUG: unimplemented ttl mode\")\n\t\t}\n\t\treturn existingEntry\n\t}\n\n\tvar entries []*pb.AddrBookRecord_AddrEntry\n\tfor _, incoming := range addrs {\n\t\texistingEntry := updateExisting(incoming)\n\n\t\tif existingEntry == nil {\n\t\t\t// \tif signed {\n\t\t\t// \t\tentries = append(entries, existingEntry)\n\t\t\t// \t}\n\t\t\t// } else {\n\t\t\t// new addr, add & broadcast\n\t\t\tentry := &pb.AddrBookRecord_AddrEntry{\n\t\t\t\tAddr:   incoming.Bytes(),\n\t\t\t\tTtl:    int64(ttl),\n\t\t\t\tExpiry: newExp,\n\t\t\t}\n\t\t\tentries = append(entries, entry)\n\n\t\t\t// note: there's a minor chance that writing the record will fail, in which case we would've broadcast\n\t\t\t// the addresses without persisting them. This is very unlikely and not much of an issue.\n\t\t\tab.subsManager.BroadcastAddr(p, incoming)\n\t\t}\n\t}\n\n\t// if signed {\n\t// \t// when adding signed addrs, we want to keep _only_ the incoming addrs\n\t// \tpr.Addrs = entries\n\t// } else {\n\tpr.Addrs = append(pr.Addrs, entries...)\n\t// }\n\n\tpr.dirty = true\n\tpr.clean(ab.clock.Now())\n\treturn pr.flush(ab.ds)\n}\n\n// deletes addresses in place, avoiding copies until we encounter the first deletion.\n// does not preserve order, but entries are re-sorted before flushing to disk anyway.\nfunc deleteInPlace(s []*pb.AddrBookRecord_AddrEntry, addrs []ma.Multiaddr) []*pb.AddrBookRecord_AddrEntry {\n\tif s == nil || len(addrs) == 0 {\n\t\treturn s\n\t}\n\tsurvived := len(s)\nOuter:\n\tfor i, addr := range s {\n\t\tfor _, del := range addrs {\n\t\t\tif !bytes.Equal(del.Bytes(), addr.Addr) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tsurvived--\n\t\t\t// if there are no survivors, bail out\n\t\t\tif survived == 0 {\n\t\t\t\tbreak Outer\n\t\t\t}\n\t\t\ts[i] = s[survived]\n\t\t\t// we've already dealt with s[i], move to the next\n\t\t\tcontinue Outer\n\t\t}\n\t}\n\treturn s[:survived]\n}\n\nfunc (ab *dsAddrBook) deleteAddrs(p peer.ID, addrs []ma.Multiaddr) (err error) {\n\tpr, err := ab.loadRecord(p, false, false)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to load peerstore entry for peer %v while deleting addrs, err: %v\", p, err)\n\t}\n\n\tpr.Lock()\n\tdefer pr.Unlock()\n\n\tif pr.Addrs == nil {\n\t\treturn nil\n\t}\n\n\tpr.Addrs = deleteInPlace(pr.Addrs, addrs)\n\n\tpr.dirty = true\n\tpr.clean(ab.clock.Now())\n\treturn pr.flush(ab.ds)\n}\n\nfunc cleanAddrs(addrs []ma.Multiaddr, pid peer.ID) []ma.Multiaddr {\n\tclean := make([]ma.Multiaddr, 0, len(addrs))\n\tfor _, addr := range addrs {\n\t\t// Remove suffix of /p2p/peer-id from address\n\t\taddr, addrPid := peer.SplitAddr(addr)\n\t\tif addr == nil {\n\t\t\tlog.Warn(\"Was passed a nil multiaddr\", \"peer\", pid)\n\t\t\tcontinue\n\t\t}\n\t\tif addrPid != \"\" && addrPid != pid {\n\t\t\tlog.Warn(\"Was passed p2p address with a different peerId\", \"found\", addrPid, \"expected\", pid)\n\t\t\tcontinue\n\t\t}\n\t\tclean = append(clean, addr)\n\t}\n\treturn clean\n}\n"
  },
  {
    "path": "p2p/host/peerstore/pstoreds/addr_book_gc.go",
    "content": "package pstoreds\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoreds/pb\"\n\t\"google.golang.org/protobuf/proto\"\n\n\tds \"github.com/ipfs/go-datastore\"\n\t\"github.com/ipfs/go-datastore/query\"\n\tb32 \"github.com/multiformats/go-base32\"\n)\n\nvar (\n\t// GC lookahead entries are stored in key pattern:\n\t// /peers/gc/addrs/<unix timestamp of next visit>/<peer ID b32> => nil\n\t// in databases with lexicographical key order, this time-indexing allows us to visit\n\t// only the timeslice we are interested in.\n\tgcLookaheadBase = ds.NewKey(\"/peers/gc/addrs\")\n\n\t// queries\n\tpurgeLookaheadQuery = query.Query{\n\t\tPrefix:   gcLookaheadBase.String(),\n\t\tOrders:   []query.Order{query.OrderByFunction(orderByTimestampInKey)},\n\t\tKeysOnly: true,\n\t}\n\n\tpurgeStoreQuery = query.Query{\n\t\tPrefix:   addrBookBase.String(),\n\t\tOrders:   []query.Order{query.OrderByKey{}},\n\t\tKeysOnly: false,\n\t}\n\n\tpopulateLookaheadQuery = query.Query{\n\t\tPrefix:   addrBookBase.String(),\n\t\tOrders:   []query.Order{query.OrderByKey{}},\n\t\tKeysOnly: true,\n\t}\n)\n\n// dsAddrBookGc is responsible for garbage collection in a datastore-backed address book.\ntype dsAddrBookGc struct {\n\tctx              context.Context\n\tab               *dsAddrBook\n\trunning          chan struct{}\n\tlookaheadEnabled bool\n\tpurgeFunc        func()\n\tcurrWindowEnd    int64\n}\n\nfunc newAddressBookGc(ctx context.Context, ab *dsAddrBook) (*dsAddrBookGc, error) {\n\tif ab.opts.GCPurgeInterval < 0 {\n\t\treturn nil, fmt.Errorf(\"negative GC purge interval provided: %s\", ab.opts.GCPurgeInterval)\n\t}\n\tif ab.opts.GCLookaheadInterval < 0 {\n\t\treturn nil, fmt.Errorf(\"negative GC lookahead interval provided: %s\", ab.opts.GCLookaheadInterval)\n\t}\n\tif ab.opts.GCInitialDelay < 0 {\n\t\treturn nil, fmt.Errorf(\"negative GC initial delay provided: %s\", ab.opts.GCInitialDelay)\n\t}\n\tif ab.opts.GCLookaheadInterval > 0 && ab.opts.GCLookaheadInterval < ab.opts.GCPurgeInterval {\n\t\treturn nil, fmt.Errorf(\"lookahead interval must be larger than purge interval, respectively: %s, %s\",\n\t\t\tab.opts.GCLookaheadInterval, ab.opts.GCPurgeInterval)\n\t}\n\n\tlookaheadEnabled := ab.opts.GCLookaheadInterval > 0\n\tgc := &dsAddrBookGc{\n\t\tctx:              ctx,\n\t\tab:               ab,\n\t\trunning:          make(chan struct{}, 1),\n\t\tlookaheadEnabled: lookaheadEnabled,\n\t}\n\n\tif lookaheadEnabled {\n\t\tgc.purgeFunc = gc.purgeLookahead\n\t} else {\n\t\tgc.purgeFunc = gc.purgeStore\n\t}\n\n\t// do not start GC timers if purge is disabled; this GC can only be triggered manually.\n\tif ab.opts.GCPurgeInterval > 0 {\n\t\tgc.ab.childrenDone.Add(1)\n\t\tgo gc.background()\n\t}\n\n\treturn gc, nil\n}\n\n// gc prunes expired addresses from the datastore at regular intervals. It should be spawned as a goroutine.\nfunc (gc *dsAddrBookGc) background() {\n\tdefer gc.ab.childrenDone.Done()\n\n\tselect {\n\tcase <-gc.ab.clock.After(gc.ab.opts.GCInitialDelay):\n\tcase <-gc.ab.ctx.Done():\n\t\t// yield if we have been cancelled/closed before the delay elapses.\n\t\treturn\n\t}\n\n\tpurgeTimer := time.NewTicker(gc.ab.opts.GCPurgeInterval)\n\tdefer purgeTimer.Stop()\n\n\tvar lookaheadCh <-chan time.Time\n\tif gc.lookaheadEnabled {\n\t\tlookaheadTimer := time.NewTicker(gc.ab.opts.GCLookaheadInterval)\n\t\tlookaheadCh = lookaheadTimer.C\n\t\tgc.populateLookahead() // do a lookahead now\n\t\tdefer lookaheadTimer.Stop()\n\t}\n\n\tfor {\n\t\tselect {\n\t\tcase <-purgeTimer.C:\n\t\t\tgc.purgeFunc()\n\n\t\tcase <-lookaheadCh:\n\t\t\t// will never trigger if lookahead is disabled (nil Duration).\n\t\t\tgc.populateLookahead()\n\n\t\tcase <-gc.ctx.Done():\n\t\t\treturn\n\t\t}\n\t}\n}\n\n// purgeCycle runs a single GC purge cycle. It operates within the lookahead window if lookahead is enabled; else it\n// visits all entries in the datastore, deleting the addresses that have expired.\nfunc (gc *dsAddrBookGc) purgeLookahead() {\n\tselect {\n\tcase gc.running <- struct{}{}:\n\t\tdefer func() { <-gc.running }()\n\tdefault:\n\t\t// yield if lookahead is running.\n\t\treturn\n\t}\n\n\tvar id peer.ID\n\trecord := &addrsRecord{AddrBookRecord: &pb.AddrBookRecord{}} // empty record to reuse and avoid allocs.\n\tbatch, err := newCyclicBatch(gc.ab.ds, defaultOpsPerCyclicBatch)\n\tif err != nil {\n\t\tlog.Warn(\"failed while creating batch to purge GC entries\", \"err\", err)\n\t}\n\n\t// This function drops an unparseable GC entry; this is for safety. It is an escape hatch in case\n\t// we modify the format of keys going forward. If a user runs a new version against an old DB,\n\t// if we don't clean up unparseable entries we'll end up accumulating garbage.\n\tdropInError := func(key ds.Key, err error, msg string) {\n\t\tif err != nil {\n\t\t\tlog.Warn(\"failed while record with GC key; deleting\", \"message\", msg, \"key\", key, \"err\", err)\n\t\t}\n\t\tif err = batch.Delete(context.TODO(), key); err != nil {\n\t\t\tlog.Warn(\"failed to delete corrupt GC lookahead entry\", \"key\", key, \"err\", err)\n\t\t}\n\t}\n\n\t// This function drops a GC key if the entry is cleaned correctly. It may reschedule another visit\n\t// if the next earliest expiry falls within the current window again.\n\tdropOrReschedule := func(key ds.Key, ar *addrsRecord) {\n\t\tif err := batch.Delete(context.TODO(), key); err != nil {\n\t\t\tlog.Warn(\"failed to delete lookahead entry\", \"key\", key, \"err\", err)\n\t\t}\n\n\t\t// re-add the record if it needs to be visited again in this window.\n\t\tif len(ar.Addrs) != 0 && ar.Addrs[0].Expiry <= gc.currWindowEnd {\n\t\t\tgcKey := gcLookaheadBase.ChildString(fmt.Sprintf(\"%d/%s\", ar.Addrs[0].Expiry, key.Name()))\n\t\t\tif err := batch.Put(context.TODO(), gcKey, []byte{}); err != nil {\n\t\t\t\tlog.Warn(\"failed to add new GC key\", \"key\", gcKey, \"err\", err)\n\t\t\t}\n\t\t}\n\t}\n\n\tresults, err := gc.ab.ds.Query(context.TODO(), purgeLookaheadQuery)\n\tif err != nil {\n\t\tlog.Warn(\"failed while fetching entries to purge\", \"err\", err)\n\t\treturn\n\t}\n\tdefer results.Close()\n\n\tnow := gc.ab.clock.Now().Unix()\n\n\t// keys: \t/peers/gc/addrs/<unix timestamp of next visit>/<peer ID b32>\n\t// values: \tnil\n\tfor result := range results.Next() {\n\t\tgcKey := ds.RawKey(result.Key)\n\t\tts, err := strconv.ParseInt(gcKey.Parent().Name(), 10, 64)\n\t\tif err != nil {\n\t\t\tdropInError(gcKey, err, \"parsing timestamp\")\n\t\t\tlog.Warn(\"failed while parsing timestamp from key\", \"key\", result.Key, \"err\", err)\n\t\t\tcontinue\n\t\t} else if ts > now {\n\t\t\t// this is an ordered cursor; when we hit an entry with a timestamp beyond now, we can break.\n\t\t\tbreak\n\t\t}\n\n\t\tidb32, err := b32.RawStdEncoding.DecodeString(gcKey.Name())\n\t\tif err != nil {\n\t\t\tdropInError(gcKey, err, \"parsing peer ID\")\n\t\t\tlog.Warn(\"failed while parsing b32 peer ID from key\", \"key\", result.Key, \"err\", err)\n\t\t\tcontinue\n\t\t}\n\n\t\tid, err = peer.IDFromBytes(idb32)\n\t\tif err != nil {\n\t\t\tdropInError(gcKey, err, \"decoding peer ID\")\n\t\t\tlog.Warn(\"failed while decoding peer ID from key\", \"key\", result.Key, \"err\", err)\n\t\t\tcontinue\n\t\t}\n\n\t\t// if the record is in cache, we clean it and flush it if necessary.\n\t\tif cached, ok := gc.ab.cache.Peek(id); ok {\n\t\t\tcached.Lock()\n\t\t\tif cached.clean(gc.ab.clock.Now()) {\n\t\t\t\tif err = cached.flush(batch); err != nil {\n\t\t\t\t\tlog.Warn(\"failed to flush entry modified by GC for peer\", \"peer\", id, \"err\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t\tdropOrReschedule(gcKey, cached)\n\t\t\tcached.Unlock()\n\t\t\tcontinue\n\t\t}\n\n\t\trecord.Reset()\n\n\t\t// otherwise, fetch it from the store, clean it and flush it.\n\t\tentryKey := addrBookBase.ChildString(gcKey.Name())\n\t\tval, err := gc.ab.ds.Get(context.TODO(), entryKey)\n\t\tif err != nil {\n\t\t\t// captures all errors, including ErrNotFound.\n\t\t\tdropInError(gcKey, err, \"fetching entry\")\n\t\t\tcontinue\n\t\t}\n\t\terr = proto.Unmarshal(val, record)\n\t\tif err != nil {\n\t\t\tdropInError(gcKey, err, \"unmarshalling entry\")\n\t\t\tcontinue\n\t\t}\n\t\tif record.clean(gc.ab.clock.Now()) {\n\t\t\terr = record.flush(batch)\n\t\t\tif err != nil {\n\t\t\t\tlog.Warn(\"failed to flush entry modified by GC for peer\", \"peer\", id, \"err\", err)\n\t\t\t}\n\t\t}\n\t\tdropOrReschedule(gcKey, record)\n\t}\n\n\tif err = batch.Commit(context.TODO()); err != nil {\n\t\tlog.Warn(\"failed to commit GC purge batch\", \"err\", err)\n\t}\n}\n\nfunc (gc *dsAddrBookGc) purgeStore() {\n\tselect {\n\tcase gc.running <- struct{}{}:\n\t\tdefer func() { <-gc.running }()\n\tdefault:\n\t\t// yield if lookahead is running.\n\t\treturn\n\t}\n\n\trecord := &addrsRecord{AddrBookRecord: &pb.AddrBookRecord{}} // empty record to reuse and avoid allocs.\n\tbatch, err := newCyclicBatch(gc.ab.ds, defaultOpsPerCyclicBatch)\n\tif err != nil {\n\t\tlog.Warn(\"failed while creating batch to purge GC entries\", \"err\", err)\n\t}\n\n\tresults, err := gc.ab.ds.Query(context.TODO(), purgeStoreQuery)\n\tif err != nil {\n\t\tlog.Warn(\"failed while opening iterator\", \"err\", err)\n\t\treturn\n\t}\n\tdefer results.Close()\n\n\t// keys: \t/peers/addrs/<peer ID b32>\n\tfor result := range results.Next() {\n\t\trecord.Reset()\n\t\tif err = proto.Unmarshal(result.Value, record); err != nil {\n\t\t\tlog.Warn(\"failed to unmarshal record during GC purge\", \"key\", result.Key, \"err\", err)\n\t\t\tcontinue\n\t\t}\n\n\t\tid := record.Id\n\t\tif !record.clean(gc.ab.clock.Now()) {\n\t\t\tcontinue\n\t\t}\n\n\t\tif err := record.flush(batch); err != nil {\n\t\t\tlog.Warn(\"failed to flush entry modified by GC for peer\", \"peer\", id, \"err\", err)\n\t\t}\n\t\tgc.ab.cache.Remove(peer.ID(id))\n\t}\n\n\tif err = batch.Commit(context.TODO()); err != nil {\n\t\tlog.Warn(\"failed to commit GC purge batch\", \"err\", err)\n\t}\n}\n\n// populateLookahead populates the lookahead window by scanning the entire store and picking entries whose earliest\n// expiration falls within the window period.\n//\n// Those entries are stored in the lookahead region in the store, indexed by the timestamp when they need to be\n// visited, to facilitate temporal range scans.\nfunc (gc *dsAddrBookGc) populateLookahead() {\n\tif gc.ab.opts.GCLookaheadInterval == 0 {\n\t\treturn\n\t}\n\n\tselect {\n\tcase gc.running <- struct{}{}:\n\t\tdefer func() { <-gc.running }()\n\tdefault:\n\t\t// yield if something's running.\n\t\treturn\n\t}\n\n\tuntil := gc.ab.clock.Now().Add(gc.ab.opts.GCLookaheadInterval).Unix()\n\n\tvar id peer.ID\n\trecord := &addrsRecord{AddrBookRecord: &pb.AddrBookRecord{}}\n\tresults, err := gc.ab.ds.Query(context.TODO(), populateLookaheadQuery)\n\tif err != nil {\n\t\tlog.Warn(\"failed while querying to populate lookahead GC window\", \"err\", err)\n\t\treturn\n\t}\n\tdefer results.Close()\n\n\tbatch, err := newCyclicBatch(gc.ab.ds, defaultOpsPerCyclicBatch)\n\tif err != nil {\n\t\tlog.Warn(\"failed while creating batch to populate lookahead GC window\", \"err\", err)\n\t\treturn\n\t}\n\n\tfor result := range results.Next() {\n\t\tidb32 := ds.RawKey(result.Key).Name()\n\t\tk, err := b32.RawStdEncoding.DecodeString(idb32)\n\t\tif err != nil {\n\t\t\tlog.Warn(\"failed while decoding peer ID from key\", \"key\", result.Key, \"err\", err)\n\t\t\tcontinue\n\t\t}\n\t\tif id, err = peer.IDFromBytes(k); err != nil {\n\t\t\tlog.Warn(\"failed while decoding peer ID from key\", \"key\", result.Key, \"err\", err)\n\t\t}\n\n\t\t// if the record is in cache, use the cached version.\n\t\tif cached, ok := gc.ab.cache.Peek(id); ok {\n\t\t\tcached.RLock()\n\t\t\tif len(cached.Addrs) == 0 || cached.Addrs[0].Expiry > until {\n\t\t\t\tcached.RUnlock()\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tgcKey := gcLookaheadBase.ChildString(fmt.Sprintf(\"%d/%s\", cached.Addrs[0].Expiry, idb32))\n\t\t\tif err = batch.Put(context.TODO(), gcKey, []byte{}); err != nil {\n\t\t\t\tlog.Warn(\"failed while inserting GC entry for peer\", \"peer\", id, \"err\", err)\n\t\t\t}\n\t\t\tcached.RUnlock()\n\t\t\tcontinue\n\t\t}\n\n\t\trecord.Reset()\n\n\t\tval, err := gc.ab.ds.Get(context.TODO(), ds.RawKey(result.Key))\n\t\tif err != nil {\n\t\t\tlog.Warn(\"failed which getting record from store for peer\", \"peer\", id, \"err\", err)\n\t\t\tcontinue\n\t\t}\n\t\tif err := proto.Unmarshal(val, record); err != nil {\n\t\t\tlog.Warn(\"failed while unmarshalling record from store for peer\", \"peer\", id, \"err\", err)\n\t\t\tcontinue\n\t\t}\n\t\tif len(record.Addrs) > 0 && record.Addrs[0].Expiry <= until {\n\t\t\tgcKey := gcLookaheadBase.ChildString(fmt.Sprintf(\"%d/%s\", record.Addrs[0].Expiry, idb32))\n\t\t\tif err = batch.Put(context.TODO(), gcKey, []byte{}); err != nil {\n\t\t\t\tlog.Warn(\"failed while inserting GC entry for peer\", \"peer\", id, \"err\", err)\n\t\t\t}\n\t\t}\n\t}\n\n\tif err = batch.Commit(context.TODO()); err != nil {\n\t\tlog.Warn(\"failed to commit GC lookahead batch\", \"err\", err)\n\t}\n\n\tgc.currWindowEnd = until\n}\n\n// orderByTimestampInKey orders the results by comparing the timestamp in the\n// key. A lexiographic sort by itself is wrong since \"10\" is less than \"2\", but\n// as an int 2 is obviously less than 10.\nfunc orderByTimestampInKey(a, b query.Entry) int {\n\taKey := ds.RawKey(a.Key)\n\taInt, err := strconv.ParseInt(aKey.Parent().Name(), 10, 64)\n\tif err != nil {\n\t\treturn -1\n\t}\n\tbKey := ds.RawKey(b.Key)\n\tbInt, err := strconv.ParseInt(bKey.Parent().Name(), 10, 64)\n\tif err != nil {\n\t\treturn -1\n\t}\n\tif aInt < bInt {\n\t\treturn -1\n\t} else if aInt == bInt {\n\t\treturn 0\n\t}\n\treturn 1\n}\n"
  },
  {
    "path": "p2p/host/peerstore/pstoreds/addr_book_gc_test.go",
    "content": "package pstoreds\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\tpstore \"github.com/libp2p/go-libp2p/core/peerstore\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/peerstore/test\"\n\n\tmockClock \"github.com/benbjohnson/clock\"\n\t\"github.com/ipfs/go-datastore/query\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nvar lookaheadQuery = query.Query{Prefix: gcLookaheadBase.String(), KeysOnly: true}\n\ntype testProbe struct {\n\tt  *testing.T\n\tab pstore.AddrBook\n}\n\nfunc (tp *testProbe) countLookaheadEntries() (i int) {\n\tresults, err := tp.ab.(*dsAddrBook).ds.Query(context.Background(), lookaheadQuery)\n\tif err != nil {\n\t\ttp.t.Fatal(err)\n\t}\n\n\tdefer results.Close()\n\tfor range results.Next() {\n\t\ti++\n\t}\n\treturn i\n}\n\nfunc (tp *testProbe) clearCache() {\n\tfor _, k := range tp.ab.(*dsAddrBook).cache.Keys() {\n\t\ttp.ab.(*dsAddrBook).cache.Remove(k)\n\t}\n}\n\nfunc TestGCLookahead(t *testing.T) {\n\topts := DefaultOpts()\n\n\t// effectively disable automatic GC for this test.\n\topts.GCInitialDelay = 90 * time.Hour\n\topts.GCLookaheadInterval = 10 * time.Second\n\topts.GCPurgeInterval = 1 * time.Second\n\n\tfactory := addressBookFactory(t, mapDBStore, opts)\n\tab, closeFn := factory()\n\tgc := ab.(*dsAddrBook).gc\n\tdefer closeFn()\n\n\ttp := &testProbe{t, ab}\n\n\tids := test.GeneratePeerIDs(10)\n\taddrs := test.GenerateAddrs(100)\n\n\t// lookahead is 10 seconds, so these entries will be outside the lookahead window.\n\tab.AddAddrs(ids[0], addrs[:10], time.Hour)\n\tab.AddAddrs(ids[1], addrs[10:20], time.Hour)\n\tab.AddAddrs(ids[2], addrs[20:30], time.Hour)\n\n\tgc.populateLookahead()\n\tif i := tp.countLookaheadEntries(); i != 0 {\n\t\tt.Errorf(\"expected no GC lookahead entries, got: %v\", i)\n\t}\n\n\t// change addresses of a peer to have TTL 1 second, placing them in the lookahead window.\n\tab.UpdateAddrs(ids[1], time.Hour, time.Second)\n\n\t// Purge the cache, to exercise a different path in the lookahead cycle.\n\ttp.clearCache()\n\n\tgc.populateLookahead()\n\tif i := tp.countLookaheadEntries(); i != 1 {\n\t\tt.Errorf(\"expected 1 GC lookahead entry, got: %v\", i)\n\t}\n\n\t// change addresses of another to have TTL 5 second, placing them in the lookahead window.\n\tab.UpdateAddrs(ids[2], time.Hour, 5*time.Second)\n\tgc.populateLookahead()\n\tif i := tp.countLookaheadEntries(); i != 2 {\n\t\tt.Errorf(\"expected 2 GC lookahead entries, got: %v\", i)\n\t}\n}\n\nfunc TestGCPurging(t *testing.T) {\n\topts := DefaultOpts()\n\n\t// effectively disable automatic GC for this test.\n\topts.GCInitialDelay = 90 * time.Hour\n\topts.GCLookaheadInterval = 20 * time.Second\n\topts.GCPurgeInterval = 1 * time.Second\n\tclk := mockClock.NewMock()\n\topts.Clock = clk\n\n\tfactory := addressBookFactory(t, mapDBStore, opts)\n\tab, closeFn := factory()\n\tgc := ab.(*dsAddrBook).gc\n\tdefer closeFn()\n\n\ttp := &testProbe{t, ab}\n\n\tids := test.GeneratePeerIDs(10)\n\taddrs := test.GenerateAddrs(100)\n\n\t// stagger addresses within the lookahead window, but stagger them.\n\tab.AddAddrs(ids[0], addrs[:10], 1*time.Second)\n\tab.AddAddrs(ids[1], addrs[30:40], 1*time.Second)\n\tab.AddAddrs(ids[2], addrs[60:70], 1*time.Second)\n\n\tab.AddAddrs(ids[0], addrs[10:20], 4*time.Second)\n\tab.AddAddrs(ids[1], addrs[40:50], 4*time.Second)\n\n\tab.AddAddrs(ids[0], addrs[20:30], 10*time.Second)\n\tab.AddAddrs(ids[1], addrs[50:60], 10*time.Second)\n\n\t// this is inside the window, but it will survive the purges we do in the test.\n\tab.AddAddrs(ids[3], addrs[70:80], 15*time.Second)\n\n\tgc.populateLookahead()\n\tif i := tp.countLookaheadEntries(); i != 4 {\n\t\tt.Errorf(\"expected 4 GC lookahead entries, got: %v\", i)\n\t}\n\n\tclk.Add(2 * time.Second)\n\tgc.purgeLookahead()\n\tif i := tp.countLookaheadEntries(); i != 3 {\n\t\tt.Errorf(\"expected 3 GC lookahead entries, got: %v\", i)\n\t}\n\n\t// Purge the cache, to exercise a different path in the purge cycle.\n\ttp.clearCache()\n\n\tclk.Add(5 * time.Second)\n\tgc.purgeLookahead()\n\tif i := tp.countLookaheadEntries(); i != 3 {\n\t\tt.Errorf(\"expected 3 GC lookahead entries, got: %v\", i)\n\t}\n\n\tclk.Add(5 * time.Second)\n\tgc.purgeLookahead()\n\tif i := tp.countLookaheadEntries(); i != 1 {\n\t\tt.Errorf(\"expected 1 GC lookahead entries, got: %v\", i)\n\t}\n\tif i := len(ab.PeersWithAddrs()); i != 1 {\n\t\tt.Errorf(\"expected 1 entries in database, got: %v\", i)\n\t}\n\tif p := ab.PeersWithAddrs()[0]; p != ids[3] {\n\t\tt.Errorf(\"expected remaining peer to be #3, got: %v, expected: %v\", p, ids[3])\n\t}\n}\n\nfunc TestGCDelay(t *testing.T) {\n\tids := test.GeneratePeerIDs(10)\n\taddrs := test.GenerateAddrs(100)\n\n\tclk := mockClock.NewMock()\n\topts := DefaultOpts()\n\topts.GCInitialDelay = 2 * time.Second\n\topts.GCLookaheadInterval = 1 * time.Minute\n\topts.GCPurgeInterval = 30 * time.Second\n\topts.Clock = clk\n\n\tfactory := addressBookFactory(t, mapDBStore, opts)\n\tab, closeFn := factory()\n\tdefer closeFn()\n\t// give the background Go routine some time to start\n\ttime.Sleep(100 * time.Millisecond)\n\n\ttp := &testProbe{t, ab}\n\n\tab.AddAddrs(ids[0], addrs, 1*time.Second)\n\n\t// immediately after we should be having no lookahead entries.\n\tif i := tp.countLookaheadEntries(); i != 0 {\n\t\tt.Fatalf(\"expected no lookahead entries, got: %d\", i)\n\t}\n\n\t// after the initial delay has passed.\n\tclk.Add(3 * time.Second)\n\trequire.Eventually(t, func() bool { return tp.countLookaheadEntries() == 1 }, 3000*time.Millisecond, 10*time.Millisecond, \"expected 1 lookahead entry\")\n}\n\nfunc TestGCLookaheadDisabled(t *testing.T) {\n\tids := test.GeneratePeerIDs(10)\n\taddrs := test.GenerateAddrs(100)\n\n\topts := DefaultOpts()\n\n\t// effectively disable automatic GC for this test.\n\topts.GCInitialDelay = 90 * time.Hour\n\topts.GCLookaheadInterval = 0 // disable lookahead\n\topts.GCPurgeInterval = 9 * time.Hour\n\tclk := mockClock.NewMock()\n\topts.Clock = clk\n\n\tfactory := addressBookFactory(t, mapDBStore, opts)\n\tab, closeFn := factory()\n\tdefer closeFn()\n\n\ttp := &testProbe{t, ab}\n\n\t// four peers:\n\t//   ids[0] has 10 addresses, all of which expire in 500ms.\n\t//   ids[1] has 20 addresses; 50% expire in 500ms and 50% in 10 hours.\n\t//   ids[2] has 10 addresses; all expire in 10 hours.\n\t//   ids[3] has 60 addresses; all expire in 10 hours.\n\tab.AddAddrs(ids[0], addrs[:10], 500*time.Millisecond)\n\tab.AddAddrs(ids[1], addrs[10:20], 500*time.Millisecond)\n\tab.AddAddrs(ids[1], addrs[20:30], 10*time.Hour)\n\tab.AddAddrs(ids[2], addrs[30:40], 10*time.Hour)\n\tab.AddAddrs(ids[3], addrs[40:], 10*time.Hour)\n\n\tclk.Add(100 * time.Millisecond)\n\n\tif i := tp.countLookaheadEntries(); i != 0 {\n\t\tt.Errorf(\"expected no GC lookahead entries, got: %v\", i)\n\t}\n\n\tclk.Add(500 * time.Millisecond)\n\tgc := ab.(*dsAddrBook).gc\n\tgc.purgeFunc()\n\n\tvar empty []ma.Multiaddr\n\ttest.AssertAddressesEqual(t, empty, ab.Addrs(ids[0]))\n\ttest.AssertAddressesEqual(t, addrs[20:30], ab.Addrs(ids[1]))\n\ttest.AssertAddressesEqual(t, addrs[30:40], ab.Addrs(ids[2]))\n\ttest.AssertAddressesEqual(t, addrs[40:], ab.Addrs(ids[3]))\n}\n\nfunc BenchmarkLookaheadCycle(b *testing.B) {\n\tids := test.GeneratePeerIDs(100)\n\taddrs := test.GenerateAddrs(100)\n\n\topts := DefaultOpts()\n\n\topts.GCInitialDelay = 2 * time.Hour\n\topts.GCLookaheadInterval = 2 * time.Hour\n\topts.GCPurgeInterval = 6 * time.Hour\n\n\tfactory := addressBookFactory(b, mapDBStore, opts)\n\tab, closeFn := factory()\n\tdefer closeFn()\n\n\tinside, outside := 1*time.Minute, 48*time.Hour\n\tfor i, id := range ids {\n\t\tvar ttl time.Duration\n\t\tif i%2 == 0 {\n\t\t\tttl = inside\n\t\t} else {\n\t\t\tttl = outside\n\t\t}\n\t\tab.AddAddrs(id, addrs, ttl)\n\t}\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tab.(*dsAddrBook).gc.populateLookahead()\n\t}\n}\n"
  },
  {
    "path": "p2p/host/peerstore/pstoreds/cache.go",
    "content": "package pstoreds\n\n// cache abstracts all methods we access from ARCCache, to enable alternate\n// implementations such as a no-op one.\ntype cache[K comparable, V any] interface {\n\tGet(key K) (value V, ok bool)\n\tAdd(key K, value V)\n\tRemove(key K)\n\tContains(key K) bool\n\tPeek(key K) (value V, ok bool)\n\tKeys() []K\n}\n\n// noopCache is a dummy implementation that's used when the cache is disabled.\ntype noopCache[K comparable, V any] struct {\n}\n\nvar _ cache[int, int] = (*noopCache[int, int])(nil)\n\nfunc (*noopCache[K, V]) Get(_ K) (value V, ok bool) {\n\treturn *new(V), false\n}\n\nfunc (*noopCache[K, V]) Add(_ K, _ V) {\n}\n\nfunc (*noopCache[K, V]) Remove(_ K) {\n}\n\nfunc (*noopCache[K, V]) Contains(_ K) bool {\n\treturn false\n}\n\nfunc (*noopCache[K, V]) Peek(_ K) (value V, ok bool) {\n\treturn *new(V), false\n}\n\nfunc (*noopCache[K, V]) Keys() (keys []K) {\n\treturn keys\n}\n"
  },
  {
    "path": "p2p/host/peerstore/pstoreds/cyclic_batch.go",
    "content": "package pstoreds\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\n\tds \"github.com/ipfs/go-datastore\"\n)\n\n// how many operations are queued in a cyclic batch before we flush it.\nvar defaultOpsPerCyclicBatch = 20\n\n// cyclicBatch buffers ds write operations and automatically flushes them after defaultOpsPerCyclicBatch (20) have been\n// queued. An explicit `Commit()` closes this cyclic batch, erroring all further operations.\n//\n// It is similar to go-ds autobatch, but it's driven by an actual Batch facility offered by the\n// ds.\ntype cyclicBatch struct {\n\tthreshold int\n\tds.Batch\n\tds      ds.Batching\n\tpending int\n}\n\nfunc newCyclicBatch(ds ds.Batching, _ int) (ds.Batch, error) {\n\tbatch, err := ds.Batch(context.TODO())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &cyclicBatch{Batch: batch, ds: ds}, nil\n}\n\nfunc (cb *cyclicBatch) cycle() (err error) {\n\tif cb.Batch == nil {\n\t\treturn errors.New(\"cyclic batch is closed\")\n\t}\n\tif cb.pending < cb.threshold {\n\t\t// we haven't reached the threshold yet.\n\t\treturn nil\n\t}\n\t// commit and renew the batch.\n\tif err = cb.Batch.Commit(context.TODO()); err != nil {\n\t\treturn fmt.Errorf(\"failed while committing cyclic batch: %w\", err)\n\t}\n\tif cb.Batch, err = cb.ds.Batch(context.TODO()); err != nil {\n\t\treturn fmt.Errorf(\"failed while renewing cyclic batch: %w\", err)\n\t}\n\treturn nil\n}\n\nfunc (cb *cyclicBatch) Put(ctx context.Context, key ds.Key, val []byte) error {\n\tif err := cb.cycle(); err != nil {\n\t\treturn err\n\t}\n\tcb.pending++\n\treturn cb.Batch.Put(ctx, key, val)\n}\n\nfunc (cb *cyclicBatch) Delete(ctx context.Context, key ds.Key) error {\n\tif err := cb.cycle(); err != nil {\n\t\treturn err\n\t}\n\tcb.pending++\n\treturn cb.Batch.Delete(ctx, key)\n}\n\nfunc (cb *cyclicBatch) Commit(ctx context.Context) error {\n\tif cb.Batch == nil {\n\t\treturn errors.New(\"cyclic batch is closed\")\n\t}\n\tif err := cb.Batch.Commit(ctx); err != nil {\n\t\treturn err\n\t}\n\tcb.pending = 0\n\tcb.Batch = nil\n\treturn nil\n}\n"
  },
  {
    "path": "p2p/host/peerstore/pstoreds/deprecate.go",
    "content": "// Deprecated: The database-backed peerstore will be removed from go-libp2p in the future.\n// Use the memory peerstore (pstoremem) instead.\n// For more details see https://github.com/libp2p/go-libp2p/issues/2329\n// and https://github.com/libp2p/go-libp2p/issues/2355.\npackage pstoreds\n"
  },
  {
    "path": "p2p/host/peerstore/pstoreds/ds_test.go",
    "content": "package pstoreds\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\tpstore \"github.com/libp2p/go-libp2p/core/peerstore\"\n\tpt \"github.com/libp2p/go-libp2p/p2p/host/peerstore/test\"\n\n\tmockclock \"github.com/benbjohnson/clock\"\n\tds \"github.com/ipfs/go-datastore\"\n\t\"github.com/ipfs/go-datastore/sync\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc mapDBStore(_ testing.TB) (ds.Batching, func()) {\n\tstore := ds.NewMapDatastore()\n\tcloser := func() {\n\t\tstore.Close()\n\t}\n\treturn sync.MutexWrap(store), closer\n}\n\ntype datastoreFactory func(tb testing.TB) (ds.Batching, func())\n\nvar dstores = map[string]datastoreFactory{\n\t\"MapDB\": mapDBStore,\n}\n\nfunc TestDsPeerstore(t *testing.T) {\n\tfor name, dsFactory := range dstores {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tpt.TestPeerstore(t, peerstoreFactory(t, dsFactory, DefaultOpts()))\n\t\t})\n\n\t\tt.Run(\"protobook limits\", func(t *testing.T) {\n\t\t\tconst limit = 10\n\t\t\topts := DefaultOpts()\n\t\t\topts.MaxProtocols = limit\n\t\t\tds, close := dsFactory(t)\n\t\t\tdefer close()\n\t\t\tps, err := NewPeerstore(context.Background(), ds, opts)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer ps.Close()\n\t\t\tpt.TestPeerstoreProtoStoreLimits(t, ps, limit)\n\t\t})\n\t}\n}\n\nfunc TestDsAddrBook(t *testing.T) {\n\tfor name, dsFactory := range dstores {\n\t\tt.Run(name+\" Cacheful\", func(t *testing.T) {\n\t\t\topts := DefaultOpts()\n\t\t\topts.GCPurgeInterval = 1 * time.Second\n\t\t\topts.CacheSize = 1024\n\t\t\tclk := mockclock.NewMock()\n\t\t\topts.Clock = clk\n\n\t\t\tpt.TestAddrBook(t, addressBookFactory(t, dsFactory, opts), clk)\n\t\t})\n\n\t\tt.Run(name+\" Cacheless\", func(t *testing.T) {\n\t\t\topts := DefaultOpts()\n\t\t\topts.GCPurgeInterval = 1 * time.Second\n\t\t\topts.CacheSize = 0\n\t\t\tclk := mockclock.NewMock()\n\t\t\topts.Clock = clk\n\n\t\t\tpt.TestAddrBook(t, addressBookFactory(t, dsFactory, opts), clk)\n\t\t})\n\t}\n}\n\nfunc TestDsKeyBook(t *testing.T) {\n\tfor name, dsFactory := range dstores {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tpt.TestKeyBook(t, keyBookFactory(t, dsFactory, DefaultOpts()))\n\t\t})\n\t}\n}\n\nfunc BenchmarkDsKeyBook(b *testing.B) {\n\tfor name, dsFactory := range dstores {\n\t\tb.Run(name, func(b *testing.B) {\n\t\t\tpt.BenchmarkKeyBook(b, keyBookFactory(b, dsFactory, DefaultOpts()))\n\t\t})\n\t}\n}\n\nfunc BenchmarkDsPeerstore(b *testing.B) {\n\tcaching := DefaultOpts()\n\tcaching.CacheSize = 1024\n\n\tcacheless := DefaultOpts()\n\tcacheless.CacheSize = 0\n\n\tfor name, dsFactory := range dstores {\n\t\tb.Run(name, func(b *testing.B) {\n\t\t\tpt.BenchmarkPeerstore(b, peerstoreFactory(b, dsFactory, caching), \"Caching\")\n\t\t})\n\t\tb.Run(name, func(b *testing.B) {\n\t\t\tpt.BenchmarkPeerstore(b, peerstoreFactory(b, dsFactory, cacheless), \"Cacheless\")\n\t\t})\n\t}\n}\n\nfunc peerstoreFactory(tb testing.TB, storeFactory datastoreFactory, opts Options) pt.PeerstoreFactory {\n\treturn func() (pstore.Peerstore, func()) {\n\t\tstore, storeCloseFn := storeFactory(tb)\n\t\tps, err := NewPeerstore(context.Background(), store, opts)\n\t\tif err != nil {\n\t\t\ttb.Fatal(err)\n\t\t}\n\t\tcloser := func() {\n\t\t\tps.Close()\n\t\t\tstoreCloseFn()\n\t\t}\n\t\treturn ps, closer\n\t}\n}\n\nfunc addressBookFactory(tb testing.TB, storeFactory datastoreFactory, opts Options) pt.AddrBookFactory {\n\treturn func() (pstore.AddrBook, func()) {\n\t\tstore, closeFunc := storeFactory(tb)\n\t\tab, err := NewAddrBook(context.Background(), store, opts)\n\t\tif err != nil {\n\t\t\ttb.Fatal(err)\n\t\t}\n\t\tcloser := func() {\n\t\t\tab.Close()\n\t\t\tcloseFunc()\n\t\t}\n\t\treturn ab, closer\n\t}\n}\n\nfunc keyBookFactory(tb testing.TB, storeFactory datastoreFactory, opts Options) pt.KeyBookFactory {\n\treturn func() (pstore.KeyBook, func()) {\n\t\tstore, storeCloseFn := storeFactory(tb)\n\t\tkb, err := NewKeyBook(context.Background(), store, opts)\n\t\tif err != nil {\n\t\t\ttb.Fatal(err)\n\t\t}\n\t\treturn kb, storeCloseFn\n\t}\n}\n"
  },
  {
    "path": "p2p/host/peerstore/pstoreds/keybook.go",
    "content": "package pstoreds\n\nimport (\n\t\"context\"\n\t\"errors\"\n\n\tic \"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\tpstore \"github.com/libp2p/go-libp2p/core/peerstore\"\n\n\tds \"github.com/ipfs/go-datastore\"\n\t\"github.com/ipfs/go-datastore/query\"\n\t\"github.com/multiformats/go-base32\"\n)\n\n// Public and private keys are stored under the following db key pattern:\n// /peers/keys/<b32 peer id no padding>/{pub, priv}\nvar (\n\tkbBase     = ds.NewKey(\"/peers/keys\")\n\tpubSuffix  = ds.NewKey(\"/pub\")\n\tprivSuffix = ds.NewKey(\"/priv\")\n)\n\ntype dsKeyBook struct {\n\tds ds.Datastore\n}\n\nvar _ pstore.KeyBook = (*dsKeyBook)(nil)\n\nfunc NewKeyBook(_ context.Context, store ds.Datastore, _ Options) (*dsKeyBook, error) {\n\treturn &dsKeyBook{store}, nil\n}\n\nfunc (kb *dsKeyBook) PubKey(p peer.ID) ic.PubKey {\n\tkey := peerToKey(p, pubSuffix)\n\n\tvar pk ic.PubKey\n\tif value, err := kb.ds.Get(context.TODO(), key); err == nil {\n\t\tpk, err = ic.UnmarshalPublicKey(value)\n\t\tif err != nil {\n\t\t\tlog.Error(\"error when unmarshalling pubkey from datastore for peer\", \"peer\", p, \"err\", err)\n\t\t}\n\t} else if err == ds.ErrNotFound {\n\t\tpk, err = p.ExtractPublicKey()\n\t\tswitch err {\n\t\tcase nil:\n\t\tcase peer.ErrNoPublicKey:\n\t\t\treturn nil\n\t\tdefault:\n\t\t\tlog.Error(\"error when extracting pubkey from peer ID for peer\", \"peer\", p, \"err\", err)\n\t\t\treturn nil\n\t\t}\n\t\tpkb, err := ic.MarshalPublicKey(pk)\n\t\tif err != nil {\n\t\t\tlog.Error(\"error when turning extracted pubkey into bytes for peer\", \"peer\", p, \"err\", err)\n\t\t\treturn nil\n\t\t}\n\t\tif err := kb.ds.Put(context.TODO(), key, pkb); err != nil {\n\t\t\tlog.Error(\"error when adding extracted pubkey to peerstore for peer\", \"peer\", p, \"err\", err)\n\t\t\treturn nil\n\t\t}\n\t} else {\n\t\tlog.Error(\"error when fetching pubkey from datastore for peer\", \"peer\", p, \"err\", err)\n\t}\n\n\treturn pk\n}\n\nfunc (kb *dsKeyBook) AddPubKey(p peer.ID, pk ic.PubKey) error {\n\t// check it's correct.\n\tif !p.MatchesPublicKey(pk) {\n\t\treturn errors.New(\"peer ID does not match public key\")\n\t}\n\n\tval, err := ic.MarshalPublicKey(pk)\n\tif err != nil {\n\t\tlog.Error(\"error while converting pubkey byte string for peer\", \"peer\", p, \"err\", err)\n\t\treturn err\n\t}\n\tif err := kb.ds.Put(context.TODO(), peerToKey(p, pubSuffix), val); err != nil {\n\t\tlog.Error(\"error while updating pubkey in datastore for peer\", \"peer\", p, \"err\", err)\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (kb *dsKeyBook) PrivKey(p peer.ID) ic.PrivKey {\n\tvalue, err := kb.ds.Get(context.TODO(), peerToKey(p, privSuffix))\n\tif err != nil {\n\t\treturn nil\n\t}\n\tsk, err := ic.UnmarshalPrivateKey(value)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn sk\n}\n\nfunc (kb *dsKeyBook) AddPrivKey(p peer.ID, sk ic.PrivKey) error {\n\tif sk == nil {\n\t\treturn errors.New(\"private key is nil\")\n\t}\n\t// check it's correct.\n\tif !p.MatchesPrivateKey(sk) {\n\t\treturn errors.New(\"peer ID does not match private key\")\n\t}\n\n\tval, err := ic.MarshalPrivateKey(sk)\n\tif err != nil {\n\t\tlog.Error(\"error while converting privkey byte string for peer\", \"peer\", p, \"err\", err)\n\t\treturn err\n\t}\n\tif err := kb.ds.Put(context.TODO(), peerToKey(p, privSuffix), val); err != nil {\n\t\tlog.Error(\"error while updating privkey in datastore for peer\", \"peer\", p, \"err\", err)\n\t}\n\treturn err\n}\n\nfunc (kb *dsKeyBook) PeersWithKeys() peer.IDSlice {\n\tids, err := uniquePeerIds(kb.ds, kbBase, func(result query.Result) string {\n\t\treturn ds.RawKey(result.Key).Parent().Name()\n\t})\n\tif err != nil {\n\t\tlog.Error(\"error while retrieving peers with keys\", \"err\", err)\n\t}\n\treturn ids\n}\n\nfunc (kb *dsKeyBook) RemovePeer(p peer.ID) {\n\tkb.ds.Delete(context.TODO(), peerToKey(p, privSuffix))\n\tkb.ds.Delete(context.TODO(), peerToKey(p, pubSuffix))\n}\n\nfunc peerToKey(p peer.ID, suffix ds.Key) ds.Key {\n\treturn kbBase.ChildString(base32.RawStdEncoding.EncodeToString([]byte(p))).Child(suffix)\n}\n"
  },
  {
    "path": "p2p/host/peerstore/pstoreds/metadata.go",
    "content": "package pstoreds\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/gob\"\n\n\tpool \"github.com/libp2p/go-buffer-pool\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\tpstore \"github.com/libp2p/go-libp2p/core/peerstore\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\n\tds \"github.com/ipfs/go-datastore\"\n\t\"github.com/ipfs/go-datastore/query\"\n\t\"github.com/multiformats/go-base32\"\n)\n\n// Metadata is stored under the following db key pattern:\n// /peers/metadata/<b32 peer id no padding>/<key>\nvar pmBase = ds.NewKey(\"/peers/metadata\")\n\ntype dsPeerMetadata struct {\n\tds ds.Datastore\n}\n\nvar _ pstore.PeerMetadata = (*dsPeerMetadata)(nil)\n\nfunc init() {\n\t// Gob registers basic types by default.\n\t//\n\t// Register complex types used by the peerstore itself.\n\tgob.Register(make(map[protocol.ID]struct{}))\n}\n\n// NewPeerMetadata creates a metadata store backed by a persistent db. It uses gob for serialisation.\n//\n// See `init()` to learn which types are registered by default. Modules wishing to store\n// values of other types will need to `gob.Register()` them explicitly, or else callers\n// will receive runtime errors.\nfunc NewPeerMetadata(_ context.Context, store ds.Datastore, _ Options) (*dsPeerMetadata, error) {\n\treturn &dsPeerMetadata{store}, nil\n}\n\nfunc (pm *dsPeerMetadata) Get(p peer.ID, key string) (any, error) {\n\tk := pmBase.ChildString(base32.RawStdEncoding.EncodeToString([]byte(p))).ChildString(key)\n\tvalue, err := pm.ds.Get(context.TODO(), k)\n\tif err != nil {\n\t\tif err == ds.ErrNotFound {\n\t\t\terr = pstore.ErrNotFound\n\t\t}\n\t\treturn nil, err\n\t}\n\n\tvar res any\n\tif err := gob.NewDecoder(bytes.NewReader(value)).Decode(&res); err != nil {\n\t\treturn nil, err\n\t}\n\treturn res, nil\n}\n\nfunc (pm *dsPeerMetadata) Put(p peer.ID, key string, val any) error {\n\tk := pmBase.ChildString(base32.RawStdEncoding.EncodeToString([]byte(p))).ChildString(key)\n\tvar buf pool.Buffer\n\tif err := gob.NewEncoder(&buf).Encode(&val); err != nil {\n\t\treturn err\n\t}\n\treturn pm.ds.Put(context.TODO(), k, buf.Bytes())\n}\n\nfunc (pm *dsPeerMetadata) RemovePeer(p peer.ID) {\n\tresult, err := pm.ds.Query(context.TODO(), query.Query{\n\t\tPrefix:   pmBase.ChildString(base32.RawStdEncoding.EncodeToString([]byte(p))).String(),\n\t\tKeysOnly: true,\n\t})\n\tif err != nil {\n\t\tlog.Warn(\"querying datastore when removing peer failed\", \"peer\", p, \"err\", err)\n\t\treturn\n\t}\n\tfor entry := range result.Next() {\n\t\tpm.ds.Delete(context.TODO(), ds.NewKey(entry.Key))\n\t}\n}\n"
  },
  {
    "path": "p2p/host/peerstore/pstoreds/pb/pstore.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.6\n// \tprotoc        v5.29.2\n// source: p2p/host/peerstore/pstoreds/pb/pstore.proto\n\npackage pb\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// AddrBookRecord represents a record for a peer in the address book.\ntype AddrBookRecord struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The peer ID.\n\tId []byte `protobuf:\"bytes,1,opt,name=id,proto3\" json:\"id,omitempty\"`\n\t// The multiaddresses. This is a sorted list where element 0 expires the soonest.\n\tAddrs []*AddrBookRecord_AddrEntry `protobuf:\"bytes,2,rep,name=addrs,proto3\" json:\"addrs,omitempty\"`\n\t// The most recently received signed PeerRecord.\n\tCertifiedRecord *AddrBookRecord_CertifiedRecord `protobuf:\"bytes,3,opt,name=certified_record,json=certifiedRecord,proto3\" json:\"certified_record,omitempty\"`\n\tunknownFields   protoimpl.UnknownFields\n\tsizeCache       protoimpl.SizeCache\n}\n\nfunc (x *AddrBookRecord) Reset() {\n\t*x = AddrBookRecord{}\n\tmi := &file_p2p_host_peerstore_pstoreds_pb_pstore_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *AddrBookRecord) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AddrBookRecord) ProtoMessage() {}\n\nfunc (x *AddrBookRecord) ProtoReflect() protoreflect.Message {\n\tmi := &file_p2p_host_peerstore_pstoreds_pb_pstore_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AddrBookRecord.ProtoReflect.Descriptor instead.\nfunc (*AddrBookRecord) Descriptor() ([]byte, []int) {\n\treturn file_p2p_host_peerstore_pstoreds_pb_pstore_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *AddrBookRecord) GetId() []byte {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn nil\n}\n\nfunc (x *AddrBookRecord) GetAddrs() []*AddrBookRecord_AddrEntry {\n\tif x != nil {\n\t\treturn x.Addrs\n\t}\n\treturn nil\n}\n\nfunc (x *AddrBookRecord) GetCertifiedRecord() *AddrBookRecord_CertifiedRecord {\n\tif x != nil {\n\t\treturn x.CertifiedRecord\n\t}\n\treturn nil\n}\n\n// AddrEntry represents a single multiaddress.\ntype AddrBookRecord_AddrEntry struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\tAddr  []byte                 `protobuf:\"bytes,1,opt,name=addr,proto3\" json:\"addr,omitempty\"`\n\t// The point in time when this address expires.\n\tExpiry int64 `protobuf:\"varint,2,opt,name=expiry,proto3\" json:\"expiry,omitempty\"`\n\t// The original TTL of this address.\n\tTtl           int64 `protobuf:\"varint,3,opt,name=ttl,proto3\" json:\"ttl,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *AddrBookRecord_AddrEntry) Reset() {\n\t*x = AddrBookRecord_AddrEntry{}\n\tmi := &file_p2p_host_peerstore_pstoreds_pb_pstore_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *AddrBookRecord_AddrEntry) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AddrBookRecord_AddrEntry) ProtoMessage() {}\n\nfunc (x *AddrBookRecord_AddrEntry) ProtoReflect() protoreflect.Message {\n\tmi := &file_p2p_host_peerstore_pstoreds_pb_pstore_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AddrBookRecord_AddrEntry.ProtoReflect.Descriptor instead.\nfunc (*AddrBookRecord_AddrEntry) Descriptor() ([]byte, []int) {\n\treturn file_p2p_host_peerstore_pstoreds_pb_pstore_proto_rawDescGZIP(), []int{0, 0}\n}\n\nfunc (x *AddrBookRecord_AddrEntry) GetAddr() []byte {\n\tif x != nil {\n\t\treturn x.Addr\n\t}\n\treturn nil\n}\n\nfunc (x *AddrBookRecord_AddrEntry) GetExpiry() int64 {\n\tif x != nil {\n\t\treturn x.Expiry\n\t}\n\treturn 0\n}\n\nfunc (x *AddrBookRecord_AddrEntry) GetTtl() int64 {\n\tif x != nil {\n\t\treturn x.Ttl\n\t}\n\treturn 0\n}\n\n// CertifiedRecord contains a serialized signed PeerRecord used to\n// populate the signedAddrs list.\ntype AddrBookRecord_CertifiedRecord struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The Seq counter from the signed PeerRecord envelope\n\tSeq uint64 `protobuf:\"varint,1,opt,name=seq,proto3\" json:\"seq,omitempty\"`\n\t// The serialized bytes of the SignedEnvelope containing the PeerRecord.\n\tRaw           []byte `protobuf:\"bytes,2,opt,name=raw,proto3\" json:\"raw,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *AddrBookRecord_CertifiedRecord) Reset() {\n\t*x = AddrBookRecord_CertifiedRecord{}\n\tmi := &file_p2p_host_peerstore_pstoreds_pb_pstore_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *AddrBookRecord_CertifiedRecord) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AddrBookRecord_CertifiedRecord) ProtoMessage() {}\n\nfunc (x *AddrBookRecord_CertifiedRecord) ProtoReflect() protoreflect.Message {\n\tmi := &file_p2p_host_peerstore_pstoreds_pb_pstore_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AddrBookRecord_CertifiedRecord.ProtoReflect.Descriptor instead.\nfunc (*AddrBookRecord_CertifiedRecord) Descriptor() ([]byte, []int) {\n\treturn file_p2p_host_peerstore_pstoreds_pb_pstore_proto_rawDescGZIP(), []int{0, 1}\n}\n\nfunc (x *AddrBookRecord_CertifiedRecord) GetSeq() uint64 {\n\tif x != nil {\n\t\treturn x.Seq\n\t}\n\treturn 0\n}\n\nfunc (x *AddrBookRecord_CertifiedRecord) GetRaw() []byte {\n\tif x != nil {\n\t\treturn x.Raw\n\t}\n\treturn nil\n}\n\nvar File_p2p_host_peerstore_pstoreds_pb_pstore_proto protoreflect.FileDescriptor\n\nconst file_p2p_host_peerstore_pstoreds_pb_pstore_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"+p2p/host/peerstore/pstoreds/pb/pstore.proto\\x12\\tpstore.pb\\\"\\xb3\\x02\\n\" +\n\t\"\\x0eAddrBookRecord\\x12\\x0e\\n\" +\n\t\"\\x02id\\x18\\x01 \\x01(\\fR\\x02id\\x129\\n\" +\n\t\"\\x05addrs\\x18\\x02 \\x03(\\v2#.pstore.pb.AddrBookRecord.AddrEntryR\\x05addrs\\x12T\\n\" +\n\t\"\\x10certified_record\\x18\\x03 \\x01(\\v2).pstore.pb.AddrBookRecord.CertifiedRecordR\\x0fcertifiedRecord\\x1aI\\n\" +\n\t\"\\tAddrEntry\\x12\\x12\\n\" +\n\t\"\\x04addr\\x18\\x01 \\x01(\\fR\\x04addr\\x12\\x16\\n\" +\n\t\"\\x06expiry\\x18\\x02 \\x01(\\x03R\\x06expiry\\x12\\x10\\n\" +\n\t\"\\x03ttl\\x18\\x03 \\x01(\\x03R\\x03ttl\\x1a5\\n\" +\n\t\"\\x0fCertifiedRecord\\x12\\x10\\n\" +\n\t\"\\x03seq\\x18\\x01 \\x01(\\x04R\\x03seq\\x12\\x10\\n\" +\n\t\"\\x03raw\\x18\\x02 \\x01(\\fR\\x03rawB<Z:github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoreds/pbb\\x06proto3\"\n\nvar (\n\tfile_p2p_host_peerstore_pstoreds_pb_pstore_proto_rawDescOnce sync.Once\n\tfile_p2p_host_peerstore_pstoreds_pb_pstore_proto_rawDescData []byte\n)\n\nfunc file_p2p_host_peerstore_pstoreds_pb_pstore_proto_rawDescGZIP() []byte {\n\tfile_p2p_host_peerstore_pstoreds_pb_pstore_proto_rawDescOnce.Do(func() {\n\t\tfile_p2p_host_peerstore_pstoreds_pb_pstore_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_p2p_host_peerstore_pstoreds_pb_pstore_proto_rawDesc), len(file_p2p_host_peerstore_pstoreds_pb_pstore_proto_rawDesc)))\n\t})\n\treturn file_p2p_host_peerstore_pstoreds_pb_pstore_proto_rawDescData\n}\n\nvar file_p2p_host_peerstore_pstoreds_pb_pstore_proto_msgTypes = make([]protoimpl.MessageInfo, 3)\nvar file_p2p_host_peerstore_pstoreds_pb_pstore_proto_goTypes = []any{\n\t(*AddrBookRecord)(nil),                 // 0: pstore.pb.AddrBookRecord\n\t(*AddrBookRecord_AddrEntry)(nil),       // 1: pstore.pb.AddrBookRecord.AddrEntry\n\t(*AddrBookRecord_CertifiedRecord)(nil), // 2: pstore.pb.AddrBookRecord.CertifiedRecord\n}\nvar file_p2p_host_peerstore_pstoreds_pb_pstore_proto_depIdxs = []int32{\n\t1, // 0: pstore.pb.AddrBookRecord.addrs:type_name -> pstore.pb.AddrBookRecord.AddrEntry\n\t2, // 1: pstore.pb.AddrBookRecord.certified_record:type_name -> pstore.pb.AddrBookRecord.CertifiedRecord\n\t2, // [2:2] is the sub-list for method output_type\n\t2, // [2:2] is the sub-list for method input_type\n\t2, // [2:2] is the sub-list for extension type_name\n\t2, // [2:2] is the sub-list for extension extendee\n\t0, // [0:2] is the sub-list for field type_name\n}\n\nfunc init() { file_p2p_host_peerstore_pstoreds_pb_pstore_proto_init() }\nfunc file_p2p_host_peerstore_pstoreds_pb_pstore_proto_init() {\n\tif File_p2p_host_peerstore_pstoreds_pb_pstore_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_p2p_host_peerstore_pstoreds_pb_pstore_proto_rawDesc), len(file_p2p_host_peerstore_pstoreds_pb_pstore_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   3,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_p2p_host_peerstore_pstoreds_pb_pstore_proto_goTypes,\n\t\tDependencyIndexes: file_p2p_host_peerstore_pstoreds_pb_pstore_proto_depIdxs,\n\t\tMessageInfos:      file_p2p_host_peerstore_pstoreds_pb_pstore_proto_msgTypes,\n\t}.Build()\n\tFile_p2p_host_peerstore_pstoreds_pb_pstore_proto = out.File\n\tfile_p2p_host_peerstore_pstoreds_pb_pstore_proto_goTypes = nil\n\tfile_p2p_host_peerstore_pstoreds_pb_pstore_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "p2p/host/peerstore/pstoreds/pb/pstore.proto",
    "content": "syntax = \"proto3\";\npackage pstore.pb;\n\noption go_package = \"github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoreds/pb\";\n\n// AddrBookRecord represents a record for a peer in the address book.\nmessage AddrBookRecord {\n\t// The peer ID.\n\tbytes id = 1;\n\n\t// The multiaddresses. This is a sorted list where element 0 expires the soonest.\n\trepeated AddrEntry addrs = 2;\n\n\t// The most recently received signed PeerRecord.\n\tCertifiedRecord certified_record = 3;\n\n\t// AddrEntry represents a single multiaddress.\n\tmessage AddrEntry {\n\t\tbytes addr = 1;\n\n\t\t// The point in time when this address expires.\n\t\tint64 expiry = 2;\n\n\t\t// The original TTL of this address.\n\t\tint64 ttl = 3;\n\t}\n\n\t// CertifiedRecord contains a serialized signed PeerRecord used to\n\t// populate the signedAddrs list.\n\tmessage CertifiedRecord {\n\t\t// The Seq counter from the signed PeerRecord envelope\n\t\tuint64 seq = 1;\n\n\t\t// The serialized bytes of the SignedEnvelope containing the PeerRecord.\n\t\tbytes raw = 2;\n\t}\n}\n"
  },
  {
    "path": "p2p/host/peerstore/pstoreds/peerstore.go",
    "content": "package pstoreds\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\tpstore \"github.com/libp2p/go-libp2p/p2p/host/peerstore\"\n\n\tds \"github.com/ipfs/go-datastore\"\n\t\"github.com/ipfs/go-datastore/query\"\n\t\"github.com/multiformats/go-base32\"\n)\n\n// Configuration object for the peerstore.\ntype Options struct {\n\t// The size of the in-memory cache. A value of 0 or lower disables the cache.\n\tCacheSize uint\n\n\t// MaxProtocols is the maximum number of protocols we store for one peer.\n\tMaxProtocols int\n\n\t// Sweep interval to purge expired addresses from the datastore. If this is a zero value, GC will not run\n\t// automatically, but it'll be available on demand via explicit calls.\n\tGCPurgeInterval time.Duration\n\n\t// Interval to renew the GC lookahead window. If this is a zero value, lookahead will be disabled and we'll\n\t// traverse the entire datastore for every purge cycle.\n\tGCLookaheadInterval time.Duration\n\n\t// Initial delay before GC processes start. Intended to give the system breathing room to fully boot\n\t// before starting GC.\n\tGCInitialDelay time.Duration\n\n\tClock clock\n}\n\n// DefaultOpts returns the default options for a persistent peerstore, with the full-purge GC algorithm:\n//\n// * Cache size: 1024.\n// * MaxProtocols: 1024.\n// * GC purge interval: 2 hours.\n// * GC lookahead interval: disabled.\n// * GC initial delay: 60 seconds.\nfunc DefaultOpts() Options {\n\treturn Options{\n\t\tCacheSize:           1024,\n\t\tMaxProtocols:        1024,\n\t\tGCPurgeInterval:     2 * time.Hour,\n\t\tGCLookaheadInterval: 0,\n\t\tGCInitialDelay:      60 * time.Second,\n\t\tClock:               realclock{},\n\t}\n}\n\ntype pstoreds struct {\n\tpeerstore.Metrics\n\n\t*dsKeyBook\n\t*dsAddrBook\n\t*dsProtoBook\n\t*dsPeerMetadata\n}\n\nvar _ peerstore.Peerstore = &pstoreds{}\n\n// NewPeerstore creates a peerstore backed by the provided persistent datastore.\n// It's the caller's responsibility to call RemovePeer to ensure\n// that memory consumption of the peerstore doesn't grow unboundedly.\nfunc NewPeerstore(ctx context.Context, store ds.Batching, opts Options) (*pstoreds, error) {\n\taddrBook, err := NewAddrBook(ctx, store, opts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tkeyBook, err := NewKeyBook(ctx, store, opts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpeerMetadata, err := NewPeerMetadata(ctx, store, opts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tprotoBook, err := NewProtoBook(peerMetadata, WithMaxProtocols(opts.MaxProtocols))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &pstoreds{\n\t\tMetrics:        pstore.NewMetrics(),\n\t\tdsKeyBook:      keyBook,\n\t\tdsAddrBook:     addrBook,\n\t\tdsPeerMetadata: peerMetadata,\n\t\tdsProtoBook:    protoBook,\n\t}, nil\n}\n\n// uniquePeerIds extracts and returns unique peer IDs from database keys.\nfunc uniquePeerIds(ds ds.Datastore, prefix ds.Key, extractor func(result query.Result) string) (peer.IDSlice, error) {\n\tvar (\n\t\tq       = query.Query{Prefix: prefix.String(), KeysOnly: true}\n\t\tresults query.Results\n\t\terr     error\n\t)\n\n\tif results, err = ds.Query(context.TODO(), q); err != nil {\n\t\tlog.Error(\"failed to query database\", \"err\", err)\n\t\treturn nil, err\n\t}\n\n\tdefer results.Close()\n\n\tidset := make(map[string]struct{})\n\tfor result := range results.Next() {\n\t\tk := extractor(result)\n\t\tidset[k] = struct{}{}\n\t}\n\n\tif len(idset) == 0 {\n\t\treturn peer.IDSlice{}, nil\n\t}\n\n\tids := make(peer.IDSlice, 0, len(idset))\n\tfor id := range idset {\n\t\tpid, _ := base32.RawStdEncoding.DecodeString(id)\n\t\tid, _ := peer.IDFromBytes(pid)\n\t\tids = append(ids, id)\n\t}\n\treturn ids, nil\n}\n\nfunc (ps *pstoreds) Close() (err error) {\n\tvar errs []error\n\tweakClose := func(name string, c any) {\n\t\tif cl, ok := c.(io.Closer); ok {\n\t\t\tif err = cl.Close(); err != nil {\n\t\t\t\terrs = append(errs, fmt.Errorf(\"%s error: %s\", name, err))\n\t\t\t}\n\t\t}\n\t}\n\tweakClose(\"keybook\", ps.dsKeyBook)\n\tweakClose(\"addressbook\", ps.dsAddrBook)\n\tweakClose(\"protobook\", ps.dsProtoBook)\n\tweakClose(\"peermetadata\", ps.dsPeerMetadata)\n\n\tif len(errs) > 0 {\n\t\treturn fmt.Errorf(\"failed while closing peerstore; err(s): %q\", errs)\n\t}\n\treturn nil\n}\n\nfunc (ps *pstoreds) Peers() peer.IDSlice {\n\tset := map[peer.ID]struct{}{}\n\tfor _, p := range ps.PeersWithKeys() {\n\t\tset[p] = struct{}{}\n\t}\n\tfor _, p := range ps.PeersWithAddrs() {\n\t\tset[p] = struct{}{}\n\t}\n\n\tpps := make(peer.IDSlice, 0, len(set))\n\tfor p := range set {\n\t\tpps = append(pps, p)\n\t}\n\treturn pps\n}\n\nfunc (ps *pstoreds) PeerInfo(p peer.ID) peer.AddrInfo {\n\treturn peer.AddrInfo{\n\t\tID:    p,\n\t\tAddrs: ps.dsAddrBook.Addrs(p),\n\t}\n}\n\n// RemovePeer removes entries associated with a peer from:\n// * the KeyBook\n// * the ProtoBook\n// * the PeerMetadata\n// * the Metrics\n// It DOES NOT remove the peer from the AddrBook.\nfunc (ps *pstoreds) RemovePeer(p peer.ID) {\n\tps.dsKeyBook.RemovePeer(p)\n\tps.dsProtoBook.RemovePeer(p)\n\tps.dsPeerMetadata.RemovePeer(p)\n\tps.Metrics.RemovePeer(p)\n}\n"
  },
  {
    "path": "p2p/host/peerstore/pstoreds/protobook.go",
    "content": "package pstoreds\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"sync\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\tpstore \"github.com/libp2p/go-libp2p/core/peerstore\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n)\n\ntype protoSegment struct {\n\tsync.RWMutex\n}\n\ntype protoSegments [256]*protoSegment\n\nfunc (s *protoSegments) get(p peer.ID) *protoSegment {\n\treturn s[p[len(p)-1]]\n}\n\nvar errTooManyProtocols = errors.New(\"too many protocols\")\n\ntype ProtoBookOption func(*dsProtoBook) error\n\nfunc WithMaxProtocols(num int) ProtoBookOption {\n\treturn func(pb *dsProtoBook) error {\n\t\tpb.maxProtos = num\n\t\treturn nil\n\t}\n}\n\ntype dsProtoBook struct {\n\tsegments  protoSegments\n\tmeta      pstore.PeerMetadata\n\tmaxProtos int\n}\n\nvar _ pstore.ProtoBook = (*dsProtoBook)(nil)\n\nfunc NewProtoBook(meta pstore.PeerMetadata, opts ...ProtoBookOption) (*dsProtoBook, error) {\n\tpb := &dsProtoBook{\n\t\tmeta: meta,\n\t\tsegments: func() (ret protoSegments) {\n\t\t\tfor i := range ret {\n\t\t\t\tret[i] = &protoSegment{}\n\t\t\t}\n\t\t\treturn ret\n\t\t}(),\n\t\tmaxProtos: 128,\n\t}\n\n\tfor _, opt := range opts {\n\t\tif err := opt(pb); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn pb, nil\n}\n\nfunc (pb *dsProtoBook) SetProtocols(p peer.ID, protos ...protocol.ID) error {\n\tif len(protos) > pb.maxProtos {\n\t\treturn errTooManyProtocols\n\t}\n\n\tprotomap := make(map[protocol.ID]struct{}, len(protos))\n\tfor _, proto := range protos {\n\t\tprotomap[proto] = struct{}{}\n\t}\n\n\ts := pb.segments.get(p)\n\ts.Lock()\n\tdefer s.Unlock()\n\n\treturn pb.meta.Put(p, \"protocols\", protomap)\n}\n\nfunc (pb *dsProtoBook) AddProtocols(p peer.ID, protos ...protocol.ID) error {\n\ts := pb.segments.get(p)\n\ts.Lock()\n\tdefer s.Unlock()\n\n\tpmap, err := pb.getProtocolMap(p)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(pmap)+len(protos) > pb.maxProtos {\n\t\treturn errTooManyProtocols\n\t}\n\n\tfor _, proto := range protos {\n\t\tpmap[proto] = struct{}{}\n\t}\n\n\treturn pb.meta.Put(p, \"protocols\", pmap)\n}\n\nfunc (pb *dsProtoBook) GetProtocols(p peer.ID) ([]protocol.ID, error) {\n\ts := pb.segments.get(p)\n\ts.RLock()\n\tdefer s.RUnlock()\n\n\tpmap, err := pb.getProtocolMap(p)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tres := make([]protocol.ID, 0, len(pmap))\n\tfor proto := range pmap {\n\t\tres = append(res, proto)\n\t}\n\n\treturn res, nil\n}\n\nfunc (pb *dsProtoBook) SupportsProtocols(p peer.ID, protos ...protocol.ID) ([]protocol.ID, error) {\n\ts := pb.segments.get(p)\n\ts.RLock()\n\tdefer s.RUnlock()\n\n\tpmap, err := pb.getProtocolMap(p)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tres := make([]protocol.ID, 0, len(protos))\n\tfor _, proto := range protos {\n\t\tif _, ok := pmap[proto]; ok {\n\t\t\tres = append(res, proto)\n\t\t}\n\t}\n\n\treturn res, nil\n}\n\nfunc (pb *dsProtoBook) FirstSupportedProtocol(p peer.ID, protos ...protocol.ID) (protocol.ID, error) {\n\ts := pb.segments.get(p)\n\ts.RLock()\n\tdefer s.RUnlock()\n\n\tpmap, err := pb.getProtocolMap(p)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tfor _, proto := range protos {\n\t\tif _, ok := pmap[proto]; ok {\n\t\t\treturn proto, nil\n\t\t}\n\t}\n\n\treturn \"\", nil\n}\n\nfunc (pb *dsProtoBook) RemoveProtocols(p peer.ID, protos ...protocol.ID) error {\n\ts := pb.segments.get(p)\n\ts.Lock()\n\tdefer s.Unlock()\n\n\tpmap, err := pb.getProtocolMap(p)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif len(pmap) == 0 {\n\t\t// nothing to do.\n\t\treturn nil\n\t}\n\n\tfor _, proto := range protos {\n\t\tdelete(pmap, proto)\n\t}\n\n\treturn pb.meta.Put(p, \"protocols\", pmap)\n}\n\nfunc (pb *dsProtoBook) getProtocolMap(p peer.ID) (map[protocol.ID]struct{}, error) {\n\tiprotomap, err := pb.meta.Get(p, \"protocols\")\n\tswitch err {\n\tdefault:\n\t\treturn nil, err\n\tcase pstore.ErrNotFound:\n\t\treturn make(map[protocol.ID]struct{}), nil\n\tcase nil:\n\t\tcast, ok := iprotomap.(map[protocol.ID]struct{})\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"stored protocol set was not a map\")\n\t\t}\n\n\t\treturn cast, nil\n\t}\n}\n\nfunc (pb *dsProtoBook) RemovePeer(p peer.ID) {\n\tpb.meta.RemovePeer(p)\n}\n"
  },
  {
    "path": "p2p/host/peerstore/pstoremem/addr_book.go",
    "content": "package pstoremem\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"sort\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\t\"github.com/libp2p/go-libp2p/core/record\"\n\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\nvar log = logging.Logger(\"peerstore\")\n\ntype expiringAddr struct {\n\tAddr   ma.Multiaddr\n\tTTL    time.Duration\n\tExpiry time.Time\n\tPeer   peer.ID\n\t// to sort by expiry time, -1 means it's not in the heap\n\theapIndex int\n}\n\nfunc (e *expiringAddr) ExpiredBy(t time.Time) bool {\n\treturn !t.Before(e.Expiry)\n}\n\nfunc (e *expiringAddr) IsConnected() bool {\n\treturn ttlIsConnected(e.TTL)\n}\n\n// ttlIsConnected returns true if the TTL is at least as long as the connected\n// TTL.\nfunc ttlIsConnected(ttl time.Duration) bool {\n\treturn ttl >= peerstore.ConnectedAddrTTL\n}\n\ntype peerRecordState struct {\n\tEnvelope *record.Envelope\n\tSeq      uint64\n}\n\n// Essentially Go stdlib's Priority Queue example\nvar _ heap.Interface = &peerAddrs{}\n\ntype peerAddrs struct {\n\tAddrs map[peer.ID]map[string]*expiringAddr // peer.ID -> addr.Bytes() -> *expiringAddr\n\t// expiringHeap only stores non-connected addresses. Since connected address\n\t// basically have an infinite TTL\n\texpiringHeap []*expiringAddr\n}\n\nfunc newPeerAddrs() peerAddrs {\n\treturn peerAddrs{\n\t\tAddrs: make(map[peer.ID]map[string]*expiringAddr),\n\t}\n}\n\nfunc (pa *peerAddrs) Len() int { return len(pa.expiringHeap) }\nfunc (pa *peerAddrs) Less(i, j int) bool {\n\treturn pa.expiringHeap[i].Expiry.Before(pa.expiringHeap[j].Expiry)\n}\nfunc (pa *peerAddrs) Swap(i, j int) {\n\tpa.expiringHeap[i], pa.expiringHeap[j] = pa.expiringHeap[j], pa.expiringHeap[i]\n\tpa.expiringHeap[i].heapIndex = i\n\tpa.expiringHeap[j].heapIndex = j\n}\nfunc (pa *peerAddrs) Push(x any) {\n\ta := x.(*expiringAddr)\n\ta.heapIndex = len(pa.expiringHeap)\n\tpa.expiringHeap = append(pa.expiringHeap, a)\n}\nfunc (pa *peerAddrs) Pop() any {\n\ta := pa.expiringHeap[len(pa.expiringHeap)-1]\n\ta.heapIndex = -1\n\tpa.expiringHeap = pa.expiringHeap[0 : len(pa.expiringHeap)-1]\n\treturn a\n}\n\nfunc (pa *peerAddrs) Delete(a *expiringAddr) {\n\tif ea, ok := pa.Addrs[a.Peer][string(a.Addr.Bytes())]; ok {\n\t\tif ea.heapIndex != -1 {\n\t\t\theap.Remove(pa, a.heapIndex)\n\t\t}\n\t\tdelete(pa.Addrs[a.Peer], string(a.Addr.Bytes()))\n\t\tif len(pa.Addrs[a.Peer]) == 0 {\n\t\t\tdelete(pa.Addrs, a.Peer)\n\t\t}\n\t}\n}\n\nfunc (pa *peerAddrs) FindAddr(p peer.ID, addr ma.Multiaddr) (*expiringAddr, bool) {\n\tif m, ok := pa.Addrs[p]; ok {\n\t\tv, ok := m[string(addr.Bytes())]\n\t\treturn v, ok\n\t}\n\treturn nil, false\n}\n\nfunc (pa *peerAddrs) NextExpiry() time.Time {\n\tif len(pa.expiringHeap) == 0 {\n\t\treturn time.Time{}\n\t}\n\treturn pa.expiringHeap[0].Expiry\n}\n\nfunc (pa *peerAddrs) PopIfExpired(now time.Time) (*expiringAddr, bool) {\n\t// Use `!Before` instead of `After` to ensure that we expire *at* now, and not *just after now*.\n\tif len(pa.expiringHeap) > 0 && !now.Before(pa.NextExpiry()) {\n\t\tea := heap.Pop(pa).(*expiringAddr)\n\t\tdelete(pa.Addrs[ea.Peer], string(ea.Addr.Bytes()))\n\t\tif len(pa.Addrs[ea.Peer]) == 0 {\n\t\t\tdelete(pa.Addrs, ea.Peer)\n\t\t}\n\t\treturn ea, true\n\t}\n\treturn nil, false\n}\n\nfunc (pa *peerAddrs) Update(a *expiringAddr) {\n\tif a.heapIndex == -1 {\n\t\treturn\n\t}\n\tif a.IsConnected() {\n\t\theap.Remove(pa, a.heapIndex)\n\t} else {\n\t\theap.Fix(pa, a.heapIndex)\n\t}\n}\n\nfunc (pa *peerAddrs) Insert(a *expiringAddr) {\n\ta.heapIndex = -1\n\tif _, ok := pa.Addrs[a.Peer]; !ok {\n\t\tpa.Addrs[a.Peer] = make(map[string]*expiringAddr)\n\t}\n\tpa.Addrs[a.Peer][string(a.Addr.Bytes())] = a\n\t// don't add connected addr to heap.\n\tif a.IsConnected() {\n\t\treturn\n\t}\n\theap.Push(pa, a)\n}\n\nfunc (pa *peerAddrs) NumUnconnectedAddrs() int {\n\treturn len(pa.expiringHeap)\n}\n\ntype clock interface {\n\tNow() time.Time\n}\n\ntype realclock struct{}\n\nfunc (rc realclock) Now() time.Time {\n\treturn time.Now()\n}\n\nconst (\n\tdefaultMaxSignedPeerRecords = 100_000\n\tdefaultMaxUnconnectedAddrs  = 1_000_000\n)\n\n// memoryAddrBook manages addresses.\ntype memoryAddrBook struct {\n\tmu                   sync.RWMutex\n\taddrs                peerAddrs\n\tsignedPeerRecords    map[peer.ID]*peerRecordState\n\tmaxUnconnectedAddrs  int\n\tmaxSignedPeerRecords int\n\n\trefCount sync.WaitGroup\n\tcancel   func()\n\n\tsubManager *AddrSubManager\n\tclock      clock\n}\n\nvar _ peerstore.AddrBook = (*memoryAddrBook)(nil)\nvar _ peerstore.CertifiedAddrBook = (*memoryAddrBook)(nil)\n\nfunc NewAddrBook(opts ...AddrBookOption) *memoryAddrBook {\n\tctx, cancel := context.WithCancel(context.Background())\n\n\tab := &memoryAddrBook{\n\t\taddrs:                newPeerAddrs(),\n\t\tsignedPeerRecords:    make(map[peer.ID]*peerRecordState),\n\t\tsubManager:           NewAddrSubManager(),\n\t\tcancel:               cancel,\n\t\tclock:                realclock{},\n\t\tmaxUnconnectedAddrs:  defaultMaxUnconnectedAddrs,\n\t\tmaxSignedPeerRecords: defaultMaxSignedPeerRecords,\n\t}\n\tfor _, opt := range opts {\n\t\topt(ab)\n\t}\n\n\tab.refCount.Add(1)\n\tgo ab.background(ctx)\n\treturn ab\n}\n\ntype AddrBookOption func(book *memoryAddrBook) error\n\nfunc WithClock(clock clock) AddrBookOption {\n\treturn func(book *memoryAddrBook) error {\n\t\tbook.clock = clock\n\t\treturn nil\n\t}\n}\n\n// WithMaxAddresses sets the maximum number of unconnected addresses to store.\n// The maximum number of connected addresses is bounded by the connection\n// limits in the Connection Manager and Resource Manager.\nfunc WithMaxAddresses(n int) AddrBookOption {\n\treturn func(b *memoryAddrBook) error {\n\t\tb.maxUnconnectedAddrs = n\n\t\treturn nil\n\t}\n}\n\nfunc WithMaxSignedPeerRecords(n int) AddrBookOption {\n\treturn func(b *memoryAddrBook) error {\n\t\tb.maxSignedPeerRecords = n\n\t\treturn nil\n\t}\n}\n\n// background periodically schedules a gc\nfunc (mab *memoryAddrBook) background(ctx context.Context) {\n\tdefer mab.refCount.Done()\n\tticker := time.NewTicker(1 * time.Minute)\n\tdefer ticker.Stop()\n\n\tfor {\n\t\tselect {\n\t\tcase <-ticker.C:\n\t\t\tmab.gc()\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (mab *memoryAddrBook) Close() error {\n\tmab.cancel()\n\tmab.refCount.Wait()\n\treturn nil\n}\n\n// gc garbage collects the in-memory address book.\nfunc (mab *memoryAddrBook) gc() {\n\tnow := mab.clock.Now()\n\tmab.mu.Lock()\n\tdefer mab.mu.Unlock()\n\tfor {\n\t\tea, ok := mab.addrs.PopIfExpired(now)\n\t\tif !ok {\n\t\t\treturn\n\t\t}\n\t\tmab.maybeDeleteSignedPeerRecordUnlocked(ea.Peer)\n\t}\n}\n\nfunc (mab *memoryAddrBook) PeersWithAddrs() peer.IDSlice {\n\tmab.mu.RLock()\n\tdefer mab.mu.RUnlock()\n\tpeers := make(peer.IDSlice, 0, len(mab.addrs.Addrs))\n\tfor pid := range mab.addrs.Addrs {\n\t\tpeers = append(peers, pid)\n\t}\n\treturn peers\n}\n\n// AddAddr calls AddAddrs(p, []ma.Multiaddr{addr}, ttl)\nfunc (mab *memoryAddrBook) AddAddr(p peer.ID, addr ma.Multiaddr, ttl time.Duration) {\n\tmab.AddAddrs(p, []ma.Multiaddr{addr}, ttl)\n}\n\n// AddAddrs adds `addrs` for peer `p`, which will expire after the given `ttl`.\n// This function never reduces the TTL or expiration of an address.\nfunc (mab *memoryAddrBook) AddAddrs(p peer.ID, addrs []ma.Multiaddr, ttl time.Duration) {\n\tmab.addAddrs(p, addrs, ttl)\n}\n\n// ConsumePeerRecord adds addresses from a signed peer.PeerRecord, which will expire after the given TTL.\n// See https://godoc.org/github.com/libp2p/go-libp2p/core/peerstore#CertifiedAddrBook for more details.\nfunc (mab *memoryAddrBook) ConsumePeerRecord(recordEnvelope *record.Envelope, ttl time.Duration) (bool, error) {\n\tr, err := recordEnvelope.Record()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\trec, ok := r.(*peer.PeerRecord)\n\tif !ok {\n\t\treturn false, fmt.Errorf(\"unable to process envelope: not a PeerRecord\")\n\t}\n\tif !rec.PeerID.MatchesPublicKey(recordEnvelope.PublicKey) {\n\t\treturn false, fmt.Errorf(\"signing key does not match PeerID in PeerRecord\")\n\t}\n\n\tmab.mu.Lock()\n\tdefer mab.mu.Unlock()\n\n\t// ensure seq is greater than or equal to the last received\n\tlastState, found := mab.signedPeerRecords[rec.PeerID]\n\tif found && lastState.Seq > rec.Seq {\n\t\treturn false, nil\n\t}\n\t// check if we are over the max signed peer record limit\n\tif !found && len(mab.signedPeerRecords) >= mab.maxSignedPeerRecords {\n\t\treturn false, errors.New(\"too many signed peer records\")\n\t}\n\tmab.signedPeerRecords[rec.PeerID] = &peerRecordState{\n\t\tEnvelope: recordEnvelope,\n\t\tSeq:      rec.Seq,\n\t}\n\tmab.addAddrsUnlocked(rec.PeerID, rec.Addrs, ttl)\n\treturn true, nil\n}\n\nfunc (mab *memoryAddrBook) maybeDeleteSignedPeerRecordUnlocked(p peer.ID) {\n\tif len(mab.addrs.Addrs[p]) == 0 {\n\t\tdelete(mab.signedPeerRecords, p)\n\t}\n}\n\nfunc (mab *memoryAddrBook) addAddrs(p peer.ID, addrs []ma.Multiaddr, ttl time.Duration) {\n\tmab.mu.Lock()\n\tdefer mab.mu.Unlock()\n\n\tmab.addAddrsUnlocked(p, addrs, ttl)\n}\n\nfunc (mab *memoryAddrBook) addAddrsUnlocked(p peer.ID, addrs []ma.Multiaddr, ttl time.Duration) {\n\tdefer mab.maybeDeleteSignedPeerRecordUnlocked(p)\n\n\t// if ttl is zero, exit. nothing to do.\n\tif ttl <= 0 {\n\t\treturn\n\t}\n\n\t// we are over limit, drop these addrs.\n\tif !ttlIsConnected(ttl) && mab.addrs.NumUnconnectedAddrs() >= mab.maxUnconnectedAddrs {\n\t\treturn\n\t}\n\n\texp := mab.clock.Now().Add(ttl)\n\tfor _, addr := range addrs {\n\t\t// Remove suffix of /p2p/peer-id from address\n\t\taddr, addrPid := peer.SplitAddr(addr)\n\t\tif addr == nil {\n\t\t\tlog.Warn(\"Was passed nil multiaddr\", \"peer\", p)\n\t\t\tcontinue\n\t\t}\n\t\tif addrPid != \"\" && addrPid != p {\n\t\t\tlog.Warn(\"Was passed p2p address with a different peerId\", \"found\", addrPid, \"expected\", p)\n\t\t\tcontinue\n\t\t}\n\t\ta, found := mab.addrs.FindAddr(p, addr)\n\t\tif !found {\n\t\t\t// not found, announce it.\n\t\t\tentry := &expiringAddr{Addr: addr, Expiry: exp, TTL: ttl, Peer: p}\n\t\t\tmab.addrs.Insert(entry)\n\t\t\tmab.subManager.BroadcastAddr(p, addr)\n\t\t} else {\n\t\t\t// update ttl & exp to whichever is greater between new and existing entry\n\t\t\tvar changed bool\n\t\t\tif ttl > a.TTL {\n\t\t\t\tchanged = true\n\t\t\t\ta.TTL = ttl\n\t\t\t}\n\t\t\tif exp.After(a.Expiry) {\n\t\t\t\tchanged = true\n\t\t\t\ta.Expiry = exp\n\t\t\t}\n\t\t\tif changed {\n\t\t\t\tmab.addrs.Update(a)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// SetAddr calls mgr.SetAddrs(p, addr, ttl)\nfunc (mab *memoryAddrBook) SetAddr(p peer.ID, addr ma.Multiaddr, ttl time.Duration) {\n\tmab.SetAddrs(p, []ma.Multiaddr{addr}, ttl)\n}\n\n// SetAddrs sets the ttl on addresses. This clears any TTL there previously.\n// This is used when we receive the best estimate of the validity of an address.\nfunc (mab *memoryAddrBook) SetAddrs(p peer.ID, addrs []ma.Multiaddr, ttl time.Duration) {\n\tmab.mu.Lock()\n\tdefer mab.mu.Unlock()\n\n\tdefer mab.maybeDeleteSignedPeerRecordUnlocked(p)\n\n\texp := mab.clock.Now().Add(ttl)\n\tfor _, addr := range addrs {\n\t\taddr, addrPid := peer.SplitAddr(addr)\n\t\tif addr == nil {\n\t\t\tlog.Warn(\"was passed nil multiaddr\", \"peer\", p)\n\t\t\tcontinue\n\t\t}\n\t\tif addrPid != \"\" && addrPid != p {\n\t\t\tlog.Warn(\"was passed p2p address with a different peerId\", \"found\", addrPid, \"expected\", p)\n\t\t\tcontinue\n\t\t}\n\n\t\tif a, found := mab.addrs.FindAddr(p, addr); found {\n\t\t\tif ttl > 0 {\n\t\t\t\tif a.IsConnected() && !ttlIsConnected(ttl) && mab.addrs.NumUnconnectedAddrs() >= mab.maxUnconnectedAddrs {\n\t\t\t\t\tmab.addrs.Delete(a)\n\t\t\t\t} else {\n\t\t\t\t\ta.Addr = addr\n\t\t\t\t\ta.Expiry = exp\n\t\t\t\t\ta.TTL = ttl\n\t\t\t\t\tmab.addrs.Update(a)\n\t\t\t\t\tmab.subManager.BroadcastAddr(p, addr)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tmab.addrs.Delete(a)\n\t\t\t}\n\t\t} else {\n\t\t\tif ttl > 0 {\n\t\t\t\tif !ttlIsConnected(ttl) && mab.addrs.NumUnconnectedAddrs() >= mab.maxUnconnectedAddrs {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tentry := &expiringAddr{Addr: addr, Expiry: exp, TTL: ttl, Peer: p}\n\t\t\t\tmab.addrs.Insert(entry)\n\t\t\t\tmab.subManager.BroadcastAddr(p, addr)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// UpdateAddrs updates the addresses associated with the given peer that have\n// the given oldTTL to have the given newTTL.\nfunc (mab *memoryAddrBook) UpdateAddrs(p peer.ID, oldTTL time.Duration, newTTL time.Duration) {\n\tmab.mu.Lock()\n\tdefer mab.mu.Unlock()\n\n\tdefer mab.maybeDeleteSignedPeerRecordUnlocked(p)\n\n\texp := mab.clock.Now().Add(newTTL)\n\tfor _, a := range mab.addrs.Addrs[p] {\n\t\tif oldTTL == a.TTL {\n\t\t\tif newTTL == 0 {\n\t\t\t\tmab.addrs.Delete(a)\n\t\t\t} else {\n\t\t\t\t// We are over limit, drop these addresses.\n\t\t\t\tif ttlIsConnected(oldTTL) && !ttlIsConnected(newTTL) && mab.addrs.NumUnconnectedAddrs() >= mab.maxUnconnectedAddrs {\n\t\t\t\t\tmab.addrs.Delete(a)\n\t\t\t\t} else {\n\t\t\t\t\ta.TTL = newTTL\n\t\t\t\t\ta.Expiry = exp\n\t\t\t\t\tmab.addrs.Update(a)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Addrs returns all known (and valid) addresses for a given peer\nfunc (mab *memoryAddrBook) Addrs(p peer.ID) []ma.Multiaddr {\n\tmab.mu.RLock()\n\tdefer mab.mu.RUnlock()\n\tif _, ok := mab.addrs.Addrs[p]; !ok {\n\t\treturn nil\n\t}\n\treturn validAddrs(mab.clock.Now(), mab.addrs.Addrs[p])\n}\n\nfunc validAddrs(now time.Time, amap map[string]*expiringAddr) []ma.Multiaddr {\n\tgood := make([]ma.Multiaddr, 0, len(amap))\n\tif amap == nil {\n\t\treturn good\n\t}\n\tfor _, m := range amap {\n\t\tif !m.ExpiredBy(now) {\n\t\t\tgood = append(good, m.Addr)\n\t\t}\n\t}\n\treturn good\n}\n\n// GetPeerRecord returns a Envelope containing a PeerRecord for the\n// given peer id, if one exists.\n// Returns nil if no signed PeerRecord exists for the peer.\nfunc (mab *memoryAddrBook) GetPeerRecord(p peer.ID) *record.Envelope {\n\tmab.mu.RLock()\n\tdefer mab.mu.RUnlock()\n\n\tif _, ok := mab.addrs.Addrs[p]; !ok {\n\t\treturn nil\n\t}\n\t// The record may have expired, but not gargage collected.\n\tif len(validAddrs(mab.clock.Now(), mab.addrs.Addrs[p])) == 0 {\n\t\treturn nil\n\t}\n\n\tstate := mab.signedPeerRecords[p]\n\tif state == nil {\n\t\treturn nil\n\t}\n\treturn state.Envelope\n}\n\n// ClearAddrs removes all previously stored addresses\nfunc (mab *memoryAddrBook) ClearAddrs(p peer.ID) {\n\tmab.mu.Lock()\n\tdefer mab.mu.Unlock()\n\n\tdelete(mab.signedPeerRecords, p)\n\tfor _, a := range mab.addrs.Addrs[p] {\n\t\tmab.addrs.Delete(a)\n\t}\n}\n\n// AddrStream returns a channel on which all new addresses discovered for a\n// given peer ID will be published.\nfunc (mab *memoryAddrBook) AddrStream(ctx context.Context, p peer.ID) <-chan ma.Multiaddr {\n\tvar initial []ma.Multiaddr\n\n\tmab.mu.RLock()\n\tif m, ok := mab.addrs.Addrs[p]; ok {\n\t\tinitial = make([]ma.Multiaddr, 0, len(m))\n\t\tfor _, a := range m {\n\t\t\tinitial = append(initial, a.Addr)\n\t\t}\n\t}\n\tmab.mu.RUnlock()\n\n\treturn mab.subManager.AddrStream(ctx, p, initial)\n}\n\ntype addrSub struct {\n\tpubch chan ma.Multiaddr\n\tctx   context.Context\n}\n\nfunc (s *addrSub) pubAddr(a ma.Multiaddr) {\n\tselect {\n\tcase s.pubch <- a:\n\tcase <-s.ctx.Done():\n\t}\n}\n\n// An abstracted, pub-sub manager for address streams. Extracted from\n// memoryAddrBook in order to support additional implementations.\ntype AddrSubManager struct {\n\tmu   sync.RWMutex\n\tsubs map[peer.ID][]*addrSub\n}\n\n// NewAddrSubManager initializes an AddrSubManager.\nfunc NewAddrSubManager() *AddrSubManager {\n\treturn &AddrSubManager{\n\t\tsubs: make(map[peer.ID][]*addrSub),\n\t}\n}\n\n// Used internally by the address stream coroutine to remove a subscription\n// from the manager.\nfunc (mgr *AddrSubManager) removeSub(p peer.ID, s *addrSub) {\n\tmgr.mu.Lock()\n\tdefer mgr.mu.Unlock()\n\n\tsubs := mgr.subs[p]\n\tif len(subs) == 1 {\n\t\tif subs[0] != s {\n\t\t\treturn\n\t\t}\n\t\tdelete(mgr.subs, p)\n\t\treturn\n\t}\n\n\tfor i, v := range subs {\n\t\tif v == s {\n\t\t\tsubs[i] = subs[len(subs)-1]\n\t\t\tsubs[len(subs)-1] = nil\n\t\t\tmgr.subs[p] = subs[:len(subs)-1]\n\t\t\treturn\n\t\t}\n\t}\n}\n\n// BroadcastAddr broadcasts a new address to all subscribed streams.\nfunc (mgr *AddrSubManager) BroadcastAddr(p peer.ID, addr ma.Multiaddr) {\n\tmgr.mu.RLock()\n\tdefer mgr.mu.RUnlock()\n\n\tif subs, ok := mgr.subs[p]; ok {\n\t\tfor _, sub := range subs {\n\t\t\tsub.pubAddr(addr)\n\t\t}\n\t}\n}\n\n// AddrStream creates a new subscription for a given peer ID, pre-populating the\n// channel with any addresses we might already have on file.\nfunc (mgr *AddrSubManager) AddrStream(ctx context.Context, p peer.ID, initial []ma.Multiaddr) <-chan ma.Multiaddr {\n\tsub := &addrSub{pubch: make(chan ma.Multiaddr), ctx: ctx}\n\tout := make(chan ma.Multiaddr)\n\n\tmgr.mu.Lock()\n\tmgr.subs[p] = append(mgr.subs[p], sub)\n\tmgr.mu.Unlock()\n\n\tsort.Sort(addrList(initial))\n\n\tgo func(buffer []ma.Multiaddr) {\n\t\tdefer close(out)\n\n\t\tsent := make(map[string]struct{}, len(buffer))\n\t\tfor _, a := range buffer {\n\t\t\tsent[string(a.Bytes())] = struct{}{}\n\t\t}\n\n\t\tvar outch chan ma.Multiaddr\n\t\tvar next ma.Multiaddr\n\t\tif len(buffer) > 0 {\n\t\t\tnext = buffer[0]\n\t\t\tbuffer = buffer[1:]\n\t\t\toutch = out\n\t\t}\n\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase outch <- next:\n\t\t\t\tif len(buffer) > 0 {\n\t\t\t\t\tnext = buffer[0]\n\t\t\t\t\tbuffer = buffer[1:]\n\t\t\t\t} else {\n\t\t\t\t\toutch = nil\n\t\t\t\t\tnext = nil\n\t\t\t\t}\n\t\t\tcase naddr := <-sub.pubch:\n\t\t\t\tif _, ok := sent[string(naddr.Bytes())]; ok {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tsent[string(naddr.Bytes())] = struct{}{}\n\n\t\t\t\tif next == nil {\n\t\t\t\t\tnext = naddr\n\t\t\t\t\toutch = out\n\t\t\t\t} else {\n\t\t\t\t\tbuffer = append(buffer, naddr)\n\t\t\t\t}\n\t\t\tcase <-ctx.Done():\n\t\t\t\tmgr.removeSub(p, sub)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}(initial)\n\n\treturn out\n}\n"
  },
  {
    "path": "p2p/host/peerstore/pstoremem/addr_book_test.go",
    "content": "package pstoremem\n\nimport (\n\t\"container/heap\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"slices\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestPeerAddrsNextExpiry(t *testing.T) {\n\tpaa := newPeerAddrs()\n\tpa := &paa\n\ta1 := ma.StringCast(\"/ip4/1.2.3.4/udp/1/quic-v1\")\n\ta2 := ma.StringCast(\"/ip4/1.2.3.4/udp/2/quic-v1\")\n\n\t// t1 is before t2\n\tt1 := time.Time{}.Add(1 * time.Second)\n\tt2 := time.Time{}.Add(2 * time.Second)\n\tpaa.Insert(&expiringAddr{Addr: a1, Expiry: t1, TTL: 10 * time.Second, Peer: \"p1\"})\n\tpaa.Insert(&expiringAddr{Addr: a2, Expiry: t2, TTL: 10 * time.Second, Peer: \"p2\"})\n\n\tif pa.NextExpiry() != t1 {\n\t\tt.Fatal(\"expiry should be set to t1, got\", pa.NextExpiry())\n\t}\n}\n\nfunc peerAddrsInput(n int) []*expiringAddr {\n\texpiringAddrs := make([]*expiringAddr, n)\n\tfor i := range n {\n\t\tport := i % 65535\n\t\ta := ma.StringCast(fmt.Sprintf(\"/ip4/1.2.3.4/udp/%d/quic-v1\", port))\n\t\te := time.Time{}.Add(time.Duration(i) * time.Second)\n\t\tp := peer.ID(fmt.Sprintf(\"p%d\", i))\n\t\texpiringAddrs[i] = &expiringAddr{Addr: a, Expiry: e, TTL: 10 * time.Second, Peer: p}\n\t}\n\treturn expiringAddrs\n}\n\nfunc TestPeerAddrsHeapProperty(t *testing.T) {\n\tpaa := newPeerAddrs()\n\tpa := &paa\n\n\tconst N = 10000\n\texpiringAddrs := peerAddrsInput(N)\n\tfor i := range N {\n\t\tpaa.Insert(expiringAddrs[i])\n\t}\n\n\tfor i := range N {\n\t\tea, ok := pa.PopIfExpired(expiringAddrs[i].Expiry)\n\t\trequire.True(t, ok, \"pos: %d\", i)\n\t\trequire.Equal(t, ea.Addr, expiringAddrs[i].Addr)\n\n\t\tea, ok = pa.PopIfExpired(expiringAddrs[i].Expiry)\n\t\trequire.False(t, ok)\n\t\trequire.Nil(t, ea)\n\t}\n}\n\nfunc TestPeerAddrsHeapPropertyDeletions(t *testing.T) {\n\tpaa := newPeerAddrs()\n\tpa := &paa\n\n\tconst N = 10000\n\texpiringAddrs := peerAddrsInput(N)\n\tfor i := range N {\n\t\tpaa.Insert(expiringAddrs[i])\n\t}\n\n\t// delete every 3rd element\n\tfor i := 0; i < N; i += 3 {\n\t\tpaa.Delete(expiringAddrs[i])\n\t}\n\n\tfor i := range N {\n\t\tea, ok := pa.PopIfExpired(expiringAddrs[i].Expiry)\n\t\tif i%3 == 0 {\n\t\t\trequire.False(t, ok)\n\t\t\trequire.Nil(t, ea)\n\t\t} else {\n\t\t\trequire.True(t, ok)\n\t\t\trequire.Equal(t, ea.Addr, expiringAddrs[i].Addr)\n\t\t}\n\n\t\tea, ok = pa.PopIfExpired(expiringAddrs[i].Expiry)\n\t\trequire.False(t, ok)\n\t\trequire.Nil(t, ea)\n\t}\n}\n\nfunc TestPeerAddrsHeapPropertyUpdates(t *testing.T) {\n\tpaa := newPeerAddrs()\n\tpa := &paa\n\n\tconst N = 10000\n\texpiringAddrs := peerAddrsInput(N)\n\tfor i := range N {\n\t\theap.Push(pa, expiringAddrs[i])\n\t}\n\n\t// update every 3rd element to expire at the end\n\tvar endElements []ma.Multiaddr\n\tfor i := 0; i < N; i += 3 {\n\t\texpiringAddrs[i].Expiry = time.Time{}.Add(1000_000 * time.Second)\n\t\tpa.Update(expiringAddrs[i])\n\t\tendElements = append(endElements, expiringAddrs[i].Addr)\n\t}\n\n\tfor i := range N {\n\t\tif i%3 == 0 {\n\t\t\tcontinue // skip the elements at the end\n\t\t}\n\t\tea, ok := pa.PopIfExpired(expiringAddrs[i].Expiry)\n\t\trequire.True(t, ok, \"pos: %d\", i)\n\t\trequire.Equal(t, ea.Addr, expiringAddrs[i].Addr)\n\n\t\tea, ok = pa.PopIfExpired(expiringAddrs[i].Expiry)\n\t\trequire.False(t, ok)\n\t\trequire.Nil(t, ea)\n\t}\n\n\tfor len(endElements) > 0 {\n\t\tea, ok := pa.PopIfExpired(time.Time{}.Add(1000_000 * time.Second))\n\t\trequire.True(t, ok)\n\t\trequire.Contains(t, endElements, ea.Addr)\n\t\tendElements = slices.DeleteFunc(endElements, func(a ma.Multiaddr) bool { return ea.Addr.Equal(a) })\n\t}\n}\n\n// TestPeerAddrsExpiry tests for multiple element expiry with PopIfExpired.\nfunc TestPeerAddrsExpiry(t *testing.T) {\n\tconst T = 100_000\n\tfor range T {\n\t\tpaa := newPeerAddrs()\n\t\tpa := &paa\n\t\t// Try a lot of random inputs.\n\t\t// T > 5*((5^5)*5) (=15k)\n\t\t// So this should test for all possible 5 element inputs.\n\t\tconst N = 5\n\t\texpiringAddrs := peerAddrsInput(N)\n\t\tfor i := range N {\n\t\t\texpiringAddrs[i].Expiry = time.Time{}.Add(time.Duration(1+rand.Intn(N)) * time.Second)\n\t\t}\n\t\tfor i := range N {\n\t\t\tpa.Insert(expiringAddrs[i])\n\t\t}\n\n\t\texpiry := time.Time{}.Add(time.Duration(1+rand.Intn(N)) * time.Second)\n\t\texpected := []ma.Multiaddr{}\n\t\tfor i := range N {\n\t\t\tif !expiry.Before(expiringAddrs[i].Expiry) {\n\t\t\t\texpected = append(expected, expiringAddrs[i].Addr)\n\t\t\t}\n\t\t}\n\t\tgot := []ma.Multiaddr{}\n\t\tfor {\n\t\t\tea, ok := pa.PopIfExpired(expiry)\n\t\t\tif !ok {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tgot = append(got, ea.Addr)\n\t\t}\n\t\texpiries := []int{}\n\t\tfor i := range N {\n\t\t\texpiries = append(expiries, expiringAddrs[i].Expiry.Second())\n\t\t}\n\t\trequire.ElementsMatch(t, expected, got, \"failed for input: element expiries: %v, expiry: %v\", expiries, expiry.Second())\n\t}\n}\n\nfunc TestPeerLimits(t *testing.T) {\n\tab := NewAddrBook()\n\tdefer ab.Close()\n\tab.maxUnconnectedAddrs = 1024\n\n\tpeers := peerAddrsInput(2048)\n\tfor _, p := range peers {\n\t\tab.AddAddr(p.Peer, p.Addr, p.TTL)\n\t}\n\trequire.Equal(t, 1024, ab.addrs.NumUnconnectedAddrs())\n}\n\nfunc BenchmarkPeerAddrs(b *testing.B) {\n\tsizes := [...]int{1, 10, 100, 1000, 10_000, 100_000, 1000_000}\n\tfor _, sz := range sizes {\n\t\tb.Run(fmt.Sprintf(\"%d\", sz), func(b *testing.B) {\n\t\t\tfor i := 0; i < b.N; i++ {\n\t\t\t\tb.StopTimer()\n\t\t\t\tpaa := newPeerAddrs()\n\t\t\t\tpa := &paa\n\t\t\t\texpiringAddrs := peerAddrsInput(sz)\n\t\t\t\tfor i := range sz {\n\t\t\t\t\tpa.Insert(expiringAddrs[i])\n\t\t\t\t}\n\t\t\t\tb.StartTimer()\n\t\t\t\tfor {\n\t\t\t\t\t_, ok := pa.PopIfExpired(expiringAddrs[len(expiringAddrs)-1].Expiry)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n\n}\n"
  },
  {
    "path": "p2p/host/peerstore/pstoremem/inmem_test.go",
    "content": "package pstoremem\n\nimport (\n\t\"strconv\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\tpstore \"github.com/libp2p/go-libp2p/core/peerstore\"\n\tpt \"github.com/libp2p/go-libp2p/p2p/host/peerstore/test\"\n\t\"github.com/multiformats/go-multiaddr\"\n\n\tmockClock \"github.com/benbjohnson/clock\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/goleak\"\n)\n\nfunc TestInvalidOption(t *testing.T) {\n\t_, err := NewPeerstore(1337)\n\trequire.EqualError(t, err, \"unexpected peer store option: 1337\")\n}\n\nfunc TestFuzzInMemoryPeerstore(t *testing.T) {\n\t// Just create and close a bunch of peerstores. If this leaks, we'll\n\t// catch it in the leak check below.\n\tfor range 100 {\n\t\tps, err := NewPeerstore()\n\t\trequire.NoError(t, err)\n\t\tps.Close()\n\t}\n}\n\nfunc TestInMemoryPeerstore(t *testing.T) {\n\tpt.TestPeerstore(t, func() (pstore.Peerstore, func()) {\n\t\tps, err := NewPeerstore()\n\t\trequire.NoError(t, err)\n\t\treturn ps, func() { ps.Close() }\n\t})\n}\n\nfunc TestPeerstoreProtoStoreLimits(t *testing.T) {\n\tconst limit = 10\n\tps, err := NewPeerstore(WithMaxProtocols(limit))\n\trequire.NoError(t, err)\n\tdefer ps.Close()\n\tpt.TestPeerstoreProtoStoreLimits(t, ps, limit)\n}\n\nfunc TestInMemoryAddrBook(t *testing.T) {\n\tclk := mockClock.NewMock()\n\tpt.TestAddrBook(t, func() (pstore.AddrBook, func()) {\n\t\tps, err := NewPeerstore(WithClock(clk))\n\t\trequire.NoError(t, err)\n\t\treturn ps, func() { ps.Close() }\n\t}, clk)\n}\n\nfunc TestInMemoryKeyBook(t *testing.T) {\n\tpt.TestKeyBook(t, func() (pstore.KeyBook, func()) {\n\t\tps, err := NewPeerstore()\n\t\trequire.NoError(t, err)\n\t\treturn ps, func() { ps.Close() }\n\t})\n}\n\nfunc BenchmarkInMemoryPeerstore(b *testing.B) {\n\tpt.BenchmarkPeerstore(b, func() (pstore.Peerstore, func()) {\n\t\tps, err := NewPeerstore()\n\t\trequire.NoError(b, err)\n\t\treturn ps, func() { ps.Close() }\n\t}, \"InMem\")\n}\n\nfunc BenchmarkInMemoryKeyBook(b *testing.B) {\n\tpt.BenchmarkKeyBook(b, func() (pstore.KeyBook, func()) {\n\t\tps, err := NewPeerstore()\n\t\trequire.NoError(b, err)\n\t\treturn ps, func() { ps.Close() }\n\t})\n}\n\nfunc TestMain(m *testing.M) {\n\tgoleak.VerifyTestMain(\n\t\tm,\n\t\tgoleak.IgnoreTopFunction(\"github.com/libp2p/go-libp2p/gologshim/writer.(*MirrorWriter).logRoutine\"),\n\t\tgoleak.IgnoreTopFunction(\"go.opencensus.io/stats/view.(*worker).start\"),\n\t)\n}\n\nfunc BenchmarkGC(b *testing.B) {\n\tclock := mockClock.NewMock()\n\tps, err := NewPeerstore(WithClock(clock))\n\trequire.NoError(b, err)\n\tdefer ps.Close()\n\n\tpeerCount := 100_000\n\taddrsPerPeer := 32\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tb.StopTimer()\n\t\tfor i := range peerCount {\n\t\t\tid := peer.ID(strconv.Itoa(i))\n\t\t\taddrs := make([]multiaddr.Multiaddr, addrsPerPeer)\n\t\t\tfor j := range addrsPerPeer {\n\t\t\t\taddrs[j] = multiaddr.StringCast(\"/ip4/1.2.3.4/tcp/\" + strconv.Itoa(j))\n\t\t\t}\n\t\t\tps.AddAddrs(id, addrs, 24*time.Hour)\n\t\t}\n\t\tclock.Add(25 * time.Hour)\n\t\tb.StartTimer()\n\t\tps.gc()\n\t}\n}\n"
  },
  {
    "path": "p2p/host/peerstore/pstoremem/keybook.go",
    "content": "package pstoremem\n\nimport (\n\t\"errors\"\n\t\"sync\"\n\n\tic \"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\tpstore \"github.com/libp2p/go-libp2p/core/peerstore\"\n)\n\ntype memoryKeyBook struct {\n\tsync.RWMutex // same lock. wont happen a ton.\n\tpks          map[peer.ID]ic.PubKey\n\tsks          map[peer.ID]ic.PrivKey\n}\n\nvar _ pstore.KeyBook = (*memoryKeyBook)(nil)\n\nfunc NewKeyBook() *memoryKeyBook {\n\treturn &memoryKeyBook{\n\t\tpks: map[peer.ID]ic.PubKey{},\n\t\tsks: map[peer.ID]ic.PrivKey{},\n\t}\n}\n\nfunc (mkb *memoryKeyBook) PeersWithKeys() peer.IDSlice {\n\tmkb.RLock()\n\tps := make(peer.IDSlice, 0, len(mkb.pks)+len(mkb.sks))\n\tfor p := range mkb.pks {\n\t\tps = append(ps, p)\n\t}\n\tfor p := range mkb.sks {\n\t\tif _, found := mkb.pks[p]; !found {\n\t\t\tps = append(ps, p)\n\t\t}\n\t}\n\tmkb.RUnlock()\n\treturn ps\n}\n\nfunc (mkb *memoryKeyBook) PubKey(p peer.ID) ic.PubKey {\n\tmkb.RLock()\n\tpk := mkb.pks[p]\n\tmkb.RUnlock()\n\tif pk != nil {\n\t\treturn pk\n\t}\n\tpk, err := p.ExtractPublicKey()\n\tif err == nil {\n\t\tmkb.Lock()\n\t\tmkb.pks[p] = pk\n\t\tmkb.Unlock()\n\t}\n\treturn pk\n}\n\nfunc (mkb *memoryKeyBook) AddPubKey(p peer.ID, pk ic.PubKey) error {\n\t// check it's correct first\n\tif !p.MatchesPublicKey(pk) {\n\t\treturn errors.New(\"ID does not match PublicKey\")\n\t}\n\n\tmkb.Lock()\n\tmkb.pks[p] = pk\n\tmkb.Unlock()\n\treturn nil\n}\n\nfunc (mkb *memoryKeyBook) PrivKey(p peer.ID) ic.PrivKey {\n\tmkb.RLock()\n\tdefer mkb.RUnlock()\n\treturn mkb.sks[p]\n}\n\nfunc (mkb *memoryKeyBook) AddPrivKey(p peer.ID, sk ic.PrivKey) error {\n\tif sk == nil {\n\t\treturn errors.New(\"sk is nil (PrivKey)\")\n\t}\n\n\t// check it's correct first\n\tif !p.MatchesPrivateKey(sk) {\n\t\treturn errors.New(\"ID does not match PrivateKey\")\n\t}\n\n\tmkb.Lock()\n\tmkb.sks[p] = sk\n\tmkb.Unlock()\n\treturn nil\n}\n\nfunc (mkb *memoryKeyBook) RemovePeer(p peer.ID) {\n\tmkb.Lock()\n\tdelete(mkb.sks, p)\n\tdelete(mkb.pks, p)\n\tmkb.Unlock()\n}\n"
  },
  {
    "path": "p2p/host/peerstore/pstoremem/metadata.go",
    "content": "package pstoremem\n\nimport (\n\t\"sync\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\tpstore \"github.com/libp2p/go-libp2p/core/peerstore\"\n)\n\ntype memoryPeerMetadata struct {\n\t// store other data, like versions\n\tds     map[peer.ID]map[string]any\n\tdslock sync.RWMutex\n}\n\nvar _ pstore.PeerMetadata = (*memoryPeerMetadata)(nil)\n\nfunc NewPeerMetadata() *memoryPeerMetadata {\n\treturn &memoryPeerMetadata{\n\t\tds: make(map[peer.ID]map[string]any),\n\t}\n}\n\nfunc (ps *memoryPeerMetadata) Put(p peer.ID, key string, val any) error {\n\tps.dslock.Lock()\n\tdefer ps.dslock.Unlock()\n\tm, ok := ps.ds[p]\n\tif !ok {\n\t\tm = make(map[string]any)\n\t\tps.ds[p] = m\n\t}\n\tm[key] = val\n\treturn nil\n}\n\nfunc (ps *memoryPeerMetadata) Get(p peer.ID, key string) (any, error) {\n\tps.dslock.RLock()\n\tdefer ps.dslock.RUnlock()\n\tm, ok := ps.ds[p]\n\tif !ok {\n\t\treturn nil, pstore.ErrNotFound\n\t}\n\tval, ok := m[key]\n\tif !ok {\n\t\treturn nil, pstore.ErrNotFound\n\t}\n\treturn val, nil\n}\n\nfunc (ps *memoryPeerMetadata) RemovePeer(p peer.ID) {\n\tps.dslock.Lock()\n\tdelete(ps.ds, p)\n\tps.dslock.Unlock()\n}\n"
  },
  {
    "path": "p2p/host/peerstore/pstoremem/peerstore.go",
    "content": "package pstoremem\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\tpstore \"github.com/libp2p/go-libp2p/p2p/host/peerstore\"\n)\n\ntype pstoremem struct {\n\tpeerstore.Metrics\n\n\t*memoryKeyBook\n\t*memoryAddrBook\n\t*memoryProtoBook\n\t*memoryPeerMetadata\n}\n\nvar _ peerstore.Peerstore = &pstoremem{}\n\ntype Option any\n\n// NewPeerstore creates an in-memory thread-safe collection of peers.\n// It's the caller's responsibility to call RemovePeer to ensure\n// that memory consumption of the peerstore doesn't grow unboundedly.\nfunc NewPeerstore(opts ...Option) (ps *pstoremem, err error) {\n\tvar protoBookOpts []ProtoBookOption\n\tvar addrBookOpts []AddrBookOption\n\tfor _, opt := range opts {\n\t\tswitch o := opt.(type) {\n\t\tcase ProtoBookOption:\n\t\t\tprotoBookOpts = append(protoBookOpts, o)\n\t\tcase AddrBookOption:\n\t\t\taddrBookOpts = append(addrBookOpts, o)\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"unexpected peer store option: %v\", o)\n\t\t}\n\t}\n\tab := NewAddrBook(addrBookOpts...)\n\n\tpb, err := NewProtoBook(protoBookOpts...)\n\tif err != nil {\n\t\tab.Close()\n\t\treturn nil, err\n\t}\n\n\treturn &pstoremem{\n\t\tMetrics:            pstore.NewMetrics(),\n\t\tmemoryKeyBook:      NewKeyBook(),\n\t\tmemoryAddrBook:     ab,\n\t\tmemoryProtoBook:    pb,\n\t\tmemoryPeerMetadata: NewPeerMetadata(),\n\t}, nil\n}\n\nfunc (ps *pstoremem) Close() (err error) {\n\tvar errs []error\n\tweakClose := func(name string, c any) {\n\t\tif cl, ok := c.(io.Closer); ok {\n\t\t\tif err = cl.Close(); err != nil {\n\t\t\t\terrs = append(errs, fmt.Errorf(\"%s error: %s\", name, err))\n\t\t\t}\n\t\t}\n\t}\n\tweakClose(\"keybook\", ps.memoryKeyBook)\n\tweakClose(\"addressbook\", ps.memoryAddrBook)\n\tweakClose(\"protobook\", ps.memoryProtoBook)\n\tweakClose(\"peermetadata\", ps.memoryPeerMetadata)\n\n\tif len(errs) > 0 {\n\t\treturn fmt.Errorf(\"failed while closing peerstore; err(s): %q\", errs)\n\t}\n\treturn nil\n}\n\nfunc (ps *pstoremem) Peers() peer.IDSlice {\n\tset := map[peer.ID]struct{}{}\n\tfor _, p := range ps.PeersWithKeys() {\n\t\tset[p] = struct{}{}\n\t}\n\tfor _, p := range ps.PeersWithAddrs() {\n\t\tset[p] = struct{}{}\n\t}\n\n\tpps := make(peer.IDSlice, 0, len(set))\n\tfor p := range set {\n\t\tpps = append(pps, p)\n\t}\n\treturn pps\n}\n\nfunc (ps *pstoremem) PeerInfo(p peer.ID) peer.AddrInfo {\n\treturn peer.AddrInfo{\n\t\tID:    p,\n\t\tAddrs: ps.memoryAddrBook.Addrs(p),\n\t}\n}\n\n// RemovePeer removes entries associated with a peer from:\n// * the KeyBook\n// * the ProtoBook\n// * the PeerMetadata\n// * the Metrics\n// It DOES NOT remove the peer from the AddrBook.\nfunc (ps *pstoremem) RemovePeer(p peer.ID) {\n\tps.memoryKeyBook.RemovePeer(p)\n\tps.memoryProtoBook.RemovePeer(p)\n\tps.memoryPeerMetadata.RemovePeer(p)\n\tps.Metrics.RemovePeer(p)\n}\n"
  },
  {
    "path": "p2p/host/peerstore/pstoremem/peerstore_test.go",
    "content": "package pstoremem\n\nimport (\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestPeerStoreAddrBookOpts(t *testing.T) {\n\tps, err := NewPeerstore(WithMaxAddresses(1))\n\trequire.NoError(t, err)\n\tdefer ps.Close()\n\n\tps.AddAddr(\"p1\", ma.StringCast(\"/ip4/1.2.3.4/udp/1/quic-v1\"), peerstore.TempAddrTTL)\n\tres := ps.Addrs(\"p1\")\n\trequire.NotEmpty(t, res)\n\n\tps.AddAddr(\"p2\", ma.StringCast(\"/ip4/1.2.3.4/udp/1/quic-v1\"), peerstore.TempAddrTTL)\n\tres = ps.Addrs(\"p2\")\n\trequire.Empty(t, res)\n}\n"
  },
  {
    "path": "p2p/host/peerstore/pstoremem/protobook.go",
    "content": "package pstoremem\n\nimport (\n\t\"errors\"\n\t\"sync\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\tpstore \"github.com/libp2p/go-libp2p/core/peerstore\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n)\n\ntype protoSegment struct {\n\tsync.RWMutex\n\tprotocols map[peer.ID]map[protocol.ID]struct{}\n}\n\ntype protoSegments [256]*protoSegment\n\nfunc (s *protoSegments) get(p peer.ID) *protoSegment {\n\treturn s[p[len(p)-1]]\n}\n\nvar errTooManyProtocols = errors.New(\"too many protocols\")\n\ntype memoryProtoBook struct {\n\tsegments protoSegments\n\n\tmaxProtos int\n}\n\nvar _ pstore.ProtoBook = (*memoryProtoBook)(nil)\n\ntype ProtoBookOption func(book *memoryProtoBook) error\n\nfunc WithMaxProtocols(num int) ProtoBookOption {\n\treturn func(pb *memoryProtoBook) error {\n\t\tpb.maxProtos = num\n\t\treturn nil\n\t}\n}\n\nfunc NewProtoBook(opts ...ProtoBookOption) (*memoryProtoBook, error) {\n\tpb := &memoryProtoBook{\n\t\tsegments: func() (ret protoSegments) {\n\t\t\tfor i := range ret {\n\t\t\t\tret[i] = &protoSegment{\n\t\t\t\t\tprotocols: make(map[peer.ID]map[protocol.ID]struct{}),\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn ret\n\t\t}(),\n\t\tmaxProtos: 128,\n\t}\n\n\tfor _, opt := range opts {\n\t\tif err := opt(pb); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn pb, nil\n}\n\nfunc (pb *memoryProtoBook) SetProtocols(p peer.ID, protos ...protocol.ID) error {\n\tif len(protos) > pb.maxProtos {\n\t\treturn errTooManyProtocols\n\t}\n\n\tnewprotos := make(map[protocol.ID]struct{}, len(protos))\n\tfor _, proto := range protos {\n\t\tnewprotos[proto] = struct{}{}\n\t}\n\n\ts := pb.segments.get(p)\n\ts.Lock()\n\ts.protocols[p] = newprotos\n\ts.Unlock()\n\n\treturn nil\n}\n\nfunc (pb *memoryProtoBook) AddProtocols(p peer.ID, protos ...protocol.ID) error {\n\ts := pb.segments.get(p)\n\ts.Lock()\n\tdefer s.Unlock()\n\n\tprotomap, ok := s.protocols[p]\n\tif !ok {\n\t\tprotomap = make(map[protocol.ID]struct{})\n\t\ts.protocols[p] = protomap\n\t}\n\tif len(protomap)+len(protos) > pb.maxProtos {\n\t\treturn errTooManyProtocols\n\t}\n\n\tfor _, proto := range protos {\n\t\tprotomap[proto] = struct{}{}\n\t}\n\treturn nil\n}\n\nfunc (pb *memoryProtoBook) GetProtocols(p peer.ID) ([]protocol.ID, error) {\n\ts := pb.segments.get(p)\n\ts.RLock()\n\tdefer s.RUnlock()\n\n\tout := make([]protocol.ID, 0, len(s.protocols[p]))\n\tfor k := range s.protocols[p] {\n\t\tout = append(out, k)\n\t}\n\n\treturn out, nil\n}\n\nfunc (pb *memoryProtoBook) RemoveProtocols(p peer.ID, protos ...protocol.ID) error {\n\ts := pb.segments.get(p)\n\ts.Lock()\n\tdefer s.Unlock()\n\n\tprotomap, ok := s.protocols[p]\n\tif !ok {\n\t\t// nothing to remove.\n\t\treturn nil\n\t}\n\n\tfor _, proto := range protos {\n\t\tdelete(protomap, proto)\n\t}\n\tif len(protomap) == 0 {\n\t\tdelete(s.protocols, p)\n\t}\n\treturn nil\n}\n\nfunc (pb *memoryProtoBook) SupportsProtocols(p peer.ID, protos ...protocol.ID) ([]protocol.ID, error) {\n\ts := pb.segments.get(p)\n\ts.RLock()\n\tdefer s.RUnlock()\n\n\tout := make([]protocol.ID, 0, len(protos))\n\tfor _, proto := range protos {\n\t\tif _, ok := s.protocols[p][proto]; ok {\n\t\t\tout = append(out, proto)\n\t\t}\n\t}\n\n\treturn out, nil\n}\n\nfunc (pb *memoryProtoBook) FirstSupportedProtocol(p peer.ID, protos ...protocol.ID) (protocol.ID, error) {\n\ts := pb.segments.get(p)\n\ts.RLock()\n\tdefer s.RUnlock()\n\n\tfor _, proto := range protos {\n\t\tif _, ok := s.protocols[p][proto]; ok {\n\t\t\treturn proto, nil\n\t\t}\n\t}\n\treturn \"\", nil\n}\n\nfunc (pb *memoryProtoBook) RemovePeer(p peer.ID) {\n\ts := pb.segments.get(p)\n\ts.Lock()\n\tdelete(s.protocols, p)\n\ts.Unlock()\n}\n"
  },
  {
    "path": "p2p/host/peerstore/pstoremem/sorting.go",
    "content": "package pstoremem\n\nimport (\n\t\"bytes\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmafmt \"github.com/multiformats/go-multiaddr-fmt\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\nfunc isFDCostlyTransport(a ma.Multiaddr) bool {\n\treturn mafmt.TCP.Matches(a)\n}\n\ntype addrList []ma.Multiaddr\n\nfunc (al addrList) Len() int      { return len(al) }\nfunc (al addrList) Swap(i, j int) { al[i], al[j] = al[j], al[i] }\n\nfunc (al addrList) Less(i, j int) bool {\n\ta := al[i]\n\tb := al[j]\n\n\t// dial localhost addresses next, they should fail immediately\n\tlba := manet.IsIPLoopback(a)\n\tlbb := manet.IsIPLoopback(b)\n\tif lba && !lbb {\n\t\treturn true\n\t}\n\n\t// dial utp and similar 'non-fd-consuming' addresses first\n\tfda := isFDCostlyTransport(a)\n\tfdb := isFDCostlyTransport(b)\n\tif !fda {\n\t\treturn fdb\n\t}\n\n\t// if 'b' doesnt take a file descriptor\n\tif !fdb {\n\t\treturn false\n\t}\n\n\t// if 'b' is loopback and both take file descriptors\n\tif lbb {\n\t\treturn false\n\t}\n\n\t// for the rest, just sort by bytes\n\treturn bytes.Compare(a.Bytes(), b.Bytes()) > 0\n}\n"
  },
  {
    "path": "p2p/host/peerstore/pstoremem/sorting_test.go",
    "content": "package pstoremem\n\nimport (\n\t\"sort\"\n\t\"testing\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestAddressSorting(t *testing.T) {\n\tu1 := ma.StringCast(\"/ip4/152.12.23.53/udp/1234/utp\")\n\tu2l := ma.StringCast(\"/ip4/127.0.0.1/udp/1234/utp\")\n\tlocal := ma.StringCast(\"/ip4/127.0.0.1/tcp/1234\")\n\tnorm := ma.StringCast(\"/ip4/6.5.4.3/tcp/1234\")\n\n\tl := addrList{local, u1, u2l, norm}\n\tsort.Sort(l)\n\trequire.Equal(t, addrList{u2l, u1, local, norm}, l)\n}\n"
  },
  {
    "path": "p2p/host/peerstore/test/addr_book_suite.go",
    "content": "package test\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\tpstore \"github.com/libp2p/go-libp2p/core/peerstore\"\n\t\"github.com/libp2p/go-libp2p/core/record\"\n\t\"github.com/libp2p/go-libp2p/core/test\"\n\n\tmockClock \"github.com/benbjohnson/clock\"\n\t\"github.com/multiformats/go-multiaddr\"\n)\n\nvar addressBookSuite = map[string]func(book pstore.AddrBook, clk *mockClock.Mock) func(*testing.T){\n\t\"AddAddress\":           testAddAddress,\n\t\"Clear\":                testClearWorks,\n\t\"SetNegativeTTLClears\": testSetNegativeTTLClears,\n\t\"UpdateTTLs\":           testUpdateTTLs,\n\t\"NilAddrsDontBreak\":    testNilAddrsDontBreak,\n\t\"AddressesExpire\":      testAddressesExpire,\n\t\"ClearWithIter\":        testClearWithIterator,\n\t\"PeersWithAddresses\":   testPeersWithAddrs,\n\t\"CertifiedAddresses\":   testCertifiedAddresses,\n}\n\ntype AddrBookFactory func() (pstore.AddrBook, func())\n\nfunc TestAddrBook(t *testing.T, factory AddrBookFactory, clk *mockClock.Mock) {\n\tfor name, test := range addressBookSuite {\n\t\t// Create a new peerstore.\n\t\tab, closeFunc := factory()\n\n\t\t// Run the test.\n\t\tt.Run(name, test(ab, clk))\n\n\t\t// Cleanup.\n\t\tif closeFunc != nil {\n\t\t\tcloseFunc()\n\t\t}\n\t}\n}\n\nfunc testAddAddress(ab pstore.AddrBook, clk *mockClock.Mock) func(*testing.T) {\n\treturn func(t *testing.T) {\n\t\tt.Run(\"add a single address\", func(t *testing.T) {\n\t\t\tid := GeneratePeerIDs(1)[0]\n\t\t\taddrs := GenerateAddrs(1)\n\n\t\t\tab.AddAddr(id, addrs[0], time.Hour)\n\n\t\t\tAssertAddressesEqual(t, addrs, ab.Addrs(id))\n\t\t})\n\n\t\tt.Run(\"idempotent add single address\", func(t *testing.T) {\n\t\t\tid := GeneratePeerIDs(1)[0]\n\t\t\taddrs := GenerateAddrs(1)\n\n\t\t\tab.AddAddr(id, addrs[0], time.Hour)\n\t\t\tab.AddAddr(id, addrs[0], time.Hour)\n\n\t\t\tAssertAddressesEqual(t, addrs, ab.Addrs(id))\n\t\t})\n\n\t\tt.Run(\"add multiple addresses\", func(t *testing.T) {\n\t\t\tid := GeneratePeerIDs(1)[0]\n\t\t\taddrs := GenerateAddrs(3)\n\n\t\t\tab.AddAddrs(id, addrs, time.Hour)\n\t\t\tAssertAddressesEqual(t, addrs, ab.Addrs(id))\n\t\t})\n\n\t\tt.Run(\"idempotent add multiple addresses\", func(t *testing.T) {\n\t\t\tid := GeneratePeerIDs(1)[0]\n\t\t\taddrs := GenerateAddrs(3)\n\n\t\t\tab.AddAddrs(id, addrs, time.Hour)\n\t\t\tab.AddAddrs(id, addrs, time.Hour)\n\n\t\t\tAssertAddressesEqual(t, addrs, ab.Addrs(id))\n\t\t})\n\n\t\tt.Run(\"adding an existing address with a later expiration extends its ttl\", func(t *testing.T) {\n\t\t\tid := GeneratePeerIDs(1)[0]\n\t\t\taddrs := GenerateAddrs(3)\n\n\t\t\tab.AddAddrs(id, addrs, time.Second)\n\n\t\t\t// same address as before but with a higher TTL\n\t\t\tab.AddAddrs(id, addrs[2:], time.Hour)\n\n\t\t\t// after the initial TTL has expired, check that only the third address is present.\n\t\t\tclk.Add(1200 * time.Millisecond)\n\t\t\tAssertAddressesEqual(t, addrs[2:], ab.Addrs(id))\n\n\t\t\t// make sure we actually set the TTL\n\t\t\tab.UpdateAddrs(id, time.Hour, 0)\n\t\t\tAssertAddressesEqual(t, nil, ab.Addrs(id))\n\t\t})\n\n\t\tt.Run(\"adding an existing address with an earlier expiration never reduces the expiration\", func(t *testing.T) {\n\t\t\tid := GeneratePeerIDs(1)[0]\n\t\t\taddrs := GenerateAddrs(3)\n\n\t\t\tab.AddAddrs(id, addrs, time.Hour)\n\n\t\t\t// same address as before but with a lower TTL\n\t\t\tab.AddAddrs(id, addrs[2:], time.Second)\n\n\t\t\t// after the initial TTL has expired, check that all three addresses are still present (i.e. the TTL on\n\t\t\t// the modified one was not shortened).\n\t\t\tclk.Add(2100 * time.Millisecond)\n\t\t\tAssertAddressesEqual(t, addrs, ab.Addrs(id))\n\t\t})\n\n\t\tt.Run(\"adding an existing address with an earlier expiration never reduces the TTL\", func(t *testing.T) {\n\t\t\tid := GeneratePeerIDs(1)[0]\n\t\t\taddrs := GenerateAddrs(1)\n\n\t\t\tab.AddAddrs(id, addrs, 4*time.Second)\n\t\t\t// 4 seconds left\n\t\t\tclk.Add(2 * time.Second)\n\t\t\t// 2 second left\n\t\t\tab.AddAddrs(id, addrs, 3*time.Second)\n\t\t\t// 3 seconds left\n\t\t\tclk.Add(1 * time.Second)\n\t\t\t// 2 seconds left.\n\n\t\t\t// We still have the address.\n\t\t\tAssertAddressesEqual(t, addrs, ab.Addrs(id))\n\n\t\t\t// The TTL wasn't reduced\n\t\t\tab.UpdateAddrs(id, 4*time.Second, 0)\n\t\t\tAssertAddressesEqual(t, nil, ab.Addrs(id))\n\t\t})\n\n\t\tt.Run(\"accessing an empty peer ID\", func(t *testing.T) {\n\t\t\taddrs := GenerateAddrs(5)\n\t\t\tab.AddAddrs(\"\", addrs, time.Hour)\n\t\t\tAssertAddressesEqual(t, addrs, ab.Addrs(\"\"))\n\t\t})\n\n\t\tt.Run(\"add a /p2p address with valid peerid\", func(t *testing.T) {\n\t\t\tpeerId := GeneratePeerIDs(1)[0]\n\t\t\taddr := GenerateAddrs(1)\n\t\t\tp2pAddr := addr[0].Encapsulate(Multiaddr(\"/p2p/\" + peerId.String()))\n\t\t\tab.AddAddr(peerId, p2pAddr, time.Hour)\n\t\t\tAssertAddressesEqual(t, addr, ab.Addrs(peerId))\n\t\t})\n\n\t\tt.Run(\"add a /p2p address with invalid peerid\", func(t *testing.T) {\n\t\t\tpids := GeneratePeerIDs(2)\n\t\t\tpid1 := pids[0]\n\t\t\tpid2 := pids[1]\n\t\t\taddr := GenerateAddrs(1)\n\t\t\tp2pAddr := addr[0].Encapsulate(Multiaddr(\"/p2p/\" + pid1.String()))\n\t\t\tab.AddAddr(pid2, p2pAddr, time.Hour)\n\t\t\tAssertAddressesEqual(t, nil, ab.Addrs(pid2))\n\t\t})\n\t}\n}\n\nfunc testClearWorks(ab pstore.AddrBook, _ *mockClock.Mock) func(t *testing.T) {\n\treturn func(t *testing.T) {\n\t\tids := GeneratePeerIDs(2)\n\t\taddrs := GenerateAddrs(5)\n\n\t\tab.AddAddrs(ids[0], addrs[0:3], time.Hour)\n\t\tab.AddAddrs(ids[1], addrs[3:], time.Hour)\n\n\t\tAssertAddressesEqual(t, addrs[0:3], ab.Addrs(ids[0]))\n\t\tAssertAddressesEqual(t, addrs[3:], ab.Addrs(ids[1]))\n\n\t\tab.ClearAddrs(ids[0])\n\t\tAssertAddressesEqual(t, nil, ab.Addrs(ids[0]))\n\t\tAssertAddressesEqual(t, addrs[3:], ab.Addrs(ids[1]))\n\n\t\tab.ClearAddrs(ids[1])\n\t\tAssertAddressesEqual(t, nil, ab.Addrs(ids[0]))\n\t\tAssertAddressesEqual(t, nil, ab.Addrs(ids[1]))\n\t}\n}\n\nfunc testSetNegativeTTLClears(m pstore.AddrBook, _ *mockClock.Mock) func(t *testing.T) {\n\treturn func(t *testing.T) {\n\t\tid := GeneratePeerIDs(1)[0]\n\t\taddrs := GenerateAddrs(100)\n\n\t\tm.SetAddrs(id, addrs, time.Hour)\n\t\tAssertAddressesEqual(t, addrs, m.Addrs(id))\n\n\t\t// remove two addresses.\n\t\tm.SetAddr(id, addrs[50], -1)\n\t\tm.SetAddr(id, addrs[75], -1)\n\n\t\t// calculate the survivors\n\t\tsurvivors := append(addrs[0:50], addrs[51:]...)\n\t\tsurvivors = append(survivors[0:74], survivors[75:]...)\n\n\t\tAssertAddressesEqual(t, survivors, m.Addrs(id))\n\n\t\t// remove _all_ the addresses\n\t\tm.SetAddrs(id, survivors, -1)\n\t\tif len(m.Addrs(id)) != 0 {\n\t\t\tt.Error(\"expected empty address list after clearing all addresses\")\n\t\t}\n\n\t\t// add half, but try to remove more than we added\n\t\tm.SetAddrs(id, addrs[:50], time.Hour)\n\t\tm.SetAddrs(id, addrs, -1)\n\t\tif len(m.Addrs(id)) != 0 {\n\t\t\tt.Error(\"expected empty address list after clearing all addresses\")\n\t\t}\n\n\t\t// try to remove the same addr multiple times\n\t\tm.SetAddrs(id, addrs[:5], time.Hour)\n\t\trepeated := make([]multiaddr.Multiaddr, 10)\n\t\tfor i := range repeated {\n\t\t\trepeated[i] = addrs[0]\n\t\t}\n\t\tm.SetAddrs(id, repeated, -1)\n\t\tif len(m.Addrs(id)) != 4 {\n\t\t\tt.Errorf(\"expected 4 addrs after removing one, got %d\", len(m.Addrs(id)))\n\t\t}\n\t}\n}\n\nfunc testUpdateTTLs(m pstore.AddrBook, clk *mockClock.Mock) func(t *testing.T) {\n\treturn func(t *testing.T) {\n\t\tt.Run(\"update ttl of peer with no addrs\", func(_ *testing.T) {\n\t\t\tid := GeneratePeerIDs(1)[0]\n\n\t\t\t// Shouldn't panic.\n\t\t\tm.UpdateAddrs(id, time.Hour, time.Minute)\n\t\t})\n\n\t\tt.Run(\"update to 0 clears addrs\", func(t *testing.T) {\n\t\t\tid := GeneratePeerIDs(1)[0]\n\t\t\taddrs := GenerateAddrs(1)\n\n\t\t\t// Shouldn't panic.\n\t\t\tm.SetAddrs(id, addrs, time.Hour)\n\t\t\tm.UpdateAddrs(id, time.Hour, 0)\n\t\t\tif len(m.Addrs(id)) != 0 {\n\t\t\t\tt.Error(\"expected no addresses\")\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"update ttls successfully\", func(t *testing.T) {\n\t\t\tids := GeneratePeerIDs(2)\n\t\t\taddrs1, addrs2 := GenerateAddrs(2), GenerateAddrs(2)\n\n\t\t\t// set two keys with different ttls for each peer.\n\t\t\tm.SetAddr(ids[0], addrs1[0], time.Hour)\n\t\t\tm.SetAddr(ids[0], addrs1[1], time.Minute)\n\t\t\tm.SetAddr(ids[1], addrs2[0], time.Hour)\n\t\t\tm.SetAddr(ids[1], addrs2[1], time.Minute)\n\n\t\t\t// Sanity check.\n\t\t\tAssertAddressesEqual(t, addrs1, m.Addrs(ids[0]))\n\t\t\tAssertAddressesEqual(t, addrs2, m.Addrs(ids[1]))\n\n\t\t\t// Will only affect addrs1[0].\n\t\t\t// Badger does not support subsecond TTLs.\n\t\t\t// https://github.com/dgraph-io/badger/issues/339\n\t\t\tm.UpdateAddrs(ids[0], time.Hour, 1*time.Second)\n\n\t\t\t// No immediate effect.\n\t\t\tAssertAddressesEqual(t, addrs1, m.Addrs(ids[0]))\n\t\t\tAssertAddressesEqual(t, addrs2, m.Addrs(ids[1]))\n\n\t\t\t// After a wait, addrs[0] is gone.\n\t\t\tclk.Add(2 * time.Second)\n\t\t\tAssertAddressesEqual(t, addrs1[1:2], m.Addrs(ids[0]))\n\t\t\tAssertAddressesEqual(t, addrs2, m.Addrs(ids[1]))\n\n\t\t\t// Will only affect addrs2[0].\n\t\t\tm.UpdateAddrs(ids[1], time.Hour, 1*time.Second)\n\n\t\t\t// No immediate effect.\n\t\t\tAssertAddressesEqual(t, addrs1[1:2], m.Addrs(ids[0]))\n\t\t\tAssertAddressesEqual(t, addrs2, m.Addrs(ids[1]))\n\n\t\t\tclk.Add(2 * time.Second)\n\n\t\t\t// First addrs is gone in both.\n\t\t\tAssertAddressesEqual(t, addrs1[1:], m.Addrs(ids[0]))\n\t\t\tAssertAddressesEqual(t, addrs2[1:], m.Addrs(ids[1]))\n\t\t})\n\n\t}\n}\n\nfunc testNilAddrsDontBreak(m pstore.AddrBook, _ *mockClock.Mock) func(t *testing.T) {\n\treturn func(_ *testing.T) {\n\t\tid := GeneratePeerIDs(1)[0]\n\n\t\tm.SetAddr(id, nil, time.Hour)\n\t\tm.AddAddr(id, nil, time.Hour)\n\t}\n}\n\nfunc testAddressesExpire(m pstore.AddrBook, clk *mockClock.Mock) func(t *testing.T) {\n\treturn func(t *testing.T) {\n\t\tids := GeneratePeerIDs(2)\n\t\taddrs1 := GenerateAddrs(3)\n\t\taddrs2 := GenerateAddrs(2)\n\n\t\tm.AddAddrs(ids[0], addrs1, time.Hour)\n\t\tm.AddAddrs(ids[1], addrs2, time.Hour)\n\n\t\tAssertAddressesEqual(t, addrs1, m.Addrs(ids[0]))\n\t\tAssertAddressesEqual(t, addrs2, m.Addrs(ids[1]))\n\n\t\tm.AddAddrs(ids[0], addrs1, 2*time.Hour)\n\t\tm.AddAddrs(ids[1], addrs2, 2*time.Hour)\n\n\t\tAssertAddressesEqual(t, addrs1, m.Addrs(ids[0]))\n\t\tAssertAddressesEqual(t, addrs2, m.Addrs(ids[1]))\n\n\t\tm.SetAddr(ids[0], addrs1[0], 100*time.Microsecond)\n\t\tclk.Add(100 * time.Millisecond)\n\t\tAssertAddressesEqual(t, addrs1[1:3], m.Addrs(ids[0]))\n\t\tAssertAddressesEqual(t, addrs2, m.Addrs(ids[1]))\n\n\t\tm.SetAddr(ids[0], addrs1[2], 100*time.Microsecond)\n\t\tclk.Add(100 * time.Millisecond)\n\t\tAssertAddressesEqual(t, addrs1[1:2], m.Addrs(ids[0]))\n\t\tAssertAddressesEqual(t, addrs2, m.Addrs(ids[1]))\n\n\t\tm.SetAddr(ids[1], addrs2[0], 100*time.Microsecond)\n\t\tclk.Add(100 * time.Millisecond)\n\t\tAssertAddressesEqual(t, addrs1[1:2], m.Addrs(ids[0]))\n\t\tAssertAddressesEqual(t, addrs2[1:], m.Addrs(ids[1]))\n\n\t\tm.SetAddr(ids[1], addrs2[1], 100*time.Microsecond)\n\t\tclk.Add(100 * time.Millisecond)\n\t\tAssertAddressesEqual(t, addrs1[1:2], m.Addrs(ids[0]))\n\t\tAssertAddressesEqual(t, nil, m.Addrs(ids[1]))\n\n\t\tm.SetAddr(ids[0], addrs1[1], 100*time.Microsecond)\n\t\tclk.Add(100 * time.Millisecond)\n\t\tAssertAddressesEqual(t, nil, m.Addrs(ids[0]))\n\t\tAssertAddressesEqual(t, nil, m.Addrs(ids[1]))\n\t}\n}\n\nfunc testClearWithIterator(m pstore.AddrBook, _ *mockClock.Mock) func(t *testing.T) {\n\treturn func(t *testing.T) {\n\t\tids := GeneratePeerIDs(2)\n\t\taddrs := GenerateAddrs(100)\n\n\t\t// Add the peers with 50 addresses each.\n\t\tm.AddAddrs(ids[0], addrs[:50], pstore.PermanentAddrTTL)\n\t\tm.AddAddrs(ids[1], addrs[50:], pstore.PermanentAddrTTL)\n\n\t\tif all := append(m.Addrs(ids[0]), m.Addrs(ids[1])...); len(all) != 100 {\n\t\t\tt.Fatal(\"expected pstore to contain both peers with all their maddrs\")\n\t\t}\n\n\t\t// Since we don't fetch these peers, they won't be present in cache.\n\n\t\tm.ClearAddrs(ids[0])\n\t\tif all := append(m.Addrs(ids[0]), m.Addrs(ids[1])...); len(all) != 50 {\n\t\t\tt.Fatal(\"expected pstore to contain only addrs of peer 2\")\n\t\t}\n\n\t\tm.ClearAddrs(ids[1])\n\t\tif all := append(m.Addrs(ids[0]), m.Addrs(ids[1])...); len(all) != 0 {\n\t\t\tt.Fatal(\"expected pstore to contain no addresses\")\n\t\t}\n\t}\n}\n\nfunc testPeersWithAddrs(m pstore.AddrBook, _ *mockClock.Mock) func(t *testing.T) {\n\treturn func(t *testing.T) {\n\t\t// cannot run in parallel as the store is modified.\n\t\t// go runs sequentially in the specified order\n\t\t// see https://blog.golang.org/subtests\n\n\t\tt.Run(\"empty addrbook\", func(t *testing.T) {\n\t\t\tif peers := m.PeersWithAddrs(); len(peers) != 0 {\n\t\t\t\tt.Fatal(\"expected to find no peers\")\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"non-empty addrbook\", func(t *testing.T) {\n\t\t\tids := GeneratePeerIDs(2)\n\t\t\taddrs := GenerateAddrs(10)\n\n\t\t\tm.AddAddrs(ids[0], addrs[:5], pstore.PermanentAddrTTL)\n\t\t\tm.AddAddrs(ids[1], addrs[5:], pstore.PermanentAddrTTL)\n\n\t\t\tif peers := m.PeersWithAddrs(); len(peers) != 2 {\n\t\t\t\tt.Fatal(\"expected to find 2 peers\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc testCertifiedAddresses(m pstore.AddrBook, clk *mockClock.Mock) func(*testing.T) {\n\treturn func(t *testing.T) {\n\t\tcab := m.(pstore.CertifiedAddrBook)\n\n\t\tpriv, _, err := test.RandTestKeyPair(crypto.Ed25519, 256)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"error generating testing keys: %v\", err)\n\t\t}\n\n\t\tid, _ := peer.IDFromPrivateKey(priv)\n\t\tallAddrs := GenerateAddrs(10)\n\t\tcertifiedAddrs := allAddrs[:5]\n\t\tuncertifiedAddrs := allAddrs[5:]\n\t\trec1 := peer.NewPeerRecord()\n\t\trec1.PeerID = id\n\t\trec1.Addrs = certifiedAddrs\n\t\tsignedRec1, err := record.Seal(rec1, priv)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"error creating signed routing record: %v\", err)\n\t\t}\n\n\t\trec2 := peer.NewPeerRecord()\n\t\trec2.PeerID = id\n\t\trec2.Addrs = certifiedAddrs\n\t\tsignedRec2, err := record.Seal(rec2, priv)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"error creating signed routing record: %v\", err)\n\t\t}\n\n\t\t// add a few non-certified addrs\n\t\tm.AddAddrs(id, uncertifiedAddrs, time.Hour)\n\n\t\t// make sure they're present\n\t\tAssertAddressesEqual(t, uncertifiedAddrs, m.Addrs(id))\n\n\t\t// add the signed record to addr book\n\t\taccepted, err := cab.ConsumePeerRecord(signedRec2, time.Hour)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"error adding signed routing record to addrbook: %v\", err)\n\t\t}\n\t\tif !accepted {\n\t\t\tt.Errorf(\"should have accepted signed peer record\")\n\t\t}\n\n\t\t// the non-certified addrs should be gone & we should get only certified addrs back from Addrs\n\t\t// AssertAddressesEqual(t, certifiedAddrs, m.Addrs(id))\n\t\tAssertAddressesEqual(t, allAddrs, m.Addrs(id))\n\n\t\t// PeersWithAddrs should return a single peer\n\t\tif len(m.PeersWithAddrs()) != 1 {\n\t\t\tt.Errorf(\"expected PeersWithAddrs to return 1, got %d\", len(m.PeersWithAddrs()))\n\t\t}\n\n\t\t// Adding an old record should fail\n\t\taccepted, err = cab.ConsumePeerRecord(signedRec1, time.Hour)\n\t\tif accepted {\n\t\t\tt.Error(\"We should have failed to accept a record with an old sequence number\")\n\t\t}\n\t\tif err != nil {\n\t\t\tt.Errorf(\"expected no error, got: %s\", err)\n\t\t}\n\n\t\t// once certified addrs exist, trying to add non-certified addrs should have no effect\n\t\t// m.AddAddrs(id, uncertifiedAddrs, time.Hour)\n\t\t// AssertAddressesEqual(t, certifiedAddrs, m.Addrs(id))\n\t\t// XXX: Disabled until signed records are required\n\t\tm.AddAddrs(id, uncertifiedAddrs, time.Hour)\n\t\tAssertAddressesEqual(t, allAddrs, m.Addrs(id))\n\n\t\t// we should be able to retrieve the signed peer record\n\t\trec3 := cab.GetPeerRecord(id)\n\t\tif rec3 == nil || !signedRec2.Equal(rec3) {\n\t\t\tt.Error(\"unable to retrieve signed routing record from addrbook\")\n\t\t}\n\n\t\t// Adding a new envelope should clear existing certified addresses.\n\t\t// Only the newly-added ones should remain\n\t\tcertifiedAddrs = certifiedAddrs[:3]\n\t\trec4 := peer.NewPeerRecord()\n\t\trec4.PeerID = id\n\t\trec4.Addrs = certifiedAddrs\n\t\tsignedRec4, err := record.Seal(rec4, priv)\n\t\ttest.AssertNilError(t, err)\n\t\taccepted, err = cab.ConsumePeerRecord(signedRec4, time.Hour)\n\t\ttest.AssertNilError(t, err)\n\t\tif !accepted {\n\t\t\tt.Error(\"expected peer record to be accepted\")\n\t\t}\n\t\t// AssertAddressesEqual(t, certifiedAddrs, m.Addrs(id))\n\t\tAssertAddressesEqual(t, allAddrs, m.Addrs(id))\n\n\t\t// update TTL on signed addrs to -1 to remove them.\n\t\t// the signed routing record should be deleted\n\t\t// m.SetAddrs(id, certifiedAddrs, -1)\n\t\t// XXX: Disabled until signed records are required\n\t\tm.SetAddrs(id, allAddrs, -1)\n\t\tif len(m.Addrs(id)) != 0 {\n\t\t\tt.Error(\"expected zero certified addrs after setting TTL to -1\")\n\t\t}\n\t\tif cab.GetPeerRecord(id) != nil {\n\t\t\tt.Error(\"expected signed peer record to be removed when addresses expire\")\n\t\t}\n\n\t\t// Test that natural TTL expiration clears signed peer records\n\t\taccepted, err = cab.ConsumePeerRecord(signedRec4, time.Second)\n\t\tif !accepted {\n\t\t\tt.Error(\"expected peer record to be accepted\")\n\t\t}\n\t\ttest.AssertNilError(t, err)\n\t\tAssertAddressesEqual(t, certifiedAddrs, m.Addrs(id))\n\n\t\tclk.Add(2 * time.Second)\n\t\tif cab.GetPeerRecord(id) != nil {\n\t\t\tt.Error(\"expected signed peer record to be removed when addresses expire\")\n\t\t}\n\n\t\t// adding a peer record that's signed with the wrong key should fail\n\t\tpriv2, _, err := test.RandTestKeyPair(crypto.Ed25519, 256)\n\t\ttest.AssertNilError(t, err)\n\t\tenv, err := record.Seal(rec4, priv2)\n\t\ttest.AssertNilError(t, err)\n\n\t\taccepted, err = cab.ConsumePeerRecord(env, time.Second)\n\t\tif accepted || err == nil {\n\t\t\tt.Error(\"expected adding a PeerRecord that's signed with the wrong key to fail\")\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "p2p/host/peerstore/test/benchmarks_suite.go",
    "content": "package test\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\tpstore \"github.com/libp2p/go-libp2p/core/peerstore\"\n)\n\nfunc BenchmarkPeerstore(b *testing.B, factory PeerstoreFactory, _ string) {\n\tfor _, sz := range []int{1, 10, 100} {\n\t\tconst N = 10000\n\t\tpeers := getPeerPairs(b, N, sz)\n\n\t\tb.Run(fmt.Sprintf(\"AddAddrs-%d\", sz), func(b *testing.B) {\n\t\t\tps, cleanup := factory()\n\t\t\tdefer cleanup()\n\t\t\tb.ResetTimer()\n\t\t\tfor i := 0; i < b.N; i++ {\n\t\t\t\tpp := peers[i%N]\n\t\t\t\tps.AddAddrs(pp.ID, pp.Addr, pstore.RecentlyConnectedAddrTTL)\n\t\t\t}\n\t\t})\n\n\t\tb.Run(fmt.Sprintf(\"GetAddrs-%d\", sz), func(b *testing.B) {\n\t\t\tps, cleanup := factory()\n\t\t\tdefer cleanup()\n\t\t\tb.ResetTimer()\n\t\t\tfor i := 0; i < b.N; i++ {\n\t\t\t\tpp := peers[i%N]\n\t\t\t\tps.SetAddrs(pp.ID, pp.Addr, pstore.RecentlyConnectedAddrTTL)\n\t\t\t}\n\t\t})\n\n\t\tb.Run(fmt.Sprintf(\"GetAndClearAddrs-%d\", sz), func(b *testing.B) {\n\t\t\tps, cleanup := factory()\n\t\t\tdefer cleanup()\n\t\t\tb.ResetTimer()\n\t\t\titersPerBM := 10\n\t\t\tfor i := 0; i < b.N; i++ {\n\t\t\t\tfor j := range itersPerBM {\n\t\t\t\t\tpp := peers[(i+j)%N]\n\t\t\t\t\tps.AddAddrs(pp.ID, pp.Addr, pstore.RecentlyConnectedAddrTTL)\n\t\t\t\t}\n\t\t\t\tfor j := range itersPerBM {\n\t\t\t\t\tpp := peers[(i+j)%N]\n\t\t\t\t\tps.Addrs(pp.ID)\n\t\t\t\t}\n\t\t\t\tfor j := range itersPerBM {\n\t\t\t\t\tpp := peers[(i+j)%N]\n\t\t\t\t\tps.ClearAddrs(pp.ID)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\n\t\tb.Run(fmt.Sprintf(\"PeersWithAddrs-%d\", sz), func(b *testing.B) {\n\t\t\tps, cleanup := factory()\n\t\t\tdefer cleanup()\n\t\t\tfor _, pp := range peers {\n\t\t\t\tps.AddAddrs(pp.ID, pp.Addr, pstore.RecentlyConnectedAddrTTL)\n\t\t\t}\n\n\t\t\tb.ResetTimer()\n\t\t\tfor i := 0; i < b.N; i++ {\n\t\t\t\t_ = ps.PeersWithAddrs()\n\t\t\t}\n\t\t})\n\n\t\tb.Run(fmt.Sprintf(\"SetAddrs-%d\", sz), func(b *testing.B) {\n\t\t\tps, cleanup := factory()\n\t\t\tdefer cleanup()\n\t\t\tb.ResetTimer()\n\t\t\tfor i := 0; i < b.N; i++ {\n\t\t\t\tpp := peers[i%N]\n\t\t\t\tps.SetAddrs(pp.ID, pp.Addr, pstore.RecentlyConnectedAddrTTL)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "p2p/host/peerstore/test/keybook_suite.go",
    "content": "package test\n\nimport (\n\t\"sort\"\n\t\"testing\"\n\n\tic \"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\tpstore \"github.com/libp2p/go-libp2p/core/peerstore\"\n\tpt \"github.com/libp2p/go-libp2p/core/test\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nvar keyBookSuite = map[string]func(kb pstore.KeyBook) func(*testing.T){\n\t\"AddGetPrivKey\":         testKeybookPrivKey,\n\t\"AddGetPubKey\":          testKeyBookPubKey,\n\t\"PeersWithKeys\":         testKeyBookPeers,\n\t\"PubKeyAddedOnRetrieve\": testInlinedPubKeyAddedOnRetrieve,\n\t\"Delete\":                testKeyBookDelete,\n}\n\ntype KeyBookFactory func() (pstore.KeyBook, func())\n\nfunc TestKeyBook(t *testing.T, factory KeyBookFactory) {\n\tfor name, test := range keyBookSuite {\n\t\t// Create a new peerstore.\n\t\tkb, closeFunc := factory()\n\n\t\t// Run the test.\n\t\tt.Run(name, test(kb))\n\n\t\t// Cleanup.\n\t\tif closeFunc != nil {\n\t\t\tcloseFunc()\n\t\t}\n\t}\n}\n\nfunc testKeybookPrivKey(kb pstore.KeyBook) func(t *testing.T) {\n\treturn func(t *testing.T) {\n\t\tif peers := kb.PeersWithKeys(); len(peers) > 0 {\n\t\t\tt.Error(\"expected peers to be empty on init\")\n\t\t}\n\n\t\tpriv, _, err := pt.RandTestKeyPair(ic.RSA, 2048)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tid, err := peer.IDFromPrivateKey(priv)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\n\t\tif res := kb.PrivKey(id); res != nil {\n\t\t\tt.Error(\"retrieving private key should have failed\")\n\t\t}\n\n\t\terr = kb.AddPrivKey(id, priv)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\n\t\tif res := kb.PrivKey(id); !priv.Equals(res) {\n\t\t\tt.Error(\"retrieved private key did not match stored private key\")\n\t\t}\n\n\t\tif peers := kb.PeersWithKeys(); len(peers) != 1 || peers[0] != id {\n\t\t\tt.Error(\"list of peers did not include test peer\")\n\t\t}\n\t}\n}\n\nfunc testKeyBookPubKey(kb pstore.KeyBook) func(t *testing.T) {\n\treturn func(t *testing.T) {\n\t\tif peers := kb.PeersWithKeys(); len(peers) > 0 {\n\t\t\tt.Error(\"expected peers to be empty on init\")\n\t\t}\n\n\t\t_, pub, err := pt.RandTestKeyPair(ic.RSA, 2048)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tid, err := peer.IDFromPublicKey(pub)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif res := kb.PubKey(id); res != nil {\n\t\t\tt.Error(\"retrieving public key should have failed\")\n\t\t}\n\n\t\terr = kb.AddPubKey(id, pub)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\n\t\tif res := kb.PubKey(id); !pub.Equals(res) {\n\t\t\tt.Error(\"retrieved public key did not match stored public key\")\n\t\t}\n\n\t\tif peers := kb.PeersWithKeys(); len(peers) != 1 || peers[0] != id {\n\t\t\tt.Error(\"list of peers did not include test peer\")\n\t\t}\n\t}\n}\n\nfunc testKeyBookPeers(kb pstore.KeyBook) func(t *testing.T) {\n\treturn func(t *testing.T) {\n\t\tif peers := kb.PeersWithKeys(); len(peers) > 0 {\n\t\t\tt.Error(\"expected peers to be empty on init\")\n\t\t}\n\n\t\tvar peers peer.IDSlice\n\t\tfor range 10 {\n\t\t\t// Add a public key.\n\t\t\t_, pub, err := pt.RandTestKeyPair(ic.RSA, 2048)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tp1, err := peer.IDFromPublicKey(pub)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tkb.AddPubKey(p1, pub)\n\n\t\t\t// Add a private key.\n\t\t\tpriv, _, err := pt.RandTestKeyPair(ic.RSA, 2048)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tp2, err := peer.IDFromPrivateKey(priv)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tkb.AddPrivKey(p2, priv)\n\n\t\t\tpeers = append(peers, []peer.ID{p1, p2}...)\n\t\t}\n\n\t\tkbPeers := kb.PeersWithKeys()\n\t\tsort.Sort(kbPeers)\n\t\tsort.Sort(peers)\n\n\t\tfor i, p := range kbPeers {\n\t\t\tif p != peers[i] {\n\t\t\t\tt.Errorf(\"mismatch of peer at index %d\", i)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc testInlinedPubKeyAddedOnRetrieve(kb pstore.KeyBook) func(t *testing.T) {\n\treturn func(t *testing.T) {\n\t\tt.Skip(\"key inlining disabled for now: see libp2p/specs#111\")\n\n\t\tif peers := kb.PeersWithKeys(); len(peers) > 0 {\n\t\t\tt.Error(\"expected peers to be empty on init\")\n\t\t}\n\n\t\t// Key small enough for inlining.\n\t\t_, pub, err := ic.GenerateKeyPair(ic.Ed25519, 256)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\n\t\tid, err := peer.IDFromPublicKey(pub)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\n\t\tpubKey := kb.PubKey(id)\n\t\tif !pubKey.Equals(pub) {\n\t\t\tt.Error(\"mismatch between original public key and keybook-calculated one\")\n\t\t}\n\t}\n}\n\nfunc testKeyBookDelete(kb pstore.KeyBook) func(t *testing.T) {\n\treturn func(t *testing.T) {\n\t\t// don't use an ed25519 key here, otherwise the key book might try to derive the pubkey from the peer ID\n\t\tpriv, pub, err := ic.GenerateKeyPair(ic.RSA, 2048)\n\t\trequire.NoError(t, err)\n\t\tp, err := peer.IDFromPublicKey(pub)\n\t\trequire.NoError(t, err)\n\t\trequire.NoError(t, kb.AddPubKey(p, pub))\n\t\trequire.NoError(t, kb.AddPrivKey(p, priv))\n\t\trequire.NotNil(t, kb.PrivKey(p))\n\t\trequire.NotNil(t, kb.PubKey(p))\n\t\tkb.RemovePeer(p)\n\t\trequire.Nil(t, kb.PrivKey(p))\n\t\trequire.Nil(t, kb.PubKey(p))\n\t}\n}\n\nvar keybookBenchmarkSuite = map[string]func(kb pstore.KeyBook) func(*testing.B){\n\t\"PubKey\":        benchmarkPubKey,\n\t\"AddPubKey\":     benchmarkAddPubKey,\n\t\"PrivKey\":       benchmarkPrivKey,\n\t\"AddPrivKey\":    benchmarkAddPrivKey,\n\t\"PeersWithKeys\": benchmarkPeersWithKeys,\n}\n\nfunc BenchmarkKeyBook(b *testing.B, factory KeyBookFactory) {\n\tordernames := make([]string, 0, len(keybookBenchmarkSuite))\n\tfor name := range keybookBenchmarkSuite {\n\t\tordernames = append(ordernames, name)\n\t}\n\tsort.Strings(ordernames)\n\tfor _, name := range ordernames {\n\t\tbench := keybookBenchmarkSuite[name]\n\t\tkb, closeFunc := factory()\n\n\t\tb.Run(name, bench(kb))\n\n\t\tif closeFunc != nil {\n\t\t\tcloseFunc()\n\t\t}\n\t}\n}\n\nfunc benchmarkPubKey(kb pstore.KeyBook) func(*testing.B) {\n\treturn func(b *testing.B) {\n\t\t_, pub, err := pt.RandTestKeyPair(ic.RSA, 2048)\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\n\t\tid, err := peer.IDFromPublicKey(pub)\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\n\t\terr = kb.AddPubKey(id, pub)\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\n\t\tb.ResetTimer()\n\t\tfor i := 0; i < b.N; i++ {\n\t\t\tkb.PubKey(id)\n\t\t}\n\t}\n}\n\nfunc benchmarkAddPubKey(kb pstore.KeyBook) func(*testing.B) {\n\treturn func(b *testing.B) {\n\t\t_, pub, err := pt.RandTestKeyPair(ic.RSA, 2048)\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\n\t\tid, err := peer.IDFromPublicKey(pub)\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\n\t\tb.ResetTimer()\n\t\tfor i := 0; i < b.N; i++ {\n\t\t\tkb.AddPubKey(id, pub)\n\t\t}\n\t}\n}\n\nfunc benchmarkPrivKey(kb pstore.KeyBook) func(*testing.B) {\n\treturn func(b *testing.B) {\n\t\tpriv, _, err := pt.RandTestKeyPair(ic.RSA, 2048)\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\n\t\tid, err := peer.IDFromPrivateKey(priv)\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\n\t\terr = kb.AddPrivKey(id, priv)\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\n\t\tb.ResetTimer()\n\t\tfor i := 0; i < b.N; i++ {\n\t\t\tkb.PrivKey(id)\n\t\t}\n\t}\n}\n\nfunc benchmarkAddPrivKey(kb pstore.KeyBook) func(*testing.B) {\n\treturn func(b *testing.B) {\n\t\tpriv, _, err := pt.RandTestKeyPair(ic.RSA, 2048)\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\n\t\tid, err := peer.IDFromPrivateKey(priv)\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\n\t\tb.ResetTimer()\n\t\tfor i := 0; i < b.N; i++ {\n\t\t\tkb.AddPrivKey(id, priv)\n\t\t}\n\t}\n}\n\nfunc benchmarkPeersWithKeys(kb pstore.KeyBook) func(*testing.B) {\n\treturn func(b *testing.B) {\n\t\tfor range 10 {\n\t\t\tpriv, pub, err := pt.RandTestKeyPair(ic.RSA, 2048)\n\t\t\tif err != nil {\n\t\t\t\tb.Fatal(err)\n\t\t\t}\n\n\t\t\tid, err := peer.IDFromPublicKey(pub)\n\t\t\tif err != nil {\n\t\t\t\tb.Fatal(err)\n\t\t\t}\n\n\t\t\terr = kb.AddPubKey(id, pub)\n\t\t\tif err != nil {\n\t\t\t\tb.Fatal(err)\n\t\t\t}\n\t\t\terr = kb.AddPrivKey(id, priv)\n\t\t\tif err != nil {\n\t\t\t\tb.Fatal(err)\n\t\t\t}\n\t\t}\n\t\tb.ResetTimer()\n\t\tfor i := 0; i < b.N; i++ {\n\t\t\tkb.PeersWithKeys()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "p2p/host/peerstore/test/peerstore_suite.go",
    "content": "package test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"reflect\"\n\t\"slices\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\tpstore \"github.com/libp2p/go-libp2p/core/peerstore\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nvar peerstoreSuite = map[string]func(pstore.Peerstore) func(*testing.T){\n\t\"AddrStream\":               testAddrStream,\n\t\"GetStreamBeforePeerAdded\": testGetStreamBeforePeerAdded,\n\t\"AddStreamDuplicates\":      testAddrStreamDuplicates,\n\t\"PeerstoreProtoStore\":      testPeerstoreProtoStore,\n\t\"BasicPeerstore\":           testBasicPeerstore,\n\t\"Metadata\":                 testMetadata,\n\t\"CertifiedAddrBook\":        testCertifiedAddrBook,\n}\n\ntype PeerstoreFactory func() (pstore.Peerstore, func())\n\nfunc TestPeerstore(t *testing.T, factory PeerstoreFactory) {\n\tfor name, test := range peerstoreSuite {\n\t\t// Create a new peerstore.\n\t\tps, closeFunc := factory()\n\n\t\t// Run the test.\n\t\tt.Run(name, test(ps))\n\n\t\t// Cleanup.\n\t\tif closeFunc != nil {\n\t\t\tcloseFunc()\n\t\t}\n\t}\n}\n\nfunc sortProtos(protos []protocol.ID) {\n\tslices.Sort(protos)\n}\n\nfunc testAddrStream(ps pstore.Peerstore) func(t *testing.T) {\n\treturn func(t *testing.T) {\n\t\taddrs, pid := getAddrs(t, 100), peer.ID(\"testpeer\")\n\t\tps.AddAddrs(pid, addrs[:10], time.Hour)\n\n\t\tctx, cancel := context.WithCancel(context.Background())\n\t\taddrch := ps.AddrStream(ctx, pid)\n\n\t\t// while that subscription is active, publish ten more addrs\n\t\t// this tests that it doesnt hang\n\t\tfor i := 10; i < 20; i++ {\n\t\t\tps.AddAddr(pid, addrs[i], time.Hour)\n\t\t}\n\n\t\t// now receive them (without hanging)\n\t\ttimeout := time.After(time.Second * 10)\n\t\tfor range 20 {\n\t\t\tselect {\n\t\t\tcase <-addrch:\n\t\t\tcase <-timeout:\n\t\t\t\tt.Fatal(\"timed out\")\n\t\t\t}\n\t\t}\n\n\t\t// start a second stream\n\t\tctx2, cancel2 := context.WithCancel(context.Background())\n\t\taddrch2 := ps.AddrStream(ctx2, pid)\n\n\t\tdone := make(chan struct{})\n\t\tgo func() {\n\t\t\tdefer close(done)\n\t\t\t// now send the rest of the addresses\n\t\t\tfor _, a := range addrs[20:80] {\n\t\t\t\tps.AddAddr(pid, a, time.Hour)\n\t\t\t}\n\t\t}()\n\n\t\t// receive some concurrently with the goroutine\n\t\ttimeout = time.After(time.Second * 10)\n\t\tfor range 40 {\n\t\t\tselect {\n\t\t\tcase <-addrch:\n\t\t\tcase <-timeout:\n\t\t\t}\n\t\t}\n\n\t\t<-done\n\n\t\t// receive some more after waiting for that goroutine to complete\n\t\ttimeout = time.After(time.Second * 10)\n\t\tfor range 20 {\n\t\t\tselect {\n\t\t\tcase <-addrch:\n\t\t\tcase <-timeout:\n\t\t\t}\n\t\t}\n\n\t\t// now cancel it\n\t\tcancel()\n\n\t\t// now check the *second* subscription. We should see 80 addresses.\n\t\tfor range 80 {\n\t\t\t<-addrch2\n\t\t}\n\n\t\tcancel2()\n\n\t\t// and add a few more addresses it doesnt hang afterwards\n\t\tfor _, a := range addrs[80:] {\n\t\t\tps.AddAddr(pid, a, time.Hour)\n\t\t}\n\t}\n}\n\nfunc testGetStreamBeforePeerAdded(ps pstore.Peerstore) func(t *testing.T) {\n\treturn func(t *testing.T) {\n\t\taddrs, pid := getAddrs(t, 10), peer.ID(\"testpeer\")\n\n\t\tctx, cancel := context.WithCancel(context.Background())\n\t\tdefer cancel()\n\n\t\tach := ps.AddrStream(ctx, pid)\n\t\tfor i := range 10 {\n\t\t\tps.AddAddr(pid, addrs[i], time.Hour)\n\t\t}\n\n\t\treceived := make(map[string]bool)\n\t\tvar count int\n\n\t\tfor range 10 {\n\t\t\ta, ok := <-ach\n\t\t\tif !ok {\n\t\t\t\tt.Fatal(\"channel shouldnt be closed yet\")\n\t\t\t}\n\t\t\tif a == nil {\n\t\t\t\tt.Fatal(\"got a nil address, that's weird\")\n\t\t\t}\n\t\t\tcount++\n\t\t\tif received[a.String()] {\n\t\t\t\tt.Fatal(\"received duplicate address\")\n\t\t\t}\n\t\t\treceived[a.String()] = true\n\t\t}\n\n\t\tselect {\n\t\tcase <-ach:\n\t\t\tt.Fatal(\"shouldnt have received any more addresses\")\n\t\tdefault:\n\t\t}\n\n\t\tif count != 10 {\n\t\t\tt.Fatal(\"should have received exactly ten addresses, got \", count)\n\t\t}\n\n\t\tfor _, a := range addrs {\n\t\t\tif !received[a.String()] {\n\t\t\t\tt.Log(received)\n\t\t\t\tt.Fatalf(\"expected to receive address %s but didnt\", a)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc testAddrStreamDuplicates(ps pstore.Peerstore) func(t *testing.T) {\n\treturn func(t *testing.T) {\n\t\taddrs, pid := getAddrs(t, 10), peer.ID(\"testpeer\")\n\n\t\tctx, cancel := context.WithCancel(context.Background())\n\t\tdefer cancel()\n\n\t\tach := ps.AddrStream(ctx, pid)\n\t\tgo func() {\n\t\t\tfor i := range 10 {\n\t\t\t\tps.AddAddr(pid, addrs[i], time.Hour)\n\t\t\t\tps.AddAddr(pid, addrs[rand.Intn(10)], time.Hour)\n\t\t\t}\n\n\t\t\t// make sure that all addresses get processed before context is cancelled\n\t\t\ttime.Sleep(time.Millisecond * 50)\n\t\t\tcancel()\n\t\t}()\n\n\t\treceived := make(map[string]bool)\n\t\tvar count int\n\t\tfor a := range ach {\n\t\t\tif a == nil {\n\t\t\t\tt.Fatal(\"got a nil address, that's weird\")\n\t\t\t}\n\t\t\tcount++\n\t\t\tif received[a.String()] {\n\t\t\t\tt.Fatal(\"received duplicate address\")\n\t\t\t}\n\t\t\treceived[a.String()] = true\n\t\t}\n\n\t\tif count != 10 {\n\t\t\tt.Fatal(\"should have received exactly ten addresses\")\n\t\t}\n\t}\n}\n\nfunc testPeerstoreProtoStore(ps pstore.Peerstore) func(t *testing.T) {\n\treturn func(t *testing.T) {\n\t\tt.Run(\"adding and removing protocols\", func(t *testing.T) {\n\t\t\tp1 := peer.ID(\"TESTPEER\")\n\t\t\tprotos := []protocol.ID{\"a\", \"b\", \"c\", \"d\"}\n\n\t\t\trequire.NoError(t, ps.AddProtocols(p1, protos...))\n\t\t\tout, err := ps.GetProtocols(p1)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, out, len(protos), \"got wrong number of protocols back\")\n\n\t\t\tsortProtos(out)\n\t\t\tfor i, p := range protos {\n\t\t\t\tif out[i] != p {\n\t\t\t\t\tt.Fatal(\"got wrong protocol\")\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tsupported, err := ps.SupportsProtocols(p1, \"q\", \"w\", \"a\", \"y\", \"b\")\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, supported, 2, \"only expected 2 supported\")\n\n\t\t\tif supported[0] != \"a\" || supported[1] != \"b\" {\n\t\t\t\tt.Fatal(\"got wrong supported array: \", supported)\n\t\t\t}\n\n\t\t\tb, err := ps.FirstSupportedProtocol(p1, \"q\", \"w\", \"a\", \"y\", \"b\")\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, protocol.ID(\"a\"), b)\n\n\t\t\tb, err = ps.FirstSupportedProtocol(p1, \"q\", \"x\", \"z\")\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Empty(t, b)\n\n\t\t\tb, err = ps.FirstSupportedProtocol(p1, \"a\")\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, protocol.ID(\"a\"), b)\n\n\t\t\tprotos = []protocol.ID{\"other\", \"yet another\", \"one more\"}\n\t\t\trequire.NoError(t, ps.SetProtocols(p1, protos...))\n\n\t\t\tsupported, err = ps.SupportsProtocols(p1, \"q\", \"w\", \"a\", \"y\", \"b\")\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Empty(t, supported, \"none of those protocols should have been supported\")\n\n\t\t\tsupported, err = ps.GetProtocols(p1)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tsortProtos(supported)\n\t\t\tsortProtos(protos)\n\t\t\tif !reflect.DeepEqual(supported, protos) {\n\t\t\t\tt.Fatalf(\"expected previously set protos; expected: %v, have: %v\", protos, supported)\n\t\t\t}\n\n\t\t\trequire.NoError(t, ps.RemoveProtocols(p1, protos[:2]...))\n\n\t\t\tsupported, err = ps.GetProtocols(p1)\n\t\t\trequire.NoError(t, err)\n\t\t\tif !reflect.DeepEqual(supported, protos[2:]) {\n\t\t\t\tt.Fatal(\"expected only one protocol to remain\")\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"removing peer\", func(t *testing.T) {\n\t\t\tp := peer.ID(\"foobar\")\n\t\t\tprotos := []protocol.ID{\"a\", \"b\"}\n\n\t\t\trequire.NoError(t, ps.SetProtocols(p, protos...))\n\t\t\tout, err := ps.GetProtocols(p)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, out, 2)\n\t\t\tps.RemovePeer(p)\n\t\t\tout, err = ps.GetProtocols(p)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Empty(t, out)\n\t\t})\n\t}\n}\n\nfunc testBasicPeerstore(ps pstore.Peerstore) func(t *testing.T) {\n\treturn func(t *testing.T) {\n\t\tvar pids []peer.ID\n\t\taddrs := getAddrs(t, 10)\n\n\t\tfor _, a := range addrs {\n\t\t\tpriv, _, err := crypto.GenerateKeyPair(crypto.RSA, 2048)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tp, err := peer.IDFromPrivateKey(priv)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tpids = append(pids, p)\n\t\t\tps.AddAddr(p, a, pstore.PermanentAddrTTL)\n\t\t}\n\n\t\tpeers := ps.Peers()\n\t\tif len(peers) != 10 {\n\t\t\tt.Fatal(\"expected ten peers, got\", len(peers))\n\t\t}\n\n\t\tpinfo := ps.PeerInfo(pids[0])\n\t\tif !pinfo.Addrs[0].Equal(addrs[0]) {\n\t\t\tt.Fatal(\"stored wrong address\")\n\t\t}\n\t}\n}\n\nfunc testMetadata(ps pstore.Peerstore) func(t *testing.T) {\n\treturn func(t *testing.T) {\n\t\tt.Run(\"putting and getting\", func(t *testing.T) {\n\t\t\tpids := make([]peer.ID, 3)\n\t\t\tfor i := range pids {\n\t\t\t\tpriv, _, err := crypto.GenerateKeyPair(crypto.RSA, 2048)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tp, err := peer.IDFromPrivateKey(priv)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tpids[i] = p\n\t\t\t}\n\t\t\tfor _, p := range pids {\n\t\t\t\trequire.NoError(t, ps.Put(p, \"AgentVersion\", \"string\"), \"failed to put AgentVersion\")\n\t\t\t\trequire.NoError(t, ps.Put(p, \"bar\", 1), \"failed to put bar\")\n\t\t\t}\n\t\t\tfor _, p := range pids {\n\t\t\t\tv, err := ps.Get(p, \"AgentVersion\")\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, \"string\", v)\n\n\t\t\t\tv, err = ps.Get(p, \"bar\")\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, 1, v)\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"removing a peer\", func(t *testing.T) {\n\t\t\tp := peer.ID(\"foo\")\n\t\t\totherP := peer.ID(\"foobar\")\n\t\t\trequire.NoError(t, ps.Put(otherP, \"AgentVersion\", \"v1\"))\n\t\t\trequire.NoError(t, ps.Put(p, \"AgentVersion\", \"v1\"))\n\t\t\trequire.NoError(t, ps.Put(p, \"bar\", 1))\n\t\t\tps.RemovePeer(p)\n\t\t\t_, err := ps.Get(p, \"AgentVersion\")\n\t\t\trequire.ErrorIs(t, err, pstore.ErrNotFound)\n\t\t\t_, err = ps.Get(p, \"bar\")\n\t\t\trequire.ErrorIs(t, err, pstore.ErrNotFound)\n\t\t\t// make sure that entries for otherP were not deleted\n\t\t\tval, err := ps.Get(otherP, \"AgentVersion\")\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, \"v1\", val)\n\t\t})\n\t}\n}\n\nfunc testCertifiedAddrBook(ps pstore.Peerstore) func(*testing.T) {\n\treturn func(t *testing.T) {\n\t\t_, ok := ps.(pstore.CertifiedAddrBook)\n\t\tif !ok {\n\t\t\tt.Error(\"expected peerstore to implement CertifiedAddrBook interface\")\n\t\t}\n\t}\n}\n\nfunc getAddrs(t *testing.T, n int) []ma.Multiaddr {\n\taddrs := make([]ma.Multiaddr, 0, n)\n\tfor i := range n {\n\t\ta, err := ma.NewMultiaddr(fmt.Sprintf(\"/ip4/127.0.0.1/tcp/%d\", i))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\taddrs = append(addrs, a)\n\t}\n\treturn addrs\n}\n\nfunc TestPeerstoreProtoStoreLimits(t *testing.T, ps pstore.Peerstore, limit int) {\n\tp := peer.ID(\"foobar\")\n\tprotocols := make([]protocol.ID, limit)\n\tfor i := range limit {\n\t\tprotocols[i] = protocol.ID(fmt.Sprintf(\"protocol %d\", i))\n\t}\n\n\tt.Run(\"setting protocols\", func(t *testing.T) {\n\t\trequire.NoError(t, ps.SetProtocols(p, protocols...))\n\t\trequire.EqualError(t, ps.SetProtocols(p, append(protocols, \"proto\")...), \"too many protocols\")\n\t})\n\tt.Run(\"adding protocols\", func(t *testing.T) {\n\t\tp1 := protocols[:limit/2]\n\t\tp2 := protocols[limit/2:]\n\t\trequire.NoError(t, ps.SetProtocols(p, p1...))\n\t\trequire.NoError(t, ps.AddProtocols(p, p2...))\n\t\trequire.EqualError(t, ps.AddProtocols(p, \"proto\"), \"too many protocols\")\n\t})\n}\n"
  },
  {
    "path": "p2p/host/peerstore/test/utils.go",
    "content": "package test\n\nimport (\n\t\"fmt\"\n\t\"slices\"\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\tpt \"github.com/libp2p/go-libp2p/core/test\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\nfunc Multiaddr(m string) ma.Multiaddr {\n\tmaddr, err := ma.NewMultiaddr(m)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn maddr\n}\n\ntype peerpair struct {\n\tID   peer.ID\n\tAddr []ma.Multiaddr\n}\n\nfunc RandomPeer(b *testing.B, addrCount int) *peerpair {\n\tvar (\n\t\tpid   peer.ID\n\t\terr   error\n\t\taddrs = make([]ma.Multiaddr, addrCount)\n\t\taFmt  = \"/ip4/127.0.0.1/tcp/%d/ipfs/%s\"\n\t)\n\n\tb.Helper()\n\tif pid, err = pt.RandPeerID(); err != nil {\n\t\tb.Fatal(err)\n\t}\n\n\tfor i := range addrCount {\n\t\tif addrs[i], err = ma.NewMultiaddr(fmt.Sprintf(aFmt, i, pid)); err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t}\n\treturn &peerpair{pid, addrs}\n}\n\nfunc getPeerPairs(b *testing.B, n int, addrsPerPeer int) []*peerpair {\n\tpps := make([]*peerpair, n)\n\tfor i := range n {\n\t\tpps[i] = RandomPeer(b, addrsPerPeer)\n\t}\n\treturn pps\n}\n\nfunc GenerateAddrs(count int) []ma.Multiaddr {\n\tvar addrs = make([]ma.Multiaddr, count)\n\tfor i := range count {\n\t\taddrs[i] = Multiaddr(fmt.Sprintf(\"/ip4/1.1.1.%d/tcp/1111\", i))\n\t}\n\treturn addrs\n}\n\nfunc GeneratePeerIDs(count int) []peer.ID {\n\tvar ids = make([]peer.ID, count)\n\tfor i := range count {\n\t\tids[i], _ = pt.RandPeerID()\n\t}\n\treturn ids\n}\n\nfunc AssertAddressesEqual(t *testing.T, exp, act []ma.Multiaddr) {\n\tt.Helper()\n\tif len(exp) != len(act) {\n\t\tt.Fatalf(\"lengths not the same. expected %d, got %d\\n\", len(exp), len(act))\n\t}\n\n\tfor _, a := range exp {\n\t\tfound := slices.ContainsFunc(act, a.Equal)\n\n\t\tif !found {\n\t\t\tt.Fatalf(\"expected address %s not found\", a)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "p2p/host/pstoremanager/mock_peerstore_test.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github.com/libp2p/go-libp2p/core/peerstore (interfaces: Peerstore)\n//\n// Generated by this command:\n//\n//\tmockgen -package pstoremanager_test -destination mock_peerstore_test.go github.com/libp2p/go-libp2p/core/peerstore Peerstore\n//\n\n// Package pstoremanager_test is a generated GoMock package.\npackage pstoremanager_test\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\ttime \"time\"\n\n\tcrypto \"github.com/libp2p/go-libp2p/core/crypto\"\n\tpeer \"github.com/libp2p/go-libp2p/core/peer\"\n\tprotocol \"github.com/libp2p/go-libp2p/core/protocol\"\n\tmultiaddr \"github.com/multiformats/go-multiaddr\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockPeerstore is a mock of Peerstore interface.\ntype MockPeerstore struct {\n\tctrl     *gomock.Controller\n\trecorder *MockPeerstoreMockRecorder\n\tisgomock struct{}\n}\n\n// MockPeerstoreMockRecorder is the mock recorder for MockPeerstore.\ntype MockPeerstoreMockRecorder struct {\n\tmock *MockPeerstore\n}\n\n// NewMockPeerstore creates a new mock instance.\nfunc NewMockPeerstore(ctrl *gomock.Controller) *MockPeerstore {\n\tmock := &MockPeerstore{ctrl: ctrl}\n\tmock.recorder = &MockPeerstoreMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockPeerstore) EXPECT() *MockPeerstoreMockRecorder {\n\treturn m.recorder\n}\n\n// AddAddr mocks base method.\nfunc (m *MockPeerstore) AddAddr(p peer.ID, addr multiaddr.Multiaddr, ttl time.Duration) {\n\tm.ctrl.T.Helper()\n\tm.ctrl.Call(m, \"AddAddr\", p, addr, ttl)\n}\n\n// AddAddr indicates an expected call of AddAddr.\nfunc (mr *MockPeerstoreMockRecorder) AddAddr(p, addr, ttl any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"AddAddr\", reflect.TypeOf((*MockPeerstore)(nil).AddAddr), p, addr, ttl)\n}\n\n// AddAddrs mocks base method.\nfunc (m *MockPeerstore) AddAddrs(p peer.ID, addrs []multiaddr.Multiaddr, ttl time.Duration) {\n\tm.ctrl.T.Helper()\n\tm.ctrl.Call(m, \"AddAddrs\", p, addrs, ttl)\n}\n\n// AddAddrs indicates an expected call of AddAddrs.\nfunc (mr *MockPeerstoreMockRecorder) AddAddrs(p, addrs, ttl any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"AddAddrs\", reflect.TypeOf((*MockPeerstore)(nil).AddAddrs), p, addrs, ttl)\n}\n\n// AddPrivKey mocks base method.\nfunc (m *MockPeerstore) AddPrivKey(arg0 peer.ID, arg1 crypto.PrivKey) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"AddPrivKey\", arg0, arg1)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// AddPrivKey indicates an expected call of AddPrivKey.\nfunc (mr *MockPeerstoreMockRecorder) AddPrivKey(arg0, arg1 any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"AddPrivKey\", reflect.TypeOf((*MockPeerstore)(nil).AddPrivKey), arg0, arg1)\n}\n\n// AddProtocols mocks base method.\nfunc (m *MockPeerstore) AddProtocols(arg0 peer.ID, arg1 ...protocol.ID) error {\n\tm.ctrl.T.Helper()\n\tvarargs := []any{arg0}\n\tfor _, a := range arg1 {\n\t\tvarargs = append(varargs, a)\n\t}\n\tret := m.ctrl.Call(m, \"AddProtocols\", varargs...)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// AddProtocols indicates an expected call of AddProtocols.\nfunc (mr *MockPeerstoreMockRecorder) AddProtocols(arg0 any, arg1 ...any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\tvarargs := append([]any{arg0}, arg1...)\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"AddProtocols\", reflect.TypeOf((*MockPeerstore)(nil).AddProtocols), varargs...)\n}\n\n// AddPubKey mocks base method.\nfunc (m *MockPeerstore) AddPubKey(arg0 peer.ID, arg1 crypto.PubKey) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"AddPubKey\", arg0, arg1)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// AddPubKey indicates an expected call of AddPubKey.\nfunc (mr *MockPeerstoreMockRecorder) AddPubKey(arg0, arg1 any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"AddPubKey\", reflect.TypeOf((*MockPeerstore)(nil).AddPubKey), arg0, arg1)\n}\n\n// AddrStream mocks base method.\nfunc (m *MockPeerstore) AddrStream(arg0 context.Context, arg1 peer.ID) <-chan multiaddr.Multiaddr {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"AddrStream\", arg0, arg1)\n\tret0, _ := ret[0].(<-chan multiaddr.Multiaddr)\n\treturn ret0\n}\n\n// AddrStream indicates an expected call of AddrStream.\nfunc (mr *MockPeerstoreMockRecorder) AddrStream(arg0, arg1 any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"AddrStream\", reflect.TypeOf((*MockPeerstore)(nil).AddrStream), arg0, arg1)\n}\n\n// Addrs mocks base method.\nfunc (m *MockPeerstore) Addrs(p peer.ID) []multiaddr.Multiaddr {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Addrs\", p)\n\tret0, _ := ret[0].([]multiaddr.Multiaddr)\n\treturn ret0\n}\n\n// Addrs indicates an expected call of Addrs.\nfunc (mr *MockPeerstoreMockRecorder) Addrs(p any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Addrs\", reflect.TypeOf((*MockPeerstore)(nil).Addrs), p)\n}\n\n// ClearAddrs mocks base method.\nfunc (m *MockPeerstore) ClearAddrs(p peer.ID) {\n\tm.ctrl.T.Helper()\n\tm.ctrl.Call(m, \"ClearAddrs\", p)\n}\n\n// ClearAddrs indicates an expected call of ClearAddrs.\nfunc (mr *MockPeerstoreMockRecorder) ClearAddrs(p any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ClearAddrs\", reflect.TypeOf((*MockPeerstore)(nil).ClearAddrs), p)\n}\n\n// Close mocks base method.\nfunc (m *MockPeerstore) Close() error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Close\")\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Close indicates an expected call of Close.\nfunc (mr *MockPeerstoreMockRecorder) Close() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Close\", reflect.TypeOf((*MockPeerstore)(nil).Close))\n}\n\n// FirstSupportedProtocol mocks base method.\nfunc (m *MockPeerstore) FirstSupportedProtocol(arg0 peer.ID, arg1 ...protocol.ID) (protocol.ID, error) {\n\tm.ctrl.T.Helper()\n\tvarargs := []any{arg0}\n\tfor _, a := range arg1 {\n\t\tvarargs = append(varargs, a)\n\t}\n\tret := m.ctrl.Call(m, \"FirstSupportedProtocol\", varargs...)\n\tret0, _ := ret[0].(protocol.ID)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// FirstSupportedProtocol indicates an expected call of FirstSupportedProtocol.\nfunc (mr *MockPeerstoreMockRecorder) FirstSupportedProtocol(arg0 any, arg1 ...any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\tvarargs := append([]any{arg0}, arg1...)\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"FirstSupportedProtocol\", reflect.TypeOf((*MockPeerstore)(nil).FirstSupportedProtocol), varargs...)\n}\n\n// Get mocks base method.\nfunc (m *MockPeerstore) Get(p peer.ID, key string) (any, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Get\", p, key)\n\tret0, _ := ret[0].(any)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Get indicates an expected call of Get.\nfunc (mr *MockPeerstoreMockRecorder) Get(p, key any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Get\", reflect.TypeOf((*MockPeerstore)(nil).Get), p, key)\n}\n\n// GetProtocols mocks base method.\nfunc (m *MockPeerstore) GetProtocols(arg0 peer.ID) ([]protocol.ID, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetProtocols\", arg0)\n\tret0, _ := ret[0].([]protocol.ID)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetProtocols indicates an expected call of GetProtocols.\nfunc (mr *MockPeerstoreMockRecorder) GetProtocols(arg0 any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetProtocols\", reflect.TypeOf((*MockPeerstore)(nil).GetProtocols), arg0)\n}\n\n// LatencyEWMA mocks base method.\nfunc (m *MockPeerstore) LatencyEWMA(arg0 peer.ID) time.Duration {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"LatencyEWMA\", arg0)\n\tret0, _ := ret[0].(time.Duration)\n\treturn ret0\n}\n\n// LatencyEWMA indicates an expected call of LatencyEWMA.\nfunc (mr *MockPeerstoreMockRecorder) LatencyEWMA(arg0 any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"LatencyEWMA\", reflect.TypeOf((*MockPeerstore)(nil).LatencyEWMA), arg0)\n}\n\n// PeerInfo mocks base method.\nfunc (m *MockPeerstore) PeerInfo(arg0 peer.ID) peer.AddrInfo {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"PeerInfo\", arg0)\n\tret0, _ := ret[0].(peer.AddrInfo)\n\treturn ret0\n}\n\n// PeerInfo indicates an expected call of PeerInfo.\nfunc (mr *MockPeerstoreMockRecorder) PeerInfo(arg0 any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"PeerInfo\", reflect.TypeOf((*MockPeerstore)(nil).PeerInfo), arg0)\n}\n\n// Peers mocks base method.\nfunc (m *MockPeerstore) Peers() peer.IDSlice {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Peers\")\n\tret0, _ := ret[0].(peer.IDSlice)\n\treturn ret0\n}\n\n// Peers indicates an expected call of Peers.\nfunc (mr *MockPeerstoreMockRecorder) Peers() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Peers\", reflect.TypeOf((*MockPeerstore)(nil).Peers))\n}\n\n// PeersWithAddrs mocks base method.\nfunc (m *MockPeerstore) PeersWithAddrs() peer.IDSlice {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"PeersWithAddrs\")\n\tret0, _ := ret[0].(peer.IDSlice)\n\treturn ret0\n}\n\n// PeersWithAddrs indicates an expected call of PeersWithAddrs.\nfunc (mr *MockPeerstoreMockRecorder) PeersWithAddrs() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"PeersWithAddrs\", reflect.TypeOf((*MockPeerstore)(nil).PeersWithAddrs))\n}\n\n// PeersWithKeys mocks base method.\nfunc (m *MockPeerstore) PeersWithKeys() peer.IDSlice {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"PeersWithKeys\")\n\tret0, _ := ret[0].(peer.IDSlice)\n\treturn ret0\n}\n\n// PeersWithKeys indicates an expected call of PeersWithKeys.\nfunc (mr *MockPeerstoreMockRecorder) PeersWithKeys() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"PeersWithKeys\", reflect.TypeOf((*MockPeerstore)(nil).PeersWithKeys))\n}\n\n// PrivKey mocks base method.\nfunc (m *MockPeerstore) PrivKey(arg0 peer.ID) crypto.PrivKey {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"PrivKey\", arg0)\n\tret0, _ := ret[0].(crypto.PrivKey)\n\treturn ret0\n}\n\n// PrivKey indicates an expected call of PrivKey.\nfunc (mr *MockPeerstoreMockRecorder) PrivKey(arg0 any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"PrivKey\", reflect.TypeOf((*MockPeerstore)(nil).PrivKey), arg0)\n}\n\n// PubKey mocks base method.\nfunc (m *MockPeerstore) PubKey(arg0 peer.ID) crypto.PubKey {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"PubKey\", arg0)\n\tret0, _ := ret[0].(crypto.PubKey)\n\treturn ret0\n}\n\n// PubKey indicates an expected call of PubKey.\nfunc (mr *MockPeerstoreMockRecorder) PubKey(arg0 any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"PubKey\", reflect.TypeOf((*MockPeerstore)(nil).PubKey), arg0)\n}\n\n// Put mocks base method.\nfunc (m *MockPeerstore) Put(p peer.ID, key string, val any) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Put\", p, key, val)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Put indicates an expected call of Put.\nfunc (mr *MockPeerstoreMockRecorder) Put(p, key, val any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Put\", reflect.TypeOf((*MockPeerstore)(nil).Put), p, key, val)\n}\n\n// RecordLatency mocks base method.\nfunc (m *MockPeerstore) RecordLatency(arg0 peer.ID, arg1 time.Duration) {\n\tm.ctrl.T.Helper()\n\tm.ctrl.Call(m, \"RecordLatency\", arg0, arg1)\n}\n\n// RecordLatency indicates an expected call of RecordLatency.\nfunc (mr *MockPeerstoreMockRecorder) RecordLatency(arg0, arg1 any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RecordLatency\", reflect.TypeOf((*MockPeerstore)(nil).RecordLatency), arg0, arg1)\n}\n\n// RemovePeer mocks base method.\nfunc (m *MockPeerstore) RemovePeer(arg0 peer.ID) {\n\tm.ctrl.T.Helper()\n\tm.ctrl.Call(m, \"RemovePeer\", arg0)\n}\n\n// RemovePeer indicates an expected call of RemovePeer.\nfunc (mr *MockPeerstoreMockRecorder) RemovePeer(arg0 any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RemovePeer\", reflect.TypeOf((*MockPeerstore)(nil).RemovePeer), arg0)\n}\n\n// RemoveProtocols mocks base method.\nfunc (m *MockPeerstore) RemoveProtocols(arg0 peer.ID, arg1 ...protocol.ID) error {\n\tm.ctrl.T.Helper()\n\tvarargs := []any{arg0}\n\tfor _, a := range arg1 {\n\t\tvarargs = append(varargs, a)\n\t}\n\tret := m.ctrl.Call(m, \"RemoveProtocols\", varargs...)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// RemoveProtocols indicates an expected call of RemoveProtocols.\nfunc (mr *MockPeerstoreMockRecorder) RemoveProtocols(arg0 any, arg1 ...any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\tvarargs := append([]any{arg0}, arg1...)\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RemoveProtocols\", reflect.TypeOf((*MockPeerstore)(nil).RemoveProtocols), varargs...)\n}\n\n// SetAddr mocks base method.\nfunc (m *MockPeerstore) SetAddr(p peer.ID, addr multiaddr.Multiaddr, ttl time.Duration) {\n\tm.ctrl.T.Helper()\n\tm.ctrl.Call(m, \"SetAddr\", p, addr, ttl)\n}\n\n// SetAddr indicates an expected call of SetAddr.\nfunc (mr *MockPeerstoreMockRecorder) SetAddr(p, addr, ttl any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SetAddr\", reflect.TypeOf((*MockPeerstore)(nil).SetAddr), p, addr, ttl)\n}\n\n// SetAddrs mocks base method.\nfunc (m *MockPeerstore) SetAddrs(p peer.ID, addrs []multiaddr.Multiaddr, ttl time.Duration) {\n\tm.ctrl.T.Helper()\n\tm.ctrl.Call(m, \"SetAddrs\", p, addrs, ttl)\n}\n\n// SetAddrs indicates an expected call of SetAddrs.\nfunc (mr *MockPeerstoreMockRecorder) SetAddrs(p, addrs, ttl any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SetAddrs\", reflect.TypeOf((*MockPeerstore)(nil).SetAddrs), p, addrs, ttl)\n}\n\n// SetProtocols mocks base method.\nfunc (m *MockPeerstore) SetProtocols(arg0 peer.ID, arg1 ...protocol.ID) error {\n\tm.ctrl.T.Helper()\n\tvarargs := []any{arg0}\n\tfor _, a := range arg1 {\n\t\tvarargs = append(varargs, a)\n\t}\n\tret := m.ctrl.Call(m, \"SetProtocols\", varargs...)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// SetProtocols indicates an expected call of SetProtocols.\nfunc (mr *MockPeerstoreMockRecorder) SetProtocols(arg0 any, arg1 ...any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\tvarargs := append([]any{arg0}, arg1...)\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SetProtocols\", reflect.TypeOf((*MockPeerstore)(nil).SetProtocols), varargs...)\n}\n\n// SupportsProtocols mocks base method.\nfunc (m *MockPeerstore) SupportsProtocols(arg0 peer.ID, arg1 ...protocol.ID) ([]protocol.ID, error) {\n\tm.ctrl.T.Helper()\n\tvarargs := []any{arg0}\n\tfor _, a := range arg1 {\n\t\tvarargs = append(varargs, a)\n\t}\n\tret := m.ctrl.Call(m, \"SupportsProtocols\", varargs...)\n\tret0, _ := ret[0].([]protocol.ID)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// SupportsProtocols indicates an expected call of SupportsProtocols.\nfunc (mr *MockPeerstoreMockRecorder) SupportsProtocols(arg0 any, arg1 ...any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\tvarargs := append([]any{arg0}, arg1...)\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SupportsProtocols\", reflect.TypeOf((*MockPeerstore)(nil).SupportsProtocols), varargs...)\n}\n\n// UpdateAddrs mocks base method.\nfunc (m *MockPeerstore) UpdateAddrs(p peer.ID, oldTTL, newTTL time.Duration) {\n\tm.ctrl.T.Helper()\n\tm.ctrl.Call(m, \"UpdateAddrs\", p, oldTTL, newTTL)\n}\n\n// UpdateAddrs indicates an expected call of UpdateAddrs.\nfunc (mr *MockPeerstoreMockRecorder) UpdateAddrs(p, oldTTL, newTTL any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateAddrs\", reflect.TypeOf((*MockPeerstore)(nil).UpdateAddrs), p, oldTTL, newTTL)\n}\n"
  },
  {
    "path": "p2p/host/pstoremanager/pstoremanager.go",
    "content": "package pstoremanager\n\nimport (\n\t\"context\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/event\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/eventbus\"\n\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n)\n\nvar log = logging.Logger(\"pstoremanager\")\n\ntype Option func(*PeerstoreManager) error\n\n// WithGracePeriod sets the grace period.\n// If a peer doesn't reconnect during the grace period, its data is removed.\n// Default: 1 minute.\nfunc WithGracePeriod(p time.Duration) Option {\n\treturn func(m *PeerstoreManager) error {\n\t\tm.gracePeriod = p\n\t\treturn nil\n\t}\n}\n\n// WithCleanupInterval set the clean up interval.\n// During a clean up run peers that disconnected before the grace period are removed.\n// If unset, the interval is set to half the grace period.\nfunc WithCleanupInterval(t time.Duration) Option {\n\treturn func(m *PeerstoreManager) error {\n\t\tm.cleanupInterval = t\n\t\treturn nil\n\t}\n}\n\ntype PeerstoreManager struct {\n\tpstore   peerstore.Peerstore\n\teventBus event.Bus\n\tnetwork  network.Network\n\n\tcancel   context.CancelFunc\n\trefCount sync.WaitGroup\n\n\tgracePeriod     time.Duration\n\tcleanupInterval time.Duration\n}\n\nfunc NewPeerstoreManager(pstore peerstore.Peerstore, eventBus event.Bus, network network.Network, opts ...Option) (*PeerstoreManager, error) {\n\tm := &PeerstoreManager{\n\t\tpstore:      pstore,\n\t\tgracePeriod: time.Minute,\n\t\teventBus:    eventBus,\n\t\tnetwork:     network,\n\t}\n\tfor _, opt := range opts {\n\t\tif err := opt(m); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tif m.cleanupInterval == 0 {\n\t\tm.cleanupInterval = m.gracePeriod / 2\n\t}\n\treturn m, nil\n}\n\nfunc (m *PeerstoreManager) Start() {\n\tctx, cancel := context.WithCancel(context.Background())\n\tm.cancel = cancel\n\tsub, err := m.eventBus.Subscribe(&event.EvtPeerConnectednessChanged{}, eventbus.Name(\"pstoremanager\"))\n\tif err != nil {\n\t\tlog.Warn(\"subscription failed. Peerstore manager not activated\", \"err\", err)\n\t\treturn\n\t}\n\tm.refCount.Add(1)\n\tgo m.background(ctx, sub)\n}\n\nfunc (m *PeerstoreManager) background(ctx context.Context, sub event.Subscription) {\n\tdefer m.refCount.Done()\n\tdefer sub.Close()\n\tdisconnected := make(map[peer.ID]time.Time)\n\n\tticker := time.NewTicker(m.cleanupInterval)\n\tdefer ticker.Stop()\n\n\tdefer func() {\n\t\tfor p := range disconnected {\n\t\t\tm.pstore.RemovePeer(p)\n\t\t}\n\t}()\n\n\tfor {\n\t\tselect {\n\t\tcase e, ok := <-sub.Out():\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tev := e.(event.EvtPeerConnectednessChanged)\n\t\t\tp := ev.Peer\n\t\t\tswitch ev.Connectedness {\n\t\t\tcase network.Connected, network.Limited:\n\t\t\t\t// If we reconnect to the peer before we've cleared the information,\n\t\t\t\t// keep it. This is an optimization to keep the disconnected map\n\t\t\t\t// small. We still need to check that a peer is actually\n\t\t\t\t// disconnected before removing it from the peer store.\n\t\t\t\tdelete(disconnected, p)\n\t\t\tdefault:\n\t\t\t\tif _, ok := disconnected[p]; !ok {\n\t\t\t\t\tdisconnected[p] = time.Now()\n\t\t\t\t}\n\t\t\t}\n\t\tcase <-ticker.C:\n\t\t\tnow := time.Now()\n\t\t\tfor p, disconnectTime := range disconnected {\n\t\t\t\tif disconnectTime.Add(m.gracePeriod).Before(now) {\n\t\t\t\t\t// Check that the peer is actually not connected at this point.\n\t\t\t\t\t// This avoids a race condition where the Connected notification\n\t\t\t\t\t// is processed after this time has fired.\n\t\t\t\t\tswitch m.network.Connectedness(p) {\n\t\t\t\t\tcase network.Connected, network.Limited:\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tm.pstore.RemovePeer(p)\n\t\t\t\t\t}\n\t\t\t\t\tdelete(disconnected, p)\n\t\t\t\t}\n\t\t\t}\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (m *PeerstoreManager) Close() error {\n\tif m.cancel != nil {\n\t\tm.cancel()\n\t}\n\tm.refCount.Wait()\n\treturn nil\n}\n"
  },
  {
    "path": "p2p/host/pstoremanager/pstoremanager_test.go",
    "content": "package pstoremanager_test\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/event\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/eventbus\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/pstoremanager\"\n\tswarmt \"github.com/libp2p/go-libp2p/p2p/net/swarm/testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/mock/gomock\"\n)\n\n//go:generate sh -c \"go run go.uber.org/mock/mockgen -package pstoremanager_test -destination mock_peerstore_test.go github.com/libp2p/go-libp2p/core/peerstore Peerstore\"\n\nfunc TestGracePeriod(t *testing.T) {\n\tt.Parallel()\n\tctrl := gomock.NewController(t)\n\tdefer ctrl.Finish()\n\teventBus := eventbus.NewBus()\n\tpstore := NewMockPeerstore(ctrl)\n\tconst gracePeriod = 250 * time.Millisecond\n\tman, err := pstoremanager.NewPeerstoreManager(pstore, eventBus, swarmt.GenSwarm(t), pstoremanager.WithGracePeriod(gracePeriod))\n\trequire.NoError(t, err)\n\tdefer man.Close()\n\tman.Start()\n\n\temitter, err := eventBus.Emitter(new(event.EvtPeerConnectednessChanged))\n\trequire.NoError(t, err)\n\tstart := time.Now()\n\tremoved := make(chan struct{})\n\tpstore.EXPECT().RemovePeer(peer.ID(\"foobar\")).DoAndReturn(func(_ peer.ID) {\n\t\tdefer close(removed)\n\t\t// make sure the call happened after the grace period\n\t\trequire.GreaterOrEqual(t, time.Since(start), gracePeriod)\n\t\trequire.LessOrEqual(t, time.Since(start), 3*gracePeriod)\n\t})\n\trequire.NoError(t, emitter.Emit(event.EvtPeerConnectednessChanged{\n\t\tPeer:          \"foobar\",\n\t\tConnectedness: network.NotConnected,\n\t}))\n\t<-removed\n}\n\nfunc TestReconnect(t *testing.T) {\n\tt.Parallel()\n\tctrl := gomock.NewController(t)\n\teventBus := eventbus.NewBus()\n\tpstore := NewMockPeerstore(ctrl)\n\tconst gracePeriod = 200 * time.Millisecond\n\tman, err := pstoremanager.NewPeerstoreManager(pstore, eventBus, swarmt.GenSwarm(t), pstoremanager.WithGracePeriod(gracePeriod))\n\trequire.NoError(t, err)\n\tdefer man.Close()\n\tman.Start()\n\n\temitter, err := eventBus.Emitter(new(event.EvtPeerConnectednessChanged))\n\trequire.NoError(t, err)\n\trequire.NoError(t, emitter.Emit(event.EvtPeerConnectednessChanged{\n\t\tPeer:          \"foobar\",\n\t\tConnectedness: network.NotConnected,\n\t}))\n\trequire.NoError(t, emitter.Emit(event.EvtPeerConnectednessChanged{\n\t\tPeer:          \"foobar\",\n\t\tConnectedness: network.Connected,\n\t}))\n\ttime.Sleep(gracePeriod * 3 / 2)\n\t// There should have been no calls to RemovePeer.\n\tctrl.Finish()\n}\n\nfunc TestClose(t *testing.T) {\n\tctrl := gomock.NewController(t)\n\tdefer ctrl.Finish()\n\teventBus := eventbus.NewBus()\n\tpstore := NewMockPeerstore(ctrl)\n\tconst gracePeriod = time.Hour\n\tman, err := pstoremanager.NewPeerstoreManager(pstore, eventBus, swarmt.GenSwarm(t), pstoremanager.WithGracePeriod(gracePeriod))\n\trequire.NoError(t, err)\n\tman.Start()\n\n\temitter, err := eventBus.Emitter(new(event.EvtPeerConnectednessChanged))\n\trequire.NoError(t, err)\n\n\tsub, err := eventBus.Subscribe(&event.EvtPeerConnectednessChanged{})\n\trequire.NoError(t, err)\n\n\trequire.NoError(t, emitter.Emit(event.EvtPeerConnectednessChanged{\n\t\tPeer:          \"foobar\",\n\t\tConnectedness: network.NotConnected,\n\t}))\n\n\t// make sure the event is sent before we close\n\tselect {\n\tcase <-sub.Out():\n\t\ttime.Sleep(100 * time.Millisecond) // make sure this event is also picked up by the pstoremanager\n\tcase <-time.After(5 * time.Second):\n\t\tt.Fatalf(\"Hit timeout\")\n\t}\n\n\tdone := make(chan struct{})\n\tpstore.EXPECT().RemovePeer(peer.ID(\"foobar\")).Do(func(peer.ID) { close(done) })\n\trequire.NoError(t, man.Close())\n\tselect {\n\tcase <-done:\n\tcase <-time.After(5 * time.Second):\n\t\tt.Fatalf(\"Hit timeout\")\n\t}\n}\n"
  },
  {
    "path": "p2p/host/relaysvc/relay.go",
    "content": "package relaysvc\n\nimport (\n\t\"context\"\n\t\"sync\"\n\n\t\"github.com/libp2p/go-libp2p/core/event\"\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/eventbus\"\n\trelayv2 \"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/relay\"\n)\n\ntype RelayManager struct {\n\thost host.Host\n\n\tmutex sync.Mutex\n\trelay *relayv2.Relay\n\topts  []relayv2.Option\n\n\trefCount  sync.WaitGroup\n\tctxCancel context.CancelFunc\n}\n\nfunc NewRelayManager(host host.Host, opts ...relayv2.Option) *RelayManager {\n\tctx, cancel := context.WithCancel(context.Background())\n\tm := &RelayManager{\n\t\thost:      host,\n\t\topts:      opts,\n\t\tctxCancel: cancel,\n\t}\n\tm.refCount.Add(1)\n\tgo m.background(ctx)\n\treturn m\n}\n\nfunc (m *RelayManager) background(ctx context.Context) {\n\tdefer m.refCount.Done()\n\tdefer func() {\n\t\tm.mutex.Lock()\n\t\tif m.relay != nil {\n\t\t\tm.relay.Close()\n\t\t}\n\t\tm.mutex.Unlock()\n\t}()\n\n\tsubReachability, _ := m.host.EventBus().Subscribe(new(event.EvtLocalReachabilityChanged), eventbus.Name(\"relaysvc\"))\n\tdefer subReachability.Close()\n\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase ev, ok := <-subReachability.Out():\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err := m.reachabilityChanged(ev.(event.EvtLocalReachabilityChanged).Reachability); err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (m *RelayManager) reachabilityChanged(r network.Reachability) error {\n\tswitch r {\n\tcase network.ReachabilityPublic:\n\t\tm.mutex.Lock()\n\t\tdefer m.mutex.Unlock()\n\t\t// This could happen if two consecutive EvtLocalReachabilityChanged report the same reachability.\n\t\t// This shouldn't happen, but it's safer to double-check.\n\t\tif m.relay != nil {\n\t\t\treturn nil\n\t\t}\n\t\trelay, err := relayv2.New(m.host, m.opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tm.relay = relay\n\tdefault:\n\t\tm.mutex.Lock()\n\t\tdefer m.mutex.Unlock()\n\t\tif m.relay != nil {\n\t\t\terr := m.relay.Close()\n\t\t\tm.relay = nil\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (m *RelayManager) Close() error {\n\tm.ctxCancel()\n\tm.refCount.Wait()\n\treturn nil\n}\n"
  },
  {
    "path": "p2p/host/relaysvc/relay_test.go",
    "content": "package relaysvc\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/event\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\tbhost \"github.com/libp2p/go-libp2p/p2p/host/blank\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/eventbus\"\n\tswarmt \"github.com/libp2p/go-libp2p/p2p/net/swarm/testing\"\n\trelayv2 \"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/relay\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestReachabilityChangeEvent(t *testing.T) {\n\th := bhost.NewBlankHost(swarmt.GenSwarm(t))\n\trmgr := NewRelayManager(h)\n\temitter, err := rmgr.host.EventBus().Emitter(new(event.EvtLocalReachabilityChanged), eventbus.Stateful)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tevt := event.EvtLocalReachabilityChanged{Reachability: network.ReachabilityPublic}\n\temitter.Emit(evt)\n\trequire.Eventually(\n\t\tt,\n\t\tfunc() bool { rmgr.mutex.Lock(); defer rmgr.mutex.Unlock(); return rmgr.relay != nil },\n\t\t1*time.Second,\n\t\t100*time.Millisecond,\n\t\t\"relay should be set on public reachability\")\n\n\tevt = event.EvtLocalReachabilityChanged{Reachability: network.ReachabilityPrivate}\n\temitter.Emit(evt)\n\trequire.Eventually(\n\t\tt,\n\t\tfunc() bool { rmgr.mutex.Lock(); defer rmgr.mutex.Unlock(); return rmgr.relay == nil },\n\t\t3*time.Second,\n\t\t100*time.Millisecond,\n\t\t\"relay should be nil on private reachability\")\n\n\tevt = event.EvtLocalReachabilityChanged{Reachability: network.ReachabilityPublic}\n\temitter.Emit(evt)\n\tevt = event.EvtLocalReachabilityChanged{Reachability: network.ReachabilityUnknown}\n\temitter.Emit(evt)\n\trequire.Eventually(\n\t\tt,\n\t\tfunc() bool { rmgr.mutex.Lock(); defer rmgr.mutex.Unlock(); return rmgr.relay == nil },\n\t\t3*time.Second,\n\t\t100*time.Millisecond,\n\t\t\"relay should be nil on unknown reachability\")\n\n\tevt = event.EvtLocalReachabilityChanged{Reachability: network.ReachabilityPublic}\n\temitter.Emit(evt)\n\tvar relay *relayv2.Relay\n\trequire.Eventually(\n\t\tt,\n\t\tfunc() bool { rmgr.mutex.Lock(); defer rmgr.mutex.Unlock(); relay = rmgr.relay; return relay != nil },\n\t\t3*time.Second,\n\t\t100*time.Millisecond,\n\t\t\"relay should be set on public event\")\n\temitter.Emit(evt)\n\trequire.Never(t,\n\t\tfunc() bool { rmgr.mutex.Lock(); defer rmgr.mutex.Unlock(); return relay != rmgr.relay },\n\t\t3*time.Second,\n\t\t100*time.Millisecond,\n\t\t\"relay should not be updated on receiving the same event\")\n}\n"
  },
  {
    "path": "p2p/host/resource-manager/README.md",
    "content": "# The libp2p Network Resource Manager\n\nThis package contains the canonical implementation of the libp2p\nNetwork Resource Manager interface.\n\nThe implementation is based on the concept of Resource Management\nScopes, whereby resource usage is constrained by a DAG of scopes,\naccounting for multiple levels of resource constraints.\n\nThe Resource Manager doesn't prioritize resource requests at all, it simply\nchecks if the resource being requested is currently below the defined limits and\nreturns an error if the limit is reached. It has no notion of honest vs bad peers.\n\nThe Resource Manager does have a special notion of [allowlisted](#allowlisting-multiaddrs-to-mitigate-eclipse-attacks) multiaddrs that\nhave their own limits if the normal system limits are reached.\n\n## Usage\n\nThe Resource Manager is intended to be used with go-libp2p. go-libp2p sets up a\nresource manager with the default autoscaled limits if none is provided, but if\nyou want to configure things or if you want to enable metrics you'll use the\nresource manager like so:\n\n```go\n// Start with the default scaling limits.\nscalingLimits := rcmgr.DefaultLimits\n\n// Add limits around included libp2p protocols\nlibp2p.SetDefaultServiceLimits(&scalingLimits)\n\n// Turn the scaling limits into a concrete set of limits using `.AutoScale`. This\n// scales the limits proportional to your system memory.\nscaledDefaultLimits := scalingLimits.AutoScale()\n\n// Tweak certain settings\ncfg := rcmgr.PartialLimitConfig{\n  System: rcmgr.ResourceLimits{\n    // Allow unlimited outbound streams\n    StreamsOutbound: rcmgr.Unlimited,\n  },\n  // Everything else is default. The exact values will come from `scaledDefaultLimits` above.\n}\n\n// Create our limits by using our cfg and replacing the default values with values from `scaledDefaultLimits`\nlimits := cfg.Build(scaledDefaultLimits)\n\n// The resource manager expects a limiter, se we create one from our limits.\nlimiter := rcmgr.NewFixedLimiter(limits)\n\n// Metrics are enabled by default. If you want to disable metrics, use the\n// WithMetricsDisabled option\n// Initialize the resource manager\nrm, err := rcmgr.NewResourceManager(limiter, rcmgr.WithMetricsDisabled())\nif err != nil {\n  panic(err)\n}\n\n// Create a libp2p host\nhost, err := libp2p.New(libp2p.ResourceManager(rm))\n```\n\n### Saving the limits config\nThe easiest way to save the defined limits is to serialize the `PartialLimitConfig`\ntype as JSON.\n\n```go\nnoisyNeighbor, _ := peer.Decode(\"QmVvtzcZgCkMnSFf2dnrBPXrWuNFWNM9J3MpZQCvWPuVZf\")\ncfg := rcmgr.PartialLimitConfig{\n  System: &rcmgr.ResourceLimits{\n    // Allow unlimited outbound streams\n    StreamsOutbound: rcmgr.Unlimited,\n  },\n  Peer: map[peer.ID]rcmgr.ResourceLimits{\n    noisyNeighbor: {\n      // No inbound connections from this peer\n      ConnsInbound: rcmgr.BlockAllLimit,\n      // But let me open connections to them\n      Conns:         rcmgr.DefaultLimit,\n      ConnsOutbound: rcmgr.DefaultLimit,\n      // No inbound streams from this peer\n      StreamsInbound: rcmgr.BlockAllLimit,\n      // And let me open unlimited (by me) outbound streams (the peer may have their own limits on me)\n      StreamsOutbound: rcmgr.Unlimited,\n    },\n  },\n}\njsonBytes, _ := json.Marshal(&cfg)\n\n// string(jsonBytes)\n// {\n//   \"System\": {\n//     \"StreamsOutbound\": \"unlimited\"\n//   },\n//   \"Peer\": {\n//     \"QmVvtzcZgCkMnSFf2dnrBPXrWuNFWNM9J3MpZQCvWPuVZf\": {\n//       \"StreamsInbound\": \"blockAll\",\n//       \"StreamsOutbound\": \"unlimited\",\n//       \"ConnsInbound\": \"blockAll\"\n//     }\n//   }\n// }\n```\n\nThis will omit defaults from the JSON output. It will also serialize the\nblockAll, and unlimited values explicitly.\n\nThe `Memory` field is serialized as a string to workaround the JSON limitation\nof 32 bit integers (`Memory` is an int64).\n\n## Basic Resources\n\n### Memory\n\nPerhaps the most fundamental resource is memory, and in particular\nbuffers used for network operations. The system must provide an\ninterface for components to reserve memory that accounts for buffers\n(and possibly other live objects), which is scoped within the component.\nBefore a new buffer is allocated, the component should try a memory\nreservation, which can fail if the resource limit is exceeded. It is\nthen up to the component to react to the error condition, depending on\nthe situation. For example, a muxer failing to grow a buffer in\nresponse to a window change should simply retain the old buffer and\noperate at perhaps degraded performance.\n\n### File Descriptors\n\nFile descriptors are an important resource that uses memory (and\ncomputational time) at the system level. They are also a scarce\nresource, as typically (unless the user explicitly intervenes) they\nare constrained by the system. Exhaustion of file descriptors may\nrender the application incapable of operating (e.g., because it is\nunable to open a file).  This is important for libp2p because most\noperating systems represent sockets as file descriptors.\n\n### Connections\n\nConnections are a higher-level concept endemic to libp2p; in order to\ncommunicate with another peer, a connection must first be\nestablished. Connections are an important resource in libp2p, as they\nconsume memory, goroutines, and possibly file descriptors.\n\nWe distinguish between inbound and outbound connections, as the former\nare initiated by remote peers and consume resources in response to\nnetwork events and thus need to be tightly controlled in order to\nprotect the application from overload or attack.  Outbound\nconnections are typically initiated by the application's volition and\ndon't need to be controlled as tightly. However, outbound connections\nstill consume resources and may be initiated in response to network\nevents because of (potentially faulty) application logic, so they\nstill need to be constrained.\n\n### Streams\n\nStreams are the fundamental object of interaction in libp2p; all\nprotocol interactions happen through a stream that goes over some\nconnection. Streams are a fundamental resource in libp2p, as they\nconsume memory and goroutines at all levels of the stack.\n\nStreams always belong to a peer, specify a protocol and they may\nbelong to some service in the system. Hence, this suggests that apart\nfrom global limits, we can constrain stream usage at finer\ngranularity, at the protocol and service level.\n\nOnce again, we distinguish between inbound and outbound streams.\nInbound streams are initiated by remote peers and consume resources in\nresponse to network events; controlling inbound stream usage is again\nparamount for protecting the system from overload or attack.\nOutbound streams are normally initiated by the application or some\nservice in the system in order to effect some protocol\ninteraction. However, they can also be initiated in response to\nnetwork events because of application or service logic, so we still\nneed to constrain them.\n\n\n## Resource Scopes\n\nThe Resource Manager is based on the concept of resource\nscopes. Resource Scopes account for resource usage that is temporally\ndelimited for the span of the scope. Resource Scopes conceptually\nform a DAG, providing us with a mechanism to enforce multiresolution\nresource accounting. Downstream resource usage is aggregated at scopes\nhigher up the graph.\n\nThe following diagram depicts the canonical scope graph:\n```\nSystem\n  +------------> Transient.............+................+\n  |                                    .                .\n  +------------>  Service------------- . ----------+    .\n  |                                    .           |    .\n  +------------->  Protocol----------- . ----------+    .\n  |                                    .           |    .\n  +-------------->* Peer               \\/          |    .\n                     +------------> Connection     |    .\n                     |                             \\/   \\/\n                     +--------------------------->  Stream\n```\n\n### The System Scope\n\nThe system scope is the top level scope that accounts for global\nresource usage at all levels of the system. This scope nests and\nconstrains all other scopes and institutes global hard limits.\n\n### The Transient Scope\n\nThe transient scope accounts for resources that are in the process of\nfull establishment.  For instance, a new connection prior to the\nhandshake does not belong to any peer, but it still needs to be\nconstrained as this opens an avenue for attacks in transient resource\nusage. Similarly, a stream that has not negotiated a protocol yet is\nconstrained by the transient scope.\n\nThe transient scope effectively represents a DMZ (DeMilitarized Zone),\nwhere resource usage can be accounted for connections and streams that\nare not fully established.\n\n### The Allowlist System Scope\n\nSame as the normal system scope above, but is used if the normal system scope is\nalready at its limits and the resource is from an allowlisted peer. See\n[Allowlisting multiaddrs to mitigate eclipse\nattacks](#allowlisting-multiaddrs-to-mitigate-eclipse-attacks) see for more\ninformation.\n\n### The Allowlist Transient Scope\n\nSame as the normal transient scope above, but is used if the normal transient\nscope is already at its limits and the resource is from an allowlisted peer. See\n[Allowlisting multiaddrs to mitigate eclipse\nattacks](#allowlisting-multiaddrs-to-mitigate-eclipse-attacks) see for more\ninformation.\n\n### Service Scopes\n\nThe system is typically organized across services, which may be\nambient and provide basic functionality to the system (e.g. identify,\nautonat, relay, etc). Alternatively, services may be explicitly\ninstantiated by the application, and provide core components of its\nfunctionality (e.g. pubsub, the DHT, etc).\n\nServices are logical groupings of streams that implement protocol flow\nand may additionally consume resources such as memory. Services\ntypically have at least one stream handler, so they are subject to\ninbound stream creation and resource usage in response to network\nevents. As such, the system explicitly models them allowing for\nisolated resource usage that can be tuned by the user.\n\n### Protocol Scopes\n\nProtocol Scopes account for resources at the protocol level. They are\nan intermediate resource scope which can constrain streams which may\nnot have a service associated or for resource control within a\nservice. It also provides an opportunity for system operators to\nexplicitly restrict specific protocols.\n\nFor instance, a service that is not aware of the resource manager and\nhas not been ported to mark its streams, may still gain limits\ntransparently without any programmer intervention.  Furthermore, the\nprotocol scope can constrain resource usage for services that\nimplement multiple protocols for the sake of backwards\ncompatibility. A tighter limit in some older protocol can protect the\napplication from resource consumption caused by legacy clients or\npotential attacks.\n\nFor a concrete example, consider pubsub with the gossipsub router: the\nservice also understands the floodsub protocol for backwards\ncompatibility and support for unsophisticated clients that are lagging\nin the implementation effort. By specifying a lower limit for the\nfloodsub protocol, we can can constrain the service level for legacy\nclients using an inefficient protocol.\n\n### Peer Scopes\n\nThe peer scope accounts for resource usage by an individual peer. This\nconstrains connections and streams and limits the blast radius of\nresource consumption by a single remote peer.\n\nThis ensures that no single peer can use more resources than allowed\nby the peer limits. Every peer has a default limit, but the programmer\nmay raise (or lower) limits for specific peers.\n\n\n### Connection Scopes\n\nThe connection scope is delimited to the duration of a connection and\nconstrains resource usage by a single connection. The scope is a leaf\nin the DAG, with a span that begins when a connection is established\nand ends when the connection is closed. Its resources are aggregated\nto the resource usage of a peer.\n\n### Stream Scopes\n\nThe stream scope is delimited to the duration of a stream, and\nconstrains resource usage by a single stream. This scope is also a\nleaf in the DAG, with span that begins when a stream is created and\nends when the stream is closed. Its resources are aggregated to the\nresource usage of a peer, and constrained by a service and protocol\nscope.\n\n### User Transaction Scopes\n\nUser transaction scopes can be created as a child of any extant\nresource scope, and provide the programmer with a delimited scope for\neasy resource accounting. Transactions may form a tree that is rooted\nto some canonical scope in the scope DAG.\n\nFor instance, a programmer may create a transaction scope within a\nservice that accounts for some control flow delimited resource\nusage. Similarly, a programmer may create a transaction scope for some\ninteraction within a stream, e.g. a Request/Response interaction that\nuses a buffer.\n\n## Limits\n\nEach resource scope has an associated limit object, which designates\nlimits for all [basic resources](#basic-resources).  The limit is checked every time some\nresource is reserved and provides the system with an opportunity to\nconstrain resource usage.\n\nThere are separate limits for each class of scope, allowing for\nmultiresolution and aggregate resource accounting.  As such, we have\nlimits for the system and transient scopes, default and specific\nlimits for services, protocols, and peers, and limits for connections\nand streams.\n\n### Scaling Limits\n\nWhen building software that is supposed to run on many different kind of machines,\nwith various memory and CPU configurations, it is desirable to have limits that\nscale with the size of the machine.\n\nThis is done using the `ScalingLimitConfig`. For every scope, this configuration\nstruct defines the absolutely bare minimum limits, and an (optional) increase of\nthese limits, which will be applied on nodes that have sufficient memory.\n\nA `ScalingLimitConfig` can be converted into a `ConcreteLimitConfig` (which can then be\nused to initialize a fixed limiter with `NewFixedLimiter`) by calling the `Scale` method.\nThe `Scale` method takes two parameters: the amount of memory and the number of file\ndescriptors that an application is willing to dedicate to libp2p.\n\nThese amounts will differ between use cases. A blockchain node running on a dedicated\nserver might have a lot of memory, and dedicate 1/4 of that memory to libp2p. On the\nother end of the spectrum, a desktop companion application running as a background\ntask on a consumer laptop will probably dedicate significantly less than 1/4 of its system\nmemory to libp2p.\n\nFor convenience, the `ScalingLimitConfig` also provides an `AutoScale` method,\nwhich determines the amount of memory and file descriptors available on the\nsystem, and dedicates up to 1/8 of the memory and 1/2 of the file descriptors to\nlibp2p.\n\nFor example, one might set:\n```go\nvar scalingLimits = ScalingLimitConfig{\n  SystemBaseLimit: BaseLimit{\n    ConnsInbound:    64,\n    ConnsOutbound:   128,\n    Conns:           128,\n    StreamsInbound:  512,\n    StreamsOutbound: 1024,\n    Streams:         1024,\n    Memory:          128 << 20,\n    FD:              256,\n  },\n  SystemLimitIncrease: BaseLimitIncrease{\n    ConnsInbound:    32,\n    ConnsOutbound:   64,\n    Conns:           64,\n    StreamsInbound:  256,\n    StreamsOutbound: 512,\n    Streams:         512,\n    Memory:          256 << 20,\n    FDFraction:      1,\n  },\n}\n```\n\nThe base limit (`SystemBaseLimit`) here is the minimum configuration that any\nnode will have, no matter how little memory it possesses. For every GB of memory\npassed into the `Scale` method, an increase of (`SystemLimitIncrease`) is added.\n\nFor Example, calling `Scale` with 4 GB of memory will result in a limit of 384 for\n`Conns` (128 + 4*64).\n\nThe `FDFraction` defines how many of the file descriptors are allocated to this\nscope. In the example above, when called with a file descriptor value of 1000,\nthis would result in a limit of 1000 (1000 * 1) file descriptors for the system\nscope. See `TestReadmeExample` in `limit_test.go`.\n\nNote that we only showed the configuration for the system scope here, equivalent\nconfiguration options apply to all other scopes as well.\n\n### Default limits\n\nBy default the resource manager ships with some reasonable scaling limits and\nmakes a reasonable guess at how much system memory you want to dedicate to the\ngo-libp2p process. For the default definitions see [`DefaultLimits` and\n`ScalingLimitConfig.AutoScale()`](./limit_defaults.go).\n\n### Tweaking Defaults\n\nIf the defaults seem mostly okay, but you want to adjust one facet you can\nsimply copy the default struct object and update the field you want to change. You can\napply changes to a `BaseLimit`, `BaseLimitIncrease`, and `ConcreteLimitConfig` with\n`.Apply`.\n\nExample\n```\n// An example on how to tweak the default limits\ntweakedDefaults := DefaultLimits\ntweakedDefaults.ProtocolBaseLimit.Streams = 1024\ntweakedDefaults.ProtocolBaseLimit.StreamsInbound = 512\ntweakedDefaults.ProtocolBaseLimit.StreamsOutbound = 512\n```\n\n### How to tune your limits\n\nOnce you've set your limits and monitoring (see [Monitoring](#monitoring) below)\nyou can now tune your limits better.  The `rcmgr_blocked_resources` metric will\ntell you what was blocked and for what scope. If you see a steady stream of\nthese blocked requests it means your resource limits are too low for your usage.\nIf you see a rare sudden spike, this is okay and it means the resource manager\nprotected you from some anomaly.\n\n### How to disable limits\n\nSometimes disabling all limits is useful when you want to see how much\nresources you use during normal operation. You can then use this information to\ndefine your initial limits. Disable the limits by using `InfiniteLimits`.\n\n### Debug \"resource limit exceeded\" errors\n\nThese errors occur whenever a limit is hit. For example, you'll get this error if\nyou are at your limit for the number of streams you can have, and you try to\nopen one more.\n\nExample Log:\n```\n2022-08-12T15:49:35.459-0700\tDEBUG\trcmgr\tgo-libp2p-resource-manager@v0.5.3/scope.go:541\tblocked connection from constraining edge\t{\"scope\": \"conn-19667\", \"edge\": \"system\", \"direction\": \"Inbound\", \"usefd\": false, \"current\": 100, \"attempted\": 1, \"limit\": 100, \"stat\": {\"NumStreamsInbound\":28,\"NumStreamsOutbound\":66,\"NumConnsInbound\":37,\"NumConnsOutbound\":63,\"NumFD\":33,\"Memory\":8687616}, \"error\": \"system: cannot reserve connection: resource limit exceeded\"}\n```\n\nThe log line above is an example log line that gets emitted if you enable debug\nlogging in the resource manager. You can do this by setting the environment\nvariable `GOLOG_LOG_LEVEL=\"rcmgr=debug\"`. By default only the error is\nreturned to the caller, and nothing is logged by the resource manager itself.\n\nThe log line message (and returned error) will tell you which resource limit was\nhit (connection in the log above) and what blocked it (in this case it was the\nsystem scope that blocked it). The log will also include some more information\nabout the current usage of the resources. In the example log above, there is a\nlimit of 100 connections, and you can see that we have 37 inbound connections\nand 63 outbound connections. We've reached the limit and the resource manager\nwill block any further connections.\n\nThe next step in debugging is seeing if this is a recurring problem or just a\ntransient error. If it's a transient error it's okay to ignore it since the\nresource manager was doing its job in keeping resource usage under the limit. If\nit's recurring then you should understand what's causing you to hit these limits\nand either refactor your application or raise the limits.\n\nTo check if it's a recurring problem you can count the number of times you've\nseen the `\"resource limit exceeded\"` error over time. You can also check the\n`rcmgr_blocked_resources` metric to see how many times the resource manager has\nblocked a resource over time.\n\nIf the resource is blocked by a protocol-level scope, take a look at the various\nresource usages in the metrics. For example, if you run into a new stream being blocked,\nyou can check the\n`rcmgr_streams` metric and the \"Streams by protocol\" graph in the Grafana\ndashboard (assuming you've set that up or something similar – see\n[Monitoring](#monitoring)) to understand the usage pattern of that specific\nprotocol. This can help answer questions such as: \"Am I constantly around my\nlimit?\", \"Does it make sense to raise my limit?\", \"Are there any patterns around\nhitting this limit?\", and \"should I refactor my protocol implementation?\"\n\n## Monitoring\n\nOnce you have limits set, you'll want to monitor to see if you're running into\nyour limits often. This could be a sign that you need to raise your limits\n(your process is more intensive than you originally thought) or that you need\nto fix something in your application (surely you don't need over 1000 streams?).\n\nThere are Prometheus metrics that can be hooked up to the resource manager. See\n`obs/stats_test.go` for an example on how to enable this, and `DefaultViews` in\n`stats.go` for recommended views. These metrics can be hooked up to Prometheus\nor any other platform that can scrape a prometheus endpoint.\n\nThere is also an included Grafana dashboard to help kickstart your\nobservability into the resource manager. Find more information about it at\n[here](./../../../dashboards/resource-manager/README.md).\n\n## Allowlisting multiaddrs to mitigate eclipse attacks\n\nIf you have a set of trusted peers and IP addresses, you can use the resource\nmanager's [Allowlist](./docs/allowlist.md) to protect yourself from eclipse\nattacks. The set of peers in the allowlist will have their own limits in case\nthe normal limits are reached. This means you will always be able to connect to\nthese trusted peers even if you've already reached your system limits.\n\nLook at `WithAllowlistedMultiaddrs` and its example in the GoDoc to learn more.\n\n## ConnManager vs Resource Manager\n\ngo-libp2p already includes a [connection\nmanager](https://pkg.go.dev/github.com/libp2p/go-libp2p/core/connmgr#ConnManager),\nso what's the difference between the `ConnManager` and the `ResourceManager`?\n\nConnManager:\n1. Configured with a low and high watermark number of connections.\n2. Attempts to maintain the number of connections between the low and high\n   markers.\n3. Connections can be given metadata and weight (e.g. a hole punched\n   connection is more valuable than a connection to a publicly addressable\n   endpoint since it took more effort to make the hole punched connection).\n4. The ConnManager will trim connections once the high watermark is reached. and\n   trim down to the low watermark.\n5. Won't block adding another connection above the high watermark, but will\n   trigger the trim mentioned above.\n6. Can trim and prioritize connections with custom logic.\n7. No concept of scopes (like the resource manager).\n\nResource Manager:\n1. Configured with limits on the number of outgoing and incoming connections at\n   different [resource scopes](#resource-scopes).\n2. Will block adding any more connections if any of the scope-specific limits would be exceeded.\n\nThe natural question when comparing these two managers is \"how do the watermarks\nand limits interact with each other?\". The short answer is that they don't know\nabout each other. This can lead to some surprising subtleties, such as the\ntrimming never happening because the resource manager's limit is lower than the\nhigh watermark. This is confusing, and we'd like to fix it. The issue is\ncaptured in [go-libp2p#1640](https://github.com/libp2p/go-libp2p/issues/1640).\n\nWhen configuring the resource manager and connection manager, you should set the\nlimits in the resource manager as your hard limits that you would never want to\ngo over, and set the low/high watermarks as the range at which your application\nworks best.\n\n## Examples\n\nHere we consider some concrete examples that can elucidate the abstract\ndesign as described so far.\n\n### Stream Lifetime\n\nLet's consider a stream and the limits that apply to it.\nWhen the stream scope is first opened, it is created by calling\n`ResourceManager.OpenStream`.\n\nInitially the stream is constrained by:\n- the system scope, where global hard limits apply.\n- the transient scope, where unnegotiated streams live.\n- the peer scope, where the limits for the peer at the other end of the stream\n  apply.\n\nOnce the protocol has been negotiated, the protocol is set by calling\n`StreamManagementScope.SetProtocol`. The constraint from the\ntransient scope is removed and the stream is now constrained by the\nprotocol instead.\n\nMore specifically, the following constraints apply:\n- the system scope, where global hard limits apply.\n- the peer scope, where the limits for the peer at the other end of the stream\n  apply.\n- the protocol scope, where the limits of the specific protocol used apply.\n\nThe existence of the protocol limit allows us to implicitly constrain\nstreams for services that have not been ported to the resource manager\nyet.  Once the programmer attaches a stream to a service by calling\n`StreamScope.SetService`, the stream resources are aggregated and constrained\nby the service scope in addition to its protocol scope.\n\nMore specifically the following constraints apply:\n- the system scope, where global hard limits apply.\n- the peer scope, where the limits for the peer at the other end of the stream\n  apply.\n- the service scope, where the limits of the specific service owning the stream apply.\n- the protocol scope, where the limits of the specific protocol for the stream apply.\n\n\nThe resource transfer that happens in the `SetProtocol` and `SetService`\ngives the opportunity to the resource manager to gate the streams. If\nthe transfer results in exceeding the scope limits, then a error\nindicating \"resource limit exceeded\" is returned. The wrapped error\nincludes the name of the scope rejecting the resource acquisition to\naid understanding of applicable limits.  Note that the (wrapped) error\nimplements `net.Error` and is marked as temporary, so that the\nprogrammer can handle by backoff retry.\n\n\n## Implementation Notes\n\n- The package only exports a constructor for the resource manager and\n  basic types for defining limits. Internals are not exposed.\n- Internally, there is a resources object that is embedded in every scope and\n  implements resource accounting.\n- There is a single implementation of a generic resource scope, that\n  provides all necessary interface methods.\n- There are concrete types for all canonical scopes, embedding a\n  pointer to a generic resource scope.\n- Peer and Protocol scopes, which may be created in response to\n  network events, are periodically garbage collected.\n\n## Design Considerations\n\n- The Resource Manager must account for basic resource usage at all\n  levels of the stack, from the internals to application components\n  that use the network facilities of libp2p.\n- Basic resources include memory, streams, connections, and file\n  descriptors. These account for both space and time used by\n  the stack, as each resource has a direct effect on the system\n  availability and performance.\n- The design must support seamless integration for user applications,\n  which should reap the benefits of resource management without any\n  changes. That is, existing applications should be oblivious of the\n  resource manager and transparently obtain limits which protects it\n  from resource exhaustion and OOM conditions.\n- At the same time, the design must support opt-in resource usage\n  accounting for applications that want to explicitly utilize the\n  facilities of the system to inform about and constrain their own\n  resource usage.\n- The design must allow the user to set their own limits, which can be\n  static (fixed) or dynamic.\n"
  },
  {
    "path": "p2p/host/resource-manager/allowlist.go",
    "content": "package rcmgr\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"sync\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\n\t\"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\ntype Allowlist struct {\n\tmu sync.RWMutex\n\t// a simple structure of lists of networks. There is probably a faster way\n\t// to check if an IP address is in this network than iterating over this\n\t// list, but this is good enough for small numbers of networks (<1_000).\n\t// Analyze the benchmark before trying to optimize this.\n\n\t// Any peer with these IPs are allowed\n\tallowedNetworks []*net.IPNet\n\n\t// Only the specified peers can use these IPs\n\tallowedPeerByNetwork map[peer.ID][]*net.IPNet\n}\n\n// WithAllowlistedMultiaddrs sets the multiaddrs to be in the allowlist\nfunc WithAllowlistedMultiaddrs(mas []multiaddr.Multiaddr) Option {\n\treturn func(rm *resourceManager) error {\n\t\tfor _, ma := range mas {\n\t\t\terr := rm.allowlist.Add(ma)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n}\n\nfunc newAllowlist() Allowlist {\n\treturn Allowlist{\n\t\tallowedPeerByNetwork: make(map[peer.ID][]*net.IPNet),\n\t}\n}\n\nfunc toIPNet(ma multiaddr.Multiaddr) (*net.IPNet, peer.ID, error) {\n\tvar ipString string\n\tvar mask string\n\tvar allowedPeerStr string\n\tvar allowedPeer peer.ID\n\tvar isIPV4 bool\n\n\tmultiaddr.ForEach(ma, func(c multiaddr.Component) bool {\n\t\tif c.Protocol().Code == multiaddr.P_IP4 || c.Protocol().Code == multiaddr.P_IP6 {\n\t\t\tisIPV4 = c.Protocol().Code == multiaddr.P_IP4\n\t\t\tipString = c.Value()\n\t\t}\n\t\tif c.Protocol().Code == multiaddr.P_IPCIDR {\n\t\t\tmask = c.Value()\n\t\t}\n\t\tif c.Protocol().Code == multiaddr.P_P2P {\n\t\t\tallowedPeerStr = c.Value()\n\t\t}\n\t\treturn ipString == \"\" || mask == \"\" || allowedPeerStr == \"\"\n\t})\n\n\tif ipString == \"\" {\n\t\treturn nil, allowedPeer, errors.New(\"missing ip address\")\n\t}\n\n\tif allowedPeerStr != \"\" {\n\t\tvar err error\n\t\tallowedPeer, err = peer.Decode(allowedPeerStr)\n\t\tif err != nil {\n\t\t\treturn nil, allowedPeer, fmt.Errorf(\"failed to decode allowed peer: %w\", err)\n\t\t}\n\t}\n\n\tif mask == \"\" {\n\t\tip := net.ParseIP(ipString)\n\t\tif ip == nil {\n\t\t\treturn nil, allowedPeer, errors.New(\"invalid ip address\")\n\t\t}\n\t\tvar mask net.IPMask\n\t\tif isIPV4 {\n\t\t\tmask = net.CIDRMask(32, 32)\n\t\t} else {\n\t\t\tmask = net.CIDRMask(128, 128)\n\t\t}\n\n\t\tnet := &net.IPNet{IP: ip, Mask: mask}\n\t\treturn net, allowedPeer, nil\n\t}\n\n\t_, ipnet, err := net.ParseCIDR(ipString + \"/\" + mask)\n\treturn ipnet, allowedPeer, err\n\n}\n\n// Add takes a multiaddr and adds it to the allowlist. The multiaddr should be\n// an ip address of the peer with or without a `/p2p` protocol.\n// e.g. /ip4/1.2.3.4/p2p/QmFoo, /ip4/1.2.3.4, and /ip4/1.2.3.0/ipcidr/24 are valid.\n// /p2p/QmFoo is not valid.\nfunc (al *Allowlist) Add(ma multiaddr.Multiaddr) error {\n\tipnet, allowedPeer, err := toIPNet(ma)\n\tif err != nil {\n\t\treturn err\n\t}\n\tal.mu.Lock()\n\tdefer al.mu.Unlock()\n\n\tif allowedPeer != peer.ID(\"\") {\n\t\t// We have a peerID constraint\n\t\tif al.allowedPeerByNetwork == nil {\n\t\t\tal.allowedPeerByNetwork = make(map[peer.ID][]*net.IPNet)\n\t\t}\n\t\tal.allowedPeerByNetwork[allowedPeer] = append(al.allowedPeerByNetwork[allowedPeer], ipnet)\n\t} else {\n\t\tal.allowedNetworks = append(al.allowedNetworks, ipnet)\n\t}\n\treturn nil\n}\n\nfunc (al *Allowlist) Remove(ma multiaddr.Multiaddr) error {\n\tipnet, allowedPeer, err := toIPNet(ma)\n\tif err != nil {\n\t\treturn err\n\t}\n\tal.mu.Lock()\n\tdefer al.mu.Unlock()\n\n\tipNetList := al.allowedNetworks\n\n\tif allowedPeer != \"\" {\n\t\t// We have a peerID constraint\n\t\tipNetList = al.allowedPeerByNetwork[allowedPeer]\n\t}\n\n\tif ipNetList == nil {\n\t\treturn nil\n\t}\n\n\ti := len(ipNetList)\n\tfor i > 0 {\n\t\ti--\n\t\tif ipNetList[i].IP.Equal(ipnet.IP) && bytes.Equal(ipNetList[i].Mask, ipnet.Mask) {\n\t\t\t// swap remove\n\t\t\tipNetList[i] = ipNetList[len(ipNetList)-1]\n\t\t\tipNetList = ipNetList[:len(ipNetList)-1]\n\t\t\t// We only remove one thing\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif allowedPeer != \"\" {\n\t\tal.allowedPeerByNetwork[allowedPeer] = ipNetList\n\t} else {\n\t\tal.allowedNetworks = ipNetList\n\t}\n\n\treturn nil\n}\n\nfunc (al *Allowlist) Allowed(ma multiaddr.Multiaddr) bool {\n\tip, err := manet.ToIP(ma)\n\tif err != nil {\n\t\treturn false\n\t}\n\tal.mu.RLock()\n\tdefer al.mu.RUnlock()\n\n\tfor _, network := range al.allowedNetworks {\n\t\tif network.Contains(ip) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\tfor _, allowedNetworks := range al.allowedPeerByNetwork {\n\t\tfor _, network := range allowedNetworks {\n\t\t\tif network.Contains(ip) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc (al *Allowlist) AllowedPeerAndMultiaddr(peerID peer.ID, ma multiaddr.Multiaddr) bool {\n\tip, err := manet.ToIP(ma)\n\tif err != nil {\n\t\treturn false\n\t}\n\tal.mu.RLock()\n\tdefer al.mu.RUnlock()\n\n\tfor _, network := range al.allowedNetworks {\n\t\tif network.Contains(ip) {\n\t\t\t// We found a match that isn't constrained by a peerID\n\t\t\treturn true\n\t\t}\n\t}\n\n\tif expectedNetworks, ok := al.allowedPeerByNetwork[peerID]; ok {\n\t\tfor _, expectedNetwork := range expectedNetworks {\n\t\t\tif expectedNetwork.Contains(ip) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false\n}\n"
  },
  {
    "path": "p2p/host/resource-manager/allowlist_test.go",
    "content": "package rcmgr\n\nimport (\n\t\"crypto/rand\"\n\t\"fmt\"\n\t\"net\"\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/test\"\n\n\t\"github.com/multiformats/go-multiaddr\"\n)\n\nfunc ExampleWithAllowlistedMultiaddrs() {\n\tsomePeer, err := test.RandPeerID()\n\tif err != nil {\n\t\tpanic(\"Failed to generate somePeer\")\n\t}\n\n\tlimits := DefaultLimits.AutoScale()\n\trcmgr, err := NewResourceManager(NewFixedLimiter(limits), WithAllowlistedMultiaddrs([]multiaddr.Multiaddr{\n\t\t// Any peer connecting from this IP address\n\t\tmultiaddr.StringCast(\"/ip4/1.2.3.4\"),\n\t\t// Only the specified peer from this address\n\t\tmultiaddr.StringCast(\"/ip4/2.2.3.4/p2p/\" + somePeer.String()),\n\t\t// Only peers from this 1.2.3.0/24 IP address range\n\t\tmultiaddr.StringCast(\"/ip4/1.2.3.0/ipcidr/24\"),\n\t}))\n\tif err != nil {\n\t\tpanic(\"Failed to start resource manager\")\n\t}\n\n\t// Use rcmgr as before\n\t_ = rcmgr\n}\n\nfunc TestAllowedSimple(t *testing.T) {\n\tallowlist := newAllowlist()\n\tma := multiaddr.StringCast(\"/ip4/1.2.3.4/tcp/1234\")\n\terr := allowlist.Add(ma)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to add ip4: %s\", err)\n\t}\n\n\tif !allowlist.Allowed(ma) {\n\t\tt.Fatalf(\"addr should be allowed\")\n\t}\n}\n\nfunc TestAllowedWithPeer(t *testing.T) {\n\ttype testcase struct {\n\t\tname      string\n\t\tallowlist []string\n\t\tendpoint  multiaddr.Multiaddr\n\t\tpeer      peer.ID\n\t\t// Is this endpoint allowed? (We don't have peer info yet)\n\t\tisConnAllowed bool\n\t\t// Is this peer + endpoint allowed?\n\t\tisAllowedWithPeer bool\n\t}\n\n\tpeerA := test.RandPeerIDFatal(t)\n\tpeerB := test.RandPeerIDFatal(t)\n\tmultiaddrA := multiaddr.StringCast(\"/ip4/1.2.3.4/tcp/1234\")\n\tmultiaddrB := multiaddr.StringCast(\"/ip4/2.2.3.4/tcp/1234\")\n\n\ttestcases := []testcase{\n\t\t{\n\t\t\tname:              \"Blocked\",\n\t\t\tisConnAllowed:     false,\n\t\t\tisAllowedWithPeer: false,\n\t\t\tallowlist:         []string{\"/ip4/1.2.3.1\"},\n\t\t\tendpoint:          multiaddrA,\n\t\t\tpeer:              peerA,\n\t\t},\n\t\t{\n\t\t\tname:              \"Blocked wrong peer\",\n\t\t\tisConnAllowed:     true,\n\t\t\tisAllowedWithPeer: false,\n\t\t\tallowlist:         []string{\"/ip4/1.2.3.4\" + \"/p2p/\" + peerB.String()},\n\t\t\tendpoint:          multiaddrA,\n\t\t\tpeer:              peerA,\n\t\t},\n\t\t{\n\t\t\tname:              \"allowed on network\",\n\t\t\tisConnAllowed:     true,\n\t\t\tisAllowedWithPeer: true,\n\t\t\tallowlist:         []string{\"/ip4/1.2.3.0/ipcidr/24\"},\n\t\t\tendpoint:          multiaddrA,\n\t\t\tpeer:              peerA,\n\t\t},\n\t\t{\n\t\t\tname:              \"Blocked peer not on network\",\n\t\t\tisConnAllowed:     true,\n\t\t\tisAllowedWithPeer: true,\n\t\t\tallowlist:         []string{\"/ip4/1.2.3.0/ipcidr/24\"},\n\t\t\tendpoint:          multiaddrA,\n\t\t\tpeer:              peerA,\n\t\t}, {\n\t\t\tname:              \"allowed. right network, right peer\",\n\t\t\tisConnAllowed:     true,\n\t\t\tisAllowedWithPeer: true,\n\t\t\tallowlist:         []string{\"/ip4/1.2.3.0/ipcidr/24\" + \"/p2p/\" + peerA.String()},\n\t\t\tendpoint:          multiaddrA,\n\t\t\tpeer:              peerA,\n\t\t}, {\n\t\t\tname:              \"allowed. right network, no peer\",\n\t\t\tisConnAllowed:     true,\n\t\t\tisAllowedWithPeer: true,\n\t\t\tallowlist:         []string{\"/ip4/1.2.3.0/ipcidr/24\"},\n\t\t\tendpoint:          multiaddrA,\n\t\t\tpeer:              peerA,\n\t\t},\n\t\t{\n\t\t\tname:              \"Blocked. right network, wrong peer\",\n\t\t\tisConnAllowed:     true,\n\t\t\tisAllowedWithPeer: false,\n\t\t\tallowlist:         []string{\"/ip4/1.2.3.0/ipcidr/24\" + \"/p2p/\" + peerB.String()},\n\t\t\tendpoint:          multiaddrA,\n\t\t\tpeer:              peerA,\n\t\t},\n\t\t{\n\t\t\tname:              \"allowed peer any ip\",\n\t\t\tisConnAllowed:     true,\n\t\t\tisAllowedWithPeer: true,\n\t\t\tallowlist:         []string{\"/ip4/0.0.0.0/ipcidr/0\"},\n\t\t\tendpoint:          multiaddrA,\n\t\t\tpeer:              peerA,\n\t\t},\n\t\t{\n\t\t\tname:              \"allowed peer multiple ips in allowlist\",\n\t\t\tisConnAllowed:     true,\n\t\t\tisAllowedWithPeer: true,\n\t\t\tallowlist:         []string{\"/ip4/1.2.3.4/p2p/\" + peerA.String(), \"/ip4/2.2.3.4/p2p/\" + peerA.String()},\n\t\t\tendpoint:          multiaddrA,\n\t\t\tpeer:              peerA,\n\t\t},\n\t\t{\n\t\t\tname:              \"allowed peer multiple ips in allowlist\",\n\t\t\tisConnAllowed:     true,\n\t\t\tisAllowedWithPeer: true,\n\t\t\tallowlist:         []string{\"/ip4/1.2.3.4/p2p/\" + peerA.String(), \"/ip4/1.2.3.4/p2p/\" + peerA.String()},\n\t\t\tendpoint:          multiaddrA,\n\t\t\tpeer:              peerA,\n\t\t},\n\t\t{\n\t\t\tname:              \"allowed peer multiple ips in allowlist\",\n\t\t\tisConnAllowed:     true,\n\t\t\tisAllowedWithPeer: true,\n\t\t\tallowlist:         []string{\"/ip4/1.2.3.4/p2p/\" + peerA.String(), \"/ip4/2.2.3.4/p2p/\" + peerA.String()},\n\t\t\tendpoint:          multiaddrB,\n\t\t\tpeer:              peerA,\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tallowlist := newAllowlist()\n\t\t\tfor _, maStr := range tc.allowlist {\n\t\t\t\tma, err := multiaddr.NewMultiaddr(maStr)\n\t\t\t\tif err != nil {\n\t\t\t\t\tfmt.Printf(\"failed to parse multiaddr: %s\", err)\n\t\t\t\t}\n\t\t\t\tallowlist.Add(ma)\n\t\t\t}\n\n\t\t\tif allowlist.Allowed(tc.endpoint) != tc.isConnAllowed {\n\t\t\t\tt.Fatalf(\"%v: expected %v\", !tc.isConnAllowed, tc.isConnAllowed)\n\t\t\t}\n\n\t\t\tif allowlist.AllowedPeerAndMultiaddr(tc.peer, tc.endpoint) != tc.isAllowedWithPeer {\n\t\t\t\tt.Fatalf(\"%v: expected %v\", !tc.isAllowedWithPeer, tc.isAllowedWithPeer)\n\t\t\t}\n\t\t})\n\t}\n\n}\n\nfunc TestRemoved(t *testing.T) {\n\ttype testCase struct {\n\t\tname      string\n\t\tallowedMA string\n\t}\n\tpeerA := test.RandPeerIDFatal(t)\n\tmaA := multiaddr.StringCast(\"/ip4/1.2.3.4\")\n\n\ttestCases := []testCase{\n\t\t{name: \"ip4\", allowedMA: \"/ip4/1.2.3.4\"},\n\t\t{name: \"ip4 with peer\", allowedMA: \"/ip4/1.2.3.4/p2p/\" + peerA.String()},\n\t\t{name: \"ip4 network\", allowedMA: \"/ip4/0.0.0.0/ipcidr/0\"},\n\t\t{name: \"ip4 network with peer\", allowedMA: \"/ip4/0.0.0.0/ipcidr/0/p2p/\" + peerA.String()},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tallowlist := newAllowlist()\n\t\t\tma := multiaddr.StringCast(tc.allowedMA)\n\n\t\t\terr := allowlist.Add(ma)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed to add ip4: %s\", err)\n\t\t\t}\n\n\t\t\tif !allowlist.AllowedPeerAndMultiaddr(peerA, maA) {\n\t\t\t\tt.Fatalf(\"addr should be allowed\")\n\t\t\t}\n\n\t\t\tallowlist.Remove((ma))\n\n\t\t\tif allowlist.AllowedPeerAndMultiaddr(peerA, maA) {\n\t\t\t\tt.Fatalf(\"addr should not be allowed\")\n\t\t\t}\n\t\t})\n\t}\n}\n\n// BenchmarkAllowlistCheck benchmarks the allowlist with plausible conditions.\nfunc BenchmarkAllowlistCheck(b *testing.B) {\n\tallowlist := newAllowlist()\n\n\t// How often do we expect a peer to be specified? 1 in N\n\tratioOfSpecifiedPeers := 10\n\n\t// How often do we expect an allowlist hit? 1 in N\n\tratioOfAllowlistHit := 100\n\n\t// How many multiaddrs in our allowlist?\n\thowManyMultiaddrsInAllowList := 1_000\n\n\t// How often is the IP addr an IPV6? 1 in N\n\tratioOfIPV6 := 20\n\n\tcountOfTotalPeersForTest := 100_000\n\n\tmas := make([]multiaddr.Multiaddr, countOfTotalPeersForTest)\n\tfor i := range countOfTotalPeersForTest {\n\n\t\tip := make([]byte, 16)\n\t\tn, err := rand.Reader.Read(ip)\n\t\tif err != nil || n != 16 {\n\t\t\tb.Fatalf(\"Failed to generate IP address\")\n\t\t}\n\n\t\tvar ipString string\n\n\t\tif i%ratioOfIPV6 == 0 {\n\t\t\t// IPv6\n\t\t\tip6 := net.IP(ip)\n\t\t\tipString = \"/ip6/\" + ip6.String()\n\t\t} else {\n\t\t\t// IPv4\n\t\t\tip4 := net.IPv4(ip[0], ip[1], ip[2], ip[3])\n\t\t\tipString = \"/ip4/\" + ip4.String()\n\t\t}\n\n\t\tvar ma multiaddr.Multiaddr\n\t\tif i%ratioOfSpecifiedPeers == 0 {\n\t\t\tma = multiaddr.StringCast(ipString + \"/p2p/\" + test.RandPeerIDFatal(b).String())\n\t\t} else {\n\t\t\tma = multiaddr.StringCast(ipString)\n\t\t}\n\t\tif err != nil {\n\t\t\tb.Fatalf(\"Failed to generate multiaddr: %v\", ipString)\n\t\t}\n\n\t\tmas[i] = ma\n\t}\n\n\tfor _, ma := range mas[:howManyMultiaddrsInAllowList] {\n\t\terr := allowlist.Add(ma)\n\t\tif err != nil {\n\t\t\tb.Fatalf(\"Failed to add multiaddr\")\n\t\t}\n\t}\n\n\tmasInAllowList := mas[:howManyMultiaddrsInAllowList]\n\tmasNotInAllowList := mas[howManyMultiaddrsInAllowList:]\n\n\tb.ResetTimer()\n\tfor n := 0; n < b.N; n++ {\n\t\tif n%ratioOfAllowlistHit == 0 {\n\t\t\tallowlist.Allowed(masInAllowList[n%len(masInAllowList)])\n\t\t} else {\n\t\t\tallowlist.Allowed(masNotInAllowList[n%len(masNotInAllowList)])\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "p2p/host/resource-manager/conn_limiter.go",
    "content": "package rcmgr\n\nimport (\n\t\"math\"\n\t\"net/netip\"\n\t\"slices\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/x/rate\"\n)\n\ntype ConnLimitPerSubnet struct {\n\t// This defines how big the subnet is. For example, a /24 subnet has a\n\t// PrefixLength of 24. All IPs that share the same 24 bit prefix are in the same\n\t// subnet. Are in the same subnet, and bound to the same limit.\n\tPrefixLength int\n\t// The maximum number of connections allowed for each subnet.\n\tConnCount int\n}\n\ntype NetworkPrefixLimit struct {\n\t// The Network prefix for which this limit applies.\n\tNetwork netip.Prefix\n\n\t// The maximum number of connections allowed for this subnet.\n\tConnCount int\n}\n\n// 8 for now so that it matches the number of concurrent dials we may do\n// in swarm_dial.go. With future smart dialing work we should bring this\n// down\nvar defaultMaxConcurrentConns = 8\n\nvar defaultIP4Limit = ConnLimitPerSubnet{\n\tConnCount:    defaultMaxConcurrentConns,\n\tPrefixLength: 32,\n}\nvar defaultIP6Limits = []ConnLimitPerSubnet{\n\t{\n\t\tConnCount:    defaultMaxConcurrentConns,\n\t\tPrefixLength: 56,\n\t},\n\t{\n\t\tConnCount:    8 * defaultMaxConcurrentConns,\n\t\tPrefixLength: 48,\n\t},\n}\n\nvar DefaultNetworkPrefixLimitV4 = sortNetworkPrefixes([]NetworkPrefixLimit{\n\t{\n\t\t// Loopback address for v4 https://datatracker.ietf.org/doc/html/rfc6890#section-2.2.2\n\t\tNetwork:   netip.MustParsePrefix(\"127.0.0.0/8\"),\n\t\tConnCount: math.MaxInt, // Unlimited\n\t},\n})\nvar DefaultNetworkPrefixLimitV6 = sortNetworkPrefixes([]NetworkPrefixLimit{\n\t{\n\t\t// Loopback address for v6 https://datatracker.ietf.org/doc/html/rfc6890#section-2.2.3\n\t\tNetwork:   netip.MustParsePrefix(\"::1/128\"),\n\t\tConnCount: math.MaxInt, // Unlimited\n\t},\n})\n\n// Network prefixes limits must be sorted by most specific to least specific.  This lets us\n// actually use the more specific limits, otherwise only the less specific ones\n// would be matched. e.g. 1.2.3.0/24 must come before 1.2.0.0/16.\nfunc sortNetworkPrefixes(limits []NetworkPrefixLimit) []NetworkPrefixLimit {\n\tslices.SortStableFunc(limits, func(a, b NetworkPrefixLimit) int {\n\t\treturn b.Network.Bits() - a.Network.Bits()\n\t})\n\treturn limits\n}\n\n// WithNetworkPrefixLimit sets the limits for the number of connections allowed\n// for a specific Network Prefix. Use this when you want to set higher limits\n// for a specific subnet than the default limit per subnet.\nfunc WithNetworkPrefixLimit(ipv4 []NetworkPrefixLimit, ipv6 []NetworkPrefixLimit) Option {\n\treturn func(rm *resourceManager) error {\n\t\tif ipv4 != nil {\n\t\t\trm.connLimiter.networkPrefixLimitV4 = sortNetworkPrefixes(ipv4)\n\t\t}\n\t\tif ipv6 != nil {\n\t\t\trm.connLimiter.networkPrefixLimitV6 = sortNetworkPrefixes(ipv6)\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// WithLimitPerSubnet sets the limits for the number of connections allowed per\n// subnet. This will limit the number of connections per subnet if that subnet\n// is not defined in the NetworkPrefixLimit option. Think of this as a default\n// limit for any given subnet.\nfunc WithLimitPerSubnet(ipv4 []ConnLimitPerSubnet, ipv6 []ConnLimitPerSubnet) Option {\n\treturn func(rm *resourceManager) error {\n\t\tif ipv4 != nil {\n\t\t\trm.connLimiter.connLimitPerSubnetV4 = ipv4\n\t\t}\n\t\tif ipv6 != nil {\n\t\t\trm.connLimiter.connLimitPerSubnetV6 = ipv6\n\t\t}\n\t\treturn nil\n\t}\n}\n\ntype connLimiter struct {\n\tmu sync.Mutex\n\n\t// Specific Network Prefix limits. If these are set, they take precedence over the\n\t// subnet limits.\n\t// These must be sorted by most specific to least specific.\n\tnetworkPrefixLimitV4    []NetworkPrefixLimit\n\tnetworkPrefixLimitV6    []NetworkPrefixLimit\n\tconnsPerNetworkPrefixV4 []int\n\tconnsPerNetworkPrefixV6 []int\n\n\t// Subnet limits.\n\tconnLimitPerSubnetV4 []ConnLimitPerSubnet\n\tconnLimitPerSubnetV6 []ConnLimitPerSubnet\n\tip4connsPerLimit     []map[netip.Prefix]int\n\tip6connsPerLimit     []map[netip.Prefix]int\n}\n\nfunc newConnLimiter() *connLimiter {\n\treturn &connLimiter{\n\t\tnetworkPrefixLimitV4: DefaultNetworkPrefixLimitV4,\n\t\tnetworkPrefixLimitV6: DefaultNetworkPrefixLimitV6,\n\n\t\tconnLimitPerSubnetV4: []ConnLimitPerSubnet{defaultIP4Limit},\n\t\tconnLimitPerSubnetV6: defaultIP6Limits,\n\t}\n}\n\nfunc (cl *connLimiter) addNetworkPrefixLimit(isIP6 bool, npLimit NetworkPrefixLimit) {\n\tcl.mu.Lock()\n\tdefer cl.mu.Unlock()\n\tif isIP6 {\n\t\tcl.networkPrefixLimitV6 = append(cl.networkPrefixLimitV6, npLimit)\n\t\tcl.networkPrefixLimitV6 = sortNetworkPrefixes(cl.networkPrefixLimitV6)\n\t} else {\n\t\tcl.networkPrefixLimitV4 = append(cl.networkPrefixLimitV4, npLimit)\n\t\tcl.networkPrefixLimitV4 = sortNetworkPrefixes(cl.networkPrefixLimitV4)\n\t}\n}\n\n// addConn adds a connection for the given IP address. It returns true if the connection is allowed.\nfunc (cl *connLimiter) addConn(ip netip.Addr) bool {\n\tcl.mu.Lock()\n\tdefer cl.mu.Unlock()\n\tnetworkPrefixLimits := cl.networkPrefixLimitV4\n\tconnsPerNetworkPrefix := cl.connsPerNetworkPrefixV4\n\tlimits := cl.connLimitPerSubnetV4\n\tconnsPerLimit := cl.ip4connsPerLimit\n\tisIP6 := ip.Is6()\n\tif isIP6 {\n\t\tnetworkPrefixLimits = cl.networkPrefixLimitV6\n\t\tconnsPerNetworkPrefix = cl.connsPerNetworkPrefixV6\n\t\tlimits = cl.connLimitPerSubnetV6\n\t\tconnsPerLimit = cl.ip6connsPerLimit\n\t}\n\n\t// Check Network Prefix limits first\n\tif len(connsPerNetworkPrefix) == 0 && len(networkPrefixLimits) > 0 {\n\t\t// Initialize the counts\n\t\tconnsPerNetworkPrefix = make([]int, len(networkPrefixLimits))\n\t\tif isIP6 {\n\t\t\tcl.connsPerNetworkPrefixV6 = connsPerNetworkPrefix\n\t\t} else {\n\t\t\tcl.connsPerNetworkPrefixV4 = connsPerNetworkPrefix\n\t\t}\n\t}\n\n\tfor i, limit := range networkPrefixLimits {\n\t\tif limit.Network.Contains(ip) {\n\t\t\tif connsPerNetworkPrefix[i]+1 > limit.ConnCount {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconnsPerNetworkPrefix[i]++\n\t\t\t// Done. If we find a match in the network prefix limits, we use\n\t\t\t// that and don't use the general subnet limits.\n\t\t\treturn true\n\t\t}\n\t}\n\n\tif len(connsPerLimit) == 0 && len(limits) > 0 {\n\t\tconnsPerLimit = make([]map[netip.Prefix]int, len(limits))\n\t\tif isIP6 {\n\t\t\tcl.ip6connsPerLimit = connsPerLimit\n\t\t} else {\n\t\t\tcl.ip4connsPerLimit = connsPerLimit\n\t\t}\n\t}\n\n\tfor i, limit := range limits {\n\t\tprefix, err := ip.Prefix(limit.PrefixLength)\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\t\tcounts, ok := connsPerLimit[i][prefix]\n\t\tif !ok {\n\t\t\tif connsPerLimit[i] == nil {\n\t\t\t\tconnsPerLimit[i] = make(map[netip.Prefix]int)\n\t\t\t}\n\t\t\tconnsPerLimit[i][prefix] = 0\n\t\t}\n\t\tif counts+1 > limit.ConnCount {\n\t\t\treturn false\n\t\t}\n\t}\n\n\t// All limit checks passed, now we update the counts\n\tfor i, limit := range limits {\n\t\tprefix, _ := ip.Prefix(limit.PrefixLength)\n\t\tconnsPerLimit[i][prefix]++\n\t}\n\n\treturn true\n}\n\nfunc (cl *connLimiter) rmConn(ip netip.Addr) {\n\tcl.mu.Lock()\n\tdefer cl.mu.Unlock()\n\tnetworkPrefixLimits := cl.networkPrefixLimitV4\n\tconnsPerNetworkPrefix := cl.connsPerNetworkPrefixV4\n\tlimits := cl.connLimitPerSubnetV4\n\tconnsPerLimit := cl.ip4connsPerLimit\n\tisIP6 := ip.Is6()\n\tif isIP6 {\n\t\tnetworkPrefixLimits = cl.networkPrefixLimitV6\n\t\tconnsPerNetworkPrefix = cl.connsPerNetworkPrefixV6\n\t\tlimits = cl.connLimitPerSubnetV6\n\t\tconnsPerLimit = cl.ip6connsPerLimit\n\t}\n\n\t// Check NetworkPrefix limits first\n\tif len(connsPerNetworkPrefix) == 0 && len(networkPrefixLimits) > 0 {\n\t\t// Initialize just in case. We should have already initialized in\n\t\t// addConn, but if the callers calls rmConn first we don't want to panic\n\t\tconnsPerNetworkPrefix = make([]int, len(networkPrefixLimits))\n\t\tif isIP6 {\n\t\t\tcl.connsPerNetworkPrefixV6 = connsPerNetworkPrefix\n\t\t} else {\n\t\t\tcl.connsPerNetworkPrefixV4 = connsPerNetworkPrefix\n\t\t}\n\t}\n\tfor i, limit := range networkPrefixLimits {\n\t\tif limit.Network.Contains(ip) {\n\t\t\tcount := connsPerNetworkPrefix[i]\n\t\t\tif count <= 0 {\n\t\t\t\tlog.Error(\"unexpected conn count for ip. Was this not added with addConn first?\", \"ip\", ip)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconnsPerNetworkPrefix[i]--\n\t\t\t// Done. We updated the count in the defined network prefix limit.\n\t\t\treturn\n\t\t}\n\t}\n\n\tif len(connsPerLimit) == 0 && len(limits) > 0 {\n\t\t// Initialize just in case. We should have already initialized in\n\t\t// addConn, but if the callers calls rmConn first we don't want to panic\n\t\tconnsPerLimit = make([]map[netip.Prefix]int, len(limits))\n\t\tif isIP6 {\n\t\t\tcl.ip6connsPerLimit = connsPerLimit\n\t\t} else {\n\t\t\tcl.ip4connsPerLimit = connsPerLimit\n\t\t}\n\t}\n\n\tfor i, limit := range limits {\n\t\tprefix, err := ip.Prefix(limit.PrefixLength)\n\t\tif err != nil {\n\t\t\t// Unexpected since we should have seen this IP before in addConn\n\t\t\tlog.Error(\"unexpected error getting prefix\", \"err\", err)\n\t\t\tcontinue\n\t\t}\n\t\tcounts, ok := connsPerLimit[i][prefix]\n\t\tif !ok || counts == 0 {\n\t\t\t// Unexpected, but don't panic\n\t\t\tlog.Error(\"unexpected conn count\", \"prefix\", prefix, \"ok\", ok, \"count\", counts)\n\t\t\tcontinue\n\t\t}\n\t\tconnsPerLimit[i][prefix]--\n\t\tif connsPerLimit[i][prefix] <= 0 {\n\t\t\tdelete(connsPerLimit[i], prefix)\n\t\t}\n\t}\n}\n\n// handshakeDuration is a higher end estimate of QUIC handshake time\nconst handshakeDuration = 5 * time.Second\n\n// sourceAddressRPS is the refill rate for the source address verification rate limiter.\n// A spoofed address if not verified will take a connLimiter token for handshakeDuration.\n// Slow refill rate here favours increasing latency(because of address verification) in\n// exchange for reducing the chances of spoofing successfully causing a DoS.\nconst sourceAddressRPS = float64(1.0*time.Second) / (2 * float64(handshakeDuration))\n\n// newVerifySourceAddressRateLimiter returns a rate limiter for verifying source addresses.\n// The returned limiter allows maxAllowedConns / 2 unverified addresses to begin handshake.\n// This ensures that in the event someone is spoofing IPs, 1/2 the maximum allowed connections\n// will be able to connect, although they will have increased latency because of address\n// verification.\nfunc newVerifySourceAddressRateLimiter(cl *connLimiter) *rate.Limiter {\n\tnetworkPrefixLimits := make([]rate.PrefixLimit, 0, len(cl.networkPrefixLimitV4)+len(cl.networkPrefixLimitV6))\n\tfor _, l := range cl.networkPrefixLimitV4 {\n\t\tnetworkPrefixLimits = append(networkPrefixLimits, rate.PrefixLimit{\n\t\t\tPrefix: l.Network,\n\t\t\tLimit:  rate.Limit{RPS: sourceAddressRPS, Burst: l.ConnCount / 2},\n\t\t})\n\t}\n\tfor _, l := range cl.networkPrefixLimitV6 {\n\t\tnetworkPrefixLimits = append(networkPrefixLimits, rate.PrefixLimit{\n\t\t\tPrefix: l.Network,\n\t\t\tLimit:  rate.Limit{RPS: sourceAddressRPS, Burst: l.ConnCount / 2},\n\t\t})\n\t}\n\n\tipv4SubnetLimits := make([]rate.SubnetLimit, 0, len(cl.connLimitPerSubnetV4))\n\tfor _, l := range cl.connLimitPerSubnetV4 {\n\t\tipv4SubnetLimits = append(ipv4SubnetLimits, rate.SubnetLimit{\n\t\t\tPrefixLength: l.PrefixLength,\n\t\t\tLimit:        rate.Limit{RPS: sourceAddressRPS, Burst: l.ConnCount / 2},\n\t\t})\n\t}\n\n\tipv6SubnetLimits := make([]rate.SubnetLimit, 0, len(cl.connLimitPerSubnetV6))\n\tfor _, l := range cl.connLimitPerSubnetV6 {\n\t\tipv6SubnetLimits = append(ipv6SubnetLimits, rate.SubnetLimit{\n\t\t\tPrefixLength: l.PrefixLength,\n\t\t\tLimit:        rate.Limit{RPS: sourceAddressRPS, Burst: l.ConnCount / 2},\n\t\t})\n\t}\n\n\treturn &rate.Limiter{\n\t\tNetworkPrefixLimits: networkPrefixLimits,\n\t\tSubnetRateLimiter: rate.SubnetLimiter{\n\t\t\tIPv4SubnetLimits: ipv4SubnetLimits,\n\t\t\tIPv6SubnetLimits: ipv6SubnetLimits,\n\t\t\tGracePeriod:      1 * time.Minute,\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "p2p/host/resource-manager/conn_limiter_test.go",
    "content": "package rcmgr\n\nimport (\n\t\"encoding/binary\"\n\t\"net\"\n\t\"net/netip\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/x/rate\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestItLimits(t *testing.T) {\n\tt.Run(\"IPv4\", func(t *testing.T) {\n\t\tip, err := netip.ParseAddr(\"1.2.3.4\")\n\t\trequire.NoError(t, err)\n\t\tcl := newConnLimiter()\n\t\tcl.connLimitPerSubnetV4[0].ConnCount = 1\n\t\trequire.True(t, cl.addConn(ip))\n\n\t\t// should fail the second time\n\t\trequire.False(t, cl.addConn(ip))\n\n\t\totherIP, err := netip.ParseAddr(\"1.2.3.5\")\n\t\trequire.NoError(t, err)\n\t\trequire.True(t, cl.addConn(otherIP))\n\t})\n\n\tt.Run(\"IPv4 removal\", func(t *testing.T) {\n\t\tip, err := netip.ParseAddr(\"1.2.3.4\")\n\t\trequire.NoError(t, err)\n\t\tcl := newConnLimiter()\n\t\tcl.connLimitPerSubnetV4[0].ConnCount = 1\n\t\trequire.True(t, cl.addConn(ip))\n\n\t\t// should fail the second time\n\t\trequire.False(t, cl.addConn(ip))\n\t\t// remove the connection\n\t\tcl.rmConn(ip)\n\t\t// should succeed now\n\t\trequire.True(t, cl.addConn(ip))\n\t})\n\n\tt.Run(\"IPv6\", func(t *testing.T) {\n\t\tip, err := netip.ParseAddr(\"1:2:3:4::1\")\n\t\trequire.NoError(t, err)\n\t\tcl := newConnLimiter()\n\t\toriginal := cl.connLimitPerSubnetV6[0].ConnCount\n\t\tcl.connLimitPerSubnetV6[0].ConnCount = 1\n\t\tdefer func() {\n\t\t\tcl.connLimitPerSubnetV6[0].ConnCount = original\n\t\t}()\n\t\trequire.True(t, cl.addConn(ip))\n\n\t\t// should fail the second time\n\t\trequire.False(t, cl.addConn(ip))\n\t\totherIPSameSubnet := netip.MustParseAddr(\"1:2:3:4::2\")\n\t\trequire.False(t, cl.addConn(otherIPSameSubnet))\n\n\t\totherIP := netip.MustParseAddr(\"2:2:3:4::2\")\n\t\trequire.True(t, cl.addConn(otherIP))\n\t})\n\n\tt.Run(\"IPv6 with multiple limits\", func(t *testing.T) {\n\t\tcl := newConnLimiter()\n\t\tfor i := range defaultMaxConcurrentConns {\n\t\t\tip := net.ParseIP(\"ff:2:3:4::1\")\n\t\t\tbinary.BigEndian.PutUint16(ip[14:], uint16(i))\n\t\t\tipAddr := netip.MustParseAddr(ip.String())\n\t\t\trequire.True(t, cl.addConn(ipAddr))\n\t\t}\n\n\t\t// Next one should fail\n\t\tip := net.ParseIP(\"ff:2:3:4::1\")\n\t\tbinary.BigEndian.PutUint16(ip[14:], uint16(defaultMaxConcurrentConns+1))\n\t\trequire.False(t, cl.addConn(netip.MustParseAddr(ip.String())))\n\n\t\t// But on a different root subnet should work\n\t\totherIP := netip.MustParseAddr(\"ffef:2:3::1\")\n\t\trequire.True(t, cl.addConn(otherIP))\n\n\t\t// But too many on the next subnet limit will fail too\n\t\tfor i := 0; i < defaultMaxConcurrentConns*8; i++ {\n\t\t\tip := net.ParseIP(\"ffef:2:3:4::1\")\n\t\t\tbinary.BigEndian.PutUint16(ip[5:7], uint16(i))\n\t\t\tipAddr := netip.MustParseAddr(ip.String())\n\t\t\trequire.True(t, cl.addConn(ipAddr))\n\t\t}\n\n\t\tip = net.ParseIP(\"ffef:2:3:4::1\")\n\t\tbinary.BigEndian.PutUint16(ip[5:7], uint16(defaultMaxConcurrentConns*8+1))\n\t\tipAddr := netip.MustParseAddr(ip.String())\n\t\trequire.False(t, cl.addConn(ipAddr))\n\t})\n\n\tt.Run(\"IPv4 with localhost\", func(t *testing.T) {\n\t\tcl := &connLimiter{\n\t\t\tnetworkPrefixLimitV4: DefaultNetworkPrefixLimitV4,\n\t\t\tconnLimitPerSubnetV4: []ConnLimitPerSubnet{\n\t\t\t\t{PrefixLength: 0, ConnCount: 1}, // 1 connection for the whole IPv4 space\n\t\t\t},\n\t\t}\n\n\t\tip := netip.MustParseAddr(\"1.2.3.4\")\n\t\trequire.True(t, cl.addConn(ip))\n\n\t\tip = netip.MustParseAddr(\"4.3.2.1\")\n\t\t// should fail the second time, we only allow 1 connection for the whole IPv4 space\n\t\trequire.False(t, cl.addConn(ip))\n\n\t\tip = netip.MustParseAddr(\"127.0.0.1\")\n\t\t// Succeeds because we defined an explicit limit for the loopback subnet\n\t\trequire.True(t, cl.addConn(ip))\n\t})\n}\n\nfunc genIP(data *[]byte) (netip.Addr, bool) {\n\tif len(*data) < 1 {\n\t\treturn netip.Addr{}, false\n\t}\n\n\tgenIP6 := (*data)[0]&0x01 == 1\n\tbytesRequired := 4\n\tif genIP6 {\n\t\tbytesRequired = 16\n\t}\n\n\tif len((*data)[1:]) < bytesRequired {\n\t\treturn netip.Addr{}, false\n\t}\n\n\t*data = (*data)[1:]\n\tip, ok := netip.AddrFromSlice((*data)[:bytesRequired])\n\t*data = (*data)[bytesRequired:]\n\treturn ip, ok\n}\n\nfunc FuzzConnLimiter(f *testing.F) {\n\t// The goal is to try to enter a state where the count is incorrectly 0\n\tf.Fuzz(func(t *testing.T, data []byte) {\n\t\tips := make([]netip.Addr, 0, len(data)/5)\n\t\tfor {\n\t\t\tip, ok := genIP(&data)\n\t\t\tif !ok {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tips = append(ips, ip)\n\t\t}\n\n\t\tcl := newConnLimiter()\n\t\taddedConns := make([]netip.Addr, 0, len(ips))\n\t\tfor _, ip := range ips {\n\t\t\tif cl.addConn(ip) {\n\t\t\t\taddedConns = append(addedConns, ip)\n\t\t\t}\n\t\t}\n\n\t\taddedCount := 0\n\t\tfor _, ip := range cl.ip4connsPerLimit {\n\t\t\tfor _, count := range ip {\n\t\t\t\taddedCount += count\n\t\t\t}\n\t\t}\n\t\tfor _, ip := range cl.ip6connsPerLimit {\n\t\t\tfor _, count := range ip {\n\t\t\t\taddedCount += count\n\t\t\t}\n\t\t}\n\t\tfor _, count := range cl.connsPerNetworkPrefixV4 {\n\t\t\taddedCount += count\n\t\t}\n\t\tfor _, count := range cl.connsPerNetworkPrefixV6 {\n\t\t\taddedCount += count\n\t\t}\n\t\tif addedCount == 0 && len(addedConns) > 0 {\n\t\t\tt.Fatalf(\"added count: %d\", addedCount)\n\t\t}\n\n\t\tfor _, ip := range addedConns {\n\t\t\tcl.rmConn(ip)\n\t\t}\n\n\t\tleftoverCount := 0\n\t\tfor _, ip := range cl.ip4connsPerLimit {\n\t\t\tfor _, count := range ip {\n\t\t\t\tleftoverCount += count\n\t\t\t}\n\t\t}\n\t\tfor _, ip := range cl.ip6connsPerLimit {\n\t\t\tfor _, count := range ip {\n\t\t\t\tleftoverCount += count\n\t\t\t}\n\t\t}\n\t\tfor _, count := range cl.connsPerNetworkPrefixV4 {\n\t\t\taddedCount += count\n\t\t}\n\t\tfor _, count := range cl.connsPerNetworkPrefixV6 {\n\t\t\taddedCount += count\n\t\t}\n\t\tif leftoverCount != 0 {\n\t\t\tt.Fatalf(\"leftover count: %d\", leftoverCount)\n\t\t}\n\t})\n}\n\nfunc TestSortedNetworkPrefixLimits(t *testing.T) {\n\tnpLimits := []NetworkPrefixLimit{\n\t\t{\n\t\t\tNetwork: netip.MustParsePrefix(\"1.2.0.0/16\"),\n\t\t},\n\t\t{\n\t\t\tNetwork: netip.MustParsePrefix(\"1.2.3.0/28\"),\n\t\t},\n\t\t{\n\t\t\tNetwork: netip.MustParsePrefix(\"1.2.3.4/32\"),\n\t\t},\n\t}\n\tnpLimits = sortNetworkPrefixes(npLimits)\n\tsorted := []NetworkPrefixLimit{\n\t\t{\n\t\t\tNetwork: netip.MustParsePrefix(\"1.2.3.4/32\"),\n\t\t},\n\t\t{\n\t\t\tNetwork: netip.MustParsePrefix(\"1.2.3.0/28\"),\n\t\t},\n\t\t{\n\t\t\tNetwork: netip.MustParsePrefix(\"1.2.0.0/16\"),\n\t\t},\n\t}\n\trequire.EqualValues(t, sorted, npLimits)\n}\n\nfunc TestNewVerifySourceAddressRateLimiter(t *testing.T) {\n\ttestCases := []struct {\n\t\tname     string\n\t\tcl       *connLimiter\n\t\texpected *rate.Limiter\n\t}{\n\t\t{\n\t\t\tname: \"basic configuration\",\n\t\t\tcl: &connLimiter{\n\t\t\t\tnetworkPrefixLimitV4: []NetworkPrefixLimit{\n\t\t\t\t\t{\n\t\t\t\t\t\tNetwork:   netip.MustParsePrefix(\"192.168.0.0/16\"),\n\t\t\t\t\t\tConnCount: 10,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tnetworkPrefixLimitV6: []NetworkPrefixLimit{\n\t\t\t\t\t{\n\t\t\t\t\t\tNetwork:   netip.MustParsePrefix(\"2001:db8::/32\"),\n\t\t\t\t\t\tConnCount: 20,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tconnLimitPerSubnetV4: []ConnLimitPerSubnet{\n\t\t\t\t\t{\n\t\t\t\t\t\tPrefixLength: 24,\n\t\t\t\t\t\tConnCount:    5,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tconnLimitPerSubnetV6: []ConnLimitPerSubnet{\n\t\t\t\t\t{\n\t\t\t\t\t\tPrefixLength: 56,\n\t\t\t\t\t\tConnCount:    8,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: &rate.Limiter{\n\t\t\t\tNetworkPrefixLimits: []rate.PrefixLimit{\n\t\t\t\t\t{\n\t\t\t\t\t\tPrefix: netip.MustParsePrefix(\"192.168.0.0/16\"),\n\t\t\t\t\t\tLimit:  rate.Limit{RPS: sourceAddressRPS, Burst: 5},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tPrefix: netip.MustParsePrefix(\"2001:db8::/32\"),\n\t\t\t\t\t\tLimit:  rate.Limit{RPS: sourceAddressRPS, Burst: 10},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tSubnetRateLimiter: rate.SubnetLimiter{\n\t\t\t\t\tIPv4SubnetLimits: []rate.SubnetLimit{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tPrefixLength: 24,\n\t\t\t\t\t\t\tLimit:        rate.Limit{RPS: sourceAddressRPS, Burst: 2},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tIPv6SubnetLimits: []rate.SubnetLimit{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tPrefixLength: 56,\n\t\t\t\t\t\t\tLimit:        rate.Limit{RPS: sourceAddressRPS, Burst: 4},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tGracePeriod: 1 * time.Minute,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"empty configuration\",\n\t\t\tcl:   &connLimiter{},\n\t\t\texpected: &rate.Limiter{\n\t\t\t\tNetworkPrefixLimits: []rate.PrefixLimit{},\n\t\t\t\tSubnetRateLimiter: rate.SubnetLimiter{\n\t\t\t\t\tIPv4SubnetLimits: []rate.SubnetLimit{},\n\t\t\t\t\tIPv6SubnetLimits: []rate.SubnetLimit{},\n\t\t\t\t\tGracePeriod:      1 * time.Minute,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"multiple network prefixes\",\n\t\t\tcl: &connLimiter{\n\t\t\t\tnetworkPrefixLimitV4: []NetworkPrefixLimit{\n\t\t\t\t\t{\n\t\t\t\t\t\tNetwork:   netip.MustParsePrefix(\"192.168.0.0/16\"),\n\t\t\t\t\t\tConnCount: 10,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tNetwork:   netip.MustParsePrefix(\"10.0.0.0/8\"),\n\t\t\t\t\t\tConnCount: 20,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tconnLimitPerSubnetV4: []ConnLimitPerSubnet{\n\t\t\t\t\t{\n\t\t\t\t\t\tPrefixLength: 24,\n\t\t\t\t\t\tConnCount:    5,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tPrefixLength: 16,\n\t\t\t\t\t\tConnCount:    10,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: &rate.Limiter{\n\t\t\t\tNetworkPrefixLimits: []rate.PrefixLimit{\n\t\t\t\t\t{\n\t\t\t\t\t\tPrefix: netip.MustParsePrefix(\"192.168.0.0/16\"),\n\t\t\t\t\t\tLimit:  rate.Limit{RPS: sourceAddressRPS, Burst: 5},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tPrefix: netip.MustParsePrefix(\"10.0.0.0/8\"),\n\t\t\t\t\t\tLimit:  rate.Limit{RPS: sourceAddressRPS, Burst: 10},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tSubnetRateLimiter: rate.SubnetLimiter{\n\t\t\t\t\tIPv4SubnetLimits: []rate.SubnetLimit{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tPrefixLength: 24,\n\t\t\t\t\t\t\tLimit:        rate.Limit{RPS: sourceAddressRPS, Burst: 2},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tPrefixLength: 16,\n\t\t\t\t\t\t\tLimit:        rate.Limit{RPS: sourceAddressRPS, Burst: 5},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tIPv6SubnetLimits: []rate.SubnetLimit{},\n\t\t\t\t\tGracePeriod:      1 * time.Minute,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tactual := newVerifySourceAddressRateLimiter(tc.cl)\n\n\t\t\trequire.Equal(t, len(tc.expected.NetworkPrefixLimits), len(actual.NetworkPrefixLimits))\n\t\t\tfor i, expected := range tc.expected.NetworkPrefixLimits {\n\t\t\t\tactual := actual.NetworkPrefixLimits[i]\n\t\t\t\trequire.Equal(t, expected.Prefix, actual.Prefix)\n\t\t\t\trequire.Equal(t, expected.RPS, actual.RPS)\n\t\t\t\trequire.Equal(t, expected.Burst, actual.Burst)\n\t\t\t}\n\n\t\t\trequire.Equal(t, len(tc.expected.SubnetRateLimiter.IPv4SubnetLimits), len(actual.SubnetRateLimiter.IPv4SubnetLimits))\n\t\t\tfor i, expected := range tc.expected.SubnetRateLimiter.IPv4SubnetLimits {\n\t\t\t\tactual := actual.SubnetRateLimiter.IPv4SubnetLimits[i]\n\t\t\t\trequire.Equal(t, expected.PrefixLength, actual.PrefixLength)\n\t\t\t\trequire.Equal(t, expected.RPS, actual.RPS)\n\t\t\t\trequire.Equal(t, expected.Burst, actual.Burst)\n\t\t\t}\n\n\t\t\trequire.Equal(t, len(tc.expected.SubnetRateLimiter.IPv6SubnetLimits), len(actual.SubnetRateLimiter.IPv6SubnetLimits))\n\t\t\tfor i, expected := range tc.expected.SubnetRateLimiter.IPv6SubnetLimits {\n\t\t\t\tactual := actual.SubnetRateLimiter.IPv6SubnetLimits[i]\n\t\t\t\trequire.Equal(t, expected.PrefixLength, actual.PrefixLength)\n\t\t\t\trequire.Equal(t, expected.RPS, actual.RPS)\n\t\t\t\trequire.Equal(t, expected.Burst, actual.Burst)\n\t\t\t}\n\n\t\t\trequire.Equal(t, tc.expected.SubnetRateLimiter.GracePeriod, actual.SubnetRateLimiter.GracePeriod)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "p2p/host/resource-manager/conn_rate_limiter.go",
    "content": "package rcmgr\n\nimport (\n\t\"net/netip\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/x/rate\"\n)\n\nvar defaultIPv4SubnetLimits = []rate.SubnetLimit{\n\t{\n\t\tPrefixLength: 32,\n\t\tLimit:        rate.Limit{RPS: 0.2, Burst: 2 * defaultMaxConcurrentConns},\n\t},\n}\n\nvar defaultIPv6SubnetLimits = []rate.SubnetLimit{\n\t{\n\t\tPrefixLength: 56,\n\t\tLimit:        rate.Limit{RPS: 0.2, Burst: 2 * defaultMaxConcurrentConns},\n\t},\n\t{\n\t\tPrefixLength: 48,\n\t\tLimit:        rate.Limit{RPS: 0.5, Burst: 10 * defaultMaxConcurrentConns},\n\t},\n}\n\n// defaultNetworkPrefixLimits ensure that all connections on localhost always succeed\nvar defaultNetworkPrefixLimits = []rate.PrefixLimit{\n\t{\n\t\tPrefix: netip.MustParsePrefix(\"127.0.0.0/8\"),\n\t\tLimit:  rate.Limit{},\n\t},\n\t{\n\t\tPrefix: netip.MustParsePrefix(\"::1/128\"),\n\t\tLimit:  rate.Limit{},\n\t},\n}\n\n// WithConnRateLimiters sets a custom rate limiter for new connections.\n// connRateLimiter is used for OpenConnection calls\nfunc WithConnRateLimiters(connRateLimiter *rate.Limiter) Option {\n\treturn func(rm *resourceManager) error {\n\t\trm.connRateLimiter = connRateLimiter\n\t\treturn nil\n\t}\n}\n\nfunc newConnRateLimiter() *rate.Limiter {\n\treturn &rate.Limiter{\n\t\tNetworkPrefixLimits: defaultNetworkPrefixLimits,\n\t\tGlobalLimit:         rate.Limit{},\n\t\tSubnetRateLimiter: rate.SubnetLimiter{\n\t\t\tIPv4SubnetLimits: defaultIPv4SubnetLimits,\n\t\t\tIPv6SubnetLimits: defaultIPv6SubnetLimits,\n\t\t\tGracePeriod:      1 * time.Minute,\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "p2p/host/resource-manager/docs/allowlist.md",
    "content": "# Allowlist\n\nImagine you have a node that is getting overloaded by possibly malicious\nincoming connections. This node won't be able to accept incoming connections\nfrom peers it _knows_ to be good. This node would effectively be _eclipsed_ from\nthe network since no other nodes will be able to connect to it.\n\nThis is the problem that the Allowlist is designed to solve.\n\n## Design Goals\n\n- We should not fail to allocate a resource for an allowlisted peer because the\n  normal transient and system scopes are at their limits. This is the minimum\n  bar to avoid eclipse attacks.\n- Minimal changes to resource manager and existing code (e.g. go-libp2p).\n- The allowlist scope itself is limited to avoid giving an allowlisted peer the\n  ability to DoS a node.\n- PeerIDs can optionally be fed into the allowlist. This will give an extra\n  step of verification before continuing to allow the peer to open streams.\n  - A peer may be able to open a connection, but after the handshake, if it's\n    not an expected peer id we move it to the normal system scope.\n- We can have multiple PeerIDs for a given IP addr.\n- No extra cost for the happy path when we are still below system and transient\n  limits.\n\n## Proposed change\n\nAdd a change to `ResourceManager.OpenConnection` so that it accepts a multiaddr\nparameter of the endpoint the connection is for.\n\nAdd a change to `ResourceManager` to initialize it with a set of allowlisted\nmultiaddrs. This set can be modified at runtime as well for dynamic updating.\n\nFor example, an allowlist set could look like:\n```\n/ip4/1.1.1.1\n/ip6/2345:0425:2CA1::0567:5673:23b5\n/ip4/192.168.1.1/p2p/qmFoo\n/ip4/192.168.1.1/p2p/qmBar\n/ip4/1.2.3.0/ipcidr/24\n```\n\nWhen a new connection is opened, the resource manager tries to allocate with the\nnormal system and transient resource scopes. If that fails, it checks if the\nmultiaddr matches an item in the set of allowlisted multiaddrs. If so, it\ncreates the connection resource scope using the allowlisted specific system and\ntransient resource scopes. If it wasn't an allowlisted multiaddr it fails as\nbefore.\n\nWhen an allowlisted connection is tied to a peer id and transferred with\n`ConnManagementScope.SetPeer`, we check if that peer id matches the expected\nvalue in the allowlist (if it exists). If it does not match, we attempt to\ntransfer this resource to the normal system and peer scope. If that transfer\nfails we close the connection.\n"
  },
  {
    "path": "p2p/host/resource-manager/error.go",
    "content": "package rcmgr\n\nimport (\n\t\"errors\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n)\n\ntype ErrStreamOrConnLimitExceeded struct {\n\tcurrent, attempted, limit int\n\terr                       error\n}\n\nfunc (e *ErrStreamOrConnLimitExceeded) Error() string { return e.err.Error() }\nfunc (e *ErrStreamOrConnLimitExceeded) Unwrap() error { return e.err }\n\n// edge may be \"\" if this is not an edge error\nfunc logValuesStreamLimit(scope, edge string, dir network.Direction, stat network.ScopeStat, err error) []any {\n\tlogValues := make([]any, 0, 2*8)\n\tlogValues = append(logValues, \"scope\", scope)\n\tif edge != \"\" {\n\t\tlogValues = append(logValues, \"edge\", edge)\n\t}\n\tlogValues = append(logValues, \"direction\", dir)\n\tvar e *ErrStreamOrConnLimitExceeded\n\tif errors.As(err, &e) {\n\t\tlogValues = append(logValues,\n\t\t\t\"current\", e.current,\n\t\t\t\"attempted\", e.attempted,\n\t\t\t\"limit\", e.limit,\n\t\t)\n\t}\n\treturn append(logValues, \"stat\", stat, \"error\", err)\n}\n\n// edge may be \"\" if this is not an edge error\nfunc logValuesConnLimit(scope, edge string, dir network.Direction, usefd bool, stat network.ScopeStat, err error) []any {\n\tlogValues := make([]any, 0, 2*9)\n\tlogValues = append(logValues, \"scope\", scope)\n\tif edge != \"\" {\n\t\tlogValues = append(logValues, \"edge\", edge)\n\t}\n\tlogValues = append(logValues, \"direction\", dir, \"usefd\", usefd)\n\tvar e *ErrStreamOrConnLimitExceeded\n\tif errors.As(err, &e) {\n\t\tlogValues = append(logValues,\n\t\t\t\"current\", e.current,\n\t\t\t\"attempted\", e.attempted,\n\t\t\t\"limit\", e.limit,\n\t\t)\n\t}\n\treturn append(logValues, \"stat\", stat, \"error\", err)\n}\n\ntype ErrMemoryLimitExceeded struct {\n\tcurrent, attempted, limit int64\n\tpriority                  uint8\n\terr                       error\n}\n\nfunc (e *ErrMemoryLimitExceeded) Error() string { return e.err.Error() }\nfunc (e *ErrMemoryLimitExceeded) Unwrap() error { return e.err }\n\n// edge may be \"\" if this is not an edge error\nfunc logValuesMemoryLimit(scope, edge string, stat network.ScopeStat, err error) []any {\n\tlogValues := make([]any, 0, 2*8)\n\tlogValues = append(logValues, \"scope\", scope)\n\tif edge != \"\" {\n\t\tlogValues = append(logValues, \"edge\", edge)\n\t}\n\tvar e *ErrMemoryLimitExceeded\n\tif errors.As(err, &e) {\n\t\tlogValues = append(logValues,\n\t\t\t\"current\", e.current,\n\t\t\t\"attempted\", e.attempted,\n\t\t\t\"priority\", e.priority,\n\t\t\t\"limit\", e.limit,\n\t\t)\n\t}\n\treturn append(logValues, \"stat\", stat, \"error\", err)\n}\n"
  },
  {
    "path": "p2p/host/resource-manager/extapi.go",
    "content": "package rcmgr\n\nimport (\n\t\"bytes\"\n\t\"slices\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n)\n\n// ResourceScopeLimiter is a trait interface that allows you to access scope limits.\ntype ResourceScopeLimiter interface {\n\tLimit() Limit\n\tSetLimit(Limit)\n}\n\nvar _ ResourceScopeLimiter = (*resourceScope)(nil)\n\n// ResourceManagerStat is a trait that allows you to access resource manager state.\ntype ResourceManagerState interface {\n\tListServices() []string\n\tListProtocols() []protocol.ID\n\tListPeers() []peer.ID\n\n\tStat() ResourceManagerStat\n}\n\ntype ResourceManagerStat struct {\n\tSystem    network.ScopeStat\n\tTransient network.ScopeStat\n\tServices  map[string]network.ScopeStat\n\tProtocols map[protocol.ID]network.ScopeStat\n\tPeers     map[peer.ID]network.ScopeStat\n}\n\nvar _ ResourceManagerState = (*resourceManager)(nil)\n\nfunc (s *resourceScope) Limit() Limit {\n\ts.Lock()\n\tdefer s.Unlock()\n\n\treturn s.rc.limit\n}\n\nfunc (s *resourceScope) SetLimit(limit Limit) {\n\ts.Lock()\n\tdefer s.Unlock()\n\n\ts.rc.limit = limit\n}\n\nfunc (s *protocolScope) SetLimit(limit Limit) {\n\ts.rcmgr.setStickyProtocol(s.proto)\n\ts.resourceScope.SetLimit(limit)\n}\n\nfunc (s *peerScope) SetLimit(limit Limit) {\n\ts.rcmgr.setStickyPeer(s.peer)\n\ts.resourceScope.SetLimit(limit)\n}\n\nfunc (r *resourceManager) ListServices() []string {\n\tr.mx.Lock()\n\tdefer r.mx.Unlock()\n\n\tresult := make([]string, 0, len(r.svc))\n\tfor svc := range r.svc {\n\t\tresult = append(result, svc)\n\t}\n\n\tsort.Slice(result, func(i, j int) bool {\n\t\treturn strings.Compare(result[i], result[j]) < 0\n\t})\n\n\treturn result\n}\n\nfunc (r *resourceManager) ListProtocols() []protocol.ID {\n\tr.mx.Lock()\n\tdefer r.mx.Unlock()\n\n\tresult := make([]protocol.ID, 0, len(r.proto))\n\tfor p := range r.proto {\n\t\tresult = append(result, p)\n\t}\n\n\tslices.Sort(result)\n\n\treturn result\n}\n\nfunc (r *resourceManager) ListPeers() []peer.ID {\n\tr.mx.Lock()\n\tdefer r.mx.Unlock()\n\n\tresult := make([]peer.ID, 0, len(r.peer))\n\tfor p := range r.peer {\n\t\tresult = append(result, p)\n\t}\n\n\tsort.Slice(result, func(i, j int) bool {\n\t\treturn bytes.Compare([]byte(result[i]), []byte(result[j])) < 0\n\t})\n\n\treturn result\n}\n\nfunc (r *resourceManager) Stat() (result ResourceManagerStat) {\n\tr.mx.Lock()\n\tsvcs := make([]*serviceScope, 0, len(r.svc))\n\tfor _, svc := range r.svc {\n\t\tsvcs = append(svcs, svc)\n\t}\n\tprotos := make([]*protocolScope, 0, len(r.proto))\n\tfor _, proto := range r.proto {\n\t\tprotos = append(protos, proto)\n\t}\n\tpeers := make([]*peerScope, 0, len(r.peer))\n\tfor _, peer := range r.peer {\n\t\tpeers = append(peers, peer)\n\t}\n\tr.mx.Unlock()\n\n\t// Note: there is no global lock, so the system is updating while we are dumping its state...\n\t//       as such stats might not exactly add up to the system level; we take the system stat\n\t//       last nonetheless so that this is the most up-to-date snapshot\n\tresult.Peers = make(map[peer.ID]network.ScopeStat, len(peers))\n\tfor _, peer := range peers {\n\t\tresult.Peers[peer.peer] = peer.Stat()\n\t}\n\tresult.Protocols = make(map[protocol.ID]network.ScopeStat, len(protos))\n\tfor _, proto := range protos {\n\t\tresult.Protocols[proto.proto] = proto.Stat()\n\t}\n\tresult.Services = make(map[string]network.ScopeStat, len(svcs))\n\tfor _, svc := range svcs {\n\t\tresult.Services[svc.service] = svc.Stat()\n\t}\n\tresult.Transient = r.transient.Stat()\n\tresult.System = r.system.Stat()\n\n\treturn result\n}\n\nfunc (r *resourceManager) GetConnLimit() int {\n\treturn r.limits.GetSystemLimits().GetConnTotalLimit()\n}\n"
  },
  {
    "path": "p2p/host/resource-manager/limit.go",
    "content": "/*\nPackage rcmgr is the resource manager for go-libp2p. This allows you to track\nresources being used throughout your go-libp2p process. As well as making sure\nthat the process doesn't use more resources than what you define as your\nlimits. The resource manager only knows about things it is told about, so it's\nthe responsibility of the user of this library (either go-libp2p or a go-libp2p\nuser) to make sure they check with the resource manager before actually\nallocating the resource.\n*/\npackage rcmgr\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n\t\"math\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n)\n\n// Limit is an object that specifies basic resource limits.\ntype Limit interface {\n\t// GetMemoryLimit returns the (current) memory limit.\n\tGetMemoryLimit() int64\n\t// GetStreamLimit returns the stream limit, for inbound or outbound streams.\n\tGetStreamLimit(network.Direction) int\n\t// GetStreamTotalLimit returns the total stream limit\n\tGetStreamTotalLimit() int\n\t// GetConnLimit returns the connection limit, for inbound or outbound connections.\n\tGetConnLimit(network.Direction) int\n\t// GetConnTotalLimit returns the total connection limit\n\tGetConnTotalLimit() int\n\t// GetFDLimit returns the file descriptor limit.\n\tGetFDLimit() int\n}\n\n// Limiter is the interface for providing limits to the resource manager.\ntype Limiter interface {\n\tGetSystemLimits() Limit\n\tGetTransientLimits() Limit\n\tGetAllowlistedSystemLimits() Limit\n\tGetAllowlistedTransientLimits() Limit\n\tGetServiceLimits(svc string) Limit\n\tGetServicePeerLimits(svc string) Limit\n\tGetProtocolLimits(proto protocol.ID) Limit\n\tGetProtocolPeerLimits(proto protocol.ID) Limit\n\tGetPeerLimits(p peer.ID) Limit\n\tGetStreamLimits(p peer.ID) Limit\n\tGetConnLimits() Limit\n}\n\n// NewDefaultLimiterFromJSON creates a new limiter by parsing a json configuration,\n// using the default limits for fallback.\nfunc NewDefaultLimiterFromJSON(in io.Reader) (Limiter, error) {\n\treturn NewLimiterFromJSON(in, DefaultLimits.AutoScale())\n}\n\n// NewLimiterFromJSON creates a new limiter by parsing a json configuration.\nfunc NewLimiterFromJSON(in io.Reader, defaults ConcreteLimitConfig) (Limiter, error) {\n\tcfg, err := readLimiterConfigFromJSON(in, defaults)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &fixedLimiter{cfg}, nil\n}\n\nfunc readLimiterConfigFromJSON(in io.Reader, defaults ConcreteLimitConfig) (ConcreteLimitConfig, error) {\n\tvar cfg PartialLimitConfig\n\tif err := json.NewDecoder(in).Decode(&cfg); err != nil {\n\t\treturn ConcreteLimitConfig{}, err\n\t}\n\treturn cfg.Build(defaults), nil\n}\n\n// fixedLimiter is a limiter with fixed limits.\ntype fixedLimiter struct {\n\tConcreteLimitConfig\n}\n\nvar _ Limiter = (*fixedLimiter)(nil)\n\nfunc NewFixedLimiter(conf ConcreteLimitConfig) Limiter {\n\tlog.Debug(\"initializing new limiter with config\", \"limits\", conf)\n\treturn &fixedLimiter{conf}\n}\n\n// BaseLimit is a mixin type for basic resource limits.\ntype BaseLimit struct {\n\tStreams         int   `json:\",omitempty\"`\n\tStreamsInbound  int   `json:\",omitempty\"`\n\tStreamsOutbound int   `json:\",omitempty\"`\n\tConns           int   `json:\",omitempty\"`\n\tConnsInbound    int   `json:\",omitempty\"`\n\tConnsOutbound   int   `json:\",omitempty\"`\n\tFD              int   `json:\",omitempty\"`\n\tMemory          int64 `json:\",omitempty\"`\n}\n\nfunc valueOrBlockAll(n int) LimitVal {\n\tif n == 0 {\n\t\treturn BlockAllLimit\n\t} else if n == math.MaxInt {\n\t\treturn Unlimited\n\t}\n\treturn LimitVal(n)\n}\nfunc valueOrBlockAll64(n int64) LimitVal64 {\n\tif n == 0 {\n\t\treturn BlockAllLimit64\n\t} else if n == math.MaxInt {\n\t\treturn Unlimited64\n\t}\n\treturn LimitVal64(n)\n}\n\n// ToResourceLimits converts the BaseLimit to a ResourceLimits\nfunc (l BaseLimit) ToResourceLimits() ResourceLimits {\n\treturn ResourceLimits{\n\t\tStreams:         valueOrBlockAll(l.Streams),\n\t\tStreamsInbound:  valueOrBlockAll(l.StreamsInbound),\n\t\tStreamsOutbound: valueOrBlockAll(l.StreamsOutbound),\n\t\tConns:           valueOrBlockAll(l.Conns),\n\t\tConnsInbound:    valueOrBlockAll(l.ConnsInbound),\n\t\tConnsOutbound:   valueOrBlockAll(l.ConnsOutbound),\n\t\tFD:              valueOrBlockAll(l.FD),\n\t\tMemory:          valueOrBlockAll64(l.Memory),\n\t}\n}\n\n// Apply overwrites all zero-valued limits with the values of l2\n// Must not use a pointer receiver.\nfunc (l *BaseLimit) Apply(l2 BaseLimit) {\n\tif l.Streams == 0 {\n\t\tl.Streams = l2.Streams\n\t}\n\tif l.StreamsInbound == 0 {\n\t\tl.StreamsInbound = l2.StreamsInbound\n\t}\n\tif l.StreamsOutbound == 0 {\n\t\tl.StreamsOutbound = l2.StreamsOutbound\n\t}\n\tif l.Conns == 0 {\n\t\tl.Conns = l2.Conns\n\t}\n\tif l.ConnsInbound == 0 {\n\t\tl.ConnsInbound = l2.ConnsInbound\n\t}\n\tif l.ConnsOutbound == 0 {\n\t\tl.ConnsOutbound = l2.ConnsOutbound\n\t}\n\tif l.Memory == 0 {\n\t\tl.Memory = l2.Memory\n\t}\n\tif l.FD == 0 {\n\t\tl.FD = l2.FD\n\t}\n}\n\n// BaseLimitIncrease is the increase per GiB of allowed memory.\ntype BaseLimitIncrease struct {\n\tStreams         int `json:\",omitempty\"`\n\tStreamsInbound  int `json:\",omitempty\"`\n\tStreamsOutbound int `json:\",omitempty\"`\n\tConns           int `json:\",omitempty\"`\n\tConnsInbound    int `json:\",omitempty\"`\n\tConnsOutbound   int `json:\",omitempty\"`\n\t// Memory is in bytes. Values over 1>>30 (1GiB) don't make sense.\n\tMemory int64 `json:\",omitempty\"`\n\t// FDFraction is expected to be >= 0 and <= 1.\n\tFDFraction float64 `json:\",omitempty\"`\n}\n\n// Apply overwrites all zero-valued limits with the values of l2\n// Must not use a pointer receiver.\nfunc (l *BaseLimitIncrease) Apply(l2 BaseLimitIncrease) {\n\tif l.Streams == 0 {\n\t\tl.Streams = l2.Streams\n\t}\n\tif l.StreamsInbound == 0 {\n\t\tl.StreamsInbound = l2.StreamsInbound\n\t}\n\tif l.StreamsOutbound == 0 {\n\t\tl.StreamsOutbound = l2.StreamsOutbound\n\t}\n\tif l.Conns == 0 {\n\t\tl.Conns = l2.Conns\n\t}\n\tif l.ConnsInbound == 0 {\n\t\tl.ConnsInbound = l2.ConnsInbound\n\t}\n\tif l.ConnsOutbound == 0 {\n\t\tl.ConnsOutbound = l2.ConnsOutbound\n\t}\n\tif l.Memory == 0 {\n\t\tl.Memory = l2.Memory\n\t}\n\tif l.FDFraction == 0 {\n\t\tl.FDFraction = l2.FDFraction\n\t}\n}\n\nfunc (l BaseLimit) GetStreamLimit(dir network.Direction) int {\n\tif dir == network.DirInbound {\n\t\treturn l.StreamsInbound\n\t} else {\n\t\treturn l.StreamsOutbound\n\t}\n}\n\nfunc (l BaseLimit) GetStreamTotalLimit() int {\n\treturn l.Streams\n}\n\nfunc (l BaseLimit) GetConnLimit(dir network.Direction) int {\n\tif dir == network.DirInbound {\n\t\treturn l.ConnsInbound\n\t} else {\n\t\treturn l.ConnsOutbound\n\t}\n}\n\nfunc (l BaseLimit) GetConnTotalLimit() int {\n\treturn l.Conns\n}\n\nfunc (l BaseLimit) GetFDLimit() int {\n\treturn l.FD\n}\n\nfunc (l BaseLimit) GetMemoryLimit() int64 {\n\treturn l.Memory\n}\n\nfunc (l *fixedLimiter) GetSystemLimits() Limit {\n\treturn &l.system\n}\n\nfunc (l *fixedLimiter) GetTransientLimits() Limit {\n\treturn &l.transient\n}\n\nfunc (l *fixedLimiter) GetAllowlistedSystemLimits() Limit {\n\treturn &l.allowlistedSystem\n}\n\nfunc (l *fixedLimiter) GetAllowlistedTransientLimits() Limit {\n\treturn &l.allowlistedTransient\n}\n\nfunc (l *fixedLimiter) GetServiceLimits(svc string) Limit {\n\tsl, ok := l.service[svc]\n\tif !ok {\n\t\treturn &l.serviceDefault\n\t}\n\treturn &sl\n}\n\nfunc (l *fixedLimiter) GetServicePeerLimits(svc string) Limit {\n\tpl, ok := l.servicePeer[svc]\n\tif !ok {\n\t\treturn &l.servicePeerDefault\n\t}\n\treturn &pl\n}\n\nfunc (l *fixedLimiter) GetProtocolLimits(proto protocol.ID) Limit {\n\tpl, ok := l.protocol[proto]\n\tif !ok {\n\t\treturn &l.protocolDefault\n\t}\n\treturn &pl\n}\n\nfunc (l *fixedLimiter) GetProtocolPeerLimits(proto protocol.ID) Limit {\n\tpl, ok := l.protocolPeer[proto]\n\tif !ok {\n\t\treturn &l.protocolPeerDefault\n\t}\n\treturn &pl\n}\n\nfunc (l *fixedLimiter) GetPeerLimits(p peer.ID) Limit {\n\tpl, ok := l.peer[p]\n\tif !ok {\n\t\treturn &l.peerDefault\n\t}\n\treturn &pl\n}\n\nfunc (l *fixedLimiter) GetStreamLimits(_ peer.ID) Limit {\n\treturn &l.stream\n}\n\nfunc (l *fixedLimiter) GetConnLimits() Limit {\n\treturn &l.conn\n}\n"
  },
  {
    "path": "p2p/host/resource-manager/limit_config_test.backwards-compat.json",
    "content": "{\n    \"System\": {\n        \"Memory\": 65536,\n        \"Conns\": 16,\n        \"ConnsInbound\": 8,\n        \"ConnsOutbound\": 16,\n        \"FD\": 16\n    },\n    \"ServiceDefault\": {\n        \"Memory\": 8765\n    },\n    \"Service\": {\n        \"A\": {\n            \"Memory\": 8192\n        },\n        \"B\": {}\n    },\n    \"ServicePeerDefault\": {\n        \"Memory\": 2048\n    },\n    \"ServicePeer\": {\n        \"A\": {\n            \"Memory\": 4096\n        }\n    },\n    \"ProtocolDefault\": {\n        \"Memory\": 2048\n    },\n    \"ProtocolPeerDefault\": {\n        \"Memory\": 1024\n    },\n    \"Protocol\": {\n        \"/A\": {\n            \"Memory\": 8192\n        }\n    },\n    \"PeerDefault\": {\n        \"Memory\": 4096\n    },\n    \"Peer\": {\n        \"12D3KooWPFH2Bx2tPfw6RLxN8k2wh47GRXgkt9yrAHU37zFwHWzS\": {\n            \"Memory\": 4097\n        }\n    }\n}"
  },
  {
    "path": "p2p/host/resource-manager/limit_config_test.go",
    "content": "package rcmgr\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc withMemoryLimit(l BaseLimit, m int64) BaseLimit {\n\tl2 := l\n\tl2.Memory = m\n\treturn l2\n}\n\nfunc TestLimitConfigParserBackwardsCompat(t *testing.T) {\n\t// Tests that we can parse the old limit config format.\n\tin, err := os.Open(\"limit_config_test.backwards-compat.json\")\n\trequire.NoError(t, err)\n\tdefer in.Close()\n\n\tdefaultScaledLimits := DefaultLimits\n\tdefaultScaledLimits.AddServiceLimit(\"C\", DefaultLimits.ServiceBaseLimit, BaseLimitIncrease{})\n\tdefaultScaledLimits.AddProtocolPeerLimit(\"C\", DefaultLimits.ServiceBaseLimit, BaseLimitIncrease{})\n\tdefaults := defaultScaledLimits.AutoScale()\n\tcfg, err := readLimiterConfigFromJSON(in, defaults)\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, int64(65536), cfg.system.Memory)\n\trequire.Equal(t, defaults.system.Streams, cfg.system.Streams)\n\trequire.Equal(t, defaults.system.StreamsInbound, cfg.system.StreamsInbound)\n\trequire.Equal(t, defaults.system.StreamsOutbound, cfg.system.StreamsOutbound)\n\trequire.Equal(t, 16, cfg.system.Conns)\n\trequire.Equal(t, 8, cfg.system.ConnsInbound)\n\trequire.Equal(t, 16, cfg.system.ConnsOutbound)\n\trequire.Equal(t, 16, cfg.system.FD)\n\n\trequire.Equal(t, defaults.transient, cfg.transient)\n\trequire.Equal(t, int64(8765), cfg.serviceDefault.Memory)\n\n\trequire.Contains(t, cfg.service, \"A\")\n\trequire.Equal(t, withMemoryLimit(cfg.serviceDefault, 8192), cfg.service[\"A\"])\n\trequire.Contains(t, cfg.service, \"B\")\n\trequire.Equal(t, cfg.serviceDefault, cfg.service[\"B\"])\n\trequire.Contains(t, cfg.service, \"C\")\n\trequire.Equal(t, defaults.service[\"C\"], cfg.service[\"C\"])\n\n\trequire.Equal(t, int64(4096), cfg.peerDefault.Memory)\n\tpeerID, err := peer.Decode(\"12D3KooWPFH2Bx2tPfw6RLxN8k2wh47GRXgkt9yrAHU37zFwHWzS\")\n\trequire.NoError(t, err)\n\trequire.Contains(t, cfg.peer, peerID)\n\trequire.Equal(t, int64(4097), cfg.peer[peerID].Memory)\n}\n\nfunc TestLimitConfigParser(t *testing.T) {\n\tin, err := os.Open(\"limit_config_test.json\")\n\trequire.NoError(t, err)\n\tdefer in.Close()\n\n\tdefaultScaledLimits := DefaultLimits\n\tdefaultScaledLimits.AddServiceLimit(\"C\", DefaultLimits.ServiceBaseLimit, BaseLimitIncrease{})\n\tdefaultScaledLimits.AddProtocolPeerLimit(\"C\", DefaultLimits.ServiceBaseLimit, BaseLimitIncrease{})\n\tdefaults := defaultScaledLimits.AutoScale()\n\tcfg, err := readLimiterConfigFromJSON(in, defaults)\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, int64(65536), cfg.system.Memory)\n\trequire.Equal(t, defaults.system.Streams, cfg.system.Streams)\n\trequire.Equal(t, defaults.system.StreamsInbound, cfg.system.StreamsInbound)\n\trequire.Equal(t, defaults.system.StreamsOutbound, cfg.system.StreamsOutbound)\n\trequire.Equal(t, 16, cfg.system.Conns)\n\trequire.Equal(t, 8, cfg.system.ConnsInbound)\n\trequire.Equal(t, 16, cfg.system.ConnsOutbound)\n\trequire.Equal(t, 16, cfg.system.FD)\n\n\trequire.Equal(t, defaults.transient, cfg.transient)\n\trequire.Equal(t, int64(8765), cfg.serviceDefault.Memory)\n\n\trequire.Contains(t, cfg.service, \"A\")\n\trequire.Equal(t, withMemoryLimit(cfg.serviceDefault, 8192), cfg.service[\"A\"])\n\trequire.Contains(t, cfg.service, \"B\")\n\trequire.Equal(t, cfg.serviceDefault, cfg.service[\"B\"])\n\trequire.Contains(t, cfg.service, \"C\")\n\trequire.Equal(t, defaults.service[\"C\"], cfg.service[\"C\"])\n\n\trequire.Equal(t, int64(4096), cfg.peerDefault.Memory)\n\tpeerID, err := peer.Decode(\"12D3KooWPFH2Bx2tPfw6RLxN8k2wh47GRXgkt9yrAHU37zFwHWzS\")\n\trequire.NoError(t, err)\n\trequire.Contains(t, cfg.peer, peerID)\n\trequire.Equal(t, int64(4097), cfg.peer[peerID].Memory)\n\n\t// Roundtrip\n\tlimitConfig := cfg.ToPartialLimitConfig()\n\tjsonBytes, err := json.Marshal(&limitConfig)\n\trequire.NoError(t, err)\n\tcfgAfterRoundTrip, err := readLimiterConfigFromJSON(bytes.NewReader(jsonBytes), defaults)\n\trequire.NoError(t, err)\n\trequire.Equal(t, limitConfig, cfgAfterRoundTrip.ToPartialLimitConfig())\n}\n\nfunc TestLimitConfigRoundTrip(t *testing.T) {\n\t// Tests that we can roundtrip a PartialLimitConfig to a ConcreteLimitConfig and back.\n\tin, err := os.Open(\"limit_config_test.json\")\n\trequire.NoError(t, err)\n\tdefer in.Close()\n\n\tdefaults := DefaultLimits\n\tdefaults.AddServiceLimit(\"C\", DefaultLimits.ServiceBaseLimit, BaseLimitIncrease{})\n\tdefaults.AddProtocolPeerLimit(\"C\", DefaultLimits.ServiceBaseLimit, BaseLimitIncrease{})\n\tconcreteCfg, err := readLimiterConfigFromJSON(in, defaults.AutoScale())\n\trequire.NoError(t, err)\n\n\t// Roundtrip\n\tlimitConfig := concreteCfg.ToPartialLimitConfig()\n\t// Using InfiniteLimits because it's different then the defaults used above.\n\t// If anything was marked \"default\" in the round trip, it would show up as a\n\t// difference here.\n\tconcreteCfgRT := limitConfig.Build(InfiniteLimits)\n\trequire.Equal(t, concreteCfg, concreteCfgRT)\n}\n\nfunc TestDefaultsDontChange(t *testing.T) {\n\tconcrete := DefaultLimits.Scale(8<<30, 16<<10) // 8GB, 16k fds\n\tjsonBytes, err := json.MarshalIndent(concrete.ToPartialLimitConfig(), \"\", \"  \")\n\trequire.NoError(t, err)\n\n\t// Uncomment to update the defaults file\n\t// err = os.WriteFile(\"limit_config_test_default.json\", jsonBytes, 0644)\n\t// require.NoError(t, err)\n\n\tdefaultsFromFile, err := os.ReadFile(\"limit_config_test_default.json\")\n\trequire.NoError(t, err)\n\n\t// replace crlf with lf because of windows\n\tdefaultsFromFile = bytes.ReplaceAll(defaultsFromFile, []byte(\"\\r\\n\"), []byte(\"\\n\"))\n\tjsonBytes = bytes.ReplaceAll(jsonBytes, []byte(\"\\r\\n\"), []byte(\"\\n\"))\n\n\trequire.Equal(t, string(defaultsFromFile), string(jsonBytes))\n}\n\nfunc TestReadmeLimitConfigSerialization(t *testing.T) {\n\tnoisyNeighbor, _ := peer.Decode(\"QmVvtzcZgCkMnSFf2dnrBPXrWuNFWNM9J3MpZQCvWPuVZf\")\n\tcfg := PartialLimitConfig{\n\t\tSystem: ResourceLimits{\n\t\t\t// Allow unlimited outbound streams\n\t\t\tStreamsOutbound: Unlimited,\n\t\t},\n\t\tPeer: map[peer.ID]ResourceLimits{\n\t\t\tnoisyNeighbor: {\n\t\t\t\t// No inbound connections from this peer\n\t\t\t\tConnsInbound: BlockAllLimit,\n\t\t\t\t// But let me open connections to them\n\t\t\t\tConns:         DefaultLimit,\n\t\t\t\tConnsOutbound: DefaultLimit,\n\t\t\t\t// No inbound streams from this peer\n\t\t\t\tStreamsInbound: BlockAllLimit,\n\t\t\t\t// And let me open unlimited (by me) outbound streams (the peer may have their own limits on me)\n\t\t\t\tStreamsOutbound: Unlimited,\n\t\t\t},\n\t\t},\n\t}\n\tjsonBytes, err := json.Marshal(&cfg)\n\trequire.NoError(t, err)\n\trequire.Equal(t, `{\"Peer\":{\"QmVvtzcZgCkMnSFf2dnrBPXrWuNFWNM9J3MpZQCvWPuVZf\":{\"StreamsInbound\":\"blockAll\",\"StreamsOutbound\":\"unlimited\",\"ConnsInbound\":\"blockAll\"}},\"System\":{\"StreamsOutbound\":\"unlimited\"}}`, string(jsonBytes))\n}\n"
  },
  {
    "path": "p2p/host/resource-manager/limit_config_test.json",
    "content": "{\n    \"System\": {\n        \"Memory\": 65536,\n        \"Conns\": 16,\n        \"ConnsInbound\": 8,\n        \"ConnsOutbound\": 16,\n        \"FD\": 16\n    },\n    \"ServiceDefault\": {\n        \"Memory\": 8765\n    },\n    \"Service\": {\n        \"A\": {\n            \"Memory\": 8192\n        },\n        \"B\": {}\n    },\n    \"ServicePeerDefault\": {\n        \"Memory\": 2048\n    },\n    \"ServicePeer\": {\n        \"A\": {\n            \"Memory\": 4096\n        }\n    },\n    \"ProtocolDefault\": {\n        \"Memory\": 2048\n    },\n    \"ProtocolPeerDefault\": {\n        \"Memory\": 1024\n    },\n    \"Protocol\": {\n        \"/A\": {\n            \"Memory\": 8192\n        }\n    },\n    \"PeerDefault\": {\n        \"Memory\": 4096\n    },\n    \"Peer\": {\n        \"12D3KooWPFH2Bx2tPfw6RLxN8k2wh47GRXgkt9yrAHU37zFwHWzS\": {\n            \"Memory\": 4097\n        }\n    }\n}\n"
  },
  {
    "path": "p2p/host/resource-manager/limit_config_test_default.json",
    "content": "{\n  \"System\": {\n    \"Streams\": 18432,\n    \"StreamsInbound\": 9216,\n    \"StreamsOutbound\": 18432,\n    \"Conns\": 1152,\n    \"ConnsInbound\": 576,\n    \"ConnsOutbound\": 1152,\n    \"FD\": 16384,\n    \"Memory\": \"8724152320\"\n  },\n  \"Transient\": {\n    \"Streams\": 2304,\n    \"StreamsInbound\": 1152,\n    \"StreamsOutbound\": 2304,\n    \"Conns\": 320,\n    \"ConnsInbound\": 160,\n    \"ConnsOutbound\": 320,\n    \"FD\": 4096,\n    \"Memory\": \"1107296256\"\n  },\n  \"AllowlistedSystem\": {\n    \"Streams\": 18432,\n    \"StreamsInbound\": 9216,\n    \"StreamsOutbound\": 18432,\n    \"Conns\": 1152,\n    \"ConnsInbound\": 576,\n    \"ConnsOutbound\": 1152,\n    \"FD\": 16384,\n    \"Memory\": \"8724152320\"\n  },\n  \"AllowlistedTransient\": {\n    \"Streams\": 2304,\n    \"StreamsInbound\": 1152,\n    \"StreamsOutbound\": 2304,\n    \"Conns\": 320,\n    \"ConnsInbound\": 160,\n    \"ConnsOutbound\": 320,\n    \"FD\": 4096,\n    \"Memory\": \"1107296256\"\n  },\n  \"ServiceDefault\": {\n    \"Streams\": 20480,\n    \"StreamsInbound\": 5120,\n    \"StreamsOutbound\": 20480,\n    \"Conns\": \"blockAll\",\n    \"ConnsInbound\": \"blockAll\",\n    \"ConnsOutbound\": \"blockAll\",\n    \"FD\": \"blockAll\",\n    \"Memory\": \"1140850688\"\n  },\n  \"ServicePeerDefault\": {\n    \"Streams\": 320,\n    \"StreamsInbound\": 160,\n    \"StreamsOutbound\": 320,\n    \"Conns\": \"blockAll\",\n    \"ConnsInbound\": \"blockAll\",\n    \"ConnsOutbound\": \"blockAll\",\n    \"FD\": \"blockAll\",\n    \"Memory\": \"50331648\"\n  },\n  \"ProtocolDefault\": {\n    \"Streams\": 6144,\n    \"StreamsInbound\": 2560,\n    \"StreamsOutbound\": 6144,\n    \"Conns\": \"blockAll\",\n    \"ConnsInbound\": \"blockAll\",\n    \"ConnsOutbound\": \"blockAll\",\n    \"FD\": \"blockAll\",\n    \"Memory\": \"1442840576\"\n  },\n  \"ProtocolPeerDefault\": {\n    \"Streams\": 384,\n    \"StreamsInbound\": 96,\n    \"StreamsOutbound\": 192,\n    \"Conns\": \"blockAll\",\n    \"ConnsInbound\": \"blockAll\",\n    \"ConnsOutbound\": \"blockAll\",\n    \"FD\": \"blockAll\",\n    \"Memory\": \"16777248\"\n  },\n  \"PeerDefault\": {\n    \"Streams\": 2560,\n    \"StreamsInbound\": 1280,\n    \"StreamsOutbound\": 2560,\n    \"Conns\": 8,\n    \"ConnsInbound\": 8,\n    \"ConnsOutbound\": 8,\n    \"FD\": 256,\n    \"Memory\": \"1140850688\"\n  },\n  \"Conn\": {\n    \"Streams\": \"blockAll\",\n    \"StreamsInbound\": \"blockAll\",\n    \"StreamsOutbound\": \"blockAll\",\n    \"Conns\": 1,\n    \"ConnsInbound\": 1,\n    \"ConnsOutbound\": 1,\n    \"FD\": 1,\n    \"Memory\": \"33554432\"\n  },\n  \"Stream\": {\n    \"Streams\": 1,\n    \"StreamsInbound\": 1,\n    \"StreamsOutbound\": 1,\n    \"Conns\": \"blockAll\",\n    \"ConnsInbound\": \"blockAll\",\n    \"ConnsOutbound\": \"blockAll\",\n    \"FD\": \"blockAll\",\n    \"Memory\": \"16777216\"\n  }\n}"
  },
  {
    "path": "p2p/host/resource-manager/limit_defaults.go",
    "content": "package rcmgr\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"maps\"\n\t\"math\"\n\t\"strconv\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\n\t\"github.com/pbnjay/memory\"\n)\n\ntype baseLimitConfig struct {\n\tBaseLimit         BaseLimit\n\tBaseLimitIncrease BaseLimitIncrease\n}\n\n// ScalingLimitConfig is a struct for configuring default limits.\n// {}BaseLimit is the limits that Apply for a minimal node (128 MB of memory for libp2p) and 256 file descriptors.\n// {}LimitIncrease is the additional limit granted for every additional 1 GB of RAM.\ntype ScalingLimitConfig struct {\n\tSystemBaseLimit     BaseLimit\n\tSystemLimitIncrease BaseLimitIncrease\n\n\tTransientBaseLimit     BaseLimit\n\tTransientLimitIncrease BaseLimitIncrease\n\n\tAllowlistedSystemBaseLimit     BaseLimit\n\tAllowlistedSystemLimitIncrease BaseLimitIncrease\n\n\tAllowlistedTransientBaseLimit     BaseLimit\n\tAllowlistedTransientLimitIncrease BaseLimitIncrease\n\n\tServiceBaseLimit     BaseLimit\n\tServiceLimitIncrease BaseLimitIncrease\n\tServiceLimits        map[string]baseLimitConfig // use AddServiceLimit to modify\n\n\tServicePeerBaseLimit     BaseLimit\n\tServicePeerLimitIncrease BaseLimitIncrease\n\tServicePeerLimits        map[string]baseLimitConfig // use AddServicePeerLimit to modify\n\n\tProtocolBaseLimit     BaseLimit\n\tProtocolLimitIncrease BaseLimitIncrease\n\tProtocolLimits        map[protocol.ID]baseLimitConfig // use AddProtocolLimit to modify\n\n\tProtocolPeerBaseLimit     BaseLimit\n\tProtocolPeerLimitIncrease BaseLimitIncrease\n\tProtocolPeerLimits        map[protocol.ID]baseLimitConfig // use AddProtocolPeerLimit to modify\n\n\tPeerBaseLimit     BaseLimit\n\tPeerLimitIncrease BaseLimitIncrease\n\tPeerLimits        map[peer.ID]baseLimitConfig // use AddPeerLimit to modify\n\n\tConnBaseLimit     BaseLimit\n\tConnLimitIncrease BaseLimitIncrease\n\n\tStreamBaseLimit     BaseLimit\n\tStreamLimitIncrease BaseLimitIncrease\n}\n\nfunc (cfg *ScalingLimitConfig) AddServiceLimit(svc string, base BaseLimit, inc BaseLimitIncrease) {\n\tif cfg.ServiceLimits == nil {\n\t\tcfg.ServiceLimits = make(map[string]baseLimitConfig)\n\t}\n\tcfg.ServiceLimits[svc] = baseLimitConfig{\n\t\tBaseLimit:         base,\n\t\tBaseLimitIncrease: inc,\n\t}\n}\n\nfunc (cfg *ScalingLimitConfig) AddProtocolLimit(proto protocol.ID, base BaseLimit, inc BaseLimitIncrease) {\n\tif cfg.ProtocolLimits == nil {\n\t\tcfg.ProtocolLimits = make(map[protocol.ID]baseLimitConfig)\n\t}\n\tcfg.ProtocolLimits[proto] = baseLimitConfig{\n\t\tBaseLimit:         base,\n\t\tBaseLimitIncrease: inc,\n\t}\n}\n\nfunc (cfg *ScalingLimitConfig) AddPeerLimit(p peer.ID, base BaseLimit, inc BaseLimitIncrease) {\n\tif cfg.PeerLimits == nil {\n\t\tcfg.PeerLimits = make(map[peer.ID]baseLimitConfig)\n\t}\n\tcfg.PeerLimits[p] = baseLimitConfig{\n\t\tBaseLimit:         base,\n\t\tBaseLimitIncrease: inc,\n\t}\n}\n\nfunc (cfg *ScalingLimitConfig) AddServicePeerLimit(svc string, base BaseLimit, inc BaseLimitIncrease) {\n\tif cfg.ServicePeerLimits == nil {\n\t\tcfg.ServicePeerLimits = make(map[string]baseLimitConfig)\n\t}\n\tcfg.ServicePeerLimits[svc] = baseLimitConfig{\n\t\tBaseLimit:         base,\n\t\tBaseLimitIncrease: inc,\n\t}\n}\n\nfunc (cfg *ScalingLimitConfig) AddProtocolPeerLimit(proto protocol.ID, base BaseLimit, inc BaseLimitIncrease) {\n\tif cfg.ProtocolPeerLimits == nil {\n\t\tcfg.ProtocolPeerLimits = make(map[protocol.ID]baseLimitConfig)\n\t}\n\tcfg.ProtocolPeerLimits[proto] = baseLimitConfig{\n\t\tBaseLimit:         base,\n\t\tBaseLimitIncrease: inc,\n\t}\n}\n\ntype LimitVal int\n\nconst (\n\t// DefaultLimit is the default value for resources. The exact value depends on the context, but will get values from `DefaultLimits`.\n\tDefaultLimit LimitVal = 0\n\t// Unlimited is the value for unlimited resources. An arbitrarily high number will also work.\n\tUnlimited LimitVal = -1\n\t// BlockAllLimit is the LimitVal for allowing no amount of resources.\n\tBlockAllLimit LimitVal = -2\n)\n\nfunc (l LimitVal) MarshalJSON() ([]byte, error) {\n\tif l == Unlimited {\n\t\treturn json.Marshal(\"unlimited\")\n\t} else if l == DefaultLimit {\n\t\treturn json.Marshal(\"default\")\n\t} else if l == BlockAllLimit {\n\t\treturn json.Marshal(\"blockAll\")\n\t}\n\treturn json.Marshal(int(l))\n}\n\nfunc (l *LimitVal) UnmarshalJSON(b []byte) error {\n\tif string(b) == `\"default\"` {\n\t\t*l = DefaultLimit\n\t\treturn nil\n\t} else if string(b) == `\"unlimited\"` {\n\t\t*l = Unlimited\n\t\treturn nil\n\t} else if string(b) == `\"blockAll\"` {\n\t\t*l = BlockAllLimit\n\t\treturn nil\n\t}\n\n\tvar val int\n\tif err := json.Unmarshal(b, &val); err != nil {\n\t\treturn err\n\t}\n\n\tif val == 0 {\n\t\t// If there is an explicit 0 in the JSON we should interpret this as block all.\n\t\t*l = BlockAllLimit\n\t\treturn nil\n\t}\n\n\t*l = LimitVal(val)\n\treturn nil\n}\n\nfunc (l LimitVal) Build(defaultVal int) int {\n\tif l == DefaultLimit {\n\t\treturn defaultVal\n\t}\n\tif l == Unlimited {\n\t\treturn math.MaxInt\n\t}\n\tif l == BlockAllLimit {\n\t\treturn 0\n\t}\n\treturn int(l)\n}\n\ntype LimitVal64 int64\n\nconst (\n\t// Default is the default value for resources.\n\tDefaultLimit64 LimitVal64 = 0\n\t// Unlimited is the value for unlimited resources.\n\tUnlimited64 LimitVal64 = -1\n\t// BlockAllLimit64 is the LimitVal for allowing no amount of resources.\n\tBlockAllLimit64 LimitVal64 = -2\n)\n\nfunc (l LimitVal64) MarshalJSON() ([]byte, error) {\n\tif l == Unlimited64 {\n\t\treturn json.Marshal(\"unlimited\")\n\t} else if l == DefaultLimit64 {\n\t\treturn json.Marshal(\"default\")\n\t} else if l == BlockAllLimit64 {\n\t\treturn json.Marshal(\"blockAll\")\n\t}\n\n\t// Convert this to a string because JSON doesn't support 64-bit integers.\n\treturn json.Marshal(strconv.FormatInt(int64(l), 10))\n}\n\nfunc (l *LimitVal64) UnmarshalJSON(b []byte) error {\n\tif string(b) == `\"default\"` {\n\t\t*l = DefaultLimit64\n\t\treturn nil\n\t} else if string(b) == `\"unlimited\"` {\n\t\t*l = Unlimited64\n\t\treturn nil\n\t} else if string(b) == `\"blockAll\"` {\n\t\t*l = BlockAllLimit64\n\t\treturn nil\n\t}\n\n\tvar val string\n\tif err := json.Unmarshal(b, &val); err != nil {\n\t\t// Is this an integer? Possible because of backwards compatibility.\n\t\tvar val int\n\t\tif err := json.Unmarshal(b, &val); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to unmarshal limit value: %w\", err)\n\t\t}\n\n\t\tif val == 0 {\n\t\t\t// If there is an explicit 0 in the JSON we should interpret this as block all.\n\t\t\t*l = BlockAllLimit64\n\t\t\treturn nil\n\t\t}\n\n\t\t*l = LimitVal64(val)\n\t\treturn nil\n\t}\n\n\ti, err := strconv.ParseInt(val, 10, 64)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif i == 0 {\n\t\t// If there is an explicit 0 in the JSON we should interpret this as block all.\n\t\t*l = BlockAllLimit64\n\t\treturn nil\n\t}\n\n\t*l = LimitVal64(i)\n\treturn nil\n}\n\nfunc (l LimitVal64) Build(defaultVal int64) int64 {\n\tif l == DefaultLimit64 {\n\t\treturn defaultVal\n\t}\n\tif l == Unlimited64 {\n\t\treturn math.MaxInt64\n\t}\n\tif l == BlockAllLimit64 {\n\t\treturn 0\n\t}\n\treturn int64(l)\n}\n\n// ResourceLimits is the type for basic resource limits.\ntype ResourceLimits struct {\n\tStreams         LimitVal   `json:\",omitempty\"`\n\tStreamsInbound  LimitVal   `json:\",omitempty\"`\n\tStreamsOutbound LimitVal   `json:\",omitempty\"`\n\tConns           LimitVal   `json:\",omitempty\"`\n\tConnsInbound    LimitVal   `json:\",omitempty\"`\n\tConnsOutbound   LimitVal   `json:\",omitempty\"`\n\tFD              LimitVal   `json:\",omitempty\"`\n\tMemory          LimitVal64 `json:\",omitempty\"`\n}\n\nfunc (l *ResourceLimits) IsDefault() bool {\n\tif l == nil {\n\t\treturn true\n\t}\n\n\tif l.Streams == DefaultLimit &&\n\t\tl.StreamsInbound == DefaultLimit &&\n\t\tl.StreamsOutbound == DefaultLimit &&\n\t\tl.Conns == DefaultLimit &&\n\t\tl.ConnsInbound == DefaultLimit &&\n\t\tl.ConnsOutbound == DefaultLimit &&\n\t\tl.FD == DefaultLimit &&\n\t\tl.Memory == DefaultLimit64 {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc (l *ResourceLimits) ToMaybeNilPtr() *ResourceLimits {\n\tif l.IsDefault() {\n\t\treturn nil\n\t}\n\treturn l\n}\n\n// Apply overwrites all default limits with the values of l2\nfunc (l *ResourceLimits) Apply(l2 ResourceLimits) {\n\tif l.Streams == DefaultLimit {\n\t\tl.Streams = l2.Streams\n\t}\n\tif l.StreamsInbound == DefaultLimit {\n\t\tl.StreamsInbound = l2.StreamsInbound\n\t}\n\tif l.StreamsOutbound == DefaultLimit {\n\t\tl.StreamsOutbound = l2.StreamsOutbound\n\t}\n\tif l.Conns == DefaultLimit {\n\t\tl.Conns = l2.Conns\n\t}\n\tif l.ConnsInbound == DefaultLimit {\n\t\tl.ConnsInbound = l2.ConnsInbound\n\t}\n\tif l.ConnsOutbound == DefaultLimit {\n\t\tl.ConnsOutbound = l2.ConnsOutbound\n\t}\n\tif l.FD == DefaultLimit {\n\t\tl.FD = l2.FD\n\t}\n\tif l.Memory == DefaultLimit64 {\n\t\tl.Memory = l2.Memory\n\t}\n}\n\nfunc (l *ResourceLimits) Build(defaults Limit) BaseLimit {\n\tif l == nil {\n\t\treturn BaseLimit{\n\t\t\tStreams:         defaults.GetStreamTotalLimit(),\n\t\t\tStreamsInbound:  defaults.GetStreamLimit(network.DirInbound),\n\t\t\tStreamsOutbound: defaults.GetStreamLimit(network.DirOutbound),\n\t\t\tConns:           defaults.GetConnTotalLimit(),\n\t\t\tConnsInbound:    defaults.GetConnLimit(network.DirInbound),\n\t\t\tConnsOutbound:   defaults.GetConnLimit(network.DirOutbound),\n\t\t\tFD:              defaults.GetFDLimit(),\n\t\t\tMemory:          defaults.GetMemoryLimit(),\n\t\t}\n\t}\n\n\treturn BaseLimit{\n\t\tStreams:         l.Streams.Build(defaults.GetStreamTotalLimit()),\n\t\tStreamsInbound:  l.StreamsInbound.Build(defaults.GetStreamLimit(network.DirInbound)),\n\t\tStreamsOutbound: l.StreamsOutbound.Build(defaults.GetStreamLimit(network.DirOutbound)),\n\t\tConns:           l.Conns.Build(defaults.GetConnTotalLimit()),\n\t\tConnsInbound:    l.ConnsInbound.Build(defaults.GetConnLimit(network.DirInbound)),\n\t\tConnsOutbound:   l.ConnsOutbound.Build(defaults.GetConnLimit(network.DirOutbound)),\n\t\tFD:              l.FD.Build(defaults.GetFDLimit()),\n\t\tMemory:          l.Memory.Build(defaults.GetMemoryLimit()),\n\t}\n}\n\ntype PartialLimitConfig struct {\n\tSystem    ResourceLimits\n\tTransient ResourceLimits\n\n\t// Limits that are applied to resources with an allowlisted multiaddr.\n\t// These will only be used if the normal System & Transient limits are\n\t// reached.\n\tAllowlistedSystem    ResourceLimits\n\tAllowlistedTransient ResourceLimits\n\n\tServiceDefault ResourceLimits\n\tService        map[string]ResourceLimits `json:\",omitempty\"`\n\n\tServicePeerDefault ResourceLimits\n\tServicePeer        map[string]ResourceLimits `json:\",omitempty\"`\n\n\tProtocolDefault ResourceLimits\n\tProtocol        map[protocol.ID]ResourceLimits `json:\",omitempty\"`\n\n\tProtocolPeerDefault ResourceLimits\n\tProtocolPeer        map[protocol.ID]ResourceLimits `json:\",omitempty\"`\n\n\tPeerDefault ResourceLimits\n\tPeer        map[peer.ID]ResourceLimits `json:\",omitempty\"`\n\n\tConn   ResourceLimits\n\tStream ResourceLimits\n}\n\nfunc (cfg *PartialLimitConfig) MarshalJSON() ([]byte, error) {\n\t// we want to marshal the encoded peer id\n\tencodedPeerMap := make(map[string]ResourceLimits, len(cfg.Peer))\n\tfor p, v := range cfg.Peer {\n\t\tencodedPeerMap[p.String()] = v\n\t}\n\n\ttype Alias PartialLimitConfig\n\treturn json.Marshal(&struct {\n\t\t*Alias\n\t\t// String so we can have the properly marshalled peer id\n\t\tPeer map[string]ResourceLimits `json:\",omitempty\"`\n\n\t\t// The rest of the fields as pointers so that we omit empty values in the serialized result\n\t\tSystem               *ResourceLimits `json:\",omitempty\"`\n\t\tTransient            *ResourceLimits `json:\",omitempty\"`\n\t\tAllowlistedSystem    *ResourceLimits `json:\",omitempty\"`\n\t\tAllowlistedTransient *ResourceLimits `json:\",omitempty\"`\n\n\t\tServiceDefault *ResourceLimits `json:\",omitempty\"`\n\n\t\tServicePeerDefault *ResourceLimits `json:\",omitempty\"`\n\n\t\tProtocolDefault *ResourceLimits `json:\",omitempty\"`\n\n\t\tProtocolPeerDefault *ResourceLimits `json:\",omitempty\"`\n\n\t\tPeerDefault *ResourceLimits `json:\",omitempty\"`\n\n\t\tConn   *ResourceLimits `json:\",omitempty\"`\n\t\tStream *ResourceLimits `json:\",omitempty\"`\n\t}{\n\t\tAlias: (*Alias)(cfg),\n\t\tPeer:  encodedPeerMap,\n\n\t\tSystem:               cfg.System.ToMaybeNilPtr(),\n\t\tTransient:            cfg.Transient.ToMaybeNilPtr(),\n\t\tAllowlistedSystem:    cfg.AllowlistedSystem.ToMaybeNilPtr(),\n\t\tAllowlistedTransient: cfg.AllowlistedTransient.ToMaybeNilPtr(),\n\t\tServiceDefault:       cfg.ServiceDefault.ToMaybeNilPtr(),\n\t\tServicePeerDefault:   cfg.ServicePeerDefault.ToMaybeNilPtr(),\n\t\tProtocolDefault:      cfg.ProtocolDefault.ToMaybeNilPtr(),\n\t\tProtocolPeerDefault:  cfg.ProtocolPeerDefault.ToMaybeNilPtr(),\n\t\tPeerDefault:          cfg.PeerDefault.ToMaybeNilPtr(),\n\t\tConn:                 cfg.Conn.ToMaybeNilPtr(),\n\t\tStream:               cfg.Stream.ToMaybeNilPtr(),\n\t})\n}\n\nfunc applyResourceLimitsMap[K comparable](this *map[K]ResourceLimits, other map[K]ResourceLimits, fallbackDefault ResourceLimits) {\n\tfor k, l := range *this {\n\t\tr := fallbackDefault\n\t\tif l2, ok := other[k]; ok {\n\t\t\tr = l2\n\t\t}\n\t\tl.Apply(r)\n\t\t(*this)[k] = l\n\t}\n\tif *this == nil && other != nil {\n\t\t*this = make(map[K]ResourceLimits)\n\t}\n\tfor k, l := range other {\n\t\tif _, ok := (*this)[k]; !ok {\n\t\t\t(*this)[k] = l\n\t\t}\n\t}\n}\n\nfunc (cfg *PartialLimitConfig) Apply(c PartialLimitConfig) {\n\tcfg.System.Apply(c.System)\n\tcfg.Transient.Apply(c.Transient)\n\tcfg.AllowlistedSystem.Apply(c.AllowlistedSystem)\n\tcfg.AllowlistedTransient.Apply(c.AllowlistedTransient)\n\tcfg.ServiceDefault.Apply(c.ServiceDefault)\n\tcfg.ServicePeerDefault.Apply(c.ServicePeerDefault)\n\tcfg.ProtocolDefault.Apply(c.ProtocolDefault)\n\tcfg.ProtocolPeerDefault.Apply(c.ProtocolPeerDefault)\n\tcfg.PeerDefault.Apply(c.PeerDefault)\n\tcfg.Conn.Apply(c.Conn)\n\tcfg.Stream.Apply(c.Stream)\n\n\tapplyResourceLimitsMap(&cfg.Service, c.Service, cfg.ServiceDefault)\n\tapplyResourceLimitsMap(&cfg.ServicePeer, c.ServicePeer, cfg.ServicePeerDefault)\n\tapplyResourceLimitsMap(&cfg.Protocol, c.Protocol, cfg.ProtocolDefault)\n\tapplyResourceLimitsMap(&cfg.ProtocolPeer, c.ProtocolPeer, cfg.ProtocolPeerDefault)\n\tapplyResourceLimitsMap(&cfg.Peer, c.Peer, cfg.PeerDefault)\n}\n\nfunc (cfg PartialLimitConfig) Build(defaults ConcreteLimitConfig) ConcreteLimitConfig {\n\tout := defaults\n\n\tout.system = cfg.System.Build(defaults.system)\n\tout.transient = cfg.Transient.Build(defaults.transient)\n\tout.allowlistedSystem = cfg.AllowlistedSystem.Build(defaults.allowlistedSystem)\n\tout.allowlistedTransient = cfg.AllowlistedTransient.Build(defaults.allowlistedTransient)\n\tout.serviceDefault = cfg.ServiceDefault.Build(defaults.serviceDefault)\n\tout.servicePeerDefault = cfg.ServicePeerDefault.Build(defaults.servicePeerDefault)\n\tout.protocolDefault = cfg.ProtocolDefault.Build(defaults.protocolDefault)\n\tout.protocolPeerDefault = cfg.ProtocolPeerDefault.Build(defaults.protocolPeerDefault)\n\tout.peerDefault = cfg.PeerDefault.Build(defaults.peerDefault)\n\tout.conn = cfg.Conn.Build(defaults.conn)\n\tout.stream = cfg.Stream.Build(defaults.stream)\n\n\tout.service = buildMapWithDefault(cfg.Service, defaults.service, out.serviceDefault)\n\tout.servicePeer = buildMapWithDefault(cfg.ServicePeer, defaults.servicePeer, out.servicePeerDefault)\n\tout.protocol = buildMapWithDefault(cfg.Protocol, defaults.protocol, out.protocolDefault)\n\tout.protocolPeer = buildMapWithDefault(cfg.ProtocolPeer, defaults.protocolPeer, out.protocolPeerDefault)\n\tout.peer = buildMapWithDefault(cfg.Peer, defaults.peer, out.peerDefault)\n\n\treturn out\n}\n\nfunc buildMapWithDefault[K comparable](definedLimits map[K]ResourceLimits, defaults map[K]BaseLimit, fallbackDefault BaseLimit) map[K]BaseLimit {\n\tif definedLimits == nil && defaults == nil {\n\t\treturn nil\n\t}\n\n\tout := make(map[K]BaseLimit)\n\tmaps.Copy(out, defaults)\n\n\tfor k, l := range definedLimits {\n\t\tif defaultForKey, ok := out[k]; ok {\n\t\t\tout[k] = l.Build(defaultForKey)\n\t\t} else {\n\t\t\tout[k] = l.Build(fallbackDefault)\n\t\t}\n\t}\n\n\treturn out\n}\n\n// ConcreteLimitConfig is similar to PartialLimitConfig, but all values are defined.\n// There is no unset \"default\" value. Commonly constructed by calling\n// PartialLimitConfig.Build(rcmgr.DefaultLimits.AutoScale())\ntype ConcreteLimitConfig struct {\n\tsystem    BaseLimit\n\ttransient BaseLimit\n\n\t// Limits that are applied to resources with an allowlisted multiaddr.\n\t// These will only be used if the normal System & Transient limits are\n\t// reached.\n\tallowlistedSystem    BaseLimit\n\tallowlistedTransient BaseLimit\n\n\tserviceDefault BaseLimit\n\tservice        map[string]BaseLimit\n\n\tservicePeerDefault BaseLimit\n\tservicePeer        map[string]BaseLimit\n\n\tprotocolDefault BaseLimit\n\tprotocol        map[protocol.ID]BaseLimit\n\n\tprotocolPeerDefault BaseLimit\n\tprotocolPeer        map[protocol.ID]BaseLimit\n\n\tpeerDefault BaseLimit\n\tpeer        map[peer.ID]BaseLimit\n\n\tconn   BaseLimit\n\tstream BaseLimit\n}\n\nfunc resourceLimitsMapFromBaseLimitMap[K comparable](baseLimitMap map[K]BaseLimit) map[K]ResourceLimits {\n\tif baseLimitMap == nil {\n\t\treturn nil\n\t}\n\n\tout := make(map[K]ResourceLimits)\n\tfor k, l := range baseLimitMap {\n\t\tout[k] = l.ToResourceLimits()\n\t}\n\n\treturn out\n}\n\n// ToPartialLimitConfig converts a ConcreteLimitConfig to a PartialLimitConfig.\n// The returned PartialLimitConfig will have no default values.\nfunc (cfg ConcreteLimitConfig) ToPartialLimitConfig() PartialLimitConfig {\n\treturn PartialLimitConfig{\n\t\tSystem:               cfg.system.ToResourceLimits(),\n\t\tTransient:            cfg.transient.ToResourceLimits(),\n\t\tAllowlistedSystem:    cfg.allowlistedSystem.ToResourceLimits(),\n\t\tAllowlistedTransient: cfg.allowlistedTransient.ToResourceLimits(),\n\t\tServiceDefault:       cfg.serviceDefault.ToResourceLimits(),\n\t\tService:              resourceLimitsMapFromBaseLimitMap(cfg.service),\n\t\tServicePeerDefault:   cfg.servicePeerDefault.ToResourceLimits(),\n\t\tServicePeer:          resourceLimitsMapFromBaseLimitMap(cfg.servicePeer),\n\t\tProtocolDefault:      cfg.protocolDefault.ToResourceLimits(),\n\t\tProtocol:             resourceLimitsMapFromBaseLimitMap(cfg.protocol),\n\t\tProtocolPeerDefault:  cfg.protocolPeerDefault.ToResourceLimits(),\n\t\tProtocolPeer:         resourceLimitsMapFromBaseLimitMap(cfg.protocolPeer),\n\t\tPeerDefault:          cfg.peerDefault.ToResourceLimits(),\n\t\tPeer:                 resourceLimitsMapFromBaseLimitMap(cfg.peer),\n\t\tConn:                 cfg.conn.ToResourceLimits(),\n\t\tStream:               cfg.stream.ToResourceLimits(),\n\t}\n}\n\n// Scale scales up a limit configuration.\n// memory is the amount of memory that the stack is allowed to consume,\n// for a dedicated node it's recommended to use 1/8 of the installed system memory.\n// If memory is smaller than 128 MB, the base configuration will be used.\nfunc (cfg *ScalingLimitConfig) Scale(memory int64, numFD int) ConcreteLimitConfig {\n\tlc := ConcreteLimitConfig{\n\t\tsystem:               scale(cfg.SystemBaseLimit, cfg.SystemLimitIncrease, memory, numFD),\n\t\ttransient:            scale(cfg.TransientBaseLimit, cfg.TransientLimitIncrease, memory, numFD),\n\t\tallowlistedSystem:    scale(cfg.AllowlistedSystemBaseLimit, cfg.AllowlistedSystemLimitIncrease, memory, numFD),\n\t\tallowlistedTransient: scale(cfg.AllowlistedTransientBaseLimit, cfg.AllowlistedTransientLimitIncrease, memory, numFD),\n\t\tserviceDefault:       scale(cfg.ServiceBaseLimit, cfg.ServiceLimitIncrease, memory, numFD),\n\t\tservicePeerDefault:   scale(cfg.ServicePeerBaseLimit, cfg.ServicePeerLimitIncrease, memory, numFD),\n\t\tprotocolDefault:      scale(cfg.ProtocolBaseLimit, cfg.ProtocolLimitIncrease, memory, numFD),\n\t\tprotocolPeerDefault:  scale(cfg.ProtocolPeerBaseLimit, cfg.ProtocolPeerLimitIncrease, memory, numFD),\n\t\tpeerDefault:          scale(cfg.PeerBaseLimit, cfg.PeerLimitIncrease, memory, numFD),\n\t\tconn:                 scale(cfg.ConnBaseLimit, cfg.ConnLimitIncrease, memory, numFD),\n\t\tstream:               scale(cfg.StreamBaseLimit, cfg.ConnLimitIncrease, memory, numFD),\n\t}\n\tif cfg.ServiceLimits != nil {\n\t\tlc.service = make(map[string]BaseLimit)\n\t\tfor svc, l := range cfg.ServiceLimits {\n\t\t\tlc.service[svc] = scale(l.BaseLimit, l.BaseLimitIncrease, memory, numFD)\n\t\t}\n\t}\n\tif cfg.ProtocolLimits != nil {\n\t\tlc.protocol = make(map[protocol.ID]BaseLimit)\n\t\tfor proto, l := range cfg.ProtocolLimits {\n\t\t\tlc.protocol[proto] = scale(l.BaseLimit, l.BaseLimitIncrease, memory, numFD)\n\t\t}\n\t}\n\tif cfg.PeerLimits != nil {\n\t\tlc.peer = make(map[peer.ID]BaseLimit)\n\t\tfor p, l := range cfg.PeerLimits {\n\t\t\tlc.peer[p] = scale(l.BaseLimit, l.BaseLimitIncrease, memory, numFD)\n\t\t}\n\t}\n\tif cfg.ServicePeerLimits != nil {\n\t\tlc.servicePeer = make(map[string]BaseLimit)\n\t\tfor svc, l := range cfg.ServicePeerLimits {\n\t\t\tlc.servicePeer[svc] = scale(l.BaseLimit, l.BaseLimitIncrease, memory, numFD)\n\t\t}\n\t}\n\tif cfg.ProtocolPeerLimits != nil {\n\t\tlc.protocolPeer = make(map[protocol.ID]BaseLimit)\n\t\tfor p, l := range cfg.ProtocolPeerLimits {\n\t\t\tlc.protocolPeer[p] = scale(l.BaseLimit, l.BaseLimitIncrease, memory, numFD)\n\t\t}\n\t}\n\treturn lc\n}\n\nfunc (cfg *ScalingLimitConfig) AutoScale() ConcreteLimitConfig {\n\treturn cfg.Scale(\n\t\tint64(memory.TotalMemory())/8,\n\t\tgetNumFDs()/2,\n\t)\n}\n\nfunc scale(base BaseLimit, inc BaseLimitIncrease, memory int64, numFD int) BaseLimit {\n\t// mebibytesAvailable represents how many MiBs we're allowed to use. Used to\n\t// scale the limits. If this is below 128MiB we set it to 0 to just use the\n\t// base amounts.\n\tvar mebibytesAvailable int\n\tif memory > 128<<20 {\n\t\tmebibytesAvailable = int((memory) >> 20)\n\t}\n\tl := BaseLimit{\n\t\tStreamsInbound:  base.StreamsInbound + (inc.StreamsInbound*mebibytesAvailable)>>10,\n\t\tStreamsOutbound: base.StreamsOutbound + (inc.StreamsOutbound*mebibytesAvailable)>>10,\n\t\tStreams:         base.Streams + (inc.Streams*mebibytesAvailable)>>10,\n\t\tConnsInbound:    base.ConnsInbound + (inc.ConnsInbound*mebibytesAvailable)>>10,\n\t\tConnsOutbound:   base.ConnsOutbound + (inc.ConnsOutbound*mebibytesAvailable)>>10,\n\t\tConns:           base.Conns + (inc.Conns*mebibytesAvailable)>>10,\n\t\tMemory:          base.Memory + (inc.Memory*int64(mebibytesAvailable))>>10,\n\t\tFD:              base.FD,\n\t}\n\tif inc.FDFraction > 0 && numFD > 0 {\n\t\tl.FD = max(int(inc.FDFraction*float64(numFD)),\n\t\t\t// Use at least the base amount\n\t\t\tbase.FD)\n\t}\n\treturn l\n}\n\n// DefaultLimits are the limits used by the default limiter constructors.\nvar DefaultLimits = ScalingLimitConfig{\n\tSystemBaseLimit: BaseLimit{\n\t\tConnsInbound:    64,\n\t\tConnsOutbound:   128,\n\t\tConns:           128,\n\t\tStreamsInbound:  64 * 16,\n\t\tStreamsOutbound: 128 * 16,\n\t\tStreams:         128 * 16,\n\t\tMemory:          128 << 20,\n\t\tFD:              256,\n\t},\n\n\tSystemLimitIncrease: BaseLimitIncrease{\n\t\tConnsInbound:    64,\n\t\tConnsOutbound:   128,\n\t\tConns:           128,\n\t\tStreamsInbound:  64 * 16,\n\t\tStreamsOutbound: 128 * 16,\n\t\tStreams:         128 * 16,\n\t\tMemory:          1 << 30,\n\t\tFDFraction:      1,\n\t},\n\n\tTransientBaseLimit: BaseLimit{\n\t\tConnsInbound:    32,\n\t\tConnsOutbound:   64,\n\t\tConns:           64,\n\t\tStreamsInbound:  128,\n\t\tStreamsOutbound: 256,\n\t\tStreams:         256,\n\t\tMemory:          32 << 20,\n\t\tFD:              64,\n\t},\n\n\tTransientLimitIncrease: BaseLimitIncrease{\n\t\tConnsInbound:    16,\n\t\tConnsOutbound:   32,\n\t\tConns:           32,\n\t\tStreamsInbound:  128,\n\t\tStreamsOutbound: 256,\n\t\tStreams:         256,\n\t\tMemory:          128 << 20,\n\t\tFDFraction:      0.25,\n\t},\n\n\t// Setting the allowlisted limits to be the same as the normal limits. The\n\t// allowlist only activates when you reach your normal system/transient\n\t// limits. So it's okay if these limits err on the side of being too big,\n\t// since most of the time you won't even use any of these. Tune these down\n\t// if you want to manage your resources against an allowlisted endpoint.\n\tAllowlistedSystemBaseLimit: BaseLimit{\n\t\tConnsInbound:    64,\n\t\tConnsOutbound:   128,\n\t\tConns:           128,\n\t\tStreamsInbound:  64 * 16,\n\t\tStreamsOutbound: 128 * 16,\n\t\tStreams:         128 * 16,\n\t\tMemory:          128 << 20,\n\t\tFD:              256,\n\t},\n\n\tAllowlistedSystemLimitIncrease: BaseLimitIncrease{\n\t\tConnsInbound:    64,\n\t\tConnsOutbound:   128,\n\t\tConns:           128,\n\t\tStreamsInbound:  64 * 16,\n\t\tStreamsOutbound: 128 * 16,\n\t\tStreams:         128 * 16,\n\t\tMemory:          1 << 30,\n\t\tFDFraction:      1,\n\t},\n\n\tAllowlistedTransientBaseLimit: BaseLimit{\n\t\tConnsInbound:    32,\n\t\tConnsOutbound:   64,\n\t\tConns:           64,\n\t\tStreamsInbound:  128,\n\t\tStreamsOutbound: 256,\n\t\tStreams:         256,\n\t\tMemory:          32 << 20,\n\t\tFD:              64,\n\t},\n\n\tAllowlistedTransientLimitIncrease: BaseLimitIncrease{\n\t\tConnsInbound:    16,\n\t\tConnsOutbound:   32,\n\t\tConns:           32,\n\t\tStreamsInbound:  128,\n\t\tStreamsOutbound: 256,\n\t\tStreams:         256,\n\t\tMemory:          128 << 20,\n\t\tFDFraction:      0.25,\n\t},\n\n\tServiceBaseLimit: BaseLimit{\n\t\tStreamsInbound:  1024,\n\t\tStreamsOutbound: 4096,\n\t\tStreams:         4096,\n\t\tMemory:          64 << 20,\n\t},\n\n\tServiceLimitIncrease: BaseLimitIncrease{\n\t\tStreamsInbound:  512,\n\t\tStreamsOutbound: 2048,\n\t\tStreams:         2048,\n\t\tMemory:          128 << 20,\n\t},\n\n\tServicePeerBaseLimit: BaseLimit{\n\t\tStreamsInbound:  128,\n\t\tStreamsOutbound: 256,\n\t\tStreams:         256,\n\t\tMemory:          16 << 20,\n\t},\n\n\tServicePeerLimitIncrease: BaseLimitIncrease{\n\t\tStreamsInbound:  4,\n\t\tStreamsOutbound: 8,\n\t\tStreams:         8,\n\t\tMemory:          4 << 20,\n\t},\n\n\tProtocolBaseLimit: BaseLimit{\n\t\tStreamsInbound:  512,\n\t\tStreamsOutbound: 2048,\n\t\tStreams:         2048,\n\t\tMemory:          64 << 20,\n\t},\n\n\tProtocolLimitIncrease: BaseLimitIncrease{\n\t\tStreamsInbound:  256,\n\t\tStreamsOutbound: 512,\n\t\tStreams:         512,\n\t\tMemory:          164 << 20,\n\t},\n\n\tProtocolPeerBaseLimit: BaseLimit{\n\t\tStreamsInbound:  64,\n\t\tStreamsOutbound: 128,\n\t\tStreams:         256,\n\t\tMemory:          16 << 20,\n\t},\n\n\tProtocolPeerLimitIncrease: BaseLimitIncrease{\n\t\tStreamsInbound:  4,\n\t\tStreamsOutbound: 8,\n\t\tStreams:         16,\n\t\tMemory:          4,\n\t},\n\n\tPeerBaseLimit: BaseLimit{\n\t\t// 8 for now so that it matches the number of concurrent dials we may do\n\t\t// in swarm_dial.go. With future smart dialing work we should bring this\n\t\t// down\n\t\tConnsInbound:    8,\n\t\tConnsOutbound:   8,\n\t\tConns:           8,\n\t\tStreamsInbound:  256,\n\t\tStreamsOutbound: 512,\n\t\tStreams:         512,\n\t\tMemory:          64 << 20,\n\t\tFD:              4,\n\t},\n\n\tPeerLimitIncrease: BaseLimitIncrease{\n\t\tStreamsInbound:  128,\n\t\tStreamsOutbound: 256,\n\t\tStreams:         256,\n\t\tMemory:          128 << 20,\n\t\tFDFraction:      1.0 / 64,\n\t},\n\n\tConnBaseLimit: BaseLimit{\n\t\tConnsInbound:  1,\n\t\tConnsOutbound: 1,\n\t\tConns:         1,\n\t\tFD:            1,\n\t\tMemory:        32 << 20,\n\t},\n\n\tStreamBaseLimit: BaseLimit{\n\t\tStreamsInbound:  1,\n\t\tStreamsOutbound: 1,\n\t\tStreams:         1,\n\t\tMemory:          16 << 20,\n\t},\n}\n\nvar infiniteBaseLimit = BaseLimit{\n\tStreams:         math.MaxInt,\n\tStreamsInbound:  math.MaxInt,\n\tStreamsOutbound: math.MaxInt,\n\tConns:           math.MaxInt,\n\tConnsInbound:    math.MaxInt,\n\tConnsOutbound:   math.MaxInt,\n\tFD:              math.MaxInt,\n\tMemory:          math.MaxInt64,\n}\n\n// InfiniteLimits are a limiter configuration that uses unlimited limits, thus effectively not limiting anything.\n// Keep in mind that the operating system limits the number of file descriptors that an application can use.\nvar InfiniteLimits = ConcreteLimitConfig{\n\tsystem:               infiniteBaseLimit,\n\ttransient:            infiniteBaseLimit,\n\tallowlistedSystem:    infiniteBaseLimit,\n\tallowlistedTransient: infiniteBaseLimit,\n\tserviceDefault:       infiniteBaseLimit,\n\tservicePeerDefault:   infiniteBaseLimit,\n\tprotocolDefault:      infiniteBaseLimit,\n\tprotocolPeerDefault:  infiniteBaseLimit,\n\tpeerDefault:          infiniteBaseLimit,\n\tconn:                 infiniteBaseLimit,\n\tstream:               infiniteBaseLimit,\n}\n"
  },
  {
    "path": "p2p/host/resource-manager/limit_test.go",
    "content": "package rcmgr\n\nimport (\n\t\"encoding/json\"\n\t\"math\"\n\t\"runtime\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestFileDescriptorCounting(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"can't read file descriptors on Windows\")\n\t}\n\tn := getNumFDs()\n\trequire.NotZero(t, n)\n\trequire.Less(t, n, int(1e7))\n}\n\nfunc TestScaling(t *testing.T) {\n\tbase := BaseLimit{\n\t\tStreams:         100,\n\t\tStreamsInbound:  200,\n\t\tStreamsOutbound: 400,\n\t\tConns:           10,\n\t\tConnsInbound:    20,\n\t\tConnsOutbound:   40,\n\t\tFD:              1,\n\t\tMemory:          1 << 20,\n\t}\n\n\tt.Run(\"no scaling if no increase is defined\", func(t *testing.T) {\n\t\tcfg := ScalingLimitConfig{ServiceBaseLimit: base}\n\t\tscaled := cfg.Scale(8<<30, 100)\n\t\trequire.Equal(t, base, scaled.serviceDefault)\n\t})\n\n\tt.Run(\"scaling\", func(t *testing.T) {\n\t\tcfg := ScalingLimitConfig{\n\t\t\tTransientBaseLimit: base,\n\t\t\tTransientLimitIncrease: BaseLimitIncrease{\n\t\t\t\tStreams:         1,\n\t\t\t\tStreamsInbound:  2,\n\t\t\t\tStreamsOutbound: 3,\n\t\t\t\tConns:           4,\n\t\t\t\tConnsInbound:    5,\n\t\t\t\tConnsOutbound:   6,\n\t\t\t\tMemory:          7,\n\t\t\t\tFDFraction:      0.5,\n\t\t\t},\n\t\t}\n\t\tscaled := cfg.Scale(128<<20+4<<30, 1000)\n\t\trequire.Equal(t, 500, scaled.transient.FD)\n\t\trequire.Equal(t, base.Streams+4, scaled.transient.Streams)\n\t\trequire.Equal(t, base.StreamsInbound+4*2, scaled.transient.StreamsInbound)\n\t\trequire.Equal(t, base.StreamsOutbound+4*3, scaled.transient.StreamsOutbound)\n\t\trequire.Equal(t, base.Conns+4*4, scaled.transient.Conns)\n\t\trequire.Equal(t, base.ConnsInbound+4*5, scaled.transient.ConnsInbound)\n\t\trequire.Equal(t, base.ConnsOutbound+4*6, scaled.transient.ConnsOutbound)\n\t\trequire.Equal(t, base.Memory+4*7, scaled.transient.Memory)\n\t})\n\n\tt.Run(\"scaling and using the base amounts\", func(t *testing.T) {\n\t\tcfg := ScalingLimitConfig{\n\t\t\tTransientBaseLimit: base,\n\t\t\tTransientLimitIncrease: BaseLimitIncrease{\n\t\t\t\tStreams:         1,\n\t\t\t\tStreamsInbound:  2,\n\t\t\t\tStreamsOutbound: 3,\n\t\t\t\tConns:           4,\n\t\t\t\tConnsInbound:    5,\n\t\t\t\tConnsOutbound:   6,\n\t\t\t\tMemory:          7,\n\t\t\t\tFDFraction:      0.01,\n\t\t\t},\n\t\t}\n\t\tscaled := cfg.Scale(1, 10)\n\t\trequire.Equal(t, 1, scaled.transient.FD)\n\t\trequire.Equal(t, base.Streams, scaled.transient.Streams)\n\t\trequire.Equal(t, base.StreamsInbound, scaled.transient.StreamsInbound)\n\t\trequire.Equal(t, base.StreamsOutbound, scaled.transient.StreamsOutbound)\n\t\trequire.Equal(t, base.Conns, scaled.transient.Conns)\n\t\trequire.Equal(t, base.ConnsInbound, scaled.transient.ConnsInbound)\n\t\trequire.Equal(t, base.ConnsOutbound, scaled.transient.ConnsOutbound)\n\t\trequire.Equal(t, base.Memory, scaled.transient.Memory)\n\t})\n\n\tt.Run(\"scaling limits in maps\", func(t *testing.T) {\n\t\tcfg := ScalingLimitConfig{\n\t\t\tServiceLimits: map[string]baseLimitConfig{\n\t\t\t\t\"A\": {\n\t\t\t\t\tBaseLimit: BaseLimit{Streams: 10, Memory: 100, FD: 9},\n\t\t\t\t},\n\t\t\t\t\"B\": {\n\t\t\t\t\tBaseLimit:         BaseLimit{Streams: 20, Memory: 200, FD: 10},\n\t\t\t\t\tBaseLimitIncrease: BaseLimitIncrease{Streams: 2, Memory: 3, FDFraction: 0.4},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\tscaled := cfg.Scale(128<<20+4<<30, 1000)\n\n\t\trequire.Len(t, scaled.service, 2)\n\t\trequire.Contains(t, scaled.service, \"A\")\n\t\trequire.Equal(t, 10, scaled.service[\"A\"].Streams)\n\t\trequire.Equal(t, int64(100), scaled.service[\"A\"].Memory)\n\t\trequire.Equal(t, 9, scaled.service[\"A\"].FD)\n\n\t\trequire.Contains(t, scaled.service, \"B\")\n\t\trequire.Equal(t, 20+4*2, scaled.service[\"B\"].Streams)\n\t\trequire.Equal(t, int64(200+4*3), scaled.service[\"B\"].Memory)\n\t\trequire.Equal(t, 400, scaled.service[\"B\"].FD)\n\t})\n}\n\nfunc TestReadmeExample(t *testing.T) {\n\tscalingLimits := ScalingLimitConfig{\n\t\tSystemBaseLimit: BaseLimit{\n\t\t\tConnsInbound:    64,\n\t\t\tConnsOutbound:   128,\n\t\t\tConns:           128,\n\t\t\tStreamsInbound:  512,\n\t\t\tStreamsOutbound: 1024,\n\t\t\tStreams:         1024,\n\t\t\tMemory:          128 << 20,\n\t\t\tFD:              256,\n\t\t},\n\t\tSystemLimitIncrease: BaseLimitIncrease{\n\t\t\tConnsInbound:    32,\n\t\t\tConnsOutbound:   64,\n\t\t\tConns:           64,\n\t\t\tStreamsInbound:  256,\n\t\t\tStreamsOutbound: 512,\n\t\t\tStreams:         512,\n\t\t\tMemory:          256 << 20,\n\t\t\tFDFraction:      1,\n\t\t},\n\t}\n\n\tlimitConf := scalingLimits.Scale(4<<30, 1000)\n\n\trequire.Equal(t, 384, limitConf.system.Conns)\n\trequire.Equal(t, 1000, limitConf.system.FD)\n}\n\nfunc TestJSONMarshalling(t *testing.T) {\n\tbl := ResourceLimits{\n\t\tStreams:         DefaultLimit,\n\t\tStreamsInbound:  10,\n\t\tStreamsOutbound: BlockAllLimit,\n\t\tConns:           10,\n\t\t// ConnsInbound:    DefaultLimit,\n\t\tConnsOutbound: Unlimited,\n\t\tMemory:        Unlimited64,\n\t}\n\n\tjsonEncoded, err := json.Marshal(bl)\n\trequire.NoError(t, err)\n\trequire.Equal(t, `{\"StreamsInbound\":10,\"StreamsOutbound\":\"blockAll\",\"Conns\":10,\"ConnsOutbound\":\"unlimited\",\"Memory\":\"unlimited\"}`, string(jsonEncoded))\n\n\t// Roundtrip\n\tvar blDecoded ResourceLimits\n\terr = json.Unmarshal(jsonEncoded, &blDecoded)\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, bl, blDecoded)\n}\n\nfunc TestJSONRoundTripInt64(t *testing.T) {\n\tbl := ResourceLimits{\n\t\tMemory: math.MaxInt64,\n\t}\n\n\tjsonEncoded, err := json.Marshal(bl)\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, `{\"Memory\":\"9223372036854775807\"}`, string(jsonEncoded))\n\n\t// Roundtrip\n\tvar blDecoded ResourceLimits\n\terr = json.Unmarshal(jsonEncoded, &blDecoded)\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, bl, blDecoded)\n}\n\nfunc TestRoundTripFromConcreteAndBack(t *testing.T) {\n\tl := PartialLimitConfig{\n\t\tSystem: ResourceLimits{\n\t\t\tConns:  1234,\n\t\t\tMemory: 54321,\n\t\t},\n\n\t\tServiceDefault: ResourceLimits{\n\t\t\tConns: 2,\n\t\t},\n\n\t\tService: map[string]ResourceLimits{\n\t\t\t\"foo\": {\n\t\t\t\tConns: 3,\n\t\t\t},\n\t\t},\n\t}\n\n\tconcrete := l.Build(InfiniteLimits)\n\n\t// Roundtrip\n\tfromConcrete := concrete.ToPartialLimitConfig().Build(InfiniteLimits)\n\trequire.Equal(t, concrete, fromConcrete)\n}\n\nfunc TestSerializeJSON(t *testing.T) {\n\tbl := BaseLimit{\n\t\tStreams: 10,\n\t}\n\n\tout, err := json.Marshal(bl)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"{\\\"Streams\\\":10}\", string(out))\n\n\tbli := BaseLimitIncrease{\n\t\tStreams: 10,\n\t}\n\n\tout, err = json.Marshal(bli)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"{\\\"Streams\\\":10}\", string(out))\n}\n\nfunc TestWhatIsZeroInResourceLimits(t *testing.T) {\n\tl := ResourceLimits{\n\t\tStreams: BlockAllLimit,\n\t\tMemory:  BlockAllLimit64,\n\t}\n\n\tout, err := json.Marshal(l)\n\trequire.NoError(t, err)\n\trequire.Equal(t, `{\"Streams\":\"blockAll\",\"Memory\":\"blockAll\"}`, string(out))\n\n\tl2 := ResourceLimits{}\n\terr = json.Unmarshal([]byte(`{\"Streams\":0,\"Memory\":0}`), &l2)\n\trequire.NoError(t, err)\n\trequire.Equal(t, l, l2)\n\n\tl3 := ResourceLimits{}\n\terr = json.Unmarshal([]byte(`{\"Streams\":0,\"Memory\":\"0\"}`), &l3)\n\trequire.NoError(t, err)\n\trequire.Equal(t, l, l3)\n}\n"
  },
  {
    "path": "p2p/host/resource-manager/limits_metrics_test.go",
    "content": "package rcmgr\n\nimport (\n\t\"testing\"\n\n\t\"github.com/prometheus/client_golang/prometheus\"\n\tdto \"github.com/prometheus/client_model/go\"\n)\n\nfunc TestReportSystemLimits(t *testing.T) {\n\t// Register the metrics\n\treg := prometheus.NewRegistry()\n\treg.MustRegister(limits)\n\n\t// Create a simple limiter with known limits\n\tlimiter := NewFixedLimiter(ConcreteLimitConfig{\n\t\tsystem: BaseLimit{\n\t\t\tMemory:          1024 * 1024 * 1024, // 1GB\n\t\t\tFD:              256,\n\t\t\tConns:           100,\n\t\t\tConnsInbound:    50,\n\t\t\tConnsOutbound:   50,\n\t\t\tStreams:         200,\n\t\t\tStreamsInbound:  100,\n\t\t\tStreamsOutbound: 100,\n\t\t},\n\t\ttransient: BaseLimit{\n\t\t\tMemory:          512 * 1024 * 1024, // 512MB\n\t\t\tFD:              128,\n\t\t\tConns:           50,\n\t\t\tConnsInbound:    25,\n\t\t\tConnsOutbound:   25,\n\t\t\tStreams:         100,\n\t\t\tStreamsInbound:  50,\n\t\t\tStreamsOutbound: 50,\n\t\t},\n\t})\n\n\t// Create a stats reporter\n\treporter, err := NewStatsTraceReporter()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Report the limits\n\treporter.ReportSystemLimits(limiter)\n\n\t// Verify that metrics were set\n\tmetrics, err := reg.Gather()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Find the limits metric\n\tvar limitsMetric *dto.MetricFamily\n\tfor _, m := range metrics {\n\t\tif m.GetName() == \"libp2p_rcmgr_limit\" {\n\t\t\tlimitsMetric = m\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif limitsMetric == nil {\n\t\tt.Fatal(\"limits metric not found\")\n\t}\n\n\t// Verify we have metrics for both system and transient scopes\n\tfoundSystem := false\n\tfoundTransient := false\n\tfor _, metric := range limitsMetric.GetMetric() {\n\t\tfor _, label := range metric.GetLabel() {\n\t\t\tif label.GetName() == \"scope\" {\n\t\t\t\tif label.GetValue() == \"system\" {\n\t\t\t\t\tfoundSystem = true\n\t\t\t\t}\n\t\t\t\tif label.GetValue() == \"transient\" {\n\t\t\t\t\tfoundTransient = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif !foundSystem {\n\t\tt.Error(\"system scope limits not reported\")\n\t}\n\tif !foundTransient {\n\t\tt.Error(\"transient scope limits not reported\")\n\t}\n\n\t// Verify specific limit values\n\texpectedLimits := map[string]map[string]float64{\n\t\t\"system\": {\n\t\t\t\"memory\":           1024 * 1024 * 1024,\n\t\t\t\"fd\":               256,\n\t\t\t\"conns\":            100,\n\t\t\t\"conns_inbound\":    50,\n\t\t\t\"conns_outbound\":   50,\n\t\t\t\"streams\":          200,\n\t\t\t\"streams_inbound\":  100,\n\t\t\t\"streams_outbound\": 100,\n\t\t},\n\t\t\"transient\": {\n\t\t\t\"memory\":           512 * 1024 * 1024,\n\t\t\t\"fd\":               128,\n\t\t\t\"conns\":            50,\n\t\t\t\"conns_inbound\":    25,\n\t\t\t\"conns_outbound\":   25,\n\t\t\t\"streams\":          100,\n\t\t\t\"streams_inbound\":  50,\n\t\t\t\"streams_outbound\": 50,\n\t\t},\n\t}\n\n\tfor _, metric := range limitsMetric.GetMetric() {\n\t\tvar scope, resource string\n\t\tfor _, label := range metric.GetLabel() {\n\t\t\tif label.GetName() == \"scope\" {\n\t\t\t\tscope = label.GetValue()\n\t\t\t}\n\t\t\tif label.GetName() == \"resource\" {\n\t\t\t\tresource = label.GetValue()\n\t\t\t}\n\t\t}\n\n\t\tif scope == \"\" || resource == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\texpectedValue, ok := expectedLimits[scope][resource]\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tactualValue := metric.GetGauge().GetValue()\n\t\tif actualValue != expectedValue {\n\t\t\tt.Errorf(\"limit mismatch for %s/%s: expected %v, got %v\", scope, resource, expectedValue, actualValue)\n\t\t}\n\t}\n}\n\nfunc TestReportSystemLimitsNilLimiter(t *testing.T) {\n\treporter, err := NewStatsTraceReporter()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Should not panic with nil limiter\n\treporter.ReportSystemLimits(nil)\n}\n"
  },
  {
    "path": "p2p/host/resource-manager/metrics.go",
    "content": "package rcmgr\n\nimport (\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n)\n\n// MetricsReporter is an interface for collecting metrics from resource manager actions\ntype MetricsReporter interface {\n\t// AllowConn is invoked when opening a connection is allowed\n\tAllowConn(dir network.Direction, usefd bool)\n\t// BlockConn is invoked when opening a connection is blocked\n\tBlockConn(dir network.Direction, usefd bool)\n\n\t// AllowStream is invoked when opening a stream is allowed\n\tAllowStream(p peer.ID, dir network.Direction)\n\t// BlockStream is invoked when opening a stream is blocked\n\tBlockStream(p peer.ID, dir network.Direction)\n\n\t// AllowPeer is invoked when attaching ac onnection to a peer is allowed\n\tAllowPeer(p peer.ID)\n\t// BlockPeer is invoked when attaching ac onnection to a peer is blocked\n\tBlockPeer(p peer.ID)\n\n\t// AllowProtocol is invoked when setting the protocol for a stream is allowed\n\tAllowProtocol(proto protocol.ID)\n\t// BlockProtocol is invoked when setting the protocol for a stream is blocked\n\tBlockProtocol(proto protocol.ID)\n\t// BlockProtocolPeer is invoked when setting the protocol for a stream is blocked at the per protocol peer scope\n\tBlockProtocolPeer(proto protocol.ID, p peer.ID)\n\n\t// AllowService is invoked when setting the protocol for a stream is allowed\n\tAllowService(svc string)\n\t// BlockService is invoked when setting the protocol for a stream is blocked\n\tBlockService(svc string)\n\t// BlockServicePeer is invoked when setting the service for a stream is blocked at the per service peer scope\n\tBlockServicePeer(svc string, p peer.ID)\n\n\t// AllowMemory is invoked when a memory reservation is allowed\n\tAllowMemory(size int)\n\t// BlockMemory is invoked when a memory reservation is blocked\n\tBlockMemory(size int)\n}\n\ntype metrics struct {\n\treporter MetricsReporter\n}\n\n// WithMetrics is a resource manager option to enable metrics collection\nfunc WithMetrics(reporter MetricsReporter) Option {\n\treturn func(r *resourceManager) error {\n\t\tr.metrics = &metrics{reporter: reporter}\n\t\treturn nil\n\t}\n}\n\nfunc (m *metrics) AllowConn(dir network.Direction, usefd bool) {\n\tif m == nil {\n\t\treturn\n\t}\n\n\tm.reporter.AllowConn(dir, usefd)\n}\n\nfunc (m *metrics) BlockConn(dir network.Direction, usefd bool) {\n\tif m == nil {\n\t\treturn\n\t}\n\n\tm.reporter.BlockConn(dir, usefd)\n}\n\nfunc (m *metrics) AllowStream(p peer.ID, dir network.Direction) {\n\tif m == nil {\n\t\treturn\n\t}\n\n\tm.reporter.AllowStream(p, dir)\n}\n\nfunc (m *metrics) BlockStream(p peer.ID, dir network.Direction) {\n\tif m == nil {\n\t\treturn\n\t}\n\n\tm.reporter.BlockStream(p, dir)\n}\n\nfunc (m *metrics) AllowPeer(p peer.ID) {\n\tif m == nil {\n\t\treturn\n\t}\n\n\tm.reporter.AllowPeer(p)\n}\n\nfunc (m *metrics) BlockPeer(p peer.ID) {\n\tif m == nil {\n\t\treturn\n\t}\n\n\tm.reporter.BlockPeer(p)\n}\n\nfunc (m *metrics) AllowProtocol(proto protocol.ID) {\n\tif m == nil {\n\t\treturn\n\t}\n\n\tm.reporter.AllowProtocol(proto)\n}\n\nfunc (m *metrics) BlockProtocol(proto protocol.ID) {\n\tif m == nil {\n\t\treturn\n\t}\n\n\tm.reporter.BlockProtocol(proto)\n}\n\nfunc (m *metrics) BlockProtocolPeer(proto protocol.ID, p peer.ID) {\n\tif m == nil {\n\t\treturn\n\t}\n\n\tm.reporter.BlockProtocolPeer(proto, p)\n}\n\nfunc (m *metrics) AllowService(svc string) {\n\tif m == nil {\n\t\treturn\n\t}\n\n\tm.reporter.AllowService(svc)\n}\n\nfunc (m *metrics) BlockService(svc string) {\n\tif m == nil {\n\t\treturn\n\t}\n\n\tm.reporter.BlockService(svc)\n}\n\nfunc (m *metrics) BlockServicePeer(svc string, p peer.ID) {\n\tif m == nil {\n\t\treturn\n\t}\n\n\tm.reporter.BlockServicePeer(svc, p)\n}\n\nfunc (m *metrics) AllowMemory(size int) {\n\tif m == nil {\n\t\treturn\n\t}\n\n\tm.reporter.AllowMemory(size)\n}\n\nfunc (m *metrics) BlockMemory(size int) {\n\tif m == nil {\n\t\treturn\n\t}\n\n\tm.reporter.BlockMemory(size)\n}\n"
  },
  {
    "path": "p2p/host/resource-manager/noalloc_test.go",
    "content": "//go:build nocover\n\npackage rcmgr\n\nimport (\n\t\"math/rand\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc randomTraceEvt(rng *rand.Rand) TraceEvt {\n\t// Possibly non-sensical\n\ttyps := []TraceEvtTyp{\n\t\tTraceStartEvt,\n\t\tTraceCreateScopeEvt,\n\t\tTraceDestroyScopeEvt,\n\t\tTraceReserveMemoryEvt,\n\t\tTraceBlockReserveMemoryEvt,\n\t\tTraceReleaseMemoryEvt,\n\t\tTraceAddStreamEvt,\n\t\tTraceBlockAddStreamEvt,\n\t\tTraceRemoveStreamEvt,\n\t\tTraceAddConnEvt,\n\t\tTraceBlockAddConnEvt,\n\t\tTraceRemoveConnEvt,\n\t}\n\n\tnames := []string{\n\t\t\"conn-1\",\n\t\t\"stream-2\",\n\t\t\"peer:abc\",\n\t\t\"system\",\n\t\t\"transient\",\n\t\t\"peer:12D3Koo\",\n\t\t\"protocol:/libp2p/autonat/1.0.0\",\n\t\t\"protocol:/libp2p/autonat/1.0.0.peer:12D3Koo\",\n\t\t\"service:libp2p.autonat\",\n\t\t\"service:libp2p.autonat.peer:12D3Koo\",\n\t}\n\n\treturn TraceEvt{\n\t\tType:       typs[rng.Intn(len(typs))],\n\t\tName:       names[rng.Intn(len(names))],\n\t\tDeltaOut:   rng.Intn(5),\n\t\tDeltaIn:    rng.Intn(5),\n\t\tDelta:      int64(rng.Intn(5)),\n\t\tMemory:     int64(rng.Intn(10000)),\n\t\tStreamsIn:  rng.Intn(100),\n\t\tStreamsOut: rng.Intn(100),\n\t\tConnsIn:    rng.Intn(100),\n\t\tConnsOut:   rng.Intn(100),\n\t\tFD:         rng.Intn(100),\n\t\tTime:       time.Now().Format(time.RFC3339Nano),\n\t}\n\n}\n\nvar regOnce sync.Once\n\nfunc BenchmarkMetricsRecording(b *testing.B) {\n\tb.ReportAllocs()\n\n\tregisterOnce.Do(func() {\n\t\tMustRegisterWith(prometheus.DefaultRegisterer)\n\t})\n\n\tevtCount := 10000\n\tevts := make([]TraceEvt, evtCount)\n\trng := rand.New(rand.NewSource(int64(b.N)))\n\tfor i := 0; i < evtCount; i++ {\n\t\tevts[i] = randomTraceEvt(rng)\n\t}\n\n\tstr, err := NewStatsTraceReporter()\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tstr.ConsumeEvent(evts[i%len(evts)])\n\t}\n}\n\nfunc TestNoAllocsNoCover(t *testing.T) {\n\tstr, err := NewStatsTraceReporter()\n\trequire.NoError(t, err)\n\n\tevtCount := 10_000\n\tevts := make([]TraceEvt, 0, evtCount)\n\trng := rand.New(rand.NewSource(1))\n\n\tfor i := 0; i < evtCount; i++ {\n\t\tevts = append(evts, randomTraceEvt(rng))\n\t}\n\n\ttagSlice := make([]string, 0, 10)\n\tallocs := testing.AllocsPerRun(100, func() {\n\t\tfor i := 0; i < evtCount; i++ {\n\t\t\tstr.consumeEventWithLabelSlice(evts[i], &tagSlice)\n\t\t}\n\t})\n\n\tif allocs > 10 {\n\t\tt.Fatalf(\"expected less than 10 heap bytes, got %f\", allocs)\n\t}\n}\n"
  },
  {
    "path": "p2p/host/resource-manager/obs/obs.go",
    "content": "// Package obs implements metrics tracing for resource manager\n//\n// Deprecated: obs is deprecated and the exported types and methods\n// are moved to rcmgr package. Use the corresponding identifier in\n// the rcmgr package, for example\n// obs.NewStatsTraceReporter => rcmgr.NewStatsTraceReporter\npackage obs\n\nimport (\n\trcmgr \"github.com/libp2p/go-libp2p/p2p/host/resource-manager\"\n)\n\nvar MustRegisterWith = rcmgr.MustRegisterWith\n\n// StatsTraceReporter reports stats on the resource manager using its traces.\ntype StatsTraceReporter = rcmgr.StatsTraceReporter\n\nvar NewStatsTraceReporter = rcmgr.NewStatsTraceReporter\n"
  },
  {
    "path": "p2p/host/resource-manager/rcmgr.go",
    "content": "package rcmgr\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\t\"github.com/libp2p/go-libp2p/x/rate\"\n\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n\t\"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\nvar log = logging.Logger(\"rcmgr\")\n\ntype resourceManager struct {\n\tlimits Limiter\n\n\tconnLimiter                    *connLimiter\n\tconnRateLimiter                *rate.Limiter\n\tverifySourceAddressRateLimiter *rate.Limiter\n\n\ttrace          *trace\n\tmetrics        *metrics\n\tdisableMetrics bool\n\n\tallowlist *Allowlist\n\n\tsystem    *systemScope\n\ttransient *transientScope\n\n\tallowlistedSystem    *systemScope\n\tallowlistedTransient *transientScope\n\n\tcancelCtx context.Context\n\tcancel    func()\n\twg        sync.WaitGroup\n\n\tmx    sync.Mutex\n\tsvc   map[string]*serviceScope\n\tproto map[protocol.ID]*protocolScope\n\tpeer  map[peer.ID]*peerScope\n\n\tstickyProto map[protocol.ID]struct{}\n\tstickyPeer  map[peer.ID]struct{}\n\n\tconnId, streamId int64\n}\n\nvar _ network.ResourceManager = (*resourceManager)(nil)\n\ntype systemScope struct {\n\t*resourceScope\n}\n\nvar _ network.ResourceScope = (*systemScope)(nil)\n\ntype transientScope struct {\n\t*resourceScope\n\n\tsystem *systemScope\n}\n\nvar _ network.ResourceScope = (*transientScope)(nil)\n\ntype serviceScope struct {\n\t*resourceScope\n\n\tservice string\n\trcmgr   *resourceManager\n\n\tpeers map[peer.ID]*resourceScope\n}\n\nvar _ network.ServiceScope = (*serviceScope)(nil)\n\ntype protocolScope struct {\n\t*resourceScope\n\n\tproto protocol.ID\n\trcmgr *resourceManager\n\n\tpeers map[peer.ID]*resourceScope\n}\n\nvar _ network.ProtocolScope = (*protocolScope)(nil)\n\ntype peerScope struct {\n\t*resourceScope\n\n\tpeer  peer.ID\n\trcmgr *resourceManager\n}\n\nvar _ network.PeerScope = (*peerScope)(nil)\n\ntype connectionScope struct {\n\t*resourceScope\n\n\tdir           network.Direction\n\tusefd         bool\n\tisAllowlisted bool\n\trcmgr         *resourceManager\n\tpeer          *peerScope\n\tendpoint      multiaddr.Multiaddr\n\tip            netip.Addr\n}\n\nvar _ network.ConnScope = (*connectionScope)(nil)\nvar _ network.ConnManagementScope = (*connectionScope)(nil)\n\ntype streamScope struct {\n\t*resourceScope\n\n\tdir   network.Direction\n\trcmgr *resourceManager\n\tpeer  *peerScope\n\tsvc   *serviceScope\n\tproto *protocolScope\n\n\tpeerProtoScope *resourceScope\n\tpeerSvcScope   *resourceScope\n}\n\nvar _ network.StreamScope = (*streamScope)(nil)\nvar _ network.StreamManagementScope = (*streamScope)(nil)\n\ntype Option func(*resourceManager) error\n\nfunc NewResourceManager(limits Limiter, opts ...Option) (network.ResourceManager, error) {\n\tallowlist := newAllowlist()\n\tr := &resourceManager{\n\t\tlimits:          limits,\n\t\tconnLimiter:     newConnLimiter(),\n\t\tallowlist:       &allowlist,\n\t\tsvc:             make(map[string]*serviceScope),\n\t\tproto:           make(map[protocol.ID]*protocolScope),\n\t\tpeer:            make(map[peer.ID]*peerScope),\n\t\tconnRateLimiter: newConnRateLimiter(),\n\t}\n\n\tfor _, opt := range opts {\n\t\tif err := opt(r); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tregisteredConnLimiterPrefixes := make(map[string]struct{})\n\tfor _, npLimit := range r.connLimiter.networkPrefixLimitV4 {\n\t\tregisteredConnLimiterPrefixes[npLimit.Network.String()] = struct{}{}\n\t}\n\tfor _, npLimit := range r.connLimiter.networkPrefixLimitV6 {\n\t\tregisteredConnLimiterPrefixes[npLimit.Network.String()] = struct{}{}\n\t}\n\tfor _, network := range allowlist.allowedNetworks {\n\t\tprefix, err := netip.ParsePrefix(network.String())\n\t\tif err != nil {\n\t\t\tlog.Debug(\"failed to parse prefix from allowlist\", \"network\", network.String(), \"err\", err)\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := registeredConnLimiterPrefixes[prefix.String()]; !ok {\n\t\t\t// connlimiter doesn't know about this network. Let's fix that\n\t\t\tr.connLimiter.addNetworkPrefixLimit(prefix.Addr().Is6(), NetworkPrefixLimit{\n\t\t\t\tNetwork:   prefix,\n\t\t\t\tConnCount: r.limits.GetAllowlistedSystemLimits().GetConnTotalLimit(),\n\t\t\t})\n\t\t}\n\t}\n\tr.verifySourceAddressRateLimiter = newVerifySourceAddressRateLimiter(r.connLimiter)\n\n\tif !r.disableMetrics {\n\t\tsr, err := NewStatsTraceReporter()\n\t\tif err != nil {\n\t\t\tlog.Error(\"failed to initialise StatsTraceReporter\", \"err\", err)\n\t\t} else {\n\t\t\t// Report system limits to Prometheus\n\t\t\tsr.ReportSystemLimits(limits)\n\n\t\t\tif r.trace == nil {\n\t\t\t\tr.trace = &trace{}\n\t\t\t}\n\t\t\tfound := false\n\t\t\tfor _, rep := range r.trace.reporters {\n\t\t\t\t// Compare the actual reporter, not the interface\n\t\t\t\tif _, ok := rep.(StatsTraceReporter); ok {\n\t\t\t\t\tfound = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !found {\n\t\t\t\tr.trace.reporters = append(r.trace.reporters, sr)\n\t\t\t}\n\t\t}\n\t}\n\n\tif err := r.trace.Start(limits); err != nil {\n\t\treturn nil, err\n\t}\n\n\tr.system = newSystemScope(limits.GetSystemLimits(), r, \"system\")\n\tr.system.IncRef()\n\tr.transient = newTransientScope(limits.GetTransientLimits(), r, \"transient\", r.system.resourceScope)\n\tr.transient.IncRef()\n\n\tr.allowlistedSystem = newSystemScope(limits.GetAllowlistedSystemLimits(), r, \"allowlistedSystem\")\n\tr.allowlistedSystem.IncRef()\n\tr.allowlistedTransient = newTransientScope(limits.GetAllowlistedTransientLimits(), r, \"allowlistedTransient\", r.allowlistedSystem.resourceScope)\n\tr.allowlistedTransient.IncRef()\n\n\tr.cancelCtx, r.cancel = context.WithCancel(context.Background())\n\n\tr.wg.Add(1)\n\tgo r.background()\n\n\treturn r, nil\n}\n\nfunc (r *resourceManager) GetAllowlist() *Allowlist {\n\treturn r.allowlist\n}\n\n// GetAllowlist tries to get the allowlist from the given resourcemanager\n// interface by checking to see if its concrete type is a resourceManager.\n// Returns nil if it fails to get the allowlist.\nfunc GetAllowlist(rcmgr network.ResourceManager) *Allowlist {\n\tr, ok := rcmgr.(*resourceManager)\n\tif !ok {\n\t\treturn nil\n\t}\n\n\treturn r.allowlist\n}\n\nfunc (r *resourceManager) ViewSystem(f func(network.ResourceScope) error) error {\n\treturn f(r.system)\n}\n\nfunc (r *resourceManager) ViewTransient(f func(network.ResourceScope) error) error {\n\treturn f(r.transient)\n}\n\nfunc (r *resourceManager) ViewService(srv string, f func(network.ServiceScope) error) error {\n\ts := r.getServiceScope(srv)\n\tdefer s.DecRef()\n\n\treturn f(s)\n}\n\nfunc (r *resourceManager) ViewProtocol(proto protocol.ID, f func(network.ProtocolScope) error) error {\n\ts := r.getProtocolScope(proto)\n\tdefer s.DecRef()\n\n\treturn f(s)\n}\n\nfunc (r *resourceManager) ViewPeer(p peer.ID, f func(network.PeerScope) error) error {\n\ts := r.getPeerScope(p)\n\tdefer s.DecRef()\n\n\treturn f(s)\n}\n\nfunc (r *resourceManager) getServiceScope(svc string) *serviceScope {\n\tr.mx.Lock()\n\tdefer r.mx.Unlock()\n\n\ts, ok := r.svc[svc]\n\tif !ok {\n\t\ts = newServiceScope(svc, r.limits.GetServiceLimits(svc), r)\n\t\tr.svc[svc] = s\n\t}\n\n\ts.IncRef()\n\treturn s\n}\n\nfunc (r *resourceManager) getProtocolScope(proto protocol.ID) *protocolScope {\n\tr.mx.Lock()\n\tdefer r.mx.Unlock()\n\n\ts, ok := r.proto[proto]\n\tif !ok {\n\t\ts = newProtocolScope(proto, r.limits.GetProtocolLimits(proto), r)\n\t\tr.proto[proto] = s\n\t}\n\n\ts.IncRef()\n\treturn s\n}\n\nfunc (r *resourceManager) setStickyProtocol(proto protocol.ID) {\n\tr.mx.Lock()\n\tdefer r.mx.Unlock()\n\n\tif r.stickyProto == nil {\n\t\tr.stickyProto = make(map[protocol.ID]struct{})\n\t}\n\tr.stickyProto[proto] = struct{}{}\n}\n\nfunc (r *resourceManager) getPeerScope(p peer.ID) *peerScope {\n\tr.mx.Lock()\n\tdefer r.mx.Unlock()\n\n\ts, ok := r.peer[p]\n\tif !ok {\n\t\ts = newPeerScope(p, r.limits.GetPeerLimits(p), r)\n\t\tr.peer[p] = s\n\t}\n\n\ts.IncRef()\n\treturn s\n}\n\nfunc (r *resourceManager) setStickyPeer(p peer.ID) {\n\tr.mx.Lock()\n\tdefer r.mx.Unlock()\n\n\tif r.stickyPeer == nil {\n\t\tr.stickyPeer = make(map[peer.ID]struct{})\n\t}\n\n\tr.stickyPeer[p] = struct{}{}\n}\n\nfunc (r *resourceManager) nextConnId() int64 {\n\tr.mx.Lock()\n\tdefer r.mx.Unlock()\n\n\tr.connId++\n\treturn r.connId\n}\n\nfunc (r *resourceManager) nextStreamId() int64 {\n\tr.mx.Lock()\n\tdefer r.mx.Unlock()\n\n\tr.streamId++\n\treturn r.streamId\n}\n\n// VerifySourceAddress tells the transport to verify the peer's IP address before\n// initiating a handshake.\nfunc (r *resourceManager) VerifySourceAddress(addr net.Addr) bool {\n\tif r.verifySourceAddressRateLimiter == nil {\n\t\treturn false\n\t}\n\tipPort, err := netip.ParseAddrPort(addr.String())\n\tif err != nil {\n\t\treturn true\n\t}\n\treturn !r.verifySourceAddressRateLimiter.Allow(ipPort.Addr())\n}\n\n// OpenConnectionNoIP is deprecated and will be removed in the next release\n//\n// Deprecated: Use OpenConnection instead\nfunc (r *resourceManager) OpenConnectionNoIP(dir network.Direction, usefd bool, endpoint multiaddr.Multiaddr) (network.ConnManagementScope, error) {\n\treturn r.openConnection(dir, usefd, endpoint, netip.Addr{})\n}\n\nfunc (r *resourceManager) OpenConnection(dir network.Direction, usefd bool, endpoint multiaddr.Multiaddr) (network.ConnManagementScope, error) {\n\tip, err := manet.ToIP(endpoint)\n\tif err != nil {\n\t\t// No IP address\n\t\treturn r.openConnection(dir, usefd, endpoint, netip.Addr{})\n\t}\n\n\tipAddr, ok := netip.AddrFromSlice(ip)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"failed to convert ip to netip.Addr\")\n\t}\n\treturn r.openConnection(dir, usefd, endpoint, ipAddr)\n}\n\nfunc (r *resourceManager) openConnection(dir network.Direction, usefd bool, endpoint multiaddr.Multiaddr, ip netip.Addr) (network.ConnManagementScope, error) {\n\tif !r.connRateLimiter.Allow(ip) {\n\t\treturn nil, errors.New(\"rate limit exceeded\")\n\t}\n\n\tif ip.IsValid() {\n\t\tif ok := r.connLimiter.addConn(ip); !ok {\n\t\t\treturn nil, fmt.Errorf(\"connections per ip limit exceeded for %s\", endpoint)\n\t\t}\n\t}\n\n\tvar conn *connectionScope\n\tconn = newConnectionScope(dir, usefd, r.limits.GetConnLimits(), r, endpoint, ip)\n\n\terr := conn.AddConn(dir, usefd)\n\tif err != nil && ip.IsValid() {\n\t\t// Try again if this is an allowlisted connection\n\t\t// Failed to open connection, let's see if this was allowlisted and try again\n\t\tallowed := r.allowlist.Allowed(endpoint)\n\t\tif allowed {\n\t\t\tconn.Done()\n\t\t\tconn = newAllowListedConnectionScope(dir, usefd, r.limits.GetConnLimits(), r, endpoint)\n\t\t\terr = conn.AddConn(dir, usefd)\n\t\t}\n\t}\n\n\tif err != nil {\n\t\tconn.Done()\n\t\tr.metrics.BlockConn(dir, usefd)\n\t\treturn nil, err\n\t}\n\n\tr.metrics.AllowConn(dir, usefd)\n\treturn conn, nil\n}\n\nfunc (r *resourceManager) OpenStream(p peer.ID, dir network.Direction) (network.StreamManagementScope, error) {\n\tpeer := r.getPeerScope(p)\n\tstream := newStreamScope(dir, r.limits.GetStreamLimits(p), peer, r)\n\tpeer.DecRef() // we have the reference in edges\n\n\terr := stream.AddStream(dir)\n\tif err != nil {\n\t\tstream.Done()\n\t\tr.metrics.BlockStream(p, dir)\n\t\treturn nil, err\n\t}\n\n\tr.metrics.AllowStream(p, dir)\n\treturn stream, nil\n}\n\nfunc (r *resourceManager) Close() error {\n\tr.cancel()\n\tr.wg.Wait()\n\tr.trace.Close()\n\n\treturn nil\n}\n\nfunc (r *resourceManager) background() {\n\tdefer r.wg.Done()\n\n\t// periodically garbage collects unused peer and protocol scopes\n\tticker := time.NewTicker(time.Minute)\n\tdefer ticker.Stop()\n\n\tfor {\n\t\tselect {\n\t\tcase <-ticker.C:\n\t\t\tr.gc()\n\t\tcase <-r.cancelCtx.Done():\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (r *resourceManager) gc() {\n\tr.mx.Lock()\n\tdefer r.mx.Unlock()\n\n\tfor proto, s := range r.proto {\n\t\t_, sticky := r.stickyProto[proto]\n\t\tif sticky {\n\t\t\tcontinue\n\t\t}\n\t\tif s.IsUnused() {\n\t\t\ts.Done()\n\t\t\tdelete(r.proto, proto)\n\t\t}\n\t}\n\n\tvar deadPeers []peer.ID\n\tfor p, s := range r.peer {\n\t\t_, sticky := r.stickyPeer[p]\n\t\tif sticky {\n\t\t\tcontinue\n\t\t}\n\n\t\tif s.IsUnused() {\n\t\t\ts.Done()\n\t\t\tdelete(r.peer, p)\n\t\t\tdeadPeers = append(deadPeers, p)\n\t\t}\n\t}\n\n\tfor _, s := range r.svc {\n\t\ts.Lock()\n\t\tfor _, p := range deadPeers {\n\t\t\tps, ok := s.peers[p]\n\t\t\tif ok {\n\t\t\t\tps.Done()\n\t\t\t\tdelete(s.peers, p)\n\t\t\t}\n\t\t}\n\t\ts.Unlock()\n\t}\n\n\tfor _, s := range r.proto {\n\t\ts.Lock()\n\t\tfor _, p := range deadPeers {\n\t\t\tps, ok := s.peers[p]\n\t\t\tif ok {\n\t\t\t\tps.Done()\n\t\t\t\tdelete(s.peers, p)\n\t\t\t}\n\t\t}\n\t\ts.Unlock()\n\t}\n}\n\nfunc newSystemScope(limit Limit, rcmgr *resourceManager, name string) *systemScope {\n\treturn &systemScope{\n\t\tresourceScope: newResourceScope(limit, nil, name, rcmgr.trace, rcmgr.metrics),\n\t}\n}\n\nfunc newTransientScope(limit Limit, rcmgr *resourceManager, name string, systemScope *resourceScope) *transientScope {\n\treturn &transientScope{\n\t\tresourceScope: newResourceScope(limit,\n\t\t\t[]*resourceScope{systemScope},\n\t\t\tname, rcmgr.trace, rcmgr.metrics),\n\t\tsystem: rcmgr.system,\n\t}\n}\n\nfunc newServiceScope(service string, limit Limit, rcmgr *resourceManager) *serviceScope {\n\treturn &serviceScope{\n\t\tresourceScope: newResourceScope(limit,\n\t\t\t[]*resourceScope{rcmgr.system.resourceScope},\n\t\t\tfmt.Sprintf(\"service:%s\", service), rcmgr.trace, rcmgr.metrics),\n\t\tservice: service,\n\t\trcmgr:   rcmgr,\n\t}\n}\n\nfunc newProtocolScope(proto protocol.ID, limit Limit, rcmgr *resourceManager) *protocolScope {\n\treturn &protocolScope{\n\t\tresourceScope: newResourceScope(limit,\n\t\t\t[]*resourceScope{rcmgr.system.resourceScope},\n\t\t\tfmt.Sprintf(\"protocol:%s\", proto), rcmgr.trace, rcmgr.metrics),\n\t\tproto: proto,\n\t\trcmgr: rcmgr,\n\t}\n}\n\nfunc newPeerScope(p peer.ID, limit Limit, rcmgr *resourceManager) *peerScope {\n\treturn &peerScope{\n\t\tresourceScope: newResourceScope(limit,\n\t\t\t[]*resourceScope{rcmgr.system.resourceScope},\n\t\t\tpeerScopeName(p), rcmgr.trace, rcmgr.metrics),\n\t\tpeer:  p,\n\t\trcmgr: rcmgr,\n\t}\n}\n\nfunc newConnectionScope(dir network.Direction, usefd bool, limit Limit, rcmgr *resourceManager, endpoint multiaddr.Multiaddr, ip netip.Addr) *connectionScope {\n\treturn &connectionScope{\n\t\tresourceScope: newResourceScope(limit,\n\t\t\t[]*resourceScope{rcmgr.transient.resourceScope, rcmgr.system.resourceScope},\n\t\t\tconnScopeName(rcmgr.nextConnId()), rcmgr.trace, rcmgr.metrics),\n\t\tdir:      dir,\n\t\tusefd:    usefd,\n\t\trcmgr:    rcmgr,\n\t\tendpoint: endpoint,\n\t\tip:       ip,\n\t}\n}\n\nfunc newAllowListedConnectionScope(dir network.Direction, usefd bool, limit Limit, rcmgr *resourceManager, endpoint multiaddr.Multiaddr) *connectionScope {\n\treturn &connectionScope{\n\t\tresourceScope: newResourceScope(limit,\n\t\t\t[]*resourceScope{rcmgr.allowlistedTransient.resourceScope, rcmgr.allowlistedSystem.resourceScope},\n\t\t\tconnScopeName(rcmgr.nextConnId()), rcmgr.trace, rcmgr.metrics),\n\t\tdir:           dir,\n\t\tusefd:         usefd,\n\t\trcmgr:         rcmgr,\n\t\tendpoint:      endpoint,\n\t\tisAllowlisted: true,\n\t}\n}\n\nfunc newStreamScope(dir network.Direction, limit Limit, peer *peerScope, rcmgr *resourceManager) *streamScope {\n\treturn &streamScope{\n\t\tresourceScope: newResourceScope(limit,\n\t\t\t[]*resourceScope{peer.resourceScope, rcmgr.transient.resourceScope, rcmgr.system.resourceScope},\n\t\t\tstreamScopeName(rcmgr.nextStreamId()), rcmgr.trace, rcmgr.metrics),\n\t\tdir:   dir,\n\t\trcmgr: peer.rcmgr,\n\t\tpeer:  peer,\n\t}\n}\n\nfunc IsSystemScope(name string) bool {\n\treturn name == \"system\"\n}\n\nfunc IsTransientScope(name string) bool {\n\treturn name == \"transient\"\n}\n\nfunc streamScopeName(streamId int64) string {\n\treturn fmt.Sprintf(\"stream-%d\", streamId)\n}\n\nfunc IsStreamScope(name string) bool {\n\treturn strings.HasPrefix(name, \"stream-\") && !IsSpan(name)\n}\n\nfunc connScopeName(streamId int64) string {\n\treturn fmt.Sprintf(\"conn-%d\", streamId)\n}\n\nfunc IsConnScope(name string) bool {\n\treturn strings.HasPrefix(name, \"conn-\") && !IsSpan(name)\n}\n\nfunc peerScopeName(p peer.ID) string {\n\treturn fmt.Sprintf(\"peer:%s\", p)\n}\n\n// PeerStrInScopeName returns \"\" if name is not a peerScopeName. Returns a string to avoid allocating a peer ID object\nfunc PeerStrInScopeName(name string) string {\n\tif !strings.HasPrefix(name, \"peer:\") || IsSpan(name) {\n\t\treturn \"\"\n\t}\n\t// Index to avoid allocating a new string\n\t_, after, ok := strings.Cut(name, \"peer:\")\n\tif !ok {\n\t\treturn \"\"\n\t}\n\tp := (after)\n\treturn p\n}\n\n// ParseProtocolScopeName returns the service name if name is a serviceScopeName.\n// Otherwise returns \"\"\nfunc ParseProtocolScopeName(name string) string {\n\tif strings.HasPrefix(name, \"protocol:\") && !IsSpan(name) {\n\t\tif strings.Contains(name, \"peer:\") {\n\t\t\t// This is a protocol peer scope\n\t\t\treturn \"\"\n\t\t}\n\n\t\t// Index to avoid allocating a new string\n\t\t_, after, ok := strings.Cut(name, \":\")\n\t\tif !ok {\n\t\t\treturn \"\"\n\t\t}\n\t\treturn after\n\t}\n\treturn \"\"\n}\n\nfunc (s *serviceScope) Name() string {\n\treturn s.service\n}\n\nfunc (s *serviceScope) getPeerScope(p peer.ID) *resourceScope {\n\ts.Lock()\n\tdefer s.Unlock()\n\n\tps, ok := s.peers[p]\n\tif ok {\n\t\tps.IncRef()\n\t\treturn ps\n\t}\n\n\tl := s.rcmgr.limits.GetServicePeerLimits(s.service)\n\n\tif s.peers == nil {\n\t\ts.peers = make(map[peer.ID]*resourceScope)\n\t}\n\n\tps = newResourceScope(l, nil, fmt.Sprintf(\"%s.peer:%s\", s.name, p), s.rcmgr.trace, s.rcmgr.metrics)\n\ts.peers[p] = ps\n\n\tps.IncRef()\n\treturn ps\n}\n\nfunc (s *protocolScope) Protocol() protocol.ID {\n\treturn s.proto\n}\n\nfunc (s *protocolScope) getPeerScope(p peer.ID) *resourceScope {\n\ts.Lock()\n\tdefer s.Unlock()\n\n\tps, ok := s.peers[p]\n\tif ok {\n\t\tps.IncRef()\n\t\treturn ps\n\t}\n\n\tl := s.rcmgr.limits.GetProtocolPeerLimits(s.proto)\n\n\tif s.peers == nil {\n\t\ts.peers = make(map[peer.ID]*resourceScope)\n\t}\n\n\tps = newResourceScope(l, nil, fmt.Sprintf(\"%s.peer:%s\", s.name, p), s.rcmgr.trace, s.rcmgr.metrics)\n\ts.peers[p] = ps\n\n\tps.IncRef()\n\treturn ps\n}\n\nfunc (s *peerScope) Peer() peer.ID {\n\treturn s.peer\n}\n\nfunc (s *connectionScope) PeerScope() network.PeerScope {\n\ts.Lock()\n\tdefer s.Unlock()\n\n\t// avoid nil is not nil footgun; go....\n\tif s.peer == nil {\n\t\treturn nil\n\t}\n\n\treturn s.peer\n}\n\nfunc (s *connectionScope) Done() {\n\ts.Lock()\n\tdefer s.Unlock()\n\tif s.done {\n\t\treturn\n\t}\n\tif s.ip.IsValid() {\n\t\ts.rcmgr.connLimiter.rmConn(s.ip)\n\t}\n\ts.resourceScope.doneUnlocked()\n}\n\n// transferAllowedToStandard transfers this connection scope from being part of\n// the allowlist set of scopes to being part of the standard set of scopes.\n// Happens when we first allowlisted this connection due to its IP, but later\n// discovered that the peer id not what we expected.\nfunc (s *connectionScope) transferAllowedToStandard() (err error) {\n\n\tsystemScope := s.rcmgr.system.resourceScope\n\ttransientScope := s.rcmgr.transient.resourceScope\n\n\tstat := s.resourceScope.rc.stat()\n\n\tfor _, scope := range s.edges {\n\t\tscope.ReleaseForChild(stat)\n\t\tscope.DecRef() // removed from edges\n\t}\n\ts.edges = nil\n\n\tif err := systemScope.ReserveForChild(stat); err != nil {\n\t\treturn err\n\t}\n\tsystemScope.IncRef()\n\n\t// Undo this if we fail later\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tsystemScope.ReleaseForChild(stat)\n\t\t\tsystemScope.DecRef()\n\t\t}\n\t}()\n\n\tif err := transientScope.ReserveForChild(stat); err != nil {\n\t\treturn err\n\t}\n\ttransientScope.IncRef()\n\n\t// Update edges\n\ts.edges = []*resourceScope{\n\t\tsystemScope,\n\t\ttransientScope,\n\t}\n\treturn nil\n}\n\nfunc (s *connectionScope) SetPeer(p peer.ID) error {\n\ts.Lock()\n\tdefer s.Unlock()\n\n\tif s.peer != nil {\n\t\treturn fmt.Errorf(\"connection scope already attached to a peer\")\n\t}\n\n\tsystem := s.rcmgr.system\n\ttransient := s.rcmgr.transient\n\n\tif s.isAllowlisted {\n\t\tsystem = s.rcmgr.allowlistedSystem\n\t\ttransient = s.rcmgr.allowlistedTransient\n\n\t\tif !s.rcmgr.allowlist.AllowedPeerAndMultiaddr(p, s.endpoint) {\n\t\t\ts.isAllowlisted = false\n\n\t\t\t// This is not an allowed peer + multiaddr combination. We need to\n\t\t\t// transfer this connection to the general scope. We'll do this first by\n\t\t\t// transferring the connection to the system and transient scopes, then\n\t\t\t// continue on with this function. The idea is that a connection\n\t\t\t// shouldn't get the benefit of evading the transient scope because it\n\t\t\t// was _almost_ an allowlisted connection.\n\t\t\tif err := s.transferAllowedToStandard(); err != nil {\n\t\t\t\t// Failed to transfer this connection to the standard scopes\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\t// set the system and transient scopes to the non-allowlisted ones\n\t\t\tsystem = s.rcmgr.system\n\t\t\ttransient = s.rcmgr.transient\n\t\t}\n\t}\n\n\ts.peer = s.rcmgr.getPeerScope(p)\n\n\t// juggle resources from transient scope to peer scope\n\tstat := s.resourceScope.rc.stat()\n\tif err := s.peer.ReserveForChild(stat); err != nil {\n\t\ts.peer.DecRef()\n\t\ts.peer = nil\n\t\ts.rcmgr.metrics.BlockPeer(p)\n\t\treturn err\n\t}\n\n\ttransient.ReleaseForChild(stat)\n\ttransient.DecRef() // removed from edges\n\n\t// update edges\n\tedges := []*resourceScope{\n\t\ts.peer.resourceScope,\n\t\tsystem.resourceScope,\n\t}\n\ts.resourceScope.edges = edges\n\n\ts.rcmgr.metrics.AllowPeer(p)\n\treturn nil\n}\n\nfunc (s *streamScope) ProtocolScope() network.ProtocolScope {\n\ts.Lock()\n\tdefer s.Unlock()\n\n\t// avoid nil is not nil footgun; go....\n\tif s.proto == nil {\n\t\treturn nil\n\t}\n\n\treturn s.proto\n}\n\nfunc (s *streamScope) SetProtocol(proto protocol.ID) error {\n\ts.Lock()\n\tdefer s.Unlock()\n\n\tif s.proto != nil {\n\t\treturn fmt.Errorf(\"stream scope already attached to a protocol\")\n\t}\n\n\ts.proto = s.rcmgr.getProtocolScope(proto)\n\n\t// juggle resources from transient scope to protocol scope\n\tstat := s.resourceScope.rc.stat()\n\tif err := s.proto.ReserveForChild(stat); err != nil {\n\t\ts.proto.DecRef()\n\t\ts.proto = nil\n\t\ts.rcmgr.metrics.BlockProtocol(proto)\n\t\treturn err\n\t}\n\n\ts.peerProtoScope = s.proto.getPeerScope(s.peer.peer)\n\tif err := s.peerProtoScope.ReserveForChild(stat); err != nil {\n\t\ts.proto.ReleaseForChild(stat)\n\t\ts.proto.DecRef()\n\t\ts.proto = nil\n\t\ts.peerProtoScope.DecRef()\n\t\ts.peerProtoScope = nil\n\t\ts.rcmgr.metrics.BlockProtocolPeer(proto, s.peer.peer)\n\t\treturn err\n\t}\n\n\ts.rcmgr.transient.ReleaseForChild(stat)\n\ts.rcmgr.transient.DecRef() // removed from edges\n\n\t// update edges\n\tedges := []*resourceScope{\n\t\ts.peer.resourceScope,\n\t\ts.peerProtoScope,\n\t\ts.proto.resourceScope,\n\t\ts.rcmgr.system.resourceScope,\n\t}\n\ts.resourceScope.edges = edges\n\n\ts.rcmgr.metrics.AllowProtocol(proto)\n\treturn nil\n}\n\nfunc (s *streamScope) ServiceScope() network.ServiceScope {\n\ts.Lock()\n\tdefer s.Unlock()\n\n\t// avoid nil is not nil footgun; go....\n\tif s.svc == nil {\n\t\treturn nil\n\t}\n\n\treturn s.svc\n}\n\nfunc (s *streamScope) SetService(svc string) error {\n\ts.Lock()\n\tdefer s.Unlock()\n\n\tif s.svc != nil {\n\t\treturn fmt.Errorf(\"stream scope already attached to a service\")\n\t}\n\tif s.proto == nil {\n\t\treturn fmt.Errorf(\"stream scope not attached to a protocol\")\n\t}\n\n\ts.svc = s.rcmgr.getServiceScope(svc)\n\n\t// reserve resources in service\n\tstat := s.resourceScope.rc.stat()\n\tif err := s.svc.ReserveForChild(stat); err != nil {\n\t\ts.svc.DecRef()\n\t\ts.svc = nil\n\t\ts.rcmgr.metrics.BlockService(svc)\n\t\treturn err\n\t}\n\n\t// get the per peer service scope constraint, if any\n\ts.peerSvcScope = s.svc.getPeerScope(s.peer.peer)\n\tif err := s.peerSvcScope.ReserveForChild(stat); err != nil {\n\t\ts.svc.ReleaseForChild(stat)\n\t\ts.svc.DecRef()\n\t\ts.svc = nil\n\t\ts.peerSvcScope.DecRef()\n\t\ts.peerSvcScope = nil\n\t\ts.rcmgr.metrics.BlockServicePeer(svc, s.peer.peer)\n\t\treturn err\n\t}\n\n\t// update edges\n\tedges := []*resourceScope{\n\t\ts.peer.resourceScope,\n\t\ts.peerProtoScope,\n\t\ts.peerSvcScope,\n\t\ts.proto.resourceScope,\n\t\ts.svc.resourceScope,\n\t\ts.rcmgr.system.resourceScope,\n\t}\n\ts.resourceScope.edges = edges\n\n\ts.rcmgr.metrics.AllowService(svc)\n\treturn nil\n}\n\nfunc (s *streamScope) PeerScope() network.PeerScope {\n\ts.Lock()\n\tdefer s.Unlock()\n\n\t// avoid nil is not nil footgun; go....\n\tif s.peer == nil {\n\t\treturn nil\n\t}\n\n\treturn s.peer\n}\n"
  },
  {
    "path": "p2p/host/resource-manager/rcmgr_test.go",
    "content": "package rcmgr\n\nimport (\n\t\"net\"\n\t\"net/netip\"\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\t\"github.com/libp2p/go-libp2p/core/test\"\n\t\"github.com/libp2p/go-libp2p/x/rate\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/multiformats/go-multiaddr\"\n)\n\nvar dummyMA = multiaddr.StringCast(\"/ip4/1.2.3.4/tcp/1234\")\n\nfunc TestResourceManager(t *testing.T) {\n\tpeerA := peer.ID(\"A\")\n\tpeerB := peer.ID(\"B\")\n\tprotoA := protocol.ID(\"/A\")\n\tprotoB := protocol.ID(\"/B\")\n\tsvcA := \"A.svc\"\n\tsvcB := \"B.svc\"\n\tnmgr, err := NewResourceManager(\n\t\tNewFixedLimiter(ConcreteLimitConfig{\n\t\t\tsystem: BaseLimit{\n\t\t\t\tMemory:          16384,\n\t\t\t\tStreamsInbound:  3,\n\t\t\t\tStreamsOutbound: 3,\n\t\t\t\tStreams:         6,\n\t\t\t\tConnsInbound:    3,\n\t\t\t\tConnsOutbound:   3,\n\t\t\t\tConns:           6,\n\t\t\t\tFD:              2,\n\t\t\t},\n\t\t\ttransient: BaseLimit{\n\t\t\t\tMemory:          4096,\n\t\t\t\tStreamsInbound:  1,\n\t\t\t\tStreamsOutbound: 1,\n\t\t\t\tStreams:         2,\n\t\t\t\tConnsInbound:    1,\n\t\t\t\tConnsOutbound:   1,\n\t\t\t\tConns:           2,\n\t\t\t\tFD:              1,\n\t\t\t},\n\t\t\tserviceDefault: BaseLimit{\n\t\t\t\tMemory:          4096,\n\t\t\t\tStreamsInbound:  1,\n\t\t\t\tStreamsOutbound: 1,\n\t\t\t\tStreams:         2,\n\t\t\t\tConnsInbound:    1,\n\t\t\t\tConnsOutbound:   1,\n\t\t\t\tConns:           2,\n\t\t\t\tFD:              1,\n\t\t\t},\n\t\t\tservicePeerDefault: BaseLimit{\n\t\t\t\tMemory:          4096,\n\t\t\t\tStreamsInbound:  5,\n\t\t\t\tStreamsOutbound: 5,\n\t\t\t\tStreams:         10,\n\t\t\t},\n\t\t\tservice: map[string]BaseLimit{\n\t\t\t\tsvcA: {\n\t\t\t\t\tMemory:          8192,\n\t\t\t\t\tStreamsInbound:  2,\n\t\t\t\t\tStreamsOutbound: 2,\n\t\t\t\t\tStreams:         4,\n\t\t\t\t\tConnsInbound:    2,\n\t\t\t\t\tConnsOutbound:   2,\n\t\t\t\t\tConns:           4,\n\t\t\t\t\tFD:              1,\n\t\t\t\t},\n\t\t\t\tsvcB: {\n\t\t\t\t\tMemory:          8192,\n\t\t\t\t\tStreamsInbound:  2,\n\t\t\t\t\tStreamsOutbound: 2,\n\t\t\t\t\tStreams:         4,\n\t\t\t\t\tConnsInbound:    2,\n\t\t\t\t\tConnsOutbound:   2,\n\t\t\t\t\tConns:           4,\n\t\t\t\t\tFD:              1,\n\t\t\t\t},\n\t\t\t},\n\t\t\tservicePeer: map[string]BaseLimit{\n\t\t\t\tsvcB: {\n\t\t\t\t\tMemory:          8192,\n\t\t\t\t\tStreamsInbound:  1,\n\t\t\t\t\tStreamsOutbound: 1,\n\t\t\t\t\tStreams:         2,\n\t\t\t\t},\n\t\t\t},\n\t\t\tprotocolDefault: BaseLimit{\n\t\t\t\tMemory:          4096,\n\t\t\t\tStreamsInbound:  1,\n\t\t\t\tStreamsOutbound: 1,\n\t\t\t\tStreams:         2,\n\t\t\t},\n\t\t\tprotocol: map[protocol.ID]BaseLimit{\n\t\t\t\tprotoA: {\n\t\t\t\t\tMemory:          8192,\n\t\t\t\t\tStreamsInbound:  2,\n\t\t\t\t\tStreamsOutbound: 2,\n\t\t\t\t\tStreams:         2,\n\t\t\t\t},\n\t\t\t},\n\t\t\tprotocolPeer: map[protocol.ID]BaseLimit{\n\t\t\t\tprotoB: {\n\t\t\t\t\tMemory:          8192,\n\t\t\t\t\tStreamsInbound:  1,\n\t\t\t\t\tStreamsOutbound: 1,\n\t\t\t\t\tStreams:         2,\n\t\t\t\t},\n\t\t\t},\n\t\t\tpeerDefault: BaseLimit{\n\t\t\t\tMemory:          4096,\n\t\t\t\tStreamsInbound:  1,\n\t\t\t\tStreamsOutbound: 1,\n\t\t\t\tStreams:         2,\n\t\t\t\tConnsInbound:    1,\n\t\t\t\tConnsOutbound:   1,\n\t\t\t\tConns:           2,\n\t\t\t\tFD:              1,\n\t\t\t},\n\t\t\tprotocolPeerDefault: BaseLimit{\n\t\t\t\tMemory:          4096,\n\t\t\t\tStreamsInbound:  5,\n\t\t\t\tStreamsOutbound: 5,\n\t\t\t\tStreams:         10,\n\t\t\t},\n\t\t\tpeer: map[peer.ID]BaseLimit{\n\t\t\t\tpeerA: {\n\t\t\t\t\tMemory:          8192,\n\t\t\t\t\tStreamsInbound:  2,\n\t\t\t\t\tStreamsOutbound: 2,\n\t\t\t\t\tStreams:         4,\n\t\t\t\t\tConnsInbound:    2,\n\t\t\t\t\tConnsOutbound:   2,\n\t\t\t\t\tConns:           4,\n\t\t\t\t\tFD:              1,\n\t\t\t\t},\n\t\t\t},\n\t\t\tconn: BaseLimit{\n\t\t\t\tMemory:        4096,\n\t\t\t\tConnsInbound:  1,\n\t\t\t\tConnsOutbound: 1,\n\t\t\t\tConns:         1,\n\t\t\t\tFD:            1,\n\t\t\t},\n\t\t\tstream: BaseLimit{\n\t\t\t\tMemory:          4096,\n\t\t\t\tStreamsInbound:  1,\n\t\t\t\tStreamsOutbound: 1,\n\t\t\t\tStreams:         1,\n\t\t\t},\n\t\t}),\n\t)\n\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tmgr := nmgr.(*resourceManager)\n\tdefer mgr.Close()\n\n\tcheckRefCnt := func(s *resourceScope, count int) {\n\t\tt.Helper()\n\t\tif refCnt := s.refCnt; refCnt != count {\n\t\t\tt.Fatalf(\"expected refCnt of %d, got %d\", count, refCnt)\n\t\t}\n\t}\n\tcheckSystem := func(check func(s *resourceScope)) {\n\t\tif err := mgr.ViewSystem(func(s network.ResourceScope) error {\n\t\t\tcheck(s.(*systemScope).resourceScope)\n\t\t\treturn nil\n\t\t}); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n\tcheckTransient := func(check func(s *resourceScope)) {\n\t\tif err := mgr.ViewTransient(func(s network.ResourceScope) error {\n\t\t\tcheck(s.(*transientScope).resourceScope)\n\t\t\treturn nil\n\t\t}); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n\tcheckService := func(svc string, check func(s *resourceScope)) {\n\t\tif err := mgr.ViewService(svc, func(s network.ServiceScope) error {\n\t\t\tcheck(s.(*serviceScope).resourceScope)\n\t\t\treturn nil\n\t\t}); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n\tcheckProtocol := func(p protocol.ID, check func(s *resourceScope)) {\n\t\tif err := mgr.ViewProtocol(p, func(s network.ProtocolScope) error {\n\t\t\tcheck(s.(*protocolScope).resourceScope)\n\t\t\treturn nil\n\t\t}); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n\tcheckPeer := func(p peer.ID, check func(s *resourceScope)) {\n\t\tif err := mgr.ViewPeer(p, func(s network.PeerScope) error {\n\t\t\tcheck(s.(*peerScope).resourceScope)\n\t\t\treturn nil\n\t\t}); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n\n\tcheckSystem(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{})\n\t})\n\tcheckTransient(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 1)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{})\n\t})\n\n\t// open an inbound connection, using an fd\n\tconn, err := mgr.OpenConnection(network.DirInbound, true, dummyMA)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcheckSystem(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 3)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumConnsInbound: 1, NumFD: 1})\n\t})\n\tcheckTransient(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumConnsInbound: 1, NumFD: 1})\n\t})\n\n\t// the connection is transient, we shouldn't be able to open a second one\n\tif _, err := mgr.OpenConnection(network.DirInbound, true, dummyMA); err == nil {\n\t\tt.Fatal(\"expected OpenConnection to fail\")\n\t}\n\tif _, err := mgr.OpenConnection(network.DirInbound, false, dummyMA); err == nil {\n\t\tt.Fatal(\"expected OpenConnection to fail\")\n\t}\n\n\t// close it to check resources are reclaimed\n\tconn.Done()\n\n\tcheckSystem(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{})\n\t})\n\tcheckTransient(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 1)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{})\n\t})\n\n\t// open another inbound connection, using an fd\n\tconn1, err := mgr.OpenConnection(network.DirInbound, true, dummyMA)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcheckSystem(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 3)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumConnsInbound: 1, NumFD: 1})\n\t})\n\tcheckTransient(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumConnsInbound: 1, NumFD: 1})\n\t})\n\n\t// check nility of current peer scope\n\tif conn1.PeerScope() != nil {\n\t\tt.Fatal(\"peer scope should be nil\")\n\t}\n\n\t// attach to a peer\n\tif err := conn1.SetPeer(peerA); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcheckPeer(peerA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumConnsInbound: 1, NumFD: 1})\n\t})\n\tcheckSystem(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 4)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumConnsInbound: 1, NumFD: 1})\n\t})\n\tcheckTransient(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 1)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{})\n\t})\n\n\t// we should be able to open a second transient connection now\n\tconn2, err := mgr.OpenConnection(network.DirInbound, true, dummyMA)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcheckSystem(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 5)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumConnsInbound: 2, NumFD: 2})\n\t})\n\tcheckTransient(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumConnsInbound: 1, NumFD: 1})\n\t})\n\n\t// but we shouldn't be able to attach it to the same peer due to the fd limit\n\tif err := conn2.SetPeer(peerA); err == nil {\n\t\tt.Fatal(\"expected SetPeer to fail\")\n\t}\n\n\tcheckPeer(peerA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumConnsInbound: 1, NumFD: 1})\n\t})\n\tcheckSystem(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 5)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumConnsInbound: 2, NumFD: 2})\n\t})\n\tcheckTransient(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumConnsInbound: 1, NumFD: 1})\n\t})\n\n\t// close it and reopen without using an FD -- we should be able to attach now\n\tconn2.Done()\n\n\tconn2, err = mgr.OpenConnection(network.DirInbound, false, dummyMA)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcheckSystem(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 5)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumConnsInbound: 2, NumFD: 1})\n\t})\n\tcheckTransient(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumConnsInbound: 1, NumFD: 0})\n\t})\n\n\tif err := conn2.SetPeer(peerA); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcheckPeer(peerA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 3)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumConnsInbound: 2, NumFD: 1})\n\t})\n\tcheckSystem(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 5)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumConnsInbound: 2, NumFD: 1})\n\t})\n\tcheckTransient(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 1)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{})\n\t})\n\n\t// open a stream\n\tstream, err := mgr.OpenStream(peerA, network.DirInbound)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcheckPeer(peerA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 4)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1, NumConnsInbound: 2, NumFD: 1})\n\t})\n\tcheckSystem(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 6)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1, NumConnsInbound: 2, NumFD: 1})\n\t})\n\tcheckTransient(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1})\n\t})\n\n\t// the stream is transient we shouldn't be able to open a second one\n\tif _, err := mgr.OpenStream(peerA, network.DirInbound); err == nil {\n\t\tt.Fatal(\"expected OpenStream to fail\")\n\t}\n\n\t// close the stream to check resource reclamation\n\tstream.Done()\n\n\tcheckPeer(peerA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 3)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumConnsInbound: 2, NumFD: 1})\n\t})\n\tcheckSystem(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 5)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumConnsInbound: 2, NumFD: 1})\n\t})\n\tcheckTransient(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 1)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{})\n\t})\n\n\t// open another stream, but this time attach it to a protocol\n\tstream1, err := mgr.OpenStream(peerA, network.DirInbound)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcheckPeer(peerA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 4)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1, NumConnsInbound: 2, NumFD: 1})\n\t})\n\tcheckSystem(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 6)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1, NumConnsInbound: 2, NumFD: 1})\n\t})\n\tcheckTransient(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1})\n\t})\n\n\t// check nility of protocol scope\n\tif stream1.ProtocolScope() != nil {\n\t\tt.Fatal(\"protocol scope should be nil\")\n\t}\n\n\tif err := stream1.SetProtocol(protoA); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcheckPeer(peerA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 4)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1, NumConnsInbound: 2, NumFD: 1})\n\t})\n\tcheckProtocol(protoA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1})\n\t})\n\tcheckSystem(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 7)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1, NumConnsInbound: 2, NumFD: 1})\n\t})\n\tcheckTransient(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 1)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{})\n\t})\n\n\t// and now we should be able to open another stream and attach it to the protocol\n\tstream2, err := mgr.OpenStream(peerA, network.DirInbound)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcheckPeer(peerA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 5)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 2, NumConnsInbound: 2, NumFD: 1})\n\t})\n\tcheckSystem(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 8)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 2, NumConnsInbound: 2, NumFD: 1})\n\t})\n\tcheckTransient(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1})\n\t})\n\n\tif err := stream2.SetProtocol(protoA); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcheckPeer(peerA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 5)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 2, NumConnsInbound: 2, NumFD: 1})\n\t})\n\tcheckProtocol(protoA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 3)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 2})\n\t})\n\tcheckSystem(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 8)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 2, NumConnsInbound: 2, NumFD: 1})\n\t})\n\tcheckTransient(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 1)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{})\n\t})\n\n\t// open a 3rd stream, and try to attach it to the same protocol\n\tstream3, err := mgr.OpenStream(peerB, network.DirInbound)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcheckPeer(peerA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 5)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 2, NumConnsInbound: 2, NumFD: 1})\n\t})\n\tcheckPeer(peerB, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1})\n\t})\n\tcheckSystem(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 10)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 3, NumConnsInbound: 2, NumFD: 1})\n\t})\n\tcheckTransient(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1})\n\t})\n\n\tif err := stream3.SetProtocol(protoA); err == nil {\n\t\tt.Fatal(\"expected SetProtocol to fail\")\n\t}\n\n\tcheckPeer(peerA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 5)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 2, NumConnsInbound: 2, NumFD: 1})\n\t})\n\tcheckPeer(peerB, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1})\n\t})\n\tcheckProtocol(protoA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 3)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 2})\n\t})\n\tcheckSystem(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 10)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 3, NumConnsInbound: 2, NumFD: 1})\n\t})\n\tcheckTransient(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1})\n\t})\n\n\t// but we should be able to set to another protocol\n\tif err := stream3.SetProtocol(protoB); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcheckPeer(peerA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 5)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 2, NumConnsInbound: 2, NumFD: 1})\n\t})\n\tcheckPeer(peerB, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1})\n\t})\n\tcheckProtocol(protoA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 3)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 2})\n\t})\n\tcheckProtocol(protoB, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1})\n\t})\n\tcheckSystem(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 11)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 3, NumConnsInbound: 2, NumFD: 1})\n\t})\n\tcheckTransient(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 1)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{})\n\t})\n\n\t// check nility of current service scope\n\tif stream1.ServiceScope() != nil {\n\t\tt.Fatal(\"service scope should be nil\")\n\t}\n\n\t// we should be able to attach stream1 and stream2 to svcA, but stream3 should fail due to limit\n\tif err := stream1.SetService(svcA); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcheckPeer(peerA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 5)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 2, NumConnsInbound: 2, NumFD: 1})\n\t})\n\tcheckPeer(peerB, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1})\n\t})\n\tcheckService(svcA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1})\n\t})\n\tcheckProtocol(protoA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 3)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 2})\n\t})\n\tcheckProtocol(protoB, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1})\n\t})\n\tcheckSystem(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 12)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 3, NumConnsInbound: 2, NumFD: 1})\n\t})\n\tcheckTransient(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 1)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{})\n\t})\n\n\tif err := stream2.SetService(svcA); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcheckPeer(peerA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 5)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 2, NumConnsInbound: 2, NumFD: 1})\n\t})\n\tcheckPeer(peerB, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1})\n\t})\n\tcheckService(svcA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 3)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 2})\n\t})\n\tcheckProtocol(protoA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 3)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 2})\n\t})\n\tcheckProtocol(protoB, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1})\n\t})\n\tcheckSystem(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 12)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 3, NumConnsInbound: 2, NumFD: 1})\n\t})\n\tcheckTransient(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 1)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{})\n\t})\n\n\tif err := stream3.SetService(svcA); err == nil {\n\t\tt.Fatal(\"expected SetService to fail\")\n\t}\n\n\tcheckPeer(peerA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 5)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 2, NumConnsInbound: 2, NumFD: 1})\n\t})\n\tcheckPeer(peerB, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1})\n\t})\n\tcheckService(svcA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 3)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 2})\n\t})\n\tcheckProtocol(protoA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 3)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 2})\n\t})\n\tcheckProtocol(protoB, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1})\n\t})\n\tcheckSystem(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 12)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 3, NumConnsInbound: 2, NumFD: 1})\n\t})\n\tcheckTransient(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 1)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{})\n\t})\n\n\t// and now let's reclaim our resources to make sure we can gc unused peer and proto scopes\n\t// but first check internal refs\n\tmgr.mx.Lock()\n\t_, okProtoA := mgr.proto[protoA]\n\t_, okProtoB := mgr.proto[protoB]\n\t_, okPeerA := mgr.peer[peerA]\n\t_, okPeerB := mgr.peer[peerB]\n\tmgr.mx.Unlock()\n\n\tif !okProtoA {\n\t\tt.Fatal(\"protocol scope is not stored\")\n\t}\n\tif !okProtoB {\n\t\tt.Fatal(\"protocol scope is not stored\")\n\t}\n\tif !okPeerA {\n\t\tt.Fatal(\"peer scope is not stored\")\n\t}\n\tif !okPeerB {\n\t\tt.Fatal(\"peer scope is not stored\")\n\t}\n\n\t// ok, reclaim\n\tstream1.Done()\n\tstream2.Done()\n\tstream3.Done()\n\tconn1.Done()\n\tconn2.Done()\n\n\t// check everything released\n\tcheckPeer(peerA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 1)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{})\n\t})\n\tcheckPeer(peerB, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 1)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{})\n\t})\n\tcheckService(svcA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 1)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{})\n\t})\n\tcheckProtocol(protoA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 1)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{})\n\t})\n\tcheckProtocol(protoB, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 1)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{})\n\t})\n\tcheckSystem(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 7)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{})\n\t})\n\tcheckTransient(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 1)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{})\n\t})\n\n\tmgr.gc()\n\n\tcheckSystem(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 3)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{})\n\t})\n\tcheckTransient(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 1)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{})\n\t})\n\n\tmgr.mx.Lock()\n\tlenProto := len(mgr.proto)\n\tlenPeer := len(mgr.peer)\n\tmgr.mx.Unlock()\n\n\tif lenProto != 0 {\n\t\tt.Fatal(\"protocols were not gc'ed\")\n\t}\n\tif lenPeer != 0 {\n\t\tt.Fatal(\"perrs were not gc'ed\")\n\t}\n\n\t// check that per protocol peer scopes work as intended\n\tstream1, err = mgr.OpenStream(peerA, network.DirInbound)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcheckPeer(peerA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1})\n\t})\n\tcheckSystem(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 5)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1})\n\t})\n\tcheckTransient(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1})\n\t})\n\n\tif err := stream1.SetProtocol(protoB); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcheckPeer(peerA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1})\n\t})\n\tcheckProtocol(protoB, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1})\n\t})\n\tcheckSystem(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 6)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1})\n\t})\n\tcheckTransient(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 1)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{})\n\t})\n\n\tstream2, err = mgr.OpenStream(peerA, network.DirInbound)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcheckPeer(peerA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 3)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 2})\n\t})\n\tcheckSystem(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 7)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 2})\n\t})\n\tcheckTransient(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1})\n\t})\n\n\tif err := stream2.SetProtocol(protoB); err == nil {\n\t\tt.Fatal(\"expected SetProtocol to fail\")\n\t}\n\n\tcheckPeer(peerA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 3)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 2})\n\t})\n\tcheckSystem(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 7)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 2})\n\t})\n\tcheckTransient(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1})\n\t})\n\tcheckProtocol(protoB, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1})\n\t})\n\n\tstream1.Done()\n\tstream2.Done()\n\n\t// check that per service peer scopes work as intended\n\tstream1, err = mgr.OpenStream(peerA, network.DirInbound)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcheckPeer(peerA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1})\n\t})\n\tcheckSystem(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 6)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1})\n\t})\n\tcheckTransient(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1})\n\t})\n\n\tif err := stream1.SetProtocol(protoA); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcheckPeer(peerA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1})\n\t})\n\tcheckProtocol(protoA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1})\n\t})\n\tcheckSystem(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 7)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1})\n\t})\n\tcheckTransient(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 1)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{})\n\t})\n\n\tstream2, err = mgr.OpenStream(peerA, network.DirInbound)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcheckPeer(peerA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 3)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 2})\n\t})\n\tcheckSystem(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 8)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 2})\n\t})\n\tcheckTransient(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1})\n\t})\n\n\tif err := stream2.SetProtocol(protoA); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcheckPeer(peerA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 3)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 2})\n\t})\n\tcheckProtocol(protoA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 3)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 2})\n\t})\n\tcheckSystem(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 8)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 2})\n\t})\n\tcheckTransient(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 1)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{})\n\t})\n\n\tif err := stream1.SetService(svcB); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcheckPeer(peerA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 3)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 2})\n\t})\n\tcheckService(svcB, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 2)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1})\n\t})\n\tcheckProtocol(protoA, func(s *resourceScope) {\n\t\tcheckRefCnt(s, 3)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 2})\n\t})\n\tcheckSystem(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 9)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 2})\n\t})\n\tcheckTransient(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 1)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{})\n\t})\n\n\t// now we should fail to set the service for stream2 to svcB because of the service peer limit\n\tif err := stream2.SetService(svcB); err == nil {\n\t\tt.Fatal(\"expected SetService to fail\")\n\t}\n\n\t// now release resources and check interior gc of per service peer scopes\n\tstream1.Done()\n\tstream2.Done()\n\n\tmgr.gc()\n\n\tcheckSystem(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 4)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{})\n\t})\n\tcheckTransient(func(s *resourceScope) {\n\t\tcheckRefCnt(s, 1)\n\t\tcheckResources(t, &s.rc, network.ScopeStat{})\n\t})\n\n\tmgr.mx.Lock()\n\tlenProto = len(mgr.proto)\n\tlenPeer = len(mgr.peer)\n\tmgr.mx.Unlock()\n\n\tsvc := mgr.svc[svcB]\n\tsvc.Lock()\n\tlenSvcPeer := len(svc.peers)\n\tsvc.Unlock()\n\n\tif lenProto != 0 {\n\t\tt.Fatal(\"protocols were not gc'ed\")\n\t}\n\tif lenPeer != 0 {\n\t\tt.Fatal(\"peers were not gc'ed\")\n\t}\n\tif lenSvcPeer != 0 {\n\t\tt.Fatal(\"service peers were not gc'ed\")\n\t}\n\n}\n\nfunc TestResourceManagerWithAllowlist(t *testing.T) {\n\tpeerA := test.RandPeerIDFatal(t)\n\n\tlimits := DefaultLimits.AutoScale()\n\tlimits.system.Conns = 0\n\tlimits.transient.Conns = 0\n\n\tbaseLimit := BaseLimit{\n\t\tConns:         2,\n\t\tConnsInbound:  2,\n\t\tConnsOutbound: 1,\n\t}\n\tbaseLimit.Apply(limits.allowlistedSystem)\n\tlimits.allowlistedSystem = baseLimit\n\n\tbaseLimit = BaseLimit{\n\t\tConns:         1,\n\t\tConnsInbound:  1,\n\t\tConnsOutbound: 1,\n\t}\n\tbaseLimit.Apply(limits.allowlistedTransient)\n\tlimits.allowlistedTransient = baseLimit\n\n\trcmgr, err := NewResourceManager(NewFixedLimiter(limits), WithAllowlistedMultiaddrs([]multiaddr.Multiaddr{\n\t\tmultiaddr.StringCast(\"/ip4/1.2.3.4\"),\n\t\tmultiaddr.StringCast(\"/ip4/4.3.2.1/p2p/\" + peerA.String()),\n\t}))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer rcmgr.Close()\n\n\tableToGetAllowlist := GetAllowlist(rcmgr)\n\tif ableToGetAllowlist == nil {\n\t\tt.Fatal(\"Expected to be able to get the allowlist\")\n\t}\n\n\t// A connection comes in from a non-allowlisted ip address\n\t_, err = rcmgr.OpenConnection(network.DirInbound, true, multiaddr.StringCast(\"/ip4/1.2.3.5\"))\n\tif err == nil {\n\t\tt.Fatalf(\"Expected this to fail. err=%v\", err)\n\t}\n\n\t// A connection comes in from an allowlisted ip address\n\tconnScope, err := rcmgr.OpenConnection(network.DirInbound, true, multiaddr.StringCast(\"/ip4/1.2.3.4\"))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = connScope.SetPeer(test.RandPeerIDFatal(t))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// A connection comes in that looks like it should be allowlisted, but then has the wrong peer id.\n\tconnScope, err = rcmgr.OpenConnection(network.DirInbound, true, multiaddr.StringCast(\"/ip4/4.3.2.1\"))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = connScope.SetPeer(test.RandPeerIDFatal(t))\n\tif err == nil {\n\t\tt.Fatalf(\"Expected this to fail. err=%v\", err)\n\t}\n\n\t// A connection comes in that looks like it should be allowlisted, and it has the allowlisted peer id\n\tconnScope, err = rcmgr.OpenConnection(network.DirInbound, true, multiaddr.StringCast(\"/ip4/4.3.2.1\"))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = connScope.SetPeer(peerA)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\n// TestAllowlistAndConnLimiterPlayNice checks that the connLimiter learns about network prefix limits from the allowlist.\nfunc TestAllowlistAndConnLimiterPlayNice(t *testing.T) {\n\tlimits := DefaultLimits.AutoScale()\n\tlimits.allowlistedSystem.Conns = 8\n\tlimits.allowlistedSystem.ConnsInbound = 8\n\tlimits.allowlistedSystem.ConnsOutbound = 8\n\tt.Run(\"IPv4\", func(t *testing.T) {\n\t\trcmgr, err := NewResourceManager(NewFixedLimiter(limits), WithAllowlistedMultiaddrs([]multiaddr.Multiaddr{\n\t\t\tmultiaddr.StringCast(\"/ip4/1.2.3.0/ipcidr/24\"),\n\t\t}), WithNetworkPrefixLimit([]NetworkPrefixLimit{}, []NetworkPrefixLimit{}))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer rcmgr.Close()\n\n\t\t// The connLimiter should have the allowlisted network prefix\n\t\trequire.Equal(t, netip.MustParsePrefix(\"1.2.3.0/24\"), rcmgr.(*resourceManager).connLimiter.networkPrefixLimitV4[0].Network)\n\n\t\t// The connLimiter should use the limit from the allowlist\n\t\trequire.Equal(t, 8, rcmgr.(*resourceManager).connLimiter.networkPrefixLimitV4[0].ConnCount)\n\t})\n\tt.Run(\"IPv6\", func(t *testing.T) {\n\t\trcmgr, err := NewResourceManager(NewFixedLimiter(limits), WithAllowlistedMultiaddrs([]multiaddr.Multiaddr{\n\t\t\tmultiaddr.StringCast(\"/ip6/1:2:3::/ipcidr/58\"),\n\t\t}), WithNetworkPrefixLimit([]NetworkPrefixLimit{}, []NetworkPrefixLimit{}))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer rcmgr.Close()\n\n\t\t// The connLimiter should have the allowlisted network prefix\n\t\trequire.Equal(t, netip.MustParsePrefix(\"1:2:3::/58\"), rcmgr.(*resourceManager).connLimiter.networkPrefixLimitV6[0].Network)\n\n\t\t// The connLimiter should use the limit from the allowlist\n\t\trequire.Equal(t, 8, rcmgr.(*resourceManager).connLimiter.networkPrefixLimitV6[0].ConnCount)\n\t})\n\n\tt.Run(\"Does not override if you set a limit directly\", func(t *testing.T) {\n\t\trcmgr, err := NewResourceManager(NewFixedLimiter(limits), WithAllowlistedMultiaddrs([]multiaddr.Multiaddr{\n\t\t\tmultiaddr.StringCast(\"/ip4/1.2.3.0/ipcidr/24\"),\n\t\t}), WithNetworkPrefixLimit([]NetworkPrefixLimit{\n\t\t\t{Network: netip.MustParsePrefix(\"1.2.3.0/24\"), ConnCount: 1},\n\t\t}, []NetworkPrefixLimit{}))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer rcmgr.Close()\n\n\t\t// The connLimiter should have it because we set it\n\t\trequire.Equal(t, netip.MustParsePrefix(\"1.2.3.0/24\"), rcmgr.(*resourceManager).connLimiter.networkPrefixLimitV4[0].Network)\n\t\t// should only have one network prefix limit\n\t\trequire.Equal(t, 1, len(rcmgr.(*resourceManager).connLimiter.networkPrefixLimitV4))\n\n\t\t// The connLimiter should use the limit we defined explicitly\n\t\trequire.Equal(t, 1, rcmgr.(*resourceManager).connLimiter.networkPrefixLimitV4[0].ConnCount)\n\t})\n}\n\nfunc TestResourceManagerRateLimiting(t *testing.T) {\n\t// Create a resource manager with very low rate limits\n\tlimits := DefaultLimits.AutoScale()\n\tlimits.system.Conns = 100 // High enough to not be the limiting factor\n\tlimits.transient.Conns = 100\n\n\t// Create limiters with very low RPS\n\tlimiter := &rate.Limiter{\n\t\tGlobalLimit: rate.Limit{RPS: 0.00001, Burst: 2},\n\t}\n\n\trcmgr, err := NewResourceManager(NewFixedLimiter(limits), WithConnRateLimiters(limiter))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer rcmgr.Close()\n\n\taddr := multiaddr.StringCast(\"/ip4/1.2.3.4\")\n\n\tconnScope, err := rcmgr.OpenConnection(network.DirInbound, true, addr)\n\trequire.NoError(t, err)\n\tconnScope.Done()\n\n\tconnScope, err = rcmgr.OpenConnection(network.DirInbound, true, addr)\n\trequire.NoError(t, err)\n\tconnScope.Done()\n\n\t_, err = rcmgr.OpenConnection(network.DirInbound, true, addr)\n\trequire.ErrorContains(t, err, \"rate limit exceeded\")\n}\n\nfunc TestVerifySourceAddressRateLimiter(t *testing.T) {\n\tlimits := DefaultLimits.AutoScale()\n\tlimits.allowlistedSystem.Conns = 100\n\tlimits.allowlistedSystem.ConnsInbound = 100\n\tlimits.allowlistedSystem.ConnsOutbound = 100\n\n\trcmgr, err := NewResourceManager(NewFixedLimiter(limits), WithLimitPerSubnet([]ConnLimitPerSubnet{\n\t\t{PrefixLength: 32, ConnCount: 2},\n\t}, []ConnLimitPerSubnet{}))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer rcmgr.Close()\n\n\tna1 := &net.UDPAddr{\n\t\tIP:   net.ParseIP(\"1.2.3.4\"),\n\t\tPort: 1234,\n\t}\n\trequire.False(t, rcmgr.VerifySourceAddress(na1))\n\trequire.True(t, rcmgr.VerifySourceAddress(na1))\n\n\tna2 := &net.UDPAddr{\n\t\tIP:   net.ParseIP(\"1.2.3.5\"),\n\t\tPort: 1234,\n\t}\n\trequire.False(t, rcmgr.VerifySourceAddress(na2))\n\trequire.True(t, rcmgr.VerifySourceAddress(na2))\n}\n"
  },
  {
    "path": "p2p/host/resource-manager/scope.go",
    "content": "package rcmgr\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\t\"math/big\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n)\n\n// resources tracks the current state of resource consumption\ntype resources struct {\n\tlimit Limit\n\n\tnconnsIn, nconnsOut     int\n\tnstreamsIn, nstreamsOut int\n\tnfd                     int\n\n\tmemory int64\n}\n\n// A resourceScope can be a DAG, where a downstream node is not allowed to outlive an upstream node\n// (ie cannot call Done in the upstream node before the downstream node) and account for resources\n// using a linearized parent set.\n// A resourceScope can be a span scope, where it has a specific owner; span scopes create a tree rooted\n// at the owner (which can be a DAG scope) and can outlive their parents -- this is important because\n// span scopes are the main *user* interface for memory management, and the user may call\n// Done in a span scope after the system has closed the root of the span tree in some background\n// goroutine.\n// If we didn't make this distinction we would have a double release problem in that case.\ntype resourceScope struct {\n\tsync.Mutex\n\tdone   bool\n\trefCnt int\n\n\tspanID int\n\n\trc    resources\n\towner *resourceScope   // set in span scopes, which define trees\n\tedges []*resourceScope // set in DAG scopes, it's the linearized parent set\n\n\tname    string   // for debugging purposes\n\ttrace   *trace   // debug tracing\n\tmetrics *metrics // metrics collection\n}\n\nvar _ network.ResourceScope = (*resourceScope)(nil)\nvar _ network.ResourceScopeSpan = (*resourceScope)(nil)\n\nfunc newResourceScope(limit Limit, edges []*resourceScope, name string, trace *trace, metrics *metrics) *resourceScope {\n\tfor _, e := range edges {\n\t\te.IncRef()\n\t}\n\tr := &resourceScope{\n\t\trc:      resources{limit: limit},\n\t\tedges:   edges,\n\t\tname:    name,\n\t\ttrace:   trace,\n\t\tmetrics: metrics,\n\t}\n\tr.trace.CreateScope(name, limit)\n\treturn r\n}\n\nfunc newResourceScopeSpan(owner *resourceScope, id int) *resourceScope {\n\tr := &resourceScope{\n\t\trc:      resources{limit: owner.rc.limit},\n\t\towner:   owner,\n\t\tname:    fmt.Sprintf(\"%s.span-%d\", owner.name, id),\n\t\ttrace:   owner.trace,\n\t\tmetrics: owner.metrics,\n\t}\n\tr.trace.CreateScope(r.name, r.rc.limit)\n\treturn r\n}\n\n// IsSpan will return true if this name was created by newResourceScopeSpan\nfunc IsSpan(name string) bool {\n\treturn strings.Contains(name, \".span-\")\n}\n\nfunc addInt64WithOverflow(a int64, b int64) (c int64, ok bool) {\n\tc = a + b\n\treturn c, (c > a) == (b > 0)\n}\n\n// mulInt64WithOverflow checks for overflow in multiplying two int64s. See\n// https://groups.google.com/g/golang-nuts/c/h5oSN5t3Au4/m/KaNQREhZh0QJ\nfunc mulInt64WithOverflow(a, b int64) (c int64, ok bool) {\n\tconst mostPositive = 1<<63 - 1\n\tconst mostNegative = -(mostPositive + 1)\n\tc = a * b\n\tif a == 0 || b == 0 || a == 1 || b == 1 {\n\t\treturn c, true\n\t}\n\tif a == mostNegative || b == mostNegative {\n\t\treturn c, false\n\t}\n\treturn c, c/b == a\n}\n\n// Resources implementation\nfunc (rc *resources) checkMemory(rsvp int64, prio uint8) error {\n\tif rsvp < 0 {\n\t\treturn fmt.Errorf(\"can't reserve negative memory. rsvp=%v\", rsvp)\n\t}\n\n\tlimit := rc.limit.GetMemoryLimit()\n\tif limit == math.MaxInt64 {\n\t\t// Special case where we've set max limits.\n\t\treturn nil\n\t}\n\n\tnewmem, addOk := addInt64WithOverflow(rc.memory, rsvp)\n\n\tthreshold, mulOk := mulInt64WithOverflow(1+int64(prio), limit)\n\tif !mulOk {\n\t\tthresholdBig := big.NewInt(limit)\n\t\tthresholdBig.Mul(thresholdBig, big.NewInt(1+int64(prio)))\n\t\tthresholdBig.Rsh(thresholdBig, 8) // Divide 256\n\t\t// necessarily a Int64 since we multiplied a int64 != MaxInt64 with\n\t\t// a uint8+1 (max 255+1 = 256) and divided by 256\n\t\tthreshold = thresholdBig.Int64()\n\t} else {\n\t\tthreshold = threshold / 256\n\t}\n\n\tif !addOk || newmem > threshold {\n\t\treturn &ErrMemoryLimitExceeded{\n\t\t\tcurrent:   rc.memory,\n\t\t\tattempted: rsvp,\n\t\t\tlimit:     limit,\n\t\t\tpriority:  prio,\n\t\t\terr:       network.ErrResourceLimitExceeded,\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (rc *resources) reserveMemory(size int64, prio uint8) error {\n\tif err := rc.checkMemory(size, prio); err != nil {\n\t\treturn err\n\t}\n\n\trc.memory += size\n\treturn nil\n}\n\nfunc (rc *resources) releaseMemory(size int64) {\n\trc.memory -= size\n\n\t// sanity check for bugs upstream\n\tif rc.memory < 0 {\n\t\tlog.Warn(\"BUG: too much memory released\")\n\t\trc.memory = 0\n\t}\n}\n\nfunc (rc *resources) addStream(dir network.Direction) error {\n\tif dir == network.DirInbound {\n\t\treturn rc.addStreams(1, 0)\n\t}\n\treturn rc.addStreams(0, 1)\n}\n\nfunc (rc *resources) addStreams(incount, outcount int) error {\n\tif incount > 0 {\n\t\tlimit := rc.limit.GetStreamLimit(network.DirInbound)\n\t\tif rc.nstreamsIn+incount > limit {\n\t\t\treturn &ErrStreamOrConnLimitExceeded{\n\t\t\t\tcurrent:   rc.nstreamsIn,\n\t\t\t\tattempted: incount,\n\t\t\t\tlimit:     limit,\n\t\t\t\terr:       fmt.Errorf(\"cannot reserve inbound stream: %w\", network.ErrResourceLimitExceeded),\n\t\t\t}\n\t\t}\n\t}\n\tif outcount > 0 {\n\t\tlimit := rc.limit.GetStreamLimit(network.DirOutbound)\n\t\tif rc.nstreamsOut+outcount > limit {\n\t\t\treturn &ErrStreamOrConnLimitExceeded{\n\t\t\t\tcurrent:   rc.nstreamsOut,\n\t\t\t\tattempted: outcount,\n\t\t\t\tlimit:     limit,\n\t\t\t\terr:       fmt.Errorf(\"cannot reserve outbound stream: %w\", network.ErrResourceLimitExceeded),\n\t\t\t}\n\t\t}\n\t}\n\n\tif limit := rc.limit.GetStreamTotalLimit(); rc.nstreamsIn+incount+rc.nstreamsOut+outcount > limit {\n\t\treturn &ErrStreamOrConnLimitExceeded{\n\t\t\tcurrent:   rc.nstreamsIn + rc.nstreamsOut,\n\t\t\tattempted: incount + outcount,\n\t\t\tlimit:     limit,\n\t\t\terr:       fmt.Errorf(\"cannot reserve stream: %w\", network.ErrResourceLimitExceeded),\n\t\t}\n\t}\n\n\trc.nstreamsIn += incount\n\trc.nstreamsOut += outcount\n\treturn nil\n}\n\nfunc (rc *resources) removeStream(dir network.Direction) {\n\tif dir == network.DirInbound {\n\t\trc.removeStreams(1, 0)\n\t} else {\n\t\trc.removeStreams(0, 1)\n\t}\n}\n\nfunc (rc *resources) removeStreams(incount, outcount int) {\n\trc.nstreamsIn -= incount\n\trc.nstreamsOut -= outcount\n\n\tif rc.nstreamsIn < 0 {\n\t\tlog.Warn(\"BUG: too many inbound streams released\")\n\t\trc.nstreamsIn = 0\n\t}\n\tif rc.nstreamsOut < 0 {\n\t\tlog.Warn(\"BUG: too many outbound streams released\")\n\t\trc.nstreamsOut = 0\n\t}\n}\n\nfunc (rc *resources) addConn(dir network.Direction, usefd bool) error {\n\tvar fd int\n\tif usefd {\n\t\tfd = 1\n\t}\n\n\tif dir == network.DirInbound {\n\t\treturn rc.addConns(1, 0, fd)\n\t}\n\n\treturn rc.addConns(0, 1, fd)\n}\n\nfunc (rc *resources) addConns(incount, outcount, fdcount int) error {\n\tif incount > 0 {\n\t\tlimit := rc.limit.GetConnLimit(network.DirInbound)\n\t\tif rc.nconnsIn+incount > limit {\n\t\t\treturn &ErrStreamOrConnLimitExceeded{\n\t\t\t\tcurrent:   rc.nconnsIn,\n\t\t\t\tattempted: incount,\n\t\t\t\tlimit:     limit,\n\t\t\t\terr:       fmt.Errorf(\"cannot reserve inbound connection: %w\", network.ErrResourceLimitExceeded),\n\t\t\t}\n\t\t}\n\t}\n\tif outcount > 0 {\n\t\tlimit := rc.limit.GetConnLimit(network.DirOutbound)\n\t\tif rc.nconnsOut+outcount > limit {\n\t\t\treturn &ErrStreamOrConnLimitExceeded{\n\t\t\t\tcurrent:   rc.nconnsOut,\n\t\t\t\tattempted: outcount,\n\t\t\t\tlimit:     limit,\n\t\t\t\terr:       fmt.Errorf(\"cannot reserve outbound connection: %w\", network.ErrResourceLimitExceeded),\n\t\t\t}\n\t\t}\n\t}\n\n\tif connLimit := rc.limit.GetConnTotalLimit(); rc.nconnsIn+incount+rc.nconnsOut+outcount > connLimit {\n\t\treturn &ErrStreamOrConnLimitExceeded{\n\t\t\tcurrent:   rc.nconnsIn + rc.nconnsOut,\n\t\t\tattempted: incount + outcount,\n\t\t\tlimit:     connLimit,\n\t\t\terr:       fmt.Errorf(\"cannot reserve connection: %w\", network.ErrResourceLimitExceeded),\n\t\t}\n\t}\n\tif fdcount > 0 {\n\t\tlimit := rc.limit.GetFDLimit()\n\t\tif rc.nfd+fdcount > limit {\n\t\t\treturn &ErrStreamOrConnLimitExceeded{\n\t\t\t\tcurrent:   rc.nfd,\n\t\t\t\tattempted: fdcount,\n\t\t\t\tlimit:     limit,\n\t\t\t\terr:       fmt.Errorf(\"cannot reserve file descriptor: %w\", network.ErrResourceLimitExceeded),\n\t\t\t}\n\t\t}\n\t}\n\n\trc.nconnsIn += incount\n\trc.nconnsOut += outcount\n\trc.nfd += fdcount\n\treturn nil\n}\n\nfunc (rc *resources) removeConn(dir network.Direction, usefd bool) {\n\tvar fd int\n\tif usefd {\n\t\tfd = 1\n\t}\n\n\tif dir == network.DirInbound {\n\t\trc.removeConns(1, 0, fd)\n\t} else {\n\t\trc.removeConns(0, 1, fd)\n\t}\n}\n\nfunc (rc *resources) removeConns(incount, outcount, fdcount int) {\n\trc.nconnsIn -= incount\n\trc.nconnsOut -= outcount\n\trc.nfd -= fdcount\n\n\tif rc.nconnsIn < 0 {\n\t\tlog.Warn(\"BUG: too many inbound connections released\")\n\t\trc.nconnsIn = 0\n\t}\n\tif rc.nconnsOut < 0 {\n\t\tlog.Warn(\"BUG: too many outbound connections released\")\n\t\trc.nconnsOut = 0\n\t}\n\tif rc.nfd < 0 {\n\t\tlog.Warn(\"BUG: too many file descriptors released\")\n\t\trc.nfd = 0\n\t}\n}\n\nfunc (rc *resources) stat() network.ScopeStat {\n\treturn network.ScopeStat{\n\t\tMemory:             rc.memory,\n\t\tNumStreamsInbound:  rc.nstreamsIn,\n\t\tNumStreamsOutbound: rc.nstreamsOut,\n\t\tNumConnsInbound:    rc.nconnsIn,\n\t\tNumConnsOutbound:   rc.nconnsOut,\n\t\tNumFD:              rc.nfd,\n\t}\n}\n\n// resourceScope implementation\nfunc (s *resourceScope) wrapError(err error) error {\n\treturn fmt.Errorf(\"%s: %w\", s.name, err)\n}\n\nfunc (s *resourceScope) ReserveMemory(size int, prio uint8) error {\n\ts.Lock()\n\tdefer s.Unlock()\n\n\tif s.done {\n\t\treturn s.wrapError(network.ErrResourceScopeClosed)\n\t}\n\n\tif err := s.rc.reserveMemory(int64(size), prio); err != nil {\n\t\tlog.Debug(\"blocked memory reservation\", logValuesMemoryLimit(s.name, \"\", s.rc.stat(), err)...)\n\t\ts.trace.BlockReserveMemory(s.name, prio, int64(size), s.rc.memory)\n\t\ts.metrics.BlockMemory(size)\n\t\treturn s.wrapError(err)\n\t}\n\n\tif err := s.reserveMemoryForEdges(size, prio); err != nil {\n\t\ts.rc.releaseMemory(int64(size))\n\t\ts.metrics.BlockMemory(size)\n\t\treturn s.wrapError(err)\n\t}\n\n\ts.trace.ReserveMemory(s.name, prio, int64(size), s.rc.memory)\n\ts.metrics.AllowMemory(size)\n\treturn nil\n}\n\nfunc (s *resourceScope) reserveMemoryForEdges(size int, prio uint8) error {\n\tif s.owner != nil {\n\t\treturn s.owner.ReserveMemory(size, prio)\n\t}\n\n\tvar reserved int\n\tvar err error\n\tfor _, e := range s.edges {\n\t\tvar stat network.ScopeStat\n\t\tstat, err = e.ReserveMemoryForChild(int64(size), prio)\n\t\tif err != nil {\n\t\t\tlog.Debug(\"blocked memory reservation from constraining edge\", logValuesMemoryLimit(s.name, e.name, stat, err)...)\n\t\t\tbreak\n\t\t}\n\n\t\treserved++\n\t}\n\n\tif err != nil {\n\t\t// we failed because of a constraint; undo memory reservations\n\t\tfor _, e := range s.edges[:reserved] {\n\t\t\te.ReleaseMemoryForChild(int64(size))\n\t\t}\n\t}\n\n\treturn err\n}\n\nfunc (s *resourceScope) releaseMemoryForEdges(size int) {\n\tif s.owner != nil {\n\t\ts.owner.ReleaseMemory(size)\n\t\treturn\n\t}\n\n\tfor _, e := range s.edges {\n\t\te.ReleaseMemoryForChild(int64(size))\n\t}\n}\n\nfunc (s *resourceScope) ReserveMemoryForChild(size int64, prio uint8) (network.ScopeStat, error) {\n\ts.Lock()\n\tdefer s.Unlock()\n\n\tif s.done {\n\t\treturn s.rc.stat(), s.wrapError(network.ErrResourceScopeClosed)\n\t}\n\n\tif err := s.rc.reserveMemory(size, prio); err != nil {\n\t\ts.trace.BlockReserveMemory(s.name, prio, size, s.rc.memory)\n\t\treturn s.rc.stat(), s.wrapError(err)\n\t}\n\n\ts.trace.ReserveMemory(s.name, prio, size, s.rc.memory)\n\treturn network.ScopeStat{}, nil\n}\n\nfunc (s *resourceScope) ReleaseMemory(size int) {\n\ts.Lock()\n\tdefer s.Unlock()\n\n\tif s.done {\n\t\treturn\n\t}\n\n\ts.rc.releaseMemory(int64(size))\n\ts.releaseMemoryForEdges(size)\n\ts.trace.ReleaseMemory(s.name, int64(size), s.rc.memory)\n}\n\nfunc (s *resourceScope) ReleaseMemoryForChild(size int64) {\n\ts.Lock()\n\tdefer s.Unlock()\n\n\tif s.done {\n\t\treturn\n\t}\n\n\ts.rc.releaseMemory(size)\n\ts.trace.ReleaseMemory(s.name, size, s.rc.memory)\n}\n\nfunc (s *resourceScope) AddStream(dir network.Direction) error {\n\ts.Lock()\n\tdefer s.Unlock()\n\n\tif s.done {\n\t\treturn s.wrapError(network.ErrResourceScopeClosed)\n\t}\n\n\tif err := s.rc.addStream(dir); err != nil {\n\t\tlog.Debug(\"blocked stream\", logValuesStreamLimit(s.name, \"\", dir, s.rc.stat(), err)...)\n\t\ts.trace.BlockAddStream(s.name, dir, s.rc.nstreamsIn, s.rc.nstreamsOut)\n\t\treturn s.wrapError(err)\n\t}\n\n\tif err := s.addStreamForEdges(dir); err != nil {\n\t\ts.rc.removeStream(dir)\n\t\treturn s.wrapError(err)\n\t}\n\n\ts.trace.AddStream(s.name, dir, s.rc.nstreamsIn, s.rc.nstreamsOut)\n\treturn nil\n}\n\nfunc (s *resourceScope) addStreamForEdges(dir network.Direction) error {\n\tif s.owner != nil {\n\t\treturn s.owner.AddStream(dir)\n\t}\n\n\tvar err error\n\tvar reserved int\n\tfor _, e := range s.edges {\n\t\tvar stat network.ScopeStat\n\t\tstat, err = e.AddStreamForChild(dir)\n\t\tif err != nil {\n\t\t\tlog.Debug(\"blocked stream from constraining edge\", logValuesStreamLimit(s.name, e.name, dir, stat, err)...)\n\t\t\tbreak\n\t\t}\n\t\treserved++\n\t}\n\n\tif err != nil {\n\t\tfor _, e := range s.edges[:reserved] {\n\t\t\te.RemoveStreamForChild(dir)\n\t\t}\n\t}\n\n\treturn err\n}\n\nfunc (s *resourceScope) AddStreamForChild(dir network.Direction) (network.ScopeStat, error) {\n\ts.Lock()\n\tdefer s.Unlock()\n\n\tif s.done {\n\t\treturn s.rc.stat(), s.wrapError(network.ErrResourceScopeClosed)\n\t}\n\n\tif err := s.rc.addStream(dir); err != nil {\n\t\ts.trace.BlockAddStream(s.name, dir, s.rc.nstreamsIn, s.rc.nstreamsOut)\n\t\treturn s.rc.stat(), s.wrapError(err)\n\t}\n\n\ts.trace.AddStream(s.name, dir, s.rc.nstreamsIn, s.rc.nstreamsOut)\n\treturn network.ScopeStat{}, nil\n}\n\nfunc (s *resourceScope) RemoveStream(dir network.Direction) {\n\ts.Lock()\n\tdefer s.Unlock()\n\n\tif s.done {\n\t\treturn\n\t}\n\n\ts.rc.removeStream(dir)\n\ts.removeStreamForEdges(dir)\n\ts.trace.RemoveStream(s.name, dir, s.rc.nstreamsIn, s.rc.nstreamsOut)\n}\n\nfunc (s *resourceScope) removeStreamForEdges(dir network.Direction) {\n\tif s.owner != nil {\n\t\ts.owner.RemoveStream(dir)\n\t\treturn\n\t}\n\n\tfor _, e := range s.edges {\n\t\te.RemoveStreamForChild(dir)\n\t}\n}\n\nfunc (s *resourceScope) RemoveStreamForChild(dir network.Direction) {\n\ts.Lock()\n\tdefer s.Unlock()\n\n\tif s.done {\n\t\treturn\n\t}\n\n\ts.rc.removeStream(dir)\n\ts.trace.RemoveStream(s.name, dir, s.rc.nstreamsIn, s.rc.nstreamsOut)\n}\n\nfunc (s *resourceScope) AddConn(dir network.Direction, usefd bool) error {\n\ts.Lock()\n\tdefer s.Unlock()\n\n\tif s.done {\n\t\treturn s.wrapError(network.ErrResourceScopeClosed)\n\t}\n\n\tif err := s.rc.addConn(dir, usefd); err != nil {\n\t\tlog.Debug(\"blocked connection\", logValuesConnLimit(s.name, \"\", dir, usefd, s.rc.stat(), err)...)\n\t\ts.trace.BlockAddConn(s.name, dir, usefd, s.rc.nconnsIn, s.rc.nconnsOut, s.rc.nfd)\n\t\treturn s.wrapError(err)\n\t}\n\n\tif err := s.addConnForEdges(dir, usefd); err != nil {\n\t\ts.rc.removeConn(dir, usefd)\n\t\treturn s.wrapError(err)\n\t}\n\n\ts.trace.AddConn(s.name, dir, usefd, s.rc.nconnsIn, s.rc.nconnsOut, s.rc.nfd)\n\treturn nil\n}\n\nfunc (s *resourceScope) addConnForEdges(dir network.Direction, usefd bool) error {\n\tif s.owner != nil {\n\t\treturn s.owner.AddConn(dir, usefd)\n\t}\n\n\tvar err error\n\tvar reserved int\n\tfor _, e := range s.edges {\n\t\tvar stat network.ScopeStat\n\t\tstat, err = e.AddConnForChild(dir, usefd)\n\t\tif err != nil {\n\t\t\tlog.Debug(\"blocked connection from constraining edge\", logValuesConnLimit(s.name, e.name, dir, usefd, stat, err)...)\n\t\t\tbreak\n\t\t}\n\t\treserved++\n\t}\n\n\tif err != nil {\n\t\tfor _, e := range s.edges[:reserved] {\n\t\t\te.RemoveConnForChild(dir, usefd)\n\t\t}\n\t}\n\n\treturn err\n}\n\nfunc (s *resourceScope) AddConnForChild(dir network.Direction, usefd bool) (network.ScopeStat, error) {\n\ts.Lock()\n\tdefer s.Unlock()\n\n\tif s.done {\n\t\treturn s.rc.stat(), s.wrapError(network.ErrResourceScopeClosed)\n\t}\n\n\tif err := s.rc.addConn(dir, usefd); err != nil {\n\t\ts.trace.BlockAddConn(s.name, dir, usefd, s.rc.nconnsIn, s.rc.nconnsOut, s.rc.nfd)\n\t\treturn s.rc.stat(), s.wrapError(err)\n\t}\n\n\ts.trace.AddConn(s.name, dir, usefd, s.rc.nconnsIn, s.rc.nconnsOut, s.rc.nfd)\n\treturn network.ScopeStat{}, nil\n}\n\nfunc (s *resourceScope) RemoveConn(dir network.Direction, usefd bool) {\n\ts.Lock()\n\tdefer s.Unlock()\n\n\tif s.done {\n\t\treturn\n\t}\n\n\ts.rc.removeConn(dir, usefd)\n\ts.removeConnForEdges(dir, usefd)\n\ts.trace.RemoveConn(s.name, dir, usefd, s.rc.nconnsIn, s.rc.nconnsOut, s.rc.nfd)\n}\n\nfunc (s *resourceScope) removeConnForEdges(dir network.Direction, usefd bool) {\n\tif s.owner != nil {\n\t\ts.owner.RemoveConn(dir, usefd)\n\t}\n\n\tfor _, e := range s.edges {\n\t\te.RemoveConnForChild(dir, usefd)\n\t}\n}\n\nfunc (s *resourceScope) RemoveConnForChild(dir network.Direction, usefd bool) {\n\ts.Lock()\n\tdefer s.Unlock()\n\n\tif s.done {\n\t\treturn\n\t}\n\n\ts.rc.removeConn(dir, usefd)\n\ts.trace.RemoveConn(s.name, dir, usefd, s.rc.nconnsIn, s.rc.nconnsOut, s.rc.nfd)\n}\n\nfunc (s *resourceScope) ReserveForChild(st network.ScopeStat) error {\n\ts.Lock()\n\tdefer s.Unlock()\n\n\tif s.done {\n\t\treturn s.wrapError(network.ErrResourceScopeClosed)\n\t}\n\n\tif err := s.rc.reserveMemory(st.Memory, network.ReservationPriorityAlways); err != nil {\n\t\ts.trace.BlockReserveMemory(s.name, 255, st.Memory, s.rc.memory)\n\t\treturn s.wrapError(err)\n\t}\n\n\tif err := s.rc.addStreams(st.NumStreamsInbound, st.NumStreamsOutbound); err != nil {\n\t\ts.trace.BlockAddStreams(s.name, st.NumStreamsInbound, st.NumStreamsOutbound, s.rc.nstreamsIn, s.rc.nstreamsOut)\n\t\ts.rc.releaseMemory(st.Memory)\n\t\treturn s.wrapError(err)\n\t}\n\n\tif err := s.rc.addConns(st.NumConnsInbound, st.NumConnsOutbound, st.NumFD); err != nil {\n\t\ts.trace.BlockAddConns(s.name, st.NumConnsInbound, st.NumConnsOutbound, st.NumFD, s.rc.nconnsIn, s.rc.nconnsOut, s.rc.nfd)\n\n\t\ts.rc.releaseMemory(st.Memory)\n\t\ts.rc.removeStreams(st.NumStreamsInbound, st.NumStreamsOutbound)\n\t\treturn s.wrapError(err)\n\t}\n\n\ts.trace.ReserveMemory(s.name, 255, st.Memory, s.rc.memory)\n\ts.trace.AddStreams(s.name, st.NumStreamsInbound, st.NumStreamsOutbound, s.rc.nstreamsIn, s.rc.nstreamsOut)\n\ts.trace.AddConns(s.name, st.NumConnsInbound, st.NumConnsOutbound, st.NumFD, s.rc.nconnsIn, s.rc.nconnsOut, s.rc.nfd)\n\n\treturn nil\n}\n\nfunc (s *resourceScope) ReleaseForChild(st network.ScopeStat) {\n\ts.Lock()\n\tdefer s.Unlock()\n\n\tif s.done {\n\t\treturn\n\t}\n\n\ts.rc.releaseMemory(st.Memory)\n\ts.rc.removeStreams(st.NumStreamsInbound, st.NumStreamsOutbound)\n\ts.rc.removeConns(st.NumConnsInbound, st.NumConnsOutbound, st.NumFD)\n\n\ts.trace.ReleaseMemory(s.name, st.Memory, s.rc.memory)\n\ts.trace.RemoveStreams(s.name, st.NumStreamsInbound, st.NumStreamsOutbound, s.rc.nstreamsIn, s.rc.nstreamsOut)\n\ts.trace.RemoveConns(s.name, st.NumConnsInbound, st.NumConnsOutbound, st.NumFD, s.rc.nconnsIn, s.rc.nconnsOut, s.rc.nfd)\n}\n\nfunc (s *resourceScope) ReleaseResources(st network.ScopeStat) {\n\ts.Lock()\n\tdefer s.Unlock()\n\n\tif s.done {\n\t\treturn\n\t}\n\n\ts.rc.releaseMemory(st.Memory)\n\ts.rc.removeStreams(st.NumStreamsInbound, st.NumStreamsOutbound)\n\ts.rc.removeConns(st.NumConnsInbound, st.NumConnsOutbound, st.NumFD)\n\n\tif s.owner != nil {\n\t\ts.owner.ReleaseResources(st)\n\t} else {\n\t\tfor _, e := range s.edges {\n\t\t\te.ReleaseForChild(st)\n\t\t}\n\t}\n\n\ts.trace.ReleaseMemory(s.name, st.Memory, s.rc.memory)\n\ts.trace.RemoveStreams(s.name, st.NumStreamsInbound, st.NumStreamsOutbound, s.rc.nstreamsIn, s.rc.nstreamsOut)\n\ts.trace.RemoveConns(s.name, st.NumConnsInbound, st.NumConnsOutbound, st.NumFD, s.rc.nconnsIn, s.rc.nconnsOut, s.rc.nfd)\n}\n\nfunc (s *resourceScope) nextSpanID() int {\n\ts.spanID++\n\treturn s.spanID\n}\n\nfunc (s *resourceScope) BeginSpan() (network.ResourceScopeSpan, error) {\n\ts.Lock()\n\tdefer s.Unlock()\n\n\tif s.done {\n\t\treturn nil, s.wrapError(network.ErrResourceScopeClosed)\n\t}\n\n\ts.refCnt++\n\treturn newResourceScopeSpan(s, s.nextSpanID()), nil\n}\n\nfunc (s *resourceScope) Done() {\n\ts.Lock()\n\tdefer s.Unlock()\n\n\ts.doneUnlocked()\n}\n\nfunc (s *resourceScope) doneUnlocked() {\n\tif s.done {\n\t\treturn\n\t}\n\tstat := s.rc.stat()\n\tif s.owner != nil {\n\t\ts.owner.ReleaseResources(stat)\n\t\ts.owner.DecRef()\n\t} else {\n\t\tfor _, e := range s.edges {\n\t\t\te.ReleaseForChild(stat)\n\t\t\te.DecRef()\n\t\t}\n\t}\n\n\ts.rc.nstreamsIn = 0\n\ts.rc.nstreamsOut = 0\n\ts.rc.nconnsIn = 0\n\ts.rc.nconnsOut = 0\n\ts.rc.nfd = 0\n\ts.rc.memory = 0\n\n\ts.done = true\n\n\ts.trace.DestroyScope(s.name)\n}\n\nfunc (s *resourceScope) Stat() network.ScopeStat {\n\ts.Lock()\n\tdefer s.Unlock()\n\n\treturn s.rc.stat()\n}\n\nfunc (s *resourceScope) IncRef() {\n\ts.Lock()\n\tdefer s.Unlock()\n\n\ts.refCnt++\n}\n\nfunc (s *resourceScope) DecRef() {\n\ts.Lock()\n\tdefer s.Unlock()\n\n\ts.refCnt--\n}\n\nfunc (s *resourceScope) IsUnused() bool {\n\ts.Lock()\n\tdefer s.Unlock()\n\n\tif s.done {\n\t\treturn true\n\t}\n\n\tif s.refCnt > 0 {\n\t\treturn false\n\t}\n\n\tst := s.rc.stat()\n\treturn st.NumStreamsInbound == 0 &&\n\t\tst.NumStreamsOutbound == 0 &&\n\t\tst.NumConnsInbound == 0 &&\n\t\tst.NumConnsOutbound == 0 &&\n\t\tst.NumFD == 0\n}\n"
  },
  {
    "path": "p2p/host/resource-manager/scope_test.go",
    "content": "package rcmgr\n\nimport (\n\t\"math\"\n\t\"testing\"\n\t\"testing/quick\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc checkResources(t *testing.T, rc *resources, st network.ScopeStat) {\n\tt.Helper()\n\n\tif rc.nconnsIn != st.NumConnsInbound {\n\t\tt.Fatalf(\"expected %d inbound conns, got %d\", st.NumConnsInbound, rc.nconnsIn)\n\t}\n\tif rc.nconnsOut != st.NumConnsOutbound {\n\t\tt.Fatalf(\"expected %d outbound conns, got %d\", st.NumConnsOutbound, rc.nconnsOut)\n\t}\n\tif rc.nstreamsIn != st.NumStreamsInbound {\n\t\tt.Fatalf(\"expected %d inbound streams, got %d\", st.NumStreamsInbound, rc.nstreamsIn)\n\t}\n\tif rc.nstreamsOut != st.NumStreamsOutbound {\n\t\tt.Fatalf(\"expected %d outbound streams, got %d\", st.NumStreamsOutbound, rc.nstreamsOut)\n\t}\n\tif rc.nfd != st.NumFD {\n\t\tt.Fatalf(\"expected %d file descriptors, got %d\", st.NumFD, rc.nfd)\n\t}\n\tif rc.memory != st.Memory {\n\t\tt.Fatalf(\"expected %d reserved bytes of memory, got %d\", st.Memory, rc.memory)\n\t}\n}\n\nfunc TestCheckMemory(t *testing.T) {\n\tt.Run(\"overflows\", func(t *testing.T) {\n\t\trc := resources{limit: &BaseLimit{\n\t\t\tMemory:          math.MaxInt64 - 1,\n\t\t\tStreamsInbound:  1,\n\t\t\tStreamsOutbound: 1,\n\t\t\tStreams:         1,\n\t\t\tConnsInbound:    1,\n\t\t\tConnsOutbound:   1,\n\t\t\tConns:           1,\n\t\t\tFD:              1,\n\t\t}}\n\t\trc.memory = math.MaxInt64 - 1\n\t\trequire.Error(t, rc.checkMemory(2, network.ReservationPriorityAlways))\n\n\t\trc.memory = 1024\n\t\trequire.NoError(t, rc.checkMemory(1, network.ReservationPriorityAlways))\n\t})\n\n\tt.Run(\"negative mem\", func(t *testing.T) {\n\t\trc := resources{limit: &BaseLimit{\n\t\t\tMemory:          math.MaxInt64,\n\t\t\tStreamsInbound:  1,\n\t\t\tStreamsOutbound: 1,\n\t\t\tStreams:         1,\n\t\t\tConnsInbound:    1,\n\t\t\tConnsOutbound:   1,\n\t\t\tConns:           1,\n\t\t\tFD:              1,\n\t\t}}\n\t\trc.memory = math.MaxInt64\n\n\t\trequire.Error(t, rc.checkMemory(-1, network.ReservationPriorityAlways))\n\t})\n\n\tf := func(limit uint64, res uint64, currentMem uint64, priShift uint8) bool {\n\t\tlimit = max((limit%math.MaxInt64)+1,\n\t\t\t// We set the min to 1KiB\n\t\t\t1024)\n\t\tcurrentMem = (currentMem % limit) // We can't have reserved more than our limit\n\t\tres = (res >> 14)                 // We won't reasonably ever have a reservation > 2^50\n\t\trc := resources{limit: &BaseLimit{\n\t\t\tMemory:          int64(limit),\n\t\t\tStreamsInbound:  1,\n\t\t\tStreamsOutbound: 1,\n\t\t\tStreams:         1,\n\t\t\tConnsInbound:    1,\n\t\t\tConnsOutbound:   1,\n\t\t\tConns:           1,\n\t\t\tFD:              1,\n\t\t}}\n\t\trc.memory = int64(currentMem)\n\n\t\tpriShift = (priShift % 9)\n\t\t// Check different priorities at 2^0, 2^1,...2^8. This lets our math be correct in the check below (and avoid overflows).\n\t\tpri := uint8((1 << priShift) - 1)\n\n\t\terr := rc.checkMemory(int64(res), pri)\n\t\tif limit == math.MaxInt64 && err == nil {\n\t\t\t// Special case logic\n\t\t\treturn true\n\t\t}\n\n\t\treturn (err != nil) == (res+uint64(rc.memory) > (limit >> uint64(8-priShift)))\n\t}\n\n\trequire.NoError(t, quick.Check(f, nil))\n}\n\nfunc TestResources(t *testing.T) {\n\trc := resources{limit: &BaseLimit{\n\t\tMemory:          4096,\n\t\tStreamsInbound:  1,\n\t\tStreamsOutbound: 1,\n\t\tStreams:         1,\n\t\tConnsInbound:    1,\n\t\tConnsOutbound:   1,\n\t\tConns:           1,\n\t\tFD:              1,\n\t}}\n\n\tcheckResources(t, &rc, network.ScopeStat{})\n\n\t// test checkMemory\n\tif err := rc.checkMemory(1024, network.ReservationPriorityAlways); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif err := rc.checkMemory(2048, network.ReservationPriorityAlways); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif err := rc.checkMemory(3072, network.ReservationPriorityAlways); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif err := rc.checkMemory(4096, network.ReservationPriorityAlways); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif err := rc.checkMemory(8192, network.ReservationPriorityAlways); err == nil {\n\t\tt.Fatal(\"expected memory check to fail\")\n\t}\n\n\tif err := rc.checkMemory(1024, network.ReservationPriorityLow); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif err := rc.checkMemory(2048, network.ReservationPriorityLow); err == nil {\n\t\tt.Fatal(\"expected memory check to fail\")\n\t}\n\n\tif err := rc.checkMemory(2048, network.ReservationPriorityMedium); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif err := rc.checkMemory(3072, network.ReservationPriorityMedium); err == nil {\n\t\tt.Fatal(\"expected memory check to fail\")\n\t}\n\n\tif err := rc.checkMemory(3072, network.ReservationPriorityHigh); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif err := rc.checkMemory(3584, network.ReservationPriorityHigh); err == nil {\n\t\tt.Fatal(\"expected memory check to fail\")\n\t}\n\n\t// test reserveMemory\n\tif err := rc.reserveMemory(1024, network.ReservationPriorityAlways); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &rc, network.ScopeStat{Memory: 1024})\n\n\tif err := rc.reserveMemory(1024, network.ReservationPriorityAlways); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &rc, network.ScopeStat{Memory: 2048})\n\n\tif err := rc.reserveMemory(1024, network.ReservationPriorityAlways); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &rc, network.ScopeStat{Memory: 3072})\n\n\tif err := rc.reserveMemory(512, network.ReservationPriorityAlways); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &rc, network.ScopeStat{Memory: 3584})\n\n\tif err := rc.reserveMemory(4096, network.ReservationPriorityAlways); err == nil {\n\t\tt.Fatal(\"expected memory reservation to fail\")\n\t}\n\tcheckResources(t, &rc, network.ScopeStat{Memory: 3584})\n\n\trc.releaseMemory(2560)\n\tcheckResources(t, &rc, network.ScopeStat{Memory: 1024})\n\n\tif err := rc.reserveMemory(2048, network.ReservationPriorityAlways); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &rc, network.ScopeStat{Memory: 3072})\n\n\trc.releaseMemory(3072)\n\tcheckResources(t, &rc, network.ScopeStat{})\n\n\tif err := rc.reserveMemory(1024, network.ReservationPriorityLow); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &rc, network.ScopeStat{Memory: 1024})\n\n\tif err := rc.reserveMemory(1024, network.ReservationPriorityLow); err == nil {\n\t\tt.Fatal(\"expected memory check to fail\")\n\t}\n\tcheckResources(t, &rc, network.ScopeStat{Memory: 1024})\n\n\tif err := rc.reserveMemory(1024, network.ReservationPriorityMedium); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &rc, network.ScopeStat{Memory: 2048})\n\n\tif err := rc.reserveMemory(1024, network.ReservationPriorityMedium); err == nil {\n\t\tt.Fatal(\"expected memory check to fail\")\n\t}\n\tcheckResources(t, &rc, network.ScopeStat{Memory: 2048})\n\n\tif err := rc.reserveMemory(1024, network.ReservationPriorityHigh); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &rc, network.ScopeStat{Memory: 3072})\n\n\tif err := rc.reserveMemory(512, network.ReservationPriorityHigh); err == nil {\n\t\tt.Fatal(\"expected memory check to fail\")\n\t}\n\tcheckResources(t, &rc, network.ScopeStat{Memory: 3072})\n\n\tif err := rc.reserveMemory(512, network.ReservationPriorityAlways); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &rc, network.ScopeStat{Memory: 3584})\n\n\trc.releaseMemory(3584)\n\tcheckResources(t, &rc, network.ScopeStat{})\n\n\t// test addStream\n\tif err := rc.addStream(network.DirInbound); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &rc, network.ScopeStat{NumStreamsInbound: 1})\n\n\tif err := rc.addStream(network.DirInbound); err == nil {\n\t\tt.Fatal(\"expected addStream to fail\")\n\t}\n\tcheckResources(t, &rc, network.ScopeStat{NumStreamsInbound: 1})\n\n\tif err := rc.addStream(network.DirOutbound); err == nil {\n\t\tt.Fatal(\"expected addStream to fail\")\n\t}\n\tcheckResources(t, &rc, network.ScopeStat{NumStreamsInbound: 1})\n\n\trc.removeStream(network.DirInbound)\n\tcheckResources(t, &rc, network.ScopeStat{})\n\n\tif err := rc.addStream(network.DirOutbound); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &rc, network.ScopeStat{NumStreamsOutbound: 1})\n\n\tif err := rc.addStream(network.DirOutbound); err == nil {\n\t\tt.Fatal(\"expected addStream to fail\")\n\t}\n\tcheckResources(t, &rc, network.ScopeStat{NumStreamsOutbound: 1})\n\n\tif err := rc.addStream(network.DirInbound); err == nil {\n\t\tt.Fatal(\"expected addStream to fail\")\n\t}\n\tcheckResources(t, &rc, network.ScopeStat{NumStreamsOutbound: 1})\n\n\trc.removeStream(network.DirOutbound)\n\tcheckResources(t, &rc, network.ScopeStat{})\n\n\t// test addConn\n\tif err := rc.addConn(network.DirInbound, false); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &rc, network.ScopeStat{NumConnsInbound: 1})\n\n\tif err := rc.addConn(network.DirInbound, false); err == nil {\n\t\tt.Fatal(\"expected addConn to fail\")\n\t}\n\tcheckResources(t, &rc, network.ScopeStat{NumConnsInbound: 1})\n\n\trc.removeConn(network.DirInbound, false)\n\tcheckResources(t, &rc, network.ScopeStat{})\n\n\tif err := rc.addConn(network.DirOutbound, false); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &rc, network.ScopeStat{NumConnsOutbound: 1})\n\n\tif err := rc.addConn(network.DirOutbound, false); err == nil {\n\t\tt.Fatal(\"expected addConn to fail\")\n\t}\n\tcheckResources(t, &rc, network.ScopeStat{NumConnsOutbound: 1})\n\n\trc.removeConn(network.DirOutbound, false)\n\tcheckResources(t, &rc, network.ScopeStat{})\n\n\tif err := rc.addConn(network.DirInbound, true); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &rc, network.ScopeStat{NumConnsInbound: 1, NumFD: 1})\n\n\tif err := rc.addConn(network.DirOutbound, true); err == nil {\n\t\tt.Fatal(\"expected addConn to fail\")\n\t}\n\tcheckResources(t, &rc, network.ScopeStat{NumConnsInbound: 1, NumFD: 1})\n\n\trc.removeConn(network.DirInbound, true)\n\tcheckResources(t, &rc, network.ScopeStat{})\n}\n\nfunc TestResourceScopeSimple(t *testing.T) {\n\ts := newResourceScope(\n\t\t&BaseLimit{\n\t\t\tMemory:          4096,\n\t\t\tStreamsInbound:  1,\n\t\t\tStreamsOutbound: 1,\n\t\t\tStreams:         1,\n\t\t\tConnsInbound:    1,\n\t\t\tConnsOutbound:   1,\n\t\t\tConns:           1,\n\t\t\tFD:              1,\n\t\t},\n\t\tnil, \"test\", nil, nil,\n\t)\n\n\ts.IncRef()\n\tif s.refCnt != 1 {\n\t\tt.Fatal(\"expected refcnt of 1\")\n\t}\n\ts.DecRef()\n\tif s.refCnt != 0 {\n\t\tt.Fatal(\"expected refcnt of 0\")\n\t}\n\n\ttestResourceScopeBasic(t, s)\n}\n\nfunc testResourceScopeBasic(t *testing.T, s *resourceScope) {\n\tif err := s.ReserveMemory(2048, network.ReservationPriorityAlways); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &s.rc, network.ScopeStat{Memory: 2048})\n\n\tif err := s.ReserveMemory(1024, network.ReservationPriorityAlways); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &s.rc, network.ScopeStat{Memory: 3072})\n\n\tif err := s.ReserveMemory(512, network.ReservationPriorityAlways); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &s.rc, network.ScopeStat{Memory: 3584})\n\n\tif err := s.ReserveMemory(512, network.ReservationPriorityAlways); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &s.rc, network.ScopeStat{Memory: 4096})\n\n\tif err := s.ReserveMemory(1024, network.ReservationPriorityAlways); err == nil {\n\t\tt.Fatal(\"expected ReserveMemory to fail\")\n\t}\n\tcheckResources(t, &s.rc, network.ScopeStat{Memory: 4096})\n\n\ts.ReleaseMemory(4096)\n\tcheckResources(t, &s.rc, network.ScopeStat{})\n\n\tif err := s.AddStream(network.DirInbound); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1})\n\n\tif err := s.AddStream(network.DirInbound); err == nil {\n\t\tt.Fatal(\"expected AddStream to fail\")\n\t}\n\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1})\n\n\tif err := s.AddStream(network.DirOutbound); err == nil {\n\t\tt.Fatal(\"expected AddStream to fail\")\n\t}\n\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsInbound: 1})\n\n\ts.RemoveStream(network.DirInbound)\n\tcheckResources(t, &s.rc, network.ScopeStat{})\n\n\tif err := s.AddStream(network.DirOutbound); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsOutbound: 1})\n\n\tif err := s.AddStream(network.DirOutbound); err == nil {\n\t\tt.Fatal(\"expected AddStream to fail\")\n\t}\n\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsOutbound: 1})\n\n\tif err := s.AddStream(network.DirInbound); err == nil {\n\t\tt.Fatal(\"expected AddStream to fail\")\n\t}\n\tcheckResources(t, &s.rc, network.ScopeStat{NumStreamsOutbound: 1})\n\n\ts.RemoveStream(network.DirOutbound)\n\tcheckResources(t, &s.rc, network.ScopeStat{})\n\n\tif err := s.AddConn(network.DirInbound, false); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &s.rc, network.ScopeStat{NumConnsInbound: 1})\n\n\tif err := s.AddConn(network.DirInbound, false); err == nil {\n\t\tt.Fatal(\"expected AddConn to fail\")\n\t}\n\tcheckResources(t, &s.rc, network.ScopeStat{NumConnsInbound: 1})\n\n\ts.RemoveConn(network.DirInbound, false)\n\tcheckResources(t, &s.rc, network.ScopeStat{})\n\n\tif err := s.AddConn(network.DirOutbound, false); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &s.rc, network.ScopeStat{NumConnsOutbound: 1})\n\n\tif err := s.AddConn(network.DirOutbound, false); err == nil {\n\t\tt.Fatal(\"expected AddConn to fail\")\n\t}\n\tcheckResources(t, &s.rc, network.ScopeStat{NumConnsOutbound: 1})\n\n\ts.RemoveConn(network.DirOutbound, false)\n\tcheckResources(t, &s.rc, network.ScopeStat{})\n\n\tif err := s.AddConn(network.DirInbound, true); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &s.rc, network.ScopeStat{NumConnsInbound: 1, NumFD: 1})\n\n\tif err := s.AddConn(network.DirOutbound, true); err == nil {\n\t\tt.Fatal(\"expected AddConn to fail\")\n\t}\n\tcheckResources(t, &s.rc, network.ScopeStat{NumConnsInbound: 1, NumFD: 1})\n\n\ts.RemoveConn(network.DirInbound, true)\n\tcheckResources(t, &s.rc, network.ScopeStat{})\n}\n\nfunc TestResourceScopeTxnBasic(t *testing.T) {\n\ts := newResourceScope(\n\t\t&BaseLimit{\n\t\t\tMemory:          4096,\n\t\t\tStreamsInbound:  1,\n\t\t\tStreamsOutbound: 1,\n\t\t\tStreams:         1,\n\t\t\tConnsInbound:    1,\n\t\t\tConnsOutbound:   1,\n\t\t\tConns:           1,\n\t\t\tFD:              1,\n\t\t},\n\t\tnil, \"test\", nil, nil,\n\t)\n\n\ttxn, err := s.BeginSpan()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttestResourceScopeBasic(t, txn.(*resourceScope))\n\tcheckResources(t, &s.rc, network.ScopeStat{})\n\n\t// check constraint propagation\n\tif err := txn.ReserveMemory(4096, network.ReservationPriorityAlways); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &txn.(*resourceScope).rc, network.ScopeStat{Memory: 4096})\n\tcheckResources(t, &s.rc, network.ScopeStat{Memory: 4096})\n\ttxn.Done()\n\tcheckResources(t, &s.rc, network.ScopeStat{})\n\ttxn.Done() // idempotent\n\tcheckResources(t, &s.rc, network.ScopeStat{})\n}\n\nfunc TestResourceScopeTxnZombie(t *testing.T) {\n\ts := newResourceScope(\n\t\t&BaseLimit{\n\t\t\tMemory:          4096,\n\t\t\tStreamsInbound:  1,\n\t\t\tStreamsOutbound: 1,\n\t\t\tStreams:         1,\n\t\t\tConnsInbound:    1,\n\t\t\tConnsOutbound:   1,\n\t\t\tConns:           1,\n\t\t\tFD:              1,\n\t\t},\n\t\tnil, \"test\", nil, nil,\n\t)\n\n\ttxn1, err := s.BeginSpan()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttxn2, err := txn1.BeginSpan()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif err := txn2.ReserveMemory(4096, network.ReservationPriorityAlways); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &txn2.(*resourceScope).rc, network.ScopeStat{Memory: 4096})\n\tcheckResources(t, &txn1.(*resourceScope).rc, network.ScopeStat{Memory: 4096})\n\tcheckResources(t, &s.rc, network.ScopeStat{Memory: 4096})\n\n\ttxn1.Done()\n\tcheckResources(t, &s.rc, network.ScopeStat{})\n\tif err := txn2.ReserveMemory(4096, network.ReservationPriorityAlways); err == nil {\n\t\tt.Fatal(\"expected ReserveMemory to fail\")\n\t}\n\n\ttxn2.Done()\n\tcheckResources(t, &s.rc, network.ScopeStat{})\n}\n\nfunc TestResourceScopeTxnTree(t *testing.T) {\n\ts := newResourceScope(\n\t\t&BaseLimit{\n\t\t\tMemory:          4096,\n\t\t\tStreamsInbound:  1,\n\t\t\tStreamsOutbound: 1,\n\t\t\tStreams:         1,\n\t\t\tConnsInbound:    1,\n\t\t\tConnsOutbound:   1,\n\t\t\tConns:           1,\n\t\t\tFD:              1,\n\t\t},\n\t\tnil, \"test\", nil, nil,\n\t)\n\n\ttxn1, err := s.BeginSpan()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttxn2, err := txn1.BeginSpan()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttxn3, err := txn1.BeginSpan()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttxn4, err := txn2.BeginSpan()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttxn5, err := txn2.BeginSpan()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif err := txn3.ReserveMemory(1024, network.ReservationPriorityAlways); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &txn3.(*resourceScope).rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &txn1.(*resourceScope).rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &s.rc, network.ScopeStat{Memory: 1024})\n\n\tif err := txn4.ReserveMemory(1024, network.ReservationPriorityAlways); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &txn4.(*resourceScope).rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &txn3.(*resourceScope).rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &txn2.(*resourceScope).rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &txn1.(*resourceScope).rc, network.ScopeStat{Memory: 2048})\n\tcheckResources(t, &s.rc, network.ScopeStat{Memory: 2048})\n\n\tif err := txn5.ReserveMemory(1024, network.ReservationPriorityAlways); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &txn5.(*resourceScope).rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &txn4.(*resourceScope).rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &txn3.(*resourceScope).rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &txn2.(*resourceScope).rc, network.ScopeStat{Memory: 2048})\n\tcheckResources(t, &txn1.(*resourceScope).rc, network.ScopeStat{Memory: 3072})\n\tcheckResources(t, &s.rc, network.ScopeStat{Memory: 3072})\n\n\tif err := txn1.ReserveMemory(1024, network.ReservationPriorityAlways); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &txn5.(*resourceScope).rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &txn4.(*resourceScope).rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &txn3.(*resourceScope).rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &txn2.(*resourceScope).rc, network.ScopeStat{Memory: 2048})\n\tcheckResources(t, &txn1.(*resourceScope).rc, network.ScopeStat{Memory: 4096})\n\tcheckResources(t, &s.rc, network.ScopeStat{Memory: 4096})\n\n\tif err := txn5.ReserveMemory(1024, network.ReservationPriorityAlways); err == nil {\n\t\tt.Fatal(\"expected ReserveMemory to fail\")\n\t}\n\tif err := txn4.ReserveMemory(1024, network.ReservationPriorityAlways); err == nil {\n\t\tt.Fatal(\"expected ReserveMemory to fail\")\n\t}\n\tif err := txn3.ReserveMemory(1024, network.ReservationPriorityAlways); err == nil {\n\t\tt.Fatal(\"expected ReserveMemory to fail\")\n\t}\n\tif err := txn2.ReserveMemory(1024, network.ReservationPriorityAlways); err == nil {\n\t\tt.Fatal(\"expected ReserveMemory to fail\")\n\t}\n\tcheckResources(t, &txn5.(*resourceScope).rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &txn4.(*resourceScope).rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &txn3.(*resourceScope).rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &txn2.(*resourceScope).rc, network.ScopeStat{Memory: 2048})\n\tcheckResources(t, &txn1.(*resourceScope).rc, network.ScopeStat{Memory: 4096})\n\tcheckResources(t, &s.rc, network.ScopeStat{Memory: 4096})\n\n\ttxn1.Done()\n\tcheckResources(t, &s.rc, network.ScopeStat{})\n}\n\nfunc TestResourceScopeDAG(t *testing.T) {\n\t// A small DAG of scopes\n\t// s1\n\t// +---> s2\n\t//        +------------> s5\n\t//        +----\n\t// +---> s3 +.  \\\n\t//          | \\  -----+-> s4 (a diamond!)\n\t//          |  ------/\n\t//          \\\n\t//           ------> s6\n\ts1 := newResourceScope(\n\t\t&BaseLimit{\n\t\t\tMemory:          4096,\n\t\t\tStreamsInbound:  4,\n\t\t\tStreamsOutbound: 4,\n\t\t\tStreams:         4,\n\t\t\tConnsInbound:    4,\n\t\t\tConnsOutbound:   4,\n\t\t\tConns:           4,\n\t\t\tFD:              4,\n\t\t},\n\t\tnil, \"test\", nil, nil,\n\t)\n\ts2 := newResourceScope(\n\t\t&BaseLimit{\n\t\t\tMemory:          2048,\n\t\t\tStreamsInbound:  2,\n\t\t\tStreamsOutbound: 2,\n\t\t\tStreams:         2,\n\t\t\tConnsInbound:    2,\n\t\t\tConnsOutbound:   2,\n\t\t\tConns:           2,\n\t\t\tFD:              2,\n\t\t},\n\t\t[]*resourceScope{s1}, \"test\", nil, nil,\n\t)\n\ts3 := newResourceScope(\n\t\t&BaseLimit{\n\t\t\tMemory:          2048,\n\t\t\tStreamsInbound:  2,\n\t\t\tStreamsOutbound: 2,\n\t\t\tStreams:         2,\n\t\t\tConnsInbound:    2,\n\t\t\tConnsOutbound:   2,\n\t\t\tConns:           2,\n\t\t\tFD:              2,\n\t\t},\n\t\t[]*resourceScope{s1}, \"test\", nil, nil,\n\t)\n\ts4 := newResourceScope(\n\t\t&BaseLimit{\n\t\t\tMemory:          2048,\n\t\t\tStreamsInbound:  2,\n\t\t\tStreamsOutbound: 2,\n\t\t\tStreams:         2,\n\t\t\tConnsInbound:    2,\n\t\t\tConnsOutbound:   2,\n\t\t\tConns:           2,\n\t\t\tFD:              2,\n\t\t},\n\t\t[]*resourceScope{s2, s3, s1}, \"test\", nil, nil,\n\t)\n\ts5 := newResourceScope(\n\t\t&BaseLimit{\n\t\t\tMemory:          2048,\n\t\t\tStreamsInbound:  2,\n\t\t\tStreamsOutbound: 2,\n\t\t\tStreams:         2,\n\t\t\tConnsInbound:    2,\n\t\t\tConnsOutbound:   2,\n\t\t\tConns:           2,\n\t\t\tFD:              2,\n\t\t},\n\t\t[]*resourceScope{s2, s1}, \"test\", nil, nil,\n\t)\n\ts6 := newResourceScope(\n\t\t&BaseLimit{\n\t\t\tMemory:          2048,\n\t\t\tStreamsInbound:  2,\n\t\t\tStreamsOutbound: 2,\n\t\t\tStreams:         2,\n\t\t\tConnsInbound:    2,\n\t\t\tConnsOutbound:   2,\n\t\t\tConns:           2,\n\t\t\tFD:              2,\n\t\t},\n\t\t[]*resourceScope{s3, s1}, \"test\", nil, nil,\n\t)\n\n\tif err := s4.ReserveMemory(1024, network.ReservationPriorityAlways); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &s6.rc, network.ScopeStat{})\n\tcheckResources(t, &s5.rc, network.ScopeStat{})\n\tcheckResources(t, &s4.rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &s3.rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &s2.rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &s1.rc, network.ScopeStat{Memory: 1024})\n\n\tif err := s5.ReserveMemory(1024, network.ReservationPriorityAlways); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &s6.rc, network.ScopeStat{})\n\tcheckResources(t, &s5.rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &s4.rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &s3.rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &s2.rc, network.ScopeStat{Memory: 2048})\n\tcheckResources(t, &s1.rc, network.ScopeStat{Memory: 2048})\n\n\tif err := s6.ReserveMemory(1024, network.ReservationPriorityAlways); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &s6.rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &s5.rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &s4.rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &s3.rc, network.ScopeStat{Memory: 2048})\n\tcheckResources(t, &s2.rc, network.ScopeStat{Memory: 2048})\n\tcheckResources(t, &s1.rc, network.ScopeStat{Memory: 3072})\n\n\tif err := s4.ReserveMemory(1024, network.ReservationPriorityAlways); err == nil {\n\t\tt.Fatal(\"expcted ReserveMemory to fail\")\n\t}\n\tif err := s5.ReserveMemory(1024, network.ReservationPriorityAlways); err == nil {\n\t\tt.Fatal(\"expcted ReserveMemory to fail\")\n\t}\n\tif err := s6.ReserveMemory(1024, network.ReservationPriorityAlways); err == nil {\n\t\tt.Fatal(\"expcted ReserveMemory to fail\")\n\t}\n\n\tcheckResources(t, &s6.rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &s5.rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &s4.rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &s3.rc, network.ScopeStat{Memory: 2048})\n\tcheckResources(t, &s2.rc, network.ScopeStat{Memory: 2048})\n\tcheckResources(t, &s1.rc, network.ScopeStat{Memory: 3072})\n\n\ts4.ReleaseMemory(1024)\n\tcheckResources(t, &s6.rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &s5.rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &s4.rc, network.ScopeStat{})\n\tcheckResources(t, &s3.rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &s2.rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &s1.rc, network.ScopeStat{Memory: 2048})\n\n\ts5.ReleaseMemory(1024)\n\tcheckResources(t, &s6.rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &s5.rc, network.ScopeStat{})\n\tcheckResources(t, &s4.rc, network.ScopeStat{})\n\tcheckResources(t, &s3.rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &s2.rc, network.ScopeStat{})\n\tcheckResources(t, &s1.rc, network.ScopeStat{Memory: 1024})\n\n\ts6.ReleaseMemory(1024)\n\tcheckResources(t, &s6.rc, network.ScopeStat{})\n\tcheckResources(t, &s5.rc, network.ScopeStat{})\n\tcheckResources(t, &s4.rc, network.ScopeStat{})\n\tcheckResources(t, &s3.rc, network.ScopeStat{})\n\tcheckResources(t, &s2.rc, network.ScopeStat{})\n\tcheckResources(t, &s1.rc, network.ScopeStat{})\n\n\tif err := s4.AddStream(network.DirInbound); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &s6.rc, network.ScopeStat{})\n\tcheckResources(t, &s5.rc, network.ScopeStat{})\n\tcheckResources(t, &s4.rc, network.ScopeStat{NumStreamsInbound: 1})\n\tcheckResources(t, &s3.rc, network.ScopeStat{NumStreamsInbound: 1})\n\tcheckResources(t, &s2.rc, network.ScopeStat{NumStreamsInbound: 1})\n\tcheckResources(t, &s1.rc, network.ScopeStat{NumStreamsInbound: 1})\n\n\tif err := s5.AddStream(network.DirInbound); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &s6.rc, network.ScopeStat{})\n\tcheckResources(t, &s5.rc, network.ScopeStat{NumStreamsInbound: 1})\n\tcheckResources(t, &s4.rc, network.ScopeStat{NumStreamsInbound: 1})\n\tcheckResources(t, &s3.rc, network.ScopeStat{NumStreamsInbound: 1})\n\tcheckResources(t, &s2.rc, network.ScopeStat{NumStreamsInbound: 2})\n\tcheckResources(t, &s1.rc, network.ScopeStat{NumStreamsInbound: 2})\n\n\tif err := s6.AddStream(network.DirInbound); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &s6.rc, network.ScopeStat{NumStreamsInbound: 1})\n\tcheckResources(t, &s5.rc, network.ScopeStat{NumStreamsInbound: 1})\n\tcheckResources(t, &s4.rc, network.ScopeStat{NumStreamsInbound: 1})\n\tcheckResources(t, &s3.rc, network.ScopeStat{NumStreamsInbound: 2})\n\tcheckResources(t, &s2.rc, network.ScopeStat{NumStreamsInbound: 2})\n\tcheckResources(t, &s1.rc, network.ScopeStat{NumStreamsInbound: 3})\n\n\tif err := s4.AddStream(network.DirInbound); err == nil {\n\t\tt.Fatal(\"expected AddStream to fail\")\n\t}\n\tif err := s5.AddStream(network.DirInbound); err == nil {\n\t\tt.Fatal(\"expected AddStream to fail\")\n\t}\n\tif err := s6.AddStream(network.DirInbound); err == nil {\n\t\tt.Fatal(\"expected AddStream to fail\")\n\t}\n\tcheckResources(t, &s6.rc, network.ScopeStat{NumStreamsInbound: 1})\n\tcheckResources(t, &s5.rc, network.ScopeStat{NumStreamsInbound: 1})\n\tcheckResources(t, &s4.rc, network.ScopeStat{NumStreamsInbound: 1})\n\tcheckResources(t, &s3.rc, network.ScopeStat{NumStreamsInbound: 2})\n\tcheckResources(t, &s2.rc, network.ScopeStat{NumStreamsInbound: 2})\n\tcheckResources(t, &s1.rc, network.ScopeStat{NumStreamsInbound: 3})\n\n\ts4.RemoveStream(network.DirInbound)\n\tcheckResources(t, &s6.rc, network.ScopeStat{NumStreamsInbound: 1})\n\tcheckResources(t, &s5.rc, network.ScopeStat{NumStreamsInbound: 1})\n\tcheckResources(t, &s4.rc, network.ScopeStat{})\n\tcheckResources(t, &s3.rc, network.ScopeStat{NumStreamsInbound: 1})\n\tcheckResources(t, &s2.rc, network.ScopeStat{NumStreamsInbound: 1})\n\tcheckResources(t, &s1.rc, network.ScopeStat{NumStreamsInbound: 2})\n\n\ts5.RemoveStream(network.DirInbound)\n\tcheckResources(t, &s6.rc, network.ScopeStat{NumStreamsInbound: 1})\n\tcheckResources(t, &s5.rc, network.ScopeStat{})\n\tcheckResources(t, &s4.rc, network.ScopeStat{})\n\tcheckResources(t, &s3.rc, network.ScopeStat{NumStreamsInbound: 1})\n\tcheckResources(t, &s2.rc, network.ScopeStat{})\n\tcheckResources(t, &s1.rc, network.ScopeStat{NumStreamsInbound: 1})\n\n\ts6.RemoveStream(network.DirInbound)\n\tcheckResources(t, &s6.rc, network.ScopeStat{})\n\tcheckResources(t, &s5.rc, network.ScopeStat{})\n\tcheckResources(t, &s4.rc, network.ScopeStat{})\n\tcheckResources(t, &s3.rc, network.ScopeStat{})\n\tcheckResources(t, &s2.rc, network.ScopeStat{})\n\tcheckResources(t, &s1.rc, network.ScopeStat{})\n\n\tif err := s4.AddStream(network.DirOutbound); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &s6.rc, network.ScopeStat{})\n\tcheckResources(t, &s5.rc, network.ScopeStat{})\n\tcheckResources(t, &s4.rc, network.ScopeStat{NumStreamsOutbound: 1})\n\tcheckResources(t, &s3.rc, network.ScopeStat{NumStreamsOutbound: 1})\n\tcheckResources(t, &s2.rc, network.ScopeStat{NumStreamsOutbound: 1})\n\tcheckResources(t, &s1.rc, network.ScopeStat{NumStreamsOutbound: 1})\n\n\tif err := s5.AddStream(network.DirOutbound); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &s6.rc, network.ScopeStat{})\n\tcheckResources(t, &s5.rc, network.ScopeStat{NumStreamsOutbound: 1})\n\tcheckResources(t, &s4.rc, network.ScopeStat{NumStreamsOutbound: 1})\n\tcheckResources(t, &s3.rc, network.ScopeStat{NumStreamsOutbound: 1})\n\tcheckResources(t, &s2.rc, network.ScopeStat{NumStreamsOutbound: 2})\n\tcheckResources(t, &s1.rc, network.ScopeStat{NumStreamsOutbound: 2})\n\n\tif err := s6.AddStream(network.DirOutbound); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &s6.rc, network.ScopeStat{NumStreamsOutbound: 1})\n\tcheckResources(t, &s5.rc, network.ScopeStat{NumStreamsOutbound: 1})\n\tcheckResources(t, &s4.rc, network.ScopeStat{NumStreamsOutbound: 1})\n\tcheckResources(t, &s3.rc, network.ScopeStat{NumStreamsOutbound: 2})\n\tcheckResources(t, &s2.rc, network.ScopeStat{NumStreamsOutbound: 2})\n\tcheckResources(t, &s1.rc, network.ScopeStat{NumStreamsOutbound: 3})\n\n\tif err := s4.AddStream(network.DirOutbound); err == nil {\n\t\tt.Fatal(\"expected AddStream to fail\")\n\t}\n\tif err := s5.AddStream(network.DirOutbound); err == nil {\n\t\tt.Fatal(\"expected AddStream to fail\")\n\t}\n\tif err := s6.AddStream(network.DirOutbound); err == nil {\n\t\tt.Fatal(\"expected AddStream to fail\")\n\t}\n\tcheckResources(t, &s6.rc, network.ScopeStat{NumStreamsOutbound: 1})\n\tcheckResources(t, &s5.rc, network.ScopeStat{NumStreamsOutbound: 1})\n\tcheckResources(t, &s4.rc, network.ScopeStat{NumStreamsOutbound: 1})\n\tcheckResources(t, &s3.rc, network.ScopeStat{NumStreamsOutbound: 2})\n\tcheckResources(t, &s2.rc, network.ScopeStat{NumStreamsOutbound: 2})\n\tcheckResources(t, &s1.rc, network.ScopeStat{NumStreamsOutbound: 3})\n\n\ts4.RemoveStream(network.DirOutbound)\n\tcheckResources(t, &s6.rc, network.ScopeStat{NumStreamsOutbound: 1})\n\tcheckResources(t, &s5.rc, network.ScopeStat{NumStreamsOutbound: 1})\n\tcheckResources(t, &s4.rc, network.ScopeStat{})\n\tcheckResources(t, &s3.rc, network.ScopeStat{NumStreamsOutbound: 1})\n\tcheckResources(t, &s2.rc, network.ScopeStat{NumStreamsOutbound: 1})\n\tcheckResources(t, &s1.rc, network.ScopeStat{NumStreamsOutbound: 2})\n\n\ts5.RemoveStream(network.DirOutbound)\n\tcheckResources(t, &s6.rc, network.ScopeStat{NumStreamsOutbound: 1})\n\tcheckResources(t, &s5.rc, network.ScopeStat{})\n\tcheckResources(t, &s4.rc, network.ScopeStat{})\n\tcheckResources(t, &s3.rc, network.ScopeStat{NumStreamsOutbound: 1})\n\tcheckResources(t, &s2.rc, network.ScopeStat{})\n\tcheckResources(t, &s1.rc, network.ScopeStat{NumStreamsOutbound: 1})\n\n\ts6.RemoveStream(network.DirOutbound)\n\tcheckResources(t, &s6.rc, network.ScopeStat{})\n\tcheckResources(t, &s5.rc, network.ScopeStat{})\n\tcheckResources(t, &s4.rc, network.ScopeStat{})\n\tcheckResources(t, &s3.rc, network.ScopeStat{})\n\tcheckResources(t, &s2.rc, network.ScopeStat{})\n\tcheckResources(t, &s1.rc, network.ScopeStat{})\n\n\tif err := s4.AddConn(network.DirInbound, false); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &s6.rc, network.ScopeStat{})\n\tcheckResources(t, &s5.rc, network.ScopeStat{})\n\tcheckResources(t, &s4.rc, network.ScopeStat{NumConnsInbound: 1})\n\tcheckResources(t, &s3.rc, network.ScopeStat{NumConnsInbound: 1})\n\tcheckResources(t, &s2.rc, network.ScopeStat{NumConnsInbound: 1})\n\tcheckResources(t, &s1.rc, network.ScopeStat{NumConnsInbound: 1})\n\n\tif err := s5.AddConn(network.DirInbound, false); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &s6.rc, network.ScopeStat{})\n\tcheckResources(t, &s5.rc, network.ScopeStat{NumConnsInbound: 1})\n\tcheckResources(t, &s4.rc, network.ScopeStat{NumConnsInbound: 1})\n\tcheckResources(t, &s3.rc, network.ScopeStat{NumConnsInbound: 1})\n\tcheckResources(t, &s2.rc, network.ScopeStat{NumConnsInbound: 2})\n\tcheckResources(t, &s1.rc, network.ScopeStat{NumConnsInbound: 2})\n\n\tif err := s6.AddConn(network.DirInbound, false); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &s6.rc, network.ScopeStat{NumConnsInbound: 1})\n\tcheckResources(t, &s5.rc, network.ScopeStat{NumConnsInbound: 1})\n\tcheckResources(t, &s4.rc, network.ScopeStat{NumConnsInbound: 1})\n\tcheckResources(t, &s3.rc, network.ScopeStat{NumConnsInbound: 2})\n\tcheckResources(t, &s2.rc, network.ScopeStat{NumConnsInbound: 2})\n\tcheckResources(t, &s1.rc, network.ScopeStat{NumConnsInbound: 3})\n\n\tif err := s4.AddConn(network.DirInbound, false); err == nil {\n\t\tt.Fatal(\"expected AddConn to fail\")\n\t}\n\tif err := s5.AddConn(network.DirInbound, false); err == nil {\n\t\tt.Fatal(\"expected AddConn to fail\")\n\t}\n\tif err := s6.AddConn(network.DirInbound, false); err == nil {\n\t\tt.Fatal(\"expected AddConn to fail\")\n\t}\n\tcheckResources(t, &s6.rc, network.ScopeStat{NumConnsInbound: 1})\n\tcheckResources(t, &s5.rc, network.ScopeStat{NumConnsInbound: 1})\n\tcheckResources(t, &s4.rc, network.ScopeStat{NumConnsInbound: 1})\n\tcheckResources(t, &s3.rc, network.ScopeStat{NumConnsInbound: 2})\n\tcheckResources(t, &s2.rc, network.ScopeStat{NumConnsInbound: 2})\n\tcheckResources(t, &s1.rc, network.ScopeStat{NumConnsInbound: 3})\n\n\ts4.RemoveConn(network.DirInbound, false)\n\tcheckResources(t, &s6.rc, network.ScopeStat{NumConnsInbound: 1})\n\tcheckResources(t, &s5.rc, network.ScopeStat{NumConnsInbound: 1})\n\tcheckResources(t, &s4.rc, network.ScopeStat{})\n\tcheckResources(t, &s3.rc, network.ScopeStat{NumConnsInbound: 1})\n\tcheckResources(t, &s2.rc, network.ScopeStat{NumConnsInbound: 1})\n\tcheckResources(t, &s1.rc, network.ScopeStat{NumConnsInbound: 2})\n\n\ts5.RemoveConn(network.DirInbound, false)\n\tcheckResources(t, &s6.rc, network.ScopeStat{NumConnsInbound: 1})\n\tcheckResources(t, &s5.rc, network.ScopeStat{})\n\tcheckResources(t, &s4.rc, network.ScopeStat{})\n\tcheckResources(t, &s3.rc, network.ScopeStat{NumConnsInbound: 1})\n\tcheckResources(t, &s2.rc, network.ScopeStat{})\n\tcheckResources(t, &s1.rc, network.ScopeStat{NumConnsInbound: 1})\n\n\ts6.RemoveConn(network.DirInbound, false)\n\tcheckResources(t, &s6.rc, network.ScopeStat{})\n\tcheckResources(t, &s5.rc, network.ScopeStat{})\n\tcheckResources(t, &s4.rc, network.ScopeStat{})\n\tcheckResources(t, &s3.rc, network.ScopeStat{})\n\tcheckResources(t, &s2.rc, network.ScopeStat{})\n\tcheckResources(t, &s1.rc, network.ScopeStat{})\n\n\tif err := s4.AddConn(network.DirOutbound, false); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &s6.rc, network.ScopeStat{})\n\tcheckResources(t, &s5.rc, network.ScopeStat{})\n\tcheckResources(t, &s4.rc, network.ScopeStat{NumConnsOutbound: 1})\n\tcheckResources(t, &s3.rc, network.ScopeStat{NumConnsOutbound: 1})\n\tcheckResources(t, &s2.rc, network.ScopeStat{NumConnsOutbound: 1})\n\tcheckResources(t, &s1.rc, network.ScopeStat{NumConnsOutbound: 1})\n\n\tif err := s5.AddConn(network.DirOutbound, false); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &s6.rc, network.ScopeStat{})\n\tcheckResources(t, &s5.rc, network.ScopeStat{NumConnsOutbound: 1})\n\tcheckResources(t, &s4.rc, network.ScopeStat{NumConnsOutbound: 1})\n\tcheckResources(t, &s3.rc, network.ScopeStat{NumConnsOutbound: 1})\n\tcheckResources(t, &s2.rc, network.ScopeStat{NumConnsOutbound: 2})\n\tcheckResources(t, &s1.rc, network.ScopeStat{NumConnsOutbound: 2})\n\n\tif err := s6.AddConn(network.DirOutbound, false); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &s6.rc, network.ScopeStat{NumConnsOutbound: 1})\n\tcheckResources(t, &s5.rc, network.ScopeStat{NumConnsOutbound: 1})\n\tcheckResources(t, &s4.rc, network.ScopeStat{NumConnsOutbound: 1})\n\tcheckResources(t, &s3.rc, network.ScopeStat{NumConnsOutbound: 2})\n\tcheckResources(t, &s2.rc, network.ScopeStat{NumConnsOutbound: 2})\n\tcheckResources(t, &s1.rc, network.ScopeStat{NumConnsOutbound: 3})\n\n\tif err := s4.AddConn(network.DirOutbound, false); err == nil {\n\t\tt.Fatal(\"expected AddConn to fail\")\n\t}\n\tif err := s5.AddConn(network.DirOutbound, false); err == nil {\n\t\tt.Fatal(\"expected AddConn to fail\")\n\t}\n\tif err := s6.AddConn(network.DirOutbound, false); err == nil {\n\t\tt.Fatal(\"expected AddConn to fail\")\n\t}\n\tcheckResources(t, &s6.rc, network.ScopeStat{NumConnsOutbound: 1})\n\tcheckResources(t, &s5.rc, network.ScopeStat{NumConnsOutbound: 1})\n\tcheckResources(t, &s4.rc, network.ScopeStat{NumConnsOutbound: 1})\n\tcheckResources(t, &s3.rc, network.ScopeStat{NumConnsOutbound: 2})\n\tcheckResources(t, &s2.rc, network.ScopeStat{NumConnsOutbound: 2})\n\tcheckResources(t, &s1.rc, network.ScopeStat{NumConnsOutbound: 3})\n\n\ts4.RemoveConn(network.DirOutbound, false)\n\tcheckResources(t, &s6.rc, network.ScopeStat{NumConnsOutbound: 1})\n\tcheckResources(t, &s5.rc, network.ScopeStat{NumConnsOutbound: 1})\n\tcheckResources(t, &s4.rc, network.ScopeStat{})\n\tcheckResources(t, &s3.rc, network.ScopeStat{NumConnsOutbound: 1})\n\tcheckResources(t, &s2.rc, network.ScopeStat{NumConnsOutbound: 1})\n\tcheckResources(t, &s1.rc, network.ScopeStat{NumConnsOutbound: 2})\n\n\ts5.RemoveConn(network.DirOutbound, false)\n\tcheckResources(t, &s6.rc, network.ScopeStat{NumConnsOutbound: 1})\n\tcheckResources(t, &s5.rc, network.ScopeStat{})\n\tcheckResources(t, &s4.rc, network.ScopeStat{})\n\tcheckResources(t, &s3.rc, network.ScopeStat{NumConnsOutbound: 1})\n\tcheckResources(t, &s2.rc, network.ScopeStat{})\n\tcheckResources(t, &s1.rc, network.ScopeStat{NumConnsOutbound: 1})\n\n\ts6.RemoveConn(network.DirOutbound, false)\n\tcheckResources(t, &s6.rc, network.ScopeStat{})\n\tcheckResources(t, &s5.rc, network.ScopeStat{})\n\tcheckResources(t, &s4.rc, network.ScopeStat{})\n\tcheckResources(t, &s3.rc, network.ScopeStat{})\n\tcheckResources(t, &s2.rc, network.ScopeStat{})\n\tcheckResources(t, &s1.rc, network.ScopeStat{})\n\n\tif err := s4.AddConn(network.DirInbound, true); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &s6.rc, network.ScopeStat{})\n\tcheckResources(t, &s5.rc, network.ScopeStat{})\n\tcheckResources(t, &s4.rc, network.ScopeStat{NumConnsInbound: 1, NumFD: 1})\n\tcheckResources(t, &s3.rc, network.ScopeStat{NumConnsInbound: 1, NumFD: 1})\n\tcheckResources(t, &s2.rc, network.ScopeStat{NumConnsInbound: 1, NumFD: 1})\n\tcheckResources(t, &s1.rc, network.ScopeStat{NumConnsInbound: 1, NumFD: 1})\n\n\tif err := s5.AddConn(network.DirInbound, true); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &s6.rc, network.ScopeStat{})\n\tcheckResources(t, &s5.rc, network.ScopeStat{NumConnsInbound: 1, NumFD: 1})\n\tcheckResources(t, &s4.rc, network.ScopeStat{NumConnsInbound: 1, NumFD: 1})\n\tcheckResources(t, &s3.rc, network.ScopeStat{NumConnsInbound: 1, NumFD: 1})\n\tcheckResources(t, &s2.rc, network.ScopeStat{NumConnsInbound: 2, NumFD: 2})\n\tcheckResources(t, &s1.rc, network.ScopeStat{NumConnsInbound: 2, NumFD: 2})\n\n\tif err := s6.AddConn(network.DirInbound, true); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &s6.rc, network.ScopeStat{NumConnsInbound: 1, NumFD: 1})\n\tcheckResources(t, &s5.rc, network.ScopeStat{NumConnsInbound: 1, NumFD: 1})\n\tcheckResources(t, &s4.rc, network.ScopeStat{NumConnsInbound: 1, NumFD: 1})\n\tcheckResources(t, &s3.rc, network.ScopeStat{NumConnsInbound: 2, NumFD: 2})\n\tcheckResources(t, &s2.rc, network.ScopeStat{NumConnsInbound: 2, NumFD: 2})\n\tcheckResources(t, &s1.rc, network.ScopeStat{NumConnsInbound: 3, NumFD: 3})\n\n\tif err := s4.AddConn(network.DirOutbound, true); err == nil {\n\t\tt.Fatal(\"expected AddConn to fail\")\n\t}\n\tif err := s5.AddConn(network.DirOutbound, true); err == nil {\n\t\tt.Fatal(\"expected AddConn to fail\")\n\t}\n\tif err := s6.AddConn(network.DirOutbound, true); err == nil {\n\t\tt.Fatal(\"expected AddConn to fail\")\n\t}\n\tcheckResources(t, &s6.rc, network.ScopeStat{NumConnsInbound: 1, NumFD: 1})\n\tcheckResources(t, &s5.rc, network.ScopeStat{NumConnsInbound: 1, NumFD: 1})\n\tcheckResources(t, &s4.rc, network.ScopeStat{NumConnsInbound: 1, NumFD: 1})\n\tcheckResources(t, &s3.rc, network.ScopeStat{NumConnsInbound: 2, NumFD: 2})\n\tcheckResources(t, &s2.rc, network.ScopeStat{NumConnsInbound: 2, NumFD: 2})\n\tcheckResources(t, &s1.rc, network.ScopeStat{NumConnsInbound: 3, NumFD: 3})\n\n\ts4.RemoveConn(network.DirInbound, true)\n\tcheckResources(t, &s6.rc, network.ScopeStat{NumConnsInbound: 1, NumFD: 1})\n\tcheckResources(t, &s5.rc, network.ScopeStat{NumConnsInbound: 1, NumFD: 1})\n\tcheckResources(t, &s4.rc, network.ScopeStat{})\n\tcheckResources(t, &s3.rc, network.ScopeStat{NumConnsInbound: 1, NumFD: 1})\n\tcheckResources(t, &s2.rc, network.ScopeStat{NumConnsInbound: 1, NumFD: 1})\n\tcheckResources(t, &s1.rc, network.ScopeStat{NumConnsInbound: 2, NumFD: 2})\n\n\ts5.RemoveConn(network.DirInbound, true)\n\tcheckResources(t, &s6.rc, network.ScopeStat{NumConnsInbound: 1, NumFD: 1})\n\tcheckResources(t, &s5.rc, network.ScopeStat{})\n\tcheckResources(t, &s4.rc, network.ScopeStat{})\n\tcheckResources(t, &s3.rc, network.ScopeStat{NumConnsInbound: 1, NumFD: 1})\n\tcheckResources(t, &s2.rc, network.ScopeStat{})\n\tcheckResources(t, &s1.rc, network.ScopeStat{NumConnsInbound: 1, NumFD: 1})\n\n\ts6.RemoveConn(network.DirInbound, true)\n\tcheckResources(t, &s6.rc, network.ScopeStat{})\n\tcheckResources(t, &s5.rc, network.ScopeStat{})\n\tcheckResources(t, &s4.rc, network.ScopeStat{})\n\tcheckResources(t, &s3.rc, network.ScopeStat{})\n\tcheckResources(t, &s2.rc, network.ScopeStat{})\n\tcheckResources(t, &s1.rc, network.ScopeStat{})\n}\n\nfunc TestResourceScopeDAGTxn(t *testing.T) {\n\t// A small DAG of scopes\n\t// s1\n\t// +---> s2\n\t//        +------------> s5\n\t//        +----\n\t// +---> s3 +.  \\\n\t//          | \\  -----+-> s4 (a diamond!)\n\t//          |  ------/\n\t//          \\\n\t//           ------> s6\n\ts1 := newResourceScope(\n\t\t&BaseLimit{Memory: 8192},\n\t\tnil, \"test\", nil, nil,\n\t)\n\ts2 := newResourceScope(\n\t\t&BaseLimit{Memory: 4096 + 2048},\n\t\t[]*resourceScope{s1}, \"test\", nil, nil,\n\t)\n\ts3 := newResourceScope(\n\t\t&BaseLimit{Memory: 4096 + 2048},\n\t\t[]*resourceScope{s1}, \"test\", nil, nil,\n\t)\n\ts4 := newResourceScope(\n\t\t&BaseLimit{Memory: 4096 + 1024},\n\t\t[]*resourceScope{s2, s3, s1}, \"test\", nil, nil,\n\t)\n\ts5 := newResourceScope(\n\t\t&BaseLimit{Memory: 4096 + 1024},\n\t\t[]*resourceScope{s2, s1}, \"test\", nil, nil,\n\t)\n\ts6 := newResourceScope(\n\t\t&BaseLimit{Memory: 4096 + 1024},\n\t\t[]*resourceScope{s3, s1}, \"test\", nil, nil,\n\t)\n\n\ttxn4, err := s4.BeginSpan()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttxn5, err := s5.BeginSpan()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttxn6, err := s6.BeginSpan()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif err := txn4.ReserveMemory(1024, network.ReservationPriorityAlways); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &s4.rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &s3.rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &s2.rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &s1.rc, network.ScopeStat{Memory: 1024})\n\n\tif err := txn5.ReserveMemory(1024, network.ReservationPriorityAlways); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &s5.rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &s4.rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &s3.rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &s2.rc, network.ScopeStat{Memory: 2048})\n\tcheckResources(t, &s1.rc, network.ScopeStat{Memory: 2048})\n\n\tif err := txn6.ReserveMemory(1024, network.ReservationPriorityAlways); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &s6.rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &s5.rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &s4.rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &s3.rc, network.ScopeStat{Memory: 2048})\n\tcheckResources(t, &s2.rc, network.ScopeStat{Memory: 2048})\n\tcheckResources(t, &s1.rc, network.ScopeStat{Memory: 3072})\n\n\tif err := txn4.ReserveMemory(4096, network.ReservationPriorityAlways); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcheckResources(t, &s6.rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &s5.rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &s4.rc, network.ScopeStat{Memory: 1024 + 4096})\n\tcheckResources(t, &s3.rc, network.ScopeStat{Memory: 2048 + 4096})\n\tcheckResources(t, &s2.rc, network.ScopeStat{Memory: 2048 + 4096})\n\tcheckResources(t, &s1.rc, network.ScopeStat{Memory: 3072 + 4096})\n\n\tif err := txn4.ReserveMemory(1024, network.ReservationPriorityAlways); err == nil {\n\t\tt.Fatal(\"expected ReserveMemory to fail\")\n\t}\n\tif err := txn5.ReserveMemory(1024, network.ReservationPriorityAlways); err == nil {\n\t\tt.Fatal(\"expected ReserveMemory to fail\")\n\t}\n\tif err := txn6.ReserveMemory(1024, network.ReservationPriorityAlways); err == nil {\n\t\tt.Fatal(\"expected ReserveMemory to fail\")\n\t}\n\tcheckResources(t, &s6.rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &s5.rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &s4.rc, network.ScopeStat{Memory: 1024 + 4096})\n\tcheckResources(t, &s3.rc, network.ScopeStat{Memory: 2048 + 4096})\n\tcheckResources(t, &s2.rc, network.ScopeStat{Memory: 2048 + 4096})\n\tcheckResources(t, &s1.rc, network.ScopeStat{Memory: 3072 + 4096})\n\n\ttxn4.Done()\n\n\tcheckResources(t, &s6.rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &s5.rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &s4.rc, network.ScopeStat{})\n\tcheckResources(t, &s3.rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &s2.rc, network.ScopeStat{Memory: 1024})\n\tcheckResources(t, &s1.rc, network.ScopeStat{Memory: 2048})\n\n\tif err := txn5.ReserveMemory(1024, network.ReservationPriorityAlways); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := txn6.ReserveMemory(1024, network.ReservationPriorityAlways); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcheckResources(t, &s6.rc, network.ScopeStat{Memory: 2048})\n\tcheckResources(t, &s5.rc, network.ScopeStat{Memory: 2048})\n\tcheckResources(t, &s4.rc, network.ScopeStat{})\n\tcheckResources(t, &s3.rc, network.ScopeStat{Memory: 2048})\n\tcheckResources(t, &s2.rc, network.ScopeStat{Memory: 2048})\n\tcheckResources(t, &s1.rc, network.ScopeStat{Memory: 4096})\n\n\ttxn5.Done()\n\ttxn6.Done()\n\n\tcheckResources(t, &s6.rc, network.ScopeStat{})\n\tcheckResources(t, &s5.rc, network.ScopeStat{})\n\tcheckResources(t, &s4.rc, network.ScopeStat{})\n\tcheckResources(t, &s3.rc, network.ScopeStat{})\n\tcheckResources(t, &s2.rc, network.ScopeStat{})\n\tcheckResources(t, &s1.rc, network.ScopeStat{})\n}\n"
  },
  {
    "path": "p2p/host/resource-manager/stats.go",
    "content": "package rcmgr\n\nimport (\n\t\"strings\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/p2p/metricshelper\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n)\n\nconst metricNamespace = \"libp2p_rcmgr\"\n\nvar (\n\n\t// Conns\n\tconns = prometheus.NewGaugeVec(prometheus.GaugeOpts{\n\t\tNamespace: metricNamespace,\n\t\tName:      \"connections\",\n\t\tHelp:      \"Number of Connections\",\n\t}, []string{\"dir\", \"scope\"})\n\n\tconnsInboundSystem     = conns.With(prometheus.Labels{\"dir\": \"inbound\", \"scope\": \"system\"})\n\tconnsInboundTransient  = conns.With(prometheus.Labels{\"dir\": \"inbound\", \"scope\": \"transient\"})\n\tconnsOutboundSystem    = conns.With(prometheus.Labels{\"dir\": \"outbound\", \"scope\": \"system\"})\n\tconnsOutboundTransient = conns.With(prometheus.Labels{\"dir\": \"outbound\", \"scope\": \"transient\"})\n\n\toneTenThenExpDistributionBuckets = []float64{\n\t\t1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 16, 32, 64, 128, 256,\n\t}\n\n\t// PeerConns\n\tpeerConns = prometheus.NewHistogramVec(prometheus.HistogramOpts{\n\t\tNamespace: metricNamespace,\n\t\tName:      \"peer_connections\",\n\t\tBuckets:   oneTenThenExpDistributionBuckets,\n\t\tHelp:      \"Number of connections this peer has\",\n\t}, []string{\"dir\"})\n\tpeerConnsInbound  = peerConns.With(prometheus.Labels{\"dir\": \"inbound\"})\n\tpeerConnsOutbound = peerConns.With(prometheus.Labels{\"dir\": \"outbound\"})\n\n\t// Lets us build a histogram of our current state. See https://github.com/libp2p/go-libp2p-resource-manager/pull/54#discussion_r911244757 for more information.\n\tpreviousPeerConns = prometheus.NewHistogramVec(prometheus.HistogramOpts{\n\t\tNamespace: metricNamespace,\n\t\tName:      \"previous_peer_connections\",\n\t\tBuckets:   oneTenThenExpDistributionBuckets,\n\t\tHelp:      \"Number of connections this peer previously had. This is used to get the current connection number per peer histogram by subtracting this from the peer_connections histogram\",\n\t}, []string{\"dir\"})\n\tpreviousPeerConnsInbound  = previousPeerConns.With(prometheus.Labels{\"dir\": \"inbound\"})\n\tpreviousPeerConnsOutbound = previousPeerConns.With(prometheus.Labels{\"dir\": \"outbound\"})\n\n\t// Streams\n\tstreams = prometheus.NewGaugeVec(prometheus.GaugeOpts{\n\t\tNamespace: metricNamespace,\n\t\tName:      \"streams\",\n\t\tHelp:      \"Number of Streams\",\n\t}, []string{\"dir\", \"scope\", \"protocol\"})\n\n\tpeerStreams = prometheus.NewHistogramVec(prometheus.HistogramOpts{\n\t\tNamespace: metricNamespace,\n\t\tName:      \"peer_streams\",\n\t\tBuckets:   oneTenThenExpDistributionBuckets,\n\t\tHelp:      \"Number of streams this peer has\",\n\t}, []string{\"dir\"})\n\tpeerStreamsInbound  = peerStreams.With(prometheus.Labels{\"dir\": \"inbound\"})\n\tpeerStreamsOutbound = peerStreams.With(prometheus.Labels{\"dir\": \"outbound\"})\n\n\tpreviousPeerStreams = prometheus.NewHistogramVec(prometheus.HistogramOpts{\n\t\tNamespace: metricNamespace,\n\t\tName:      \"previous_peer_streams\",\n\t\tBuckets:   oneTenThenExpDistributionBuckets,\n\t\tHelp:      \"Number of streams this peer has\",\n\t}, []string{\"dir\"})\n\tpreviousPeerStreamsInbound  = previousPeerStreams.With(prometheus.Labels{\"dir\": \"inbound\"})\n\tpreviousPeerStreamsOutbound = previousPeerStreams.With(prometheus.Labels{\"dir\": \"outbound\"})\n\n\t// Memory\n\tmemoryTotal = prometheus.NewGaugeVec(prometheus.GaugeOpts{\n\t\tNamespace: metricNamespace,\n\t\tName:      \"memory\",\n\t\tHelp:      \"Amount of memory reserved as reported to the Resource Manager\",\n\t}, []string{\"scope\", \"protocol\"})\n\n\t// PeerMemory\n\tpeerMemory = prometheus.NewHistogram(prometheus.HistogramOpts{\n\t\tNamespace: metricNamespace,\n\t\tName:      \"peer_memory\",\n\t\tBuckets:   memDistribution,\n\t\tHelp:      \"How many peers have reserved this bucket of memory, as reported to the Resource Manager\",\n\t})\n\tpreviousPeerMemory = prometheus.NewHistogram(prometheus.HistogramOpts{\n\t\tNamespace: metricNamespace,\n\t\tName:      \"previous_peer_memory\",\n\t\tBuckets:   memDistribution,\n\t\tHelp:      \"How many peers have previously reserved this bucket of memory, as reported to the Resource Manager\",\n\t})\n\n\t// ConnMemory\n\tconnMemory = prometheus.NewHistogram(prometheus.HistogramOpts{\n\t\tNamespace: metricNamespace,\n\t\tName:      \"conn_memory\",\n\t\tBuckets:   memDistribution,\n\t\tHelp:      \"How many conns have reserved this bucket of memory, as reported to the Resource Manager\",\n\t})\n\tpreviousConnMemory = prometheus.NewHistogram(prometheus.HistogramOpts{\n\t\tNamespace: metricNamespace,\n\t\tName:      \"previous_conn_memory\",\n\t\tBuckets:   memDistribution,\n\t\tHelp:      \"How many conns have previously reserved this bucket of memory, as reported to the Resource Manager\",\n\t})\n\n\t// FDs\n\tfds = prometheus.NewGaugeVec(prometheus.GaugeOpts{\n\t\tNamespace: metricNamespace,\n\t\tName:      \"fds\",\n\t\tHelp:      \"Number of file descriptors reserved as reported to the Resource Manager\",\n\t}, []string{\"scope\"})\n\n\tfdsSystem    = fds.With(prometheus.Labels{\"scope\": \"system\"})\n\tfdsTransient = fds.With(prometheus.Labels{\"scope\": \"transient\"})\n\n\t// Blocked resources\n\tblockedResources = prometheus.NewGaugeVec(prometheus.GaugeOpts{\n\t\tNamespace: metricNamespace,\n\t\tName:      \"blocked_resources\",\n\t\tHelp:      \"Number of blocked resources\",\n\t}, []string{\"dir\", \"scope\", \"resource\"})\n\n\t// System limits\n\tlimits = prometheus.NewGaugeVec(prometheus.GaugeOpts{\n\t\tNamespace: metricNamespace,\n\t\tName:      \"limit\",\n\t\tHelp:      \"Resource manager limits\",\n\t}, []string{\"scope\", \"resource\"})\n)\n\nvar (\n\tmemDistribution = []float64{\n\t\t1 << 10,   // 1KB\n\t\t4 << 10,   // 4KB\n\t\t32 << 10,  // 32KB\n\t\t1 << 20,   // 1MB\n\t\t32 << 20,  // 32MB\n\t\t256 << 20, // 256MB\n\t\t512 << 20, // 512MB\n\t\t1 << 30,   // 1GB\n\t\t2 << 30,   // 2GB\n\t\t4 << 30,   // 4GB\n\t}\n)\n\nfunc MustRegisterWith(reg prometheus.Registerer) {\n\tmetricshelper.RegisterCollectors(reg,\n\t\tconns,\n\t\tpeerConns,\n\t\tpreviousPeerConns,\n\t\tstreams,\n\t\tpeerStreams,\n\n\t\tpreviousPeerStreams,\n\n\t\tmemoryTotal,\n\t\tpeerMemory,\n\t\tpreviousPeerMemory,\n\t\tconnMemory,\n\t\tpreviousConnMemory,\n\t\tfds,\n\t\tblockedResources,\n\t\tlimits,\n\t)\n}\n\nfunc WithMetricsDisabled() Option {\n\treturn func(r *resourceManager) error {\n\t\tr.disableMetrics = true\n\t\treturn nil\n\t}\n}\n\n// StatsTraceReporter reports stats on the resource manager using its traces.\ntype StatsTraceReporter struct{}\n\nfunc NewStatsTraceReporter() (StatsTraceReporter, error) {\n\treturn StatsTraceReporter{}, nil\n}\n\n// reportLimit reports a limit value to Prometheus\nfunc reportLimit(scope, resource string, value int64) {\n\tlimits.With(prometheus.Labels{\n\t\t\"scope\":    scope,\n\t\t\"resource\": resource,\n\t}).Set(float64(value))\n}\n\n// ReportSystemLimits reports the system limits to Prometheus.\n// This should be called after creating the StatsTraceReporter with the resource manager's limits.\nfunc (r StatsTraceReporter) ReportSystemLimits(limiter Limiter) {\n\tif limiter == nil {\n\t\treturn\n\t}\n\n\t// System limits\n\tsystemLimits := limiter.GetSystemLimits()\n\treportLimit(\"system\", \"memory\", systemLimits.GetMemoryLimit())\n\treportLimit(\"system\", \"fd\", int64(systemLimits.GetFDLimit()))\n\treportLimit(\"system\", \"conns\", int64(systemLimits.GetConnTotalLimit()))\n\treportLimit(\"system\", \"conns_inbound\", int64(systemLimits.GetConnLimit(network.DirInbound)))\n\treportLimit(\"system\", \"conns_outbound\", int64(systemLimits.GetConnLimit(network.DirOutbound)))\n\treportLimit(\"system\", \"streams\", int64(systemLimits.GetStreamTotalLimit()))\n\treportLimit(\"system\", \"streams_inbound\", int64(systemLimits.GetStreamLimit(network.DirInbound)))\n\treportLimit(\"system\", \"streams_outbound\", int64(systemLimits.GetStreamLimit(network.DirOutbound)))\n\n\t// Transient limits\n\ttransientLimits := limiter.GetTransientLimits()\n\treportLimit(\"transient\", \"memory\", transientLimits.GetMemoryLimit())\n\treportLimit(\"transient\", \"fd\", int64(transientLimits.GetFDLimit()))\n\treportLimit(\"transient\", \"conns\", int64(transientLimits.GetConnTotalLimit()))\n\treportLimit(\"transient\", \"conns_inbound\", int64(transientLimits.GetConnLimit(network.DirInbound)))\n\treportLimit(\"transient\", \"conns_outbound\", int64(transientLimits.GetConnLimit(network.DirOutbound)))\n\treportLimit(\"transient\", \"streams\", int64(transientLimits.GetStreamTotalLimit()))\n\treportLimit(\"transient\", \"streams_inbound\", int64(transientLimits.GetStreamLimit(network.DirInbound)))\n\treportLimit(\"transient\", \"streams_outbound\", int64(transientLimits.GetStreamLimit(network.DirOutbound)))\n}\n\nfunc (r StatsTraceReporter) ConsumeEvent(evt TraceEvt) {\n\ttags := metricshelper.GetStringSlice()\n\tdefer metricshelper.PutStringSlice(tags)\n\n\tr.consumeEventWithLabelSlice(evt, tags)\n}\n\n// Separate func so that we can test that this function does not allocate. The syncPool may allocate.\nfunc (r StatsTraceReporter) consumeEventWithLabelSlice(evt TraceEvt, tags *[]string) {\n\tswitch evt.Type {\n\tcase TraceAddStreamEvt, TraceRemoveStreamEvt:\n\t\tif p := PeerStrInScopeName(evt.Name); p != \"\" {\n\t\t\t// Aggregated peer stats. Counts how many peers have N number of streams open.\n\t\t\t// Uses two buckets aggregations. One to count how many streams the\n\t\t\t// peer has now. The other to count the negative value, or how many\n\t\t\t// streams did the peer use to have. When looking at the data you\n\t\t\t// take the difference from the two.\n\n\t\t\toldStreamsOut := int64(evt.StreamsOut - evt.DeltaOut)\n\t\t\tpeerStreamsOut := int64(evt.StreamsOut)\n\t\t\tif oldStreamsOut != peerStreamsOut {\n\t\t\t\tif oldStreamsOut != 0 {\n\t\t\t\t\tpreviousPeerStreamsOutbound.Observe(float64(oldStreamsOut))\n\t\t\t\t}\n\t\t\t\tif peerStreamsOut != 0 {\n\t\t\t\t\tpeerStreamsOutbound.Observe(float64(peerStreamsOut))\n\t\t\t\t}\n\t\t\t}\n\n\t\t\toldStreamsIn := int64(evt.StreamsIn - evt.DeltaIn)\n\t\t\tpeerStreamsIn := int64(evt.StreamsIn)\n\t\t\tif oldStreamsIn != peerStreamsIn {\n\t\t\t\tif oldStreamsIn != 0 {\n\t\t\t\t\tpreviousPeerStreamsInbound.Observe(float64(oldStreamsIn))\n\t\t\t\t}\n\t\t\t\tif peerStreamsIn != 0 {\n\t\t\t\t\tpeerStreamsInbound.Observe(float64(peerStreamsIn))\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tif evt.DeltaOut != 0 {\n\t\t\t\tif IsSystemScope(evt.Name) || IsTransientScope(evt.Name) {\n\t\t\t\t\t*tags = (*tags)[:0]\n\t\t\t\t\t*tags = append(*tags, \"outbound\", evt.Name, \"\")\n\t\t\t\t\tstreams.WithLabelValues(*tags...).Set(float64(evt.StreamsOut))\n\t\t\t\t} else if proto := ParseProtocolScopeName(evt.Name); proto != \"\" {\n\t\t\t\t\t*tags = (*tags)[:0]\n\t\t\t\t\t*tags = append(*tags, \"outbound\", \"protocol\", proto)\n\t\t\t\t\tstreams.WithLabelValues(*tags...).Set(float64(evt.StreamsOut))\n\t\t\t\t} else {\n\t\t\t\t\t// Not measuring service scope, connscope, servicepeer and protocolpeer. Lots of data, and\n\t\t\t\t\t// you can use aggregated peer stats + service stats to infer\n\t\t\t\t\t// this.\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif evt.DeltaIn != 0 {\n\t\t\t\tif IsSystemScope(evt.Name) || IsTransientScope(evt.Name) {\n\t\t\t\t\t*tags = (*tags)[:0]\n\t\t\t\t\t*tags = append(*tags, \"inbound\", evt.Name, \"\")\n\t\t\t\t\tstreams.WithLabelValues(*tags...).Set(float64(evt.StreamsIn))\n\t\t\t\t} else if proto := ParseProtocolScopeName(evt.Name); proto != \"\" {\n\t\t\t\t\t*tags = (*tags)[:0]\n\t\t\t\t\t*tags = append(*tags, \"inbound\", \"protocol\", proto)\n\t\t\t\t\tstreams.WithLabelValues(*tags...).Set(float64(evt.StreamsIn))\n\t\t\t\t} else {\n\t\t\t\t\t// Not measuring service scope, connscope, servicepeer and protocolpeer. Lots of data, and\n\t\t\t\t\t// you can use aggregated peer stats + service stats to infer\n\t\t\t\t\t// this.\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase TraceAddConnEvt, TraceRemoveConnEvt:\n\t\tif p := PeerStrInScopeName(evt.Name); p != \"\" {\n\t\t\t// Aggregated peer stats. Counts how many peers have N number of connections.\n\t\t\t// Uses two buckets aggregations. One to count how many streams the\n\t\t\t// peer has now. The other to count the negative value, or how many\n\t\t\t// conns did the peer use to have. When looking at the data you\n\t\t\t// take the difference from the two.\n\n\t\t\toldConnsOut := int64(evt.ConnsOut - evt.DeltaOut)\n\t\t\tconnsOut := int64(evt.ConnsOut)\n\t\t\tif oldConnsOut != connsOut {\n\t\t\t\tif oldConnsOut != 0 {\n\t\t\t\t\tpreviousPeerConnsOutbound.Observe(float64(oldConnsOut))\n\t\t\t\t}\n\t\t\t\tif connsOut != 0 {\n\t\t\t\t\tpeerConnsOutbound.Observe(float64(connsOut))\n\t\t\t\t}\n\t\t\t}\n\n\t\t\toldConnsIn := int64(evt.ConnsIn - evt.DeltaIn)\n\t\t\tconnsIn := int64(evt.ConnsIn)\n\t\t\tif oldConnsIn != connsIn {\n\t\t\t\tif oldConnsIn != 0 {\n\t\t\t\t\tpreviousPeerConnsInbound.Observe(float64(oldConnsIn))\n\t\t\t\t}\n\t\t\t\tif connsIn != 0 {\n\t\t\t\t\tpeerConnsInbound.Observe(float64(connsIn))\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tif IsConnScope(evt.Name) {\n\t\t\t\t// Not measuring this. I don't think it's useful.\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tif IsSystemScope(evt.Name) {\n\t\t\t\tconnsInboundSystem.Set(float64(evt.ConnsIn))\n\t\t\t\tconnsOutboundSystem.Set(float64(evt.ConnsOut))\n\t\t\t} else if IsTransientScope(evt.Name) {\n\t\t\t\tconnsInboundTransient.Set(float64(evt.ConnsIn))\n\t\t\t\tconnsOutboundTransient.Set(float64(evt.ConnsOut))\n\t\t\t}\n\n\t\t\t// Represents the delta in fds\n\t\t\tif evt.Delta != 0 {\n\t\t\t\tif IsSystemScope(evt.Name) {\n\t\t\t\t\tfdsSystem.Set(float64(evt.FD))\n\t\t\t\t} else if IsTransientScope(evt.Name) {\n\t\t\t\t\tfdsTransient.Set(float64(evt.FD))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase TraceReserveMemoryEvt, TraceReleaseMemoryEvt:\n\t\tif p := PeerStrInScopeName(evt.Name); p != \"\" {\n\t\t\toldMem := evt.Memory - evt.Delta\n\t\t\tif oldMem != evt.Memory {\n\t\t\t\tif oldMem != 0 {\n\t\t\t\t\tpreviousPeerMemory.Observe(float64(oldMem))\n\t\t\t\t}\n\t\t\t\tif evt.Memory != 0 {\n\t\t\t\t\tpeerMemory.Observe(float64(evt.Memory))\n\t\t\t\t}\n\t\t\t}\n\t\t} else if IsConnScope(evt.Name) {\n\t\t\toldMem := evt.Memory - evt.Delta\n\t\t\tif oldMem != evt.Memory {\n\t\t\t\tif oldMem != 0 {\n\t\t\t\t\tpreviousConnMemory.Observe(float64(oldMem))\n\t\t\t\t}\n\t\t\t\tif evt.Memory != 0 {\n\t\t\t\t\tconnMemory.Observe(float64(evt.Memory))\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tif IsSystemScope(evt.Name) || IsTransientScope(evt.Name) {\n\t\t\t\t*tags = (*tags)[:0]\n\t\t\t\t*tags = append(*tags, evt.Name, \"\")\n\t\t\t\tmemoryTotal.WithLabelValues(*tags...).Set(float64(evt.Memory))\n\t\t\t} else if proto := ParseProtocolScopeName(evt.Name); proto != \"\" {\n\t\t\t\t*tags = (*tags)[:0]\n\t\t\t\t*tags = append(*tags, \"protocol\", proto)\n\t\t\t\tmemoryTotal.WithLabelValues(*tags...).Set(float64(evt.Memory))\n\t\t\t} else {\n\t\t\t\t// Not measuring connscope, servicepeer and protocolpeer. Lots of data, and\n\t\t\t\t// you can use aggregated peer stats + service stats to infer\n\t\t\t\t// this.\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\tcase TraceBlockAddConnEvt, TraceBlockAddStreamEvt, TraceBlockReserveMemoryEvt:\n\t\tvar resource string\n\t\tif evt.Type == TraceBlockAddConnEvt {\n\t\t\tresource = \"connection\"\n\t\t} else if evt.Type == TraceBlockAddStreamEvt {\n\t\t\tresource = \"stream\"\n\t\t} else {\n\t\t\tresource = \"memory\"\n\t\t}\n\n\t\tscopeName := evt.Name\n\t\t// Only the top scopeName. We don't want to get the peerid here.\n\t\t// Using indexes and slices to avoid allocating.\n\t\tscopeSplitIdx := strings.IndexByte(scopeName, ':')\n\t\tif scopeSplitIdx != -1 {\n\t\t\tscopeName = evt.Name[0:scopeSplitIdx]\n\t\t}\n\t\t// Drop the connection or stream id\n\t\tidSplitIdx := strings.IndexByte(scopeName, '-')\n\t\tif idSplitIdx != -1 {\n\t\t\tscopeName = scopeName[0:idSplitIdx]\n\t\t}\n\n\t\tif evt.DeltaIn != 0 {\n\t\t\t*tags = (*tags)[:0]\n\t\t\t*tags = append(*tags, \"inbound\", scopeName, resource)\n\t\t\tblockedResources.WithLabelValues(*tags...).Add(float64(evt.DeltaIn))\n\t\t}\n\n\t\tif evt.DeltaOut != 0 {\n\t\t\t*tags = (*tags)[:0]\n\t\t\t*tags = append(*tags, \"outbound\", scopeName, resource)\n\t\t\tblockedResources.WithLabelValues(*tags...).Add(float64(evt.DeltaOut))\n\t\t}\n\n\t\tif evt.Delta != 0 && resource == \"connection\" {\n\t\t\t// This represents fds blocked\n\t\t\t*tags = (*tags)[:0]\n\t\t\t*tags = append(*tags, \"\", scopeName, \"fd\")\n\t\t\tblockedResources.WithLabelValues(*tags...).Add(float64(evt.Delta))\n\t\t} else if evt.Delta != 0 {\n\t\t\t*tags = (*tags)[:0]\n\t\t\t*tags = append(*tags, \"\", scopeName, resource)\n\t\t\tblockedResources.WithLabelValues(*tags...).Add(float64(evt.Delta))\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "p2p/host/resource-manager/stats_test.go",
    "content": "package rcmgr\n\nimport (\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/prometheus/client_golang/prometheus\"\n)\n\nvar registerOnce sync.Once\n\nfunc TestTraceReporterStartAndClose(t *testing.T) {\n\trcmgr, err := NewResourceManager(NewFixedLimiter(DefaultLimits.AutoScale()), WithTraceReporter(StatsTraceReporter{}))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer rcmgr.Close()\n}\n\nfunc TestConsumeEvent(t *testing.T) {\n\tevt := TraceEvt{\n\t\tType:     TraceBlockAddStreamEvt,\n\t\tName:     \"conn-1\",\n\t\tDeltaOut: 1,\n\t\tTime:     time.Now().Format(time.RFC3339Nano),\n\t}\n\n\tregisterOnce.Do(func() {\n\t\tMustRegisterWith(prometheus.DefaultRegisterer)\n\t})\n\n\tstr, err := NewStatsTraceReporter()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tstr.ConsumeEvent(evt)\n}\n"
  },
  {
    "path": "p2p/host/resource-manager/sys_not_unix.go",
    "content": "//go:build !linux && !darwin && !windows\n\npackage rcmgr\n\nimport \"runtime\"\n\n// TODO: figure out how to get the number of file descriptors on Windows and other systems\nfunc getNumFDs() int {\n\tlog.Warn(\"cannot determine number of file descriptors\", \"os\", runtime.GOOS)\n\treturn 0\n}\n"
  },
  {
    "path": "p2p/host/resource-manager/sys_unix.go",
    "content": "//go:build linux || darwin\n\npackage rcmgr\n\nimport (\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc getNumFDs() int {\n\tvar l unix.Rlimit\n\tif err := unix.Getrlimit(unix.RLIMIT_NOFILE, &l); err != nil {\n\t\tlog.Error(\"failed to get fd limit\", \"err\", err)\n\t\treturn 0\n\t}\n\treturn int(l.Cur)\n}\n"
  },
  {
    "path": "p2p/host/resource-manager/sys_windows.go",
    "content": "//go:build windows\n\npackage rcmgr\n\nimport (\n\t\"math\"\n)\n\nfunc getNumFDs() int {\n\treturn math.MaxInt\n}\n"
  },
  {
    "path": "p2p/host/resource-manager/trace.go",
    "content": "package rcmgr\n\nimport (\n\t\"compress/gzip\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n)\n\ntype trace struct {\n\tpath string\n\n\tctx    context.Context\n\tcancel func()\n\twg     sync.WaitGroup\n\n\tmx            sync.Mutex\n\tdone          bool\n\tpendingWrites []any\n\treporters     []TraceReporter\n}\n\ntype TraceReporter interface {\n\t// ConsumeEvent consumes a trace event. This is called synchronously,\n\t// implementations should process the event quickly.\n\tConsumeEvent(TraceEvt)\n}\n\nfunc WithTrace(path string) Option {\n\treturn func(r *resourceManager) error {\n\t\tif r.trace == nil {\n\t\t\tr.trace = &trace{path: path}\n\t\t} else {\n\t\t\tr.trace.path = path\n\t\t}\n\t\treturn nil\n\t}\n}\n\nfunc WithTraceReporter(reporter TraceReporter) Option {\n\treturn func(r *resourceManager) error {\n\t\tif r.trace == nil {\n\t\t\tr.trace = &trace{}\n\t\t}\n\t\tr.trace.reporters = append(r.trace.reporters, reporter)\n\t\treturn nil\n\t}\n}\n\ntype TraceEvtTyp string\n\nconst (\n\tTraceStartEvt              TraceEvtTyp = \"start\"\n\tTraceCreateScopeEvt        TraceEvtTyp = \"create_scope\"\n\tTraceDestroyScopeEvt       TraceEvtTyp = \"destroy_scope\"\n\tTraceReserveMemoryEvt      TraceEvtTyp = \"reserve_memory\"\n\tTraceBlockReserveMemoryEvt TraceEvtTyp = \"block_reserve_memory\"\n\tTraceReleaseMemoryEvt      TraceEvtTyp = \"release_memory\"\n\tTraceAddStreamEvt          TraceEvtTyp = \"add_stream\"\n\tTraceBlockAddStreamEvt     TraceEvtTyp = \"block_add_stream\"\n\tTraceRemoveStreamEvt       TraceEvtTyp = \"remove_stream\"\n\tTraceAddConnEvt            TraceEvtTyp = \"add_conn\"\n\tTraceBlockAddConnEvt       TraceEvtTyp = \"block_add_conn\"\n\tTraceRemoveConnEvt         TraceEvtTyp = \"remove_conn\"\n)\n\ntype scopeClass struct {\n\tname string\n}\n\nfunc (s scopeClass) MarshalJSON() ([]byte, error) {\n\tname := s.name\n\tvar span string\n\tif idx := strings.Index(name, \"span:\"); idx > -1 {\n\t\tname = name[:idx-1]\n\t\tspan = name[idx+5:]\n\t}\n\t// System and Transient scope\n\tif name == \"system\" || name == \"transient\" || name == \"allowlistedSystem\" || name == \"allowlistedTransient\" {\n\t\treturn json.Marshal(struct {\n\t\t\tClass string\n\t\t\tSpan  string `json:\",omitempty\"`\n\t\t}{\n\t\t\tClass: name,\n\t\t\tSpan:  span,\n\t\t})\n\t}\n\t// Connection scope\n\tif strings.HasPrefix(name, \"conn-\") {\n\t\treturn json.Marshal(struct {\n\t\t\tClass string\n\t\t\tConn  string\n\t\t\tSpan  string `json:\",omitempty\"`\n\t\t}{\n\t\t\tClass: \"conn\",\n\t\t\tConn:  name[5:],\n\t\t\tSpan:  span,\n\t\t})\n\t}\n\t// Stream scope\n\tif strings.HasPrefix(name, \"stream-\") {\n\t\treturn json.Marshal(struct {\n\t\t\tClass  string\n\t\t\tStream string\n\t\t\tSpan   string `json:\",omitempty\"`\n\t\t}{\n\t\t\tClass:  \"stream\",\n\t\t\tStream: name[7:],\n\t\t\tSpan:   span,\n\t\t})\n\t}\n\t// Peer scope\n\tif strings.HasPrefix(name, \"peer:\") {\n\t\treturn json.Marshal(struct {\n\t\t\tClass string\n\t\t\tPeer  string\n\t\t\tSpan  string `json:\",omitempty\"`\n\t\t}{\n\t\t\tClass: \"peer\",\n\t\t\tPeer:  name[5:],\n\t\t\tSpan:  span,\n\t\t})\n\t}\n\n\tif strings.HasPrefix(name, \"service:\") {\n\t\tif idx := strings.Index(name, \"peer:\"); idx > 0 { // Peer-Service scope\n\t\t\treturn json.Marshal(struct {\n\t\t\t\tClass   string\n\t\t\t\tService string\n\t\t\t\tPeer    string\n\t\t\t\tSpan    string `json:\",omitempty\"`\n\t\t\t}{\n\t\t\t\tClass:   \"service-peer\",\n\t\t\t\tService: name[8 : idx-1],\n\t\t\t\tPeer:    name[idx+5:],\n\t\t\t\tSpan:    span,\n\t\t\t})\n\t\t} else { // Service scope\n\t\t\treturn json.Marshal(struct {\n\t\t\t\tClass   string\n\t\t\t\tService string\n\t\t\t\tSpan    string `json:\",omitempty\"`\n\t\t\t}{\n\t\t\t\tClass:   \"service\",\n\t\t\t\tService: name[8:],\n\t\t\t\tSpan:    span,\n\t\t\t})\n\t\t}\n\t}\n\n\tif strings.HasPrefix(name, \"protocol:\") {\n\t\tif idx := strings.Index(name, \"peer:\"); idx > -1 { // Peer-Protocol scope\n\t\t\treturn json.Marshal(struct {\n\t\t\t\tClass    string\n\t\t\t\tProtocol string\n\t\t\t\tPeer     string\n\t\t\t\tSpan     string `json:\",omitempty\"`\n\t\t\t}{\n\t\t\t\tClass:    \"protocol-peer\",\n\t\t\t\tProtocol: name[9 : idx-1],\n\t\t\t\tPeer:     name[idx+5:],\n\t\t\t\tSpan:     span,\n\t\t\t})\n\t\t} else { // Protocol scope\n\t\t\treturn json.Marshal(struct {\n\t\t\t\tClass    string\n\t\t\t\tProtocol string\n\t\t\t\tSpan     string `json:\",omitempty\"`\n\t\t\t}{\n\t\t\t\tClass:    \"protocol\",\n\t\t\t\tProtocol: name[9:],\n\t\t\t\tSpan:     span,\n\t\t\t})\n\t\t}\n\t}\n\n\treturn nil, fmt.Errorf(\"unrecognized scope: %s\", name)\n}\n\ntype TraceEvt struct {\n\tTime string\n\tType TraceEvtTyp\n\n\tScope *scopeClass `json:\",omitempty\"`\n\tName  string      `json:\",omitempty\"`\n\n\tLimit any `json:\",omitempty\"`\n\n\tPriority uint8 `json:\",omitempty\"`\n\n\tDelta    int64 `json:\",omitempty\"`\n\tDeltaIn  int   `json:\",omitempty\"`\n\tDeltaOut int   `json:\",omitempty\"`\n\n\tMemory int64 `json:\",omitempty\"`\n\n\tStreamsIn  int `json:\",omitempty\"`\n\tStreamsOut int `json:\",omitempty\"`\n\n\tConnsIn  int `json:\",omitempty\"`\n\tConnsOut int `json:\",omitempty\"`\n\n\tFD int `json:\",omitempty\"`\n}\n\nfunc (t *trace) push(evt TraceEvt) {\n\tt.mx.Lock()\n\tdefer t.mx.Unlock()\n\n\tif t.done {\n\t\treturn\n\t}\n\tevt.Time = time.Now().Format(time.RFC3339Nano)\n\tif evt.Name != \"\" {\n\t\tevt.Scope = &scopeClass{name: evt.Name}\n\t}\n\n\tfor _, reporter := range t.reporters {\n\t\treporter.ConsumeEvent(evt)\n\t}\n\n\tif t.path != \"\" {\n\t\tt.pendingWrites = append(t.pendingWrites, evt)\n\t}\n}\n\nfunc (t *trace) backgroundWriter(out io.WriteCloser) {\n\tdefer t.wg.Done()\n\tdefer out.Close()\n\n\tgzOut := gzip.NewWriter(out)\n\tdefer gzOut.Close()\n\n\tjsonOut := json.NewEncoder(gzOut)\n\n\tticker := time.NewTicker(time.Second)\n\tdefer ticker.Stop()\n\n\tvar pend []any\n\n\tgetEvents := func() {\n\t\tt.mx.Lock()\n\t\ttmp := t.pendingWrites\n\t\tt.pendingWrites = pend[:0]\n\t\tpend = tmp\n\t\tt.mx.Unlock()\n\t}\n\n\tfor {\n\t\tselect {\n\t\tcase <-ticker.C:\n\t\t\tgetEvents()\n\n\t\t\tif len(pend) == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif err := t.writeEvents(pend, jsonOut); err != nil {\n\t\t\t\tlog.Warn(\"error writing rcmgr trace\", \"err\", err)\n\t\t\t\tt.mx.Lock()\n\t\t\t\tt.done = true\n\t\t\t\tt.mx.Unlock()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif err := gzOut.Flush(); err != nil {\n\t\t\t\tlog.Warn(\"error flushing rcmgr trace\", \"err\", err)\n\t\t\t\tt.mx.Lock()\n\t\t\t\tt.done = true\n\t\t\t\tt.mx.Unlock()\n\t\t\t\treturn\n\t\t\t}\n\n\t\tcase <-t.ctx.Done():\n\t\t\tgetEvents()\n\n\t\t\tif len(pend) == 0 {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif err := t.writeEvents(pend, jsonOut); err != nil {\n\t\t\t\tlog.Warn(\"error writing rcmgr trace\", \"err\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif err := gzOut.Flush(); err != nil {\n\t\t\t\tlog.Warn(\"error flushing rcmgr trace\", \"err\", err)\n\t\t\t}\n\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (t *trace) writeEvents(pend []any, jout *json.Encoder) error {\n\tfor _, e := range pend {\n\t\tif err := jout.Encode(e); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (t *trace) Start(limits Limiter) error {\n\tif t == nil {\n\t\treturn nil\n\t}\n\n\tt.ctx, t.cancel = context.WithCancel(context.Background())\n\n\tif t.path != \"\" {\n\t\tout, err := os.OpenFile(t.path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\n\t\tt.wg.Add(1)\n\t\tgo t.backgroundWriter(out)\n\t}\n\n\tt.push(TraceEvt{\n\t\tType:  TraceStartEvt,\n\t\tLimit: limits,\n\t})\n\n\treturn nil\n}\n\nfunc (t *trace) Close() error {\n\tif t == nil {\n\t\treturn nil\n\t}\n\n\tt.mx.Lock()\n\n\tif t.done {\n\t\tt.mx.Unlock()\n\t\treturn nil\n\t}\n\n\tt.cancel()\n\tt.done = true\n\tt.mx.Unlock()\n\n\tt.wg.Wait()\n\treturn nil\n}\n\nfunc (t *trace) CreateScope(scope string, limit Limit) {\n\tif t == nil {\n\t\treturn\n\t}\n\n\tt.push(TraceEvt{\n\t\tType:  TraceCreateScopeEvt,\n\t\tName:  scope,\n\t\tLimit: limit,\n\t})\n}\n\nfunc (t *trace) DestroyScope(scope string) {\n\tif t == nil {\n\t\treturn\n\t}\n\n\tt.push(TraceEvt{\n\t\tType: TraceDestroyScopeEvt,\n\t\tName: scope,\n\t})\n}\n\nfunc (t *trace) ReserveMemory(scope string, prio uint8, size, mem int64) {\n\tif t == nil {\n\t\treturn\n\t}\n\n\tif size == 0 {\n\t\treturn\n\t}\n\n\tt.push(TraceEvt{\n\t\tType:     TraceReserveMemoryEvt,\n\t\tName:     scope,\n\t\tPriority: prio,\n\t\tDelta:    size,\n\t\tMemory:   mem,\n\t})\n}\n\nfunc (t *trace) BlockReserveMemory(scope string, prio uint8, size, mem int64) {\n\tif t == nil {\n\t\treturn\n\t}\n\n\tif size == 0 {\n\t\treturn\n\t}\n\n\tt.push(TraceEvt{\n\t\tType:     TraceBlockReserveMemoryEvt,\n\t\tName:     scope,\n\t\tPriority: prio,\n\t\tDelta:    size,\n\t\tMemory:   mem,\n\t})\n}\n\nfunc (t *trace) ReleaseMemory(scope string, size, mem int64) {\n\tif t == nil {\n\t\treturn\n\t}\n\n\tif size == 0 {\n\t\treturn\n\t}\n\n\tt.push(TraceEvt{\n\t\tType:   TraceReleaseMemoryEvt,\n\t\tName:   scope,\n\t\tDelta:  -size,\n\t\tMemory: mem,\n\t})\n}\n\nfunc (t *trace) AddStream(scope string, dir network.Direction, nstreamsIn, nstreamsOut int) {\n\tif t == nil {\n\t\treturn\n\t}\n\n\tvar deltaIn, deltaOut int\n\tif dir == network.DirInbound {\n\t\tdeltaIn = 1\n\t} else {\n\t\tdeltaOut = 1\n\t}\n\n\tt.push(TraceEvt{\n\t\tType:       TraceAddStreamEvt,\n\t\tName:       scope,\n\t\tDeltaIn:    deltaIn,\n\t\tDeltaOut:   deltaOut,\n\t\tStreamsIn:  nstreamsIn,\n\t\tStreamsOut: nstreamsOut,\n\t})\n}\n\nfunc (t *trace) BlockAddStream(scope string, dir network.Direction, nstreamsIn, nstreamsOut int) {\n\tif t == nil {\n\t\treturn\n\t}\n\n\tvar deltaIn, deltaOut int\n\tif dir == network.DirInbound {\n\t\tdeltaIn = 1\n\t} else {\n\t\tdeltaOut = 1\n\t}\n\n\tt.push(TraceEvt{\n\t\tType:       TraceBlockAddStreamEvt,\n\t\tName:       scope,\n\t\tDeltaIn:    deltaIn,\n\t\tDeltaOut:   deltaOut,\n\t\tStreamsIn:  nstreamsIn,\n\t\tStreamsOut: nstreamsOut,\n\t})\n}\n\nfunc (t *trace) RemoveStream(scope string, dir network.Direction, nstreamsIn, nstreamsOut int) {\n\tif t == nil {\n\t\treturn\n\t}\n\n\tvar deltaIn, deltaOut int\n\tif dir == network.DirInbound {\n\t\tdeltaIn = -1\n\t} else {\n\t\tdeltaOut = -1\n\t}\n\n\tt.push(TraceEvt{\n\t\tType:       TraceRemoveStreamEvt,\n\t\tName:       scope,\n\t\tDeltaIn:    deltaIn,\n\t\tDeltaOut:   deltaOut,\n\t\tStreamsIn:  nstreamsIn,\n\t\tStreamsOut: nstreamsOut,\n\t})\n}\n\nfunc (t *trace) AddStreams(scope string, deltaIn, deltaOut, nstreamsIn, nstreamsOut int) {\n\tif t == nil {\n\t\treturn\n\t}\n\n\tif deltaIn == 0 && deltaOut == 0 {\n\t\treturn\n\t}\n\n\tt.push(TraceEvt{\n\t\tType:       TraceAddStreamEvt,\n\t\tName:       scope,\n\t\tDeltaIn:    deltaIn,\n\t\tDeltaOut:   deltaOut,\n\t\tStreamsIn:  nstreamsIn,\n\t\tStreamsOut: nstreamsOut,\n\t})\n}\n\nfunc (t *trace) BlockAddStreams(scope string, deltaIn, deltaOut, nstreamsIn, nstreamsOut int) {\n\tif t == nil {\n\t\treturn\n\t}\n\n\tif deltaIn == 0 && deltaOut == 0 {\n\t\treturn\n\t}\n\n\tt.push(TraceEvt{\n\t\tType:       TraceBlockAddStreamEvt,\n\t\tName:       scope,\n\t\tDeltaIn:    deltaIn,\n\t\tDeltaOut:   deltaOut,\n\t\tStreamsIn:  nstreamsIn,\n\t\tStreamsOut: nstreamsOut,\n\t})\n}\n\nfunc (t *trace) RemoveStreams(scope string, deltaIn, deltaOut, nstreamsIn, nstreamsOut int) {\n\tif t == nil {\n\t\treturn\n\t}\n\n\tif deltaIn == 0 && deltaOut == 0 {\n\t\treturn\n\t}\n\n\tt.push(TraceEvt{\n\t\tType:       TraceRemoveStreamEvt,\n\t\tName:       scope,\n\t\tDeltaIn:    -deltaIn,\n\t\tDeltaOut:   -deltaOut,\n\t\tStreamsIn:  nstreamsIn,\n\t\tStreamsOut: nstreamsOut,\n\t})\n}\n\nfunc (t *trace) AddConn(scope string, dir network.Direction, usefd bool, nconnsIn, nconnsOut, nfd int) {\n\tif t == nil {\n\t\treturn\n\t}\n\n\tvar deltaIn, deltaOut, deltafd int\n\tif dir == network.DirInbound {\n\t\tdeltaIn = 1\n\t} else {\n\t\tdeltaOut = 1\n\t}\n\tif usefd {\n\t\tdeltafd = 1\n\t}\n\n\tt.push(TraceEvt{\n\t\tType:     TraceAddConnEvt,\n\t\tName:     scope,\n\t\tDeltaIn:  deltaIn,\n\t\tDeltaOut: deltaOut,\n\t\tDelta:    int64(deltafd),\n\t\tConnsIn:  nconnsIn,\n\t\tConnsOut: nconnsOut,\n\t\tFD:       nfd,\n\t})\n}\n\nfunc (t *trace) BlockAddConn(scope string, dir network.Direction, usefd bool, nconnsIn, nconnsOut, nfd int) {\n\tif t == nil {\n\t\treturn\n\t}\n\n\tvar deltaIn, deltaOut, deltafd int\n\tif dir == network.DirInbound {\n\t\tdeltaIn = 1\n\t} else {\n\t\tdeltaOut = 1\n\t}\n\tif usefd {\n\t\tdeltafd = 1\n\t}\n\n\tt.push(TraceEvt{\n\t\tType:     TraceBlockAddConnEvt,\n\t\tName:     scope,\n\t\tDeltaIn:  deltaIn,\n\t\tDeltaOut: deltaOut,\n\t\tDelta:    int64(deltafd),\n\t\tConnsIn:  nconnsIn,\n\t\tConnsOut: nconnsOut,\n\t\tFD:       nfd,\n\t})\n}\n\nfunc (t *trace) RemoveConn(scope string, dir network.Direction, usefd bool, nconnsIn, nconnsOut, nfd int) {\n\tif t == nil {\n\t\treturn\n\t}\n\n\tvar deltaIn, deltaOut, deltafd int\n\tif dir == network.DirInbound {\n\t\tdeltaIn = -1\n\t} else {\n\t\tdeltaOut = -1\n\t}\n\tif usefd {\n\t\tdeltafd = -1\n\t}\n\n\tt.push(TraceEvt{\n\t\tType:     TraceRemoveConnEvt,\n\t\tName:     scope,\n\t\tDeltaIn:  deltaIn,\n\t\tDeltaOut: deltaOut,\n\t\tDelta:    int64(deltafd),\n\t\tConnsIn:  nconnsIn,\n\t\tConnsOut: nconnsOut,\n\t\tFD:       nfd,\n\t})\n}\n\nfunc (t *trace) AddConns(scope string, deltaIn, deltaOut, deltafd, nconnsIn, nconnsOut, nfd int) {\n\tif t == nil {\n\t\treturn\n\t}\n\n\tif deltaIn == 0 && deltaOut == 0 && deltafd == 0 {\n\t\treturn\n\t}\n\n\tt.push(TraceEvt{\n\t\tType:     TraceAddConnEvt,\n\t\tName:     scope,\n\t\tDeltaIn:  deltaIn,\n\t\tDeltaOut: deltaOut,\n\t\tDelta:    int64(deltafd),\n\t\tConnsIn:  nconnsIn,\n\t\tConnsOut: nconnsOut,\n\t\tFD:       nfd,\n\t})\n}\n\nfunc (t *trace) BlockAddConns(scope string, deltaIn, deltaOut, deltafd, nconnsIn, nconnsOut, nfd int) {\n\tif t == nil {\n\t\treturn\n\t}\n\n\tif deltaIn == 0 && deltaOut == 0 && deltafd == 0 {\n\t\treturn\n\t}\n\n\tt.push(TraceEvt{\n\t\tType:     TraceBlockAddConnEvt,\n\t\tName:     scope,\n\t\tDeltaIn:  deltaIn,\n\t\tDeltaOut: deltaOut,\n\t\tDelta:    int64(deltafd),\n\t\tConnsIn:  nconnsIn,\n\t\tConnsOut: nconnsOut,\n\t\tFD:       nfd,\n\t})\n}\n\nfunc (t *trace) RemoveConns(scope string, deltaIn, deltaOut, deltafd, nconnsIn, nconnsOut, nfd int) {\n\tif t == nil {\n\t\treturn\n\t}\n\n\tif deltaIn == 0 && deltaOut == 0 && deltafd == 0 {\n\t\treturn\n\t}\n\n\tt.push(TraceEvt{\n\t\tType:     TraceRemoveConnEvt,\n\t\tName:     scope,\n\t\tDeltaIn:  -deltaIn,\n\t\tDeltaOut: -deltaOut,\n\t\tDelta:    -int64(deltafd),\n\t\tConnsIn:  nconnsIn,\n\t\tConnsOut: nconnsOut,\n\t\tFD:       nfd,\n\t})\n}\n"
  },
  {
    "path": "p2p/host/routed/routed.go",
    "content": "package routedhost\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/connmgr\"\n\t\"github.com/libp2p/go-libp2p/core/event\"\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\nvar log = logging.Logger(\"routedhost\")\n\n// AddressTTL is the expiry time for our addresses.\n// We expire them quickly.\nconst AddressTTL = time.Second * 10\n\n// RoutedHost is a p2p Host that includes a routing system.\n// This allows the Host to find the addresses for peers when\n// it does not have them.\ntype RoutedHost struct {\n\thost  host.Host // embedded other host.\n\troute Routing\n}\n\ntype Routing interface {\n\tFindPeer(context.Context, peer.ID) (peer.AddrInfo, error)\n}\n\nfunc Wrap(h host.Host, r Routing) *RoutedHost {\n\treturn &RoutedHost{h, r}\n}\n\n// Connect ensures there is a connection between this host and the peer with\n// given peer.ID. See (host.Host).Connect for more information.\n//\n// RoutedHost's Connect differs in that if the host has no addresses for a\n// given peer, it will use its routing system to try to find some.\nfunc (rh *RoutedHost) Connect(ctx context.Context, pi peer.AddrInfo) error {\n\t// first, check if we're already connected unless force direct dial.\n\tforceDirect, _ := network.GetForceDirectDial(ctx)\n\tcanUseLimitedConn, _ := network.GetAllowLimitedConn(ctx)\n\tif !forceDirect {\n\t\tconnectedness := rh.Network().Connectedness(pi.ID)\n\t\tif connectedness == network.Connected || (canUseLimitedConn && connectedness == network.Limited) {\n\t\t\treturn nil\n\t\t}\n\t}\n\n\t// if we were given some addresses, keep + use them.\n\tif len(pi.Addrs) > 0 {\n\t\trh.Peerstore().AddAddrs(pi.ID, pi.Addrs, peerstore.TempAddrTTL)\n\t}\n\n\t// Check if we have some addresses in our recent memory.\n\taddrs := rh.Peerstore().Addrs(pi.ID)\n\tif len(addrs) < 1 {\n\t\t// no addrs? find some with the routing system.\n\t\tvar err error\n\t\taddrs, err = rh.findPeerAddrs(ctx, pi.ID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// Issue 448: if our address set includes routed specific relay addrs,\n\t// we need to make sure the relay's addr itself is in the peerstore or else\n\t// we won't be able to dial it.\n\tfor _, addr := range addrs {\n\t\tif _, err := addr.ValueForProtocol(ma.P_CIRCUIT); err != nil {\n\t\t\t// not a relay address\n\t\t\tcontinue\n\t\t}\n\n\t\tif addr.Protocols()[0].Code != ma.P_P2P {\n\t\t\t// not a routed relay specific address\n\t\t\tcontinue\n\t\t}\n\n\t\trelay, _ := addr.ValueForProtocol(ma.P_P2P)\n\t\trelayID, err := peer.Decode(relay)\n\t\tif err != nil {\n\t\t\tlog.Debug(\"failed to parse relay ID in address\", \"relay\", relay, \"err\", err)\n\t\t\tcontinue\n\t\t}\n\n\t\tif len(rh.Peerstore().Addrs(relayID)) > 0 {\n\t\t\t// we already have addrs for this relay\n\t\t\tcontinue\n\t\t}\n\n\t\trelayAddrs, err := rh.findPeerAddrs(ctx, relayID)\n\t\tif err != nil {\n\t\t\tlog.Debug(\"failed to find relay\", \"relay\", relay, \"err\", err)\n\t\t\tcontinue\n\t\t}\n\n\t\trh.Peerstore().AddAddrs(relayID, relayAddrs, peerstore.TempAddrTTL)\n\t}\n\n\t// if we're here, we got some addrs. let's use our wrapped host to connect.\n\tpi.Addrs = addrs\n\tif cerr := rh.host.Connect(ctx, pi); cerr != nil {\n\t\t// We couldn't connect. Let's check if we have the most\n\t\t// up-to-date addresses for the given peer. If there\n\t\t// are addresses we didn't know about previously, we\n\t\t// try to connect again.\n\t\tnewAddrs, err := rh.findPeerAddrs(ctx, pi.ID)\n\t\tif err != nil {\n\t\t\tlog.Debug(\"failed to find more peer addresses\", \"peer\", pi.ID, \"err\", err)\n\t\t\treturn cerr\n\t\t}\n\n\t\t// Build lookup map\n\t\tlookup := make(map[string]struct{}, len(addrs))\n\t\tfor _, addr := range addrs {\n\t\t\tlookup[string(addr.Bytes())] = struct{}{}\n\t\t}\n\n\t\t// if there's any address that's not in the previous set\n\t\t// of addresses, try to connect again. If all addresses\n\t\t// where known previously we return the original error.\n\t\tfor _, newAddr := range newAddrs {\n\t\t\tif _, found := lookup[string(newAddr.Bytes())]; found {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tpi.Addrs = newAddrs\n\t\t\treturn rh.host.Connect(ctx, pi)\n\t\t}\n\t\t// No appropriate new address found.\n\t\t// Return the original dial error.\n\t\treturn cerr\n\t}\n\treturn nil\n}\n\nfunc (rh *RoutedHost) findPeerAddrs(ctx context.Context, id peer.ID) ([]ma.Multiaddr, error) {\n\tpi, err := rh.route.FindPeer(ctx, id)\n\tif err != nil {\n\t\treturn nil, err // couldnt find any :(\n\t}\n\n\tif pi.ID != id {\n\t\terr = fmt.Errorf(\"routing failure: provided addrs for different peer\")\n\t\tlog.Error(\"got wrong peer\",\n\t\t\t\"error\", err,\n\t\t\t\"wantedPeer\", id,\n\t\t\t\"gotPeer\", pi.ID,\n\t\t)\n\t\treturn nil, err\n\t}\n\n\treturn pi.Addrs, nil\n}\n\nfunc (rh *RoutedHost) ID() peer.ID {\n\treturn rh.host.ID()\n}\n\nfunc (rh *RoutedHost) Peerstore() peerstore.Peerstore {\n\treturn rh.host.Peerstore()\n}\n\nfunc (rh *RoutedHost) Addrs() []ma.Multiaddr {\n\treturn rh.host.Addrs()\n}\n\nfunc (rh *RoutedHost) Network() network.Network {\n\treturn rh.host.Network()\n}\n\nfunc (rh *RoutedHost) Mux() protocol.Switch {\n\treturn rh.host.Mux()\n}\n\nfunc (rh *RoutedHost) EventBus() event.Bus {\n\treturn rh.host.EventBus()\n}\n\nfunc (rh *RoutedHost) SetStreamHandler(pid protocol.ID, handler network.StreamHandler) {\n\trh.host.SetStreamHandler(pid, handler)\n}\n\nfunc (rh *RoutedHost) SetStreamHandlerMatch(pid protocol.ID, m func(protocol.ID) bool, handler network.StreamHandler) {\n\trh.host.SetStreamHandlerMatch(pid, m, handler)\n}\n\nfunc (rh *RoutedHost) RemoveStreamHandler(pid protocol.ID) {\n\trh.host.RemoveStreamHandler(pid)\n}\n\nfunc (rh *RoutedHost) NewStream(ctx context.Context, p peer.ID, pids ...protocol.ID) (network.Stream, error) {\n\t// Ensure we have a connection, with peer addresses resolved by the routing system (#207)\n\t// It is not sufficient to let the underlying host connect, it will most likely not have\n\t// any addresses for the peer without any prior connections.\n\t// If the caller wants to prevent the host from dialing, it should use the NoDial option.\n\tif nodial, _ := network.GetNoDial(ctx); !nodial {\n\t\terr := rh.Connect(ctx, peer.AddrInfo{ID: p})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn rh.host.NewStream(ctx, p, pids...)\n}\nfunc (rh *RoutedHost) Close() error {\n\t// no need to close IpfsRouting. we dont own it.\n\treturn rh.host.Close()\n}\nfunc (rh *RoutedHost) ConnManager() connmgr.ConnManager {\n\treturn rh.host.ConnManager()\n}\n\nvar _ (host.Host) = (*RoutedHost)(nil)\n"
  },
  {
    "path": "p2p/host/routed/routed_test.go",
    "content": "package routedhost\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\tbasic \"github.com/libp2p/go-libp2p/p2p/host/basic\"\n\tswarmt \"github.com/libp2p/go-libp2p/p2p/net/swarm/testing\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nvar _ Routing = (*mockRouting)(nil)\n\ntype mockRouting struct {\n\tcallCount  int\n\tfindPeerFn func(ctx context.Context, id peer.ID) (peer.AddrInfo, error)\n}\n\nfunc (m *mockRouting) FindPeer(ctx context.Context, pid peer.ID) (peer.AddrInfo, error) {\n\tm.callCount++\n\treturn m.findPeerFn(ctx, pid)\n}\n\nfunc TestRoutedHostConnectToObsoleteAddresses(t *testing.T) {\n\th1, err := basic.NewHost(swarmt.GenSwarm(t), nil)\n\trequire.NoError(t, err)\n\tdefer h1.Close()\n\th1.Start()\n\n\th2, err := basic.NewHost(swarmt.GenSwarm(t), nil)\n\trequire.NoError(t, err)\n\tdefer h2.Close()\n\th2.Start()\n\n\t// assemble the AddrInfo struct to use for the connection attempt\n\tpi := peer.AddrInfo{\n\t\tID: h2.ID(),\n\t\t// Use a wrong multi address for host 2, so that the initial connection attempt will fail\n\t\t// (we have obsolete, old multi address information)\n\t\tAddrs: []ma.Multiaddr{ma.StringCast(\"/ip4/127.0.0.1/tcp/1234\")},\n\t}\n\n\t// Build mock routing module and replace the FindPeer function.\n\t// Now, that function will return the correct multi addresses for host 2\n\t// (we have fetched the most up-to-date data from the DHT)\n\tmr := &mockRouting{\n\t\tfindPeerFn: func(context.Context, peer.ID) (peer.AddrInfo, error) {\n\t\t\treturn peer.AddrInfo{\n\t\t\t\tID:    h2.ID(),\n\t\t\t\tAddrs: h2.Addrs(),\n\t\t\t}, nil\n\t\t},\n\t}\n\n\t// Build routed host\n\trh := Wrap(h1, mr)\n\t// Connection establishment should have worked without an error\n\trequire.NoError(t, rh.Connect(context.Background(), pi))\n\trequire.Equal(t, 1, mr.callCount, \"the mocked FindPeer function should have been called\")\n}\n\nfunc TestRoutedHostConnectFindPeerNoUsefulAddrs(t *testing.T) {\n\th1, err := basic.NewHost(swarmt.GenSwarm(t), nil)\n\trequire.NoError(t, err)\n\tdefer h1.Close()\n\n\th2, err := basic.NewHost(swarmt.GenSwarm(t), nil)\n\trequire.NoError(t, err)\n\tdefer h2.Close()\n\n\t// assemble the AddrInfo struct to use for the connection attempt\n\tpi := peer.AddrInfo{\n\t\tID: h2.ID(),\n\t\t// Use a wrong multi address for host 2, so that the initial connection attempt will fail\n\t\t// (we have obsolete, old multi address information)\n\t\tAddrs: []ma.Multiaddr{ma.StringCast(\"/ip4/127.0.0.1/tcp/1234\")},\n\t}\n\n\t// Build mock routing module and replace the FindPeer function.\n\t// Now, that function will return the correct multi addresses for host 2\n\t// (we have fetched the most up-to-date data from the DHT)\n\tmr := &mockRouting{findPeerFn: func(context.Context, peer.ID) (peer.AddrInfo, error) { return pi, nil }}\n\n\t// Build routed host\n\trh := Wrap(h1, mr)\n\t// Connection establishment should fail, since we didn't provide any useful addresses in FindPeer.\n\trequire.Error(t, rh.Connect(context.Background(), pi))\n\trequire.Equal(t, 1, mr.callCount, \"the mocked FindPeer function should have been called\")\n}\n"
  },
  {
    "path": "p2p/http/auth/auth.go",
    "content": "package httppeeridauth\n\nimport (\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n\t\"github.com/libp2p/go-libp2p/p2p/http/auth/internal/handshake\"\n)\n\nconst PeerIDAuthScheme = handshake.PeerIDAuthScheme\nconst ProtocolID = \"/http-peer-id-auth/1.0.0\"\n\nvar log = logging.Logger(\"http-peer-id-auth\")\n"
  },
  {
    "path": "p2p/http/auth/auth_test.go",
    "content": "package httppeeridauth\n\nimport (\n\t\"bytes\"\n\t\"crypto/rand\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"io\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\n// TestMutualAuth tests that we can do a mutually authenticated round trip\nfunc TestMutualAuth(t *testing.T) {\n\toriginalLogger := log\n\tdefer func() {\n\t\tlog = originalLogger\n\t}()\n\t// Override to print debug logs\n\tlog = slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{\n\t\tLevel: slog.LevelDebug,\n\t}))\n\n\tzeroBytes := make([]byte, 64)\n\tserverKey, _, err := crypto.GenerateEd25519Key(bytes.NewReader(zeroBytes))\n\trequire.NoError(t, err)\n\n\ttype clientTestCase struct {\n\t\tname         string\n\t\tclientKeyGen func(t *testing.T) crypto.PrivKey\n\t}\n\n\tclientTestCases := []clientTestCase{\n\t\t{\n\t\t\tname: \"ED25519\",\n\t\t\tclientKeyGen: func(t *testing.T) crypto.PrivKey {\n\t\t\t\tt.Helper()\n\t\t\t\tclientKey, _, err := crypto.GenerateEd25519Key(rand.Reader)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\treturn clientKey\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"RSA\",\n\t\t\tclientKeyGen: func(t *testing.T) crypto.PrivKey {\n\t\t\t\tt.Helper()\n\t\t\t\tclientKey, _, err := crypto.GenerateRSAKeyPair(2048, rand.Reader)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\treturn clientKey\n\t\t\t},\n\t\t},\n\t}\n\n\ttype serverTestCase struct {\n\t\tname      string\n\t\tserverGen func(t *testing.T) (*httptest.Server, *ServerPeerIDAuth)\n\t}\n\n\tserverTestCases := []serverTestCase{\n\t\t{\n\t\t\tname: \"no TLS\",\n\t\t\tserverGen: func(t *testing.T) (*httptest.Server, *ServerPeerIDAuth) {\n\t\t\t\tt.Helper()\n\t\t\t\tauth := ServerPeerIDAuth{\n\t\t\t\t\tPrivKey: serverKey,\n\t\t\t\t\tValidHostnameFn: func(s string) bool {\n\t\t\t\t\t\treturn s == \"example.com\"\n\t\t\t\t\t},\n\t\t\t\t\tTokenTTL: time.Hour,\n\t\t\t\t\tNoTLS:    true,\n\t\t\t\t}\n\n\t\t\t\tts := httptest.NewServer(&auth)\n\t\t\t\tt.Cleanup(ts.Close)\n\t\t\t\treturn ts, &auth\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"TLS\",\n\t\t\tserverGen: func(t *testing.T) (*httptest.Server, *ServerPeerIDAuth) {\n\t\t\t\tt.Helper()\n\t\t\t\tauth := ServerPeerIDAuth{\n\t\t\t\t\tPrivKey: serverKey,\n\t\t\t\t\tValidHostnameFn: func(s string) bool {\n\t\t\t\t\t\treturn s == \"example.com\"\n\t\t\t\t\t},\n\t\t\t\t\tTokenTTL: time.Hour,\n\t\t\t\t}\n\n\t\t\t\tts := httptest.NewTLSServer(&auth)\n\t\t\t\tt.Cleanup(ts.Close)\n\t\t\t\treturn ts, &auth\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, ctc := range clientTestCases {\n\t\tfor _, stc := range serverTestCases {\n\t\t\tt.Run(ctc.name+\"+\"+stc.name, func(t *testing.T) {\n\t\t\t\tts, server := stc.serverGen(t)\n\t\t\t\tclient := ts.Client()\n\t\t\t\troundTripper := instrumentedRoundTripper{client.Transport, 0}\n\t\t\t\tclient.Transport = &roundTripper\n\t\t\t\trequestsSent := func() int {\n\t\t\t\t\tdefer func() { roundTripper.timesRoundtripped = 0 }()\n\t\t\t\t\treturn roundTripper.timesRoundtripped\n\t\t\t\t}\n\n\t\t\t\ttlsClientConfig := roundTripper.TLSClientConfig()\n\t\t\t\tif tlsClientConfig != nil {\n\t\t\t\t\t// If we're using TLS, we need to set the SNI so that the\n\t\t\t\t\t// server can verify the request Host matches it.\n\t\t\t\t\ttlsClientConfig.ServerName = \"example.com\"\n\t\t\t\t}\n\t\t\t\tclientKey := ctc.clientKeyGen(t)\n\t\t\t\tclientAuth := ClientPeerIDAuth{PrivKey: clientKey}\n\n\t\t\t\texpectedServerID, err := peer.IDFromPrivateKey(serverKey)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\treq, err := http.NewRequest(\"POST\", ts.URL, nil)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\treq.Host = \"example.com\"\n\t\t\t\tserverID, resp, err := clientAuth.AuthenticatedDo(client, req)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, expectedServerID, serverID)\n\t\t\t\trequire.NotZero(t, clientAuth.tm.tokenMap[\"example.com\"])\n\t\t\t\trequire.Equal(t, http.StatusOK, resp.StatusCode)\n\t\t\t\trequire.Equal(t, 2, requestsSent())\n\n\t\t\t\t// Once more with the auth token\n\t\t\t\treq, err = http.NewRequest(\"POST\", ts.URL, nil)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\treq.Host = \"example.com\"\n\t\t\t\tserverID, resp, err = clientAuth.AuthenticatedDo(client, req)\n\t\t\t\trequire.NotEmpty(t, req.Header.Get(\"Authorization\"))\n\t\t\t\trequire.True(t, HasAuthHeader(req))\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, expectedServerID, serverID)\n\t\t\t\trequire.NotZero(t, clientAuth.tm.tokenMap[\"example.com\"])\n\t\t\t\trequire.Equal(t, http.StatusOK, resp.StatusCode)\n\t\t\t\trequire.Equal(t, 1, requestsSent(), \"should only call newRequest once since we have a token\")\n\n\t\t\t\tt.Run(\"Tokens Expired\", func(t *testing.T) {\n\t\t\t\t\t// Clear the auth token on the server side\n\t\t\t\t\tserver.TokenTTL = 1 // Small TTL\n\t\t\t\t\ttime.Sleep(100 * time.Millisecond)\n\t\t\t\t\tresetServerTokenTTL := sync.OnceFunc(func() {\n\t\t\t\t\t\tserver.TokenTTL = time.Hour\n\t\t\t\t\t})\n\n\t\t\t\t\treq, err := http.NewRequest(\"POST\", ts.URL, nil)\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\treq.Host = \"example.com\"\n\t\t\t\t\treq.GetBody = func() (io.ReadCloser, error) {\n\t\t\t\t\t\tresetServerTokenTTL()\n\t\t\t\t\t\treturn nil, nil\n\t\t\t\t\t}\n\t\t\t\t\tserverID, resp, err = clientAuth.AuthenticatedDo(client, req)\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\trequire.NotEmpty(t, req.Header.Get(\"Authorization\"))\n\t\t\t\t\trequire.Equal(t, http.StatusOK, resp.StatusCode)\n\t\t\t\t\trequire.Equal(t, expectedServerID, serverID)\n\t\t\t\t\trequire.NotZero(t, clientAuth.tm.tokenMap[\"example.com\"])\n\t\t\t\t\trequire.Equal(t, 3, requestsSent(), \"should call newRequest 3x since our token expired\")\n\t\t\t\t})\n\n\t\t\t\tt.Run(\"Tokens Invalidated\", func(t *testing.T) {\n\t\t\t\t\t// Clear the auth token on the server side\n\t\t\t\t\tkey := make([]byte, 32)\n\t\t\t\t\t_, err := rand.Read(key)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tpanic(err)\n\t\t\t\t\t}\n\t\t\t\t\tserver.hmacPool = newHmacPool(key)\n\n\t\t\t\t\treq, err := http.NewRequest(\"POST\", ts.URL, nil)\n\t\t\t\t\treq.GetBody = func() (io.ReadCloser, error) {\n\t\t\t\t\t\treturn nil, nil\n\t\t\t\t\t}\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\treq.Host = \"example.com\"\n\t\t\t\t\tserverID, resp, err = clientAuth.AuthenticatedDo(client, req)\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\trequire.NotEmpty(t, req.Header.Get(\"Authorization\"))\n\t\t\t\t\trequire.Equal(t, http.StatusOK, resp.StatusCode)\n\t\t\t\t\trequire.Equal(t, expectedServerID, serverID)\n\t\t\t\t\trequire.NotZero(t, clientAuth.tm.tokenMap[\"example.com\"])\n\t\t\t\t\trequire.Equal(t, 3, requestsSent(), \"should call have sent 3 reqs since our token expired\")\n\t\t\t\t})\n\n\t\t\t})\n\t\t}\n\t}\n}\n\nfunc TestBodyNotSentDuringRedirect(t *testing.T) {\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tb, err := io.ReadAll(r.Body)\n\t\tassert.NoError(t, err)\n\t\tassert.Empty(t, string(b))\n\t\tif r.URL.Path != \"/redirected\" {\n\t\t\tw.Header().Set(\"Location\", \"/redirected\")\n\t\t\tw.WriteHeader(http.StatusTemporaryRedirect)\n\t\t\treturn\n\t\t}\n\t}))\n\tt.Cleanup(ts.Close)\n\tclient := ts.Client()\n\tclientKey, _, _ := crypto.GenerateEd25519Key(rand.Reader)\n\tclientAuth := ClientPeerIDAuth{PrivKey: clientKey}\n\n\treq, err :=\n\t\thttp.NewRequest(\n\t\t\t\"POST\",\n\t\t\tts.URL,\n\t\t\tstrings.NewReader(\"Only for authenticated servers\"),\n\t\t)\n\treq.Host = \"example.com\"\n\trequire.NoError(t, err)\n\t_, _, err = clientAuth.AuthenticatedDo(client, req)\n\trequire.ErrorContains(t, err, \"signature not set\") // server doesn't actually handshake\n}\n\ntype instrumentedRoundTripper struct {\n\thttp.RoundTripper\n\ttimesRoundtripped int\n}\n\nfunc (irt *instrumentedRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {\n\tirt.timesRoundtripped++\n\treturn irt.RoundTripper.RoundTrip(req)\n}\n\nfunc (irt *instrumentedRoundTripper) TLSClientConfig() *tls.Config {\n\treturn irt.RoundTripper.(*http.Transport).TLSClientConfig\n}\n\nfunc TestConcurrentAuth(t *testing.T) {\n\tserverKey, _, err := crypto.GenerateEd25519Key(rand.Reader)\n\trequire.NoError(t, err)\n\n\tauth := ServerPeerIDAuth{\n\t\tPrivKey: serverKey,\n\t\tValidHostnameFn: func(s string) bool {\n\t\t\treturn s == \"example.com\"\n\t\t},\n\t\tTokenTTL: time.Hour,\n\t\tNoTLS:    true,\n\t\tNext: func(_ peer.ID, w http.ResponseWriter, r *http.Request) {\n\t\t\treqBody, err := io.ReadAll(r.Body)\n\t\t\trequire.NoError(t, err)\n\t\t\t_, err = w.Write(reqBody)\n\t\t\trequire.NoError(t, err)\n\t\t},\n\t}\n\n\tts := httptest.NewServer(&auth)\n\tt.Cleanup(ts.Close)\n\n\twg := sync.WaitGroup{}\n\tfor i := range 10 {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tclientKey, _, err := crypto.GenerateEd25519Key(rand.Reader)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tclientAuth := ClientPeerIDAuth{PrivKey: clientKey}\n\t\t\treqBody := fmt.Appendf(nil, \"echo %d\", i)\n\t\t\treq, err := http.NewRequest(\"POST\", ts.URL, bytes.NewReader(reqBody))\n\t\t\trequire.NoError(t, err)\n\t\t\treq.Host = \"example.com\"\n\n\t\t\tclient := ts.Client()\n\t\t\t_, resp, err := clientAuth.AuthenticatedDo(client, req)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, http.StatusOK, resp.StatusCode)\n\t\t\trespBody, err := io.ReadAll(resp.Body)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, reqBody, respBody)\n\t\t}()\n\t}\n\twg.Wait()\n}\n"
  },
  {
    "path": "p2p/http/auth/client.go",
    "content": "package httppeeridauth\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/p2p/http/auth/internal/handshake\"\n)\n\ntype ClientPeerIDAuth struct {\n\tPrivKey  crypto.PrivKey\n\tTokenTTL time.Duration\n\n\ttm tokenMap\n}\n\ntype clientAsRoundTripper struct {\n\t*http.Client\n}\n\nfunc (c clientAsRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {\n\treturn c.Client.Do(req)\n}\n\n// AuthenticatedDo is like http.Client.Do, but it does the libp2p peer ID auth\n// handshake if needed.\n//\n// It is recommended to pass in an http.Request with `GetBody` set, so that this\n// method can retry sending the request in case a previously used token has\n// expired.\nfunc (a *ClientPeerIDAuth) AuthenticatedDo(client *http.Client, req *http.Request) (peer.ID, *http.Response, error) {\n\treturn a.AuthenticateWithRoundTripper(clientAsRoundTripper{client}, req)\n}\n\nfunc (a *ClientPeerIDAuth) AuthenticateWithRoundTripper(rt http.RoundTripper, req *http.Request) (peer.ID, *http.Response, error) {\n\thostname := req.Host\n\tti, hasToken := a.tm.get(hostname, a.TokenTTL)\n\thandshake := handshake.PeerIDAuthHandshakeClient{\n\t\tHostname: hostname,\n\t\tPrivKey:  a.PrivKey,\n\t}\n\n\tif hasToken {\n\t\t// We have a token. Attempt to use that, but fallback to server initiated challenge if it fails.\n\t\tpeer, resp, err := a.doWithToken(rt, req, ti)\n\t\tswitch {\n\t\tcase err == nil:\n\t\t\treturn peer, resp, nil\n\t\tcase errors.Is(err, errTokenRejected):\n\t\t\t// Token was rejected, we need to re-authenticate\n\t\t\tbreak\n\t\tdefault:\n\t\t\treturn \"\", nil, err\n\t\t}\n\n\t\t// Token didn't work, we need to re-authenticate.\n\t\t// Run the server-initiated handshake\n\t\treq = req.Clone(req.Context())\n\t\treq.Body, err = req.GetBody()\n\t\tif err != nil {\n\t\t\treturn \"\", nil, err\n\t\t}\n\n\t\thandshake.ParseHeader(resp.Header)\n\t} else {\n\t\t// We didn't have a handshake token, so we initiate the handshake.\n\t\t// If our token was rejected, the server initiates the handshake.\n\t\thandshake.SetInitiateChallenge()\n\t}\n\n\tserverPeerID, resp, err := a.runHandshake(rt, req, clearBody(req), &handshake)\n\tif err != nil {\n\t\treturn \"\", nil, fmt.Errorf(\"failed to run handshake: %w\", err)\n\t}\n\ta.tm.set(hostname, tokenInfo{\n\t\ttoken:      handshake.BearerToken(),\n\t\tinsertedAt: time.Now(),\n\t\tpeerID:     serverPeerID,\n\t})\n\treturn serverPeerID, resp, nil\n}\n\nfunc (a *ClientPeerIDAuth) HasToken(hostname string) bool {\n\t_, hasToken := a.tm.get(hostname, a.TokenTTL)\n\treturn hasToken\n}\n\nfunc (a *ClientPeerIDAuth) runHandshake(rt http.RoundTripper, req *http.Request, b bodyMeta, hs *handshake.PeerIDAuthHandshakeClient) (peer.ID, *http.Response, error) {\n\tmaxSteps := 5 // Avoid infinite loops in case of buggy handshake. Shouldn't happen.\n\tvar resp *http.Response\n\n\terr := hs.Run()\n\tif err != nil {\n\t\treturn \"\", nil, err\n\t}\n\n\tsentBody := false\n\tfor !hs.HandshakeDone() || !sentBody {\n\t\treq = req.Clone(req.Context())\n\t\ths.AddHeader(req.Header)\n\t\tif hs.ServerAuthenticated() {\n\t\t\tsentBody = true\n\t\t\tb.setBody(req)\n\t\t}\n\n\t\tresp, err = rt.RoundTrip(req)\n\t\tif err != nil {\n\t\t\treturn \"\", nil, err\n\t\t}\n\n\t\ths.ParseHeader(resp.Header)\n\t\terr = hs.Run()\n\t\tif err != nil {\n\t\t\tresp.Body.Close()\n\t\t\treturn \"\", nil, err\n\t\t}\n\n\t\tif maxSteps--; maxSteps == 0 {\n\t\t\treturn \"\", nil, errors.New(\"handshake took too many steps\")\n\t\t}\n\t}\n\n\tp, err := hs.PeerID()\n\tif err != nil {\n\t\tresp.Body.Close()\n\t\treturn \"\", nil, err\n\t}\n\treturn p, resp, nil\n}\n\nvar errTokenRejected = errors.New(\"token rejected\")\n\nfunc (a *ClientPeerIDAuth) doWithToken(rt http.RoundTripper, req *http.Request, ti tokenInfo) (peer.ID, *http.Response, error) {\n\t// Try to make the request with the token\n\treq.Header.Set(\"Authorization\", ti.token)\n\tresp, err := rt.RoundTrip(req)\n\tif err != nil {\n\t\treturn \"\", nil, err\n\t}\n\tif resp.StatusCode != http.StatusUnauthorized {\n\t\t// our token is still valid\n\t\treturn ti.peerID, resp, nil\n\t}\n\tif req.GetBody == nil {\n\t\t// We can't retry this request even if we wanted to.\n\t\t// Return the response and an error\n\t\treturn \"\", resp, errors.New(\"expired token. Couldn't run handshake because req.GetBody is nil\")\n\t}\n\tresp.Body.Close()\n\n\treturn \"\", resp, errTokenRejected\n}\n\ntype bodyMeta struct {\n\tbody          io.ReadCloser\n\tcontentLength int64\n\tgetBody       func() (io.ReadCloser, error)\n}\n\nfunc clearBody(req *http.Request) bodyMeta {\n\tdefer func() {\n\t\treq.Body = nil\n\t\treq.ContentLength = 0\n\t\treq.GetBody = nil\n\t}()\n\treturn bodyMeta{body: req.Body, contentLength: req.ContentLength, getBody: req.GetBody}\n}\n\nfunc (b *bodyMeta) setBody(req *http.Request) {\n\treq.Body = b.body\n\treq.ContentLength = b.contentLength\n\treq.GetBody = b.getBody\n}\n\ntype tokenInfo struct {\n\ttoken      string\n\tinsertedAt time.Time\n\tpeerID     peer.ID\n}\n\ntype tokenMap struct {\n\ttokenMapMu sync.Mutex\n\ttokenMap   map[string]tokenInfo\n}\n\nfunc (tm *tokenMap) get(hostname string, ttl time.Duration) (tokenInfo, bool) {\n\ttm.tokenMapMu.Lock()\n\tdefer tm.tokenMapMu.Unlock()\n\n\tti, ok := tm.tokenMap[hostname]\n\tif ok && ttl != 0 && time.Since(ti.insertedAt) > ttl {\n\t\tdelete(tm.tokenMap, hostname)\n\t\treturn tokenInfo{}, false\n\t}\n\treturn ti, ok\n}\n\nfunc (tm *tokenMap) set(hostname string, ti tokenInfo) {\n\ttm.tokenMapMu.Lock()\n\tdefer tm.tokenMapMu.Unlock()\n\tif tm.tokenMap == nil {\n\t\ttm.tokenMap = make(map[string]tokenInfo)\n\t}\n\ttm.tokenMap[hostname] = ti\n}\n"
  },
  {
    "path": "p2p/http/auth/internal/handshake/alloc_test.go",
    "content": "//go:build nocover\n\npackage handshake\n\nimport \"testing\"\n\nfunc TestParsePeerIDAuthSchemeParamsNoAllocNoCover(t *testing.T) {\n\tstr := []byte(`libp2p-PeerID peer-id=\"<server-peer-id-string>\", sig=\"<base64-signature-bytes>\", public-key=\"<base64-encoded-public-key-bytes>\", bearer=\"<base64-encoded-opaque-blob>\"`)\n\n\tallocs := testing.AllocsPerRun(1000, func() {\n\t\tp := params{}\n\t\terr := p.parsePeerIDAuthSchemeParams(str)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t})\n\tif allocs > 0 {\n\t\tt.Fatalf(\"alloc test failed expected 0 received %0.2f\", allocs)\n\t}\n}\n"
  },
  {
    "path": "p2p/http/auth/internal/handshake/client.go",
    "content": "package handshake\n\nimport (\n\t\"encoding/base64\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n)\n\ntype peerIDAuthClientState int\n\nconst (\n\tpeerIDAuthClientStateSignChallenge peerIDAuthClientState = iota\n\tpeerIDAuthClientStateVerifyChallenge\n\tpeerIDAuthClientStateDone // We have the bearer token, and there's nothing left to do\n\n\t// Client initiated handshake\n\tpeerIDAuthClientInitiateChallenge\n\tpeerIDAuthClientStateVerifyAndSignChallenge\n\tpeerIDAuthClientStateWaitingForBearer\n)\n\ntype PeerIDAuthHandshakeClient struct {\n\tHostname string\n\tPrivKey  crypto.PrivKey\n\n\tserverPeerID    peer.ID\n\tserverPubKey    crypto.PubKey\n\tstate           peerIDAuthClientState\n\tp               params\n\thb              headerBuilder\n\tchallengeServer []byte\n\tbuf             [128]byte\n}\n\nvar errMissingChallenge = errors.New(\"missing challenge\")\n\nfunc (h *PeerIDAuthHandshakeClient) SetInitiateChallenge() {\n\th.state = peerIDAuthClientInitiateChallenge\n}\n\nfunc (h *PeerIDAuthHandshakeClient) ParseHeader(header http.Header) error {\n\tif h.state == peerIDAuthClientStateDone || h.state == peerIDAuthClientInitiateChallenge {\n\t\treturn nil\n\t}\n\th.p = params{}\n\n\tvar headerVal []byte\n\tswitch h.state {\n\tcase peerIDAuthClientStateSignChallenge, peerIDAuthClientStateVerifyAndSignChallenge:\n\t\theaderVal = []byte(header.Get(\"WWW-Authenticate\"))\n\tcase peerIDAuthClientStateVerifyChallenge, peerIDAuthClientStateWaitingForBearer:\n\t\theaderVal = []byte(header.Get(\"Authentication-Info\"))\n\t}\n\n\tif len(headerVal) == 0 {\n\t\treturn errMissingChallenge\n\t}\n\n\terr := h.p.parsePeerIDAuthSchemeParams(headerVal)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif h.serverPubKey == nil && len(h.p.publicKeyB64) > 0 {\n\t\tserverPubKeyBytes, err := base64.URLEncoding.AppendDecode(nil, h.p.publicKeyB64)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\th.serverPubKey, err = crypto.UnmarshalPublicKey(serverPubKeyBytes)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\th.serverPeerID, err = peer.IDFromPublicKey(h.serverPubKey)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn err\n}\n\nfunc (h *PeerIDAuthHandshakeClient) Run() error {\n\tif h.state == peerIDAuthClientStateDone {\n\t\treturn nil\n\t}\n\n\th.hb.clear()\n\tclientPubKeyBytes, err := crypto.MarshalPublicKey(h.PrivKey.GetPublic())\n\tif err != nil {\n\t\treturn err\n\t}\n\tswitch h.state {\n\tcase peerIDAuthClientInitiateChallenge:\n\t\th.hb.writeScheme(PeerIDAuthScheme)\n\t\th.addChallengeServerParam()\n\t\th.hb.writeParamB64(nil, \"public-key\", clientPubKeyBytes)\n\t\th.state = peerIDAuthClientStateVerifyAndSignChallenge\n\t\treturn nil\n\tcase peerIDAuthClientStateVerifyAndSignChallenge:\n\t\tif len(h.p.sigB64) == 0 && len(h.p.challengeClient) != 0 {\n\t\t\t// The server refused a client initiated handshake, so we need run the server initiated handshake\n\t\t\th.state = peerIDAuthClientStateSignChallenge\n\t\t\treturn h.Run()\n\t\t}\n\t\tif err := h.verifySig(clientPubKeyBytes); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\th.hb.writeScheme(PeerIDAuthScheme)\n\t\th.hb.writeParam(\"opaque\", h.p.opaqueB64)\n\t\th.addSigParam()\n\t\th.state = peerIDAuthClientStateWaitingForBearer\n\t\treturn nil\n\n\tcase peerIDAuthClientStateWaitingForBearer:\n\t\th.hb.writeScheme(PeerIDAuthScheme)\n\t\th.hb.writeParam(\"bearer\", h.p.bearerTokenB64)\n\t\th.state = peerIDAuthClientStateDone\n\t\treturn nil\n\n\tcase peerIDAuthClientStateSignChallenge:\n\t\tif len(h.p.challengeClient) < challengeLen {\n\t\t\treturn errors.New(\"challenge too short\")\n\t\t}\n\n\t\th.hb.writeScheme(PeerIDAuthScheme)\n\t\th.hb.writeParamB64(nil, \"public-key\", clientPubKeyBytes)\n\t\tif err := h.addChallengeServerParam(); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := h.addSigParam(); err != nil {\n\t\t\treturn err\n\t\t}\n\t\th.hb.writeParam(\"opaque\", h.p.opaqueB64)\n\n\t\th.state = peerIDAuthClientStateVerifyChallenge\n\t\treturn nil\n\tcase peerIDAuthClientStateVerifyChallenge:\n\t\tif err := h.verifySig(clientPubKeyBytes); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\th.hb.writeScheme(PeerIDAuthScheme)\n\t\th.hb.writeParam(\"bearer\", h.p.bearerTokenB64)\n\t\th.state = peerIDAuthClientStateDone\n\n\t\treturn nil\n\t}\n\n\treturn errors.New(\"unhandled state\")\n}\n\nfunc (h *PeerIDAuthHandshakeClient) addChallengeServerParam() error {\n\t_, err := io.ReadFull(randReader, h.buf[:challengeLen])\n\tif err != nil {\n\t\treturn err\n\t}\n\th.challengeServer = base64.URLEncoding.AppendEncode(nil, h.buf[:challengeLen])\n\tclear(h.buf[:challengeLen])\n\th.hb.writeParam(\"challenge-server\", h.challengeServer)\n\treturn nil\n}\n\nfunc (h *PeerIDAuthHandshakeClient) verifySig(clientPubKeyBytes []byte) error {\n\tif len(h.p.sigB64) == 0 {\n\t\treturn errors.New(\"signature not set\")\n\t}\n\tsig, err := base64.URLEncoding.AppendDecode(nil, h.p.sigB64)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to decode signature: %w\", err)\n\t}\n\terr = verifySig(h.serverPubKey, PeerIDAuthScheme, []sigParam{\n\t\t{\"challenge-server\", h.challengeServer},\n\t\t{\"client-public-key\", clientPubKeyBytes},\n\t\t{\"hostname\", []byte(h.Hostname)},\n\t}, sig)\n\treturn err\n}\n\nfunc (h *PeerIDAuthHandshakeClient) addSigParam() error {\n\tif h.serverPubKey == nil {\n\t\treturn errors.New(\"server public key not set\")\n\t}\n\tserverPubKeyBytes, err := crypto.MarshalPublicKey(h.serverPubKey)\n\tif err != nil {\n\t\treturn err\n\t}\n\tclientSig, err := sign(h.PrivKey, PeerIDAuthScheme, []sigParam{\n\t\t{\"challenge-client\", h.p.challengeClient},\n\t\t{\"server-public-key\", serverPubKeyBytes},\n\t\t{\"hostname\", []byte(h.Hostname)},\n\t})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to sign challenge: %w\", err)\n\t}\n\th.hb.writeParamB64(nil, \"sig\", clientSig)\n\treturn nil\n\n}\n\n// PeerID returns the peer ID of the authenticated client.\nfunc (h *PeerIDAuthHandshakeClient) PeerID() (peer.ID, error) {\n\tswitch h.state {\n\tcase peerIDAuthClientStateDone:\n\tcase peerIDAuthClientStateWaitingForBearer:\n\tdefault:\n\t\treturn \"\", errors.New(\"server not authenticated yet\")\n\t}\n\n\tif h.serverPeerID == \"\" {\n\t\treturn \"\", errors.New(\"peer ID not set\")\n\t}\n\treturn h.serverPeerID, nil\n}\n\nfunc (h *PeerIDAuthHandshakeClient) AddHeader(hdr http.Header) {\n\thdr.Set(\"Authorization\", h.hb.b.String())\n}\n\n// BearerToken returns the server given bearer token for the client. Set this on\n// the Authorization header in the client's request.\nfunc (h *PeerIDAuthHandshakeClient) BearerToken() string {\n\tif h.state != peerIDAuthClientStateDone {\n\t\treturn \"\"\n\t}\n\treturn h.hb.b.String()\n}\n\nfunc (h *PeerIDAuthHandshakeClient) ServerAuthenticated() bool {\n\tswitch h.state {\n\tcase peerIDAuthClientStateDone:\n\tcase peerIDAuthClientStateWaitingForBearer:\n\tdefault:\n\t\treturn false\n\t}\n\n\treturn h.serverPeerID != \"\"\n}\n\nfunc (h *PeerIDAuthHandshakeClient) HandshakeDone() bool {\n\treturn h.state == peerIDAuthClientStateDone\n}\n"
  },
  {
    "path": "p2p/http/auth/internal/handshake/handshake.go",
    "content": "package handshake\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"crypto/rand\"\n\t\"encoding/base64\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"slices\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\n\tpool \"github.com/libp2p/go-buffer-pool\"\n)\n\nconst PeerIDAuthScheme = \"libp2p-PeerID\"\nconst challengeLen = 32\nconst maxHeaderSize = 2048\n\nvar peerIDAuthSchemeBytes = []byte(PeerIDAuthScheme)\n\nvar errTooBig = errors.New(\"header value too big\")\nvar errInvalid = errors.New(\"invalid header value\")\nvar errNotRan = errors.New(\"not ran. call Run() first\")\n\nvar randReader = rand.Reader // A var so it can be changed in tests\nvar nowFn = time.Now         // A var so it can be changed in tests\n\n// params represent params passed in via headers. All []byte fields to avoid allocations.\ntype params struct {\n\tbearerTokenB64  []byte\n\tchallengeClient []byte\n\tchallengeServer []byte\n\topaqueB64       []byte\n\tpublicKeyB64    []byte\n\tsigB64          []byte\n}\n\n// parsePeerIDAuthSchemeParams parses the parameters of the PeerID auth scheme\n// from the header string. zero alloc.\nfunc (p *params) parsePeerIDAuthSchemeParams(headerVal []byte) error {\n\tif len(headerVal) > maxHeaderSize {\n\t\treturn errTooBig\n\t}\n\tstartIdx := bytes.Index(headerVal, peerIDAuthSchemeBytes)\n\tif startIdx == -1 {\n\t\treturn nil\n\t}\n\n\theaderVal = headerVal[startIdx+len(PeerIDAuthScheme):]\n\tadvance, token, err := splitAuthHeaderParams(headerVal, true)\n\tfor ; err == nil; advance, token, err = splitAuthHeaderParams(headerVal, true) {\n\t\theaderVal = headerVal[advance:]\n\t\tbs := token\n\t\tbefore, after, ok := bytes.Cut(bs, []byte(\"=\"))\n\t\tif !ok {\n\t\t\treturn errInvalid\n\t\t}\n\t\tkB := before\n\t\tv := after\n\t\tif len(v) < 2 || v[0] != '\"' || v[len(v)-1] != '\"' {\n\t\t\treturn errInvalid\n\t\t}\n\t\tv = v[1 : len(v)-1] // drop quotes\n\t\tswitch string(kB) {\n\t\tcase \"bearer\":\n\t\t\tp.bearerTokenB64 = v\n\t\tcase \"challenge-client\":\n\t\t\tp.challengeClient = v\n\t\tcase \"challenge-server\":\n\t\t\tp.challengeServer = v\n\t\tcase \"opaque\":\n\t\t\tp.opaqueB64 = v\n\t\tcase \"public-key\":\n\t\t\tp.publicKeyB64 = v\n\t\tcase \"sig\":\n\t\t\tp.sigB64 = v\n\t\t}\n\t}\n\tif err == bufio.ErrFinalToken {\n\t\terr = nil\n\t}\n\treturn err\n}\n\nfunc splitAuthHeaderParams(data []byte, atEOF bool) (advance int, token []byte, err error) {\n\tif len(data) == 0 && atEOF {\n\t\treturn 0, nil, bufio.ErrFinalToken\n\t}\n\n\tstart := 0\n\tfor start < len(data) && (data[start] == ' ' || data[start] == ',') {\n\t\t// Ignore leading spaces and commas\n\t\tstart++\n\t}\n\tif start == len(data) {\n\t\treturn len(data), nil, nil\n\t}\n\tend := start + 1\n\tfor end < len(data) && data[end] != ' ' && data[end] != ',' {\n\t\t// Consume until we hit a space or comma\n\t\tend++\n\t}\n\ttoken = data[start:end]\n\tif !bytes.ContainsAny(token, \"=\") {\n\t\t// This isn't a param. It's likely the next scheme. We're done\n\t\treturn len(data), nil, bufio.ErrFinalToken\n\t}\n\n\treturn end, token, nil\n}\n\ntype headerBuilder struct {\n\tb              strings.Builder\n\tpastFirstField bool\n}\n\nfunc (h *headerBuilder) clear() {\n\th.b.Reset()\n\th.pastFirstField = false\n}\n\nfunc (h *headerBuilder) writeScheme(scheme string) {\n\th.b.WriteString(scheme)\n\th.b.WriteByte(' ')\n}\n\nfunc (h *headerBuilder) maybeAddComma() {\n\tif !h.pastFirstField {\n\t\th.pastFirstField = true\n\t\treturn\n\t}\n\th.b.WriteString(\", \")\n}\n\n// writeParam writes a key value pair to the header. It first b64 encodes the\n// value. It uses buf as scratch space.\nfunc (h *headerBuilder) writeParamB64(buf []byte, key string, val []byte) {\n\tif buf == nil {\n\t\tbuf = make([]byte, base64.URLEncoding.EncodedLen(len(val)))\n\t}\n\tencodedVal := base64.URLEncoding.AppendEncode(buf[:0], val)\n\th.writeParam(key, encodedVal)\n}\n\n// writeParam writes a key value pair to the header. It writes the val as-is.\nfunc (h *headerBuilder) writeParam(key string, val []byte) {\n\tif len(val) == 0 {\n\t\treturn\n\t}\n\th.maybeAddComma()\n\n\th.b.Grow(len(key) + len(`=\"`) + len(val) + 1)\n\t// Not doing fmt.Fprintf here to avoid one allocation\n\th.b.WriteString(key)\n\th.b.WriteString(`=\"`)\n\th.b.Write(val)\n\th.b.WriteByte('\"')\n}\n\ntype sigParam struct {\n\tk string\n\tv []byte\n}\n\nfunc verifySig(publicKey crypto.PubKey, prefix string, signedParts []sigParam, sig []byte) error {\n\tif publicKey == nil {\n\t\treturn fmt.Errorf(\"no public key to verify signature\")\n\t}\n\n\tb := pool.Get(4096)\n\tdefer pool.Put(b)\n\tbuf, err := genDataToSign(b[:0], prefix, signedParts)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to generate signed data: %w\", err)\n\t}\n\tok, err := publicKey.Verify(buf, sig)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !ok {\n\t\treturn fmt.Errorf(\"signature verification failed\")\n\t}\n\n\treturn nil\n}\n\nfunc sign(privKey crypto.PrivKey, prefix string, partsToSign []sigParam) ([]byte, error) {\n\tif privKey == nil {\n\t\treturn nil, fmt.Errorf(\"no private key available to sign\")\n\t}\n\tb := pool.Get(4096)\n\tdefer pool.Put(b)\n\tbuf, err := genDataToSign(b[:0], prefix, partsToSign)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to generate data to sign: %w\", err)\n\t}\n\treturn privKey.Sign(buf)\n}\n\nfunc genDataToSign(buf []byte, prefix string, parts []sigParam) ([]byte, error) {\n\t// Sort the parts in lexicographic order\n\tslices.SortFunc(parts, func(a, b sigParam) int {\n\t\treturn strings.Compare(a.k, b.k)\n\t})\n\tbuf = append(buf, prefix...)\n\tfor _, p := range parts {\n\t\tbuf = binary.AppendUvarint(buf, uint64(len(p.k)+1+len(p.v))) // +1 for '='\n\t\tbuf = append(buf, p.k...)\n\t\tbuf = append(buf, '=')\n\t\tbuf = append(buf, p.v...)\n\t}\n\treturn buf, nil\n}\n"
  },
  {
    "path": "p2p/http/auth/internal/handshake/handshake_test.go",
    "content": "package handshake\n\nimport (\n\t\"bytes\"\n\t\"crypto/hmac\"\n\t\"crypto/rand\"\n\t\"crypto/sha256\"\n\t\"encoding/base64\"\n\t\"encoding/hex\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestHandshake(t *testing.T) {\n\tfor _, clientInitiated := range []bool{true, false} {\n\t\tt.Run(fmt.Sprintf(\"clientInitiated=%t\", clientInitiated), func(t *testing.T) {\n\t\t\thostname := \"example.com\"\n\t\t\tserverPriv, _, _ := crypto.GenerateEd25519Key(rand.Reader)\n\t\t\tclientPriv, _, _ := crypto.GenerateEd25519Key(rand.Reader)\n\n\t\t\tserverHandshake := PeerIDAuthHandshakeServer{\n\t\t\t\tHostname: hostname,\n\t\t\t\tPrivKey:  serverPriv,\n\t\t\t\tTokenTTL: time.Hour,\n\t\t\t\tHmac:     hmac.New(sha256.New, make([]byte, 32)),\n\t\t\t}\n\n\t\t\tclientHandshake := PeerIDAuthHandshakeClient{\n\t\t\t\tHostname: hostname,\n\t\t\t\tPrivKey:  clientPriv,\n\t\t\t}\n\t\t\tif clientInitiated {\n\t\t\t\tclientHandshake.state = peerIDAuthClientInitiateChallenge\n\t\t\t}\n\n\t\t\theaders := make(http.Header)\n\n\t\t\t// Start the handshake\n\t\t\tif !clientInitiated {\n\t\t\t\trequire.NoError(t, serverHandshake.ParseHeaderVal(nil))\n\t\t\t\trequire.NoError(t, serverHandshake.Run())\n\t\t\t\tserverHandshake.SetHeader(headers)\n\t\t\t}\n\n\t\t\t// Server Inititated: Client receives the challenge and signs it. Also sends the challenge server\n\t\t\t// Client Inititated: Client forms the challenge and sends it\n\t\t\trequire.NoError(t, clientHandshake.ParseHeader(headers))\n\t\t\tclear(headers)\n\t\t\trequire.NoError(t, clientHandshake.Run())\n\t\t\tclientHandshake.AddHeader(headers)\n\n\t\t\t// Server Inititated: Server receives the sig and verifies it. Also signs the challenge-server (client authenticated)\n\t\t\t// Client Inititated: Server receives the challenge and signs it. Also sends the challenge-client\n\t\t\tserverHandshake.Reset()\n\t\t\trequire.NoError(t, serverHandshake.ParseHeaderVal([]byte(headers.Get(\"Authorization\"))))\n\t\t\tclear(headers)\n\t\t\trequire.NoError(t, serverHandshake.Run())\n\t\t\tserverHandshake.SetHeader(headers)\n\n\t\t\t// Server Inititated: Client verifies sig and sets the bearer token for future requests  (server authenticated)\n\t\t\t// Client Inititated: Client verifies sig, and signs challenge. Sends it along with any application data (server authenticated)\n\t\t\trequire.NoError(t, clientHandshake.ParseHeader(headers))\n\t\t\tclear(headers)\n\t\t\trequire.NoError(t, clientHandshake.Run())\n\t\t\tclientHandshake.AddHeader(headers)\n\n\t\t\t// Server Inititated: Server verifies the bearer token\n\t\t\t// Client Inititated: Server verifies the sig, sets the bearer token (client authenticated)\n\t\t\t// and processes any application data\n\t\t\tserverHandshake.Reset()\n\t\t\trequire.NoError(t, serverHandshake.ParseHeaderVal([]byte(headers.Get(\"Authorization\"))))\n\t\t\tclear(headers)\n\t\t\trequire.NoError(t, serverHandshake.Run())\n\t\t\tserverHandshake.SetHeader(headers)\n\n\t\t\texpectedClientPeerID, _ := peer.IDFromPrivateKey(clientPriv)\n\t\t\texpectedServerPeerID, _ := peer.IDFromPrivateKey(serverPriv)\n\t\t\tclientPeerID, err := serverHandshake.PeerID()\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, expectedClientPeerID, clientPeerID)\n\n\t\t\tserverPeerID, err := clientHandshake.PeerID()\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, expectedServerPeerID, serverPeerID)\n\t\t})\n\t}\n}\n\nfunc TestServerRefusesClientInitiatedHandshake(t *testing.T) {\n\thostname := \"example.com\"\n\tserverPriv, _, _ := crypto.GenerateEd25519Key(rand.Reader)\n\tclientPriv, _, _ := crypto.GenerateEd25519Key(rand.Reader)\n\n\tserverHandshake := PeerIDAuthHandshakeServer{\n\t\tHostname: hostname,\n\t\tPrivKey:  serverPriv,\n\t\tTokenTTL: time.Hour,\n\t\tHmac:     hmac.New(sha256.New, make([]byte, 32)),\n\t}\n\n\tclientHandshake := PeerIDAuthHandshakeClient{\n\t\tHostname: hostname,\n\t\tPrivKey:  clientPriv,\n\t}\n\tclientHandshake.SetInitiateChallenge()\n\n\theaders := make(http.Header)\n\t// Client initiates the handshake\n\trequire.NoError(t, clientHandshake.Run())\n\tclientHandshake.AddHeader(headers)\n\n\t// Server receives the challenge-server, but chooses to reject it (simulating this by not passing the challenge)\n\tserverHandshake.Reset()\n\trequire.NoError(t, serverHandshake.ParseHeaderVal(nil))\n\tclear(headers)\n\trequire.NoError(t, serverHandshake.Run())\n\tserverHandshake.SetHeader(headers)\n\n\t// Client now runs the server-initiated handshake. Signs challenge-client; sends challenge-server\n\trequire.NoError(t, clientHandshake.ParseHeader(headers))\n\tclear(headers)\n\trequire.NoError(t, clientHandshake.Run())\n\tclientHandshake.AddHeader(headers)\n\n\t// Server verifies the challenge-client and signs the challenge-server\n\tserverHandshake.Reset()\n\trequire.NoError(t, serverHandshake.ParseHeaderVal([]byte(headers.Get(\"Authorization\"))))\n\tclear(headers)\n\trequire.NoError(t, serverHandshake.Run())\n\tserverHandshake.SetHeader(headers)\n\n\t// Client verifies the challenge-server and sets the bearer token\n\trequire.NoError(t, clientHandshake.ParseHeader(headers))\n\tclear(headers)\n\trequire.NoError(t, clientHandshake.Run())\n\tclientHandshake.AddHeader(headers)\n\n\texpectedClientPeerID, _ := peer.IDFromPrivateKey(clientPriv)\n\texpectedServerPeerID, _ := peer.IDFromPrivateKey(serverPriv)\n\tclientPeerID, err := serverHandshake.PeerID()\n\trequire.NoError(t, err)\n\trequire.Equal(t, expectedClientPeerID, clientPeerID)\n\n\tserverPeerID, err := clientHandshake.PeerID()\n\trequire.NoError(t, err)\n\trequire.True(t, clientHandshake.HandshakeDone())\n\trequire.Equal(t, expectedServerPeerID, serverPeerID)\n}\n\nfunc BenchmarkServerHandshake(b *testing.B) {\n\tclientHeader1 := make(http.Header)\n\tclientHeader2 := make(http.Header)\n\theaders := make(http.Header)\n\n\thostname := \"example.com\"\n\tserverPriv, _, _ := crypto.GenerateEd25519Key(rand.Reader)\n\tclientPriv, _, _ := crypto.GenerateEd25519Key(rand.Reader)\n\n\tserverHandshake := PeerIDAuthHandshakeServer{\n\t\tHostname: hostname,\n\t\tPrivKey:  serverPriv,\n\t\tTokenTTL: time.Hour,\n\t\tHmac:     hmac.New(sha256.New, make([]byte, 32)),\n\t}\n\n\tclientHandshake := PeerIDAuthHandshakeClient{\n\t\tHostname: hostname,\n\t\tPrivKey:  clientPriv,\n\t}\n\trequire.NoError(b, serverHandshake.ParseHeaderVal(nil))\n\trequire.NoError(b, serverHandshake.Run())\n\tserverHandshake.SetHeader(headers)\n\n\t// Client receives the challenge and signs it. Also sends the challenge server\n\trequire.NoError(b, clientHandshake.ParseHeader(headers))\n\tclear(headers)\n\trequire.NoError(b, clientHandshake.Run())\n\tclientHandshake.AddHeader(clientHeader1)\n\n\t// Server receives the sig and verifies it. Also signs the challenge server\n\tserverHandshake.Reset()\n\trequire.NoError(b, serverHandshake.ParseHeaderVal([]byte(clientHeader1.Get(\"Authorization\"))))\n\tclear(headers)\n\trequire.NoError(b, serverHandshake.Run())\n\tserverHandshake.SetHeader(headers)\n\n\t// Client verifies sig and sets the bearer token for future requests\n\trequire.NoError(b, clientHandshake.ParseHeader(headers))\n\tclear(headers)\n\trequire.NoError(b, clientHandshake.Run())\n\tclientHandshake.AddHeader(clientHeader2)\n\n\t// Server verifies the bearer token\n\tserverHandshake.Reset()\n\trequire.NoError(b, serverHandshake.ParseHeaderVal([]byte(clientHeader2.Get(\"Authorization\"))))\n\tclear(headers)\n\trequire.NoError(b, serverHandshake.Run())\n\tserverHandshake.SetHeader(headers)\n\n\tinitialClientAuth := []byte(clientHeader1.Get(\"Authorization\"))\n\tbearerClientAuth := []byte(clientHeader2.Get(\"Authorization\"))\n\t_ = initialClientAuth\n\t_ = bearerClientAuth\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tserverHandshake.Reset()\n\t\tserverHandshake.ParseHeaderVal(nil)\n\t\tserverHandshake.Run()\n\n\t\tserverHandshake.Reset()\n\t\tserverHandshake.ParseHeaderVal(initialClientAuth)\n\t\tserverHandshake.Run()\n\n\t\tserverHandshake.Reset()\n\t\tserverHandshake.ParseHeaderVal(bearerClientAuth)\n\t\tserverHandshake.Run()\n\t}\n\n}\n\nfunc TestParsePeerIDAuthSchemeParams(t *testing.T) {\n\tstr := `libp2p-PeerID sig=\"<base64-signature-bytes>\", public-key=\"<base64-encoded-public-key-bytes>\", bearer=\"<base64-encoded-opaque-blob>\"`\n\tp := params{}\n\texpectedParam := params{\n\t\tsigB64:         []byte(`<base64-signature-bytes>`),\n\t\tpublicKeyB64:   []byte(`<base64-encoded-public-key-bytes>`),\n\t\tbearerTokenB64: []byte(`<base64-encoded-opaque-blob>`),\n\t}\n\terr := p.parsePeerIDAuthSchemeParams([]byte(str))\n\trequire.NoError(t, err)\n\trequire.Equal(t, expectedParam, p)\n}\n\nfunc BenchmarkParsePeerIDAuthSchemeParams(b *testing.B) {\n\tstr := []byte(`libp2p-PeerID peer-id=\"<server-peer-id-string>\", sig=\"<base64-signature-bytes>\", public-key=\"<base64-encoded-public-key-bytes>\", bearer=\"<base64-encoded-opaque-blob>\"`)\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tp := params{}\n\t\terr := p.parsePeerIDAuthSchemeParams(str)\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t}\n}\n\nfunc TestHeaderBuilder(t *testing.T) {\n\thb := headerBuilder{}\n\thb.writeScheme(PeerIDAuthScheme)\n\thb.writeParam(\"peer-id\", []byte(\"foo\"))\n\thb.writeParam(\"challenge-client\", []byte(\"something-else\"))\n\thb.writeParam(\"hostname\", []byte(\"example.com\"))\n\n\texpected := `libp2p-PeerID peer-id=\"foo\", challenge-client=\"something-else\", hostname=\"example.com\"`\n\trequire.Equal(t, expected, hb.b.String())\n}\n\nfunc BenchmarkHeaderBuilder(b *testing.B) {\n\th := headerBuilder{}\n\tscratch := make([]byte, 256)\n\tscratch = scratch[:0]\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\th.b.Grow(256)\n\t\th.writeParamB64(scratch, \"foo\", []byte(\"bar\"))\n\t\th.clear()\n\t}\n}\n\n// Test Vectors\nvar zeroBytes = make([]byte, 64)\nvar zeroKey, _, _ = crypto.GenerateEd25519Key(bytes.NewReader(zeroBytes))\n\n// Peer ID derived from the zero key\nvar zeroID, _ = peer.IDFromPublicKey(zeroKey.GetPublic())\n\nfunc TestOpaqueStateRoundTrip(t *testing.T) {\n\tzeroBytes := [32]byte{}\n\n\t// To drop the monotonic clock reading\n\ttimeAfterUnmarshal := time.Now()\n\tb, err := json.Marshal(timeAfterUnmarshal)\n\trequire.NoError(t, err)\n\trequire.NoError(t, json.Unmarshal(b, &timeAfterUnmarshal))\n\thmac := hmac.New(sha256.New, zeroBytes[:])\n\n\to := opaqueState{\n\t\tChallengeClient: \"foo-bar\",\n\t\tCreatedTime:     timeAfterUnmarshal,\n\t\tIsToken:         true,\n\t\tPeerID:          zeroID,\n\t\tHostname:        \"example.com\",\n\t}\n\n\thmac.Reset()\n\tb, err = o.Marshal(hmac, nil)\n\trequire.NoError(t, err)\n\n\to2 := opaqueState{}\n\n\thmac.Reset()\n\terr = o2.Unmarshal(hmac, b)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, o, o2)\n}\n\nfunc FuzzServerHandshakeNoPanic(f *testing.F) {\n\tzeroBytes := [32]byte{}\n\thmac := hmac.New(sha256.New, zeroBytes[:])\n\n\tf.Fuzz(func(_ *testing.T, data []byte) {\n\t\thmac.Reset()\n\t\th := PeerIDAuthHandshakeServer{\n\t\t\tHostname: \"example.com\",\n\t\t\tPrivKey:  zeroKey,\n\t\t\tHmac:     hmac,\n\t\t}\n\t\terr := h.ParseHeaderVal(data)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\terr = h.Run()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\th.PeerID()\n\t})\n}\n\nfunc BenchmarkOpaqueStateWrite(b *testing.B) {\n\tzeroBytes := [32]byte{}\n\thmac := hmac.New(sha256.New, zeroBytes[:])\n\to := opaqueState{\n\t\tChallengeClient: \"foo-bar\",\n\t\tCreatedTime:     time.Now(),\n\t}\n\td := make([]byte, 512)\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\thmac.Reset()\n\t\t_, err := o.Marshal(hmac, d[:0])\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t}\n}\n\nfunc BenchmarkOpaqueStateRead(b *testing.B) {\n\tzeroBytes := [32]byte{}\n\thmac := hmac.New(sha256.New, zeroBytes[:])\n\to := opaqueState{\n\t\tChallengeClient: \"foo-bar\",\n\t\tCreatedTime:     time.Now(),\n\t}\n\td := make([]byte, 256)\n\td, err := o.Marshal(hmac, d[:0])\n\trequire.NoError(b, err)\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\thmac.Reset()\n\t\terr := o.Unmarshal(hmac, d)\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t}\n}\n\nfunc FuzzParsePeerIDAuthSchemeParamsNoPanic(f *testing.F) {\n\tp := params{}\n\t// Just check that we don't panic\n\tf.Fuzz(func(_ *testing.T, data []byte) {\n\t\tp.parsePeerIDAuthSchemeParams(data)\n\t})\n}\n\ntype specsExampleParameters struct {\n\thostname      string\n\tserverPriv    crypto.PrivKey\n\tserverHmacKey [32]byte\n\tclientPriv    crypto.PrivKey\n}\n\nfunc TestSpecsExample(t *testing.T) {\n\toriginalRandReader := randReader\n\toriginalNowFn := nowFn\n\trandReader = bytes.NewReader(append(\n\t\tbytes.Repeat([]byte{0x11}, 32),\n\t\tbytes.Repeat([]byte{0x33}, 32)...,\n\t))\n\tnowFn = func() time.Time {\n\t\treturn time.Unix(0, 0)\n\t}\n\tdefer func() {\n\t\trandReader = originalRandReader\n\t\tnowFn = originalNowFn\n\t}()\n\n\tparameters := specsExampleParameters{\n\t\thostname: \"example.com\",\n\t}\n\tserverPrivBytes, err := hex.AppendDecode(nil, []byte(\"0801124001010101010101010101010101010101010101010101010101010101010101018a88e3dd7409f195fd52db2d3cba5d72ca6709bf1d94121bf3748801b40f6f5c\"))\n\trequire.NoError(t, err)\n\tclientPrivBytes, err := hex.AppendDecode(nil, []byte(\"0801124002020202020202020202020202020202020202020202020202020202020202028139770ea87d175f56a35466c34c7ecccb8d8a91b4ee37a25df60f5b8fc9b394\"))\n\trequire.NoError(t, err)\n\n\tparameters.serverPriv, err = crypto.UnmarshalPrivateKey(serverPrivBytes)\n\trequire.NoError(t, err)\n\n\tparameters.clientPriv, err = crypto.UnmarshalPrivateKey(clientPrivBytes)\n\trequire.NoError(t, err)\n\n\tserverHandshake := PeerIDAuthHandshakeServer{\n\t\tHostname: parameters.hostname,\n\t\tPrivKey:  parameters.serverPriv,\n\t\tTokenTTL: time.Hour,\n\t\tHmac:     hmac.New(sha256.New, parameters.serverHmacKey[:]),\n\t}\n\n\tclientHandshake := PeerIDAuthHandshakeClient{\n\t\tHostname: parameters.hostname,\n\t\tPrivKey:  parameters.clientPriv,\n\t}\n\n\theaders := make(http.Header)\n\n\t// Start the handshake\n\trequire.NoError(t, serverHandshake.ParseHeaderVal(nil))\n\trequire.NoError(t, serverHandshake.Run())\n\tserverHandshake.SetHeader(headers)\n\tinitialWWWAuthenticate := headers.Get(\"WWW-Authenticate\")\n\n\t// Client receives the challenge and signs it. Also sends the challenge server\n\trequire.NoError(t, clientHandshake.ParseHeader(headers))\n\tclear(headers)\n\trequire.NoError(t, clientHandshake.Run())\n\tclientHandshake.AddHeader(headers)\n\tclientAuthentication := headers.Get(\"Authorization\")\n\n\t// Server receives the sig and verifies it. Also signs the challenge server\n\tserverHandshake.Reset()\n\trequire.NoError(t, serverHandshake.ParseHeaderVal([]byte(headers.Get(\"Authorization\"))))\n\tclear(headers)\n\trequire.NoError(t, serverHandshake.Run())\n\tserverHandshake.SetHeader(headers)\n\tserverAuthentication := headers.Get(\"Authentication-Info\")\n\n\t// Client verifies sig and sets the bearer token for future requests\n\trequire.NoError(t, clientHandshake.ParseHeader(headers))\n\tclear(headers)\n\trequire.NoError(t, clientHandshake.Run())\n\tclientHandshake.AddHeader(headers)\n\tclientBearerToken := headers.Get(\"Authorization\")\n\n\tparams := params{}\n\tparams.parsePeerIDAuthSchemeParams([]byte(initialWWWAuthenticate))\n\tchallengeClient := params.challengeClient\n\tparams.parsePeerIDAuthSchemeParams([]byte(clientAuthentication))\n\tchallengeServer := params.challengeServer\n\n\tfmt.Println(\"### Parameters\")\n\tfmt.Println(\"| Parameter | Value |\")\n\tfmt.Println(\"| --- | --- |\")\n\tfmt.Printf(\"| hostname | %s |\\n\", parameters.hostname)\n\tfmt.Printf(\"| Server Private Key (pb encoded as hex) | %s |\\n\", hex.EncodeToString(serverPrivBytes))\n\tfmt.Printf(\"| Server HMAC Key (hex) | %s |\\n\", hex.EncodeToString(parameters.serverHmacKey[:]))\n\tfmt.Printf(\"| Challenge Client | %s |\\n\", string(challengeClient))\n\tfmt.Printf(\"| Client Private Key (pb encoded as hex) | %s |\\n\", hex.EncodeToString(clientPrivBytes))\n\tfmt.Printf(\"| Challenge Server | %s |\\n\", string(challengeServer))\n\tfmt.Printf(\"| \\\"Now\\\" time | %s |\\n\", nowFn())\n\tfmt.Println()\n\tfmt.Println(\"### Handshake Diagram\")\n\n\tfmt.Println(\"```mermaid\")\n\tfmt.Printf(`sequenceDiagram\nClient->>Server: Initial request\nServer->>Client: WWW-Authenticate=%s\nClient->>Server: Authorization=%s\nNote left of Server: Server has authenticated Client\nServer->>Client: Authentication-Info=%s\nNote right of Client: Client has authenticated Server\n\nNote over Client: Future requests use the bearer token\nClient->>Server: Authorization=%s\n`, initialWWWAuthenticate, clientAuthentication, serverAuthentication, clientBearerToken)\n\tfmt.Println(\"```\")\n\n}\n\nfunc TestSpecsClientInitiatedExample(t *testing.T) {\n\toriginalRandReader := randReader\n\toriginalNowFn := nowFn\n\trandReader = bytes.NewReader(append(\n\t\tbytes.Repeat([]byte{0x33}, 32),\n\t\tbytes.Repeat([]byte{0x11}, 32)...,\n\t))\n\tnowFn = func() time.Time {\n\t\treturn time.Unix(0, 0)\n\t}\n\tdefer func() {\n\t\trandReader = originalRandReader\n\t\tnowFn = originalNowFn\n\t}()\n\n\tparameters := specsExampleParameters{\n\t\thostname: \"example.com\",\n\t}\n\tserverPrivBytes, err := hex.AppendDecode(nil, []byte(\"0801124001010101010101010101010101010101010101010101010101010101010101018a88e3dd7409f195fd52db2d3cba5d72ca6709bf1d94121bf3748801b40f6f5c\"))\n\trequire.NoError(t, err)\n\tclientPrivBytes, err := hex.AppendDecode(nil, []byte(\"0801124002020202020202020202020202020202020202020202020202020202020202028139770ea87d175f56a35466c34c7ecccb8d8a91b4ee37a25df60f5b8fc9b394\"))\n\trequire.NoError(t, err)\n\n\tparameters.serverPriv, err = crypto.UnmarshalPrivateKey(serverPrivBytes)\n\trequire.NoError(t, err)\n\n\tparameters.clientPriv, err = crypto.UnmarshalPrivateKey(clientPrivBytes)\n\trequire.NoError(t, err)\n\n\tserverHandshake := PeerIDAuthHandshakeServer{\n\t\tHostname: parameters.hostname,\n\t\tPrivKey:  parameters.serverPriv,\n\t\tTokenTTL: time.Hour,\n\t\tHmac:     hmac.New(sha256.New, parameters.serverHmacKey[:]),\n\t}\n\n\tclientHandshake := PeerIDAuthHandshakeClient{\n\t\tHostname: parameters.hostname,\n\t\tPrivKey:  parameters.clientPriv,\n\t}\n\n\theaders := make(http.Header)\n\n\t// Start the handshake\n\tclientHandshake.SetInitiateChallenge()\n\trequire.NoError(t, clientHandshake.Run())\n\tclientHandshake.AddHeader(headers)\n\tclientChallenge := headers.Get(\"Authorization\")\n\n\t// Server receives the challenge and signs it. Also sends challenge-client\n\tserverHandshake.Reset()\n\trequire.NoError(t, serverHandshake.ParseHeaderVal([]byte(headers.Get(\"Authorization\"))))\n\tclear(headers)\n\trequire.NoError(t, serverHandshake.Run())\n\tserverHandshake.SetHeader(headers)\n\tserverAuthentication := headers.Get(\"WWW-Authenticate\")\n\tparams := params{}\n\tparams.parsePeerIDAuthSchemeParams([]byte(serverAuthentication))\n\tchallengeClient := params.challengeClient\n\n\t// Client verifies sig and signs the challenge-client\n\trequire.NoError(t, clientHandshake.ParseHeader(headers))\n\tclear(headers)\n\trequire.NoError(t, clientHandshake.Run())\n\tclientHandshake.AddHeader(headers)\n\tclientAuthentication := headers.Get(\"Authorization\")\n\n\t// Server verifies sig and sets the bearer token\n\tserverHandshake.Reset()\n\trequire.NoError(t, serverHandshake.ParseHeaderVal([]byte(headers.Get(\"Authorization\"))))\n\tclear(headers)\n\trequire.NoError(t, serverHandshake.Run())\n\tserverHandshake.SetHeader(headers)\n\tserverReplayWithBearer := headers.Get(\"Authentication-Info\")\n\n\tparams.parsePeerIDAuthSchemeParams([]byte(clientChallenge))\n\tchallengeServer := params.challengeServer\n\n\tfmt.Println(\"### Parameters\")\n\tfmt.Println(\"| Parameter | Value |\")\n\tfmt.Println(\"| --- | --- |\")\n\tfmt.Printf(\"| hostname | %s |\\n\", parameters.hostname)\n\tfmt.Printf(\"| Server Private Key (pb encoded as hex) | %s |\\n\", hex.EncodeToString(serverPrivBytes))\n\tfmt.Printf(\"| Server HMAC Key (hex) | %s |\\n\", hex.EncodeToString(parameters.serverHmacKey[:]))\n\tfmt.Printf(\"| Challenge Client | %s |\\n\", string(challengeClient))\n\tfmt.Printf(\"| Client Private Key (pb encoded as hex) | %s |\\n\", hex.EncodeToString(clientPrivBytes))\n\tfmt.Printf(\"| Challenge Server | %s |\\n\", string(challengeServer))\n\tfmt.Printf(\"| \\\"Now\\\" time | %s |\\n\", nowFn())\n\tfmt.Println()\n\tfmt.Println(\"### Handshake Diagram\")\n\n\tfmt.Println(\"```mermaid\")\n\tfmt.Printf(`sequenceDiagram\nClient->>Server: Authorization=%s\nServer->>Client: WWW-Authenticate=%s\nNote right of Client: Client has authenticated Server\n\nClient->>Server: Authorization=%s\nNote left of Server: Server has authenticated Client\nServer->>Client: Authentication-Info=%s\nNote over Client: Future requests use the bearer token\n`, clientChallenge, serverAuthentication, clientAuthentication, serverReplayWithBearer)\n\tfmt.Println(\"```\")\n\n}\n\nfunc TestSigningExample(t *testing.T) {\n\tserverPrivBytes, err := hex.AppendDecode(nil, []byte(\"0801124001010101010101010101010101010101010101010101010101010101010101018a88e3dd7409f195fd52db2d3cba5d72ca6709bf1d94121bf3748801b40f6f5c\"))\n\trequire.NoError(t, err)\n\tserverPriv, err := crypto.UnmarshalPrivateKey(serverPrivBytes)\n\trequire.NoError(t, err)\n\tclientPrivBytes, err := hex.AppendDecode(nil, []byte(\"0801124002020202020202020202020202020202020202020202020202020202020202028139770ea87d175f56a35466c34c7ecccb8d8a91b4ee37a25df60f5b8fc9b394\"))\n\trequire.NoError(t, err)\n\tclientPriv, err := crypto.UnmarshalPrivateKey(clientPrivBytes)\n\trequire.NoError(t, err)\n\tclientPubKeyBytes, err := crypto.MarshalPublicKey(clientPriv.GetPublic())\n\trequire.NoError(t, err)\n\n\trequire.NoError(t, err)\n\tchallenge := \"ERERERERERERERERERERERERERERERERERERERERERE=\"\n\n\thostname := \"example.com\"\n\tdataToSign, err := genDataToSign(nil, PeerIDAuthScheme, []sigParam{\n\t\t{\"challenge-server\", []byte(challenge)},\n\t\t{\"client-public-key\", clientPubKeyBytes},\n\t\t{\"hostname\", []byte(hostname)},\n\t})\n\trequire.NoError(t, err)\n\n\tsig, err := sign(serverPriv, PeerIDAuthScheme, []sigParam{\n\t\t{\"challenge-server\", []byte(challenge)},\n\t\t{\"client-public-key\", clientPubKeyBytes},\n\t\t{\"hostname\", []byte(hostname)},\n\t})\n\trequire.NoError(t, err)\n\n\tfmt.Println(\"### Signing Example\")\n\n\tfmt.Println(\"| Parameter | Value |\")\n\tfmt.Println(\"| --- | --- |\")\n\tfmt.Printf(\"| hostname | %s |\\n\", hostname)\n\tfmt.Printf(\"| Server Private Key (pb encoded as hex) | %s |\\n\", hex.EncodeToString(serverPrivBytes))\n\tfmt.Printf(\"| challenge-server | %s |\\n\", string(challenge))\n\tfmt.Printf(\"| Client Public Key (pb encoded as hex) | %s |\\n\", hex.EncodeToString(clientPubKeyBytes))\n\tfmt.Printf(\"| data to sign ([percent encoded](https://datatracker.ietf.org/doc/html/rfc3986#section-2.1)) | %s |\\n\", url.PathEscape(string(dataToSign)))\n\tfmt.Printf(\"| data to sign (hex encoded) | %s |\\n\", hex.EncodeToString(dataToSign))\n\tfmt.Printf(\"| signature (base64 encoded) | %s |\\n\", base64.URLEncoding.EncodeToString(sig))\n\tfmt.Println()\n\n\tfmt.Println(\"Note that the `=` after the libp2p-PeerID scheme is actually the varint length of the challenge-server parameter.\")\n\n}\n"
  },
  {
    "path": "p2p/http/auth/internal/handshake/server.go",
    "content": "package handshake\n\nimport (\n\t\"crypto/hmac\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"hash\"\n\t\"io\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n)\n\nvar (\n\tErrExpiredChallenge = errors.New(\"challenge expired\")\n\tErrExpiredToken     = errors.New(\"token expired\")\n\tErrInvalidHMAC      = errors.New(\"invalid HMAC\")\n)\n\nconst challengeTTL = 5 * time.Minute\n\ntype peerIDAuthServerState int\n\nconst (\n\t// Server initiated\n\tpeerIDAuthServerStateChallengeClient peerIDAuthServerState = iota\n\tpeerIDAuthServerStateVerifyChallenge\n\tpeerIDAuthServerStateVerifyBearer\n\n\t// Client initiated\n\tpeerIDAuthServerStateSignChallenge\n)\n\ntype opaqueState struct {\n\tIsToken         bool      `json:\"is-token,omitempty\"`\n\tClientPublicKey []byte    `json:\"client-public-key,omitempty\"`\n\tPeerID          peer.ID   `json:\"peer-id,omitempty\"`\n\tChallengeClient string    `json:\"challenge-client,omitempty\"`\n\tHostname        string    `json:\"hostname\"`\n\tCreatedTime     time.Time `json:\"created-time\"`\n}\n\n// Marshal serializes the state by appending it to the byte slice.\nfunc (o *opaqueState) Marshal(hmac hash.Hash, b []byte) ([]byte, error) {\n\thmac.Reset()\n\tfieldsMarshalled, err := json.Marshal(o)\n\tif err != nil {\n\t\treturn b, err\n\t}\n\t_, err = hmac.Write(fieldsMarshalled)\n\tif err != nil {\n\t\treturn b, err\n\t}\n\tb = hmac.Sum(b)\n\tb = append(b, fieldsMarshalled...)\n\treturn b, nil\n}\n\nfunc (o *opaqueState) Unmarshal(hmacImpl hash.Hash, d []byte) error {\n\thmacImpl.Reset()\n\tif len(d) < hmacImpl.Size() {\n\t\treturn ErrInvalidHMAC\n\t}\n\thmacVal := d[:hmacImpl.Size()]\n\tfields := d[hmacImpl.Size():]\n\t_, err := hmacImpl.Write(fields)\n\tif err != nil {\n\t\treturn err\n\t}\n\texpectedHmac := hmacImpl.Sum(nil)\n\tif !hmac.Equal(hmacVal, expectedHmac) {\n\t\treturn ErrInvalidHMAC\n\t}\n\n\terr = json.Unmarshal(fields, &o)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\ntype PeerIDAuthHandshakeServer struct {\n\tHostname string\n\tPrivKey  crypto.PrivKey\n\tTokenTTL time.Duration\n\t// used to authenticate opaque blobs and tokens\n\tHmac hash.Hash\n\n\tran bool\n\tbuf [1024]byte\n\n\tstate peerIDAuthServerState\n\tp     params\n\thb    headerBuilder\n\n\topaque opaqueState\n}\n\nvar errInvalidHeader = errors.New(\"invalid header\")\n\nfunc (h *PeerIDAuthHandshakeServer) Reset() {\n\th.Hmac.Reset()\n\th.ran = false\n\tclear(h.buf[:])\n\th.state = 0\n\th.p = params{}\n\th.hb.clear()\n\th.opaque = opaqueState{}\n}\n\nfunc (h *PeerIDAuthHandshakeServer) ParseHeaderVal(headerVal []byte) error {\n\tif len(headerVal) == 0 {\n\t\t// We are in the initial state. Nothing to parse.\n\t\treturn nil\n\t}\n\terr := h.p.parsePeerIDAuthSchemeParams(headerVal)\n\tif err != nil {\n\t\treturn err\n\t}\n\tswitch {\n\tcase h.p.sigB64 != nil && h.p.opaqueB64 != nil:\n\t\th.state = peerIDAuthServerStateVerifyChallenge\n\tcase h.p.bearerTokenB64 != nil:\n\t\th.state = peerIDAuthServerStateVerifyBearer\n\tcase h.p.challengeServer != nil && h.p.publicKeyB64 != nil:\n\t\th.state = peerIDAuthServerStateSignChallenge\n\tdefault:\n\t\treturn errInvalidHeader\n\n\t}\n\treturn nil\n}\n\nfunc (h *PeerIDAuthHandshakeServer) Run() error {\n\th.ran = true\n\tswitch h.state {\n\tcase peerIDAuthServerStateSignChallenge:\n\t\th.hb.writeScheme(PeerIDAuthScheme)\n\t\tif err := h.addChallengeClientParam(); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := h.addPublicKeyParam(); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tpublicKeyBytes, err := base64.URLEncoding.AppendDecode(nil, h.p.publicKeyB64)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\th.opaque.ClientPublicKey = publicKeyBytes\n\t\tif err := h.addServerSigParam(publicKeyBytes); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := h.addOpaqueParam(); err != nil {\n\t\t\treturn err\n\t\t}\n\tcase peerIDAuthServerStateChallengeClient:\n\t\th.hb.writeScheme(PeerIDAuthScheme)\n\t\tif err := h.addChallengeClientParam(); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := h.addPublicKeyParam(); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := h.addOpaqueParam(); err != nil {\n\t\t\treturn err\n\t\t}\n\tcase peerIDAuthServerStateVerifyChallenge:\n\t\topaque, err := base64.URLEncoding.AppendDecode(h.buf[:0], h.p.opaqueB64)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = h.opaque.Unmarshal(h.Hmac, opaque)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif nowFn().After(h.opaque.CreatedTime.Add(challengeTTL)) {\n\t\t\treturn ErrExpiredChallenge\n\t\t}\n\t\tif h.opaque.IsToken {\n\t\t\treturn errors.New(\"expected challenge, got token\")\n\t\t}\n\n\t\tif h.Hostname != h.opaque.Hostname {\n\t\t\treturn errors.New(\"hostname in opaque mismatch\")\n\t\t}\n\n\t\tvar publicKeyBytes []byte\n\t\tclientInitiatedHandshake := h.opaque.ClientPublicKey != nil\n\n\t\tif clientInitiatedHandshake {\n\t\t\tpublicKeyBytes = h.opaque.ClientPublicKey\n\t\t} else {\n\t\t\tif len(h.p.publicKeyB64) == 0 {\n\t\t\t\treturn errors.New(\"missing public key\")\n\t\t\t}\n\t\t\tvar err error\n\t\t\tpublicKeyBytes, err = base64.URLEncoding.AppendDecode(nil, h.p.publicKeyB64)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tpubKey, err := crypto.UnmarshalPublicKey(publicKeyBytes)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := h.verifySig(pubKey); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tpeerID, err := peer.IDFromPublicKey(pubKey)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// And create a bearer token for the client\n\t\th.opaque = opaqueState{\n\t\t\tIsToken:     true,\n\t\t\tPeerID:      peerID,\n\t\t\tHostname:    h.Hostname,\n\t\t\tCreatedTime: nowFn(),\n\t\t}\n\n\t\th.hb.writeScheme(PeerIDAuthScheme)\n\n\t\tif !clientInitiatedHandshake {\n\t\t\tif err := h.addServerSigParam(publicKeyBytes); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif err := h.addBearerParam(); err != nil {\n\t\t\treturn err\n\t\t}\n\tcase peerIDAuthServerStateVerifyBearer:\n\t\tbearerToken, err := base64.URLEncoding.AppendDecode(h.buf[:0], h.p.bearerTokenB64)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = h.opaque.Unmarshal(h.Hmac, bearerToken)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif !h.opaque.IsToken {\n\t\t\treturn errors.New(\"expected token, got challenge\")\n\t\t}\n\n\t\tif nowFn().After(h.opaque.CreatedTime.Add(h.TokenTTL)) {\n\t\t\treturn ErrExpiredToken\n\t\t}\n\n\t\treturn nil\n\tdefault:\n\t\treturn errors.New(\"unhandled state\")\n\t}\n\n\treturn nil\n}\n\nfunc (h *PeerIDAuthHandshakeServer) addChallengeClientParam() error {\n\t_, err := io.ReadFull(randReader, h.buf[:challengeLen])\n\tif err != nil {\n\t\treturn err\n\t}\n\tencodedChallenge := base64.URLEncoding.AppendEncode(h.buf[challengeLen:challengeLen], h.buf[:challengeLen])\n\th.opaque.ChallengeClient = string(encodedChallenge)\n\th.opaque.Hostname = h.Hostname\n\th.opaque.CreatedTime = nowFn()\n\th.hb.writeParam(\"challenge-client\", encodedChallenge)\n\treturn nil\n}\n\nfunc (h *PeerIDAuthHandshakeServer) addOpaqueParam() error {\n\topaqueVal, err := h.opaque.Marshal(h.Hmac, h.buf[:0])\n\tif err != nil {\n\t\treturn err\n\t}\n\th.hb.writeParamB64(h.buf[len(opaqueVal):], \"opaque\", opaqueVal)\n\treturn nil\n}\n\nfunc (h *PeerIDAuthHandshakeServer) addServerSigParam(clientPublicKeyBytes []byte) error {\n\tif len(h.p.challengeServer) < challengeLen {\n\t\treturn errors.New(\"challenge too short\")\n\t}\n\tserverSig, err := sign(h.PrivKey, PeerIDAuthScheme, []sigParam{\n\t\t{\"challenge-server\", h.p.challengeServer},\n\t\t{\"client-public-key\", clientPublicKeyBytes},\n\t\t{\"hostname\", []byte(h.Hostname)},\n\t})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to sign challenge: %w\", err)\n\t}\n\th.hb.writeParamB64(h.buf[:], \"sig\", serverSig)\n\treturn nil\n}\n\nfunc (h *PeerIDAuthHandshakeServer) addBearerParam() error {\n\tbearerToken, err := h.opaque.Marshal(h.Hmac, h.buf[:0])\n\tif err != nil {\n\t\treturn err\n\t}\n\th.hb.writeParamB64(h.buf[len(bearerToken):], \"bearer\", bearerToken)\n\treturn nil\n}\n\nfunc (h *PeerIDAuthHandshakeServer) addPublicKeyParam() error {\n\tserverPubKey := h.PrivKey.GetPublic()\n\tpubKeyBytes, err := crypto.MarshalPublicKey(serverPubKey)\n\tif err != nil {\n\t\treturn err\n\t}\n\th.hb.writeParamB64(h.buf[:], \"public-key\", pubKeyBytes)\n\treturn nil\n}\n\nfunc (h *PeerIDAuthHandshakeServer) verifySig(clientPubKey crypto.PubKey) error {\n\tserverPubKey := h.PrivKey.GetPublic()\n\tserverPubKeyBytes, err := crypto.MarshalPublicKey(serverPubKey)\n\tif err != nil {\n\t\treturn err\n\t}\n\tsig, err := base64.URLEncoding.AppendDecode(h.buf[:0], h.p.sigB64)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to decode signature: %w\", err)\n\t}\n\terr = verifySig(clientPubKey, PeerIDAuthScheme, []sigParam{\n\t\t{k: \"challenge-client\", v: []byte(h.opaque.ChallengeClient)},\n\t\t{k: \"server-public-key\", v: serverPubKeyBytes},\n\t\t{k: \"hostname\", v: []byte(h.Hostname)},\n\t}, sig)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// PeerID returns the peer ID of the authenticated client.\nfunc (h *PeerIDAuthHandshakeServer) PeerID() (peer.ID, error) {\n\tif !h.ran {\n\t\treturn \"\", errNotRan\n\t}\n\tswitch h.state {\n\tcase peerIDAuthServerStateVerifyChallenge:\n\tcase peerIDAuthServerStateVerifyBearer:\n\tdefault:\n\t\treturn \"\", errors.New(\"not in proper state\")\n\t}\n\tif h.opaque.PeerID == \"\" {\n\t\treturn \"\", errors.New(\"peer ID not set\")\n\t}\n\treturn h.opaque.PeerID, nil\n}\n\nfunc (h *PeerIDAuthHandshakeServer) SetHeader(hdr http.Header) {\n\tif !h.ran {\n\t\treturn\n\t}\n\tdefer h.hb.clear()\n\tswitch h.state {\n\tcase peerIDAuthServerStateChallengeClient, peerIDAuthServerStateSignChallenge:\n\t\thdr.Set(\"WWW-Authenticate\", h.hb.b.String())\n\tcase peerIDAuthServerStateVerifyChallenge:\n\t\thdr.Set(\"Authentication-Info\", h.hb.b.String())\n\tcase peerIDAuthServerStateVerifyBearer:\n\t\t// For completeness. Nothing to do\n\t}\n}\n"
  },
  {
    "path": "p2p/http/auth/server.go",
    "content": "package httppeeridauth\n\nimport (\n\t\"crypto/hmac\"\n\t\"crypto/rand\"\n\t\"crypto/sha256\"\n\t\"errors\"\n\t\"hash\"\n\t\"net/http\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/p2p/http/auth/internal/handshake\"\n)\n\ntype hmacPool struct {\n\tp sync.Pool\n}\n\nfunc newHmacPool(key []byte) *hmacPool {\n\treturn &hmacPool{\n\t\tp: sync.Pool{\n\t\t\tNew: func() any {\n\t\t\t\treturn hmac.New(sha256.New, key)\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc (p *hmacPool) Get() hash.Hash {\n\th := p.p.Get().(hash.Hash)\n\th.Reset()\n\treturn h\n}\n\nfunc (p *hmacPool) Put(h hash.Hash) {\n\tp.p.Put(h)\n}\n\ntype ServerPeerIDAuth struct {\n\tPrivKey  crypto.PrivKey\n\tTokenTTL time.Duration\n\tNext     func(peer peer.ID, w http.ResponseWriter, r *http.Request)\n\t// NoTLS is a flag that allows the server to accept requests without a TLS\n\t// ServerName. Used when something else is terminating the TLS connection.\n\tNoTLS bool\n\t// Required when NoTLS is true. The server will only accept requests for\n\t// which the Host header returns true.\n\tValidHostnameFn func(hostname string) bool\n\n\tHmacKey  []byte\n\tinitHmac sync.Once\n\thmacPool *hmacPool\n}\n\n// ServeHTTP implements the http.Handler interface for PeerIDAuth. It will\n// attempt to authenticate the request using using the libp2p peer ID auth\n// scheme. If a Next handler is set, it will be called on authenticated\n// requests.\nfunc (a *ServerPeerIDAuth) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\ta.ServeHTTPWithNextHandler(w, r, a.Next)\n}\n\nfunc (a *ServerPeerIDAuth) ServeHTTPWithNextHandler(w http.ResponseWriter, r *http.Request, next func(peer.ID, http.ResponseWriter, *http.Request)) {\n\ta.initHmac.Do(func() {\n\t\tif a.HmacKey == nil {\n\t\t\tkey := make([]byte, 32)\n\t\t\t_, err := rand.Read(key)\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\ta.HmacKey = key\n\t\t}\n\t\ta.hmacPool = newHmacPool(a.HmacKey)\n\t})\n\n\thostname := r.Host\n\tif a.NoTLS {\n\t\tif a.ValidHostnameFn == nil {\n\t\t\tlog.Error(\"No ValidHostnameFn set. Required for NoTLS\")\n\t\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\t\tif !a.ValidHostnameFn(hostname) {\n\t\t\tlog.Debug(\"Unauthorized request for host: hostname returned false for ValidHostnameFn\", \"hostname\", hostname)\n\t\t\tw.WriteHeader(http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tif r.TLS == nil {\n\t\t\tlog.Warn(\"No TLS connection, and NoTLS is false\")\n\t\t\tw.WriteHeader(http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\t\tif hostname != r.TLS.ServerName {\n\t\t\tlog.Debug(\"Unauthorized request for host: hostname mismatch\", \"hostname\", hostname, \"expected\", r.TLS.ServerName)\n\t\t\tw.WriteHeader(http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\t\tif a.ValidHostnameFn != nil && !a.ValidHostnameFn(hostname) {\n\t\t\tlog.Debug(\"Unauthorized request for host: hostname returned false for ValidHostnameFn\", \"hostname\", hostname)\n\t\t\tw.WriteHeader(http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\t}\n\n\thmac := a.hmacPool.Get()\n\tdefer a.hmacPool.Put(hmac)\n\ths := handshake.PeerIDAuthHandshakeServer{\n\t\tHostname: hostname,\n\t\tPrivKey:  a.PrivKey,\n\t\tTokenTTL: a.TokenTTL,\n\t\tHmac:     hmac,\n\t}\n\terr := hs.ParseHeaderVal([]byte(r.Header.Get(\"Authorization\")))\n\tif err != nil {\n\t\tlog.Debug(\"Failed to parse header\", \"err\", err)\n\t\tw.WriteHeader(http.StatusBadRequest)\n\t\treturn\n\t}\n\terr = hs.Run()\n\tif err != nil {\n\t\tswitch {\n\t\tcase errors.Is(err, handshake.ErrInvalidHMAC),\n\t\t\terrors.Is(err, handshake.ErrExpiredChallenge),\n\t\t\terrors.Is(err, handshake.ErrExpiredToken):\n\n\t\t\thmac.Reset()\n\t\t\ths := handshake.PeerIDAuthHandshakeServer{\n\t\t\t\tHostname: hostname,\n\t\t\t\tPrivKey:  a.PrivKey,\n\t\t\t\tTokenTTL: a.TokenTTL,\n\t\t\t\tHmac:     hmac,\n\t\t\t}\n\t\t\t_ = hs.Run() // First run will never err\n\t\t\ths.SetHeader(w.Header())\n\t\t\tw.WriteHeader(http.StatusUnauthorized)\n\n\t\t\treturn\n\t\t}\n\n\t\tlog.Debug(\"Failed to run handshake\", \"err\", err)\n\t\tw.WriteHeader(http.StatusBadRequest)\n\t\treturn\n\t}\n\ths.SetHeader(w.Header())\n\n\tpeer, err := hs.PeerID()\n\tif err != nil {\n\t\tw.WriteHeader(http.StatusUnauthorized)\n\t\treturn\n\t}\n\n\tif next == nil {\n\t\tw.WriteHeader(http.StatusOK)\n\t\treturn\n\t}\n\tnext(peer, w, r)\n}\n\n// HasAuthHeader checks if the HTTP request contains an Authorization header\n// that starts with the PeerIDAuthScheme prefix.\nfunc HasAuthHeader(r *http.Request) bool {\n\th := r.Header.Get(\"Authorization\")\n\treturn h != \"\" && strings.HasPrefix(h, handshake.PeerIDAuthScheme)\n}\n"
  },
  {
    "path": "p2p/http/example_test.go",
    "content": "package libp2phttp_test\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p\"\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\tlibp2phttp \"github.com/libp2p/go-libp2p/p2p/http\"\n\thttpauth \"github.com/libp2p/go-libp2p/p2p/http/auth\"\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\nfunc ExampleHost_authenticatedHTTP() {\n\tclientKey, _, err := crypto.GenerateKeyPair(crypto.Ed25519, 0)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tclient := libp2phttp.Host{\n\t\tClientPeerIDAuth: &httpauth.ClientPeerIDAuth{\n\t\t\tTokenTTL: time.Hour,\n\t\t\tPrivKey:  clientKey,\n\t\t},\n\t}\n\n\tserverKey, _, err := crypto.GenerateKeyPair(crypto.Ed25519, 0)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tserver := libp2phttp.Host{\n\t\tServerPeerIDAuth: &httpauth.ServerPeerIDAuth{\n\t\t\tPrivKey: serverKey,\n\t\t\t// No TLS for this example. In practice you want to use TLS.\n\t\t\tNoTLS: true,\n\t\t\tValidHostnameFn: func(hostname string) bool {\n\t\t\t\treturn strings.HasPrefix(hostname, \"127.0.0.1\")\n\t\t\t},\n\t\t\tTokenTTL: time.Hour,\n\t\t},\n\t\t// No TLS for this example. In practice you want to use TLS.\n\t\tInsecureAllowHTTP: true,\n\t\tListenAddrs:       []ma.Multiaddr{ma.StringCast(\"/ip4/127.0.0.1/tcp/0/http\")},\n\t}\n\n\tobservedClientID := \"\"\n\tserver.SetHTTPHandler(\"/echo-id\", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tobservedClientID = libp2phttp.ClientPeerID(r).String()\n\t\tw.WriteHeader(http.StatusOK)\n\t}))\n\n\tgo server.Serve()\n\tdefer server.Close()\n\n\texpectedServerID, err := peer.IDFromPrivateKey(serverKey)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\thttpClient := http.Client{Transport: &client}\n\turl := fmt.Sprintf(\"multiaddr:%s/p2p/%s/http-path/echo-id\", server.Addrs()[0], expectedServerID)\n\tresp, err := httpClient.Get(url)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tresp.Body.Close()\n\n\texpectedClientID, err := peer.IDFromPrivateKey(clientKey)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tif observedClientID != expectedClientID.String() {\n\t\tlog.Fatal(\"observedClientID does not match expectedClientID\")\n\t}\n\n\tobservedServerID := libp2phttp.ServerPeerID(resp)\n\tif observedServerID != expectedServerID {\n\t\tlog.Fatal(\"observedServerID does not match expectedServerID\")\n\t}\n\n\tfmt.Println(\"Successfully authenticated HTTP request\")\n\t// Output: Successfully authenticated HTTP request\n}\n\nfunc ExampleHost_withAStockGoHTTPClient() {\n\tserver := libp2phttp.Host{\n\t\tInsecureAllowHTTP: true, // For our example, we'll allow insecure HTTP\n\t\tListenAddrs:       []ma.Multiaddr{ma.StringCast(\"/ip4/127.0.0.1/tcp/0/http\")},\n\t}\n\n\t// A server with a simple echo protocol\n\tserver.SetHTTPHandler(\"/echo/1.0.0\", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Add(\"Content-Type\", \"application/octet-stream\")\n\t\tio.Copy(w, r.Body)\n\t}))\n\tgo server.Serve()\n\tdefer server.Close()\n\n\tvar serverHTTPPort string\n\tvar err error\n\tfor _, a := range server.Addrs() {\n\t\tserverHTTPPort, err = a.ValueForProtocol(ma.P_TCP)\n\t\tif err == nil {\n\t\t\tbreak\n\t\t}\n\t}\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Make an HTTP request using the Go standard library.\n\tresp, err := http.Post(\"http://127.0.0.1:\"+serverHTTPPort+\"/echo/1.0.0/\", \"application/octet-stream\", strings.NewReader(\"Hello HTTP\"))\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tdefer resp.Body.Close()\n\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tfmt.Println(string(body))\n\n\t// Output: Hello HTTP\n}\n\nfunc ExampleHost_listenOnHTTPTransportAndStreams() {\n\tserverStreamHost, err := libp2p.New(libp2p.ListenAddrStrings(\"/ip4/127.0.0.1/udp/0/quic-v1\"))\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tdefer serverStreamHost.Close()\n\n\tserver := libp2phttp.Host{\n\t\tInsecureAllowHTTP: true, // For our example, we'll allow insecure HTTP\n\t\tListenAddrs:       []ma.Multiaddr{ma.StringCast(\"/ip4/127.0.0.1/tcp/0/http\")},\n\t\tStreamHost:        serverStreamHost,\n\t}\n\tgo server.Serve()\n\tdefer server.Close()\n\n\tfor _, a := range server.Addrs() {\n\t\t_, transport := ma.SplitLast(a)\n\t\tfmt.Printf(\"Server listening on transport: %s\\n\", transport)\n\t}\n\t// Output: Server listening on transport: /quic-v1\n\t// Server listening on transport: /http\n}\n\nfunc ExampleHost_overLibp2pStreams() {\n\tserverStreamHost, err := libp2p.New(libp2p.ListenAddrStrings(\"/ip4/127.0.0.1/udp/0/quic-v1\"))\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tserver := libp2phttp.Host{\n\t\tStreamHost: serverStreamHost,\n\t}\n\n\t// A server with a simple echo protocol\n\tserver.SetHTTPHandler(\"/echo/1.0.0\", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Add(\"Content-Type\", \"application/octet-stream\")\n\t\tio.Copy(w, r.Body)\n\t}))\n\tgo server.Serve()\n\tdefer server.Close()\n\n\tclientStreamHost, err := libp2p.New(libp2p.NoListenAddrs)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tclient := libp2phttp.Host{StreamHost: clientStreamHost}\n\n\t// Make an HTTP request using the Go standard library, but over libp2p\n\t// streams. If the server were listening on an HTTP transport, this could\n\t// also make the request over the HTTP transport.\n\thttpClient, _ := client.NamespacedClient(\"/echo/1.0.0\", peer.AddrInfo{ID: server.PeerID(), Addrs: server.Addrs()})\n\n\t// Only need to Post to \"/\" because this client is namespaced to the \"/echo/1.0.0\" protocol.\n\tresp, err := httpClient.Post(\"/\", \"application/octet-stream\", strings.NewReader(\"Hello HTTP\"))\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tdefer resp.Body.Close()\n\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tfmt.Println(string(body))\n\n\t// Output: Hello HTTP\n}\n\nvar tcpPortRE = regexp.MustCompile(`/tcp/(\\d+)`)\n\nfunc ExampleHost_Serve() {\n\tserver := libp2phttp.Host{\n\t\tInsecureAllowHTTP: true, // For our example, we'll allow insecure HTTP\n\t\tListenAddrs:       []ma.Multiaddr{ma.StringCast(\"/ip4/127.0.0.1/tcp/0/http\")},\n\t}\n\n\tgo server.Serve()\n\tdefer server.Close()\n\n\tfor _, a := range server.Addrs() {\n\t\ts := a.String()\n\t\taddrWithoutSpecificPort := tcpPortRE.ReplaceAllString(s, \"/tcp/<runtime-port>\")\n\t\tfmt.Println(addrWithoutSpecificPort)\n\t}\n\n\t// Output: /ip4/127.0.0.1/tcp/<runtime-port>/http\n}\n\nfunc ExampleHost_SetHTTPHandler() {\n\tserver := libp2phttp.Host{\n\t\tInsecureAllowHTTP: true, // For our example, we'll allow insecure HTTP\n\t\tListenAddrs:       []ma.Multiaddr{ma.StringCast(\"/ip4/127.0.0.1/tcp/0/http\")},\n\t}\n\n\tserver.SetHTTPHandler(\"/hello/1\", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Header().Add(\"Content-Type\", \"text/plain\")\n\t\tw.Write([]byte(\"Hello World\"))\n\t}))\n\n\tgo server.Serve()\n\tdefer server.Close()\n\n\tport, err := server.Addrs()[0].ValueForProtocol(ma.P_TCP)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tresp, err := http.Get(\"http://127.0.0.1:\" + port + \"/hello/1/\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tdefer resp.Body.Close()\n\trespBody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tfmt.Println(string(respBody))\n\n\t// Output: Hello World\n}\n\nfunc ExampleHost_SetHTTPHandlerAtPath() {\n\tserver := libp2phttp.Host{\n\t\tInsecureAllowHTTP: true, // For our example, we'll allow insecure HTTP\n\t\tListenAddrs:       []ma.Multiaddr{ma.StringCast(\"/ip4/127.0.0.1/tcp/0/http\")},\n\t}\n\n\tserver.SetHTTPHandlerAtPath(\"/hello/1\", \"/other-place/\", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Header().Add(\"Content-Type\", \"text/plain\")\n\t\tw.Write([]byte(\"Hello World\"))\n\t}))\n\n\tgo server.Serve()\n\tdefer server.Close()\n\n\tport, err := server.Addrs()[0].ValueForProtocol(ma.P_TCP)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tresp, err := http.Get(\"http://127.0.0.1:\" + port + \"/other-place/\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tdefer resp.Body.Close()\n\trespBody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tfmt.Println(string(respBody))\n\n\t// Output: Hello World\n}\n\nfunc ExampleHost_NamespacedClient() {\n\tvar client libp2phttp.Host\n\n\t// Create the server\n\tserver := libp2phttp.Host{\n\t\tInsecureAllowHTTP: true, // For our example, we'll allow insecure HTTP\n\t\tListenAddrs:       []ma.Multiaddr{ma.StringCast(\"/ip4/127.0.0.1/tcp/0/http\")},\n\t}\n\n\tserver.SetHTTPHandlerAtPath(\"/hello/1\", \"/other-place/\", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Header().Add(\"Content-Type\", \"text/plain\")\n\t\tw.Write([]byte(\"Hello World\"))\n\t}))\n\n\tgo server.Serve()\n\tdefer server.Close()\n\n\t// Create an http.Client that is namespaced to this protocol.\n\thttpClient, err := client.NamespacedClient(\"/hello/1\", peer.AddrInfo{ID: server.PeerID(), Addrs: server.Addrs()})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tresp, err := httpClient.Get(\"/\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tdefer resp.Body.Close()\n\trespBody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tfmt.Println(string(respBody))\n\n\t// Output: Hello World\n}\n\nfunc ExampleHost_NamespaceRoundTripper() {\n\tvar client libp2phttp.Host\n\n\t// Create the server\n\tserver := libp2phttp.Host{\n\t\tInsecureAllowHTTP: true, // For our example, we'll allow insecure HTTP\n\t\tListenAddrs:       []ma.Multiaddr{ma.StringCast(\"/ip4/127.0.0.1/tcp/0/http\")},\n\t}\n\n\tserver.SetHTTPHandler(\"/hello/1\", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Header().Add(\"Content-Type\", \"text/plain\")\n\t\tw.Write([]byte(\"Hello World\"))\n\t}))\n\n\tgo server.Serve()\n\tdefer server.Close()\n\n\t// Create an http.Roundtripper for the server\n\trt, err := client.NewConstrainedRoundTripper(peer.AddrInfo{ID: server.PeerID(), Addrs: server.Addrs()})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Namespace this roundtripper to a specific protocol\n\trt, err = client.NamespaceRoundTripper(rt, \"/hello/1\", server.PeerID())\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tresp, err := (&http.Client{Transport: rt}).Get(\"/\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tdefer resp.Body.Close()\n\trespBody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tfmt.Println(string(respBody))\n\n\t// Output: Hello World\n}\n\nfunc ExampleHost_NewConstrainedRoundTripper() {\n\tvar client libp2phttp.Host\n\n\t// Create the server\n\tserver := libp2phttp.Host{\n\t\tInsecureAllowHTTP: true, // For our example, we'll allow insecure HTTP\n\t\tListenAddrs:       []ma.Multiaddr{ma.StringCast(\"/ip4/127.0.0.1/tcp/0/http\")},\n\t}\n\n\tserver.SetHTTPHandler(\"/hello/1\", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Header().Add(\"Content-Type\", \"text/plain\")\n\t\tw.Write([]byte(\"Hello World\"))\n\t}))\n\n\tgo server.Serve()\n\tdefer server.Close()\n\n\t// Create an http.Roundtripper for the server\n\trt, err := client.NewConstrainedRoundTripper(peer.AddrInfo{ID: server.PeerID(), Addrs: server.Addrs()})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tresp, err := (&http.Client{Transport: rt}).Get(\"/hello/1\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tdefer resp.Body.Close()\n\trespBody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tfmt.Println(string(respBody))\n\n\t// Output: Hello World\n}\n\nfunc ExampleWellKnownHandler() {\n\tvar h libp2phttp.WellKnownHandler\n\th.AddProtocolMeta(\"/hello/1\", libp2phttp.ProtocolMeta{\n\t\tPath: \"/hello-path/\",\n\t})\n\n\tlistener, err := net.Listen(\"tcp\", \"127.0.0.1:0\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tdefer listener.Close()\n\t// Serve the well-known resource. Note, this is handled automatically if you use the libp2phttp.Host.\n\tgo http.Serve(listener, &h)\n\n\t// Get the well-known resource\n\tresp, err := http.Get(\"http://\" + listener.Addr().String() + libp2phttp.WellKnownProtocols)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tdefer resp.Body.Close()\n\trespBody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tfmt.Println(string(respBody))\n\t// Output: {\"/hello/1\":{\"path\":\"/hello-path/\"}}\n\n}\n\nfunc ExampleHost_RoundTrip() {\n\t// Setup server for example\n\tserver := libp2phttp.Host{\n\t\tInsecureAllowHTTP: true, // For our example, we'll allow insecure HTTP\n\t\tListenAddrs:       []ma.Multiaddr{ma.StringCast(\"/ip4/127.0.0.1/tcp/0/http\")},\n\t}\n\tgo server.Serve()\n\tdefer server.Close()\n\tserver.SetHTTPHandlerAtPath(\"/hello/\", \"/\", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Write([]byte(\"Hello World\"))\n\t}))\n\n\t// Use the HTTP Host as a RoundTripper\n\thttpHost := libp2phttp.Host{}\n\tclient := http.Client{Transport: &httpHost}\n\tresp, err := client.Get(\"multiaddr:\" + server.Addrs()[0].String())\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tfmt.Println(string(body))\n\t// Output: Hello World\n}\n"
  },
  {
    "path": "p2p/http/libp2phttp.go",
    "content": "// HTTP semantics with libp2p. Can use a libp2p stream transport or stock HTTP\n// transports. This API is experimental and will likely change soon. Implements [libp2p spec #508](https://github.com/libp2p/specs/pull/508).\npackage libp2phttp\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"maps\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tlru \"github.com/hashicorp/golang-lru/v2\"\n\thost \"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n\thttpauth \"github.com/libp2p/go-libp2p/p2p/http/auth\"\n\tgostream \"github.com/libp2p/go-libp2p/p2p/net/gostream\"\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\nvar log = logging.Logger(\"libp2phttp\")\n\nvar WellKnownRequestTimeout = 30 * time.Second\n\nconst ProtocolIDForMultistreamSelect = \"/http/1.1\"\nconst WellKnownProtocols = \"/.well-known/libp2p/protocols\"\n\n// LegacyWellKnownProtocols refer to a the well-known resource used in an early\n// draft of the libp2p+http spec. Some users have deployed this, and need backwards compatibility.\n// Hopefully we can phase this out in the future. Context: https://github.com/libp2p/go-libp2p/pull/2797\nconst LegacyWellKnownProtocols = \"/.well-known/libp2p\"\n\nconst peerMetadataLimit = 8 << 10 // 8KB\nconst peerMetadataLRUSize = 256   // How many different peer's metadata to keep in our LRU cache\n\n// DefaultNewStreamTimeout is the default value for new stream establishing timeout.\n// It is the same value as basic_host.DefaultNegotiationTimeout\nvar DefaultNewStreamTimeout = 10 * time.Second\n\ntype clientPeerIDContextKey struct{}\ntype serverPeerIDContextKey struct{}\n\nfunc ClientPeerID(r *http.Request) peer.ID {\n\tif id, ok := r.Context().Value(clientPeerIDContextKey{}).(peer.ID); ok {\n\t\treturn id\n\t}\n\treturn \"\"\n}\n\nfunc ServerPeerID(r *http.Response) peer.ID {\n\tif id, ok := r.Request.Context().Value(serverPeerIDContextKey{}).(peer.ID); ok {\n\t\treturn id\n\t}\n\treturn \"\"\n}\n\n// ProtocolMeta is metadata about a protocol.\ntype ProtocolMeta struct {\n\t// Path defines the HTTP Path prefix used for this protocol\n\tPath string `json:\"path\"`\n}\n\ntype PeerMeta map[protocol.ID]ProtocolMeta\n\n// WellKnownHandler is an http.Handler that serves the well-known resource\ntype WellKnownHandler struct {\n\twellknownMapMu   sync.Mutex\n\twellKnownMapping PeerMeta\n\twellKnownCache   []byte\n}\n\n// streamHostListen returns a net.Listener that listens on libp2p streams for HTTP/1.1 messages.\nfunc streamHostListen(streamHost host.Host) (net.Listener, error) {\n\treturn gostream.Listen(streamHost, ProtocolIDForMultistreamSelect, gostream.IgnoreEOF())\n}\n\nfunc (h *WellKnownHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\t// Check if the requests accepts JSON\n\taccepts := r.Header.Get(\"Accept\")\n\tif accepts != \"\" && !(strings.Contains(accepts, \"application/json\") || strings.Contains(accepts, \"*/*\")) {\n\t\thttp.Error(w, \"Only application/json is supported\", http.StatusNotAcceptable)\n\t\treturn\n\t}\n\n\tif r.Method != http.MethodGet {\n\t\thttp.Error(w, \"Only GET requests are supported\", http.StatusMethodNotAllowed)\n\t\treturn\n\t}\n\n\t// Return a JSON object with the well-known protocols\n\th.wellknownMapMu.Lock()\n\tmapping := h.wellKnownCache\n\tvar err error\n\tif mapping == nil {\n\t\tmapping, err = json.Marshal(h.wellKnownMapping)\n\t\tif err == nil {\n\t\t\th.wellKnownCache = mapping\n\t\t}\n\t}\n\th.wellknownMapMu.Unlock()\n\tif err != nil {\n\t\thttp.Error(w, \"Marshal error\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\tw.Header().Add(\"Content-Type\", \"application/json\")\n\tw.Header().Add(\"Content-Length\", strconv.Itoa(len(mapping)))\n\tw.Write(mapping)\n}\n\nfunc (h *WellKnownHandler) AddProtocolMeta(p protocol.ID, protocolMeta ProtocolMeta) {\n\th.wellknownMapMu.Lock()\n\tif h.wellKnownMapping == nil {\n\t\th.wellKnownMapping = make(map[protocol.ID]ProtocolMeta)\n\t}\n\th.wellKnownMapping[p] = protocolMeta\n\th.wellKnownCache = nil\n\th.wellknownMapMu.Unlock()\n}\n\nfunc (h *WellKnownHandler) RemoveProtocolMeta(p protocol.ID) {\n\th.wellknownMapMu.Lock()\n\tif h.wellKnownMapping != nil {\n\t\tdelete(h.wellKnownMapping, p)\n\t}\n\th.wellKnownCache = nil\n\th.wellknownMapMu.Unlock()\n}\n\n// Host is a libp2p host for request/responses with HTTP semantics. This is\n// in contrast to a stream-oriented host like the core host.Host interface. Its\n// zero-value (&Host{}) is usable. Do not copy by value.\n// See examples for usage.\n//\n//\tWarning, this is experimental. The API will likely change.\ntype Host struct {\n\t// StreamHost is a stream based libp2p host used to do HTTP over libp2p streams. May be nil\n\tStreamHost host.Host\n\t// ListenAddrs are the requested addresses to listen on. Multiaddrs must be\n\t// valid HTTP(s) multiaddr. Only multiaddrs for an HTTP transport are\n\t// supported (must end with /http or /https).\n\tListenAddrs []ma.Multiaddr\n\t// TLSConfig is the TLS config for the server to use\n\tTLSConfig *tls.Config\n\t// InsecureAllowHTTP indicates if the server is allowed to serve unencrypted\n\t// HTTP requests over TCP.\n\tInsecureAllowHTTP bool\n\n\t// ServerPeerIDAuth sets the Server's signing key and TTL for server\n\t// provided tokens.\n\tServerPeerIDAuth *httpauth.ServerPeerIDAuth\n\t// ClientPeerIDAuth sets the Client's signing key and TTL for our stored\n\t// tokens.\n\tClientPeerIDAuth *httpauth.ClientPeerIDAuth\n\n\t// ServeMux is the http.ServeMux used by the server to serve requests. If\n\t// nil, a new serve mux will be created. Users may manually add handlers to\n\t// this mux instead of using `SetHTTPHandler`, but if they do, they should\n\t// also update the WellKnownHandler's protocol mapping.\n\tServeMux           *http.ServeMux\n\tinitializeServeMux sync.Once\n\n\t// DefaultClientRoundTripper is the default http.RoundTripper for clients to\n\t// use when making requests over an HTTP transport. This must be an\n\t// `*http.Transport` type so that the transport can be cloned and the\n\t// `TLSClientConfig` field can be configured. If unset, it will create a new\n\t// `http.Transport` on first use.\n\tDefaultClientRoundTripper *http.Transport\n\n\t// WellKnownHandler is the http handler for the well-known\n\t// resource. It is responsible for sharing this node's protocol metadata\n\t// with other nodes. Users only care about this if they set their own\n\t// ServeMux with pre-existing routes. By default, new protocols are added\n\t// here when a user calls `SetHTTPHandler` or `SetHTTPHandlerAtPath`.\n\tWellKnownHandler WellKnownHandler\n\n\t// EnableCompatibilityWithLegacyWellKnownEndpoint allows compatibility with\n\t// an older version of the spec that defined the well-known resource as:\n\t// .well-known/libp2p.\n\t// For servers, this means hosting the well-known resource at both the\n\t// legacy and current paths.\n\t// For clients it means making two parallel requests and picking the first one that succeeds.\n\t//\n\t// Long term this should be deprecated once enough users have upgraded to a\n\t// newer go-libp2p version and we can remove all this code.\n\tEnableCompatibilityWithLegacyWellKnownEndpoint bool\n\n\t// peerMetadata is an LRU cache of a peer's well-known protocol map.\n\tpeerMetadata *lru.Cache[peer.ID, PeerMeta]\n\t// createHTTPTransport is used to lazily create the httpTransport in a thread-safe way.\n\tcreateHTTPTransport sync.Once\n\t// createDefaultClientRoundTripper is used to lazily create the default\n\t// client round tripper in a thread-safe way.\n\tcreateDefaultClientRoundTripper sync.Once\n\thttpTransport                   *httpTransport\n}\n\ntype httpTransport struct {\n\tlistenAddrs         []ma.Multiaddr\n\tlisteners           []net.Listener\n\tcloseListeners      chan struct{}\n\twaitingForListeners chan struct{}\n}\n\nfunc newPeerMetadataCache() *lru.Cache[peer.ID, PeerMeta] {\n\tpeerMetadata, err := lru.New[peer.ID, PeerMeta](peerMetadataLRUSize)\n\tif err != nil {\n\t\t// Only happens if size is < 1. We make sure to not do that, so this should never happen.\n\t\tpanic(err)\n\t}\n\treturn peerMetadata\n}\n\nfunc (h *Host) httpTransportInit() {\n\th.createHTTPTransport.Do(func() {\n\t\th.httpTransport = &httpTransport{\n\t\t\tcloseListeners:      make(chan struct{}),\n\t\t\twaitingForListeners: make(chan struct{}),\n\t\t}\n\t})\n}\n\nfunc (h *Host) serveMuxInit() {\n\th.initializeServeMux.Do(func() {\n\t\tif h.ServeMux == nil {\n\t\t\th.ServeMux = http.NewServeMux()\n\t\t}\n\t})\n}\n\nfunc (h *Host) Addrs() []ma.Multiaddr {\n\th.httpTransportInit()\n\t<-h.httpTransport.waitingForListeners\n\treturn h.httpTransport.listenAddrs\n}\n\n// ID returns the peer ID of the underlying stream host, or the zero value if there is no stream host.\nfunc (h *Host) PeerID() peer.ID {\n\tif h.StreamHost != nil {\n\t\treturn h.StreamHost.ID()\n\t}\n\treturn \"\"\n}\n\nvar ErrNoListeners = errors.New(\"nothing to listen on\")\n\nfunc (h *Host) setupListeners(listenerErrCh chan error) error {\n\tfor _, addr := range h.ListenAddrs {\n\t\tparsedAddr, err := parseMultiaddr(addr)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t// resolve the host\n\t\tipaddr, err := net.ResolveIPAddr(\"ip\", parsedAddr.host)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\thost := ipaddr.String()\n\t\tl, err := net.Listen(\"tcp\", host+\":\"+parsedAddr.port)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\th.httpTransport.listeners = append(h.httpTransport.listeners, l)\n\n\t\t// get resolved port\n\t\t_, port, err := net.SplitHostPort(l.Addr().String())\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tvar listenAddr ma.Multiaddr\n\t\tif parsedAddr.useHTTPS && parsedAddr.sni != \"\" && parsedAddr.sni != host {\n\t\t\tlistenAddr = ma.StringCast(fmt.Sprintf(\"/ip4/%s/tcp/%s/tls/sni/%s/http\", host, port, parsedAddr.sni))\n\t\t} else {\n\t\t\tscheme := \"http\"\n\t\t\tif parsedAddr.useHTTPS {\n\t\t\t\tscheme = \"https\"\n\t\t\t}\n\t\t\tlistenAddr = ma.StringCast(fmt.Sprintf(\"/ip4/%s/tcp/%s/%s\", host, port, scheme))\n\t\t}\n\n\t\tif parsedAddr.useHTTPS {\n\t\t\tgo func() {\n\t\t\t\tsrv := http.Server{\n\t\t\t\t\tHandler:   maybeDecorateContextWithAuthMiddleware(h.ServerPeerIDAuth, h.ServeMux),\n\t\t\t\t\tTLSConfig: h.TLSConfig,\n\t\t\t\t}\n\t\t\t\tlistenerErrCh <- srv.ServeTLS(l, \"\", \"\")\n\t\t\t}()\n\t\t\th.httpTransport.listenAddrs = append(h.httpTransport.listenAddrs, listenAddr)\n\t\t} else if h.InsecureAllowHTTP {\n\t\t\tgo func() {\n\t\t\t\tsrv := http.Server{\n\t\t\t\t\tHandler: maybeDecorateContextWithAuthMiddleware(h.ServerPeerIDAuth, h.ServeMux),\n\t\t\t\t}\n\t\t\t\tlistenerErrCh <- srv.Serve(l)\n\t\t\t}()\n\t\t\th.httpTransport.listenAddrs = append(h.httpTransport.listenAddrs, listenAddr)\n\t\t} else {\n\t\t\t// We are not serving insecure HTTP\n\t\t\tlog.Warn(\"Not serving insecure HTTP. Prefer an HTTPS endpoint.\", \"addr\", listenAddr)\n\t\t}\n\t}\n\treturn nil\n}\n\n// Serve starts the HTTP transport listeners. Always returns a non-nil error.\n// If there are no listeners, returns ErrNoListeners.\nfunc (h *Host) Serve() error {\n\t// assert that each addr contains a /http component\n\tfor _, addr := range h.ListenAddrs {\n\t\t_, isHTTP := normalizeHTTPMultiaddr(addr)\n\t\tif !isHTTP {\n\t\t\treturn fmt.Errorf(\"address %s does not contain a /http or /https component\", addr)\n\t\t}\n\t}\n\n\th.serveMuxInit()\n\th.ServeMux.Handle(WellKnownProtocols, &h.WellKnownHandler)\n\tif h.EnableCompatibilityWithLegacyWellKnownEndpoint {\n\t\th.ServeMux.Handle(LegacyWellKnownProtocols, &h.WellKnownHandler)\n\t}\n\n\th.httpTransportInit()\n\n\tclosedWaitingForListeners := false\n\tdefer func() {\n\t\tif !closedWaitingForListeners {\n\t\t\tclose(h.httpTransport.waitingForListeners)\n\t\t}\n\t}()\n\n\tif len(h.ListenAddrs) == 0 && h.StreamHost == nil {\n\t\treturn ErrNoListeners\n\t}\n\n\th.httpTransport.listeners = make([]net.Listener, 0, len(h.ListenAddrs)+1) // +1 for stream host\n\n\tstreamHostAddrsCount := 0\n\tif h.StreamHost != nil {\n\t\tstreamHostAddrsCount = len(h.StreamHost.Addrs())\n\t}\n\th.httpTransport.listenAddrs = make([]ma.Multiaddr, 0, len(h.ListenAddrs)+streamHostAddrsCount)\n\n\terrCh := make(chan error)\n\n\tif h.StreamHost != nil {\n\t\tlistener, err := streamHostListen(h.StreamHost)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\th.httpTransport.listeners = append(h.httpTransport.listeners, listener)\n\t\th.httpTransport.listenAddrs = append(h.httpTransport.listenAddrs, h.StreamHost.Addrs()...)\n\n\t\tgo func() {\n\t\t\tsrv := &http.Server{\n\t\t\t\tHandler: connectionCloseHeaderMiddleware(h.ServeMux),\n\t\t\t\tConnContext: func(ctx context.Context, c net.Conn) context.Context {\n\t\t\t\t\tremote := c.RemoteAddr()\n\t\t\t\t\tif remote.Network() == gostream.Network {\n\t\t\t\t\t\tremoteID, err := peer.Decode(remote.String())\n\t\t\t\t\t\tif err == nil {\n\t\t\t\t\t\t\treturn context.WithValue(ctx, clientPeerIDContextKey{}, remoteID)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn ctx\n\t\t\t\t},\n\t\t\t}\n\t\t\terrCh <- srv.Serve(listener)\n\t\t}()\n\t}\n\n\tcloseAllListeners := func() {\n\t\tfor _, l := range h.httpTransport.listeners {\n\t\t\tl.Close()\n\t\t}\n\t}\n\n\terr := h.setupListeners(errCh)\n\tif err != nil {\n\t\tcloseAllListeners()\n\t\treturn err\n\t}\n\n\tclose(h.httpTransport.waitingForListeners)\n\tclosedWaitingForListeners = true\n\n\tif len(h.httpTransport.listeners) == 0 || len(h.httpTransport.listenAddrs) == 0 {\n\t\tcloseAllListeners()\n\t\treturn ErrNoListeners\n\t}\n\n\texpectedErrCount := len(h.httpTransport.listeners)\n\tselect {\n\tcase <-h.httpTransport.closeListeners:\n\t\terr = http.ErrServerClosed\n\tcase err = <-errCh:\n\t\texpectedErrCount--\n\t}\n\n\t// Close all listeners\n\tcloseAllListeners()\n\tfor i := 0; i < expectedErrCount; i++ {\n\t\t<-errCh\n\t}\n\tclose(errCh)\n\n\treturn err\n}\n\nfunc (h *Host) Close() error {\n\th.httpTransportInit()\n\tclose(h.httpTransport.closeListeners)\n\treturn nil\n}\n\n// SetHTTPHandler sets the HTTP handler for a given protocol. Automatically\n// manages the well-known resource mapping.\n// http.StripPrefix is called on the handler, so the handler will be unaware of\n// its prefix path.\nfunc (h *Host) SetHTTPHandler(p protocol.ID, handler http.Handler) {\n\th.SetHTTPHandlerAtPath(p, string(p), handler)\n}\n\n// SetHTTPHandlerAtPath sets the HTTP handler for a given protocol using the\n// given path. Automatically manages the well-known resource mapping.\n// http.StripPrefix is called on the handler, so the handler will be unaware of\n// its prefix path.\nfunc (h *Host) SetHTTPHandlerAtPath(p protocol.ID, path string, handler http.Handler) {\n\tif path == \"\" || path[len(path)-1] != '/' {\n\t\t// We are nesting this handler under this path, so it should end with a slash.\n\t\tpath += \"/\"\n\t}\n\th.WellKnownHandler.AddProtocolMeta(p, ProtocolMeta{Path: path})\n\th.serveMuxInit()\n\t// Do not trim the trailing / from path\n\t// This allows us to serve `/a/b` when we mount a handler for `/b` at path `/a`\n\th.ServeMux.Handle(path, http.StripPrefix(strings.TrimSuffix(path, \"/\"), handler))\n}\n\n// PeerMetadataGetter lets RoundTrippers implement a specific way of caching a peer's protocol mapping.\ntype PeerMetadataGetter interface {\n\tGetPeerMetadata() (PeerMeta, error)\n}\n\ntype streamRoundTripper struct {\n\tserver peer.ID\n\t// if true, we won't add the server's addresses to the peerstore. This\n\t// should only be set when creating the struct.\n\tskipAddAddrs bool\n\taddrsAdded   sync.Once\n\tserverAddrs  []ma.Multiaddr\n\th            host.Host\n\thttpHost     *Host\n}\n\n// streamReadCloser wraps an io.ReadCloser and closes the underlying stream when\n// closed (as well as closing the wrapped ReadCloser). This is necessary because\n// we have two things to close, the body and the stream. The stream isn't closed\n// by the body automatically, as hinted at by the fact that `http.ReadResponse`\n// takes a bufio.Reader.\ntype streamReadCloser struct {\n\tio.ReadCloser\n\ts network.Stream\n}\n\nfunc (s *streamReadCloser) Close() error {\n\ts.s.Close()\n\treturn s.ReadCloser.Close()\n}\n\nfunc (rt *streamRoundTripper) GetPeerMetadata() (PeerMeta, error) {\n\tctx := context.Background()\n\tctx, cancel := context.WithDeadline(ctx, time.Now().Add(WellKnownRequestTimeout))\n\tdefer cancel()\n\treturn rt.httpHost.getAndStorePeerMetadata(ctx, rt, rt.server)\n}\n\n// RoundTrip implements http.RoundTripper.\nfunc (rt *streamRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {\n\t// Add the addresses we learned about for this server\n\tif !rt.skipAddAddrs {\n\t\trt.addrsAdded.Do(func() {\n\t\t\tif len(rt.serverAddrs) > 0 {\n\t\t\t\trt.h.Peerstore().AddAddrs(rt.server, rt.serverAddrs, peerstore.TempAddrTTL)\n\t\t\t}\n\t\t\trt.serverAddrs = nil // may as well cleanup\n\t\t})\n\t}\n\n\t// If r.Context() timeout is greater than DefaultNewStreamTimeout\n\t// use DefaultNewStreamTimeout for new stream negotiation.\n\tnewStreamCtx := r.Context()\n\tif deadline, ok := newStreamCtx.Deadline(); !ok || deadline.After(time.Now().Add(DefaultNewStreamTimeout)) {\n\t\tvar cancel context.CancelFunc\n\t\tnewStreamCtx, cancel = context.WithTimeout(context.Background(), DefaultNewStreamTimeout)\n\t\tdefer cancel()\n\t}\n\n\ts, err := rt.h.NewStream(newStreamCtx, rt.server, ProtocolIDForMultistreamSelect)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Write connection: close header to ensure the stream is closed after the response\n\tr.Header.Add(\"connection\", \"close\")\n\n\tgo func() {\n\t\tdefer s.CloseWrite()\n\t\tr.Write(s)\n\t\tif r.Body != nil {\n\t\t\tr.Body.Close()\n\t\t}\n\t}()\n\n\tif deadline, ok := r.Context().Deadline(); ok {\n\t\ts.SetReadDeadline(deadline)\n\t}\n\n\tresp, err := http.ReadResponse(bufio.NewReader(s), r)\n\tif err != nil {\n\t\ts.Close()\n\t\treturn nil, err\n\t}\n\tresp.Body = &streamReadCloser{resp.Body, s}\n\n\tif r.URL.Scheme == \"multiaddr\" {\n\t\t// This was a multiaddr uri, we may need to convert relative URI\n\t\t// references to absolute multiaddr ones so that the next request\n\t\t// knows how to reach the endpoint.\n\t\tlocationHeader := resp.Header.Get(\"Location\")\n\t\tif locationHeader != \"\" {\n\t\t\tu, err := locationHeaderToMultiaddrURI(r.URL, locationHeader)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"failed to convert location header (%s) from request (%s) to multiaddr uri: %w\", locationHeader, r.URL, err)\n\t\t\t}\n\t\t\t// Update the location header to be an absolute multiaddr uri\n\t\t\tresp.Header.Set(\"Location\", u.String())\n\t\t}\n\t}\n\n\tctxWithServerID := context.WithValue(r.Context(), serverPeerIDContextKey{}, rt.server)\n\tresp.Request = resp.Request.WithContext(ctxWithServerID)\n\treturn resp, nil\n}\n\n// locationHeaderToMultiaddrURI takes our original URL and the response's Location header\n// and, if the location header is relative, turns it into an absolute multiaddr uri.\n// Refer to https://www.rfc-editor.org/rfc/rfc3986#section-4.2 for the\n// definition of a Relative Reference.\nfunc locationHeaderToMultiaddrURI(original *url.URL, locationHeader string) (*url.URL, error) {\n\tif locationHeader == \"\" {\n\t\treturn nil, errors.New(\"location header is empty\")\n\t}\n\tif strings.HasPrefix(locationHeader, \"//\") {\n\t\t// This is a network path reference. We don't support these.\n\t\treturn nil, errors.New(\"network path reference not supported\")\n\t}\n\n\tfirstSegment := strings.SplitN(locationHeader, \"/\", 2)[0]\n\tif strings.Contains(firstSegment, \":\") {\n\t\t// This location contains a scheme, so it's an absolute uri.\n\t\treturn url.Parse(locationHeader)\n\t}\n\n\t// It's a relative reference. We need to resolve it against the original URL.\n\tif original.Scheme != \"multiaddr\" {\n\t\treturn nil, errors.New(\"original uri is not a multiaddr\")\n\t}\n\n\t// Parse the original multiaddr\n\toriginalStr := original.RawPath\n\tif originalStr == \"\" {\n\t\toriginalStr = original.Path\n\t}\n\toriginalMa, err := ma.NewMultiaddr(originalStr)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"original uri is not a valid multiaddr: %w\", err)\n\t}\n\n\t// Get the target http path\n\tvar targetHTTPPath string\n\tfor _, c := range originalMa {\n\t\tif c.Protocol().Code == ma.P_HTTP_PATH {\n\t\t\ttargetHTTPPath = string(c.RawValue())\n\t\t\tbreak\n\t\t}\n\t}\n\n\t// Resolve reference from targetURL and relativeURL\n\ttargetURL := url.URL{Path: targetHTTPPath}\n\trelativeURL := url.URL{Path: locationHeader}\n\tresolved := targetURL.ResolveReference(&relativeURL)\n\n\tresolvedHTTPPath := resolved.Path\n\tif len(resolvedHTTPPath) > 0 && resolvedHTTPPath[0] == '/' {\n\t\tresolvedHTTPPath = resolvedHTTPPath[1:] // trim leading slash. It's implied by the http-path component\n\t}\n\n\tresolvedHTTPPathComponent, err := ma.NewComponent(\"http-path\", resolvedHTTPPath)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"relative path is not a valid http-path: %w\", err)\n\t}\n\n\twithoutPath, afterAndIncludingPath := ma.SplitFunc(originalMa, func(c ma.Component) bool {\n\t\treturn c.Protocol().Code == ma.P_HTTP_PATH\n\t})\n\twithNewPath := withoutPath.AppendComponent(resolvedHTTPPathComponent)\n\tif len(afterAndIncludingPath) > 1 {\n\t\t// Include after path since it may include other parts\n\t\twithNewPath = append(withNewPath, afterAndIncludingPath[1:]...)\n\t}\n\treturn url.Parse(\"multiaddr:\" + withNewPath.String())\n}\n\n// roundTripperForSpecificServer is an http.RoundTripper targets a specific server. Still reuses the underlying RoundTripper for the requests.\n// The underlying RoundTripper MUST be an HTTP Transport.\ntype roundTripperForSpecificServer struct {\n\thttp.RoundTripper\n\townRoundtripper  bool\n\thttpHost         *Host\n\tserver           peer.ID\n\ttargetServerAddr string\n\tsni              string\n\tscheme           string\n\tcachedProtos     PeerMeta\n}\n\nfunc (rt *roundTripperForSpecificServer) GetPeerMetadata() (PeerMeta, error) {\n\t// Do we already have the peer's protocol mapping?\n\tif rt.cachedProtos != nil {\n\t\treturn rt.cachedProtos, nil\n\t}\n\n\t// if the underlying roundtripper implements GetPeerMetadata, use that\n\tif g, ok := rt.RoundTripper.(PeerMetadataGetter); ok {\n\t\twk, err := g.GetPeerMetadata()\n\t\tif err == nil {\n\t\t\trt.cachedProtos = wk\n\t\t\treturn wk, nil\n\t\t}\n\t}\n\n\tctx := context.Background()\n\tctx, cancel := context.WithDeadline(ctx, time.Now().Add(WellKnownRequestTimeout))\n\tdefer cancel()\n\twk, err := rt.httpHost.getAndStorePeerMetadata(ctx, rt, rt.server)\n\tif err == nil {\n\t\trt.cachedProtos = wk\n\t\treturn wk, nil\n\t}\n\treturn wk, err\n}\n\n// RoundTrip implements http.RoundTripper.\nfunc (rt *roundTripperForSpecificServer) RoundTrip(r *http.Request) (*http.Response, error) {\n\tif (r.URL.Scheme != \"\" && r.URL.Scheme != rt.scheme) || (r.URL.Host != \"\" && r.URL.Host != rt.targetServerAddr) {\n\t\treturn nil, fmt.Errorf(\"this transport is only for requests to %s://%s\", rt.scheme, rt.targetServerAddr)\n\t}\n\tr.URL.Scheme = rt.scheme\n\tr.URL.Host = rt.targetServerAddr\n\tr.Host = rt.sni\n\treturn rt.RoundTripper.RoundTrip(r)\n}\n\nfunc (rt *roundTripperForSpecificServer) CloseIdleConnections() {\n\tif rt.ownRoundtripper {\n\t\t// Safe to close idle connections, since we own the RoundTripper. We\n\t\t// aren't closing other's idle connections.\n\t\ttype closeIdler interface {\n\t\t\tCloseIdleConnections()\n\t\t}\n\t\tif tr, ok := rt.RoundTripper.(closeIdler); ok {\n\t\t\ttr.CloseIdleConnections()\n\t\t}\n\t}\n\t// No-op, since we don't want users thinking they are closing idle\n\t// connections for this server, when in fact they are closing all idle\n\t// connections\n}\n\n// namespacedRoundTripper is a round tripper that prefixes all requests with a\n// given path prefix. It is used to namespace requests to a specific protocol.\ntype namespacedRoundTripper struct {\n\thttp.RoundTripper\n\tprotocolPrefix    string\n\tprotocolPrefixRaw string\n}\n\nfunc (rt *namespacedRoundTripper) GetPeerMetadata() (PeerMeta, error) {\n\tif g, ok := rt.RoundTripper.(PeerMetadataGetter); ok {\n\t\treturn g.GetPeerMetadata()\n\t}\n\n\treturn nil, fmt.Errorf(\"can not get peer protocol map. Inner roundtripper does not implement GetPeerMetadata\")\n}\n\n// RoundTrip implements http.RoundTripper.\nfunc (rt *namespacedRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {\n\tif !strings.HasPrefix(r.URL.Path, rt.protocolPrefix) {\n\t\tr.URL.Path = rt.protocolPrefix + r.URL.Path\n\t}\n\tif !strings.HasPrefix(r.URL.RawPath, rt.protocolPrefixRaw) {\n\t\tr.URL.RawPath = rt.protocolPrefixRaw + r.URL.Path\n\t}\n\n\treturn rt.RoundTripper.RoundTrip(r)\n}\n\n// NamespaceRoundTripper returns an http.RoundTripper that are scoped to the given protocol on the given server.\nfunc (h *Host) NamespaceRoundTripper(roundtripper http.RoundTripper, p protocol.ID, server peer.ID) (*namespacedRoundTripper, error) {\n\tctx := context.Background()\n\tctx, cancel := context.WithDeadline(ctx, time.Now().Add(WellKnownRequestTimeout))\n\tdefer cancel()\n\tprotos, err := h.getAndStorePeerMetadata(ctx, roundtripper, server)\n\tif err != nil {\n\t\treturn &namespacedRoundTripper{}, err\n\t}\n\n\tv, ok := protos[p]\n\tif !ok {\n\t\treturn &namespacedRoundTripper{}, fmt.Errorf(\"no protocol %s for server %s\", p, server)\n\t}\n\n\tpath := v.Path\n\tif path[len(path)-1] == '/' {\n\t\t// Trim the trailing slash, since it's common to make requests starting with a leading forward slash for the path\n\t\tpath = path[:len(path)-1]\n\t}\n\n\tu, err := url.Parse(path)\n\tif err != nil {\n\t\treturn &namespacedRoundTripper{}, fmt.Errorf(\"invalid path %s for protocol %s for server %s\", v.Path, p, server)\n\t}\n\n\treturn &namespacedRoundTripper{\n\t\tRoundTripper:      roundtripper,\n\t\tprotocolPrefix:    u.Path,\n\t\tprotocolPrefixRaw: u.RawPath,\n\t}, nil\n}\n\n// NamespacedClient returns an http.Client that is scoped to the given protocol\n// on the given server. It creates a new RoundTripper for each call. If you are\n// creating many namespaced clients, consider creating a round tripper directly\n// and namespacing the roundripper yourself, then creating clients from the\n// namespace round tripper.\nfunc (h *Host) NamespacedClient(p protocol.ID, server peer.AddrInfo, opts ...RoundTripperOption) (http.Client, error) {\n\trt, err := h.NewConstrainedRoundTripper(server, opts...)\n\tif err != nil {\n\t\treturn http.Client{}, err\n\t}\n\n\tnrt, err := h.NamespaceRoundTripper(rt, p, server.ID)\n\tif err != nil {\n\t\treturn http.Client{}, err\n\t}\n\n\treturn http.Client{Transport: nrt}, nil\n}\nfunc (h *Host) initDefaultRT() {\n\th.createDefaultClientRoundTripper.Do(func() {\n\t\tif h.DefaultClientRoundTripper == nil {\n\t\t\ttr, ok := http.DefaultTransport.(*http.Transport)\n\t\t\tif ok {\n\t\t\t\th.DefaultClientRoundTripper = tr\n\t\t\t} else {\n\t\t\t\th.DefaultClientRoundTripper = &http.Transport{}\n\t\t\t}\n\t\t}\n\t})\n}\n\n// RoundTrip implements http.RoundTripper for the HTTP Host.\n// This allows you to use the Host as a Transport for an http.Client.\n// See the example for idomatic usage.\nfunc (h *Host) RoundTrip(r *http.Request) (*http.Response, error) {\n\tswitch r.URL.Scheme {\n\tcase \"http\", \"https\":\n\t\th.initDefaultRT()\n\t\tif r.Host == \"\" {\n\t\t\tr.Host = r.URL.Host\n\t\t}\n\t\tif h.ClientPeerIDAuth != nil && h.ClientPeerIDAuth.HasToken(r.Host) {\n\t\t\tserverID, resp, err := h.ClientPeerIDAuth.AuthenticateWithRoundTripper(h.DefaultClientRoundTripper, r)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tctxWithServerID := context.WithValue(r.Context(), serverPeerIDContextKey{}, serverID)\n\t\t\tresp.Request = resp.Request.WithContext(ctxWithServerID)\n\t\t\treturn resp, nil\n\t\t}\n\t\treturn h.DefaultClientRoundTripper.RoundTrip(r)\n\tcase \"multiaddr\":\n\t\tbreak\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported scheme %s\", r.URL.Scheme)\n\t}\n\n\taddr, err := ma.NewMultiaddr(r.URL.String()[len(\"multiaddr:\"):])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\taddr, isHTTP := normalizeHTTPMultiaddr(addr)\n\tparsed, err := parseMultiaddr(addr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif isHTTP {\n\t\tscheme := \"http\"\n\t\tif parsed.useHTTPS {\n\t\t\tscheme = \"https\"\n\t\t}\n\t\tu := url.URL{\n\t\t\tScheme: scheme,\n\t\t\tHost:   parsed.host + \":\" + parsed.port,\n\t\t\tPath:   parsed.httpPath,\n\t\t}\n\t\tr.URL = &u\n\n\t\th.initDefaultRT()\n\t\trt := h.DefaultClientRoundTripper\n\t\tsni := parsed.sni\n\t\tif sni == \"\" {\n\t\t\tsni = parsed.host\n\t\t}\n\n\t\tif sni != parsed.host {\n\t\t\t// We have a different host and SNI (e.g. using an IP address but specifying a SNI)\n\t\t\t// We need to make our own transport to support this.\n\t\t\t//\n\t\t\t// TODO: if we end up using this code path a lot, we could maintain\n\t\t\t// a pool of these transports.  For now though, it's here for\n\t\t\t// completeness, but I don't expect us to hit it often.\n\t\t\trt = rt.Clone()\n\t\t\trt.TLSClientConfig.ServerName = parsed.sni\n\t\t}\n\n\t\tif parsed.peer != \"\" {\n\t\t\t// The peer ID is present. We are making an authenticated request\n\t\t\tif h.ClientPeerIDAuth == nil {\n\t\t\t\treturn nil, fmt.Errorf(\"can not authenticate server. Host.ClientPeerIDAuth field is not set\")\n\t\t\t}\n\n\t\t\tif r.Host == \"\" {\n\t\t\t\t// Missing a host header. Default to what we parsed earlier\n\t\t\t\tr.Host = u.Host\n\t\t\t}\n\n\t\t\tserverID, resp, err := h.ClientPeerIDAuth.AuthenticateWithRoundTripper(rt, r)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tif serverID != parsed.peer {\n\t\t\t\tresp.Body.Close()\n\t\t\t\treturn nil, fmt.Errorf(\"authenticated server ID does not match expected server ID\")\n\t\t\t}\n\n\t\t\tctxWithServerID := context.WithValue(r.Context(), serverPeerIDContextKey{}, serverID)\n\t\t\tresp.Request = resp.Request.WithContext(ctxWithServerID)\n\n\t\t\treturn resp, nil\n\t\t}\n\n\t\treturn rt.RoundTrip(r)\n\t}\n\n\tif h.StreamHost == nil {\n\t\treturn nil, fmt.Errorf(\"can not do HTTP over streams. Missing StreamHost\")\n\t}\n\n\tif parsed.peer == \"\" {\n\t\treturn nil, fmt.Errorf(\"no peer ID in multiaddr\")\n\t}\n\twithoutHTTPPath, _ := ma.SplitFunc(addr, func(c ma.Component) bool {\n\t\treturn c.Protocol().Code == ma.P_HTTP_PATH\n\t})\n\th.StreamHost.Peerstore().AddAddrs(parsed.peer, []ma.Multiaddr{withoutHTTPPath}, peerstore.TempAddrTTL)\n\n\t// Set the Opaque field to the http-path so that the HTTP request only makes\n\t// a reference to that path and not the whole multiaddr uri\n\tr.URL.Opaque = parsed.httpPath\n\tif r.Host == \"\" {\n\t\t// Fill in the host if it's not already set\n\t\tr.Host = parsed.host + \":\" + parsed.port\n\t}\n\tsrt := streamRoundTripper{\n\t\tserver:       parsed.peer,\n\t\tskipAddAddrs: true,\n\t\thttpHost:     h,\n\t\th:            h.StreamHost,\n\t}\n\treturn srt.RoundTrip(r)\n}\n\n// NewConstrainedRoundTripper returns an http.RoundTripper that can fulfill and HTTP\n// request to the given server. It may use an HTTP transport or a stream based\n// transport. It is valid to pass an empty server.ID.\n// If there are multiple addresses for the server, it will pick the best\n// transport (stream vs standard HTTP) using the following rules:\n//   - If PreferHTTPTransport is set, use the HTTP transport.\n//   - If ServerMustAuthenticatePeerID is set, use the stream transport, as the\n//     HTTP transport does not do peer id auth yet.\n//   - If we already have a connection on a stream transport, use that.\n//   - Otherwise, if we have both, use the HTTP transport.\nfunc (h *Host) NewConstrainedRoundTripper(server peer.AddrInfo, opts ...RoundTripperOption) (http.RoundTripper, error) {\n\toptions := roundTripperOpts{}\n\tfor _, o := range opts {\n\t\toptions = o(options)\n\t}\n\n\tif options.serverMustAuthenticatePeerID && server.ID == \"\" {\n\t\treturn nil, fmt.Errorf(\"server must authenticate peer ID, but no peer ID provided\")\n\t}\n\n\thttpAddrs := make([]ma.Multiaddr, 0, 1) // The common case of a single http address\n\tnonHTTPAddrs := make([]ma.Multiaddr, 0, len(server.Addrs))\n\n\tfirstAddrIsHTTP := false\n\n\tfor i, addr := range server.Addrs {\n\t\taddr, isHTTP := normalizeHTTPMultiaddr(addr)\n\t\tif isHTTP {\n\t\t\tif i == 0 {\n\t\t\t\tfirstAddrIsHTTP = true\n\t\t\t}\n\t\t\thttpAddrs = append(httpAddrs, addr)\n\t\t} else {\n\t\t\tnonHTTPAddrs = append(nonHTTPAddrs, addr)\n\t\t}\n\t}\n\n\t// Do we have an existing connection to this peer?\n\texistingStreamConn := false\n\tif server.ID != \"\" && h.StreamHost != nil {\n\t\texistingStreamConn = len(h.StreamHost.Network().ConnsToPeer(server.ID)) > 0\n\t}\n\n\t// Currently the HTTP transport can not authenticate peer IDs.\n\tif !options.serverMustAuthenticatePeerID && len(httpAddrs) > 0 && (options.preferHTTPTransport || (firstAddrIsHTTP && !existingStreamConn)) {\n\t\tparsed, err := parseMultiaddr(httpAddrs[0])\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tscheme := \"http\"\n\t\tif parsed.useHTTPS {\n\t\t\tscheme = \"https\"\n\t\t}\n\n\t\th.initDefaultRT()\n\t\trt := h.DefaultClientRoundTripper\n\t\townRoundtripper := false\n\t\tif parsed.sni != parsed.host {\n\t\t\t// We have a different host and SNI (e.g. using an IP address but specifying a SNI)\n\t\t\t// We need to make our own transport to support this.\n\t\t\trt = rt.Clone()\n\t\t\trt.TLSClientConfig.ServerName = parsed.sni\n\t\t\townRoundtripper = true\n\t\t}\n\n\t\treturn &roundTripperForSpecificServer{\n\t\t\tRoundTripper:     rt,\n\t\t\townRoundtripper:  ownRoundtripper,\n\t\t\thttpHost:         h,\n\t\t\tserver:           server.ID,\n\t\t\ttargetServerAddr: parsed.host + \":\" + parsed.port,\n\t\t\tsni:              parsed.sni,\n\t\t\tscheme:           scheme,\n\t\t}, nil\n\t}\n\n\t// Otherwise use a stream based transport\n\tif h.StreamHost == nil {\n\t\treturn nil, fmt.Errorf(\"can not use the HTTP transport (either no address or PeerID auth is required), and no stream host provided\")\n\t}\n\tif !existingStreamConn {\n\t\tif server.ID == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"can not use the HTTP transport, and no server peer ID provided\")\n\t\t}\n\t}\n\n\treturn &streamRoundTripper{h: h.StreamHost, server: server.ID, serverAddrs: nonHTTPAddrs, httpHost: h}, nil\n}\n\ntype explodedMultiaddr struct {\n\tuseHTTPS bool\n\thost     string\n\tport     string\n\tsni      string\n\thttpPath string\n\tpeer     peer.ID\n}\n\nfunc parseMultiaddr(addr ma.Multiaddr) (explodedMultiaddr, error) {\n\tout := explodedMultiaddr{}\n\tvar err error\n\tma.ForEach(addr, func(c ma.Component) bool {\n\t\tswitch c.Protocol().Code {\n\t\tcase ma.P_IP4, ma.P_IP6, ma.P_DNS, ma.P_DNS4, ma.P_DNS6:\n\t\t\tout.host = c.Value()\n\t\tcase ma.P_TCP, ma.P_UDP:\n\t\t\tout.port = c.Value()\n\t\tcase ma.P_TLS, ma.P_HTTPS:\n\t\t\tout.useHTTPS = true\n\t\tcase ma.P_SNI:\n\t\t\tout.sni = c.Value()\n\t\tcase ma.P_HTTP_PATH:\n\t\t\tout.httpPath, err = url.QueryUnescape(c.Value())\n\t\t\tif err == nil && out.httpPath[0] != '/' {\n\t\t\t\tout.httpPath = \"/\" + out.httpPath\n\t\t\t}\n\t\tcase ma.P_P2P:\n\t\t\tout.peer, err = peer.Decode(c.Value())\n\t\t}\n\n\t\t// stop if there is an error, otherwise iterate over all components in case this is a circuit address\n\t\treturn err == nil\n\t})\n\n\tif out.useHTTPS && out.sni == \"\" {\n\t\tout.sni = out.host\n\t}\n\n\tif out.httpPath == \"\" {\n\t\tout.httpPath = \"/\"\n\t}\n\treturn out, err\n}\n\nvar httpComponent, _ = ma.NewComponent(\"http\", \"\")\nvar tlsComponent, _ = ma.NewComponent(\"tls\", \"\")\n\n// normalizeHTTPMultiaddr converts an https multiaddr to a tls/http one.\n// Returns a bool indicating if the input multiaddr has an http (or https) component.\nfunc normalizeHTTPMultiaddr(addr ma.Multiaddr) (ma.Multiaddr, bool) {\n\tisHTTPMultiaddr := false\n\tbeforeHTTPS, afterIncludingHTTPS := ma.SplitFunc(addr, func(c ma.Component) bool {\n\t\tif c.Protocol().Code == ma.P_HTTP {\n\t\t\tisHTTPMultiaddr = true\n\t\t}\n\n\t\tif c.Protocol().Code == ma.P_HTTPS {\n\t\t\tisHTTPMultiaddr = true\n\t\t\treturn true\n\t\t}\n\t\treturn false\n\t})\n\tif beforeHTTPS == nil || !isHTTPMultiaddr {\n\t\treturn addr, false\n\t}\n\n\tif afterIncludingHTTPS == nil {\n\t\t// No HTTPS component, just return the original\n\t\treturn addr, isHTTPMultiaddr\n\t}\n\n\t_, afterHTTPS := ma.SplitFirst(afterIncludingHTTPS)\n\tif afterHTTPS == nil {\n\t\treturn beforeHTTPS.AppendComponent(tlsComponent, httpComponent), isHTTPMultiaddr\n\t}\n\n\tt := beforeHTTPS.AppendComponent(tlsComponent, httpComponent)\n\tt = append(t, afterHTTPS...)\n\treturn t, isHTTPMultiaddr\n}\n\n// getAndStorePeerMetadata looks up the protocol path in the well-known mapping and\n// returns it. Will only store the peer's protocol mapping if the server ID is\n// provided.\nfunc (h *Host) getAndStorePeerMetadata(ctx context.Context, roundtripper http.RoundTripper, server peer.ID) (PeerMeta, error) {\n\tif h.peerMetadata == nil {\n\t\th.peerMetadata = newPeerMetadataCache()\n\t}\n\tif meta, ok := h.peerMetadata.Get(server); server != \"\" && ok {\n\t\treturn meta, nil\n\t}\n\n\tvar meta PeerMeta\n\tvar err error\n\tif h.EnableCompatibilityWithLegacyWellKnownEndpoint {\n\t\ttype metaAndErr struct {\n\t\t\tm   PeerMeta\n\t\t\terr error\n\t\t}\n\t\tlegacyRespCh := make(chan metaAndErr, 1)\n\t\twellKnownRespCh := make(chan metaAndErr, 1)\n\t\tctx, cancel := context.WithCancel(ctx)\n\t\tgo func() {\n\t\t\tmeta, err := requestPeerMeta(ctx, roundtripper, LegacyWellKnownProtocols)\n\t\t\tlegacyRespCh <- metaAndErr{meta, err}\n\t\t}()\n\t\tgo func() {\n\t\t\tmeta, err := requestPeerMeta(ctx, roundtripper, WellKnownProtocols)\n\t\t\twellKnownRespCh <- metaAndErr{meta, err}\n\t\t}()\n\t\tselect {\n\t\tcase resp := <-legacyRespCh:\n\t\t\tif resp.err != nil {\n\t\t\t\tresp = <-wellKnownRespCh\n\t\t\t}\n\t\t\tmeta, err = resp.m, resp.err\n\t\tcase resp := <-wellKnownRespCh:\n\t\t\tif resp.err != nil {\n\t\t\t\tlegacyResp := <-legacyRespCh\n\t\t\t\tif legacyResp.err != nil {\n\t\t\t\t\t// If both endpoints error, return the error from the well\n\t\t\t\t\t// known resource (not the legacy well known resource)\n\t\t\t\t\tmeta, err = resp.m, resp.err\n\t\t\t\t} else {\n\t\t\t\t\tmeta, err = legacyResp.m, legacyResp.err\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tmeta, err = resp.m, resp.err\n\t\t\t}\n\t\t}\n\t\tcancel()\n\t} else {\n\t\tmeta, err = requestPeerMeta(ctx, roundtripper, WellKnownProtocols)\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif server != \"\" {\n\t\th.peerMetadata.Add(server, meta)\n\t}\n\n\treturn meta, nil\n}\n\nfunc requestPeerMeta(ctx context.Context, roundtripper http.RoundTripper, wellKnownResource string) (PeerMeta, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", wellKnownResource, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treq.Header.Set(\"Accept\", \"application/json\")\n\n\tclient := http.Client{Transport: roundtripper}\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n\n\tmeta := PeerMeta{}\n\terr = json.NewDecoder(&io.LimitedReader{\n\t\tR: resp.Body,\n\t\tN: peerMetadataLimit,\n\t}).Decode(&meta)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn meta, nil\n}\n\n// SetPeerMetadata adds a peer's protocol metadata to the http host. Useful if\n// you have out-of-band knowledge of a peer's protocol mapping.\nfunc (h *Host) SetPeerMetadata(server peer.ID, meta PeerMeta) {\n\tif h.peerMetadata == nil {\n\t\th.peerMetadata = newPeerMetadataCache()\n\t}\n\th.peerMetadata.Add(server, meta)\n}\n\n// AddPeerMetadata merges the given peer's protocol metadata to the http host. Useful if\n// you have out-of-band knowledge of a peer's protocol mapping.\nfunc (h *Host) AddPeerMetadata(server peer.ID, meta PeerMeta) {\n\tif h.peerMetadata == nil {\n\t\th.peerMetadata = newPeerMetadataCache()\n\t}\n\torigMeta, ok := h.peerMetadata.Get(server)\n\tif !ok {\n\t\th.peerMetadata.Add(server, meta)\n\t\treturn\n\t}\n\tmaps.Copy(origMeta, meta)\n\th.peerMetadata.Add(server, origMeta)\n}\n\n// GetPeerMetadata gets a peer's cached protocol metadata from the http host.\nfunc (h *Host) GetPeerMetadata(server peer.ID) (PeerMeta, bool) {\n\tif h.peerMetadata == nil {\n\t\treturn nil, false\n\t}\n\treturn h.peerMetadata.Get(server)\n}\n\n// RemovePeerMetadata removes a peer's protocol metadata from the http host\nfunc (h *Host) RemovePeerMetadata(server peer.ID) {\n\tif h.peerMetadata == nil {\n\t\treturn\n\t}\n\th.peerMetadata.Remove(server)\n}\n\nfunc connectionCloseHeaderMiddleware(next http.Handler) http.Handler {\n\t// Sets connection: close. It's preferable to not reuse streams for HTTP.\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"Connection\", \"close\")\n\t\tnext.ServeHTTP(w, r)\n\t})\n}\n\n// maybeDecorateContextWithAuth decorates the request context with\n// authentication information if serverAuth is provided.\nfunc maybeDecorateContextWithAuthMiddleware(serverAuth *httpauth.ServerPeerIDAuth, next http.Handler) http.Handler {\n\tif next == nil {\n\t\treturn nil\n\t}\n\tif serverAuth == nil {\n\t\treturn next\n\t}\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tif httpauth.HasAuthHeader(r) {\n\t\t\tserverAuth.ServeHTTPWithNextHandler(w, r, func(p peer.ID, w http.ResponseWriter, r *http.Request) {\n\t\t\t\tr = r.WithContext(context.WithValue(r.Context(), clientPeerIDContextKey{}, p))\n\t\t\t\tnext.ServeHTTP(w, r)\n\t\t\t})\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "p2p/http/libp2phttp_test.go",
    "content": "package libp2phttp_test\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/ecdsa\"\n\t\"crypto/elliptic\"\n\t\"crypto/rand\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"crypto/x509/pkix\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"math/big\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/netip\"\n\t\"net/url\"\n\t\"os\"\n\t\"reflect\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p\"\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\thost \"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\tlibp2phttp \"github.com/libp2p/go-libp2p/p2p/http\"\n\thttpauth \"github.com/libp2p/go-libp2p/p2p/http/auth\"\n\thttpping \"github.com/libp2p/go-libp2p/p2p/http/ping\"\n\tlibp2pquic \"github.com/libp2p/go-libp2p/p2p/transport/quic\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestHTTPOverStreams(t *testing.T) {\n\tserverHost, err := libp2p.New(\n\t\tlibp2p.ListenAddrStrings(\"/ip4/127.0.0.1/udp/0/quic-v1\"),\n\t)\n\trequire.NoError(t, err)\n\n\thttpHost := libp2phttp.Host{StreamHost: serverHost}\n\n\thttpHost.SetHTTPHandler(\"/hello\", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Write([]byte(\"hello\"))\n\t}))\n\n\t// Start server\n\tgo httpHost.Serve()\n\tdefer httpHost.Close()\n\n\t// Start client\n\tclientHost, err := libp2p.New(libp2p.NoListenAddrs)\n\trequire.NoError(t, err)\n\tclientHost.Connect(context.Background(), peer.AddrInfo{\n\t\tID:    serverHost.ID(),\n\t\tAddrs: serverHost.Addrs(),\n\t})\n\n\tclientRT, err := (&libp2phttp.Host{StreamHost: clientHost}).NewConstrainedRoundTripper(peer.AddrInfo{ID: serverHost.ID()})\n\trequire.NoError(t, err)\n\n\tclient := &http.Client{Transport: clientRT}\n\n\tresp, err := client.Get(\"/hello\")\n\trequire.NoError(t, err)\n\tdefer resp.Body.Close()\n\n\tbody, err := io.ReadAll(resp.Body)\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, \"hello\", string(body))\n}\n\nfunc TestHTTPOverStreamsSendsConnectionClose(t *testing.T) {\n\tserverHost, err := libp2p.New(\n\t\tlibp2p.ListenAddrStrings(\"/ip4/127.0.0.1/udp/0/quic-v1\"),\n\t)\n\trequire.NoError(t, err)\n\n\thttpHost := libp2phttp.Host{StreamHost: serverHost}\n\n\tconnectionHeaderVal := make(chan string, 1)\n\thttpHost.SetHTTPHandlerAtPath(\"/hello\", \"/\", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"hello\"))\n\t\tconnectionHeaderVal <- r.Header.Get(\"Connection\")\n\t}))\n\n\t// Start server\n\tgo httpHost.Serve()\n\tdefer httpHost.Close()\n\n\t// run client\n\tclientHost, err := libp2p.New(libp2p.NoListenAddrs)\n\trequire.NoError(t, err)\n\tclientHost.Connect(context.Background(), peer.AddrInfo{\n\t\tID:    serverHost.ID(),\n\t\tAddrs: serverHost.Addrs(),\n\t})\n\tclientHttpHost := libp2phttp.Host{StreamHost: clientHost}\n\trt, err := clientHttpHost.NewConstrainedRoundTripper(peer.AddrInfo{ID: serverHost.ID()})\n\trequire.NoError(t, err)\n\tclient := &http.Client{Transport: rt}\n\t_, err = client.Get(\"/\")\n\trequire.NoError(t, err)\n\n\tselect {\n\tcase val := <-connectionHeaderVal:\n\t\trequire.Equal(t, \"close\", strings.ToLower(val))\n\tcase <-time.After(5 * time.Second):\n\t\tt.Fatal(\"timeout waiting for connection header\")\n\t}\n}\n\nfunc TestHTTPOverStreamsContextAndClientTimeout(t *testing.T) {\n\tconst clientTimeout = 200 * time.Millisecond\n\n\tserverHost, err := libp2p.New(\n\t\tlibp2p.ListenAddrStrings(\"/ip4/127.0.0.1/udp/0/quic-v1\"),\n\t)\n\trequire.NoError(t, err)\n\n\thttpHost := libp2phttp.Host{StreamHost: serverHost}\n\thttpHost.SetHTTPHandler(\"/hello/\", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\ttime.Sleep(2 * clientTimeout)\n\t\tw.Write([]byte(\"hello\"))\n\t}))\n\n\t// Start server\n\tgo httpHost.Serve()\n\tdefer httpHost.Close()\n\n\t// Start client\n\tclientHost, err := libp2p.New(libp2p.NoListenAddrs)\n\trequire.NoError(t, err)\n\tclientHost.Connect(context.Background(), peer.AddrInfo{\n\t\tID:    serverHost.ID(),\n\t\tAddrs: serverHost.Addrs(),\n\t})\n\n\tclientRT, err := (&libp2phttp.Host{StreamHost: clientHost}).NewConstrainedRoundTripper(peer.AddrInfo{ID: serverHost.ID()})\n\trequire.NoError(t, err)\n\n\tctx, cancel := context.WithTimeout(context.Background(), clientTimeout)\n\tdefer cancel()\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"/hello/\", nil)\n\trequire.NoError(t, err)\n\n\tclient := &http.Client{Transport: clientRT}\n\t_, err = client.Do(req)\n\trequire.Error(t, err)\n\trequire.ErrorIs(t, err, os.ErrDeadlineExceeded)\n\tt.Log(\"OK, deadline exceeded waiting for response as expected\")\n\n\t// Make another request, this time using http.Client.Timeout.\n\tclientRT, err = (&libp2phttp.Host{StreamHost: clientHost}).NewConstrainedRoundTripper(peer.AddrInfo{ID: serverHost.ID()})\n\trequire.NoError(t, err)\n\n\tclient = &http.Client{\n\t\tTransport: clientRT,\n\t\tTimeout:   clientTimeout,\n\t}\n\n\t_, err = client.Get(\"/hello/\")\n\trequire.Error(t, err)\n\tvar uerr *url.Error\n\trequire.ErrorAs(t, err, &uerr)\n\trequire.True(t, uerr.Timeout())\n\tt.Log(\"OK, timed out waiting for response as expected\")\n}\n\nfunc TestHTTPOverStreamsReturnsConnectionClose(t *testing.T) {\n\tserverHost, err := libp2p.New(\n\t\tlibp2p.ListenAddrStrings(\"/ip4/127.0.0.1/udp/0/quic-v1\"),\n\t)\n\trequire.NoError(t, err)\n\n\thttpHost := libp2phttp.Host{StreamHost: serverHost}\n\n\thttpHost.SetHTTPHandlerAtPath(\"/hello\", \"/\", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Write([]byte(\"hello\"))\n\t}))\n\n\t// Start server\n\tgo httpHost.Serve()\n\tdefer httpHost.Close()\n\n\t// Start client\n\tclientHost, err := libp2p.New(libp2p.NoListenAddrs)\n\trequire.NoError(t, err)\n\tclientHost.Connect(context.Background(), peer.AddrInfo{\n\t\tID:    serverHost.ID(),\n\t\tAddrs: serverHost.Addrs(),\n\t})\n\n\ts, err := clientHost.NewStream(context.Background(), serverHost.ID(), libp2phttp.ProtocolIDForMultistreamSelect)\n\trequire.NoError(t, err)\n\t_, err = s.Write([]byte(\"GET / HTTP/1.1\\r\\nHost: \\r\\n\\r\\n\"))\n\trequire.NoError(t, err)\n\n\tout := make([]byte, 1024)\n\tn, err := s.Read(out)\n\tif err != io.EOF {\n\t\trequire.NoError(t, err)\n\t}\n\n\trequire.Contains(t, strings.ToLower(string(out[:n])), \"connection: close\")\n}\n\nfunc TestRoundTrippers(t *testing.T) {\n\tserverHost, err := libp2p.New(\n\t\tlibp2p.ListenAddrStrings(\"/ip4/127.0.0.1/udp/0/quic-v1\"),\n\t)\n\trequire.NoError(t, err)\n\n\thttpHost := libp2phttp.Host{\n\t\tInsecureAllowHTTP: true,\n\t\tStreamHost:        serverHost,\n\t\tListenAddrs:       []ma.Multiaddr{ma.StringCast(\"/ip4/127.0.0.1/tcp/0/http\")},\n\t}\n\n\thttpHost.SetHTTPHandler(\"/hello\", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Write([]byte(\"hello\"))\n\t}))\n\n\t// Start stream based server\n\tgo httpHost.Serve()\n\tdefer httpHost.Close()\n\n\tserverMultiaddrs := httpHost.Addrs()\n\tserverHTTPAddr := serverMultiaddrs[1]\n\n\ttestCases := []struct {\n\t\tname                     string\n\t\tsetupRoundTripper        func(t *testing.T, clientStreamHost host.Host, clientHTTPHost *libp2phttp.Host) http.RoundTripper\n\t\texpectStreamRoundTripper bool\n\t}{\n\t\t{\n\t\t\tname: \"HTTP preferred\",\n\t\t\tsetupRoundTripper: func(t *testing.T, _ host.Host, clientHTTPHost *libp2phttp.Host) http.RoundTripper {\n\t\t\t\trt, err := clientHTTPHost.NewConstrainedRoundTripper(peer.AddrInfo{\n\t\t\t\t\tID:    serverHost.ID(),\n\t\t\t\t\tAddrs: serverMultiaddrs,\n\t\t\t\t}, libp2phttp.PreferHTTPTransport)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\treturn rt\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"HTTP first\",\n\t\t\tsetupRoundTripper: func(t *testing.T, _ host.Host, clientHTTPHost *libp2phttp.Host) http.RoundTripper {\n\t\t\t\trt, err := clientHTTPHost.NewConstrainedRoundTripper(peer.AddrInfo{\n\t\t\t\t\tID:    serverHost.ID(),\n\t\t\t\t\tAddrs: []ma.Multiaddr{serverHTTPAddr, serverHost.Addrs()[0]},\n\t\t\t\t})\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\treturn rt\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"No HTTP transport\",\n\t\t\tsetupRoundTripper: func(t *testing.T, _ host.Host, clientHTTPHost *libp2phttp.Host) http.RoundTripper {\n\t\t\t\trt, err := clientHTTPHost.NewConstrainedRoundTripper(peer.AddrInfo{\n\t\t\t\t\tID:    serverHost.ID(),\n\t\t\t\t\tAddrs: []ma.Multiaddr{serverHost.Addrs()[0]},\n\t\t\t\t})\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\treturn rt\n\t\t\t},\n\t\t\texpectStreamRoundTripper: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Stream transport first\",\n\t\t\tsetupRoundTripper: func(t *testing.T, _ host.Host, clientHTTPHost *libp2phttp.Host) http.RoundTripper {\n\t\t\t\trt, err := clientHTTPHost.NewConstrainedRoundTripper(peer.AddrInfo{\n\t\t\t\t\tID:    serverHost.ID(),\n\t\t\t\t\tAddrs: []ma.Multiaddr{serverHost.Addrs()[0], serverHTTPAddr},\n\t\t\t\t})\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\treturn rt\n\t\t\t},\n\t\t\texpectStreamRoundTripper: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Existing stream transport connection\",\n\t\t\tsetupRoundTripper: func(t *testing.T, clientStreamHost host.Host, clientHTTPHost *libp2phttp.Host) http.RoundTripper {\n\t\t\t\tclientStreamHost.Connect(context.Background(), peer.AddrInfo{\n\t\t\t\t\tID:    serverHost.ID(),\n\t\t\t\t\tAddrs: serverHost.Addrs(),\n\t\t\t\t})\n\t\t\t\trt, err := clientHTTPHost.NewConstrainedRoundTripper(peer.AddrInfo{\n\t\t\t\t\tID:    serverHost.ID(),\n\t\t\t\t\tAddrs: []ma.Multiaddr{serverHTTPAddr, serverHost.Addrs()[0]},\n\t\t\t\t})\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\treturn rt\n\t\t\t},\n\t\t\texpectStreamRoundTripper: true,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t// Start client\n\t\t\tclientStreamHost, err := libp2p.New(libp2p.NoListenAddrs)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer clientStreamHost.Close()\n\n\t\t\tclientHttpHost := &libp2phttp.Host{StreamHost: clientStreamHost}\n\n\t\t\trt := tc.setupRoundTripper(t, clientStreamHost, clientHttpHost)\n\t\t\tif tc.expectStreamRoundTripper {\n\t\t\t\t// Hack to get the private type of this roundtripper\n\t\t\t\ttyp := reflect.TypeOf(rt).String()\n\t\t\t\trequire.Contains(t, typ, \"streamRoundTripper\", \"Expected stream based round tripper\")\n\t\t\t}\n\n\t\t\tfor _, tc := range []bool{true, false} {\n\t\t\t\tname := \"\"\n\t\t\t\tif tc {\n\t\t\t\t\tname = \"with namespaced roundtripper\"\n\t\t\t\t}\n\t\t\t\tt.Run(name, func(t *testing.T) {\n\t\t\t\t\tvar resp *http.Response\n\t\t\t\t\tvar err error\n\t\t\t\t\tif tc {\n\t\t\t\t\t\tvar h libp2phttp.Host\n\t\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\t\tnrt, err := h.NamespaceRoundTripper(rt, \"/hello\", serverHost.ID())\n\t\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\t\tclient := &http.Client{Transport: nrt}\n\t\t\t\t\t\tresp, err = client.Get(\"/\")\n\t\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tclient := &http.Client{Transport: rt}\n\t\t\t\t\t\tresp, err = client.Get(\"/hello/\")\n\t\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\t}\n\t\t\t\t\tdefer resp.Body.Close()\n\n\t\t\t\t\tbody, err := io.ReadAll(resp.Body)\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\trequire.Equal(t, \"hello\", string(body))\n\t\t\t\t})\n\t\t\t}\n\n\t\t\t// Read the well-known resource\n\t\t\twk, err := rt.(libp2phttp.PeerMetadataGetter).GetPeerMetadata()\n\t\t\trequire.NoError(t, err)\n\n\t\t\texpectedMap := make(libp2phttp.PeerMeta)\n\t\t\texpectedMap[\"/hello\"] = libp2phttp.ProtocolMeta{Path: \"/hello/\"}\n\t\t\trequire.Equal(t, expectedMap, wk)\n\t\t})\n\t}\n}\n\nfunc TestPlainOldHTTPServer(t *testing.T) {\n\tmux := http.NewServeMux()\n\twk := libp2phttp.WellKnownHandler{}\n\tmux.Handle(libp2phttp.WellKnownProtocols, &wk)\n\n\tmux.Handle(\"/ping/\", httpping.Ping{})\n\twk.AddProtocolMeta(httpping.PingProtocolID, libp2phttp.ProtocolMeta{Path: \"/ping/\"})\n\n\tserver := &http.Server{Addr: \"127.0.0.1:0\", Handler: mux}\n\n\tl, err := net.Listen(\"tcp\", server.Addr)\n\trequire.NoError(t, err)\n\n\tgo server.Serve(l)\n\tdefer server.Close()\n\n\t// That's all for the server, now the client:\n\n\tserverAddrParts := strings.Split(l.Addr().String(), \":\")\n\n\ttestCases := []struct {\n\t\tname         string\n\t\tdo           func(*testing.T, *http.Request) (*http.Response, error)\n\t\tgetWellKnown func(*testing.T) (libp2phttp.PeerMeta, error)\n\t}{\n\t\t{\n\t\t\tname: \"using libp2phttp\",\n\t\t\tdo: func(t *testing.T, request *http.Request) (*http.Response, error) {\n\t\t\t\tvar clientHttpHost libp2phttp.Host\n\t\t\t\trt, err := clientHttpHost.NewConstrainedRoundTripper(peer.AddrInfo{Addrs: []ma.Multiaddr{ma.StringCast(\"/ip4/127.0.0.1/tcp/\" + serverAddrParts[1] + \"/http\")}})\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tclient := &http.Client{Transport: rt}\n\t\t\t\treturn client.Do(request)\n\t\t\t},\n\t\t\tgetWellKnown: func(t *testing.T) (libp2phttp.PeerMeta, error) {\n\t\t\t\tvar clientHttpHost libp2phttp.Host\n\t\t\t\trt, err := clientHttpHost.NewConstrainedRoundTripper(peer.AddrInfo{Addrs: []ma.Multiaddr{ma.StringCast(\"/ip4/127.0.0.1/tcp/\" + serverAddrParts[1] + \"/http\")}})\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\treturn rt.(libp2phttp.PeerMetadataGetter).GetPeerMetadata()\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"using stock http client\",\n\t\t\tdo: func(_ *testing.T, request *http.Request) (*http.Response, error) {\n\t\t\t\trequest.URL.Scheme = \"http\"\n\t\t\t\trequest.URL.Host = l.Addr().String()\n\t\t\t\trequest.Host = l.Addr().String()\n\n\t\t\t\tclient := http.Client{}\n\t\t\t\treturn client.Do(request)\n\t\t\t},\n\t\t\tgetWellKnown: func(t *testing.T) (libp2phttp.PeerMeta, error) {\n\t\t\t\tclient := http.Client{}\n\t\t\t\tresp, err := client.Get(\"http://\" + l.Addr().String() + libp2phttp.WellKnownProtocols)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tb, err := io.ReadAll(resp.Body)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tvar out libp2phttp.PeerMeta\n\t\t\t\terr = json.Unmarshal(b, &out)\n\t\t\t\treturn out, err\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tbody := [32]byte{}\n\t\t\t_, err = rand.Reader.Read(body[:])\n\t\t\trequire.NoError(t, err)\n\t\t\treq, err := http.NewRequest(http.MethodPost, \"/ping/\", bytes.NewReader(body[:]))\n\t\t\trequire.NoError(t, err)\n\t\t\tresp, err := tc.do(t, req)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, http.StatusOK, resp.StatusCode)\n\t\t\trBody := [32]byte{}\n\t\t\t_, err = io.ReadFull(resp.Body, rBody[:])\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, body, rBody)\n\n\t\t\t// Make sure we can get the well known resource\n\t\t\tprotoMap, err := tc.getWellKnown(t)\n\t\t\trequire.NoError(t, err)\n\n\t\t\texpectedMap := make(libp2phttp.PeerMeta)\n\t\t\texpectedMap[httpping.PingProtocolID] = libp2phttp.ProtocolMeta{Path: \"/ping/\"}\n\t\t\trequire.Equal(t, expectedMap, protoMap)\n\t\t})\n\t}\n}\n\nfunc TestHostZeroValue(t *testing.T) {\n\tserver := libp2phttp.Host{\n\t\tInsecureAllowHTTP: true,\n\t\tListenAddrs:       []ma.Multiaddr{ma.StringCast(\"/ip4/127.0.0.1/tcp/0/http\")},\n\t}\n\tserver.SetHTTPHandler(\"/hello\", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Write([]byte(\"hello\")) }))\n\tgo func() {\n\t\tserver.Serve()\n\t}()\n\tdefer server.Close()\n\n\tc := libp2phttp.Host{}\n\tclient, err := c.NamespacedClient(\"/hello\", peer.AddrInfo{Addrs: server.Addrs()})\n\trequire.NoError(t, err)\n\tresp, err := client.Get(\"/\")\n\trequire.NoError(t, err)\n\tdefer resp.Body.Close()\n\tbody, err := io.ReadAll(resp.Body)\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, \"hello\", string(body), \"expected response from server\")\n}\n\nfunc TestHTTPS(t *testing.T) {\n\tserver := libp2phttp.Host{\n\t\tTLSConfig:   selfSignedTLSConfig(t),\n\t\tListenAddrs: []ma.Multiaddr{ma.StringCast(\"/ip4/127.0.0.1/tcp/0/https\")},\n\t}\n\tserver.SetHTTPHandler(httpping.PingProtocolID, httpping.Ping{})\n\tgo func() {\n\t\tserver.Serve()\n\t}()\n\tdefer server.Close()\n\n\tclientTransport := http.DefaultTransport.(*http.Transport).Clone()\n\tclientTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}\n\tclient := libp2phttp.Host{\n\t\tDefaultClientRoundTripper: clientTransport,\n\t}\n\thttpClient, err := client.NamespacedClient(httpping.PingProtocolID, peer.AddrInfo{Addrs: server.Addrs()})\n\trequire.NoError(t, err)\n\terr = httpping.SendPing(httpClient)\n\trequire.NoError(t, err)\n}\n\nfunc selfSignedTLSConfig(t *testing.T) *tls.Config {\n\tt.Helper()\n\tpriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)\n\trequire.NoError(t, err)\n\n\tnotBefore := time.Now()\n\tnotAfter := notBefore.Add(365 * 24 * time.Hour)\n\n\tserialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)\n\tserialNumber, err := rand.Int(rand.Reader, serialNumberLimit)\n\trequire.NoError(t, err)\n\n\tcertTemplate := x509.Certificate{\n\t\tSerialNumber: serialNumber,\n\t\tSubject: pkix.Name{\n\t\t\tOrganization: []string{\"Test\"},\n\t\t},\n\t\tNotBefore:             notBefore,\n\t\tNotAfter:              notAfter,\n\t\tKeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,\n\t\tExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},\n\t\tBasicConstraintsValid: true,\n\t}\n\n\tderBytes, err := x509.CreateCertificate(rand.Reader, &certTemplate, &certTemplate, &priv.PublicKey, priv)\n\trequire.NoError(t, err)\n\n\tcert := tls.Certificate{\n\t\tCertificate: [][]byte{derBytes},\n\t\tPrivateKey:  priv,\n\t}\n\n\ttlsConfig := &tls.Config{\n\t\tCertificates: []tls.Certificate{cert},\n\t}\n\treturn tlsConfig\n}\n\nfunc TestCustomServeMux(t *testing.T) {\n\tserveMux := http.NewServeMux()\n\tserveMux.Handle(\"/ping/\", httpping.Ping{})\n\n\tserver := libp2phttp.Host{\n\t\tListenAddrs:       []ma.Multiaddr{ma.StringCast(\"/ip4/127.0.0.1/tcp/0/http\")},\n\t\tServeMux:          serveMux,\n\t\tInsecureAllowHTTP: true,\n\t}\n\tserver.WellKnownHandler.AddProtocolMeta(httpping.PingProtocolID, libp2phttp.ProtocolMeta{Path: \"/ping/\"})\n\tgo func() {\n\t\tserver.Serve()\n\t}()\n\tdefer server.Close()\n\n\taddrs := server.Addrs()\n\trequire.Len(t, addrs, 1)\n\tvar clientHttpHost libp2phttp.Host\n\trt, err := clientHttpHost.NewConstrainedRoundTripper(peer.AddrInfo{Addrs: addrs}, libp2phttp.PreferHTTPTransport)\n\trequire.NoError(t, err)\n\n\tclient := &http.Client{Transport: rt}\n\tbody := [32]byte{}\n\treq, _ := http.NewRequest(http.MethodPost, \"/ping/\", bytes.NewReader(body[:]))\n\tresp, err := client.Do(req)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 200, resp.StatusCode)\n}\n\nfunc TestSetHandlerAtPath(t *testing.T) {\n\thf := func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Header().Add(\"Content-Type\", \"text/plain\")\n\t\tw.Write([]byte(\"Hello World\"))\n\t}\n\ttests := []struct {\n\t\tprefix, rest string\n\t\tpaths200     []string\n\t\tpaths404     []string\n\t}{\n\t\t{\n\t\t\tprefix:   \"/\",\n\t\t\trest:     \"/\",\n\t\t\tpaths200: []string{\"/\", \"/a/\", \"/b\", \"/a/b\"},\n\t\t},\n\t\t{\n\t\t\tprefix:   \"/a\",\n\t\t\trest:     \"/b/\",\n\t\t\tpaths200: []string{\"/a/b/\", \"///a///b/\", \"/a/b/c\"},\n\t\t\t// Not being able to serve /a/b when handling /a/b/ is a rather annoying limitation\n\t\t\t// of http.StripPrefix mechanism. This happens because /a/b is redirected to /b/\n\t\t\t// as the prefix /a is stripped when the redirect happens\n\t\t\tpaths404: []string{\"/a/b\", \"/a\", \"/b\", \"/a/a\"},\n\t\t},\n\t\t{\n\t\t\tprefix:   \"/\",\n\t\t\trest:     \"/b/\",\n\t\t\tpaths200: []string{\"/b\", \"/b/c\", \"/b/c/\"},\n\t\t\tpaths404: []string{\"/\", \"/a/b\"},\n\t\t},\n\t}\n\tfor i, tc := range tests {\n\t\tt.Run(fmt.Sprintf(\"%d\", i), func(t *testing.T) {\n\t\t\tnestedMx := http.NewServeMux()\n\t\t\tnestedMx.HandleFunc(tc.rest, hf)\n\t\t\tserver := libp2phttp.Host{\n\t\t\t\tListenAddrs:       []ma.Multiaddr{ma.StringCast(\"/ip4/127.0.0.1/tcp/0/http\")},\n\t\t\t\tInsecureAllowHTTP: true,\n\t\t\t}\n\t\t\tserver.SetHTTPHandlerAtPath(\"test\", tc.prefix, nestedMx)\n\t\t\tgo func() {\n\t\t\t\tserver.Serve()\n\t\t\t}()\n\t\t\tdefer server.Close()\n\t\t\taddrs := server.Addrs()\n\t\t\trequire.Len(t, addrs, 1)\n\t\t\tport, err := addrs[0].ValueForProtocol(ma.P_TCP)\n\t\t\trequire.NoError(t, err)\n\t\t\thttpAddr := fmt.Sprintf(\"http://127.0.0.1:%s\", port)\n\t\t\tfor _, p := range tc.paths200 {\n\t\t\t\tresp, err := http.Get(httpAddr + p)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, 200, resp.StatusCode, \"path:%s\", p)\n\t\t\t\tresp.Body.Close()\n\t\t\t}\n\t\t\tfor _, p := range tc.paths404 {\n\t\t\t\tresp, _ := http.Get(httpAddr + p)\n\t\t\t\trequire.Equal(t, 404, resp.StatusCode, \"path:%s\", p)\n\t\t\t\tresp.Body.Close()\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestServerLegacyWellKnownResource(t *testing.T) {\n\tmkHTTPServer := func(wellKnown string) ma.Multiaddr {\n\t\tmux := http.NewServeMux()\n\t\twk := libp2phttp.WellKnownHandler{}\n\t\tmux.Handle(wellKnown, &wk)\n\n\t\tmux.Handle(\"/ping/\", httpping.Ping{})\n\t\twk.AddProtocolMeta(httpping.PingProtocolID, libp2phttp.ProtocolMeta{Path: \"/ping/\"})\n\n\t\tserver := &http.Server{Addr: \"127.0.0.1:0\", Handler: mux}\n\n\t\tl, err := net.Listen(\"tcp\", server.Addr)\n\t\trequire.NoError(t, err)\n\n\t\tgo server.Serve(l)\n\t\tt.Cleanup(func() { server.Close() })\n\t\taddrPort, err := netip.ParseAddrPort(l.Addr().String())\n\t\trequire.NoError(t, err)\n\t\treturn ma.StringCast(fmt.Sprintf(\"/ip4/%s/tcp/%d/http\", addrPort.Addr().String(), addrPort.Port()))\n\t}\n\n\tmkServerlibp2phttp := func(enableLegacyWellKnown bool) ma.Multiaddr {\n\t\tserver := libp2phttp.Host{\n\t\t\tEnableCompatibilityWithLegacyWellKnownEndpoint: enableLegacyWellKnown,\n\t\t\tListenAddrs:       []ma.Multiaddr{ma.StringCast(\"/ip4/127.0.0.1/tcp/0/http\")},\n\t\t\tInsecureAllowHTTP: true,\n\t\t}\n\t\tserver.SetHTTPHandler(httpping.PingProtocolID, httpping.Ping{})\n\t\tgo server.Serve()\n\t\tt.Cleanup(func() { server.Close() })\n\t\treturn server.Addrs()[0]\n\t}\n\n\ttype testCase struct {\n\t\tname       string\n\t\tclient     libp2phttp.Host\n\t\tserverAddr ma.Multiaddr\n\t\texpectErr  bool\n\t}\n\n\tvar testCases = []testCase{\n\t\t{\n\t\t\tname:       \"legacy server, client with compat\",\n\t\t\tclient:     libp2phttp.Host{EnableCompatibilityWithLegacyWellKnownEndpoint: true},\n\t\t\tserverAddr: mkHTTPServer(libp2phttp.LegacyWellKnownProtocols),\n\t\t},\n\t\t{\n\t\t\tname:       \"up-to-date http server, client with compat\",\n\t\t\tclient:     libp2phttp.Host{EnableCompatibilityWithLegacyWellKnownEndpoint: true},\n\t\t\tserverAddr: mkHTTPServer(libp2phttp.WellKnownProtocols),\n\t\t},\n\t\t{\n\t\t\tname:       \"up-to-date http server, client without compat\",\n\t\t\tclient:     libp2phttp.Host{},\n\t\t\tserverAddr: mkHTTPServer(libp2phttp.WellKnownProtocols),\n\t\t},\n\t\t{\n\t\t\tname:       \"libp2phttp server with compat, client with compat\",\n\t\t\tclient:     libp2phttp.Host{EnableCompatibilityWithLegacyWellKnownEndpoint: true},\n\t\t\tserverAddr: mkServerlibp2phttp(true),\n\t\t},\n\t\t{\n\t\t\tname:       \"libp2phttp server without compat, client with compat\",\n\t\t\tclient:     libp2phttp.Host{EnableCompatibilityWithLegacyWellKnownEndpoint: true},\n\t\t\tserverAddr: mkServerlibp2phttp(false),\n\t\t},\n\t\t{\n\t\t\tname:       \"libp2phttp server with compat, client without compat\",\n\t\t\tclient:     libp2phttp.Host{},\n\t\t\tserverAddr: mkServerlibp2phttp(true),\n\t\t},\n\t\t{\n\t\t\tname:       \"legacy server, client without compat\",\n\t\t\tclient:     libp2phttp.Host{},\n\t\t\tserverAddr: mkHTTPServer(libp2phttp.LegacyWellKnownProtocols),\n\t\t\texpectErr:  true,\n\t\t},\n\t}\n\n\tfor i := range testCases {\n\t\ttc := &testCases[i] // to not copy the lock in libp2phttp.Host\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif tc.expectErr {\n\t\t\t\t_, err := tc.client.NamespacedClient(httpping.PingProtocolID, peer.AddrInfo{Addrs: []ma.Multiaddr{tc.serverAddr}})\n\t\t\t\trequire.Error(t, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\thttpClient, err := tc.client.NamespacedClient(httpping.PingProtocolID, peer.AddrInfo{Addrs: []ma.Multiaddr{tc.serverAddr}})\n\t\t\trequire.NoError(t, err)\n\n\t\t\terr = httpping.SendPing(httpClient)\n\t\t\trequire.NoError(t, err)\n\t\t})\n\t}\n\n}\n\nfunc TestResponseWriterShouldNotHaveCancelledContext(t *testing.T) {\n\th, err := libp2p.New()\n\trequire.NoError(t, err)\n\tdefer h.Close()\n\thttpHost := libp2phttp.Host{StreamHost: h}\n\tgo httpHost.Serve()\n\tdefer httpHost.Close()\n\n\tcloseNotifyCh := make(chan bool, 1)\n\thttpHost.SetHTTPHandlerAtPath(\"/test\", \"/\", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\t// Legacy code uses this to check if the connection was closed\n\t\t//lint:ignore SA1019 This is a test to assert we do the right thing since Go HTTP stdlib depends on this.\n\t\tch := w.(http.CloseNotifier).CloseNotify()\n\t\tselect {\n\t\tcase <-ch:\n\t\t\tcloseNotifyCh <- true\n\t\tcase <-time.After(100 * time.Millisecond):\n\t\t\tcloseNotifyCh <- false\n\t\t}\n\t\tw.WriteHeader(http.StatusOK)\n\t}))\n\n\tclientH, err := libp2p.New()\n\trequire.NoError(t, err)\n\tdefer clientH.Close()\n\tclientHost := libp2phttp.Host{StreamHost: clientH}\n\n\trt, err := clientHost.NewConstrainedRoundTripper(peer.AddrInfo{ID: h.ID(), Addrs: h.Addrs()})\n\trequire.NoError(t, err)\n\thttpClient := &http.Client{Transport: rt}\n\t_, err = httpClient.Get(\"/\")\n\trequire.NoError(t, err)\n\n\trequire.False(t, <-closeNotifyCh)\n}\n\nfunc TestHTTPHostAsRoundTripper(t *testing.T) {\n\tserverHost, err := libp2p.New(\n\t\tlibp2p.ListenAddrStrings(\"/ip4/127.0.0.1/udp/0/quic-v1\"),\n\t)\n\trequire.NoError(t, err)\n\n\tserverHttpHost := libp2phttp.Host{\n\t\tInsecureAllowHTTP: true,\n\t\tStreamHost:        serverHost,\n\t\tListenAddrs:       []ma.Multiaddr{ma.StringCast(\"/ip4/127.0.0.1/tcp/0/http\")},\n\t}\n\n\tserverHttpHost.SetHTTPHandlerAtPath(\"/hello\", \"/\", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tif r.URL.Path != \"/\" {\n\t\t\tw.WriteHeader(http.StatusNotFound)\n\t\t\treturn\n\t\t}\n\t\tw.Write([]byte(\"hello\"))\n\t}))\n\n\t// Different protocol.ID and mounted at a different path\n\tserverHttpHost.SetHTTPHandlerAtPath(\"/hello-again\", \"/hello2\", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Write([]byte(\"hello\"))\n\t}))\n\n\tgo serverHttpHost.Serve()\n\tdefer serverHttpHost.Close()\n\n\thttpPathSuffix := \"/http-path/hello2\"\n\tvar testCases []string\n\tfor _, a := range serverHttpHost.Addrs() {\n\t\tif _, err := a.ValueForProtocol(ma.P_HTTP); err == nil {\n\t\t\ttestCases = append(testCases, \"multiaddr:\"+a.String())\n\t\t\ttestCases = append(testCases, \"multiaddr:\"+a.String()+httpPathSuffix)\n\t\t\tserverPort, err := a.ValueForProtocol(ma.P_TCP)\n\t\t\trequire.NoError(t, err)\n\t\t\ttestCases = append(testCases, \"http://127.0.0.1:\"+serverPort)\n\t\t} else {\n\t\t\ttestCases = append(testCases, \"multiaddr:\"+a.String()+\"/p2p/\"+serverHost.ID().String())\n\t\t\ttestCases = append(testCases, \"multiaddr:\"+a.String()+\"/p2p/\"+serverHost.ID().String()+httpPathSuffix)\n\t\t}\n\t}\n\n\tclientStreamHost, err := libp2p.New()\n\trequire.NoError(t, err)\n\tdefer clientStreamHost.Close()\n\n\tclientHttpHost := libp2phttp.Host{StreamHost: clientStreamHost}\n\tclient := http.Client{Transport: &clientHttpHost}\n\tfor _, tc := range testCases {\n\t\tt.Run(tc, func(t *testing.T) {\n\t\t\tresp, err := client.Get(tc)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, http.StatusOK, resp.StatusCode)\n\t\t\tdefer resp.Body.Close()\n\t\t\tbody, err := io.ReadAll(resp.Body)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, \"hello\", string(body))\n\t\t})\n\t}\n}\n\nfunc TestHTTPHostAsRoundTripperFailsWhenNoStreamHostPresent(t *testing.T) {\n\tclientHttpHost := libp2phttp.Host{}\n\tclient := http.Client{Transport: &clientHttpHost}\n\n\t_, err := client.Get(\"multiaddr:/ip4/127.0.0.1/udp/1111/quic-v1\")\n\t// Fails because we don't have a stream host available to make the request\n\trequire.Error(t, err)\n\trequire.ErrorContains(t, err, \"Missing StreamHost\")\n}\n\n// TestRedirects tests a client being redirected through multiple HTTP redirects\nfunc TestRedirects(t *testing.T) {\n\tserverHost, err := libp2p.New(libp2p.ListenAddrStrings(\"/ip4/127.0.0.1/udp/0/quic-v1\"))\n\trequire.NoError(t, err)\n\tserverHttpHost := libp2phttp.Host{\n\t\tStreamHost:        serverHost,\n\t\tInsecureAllowHTTP: true,\n\t\tListenAddrs:       []ma.Multiaddr{ma.StringCast(\"/ip4/127.0.0.1/tcp/0/http\")},\n\t}\n\tgo serverHttpHost.Serve()\n\tdefer serverHttpHost.Close()\n\n\tserverHttpHost.SetHTTPHandlerAtPath(\"/redirect-1/0.0.1\", \"/a\", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Header().Set(\"Location\", \"/b/\")\n\t\tw.WriteHeader(http.StatusMovedPermanently)\n\t}))\n\n\tserverHttpHost.SetHTTPHandlerAtPath(\"/redirect-2/0.0.1\", \"/b\", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Header().Set(\"Location\", \"/c/\")\n\t\tw.WriteHeader(http.StatusMovedPermanently)\n\t}))\n\n\tserverHttpHost.SetHTTPHandlerAtPath(\"/redirect-3/0.0.1\", \"/c\", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Header().Set(\"Location\", \"/d/\")\n\t\tw.WriteHeader(http.StatusMovedPermanently)\n\t}))\n\n\tserverHttpHost.SetHTTPHandlerAtPath(\"/redirect-4/0.0.1\", \"/d\", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Write([]byte(\"hello\"))\n\t}))\n\n\tserverHttpHost.SetHTTPHandlerAtPath(\"/redirect-1/0.0.1\", \"/foo/bar/\", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Header().Set(\"Location\", \"../baz/\")\n\t\tw.WriteHeader(http.StatusMovedPermanently)\n\t}))\n\tserverHttpHost.SetHTTPHandlerAtPath(\"/redirect-1/0.0.1\", \"/foo/baz/\", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Write([]byte(\"hello\"))\n\t}))\n\n\tclientStreamHost, err := libp2p.New(libp2p.NoListenAddrs, libp2p.Transport(libp2pquic.NewTransport))\n\trequire.NoError(t, err)\n\tclient := http.Client{Transport: &libp2phttp.Host{StreamHost: clientStreamHost}}\n\n\ttype testCase struct {\n\t\tinitialURI  string\n\t\texpectedURI string\n\t}\n\tvar testCases []testCase\n\tfor _, a := range serverHttpHost.Addrs() {\n\t\tif _, err := a.ValueForProtocol(ma.P_HTTP); err == nil {\n\t\t\tport, err := a.ValueForProtocol(ma.P_TCP)\n\t\t\trequire.NoError(t, err)\n\t\t\tu := fmt.Sprintf(\"multiaddr:%s/http-path/a%%2f\", a)\n\t\t\tf := fmt.Sprintf(\"http://127.0.0.1:%s/d/\", port)\n\t\t\ttestCases = append(testCases, testCase{u, f})\n\n\t\t\tu = fmt.Sprintf(\"multiaddr:%s/http-path/foo%%2Fbar\", a)\n\t\t\tf = fmt.Sprintf(\"http://127.0.0.1:%s/foo/baz/\", port)\n\t\t\ttestCases = append(testCases, testCase{u, f})\n\t\t} else {\n\t\t\tu := fmt.Sprintf(\"multiaddr:%s/p2p/%s/http-path/a%%2f\", a, serverHost.ID())\n\t\t\tf := fmt.Sprintf(\"multiaddr:%s/p2p/%s/http-path/d%%2F\", a, serverHost.ID())\n\t\t\ttestCases = append(testCases, testCase{u, f})\n\n\t\t\tu = fmt.Sprintf(\"multiaddr:%s/p2p/%s/http-path/foo%%2Fbar\", a, serverHost.ID())\n\t\t\tf = fmt.Sprintf(\"multiaddr:%s/p2p/%s/http-path/foo%%2Fbaz%%2F\", a, serverHost.ID())\n\t\t\ttestCases = append(testCases, testCase{u, f})\n\t\t}\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.initialURI, func(t *testing.T) {\n\t\t\tresp, err := client.Get(tc.initialURI)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer resp.Body.Close()\n\t\t\trequire.Equal(t, http.StatusOK, resp.StatusCode)\n\t\t\tbody, err := io.ReadAll(resp.Body)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, \"hello\", string(body))\n\n\t\t\tfinalReqURL := *resp.Request.URL\n\t\t\tfinalReqURL.Opaque = \"\" // Clear the opaque so we can compare the URI\n\t\t\trequire.Equal(t, tc.expectedURI, finalReqURL.String())\n\t\t})\n\t}\n}\n\n// TestMultiaddrURIRedirect tests that we can redirect using a multiaddr URI. We\n// redirect from the http transport to the stream based transport\nfunc TestMultiaddrURIRedirect(t *testing.T) {\n\tserverHost, err := libp2p.New(libp2p.ListenAddrStrings(\"/ip4/127.0.0.1/udp/0/quic-v1\"))\n\trequire.NoError(t, err)\n\tserverHttpHost := libp2phttp.Host{\n\t\tStreamHost:        serverHost,\n\t\tInsecureAllowHTTP: true,\n\t\tListenAddrs:       []ma.Multiaddr{ma.StringCast(\"/ip4/127.0.0.1/tcp/0/http\")},\n\t}\n\tgo serverHttpHost.Serve()\n\tdefer serverHttpHost.Close()\n\n\tvar httpMultiaddr ma.Multiaddr\n\tvar streamMultiaddr ma.Multiaddr\n\tfor _, a := range serverHttpHost.Addrs() {\n\t\tif _, err := a.ValueForProtocol(ma.P_HTTP); err == nil {\n\t\t\thttpMultiaddr = a\n\t\t} else {\n\t\t\tstreamMultiaddr = a\n\t\t}\n\t}\n\trequire.NotNil(t, httpMultiaddr)\n\trequire.NotNil(t, streamMultiaddr)\n\n\t// Redirect to a whole other transport!\n\tserverHttpHost.SetHTTPHandlerAtPath(\"/redirect-1/0.0.1\", \"/a\", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Header().Set(\"Location\", fmt.Sprintf(\"multiaddr:%s/p2p/%s/http-path/b\", streamMultiaddr, serverHost.ID()))\n\t\tw.WriteHeader(http.StatusMovedPermanently)\n\t}))\n\n\tserverHttpHost.SetHTTPHandlerAtPath(\"/redirect-2/0.0.1\", \"/b\", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.WriteHeader(http.StatusOK)\n\t}))\n\n\tclientStreamHost, err := libp2p.New(libp2p.NoListenAddrs, libp2p.Transport(libp2pquic.NewTransport))\n\trequire.NoError(t, err)\n\tclient := http.Client{Transport: &libp2phttp.Host{StreamHost: clientStreamHost}}\n\n\tresp, err := client.Get(fmt.Sprintf(\"multiaddr:%s/http-path/a\", httpMultiaddr))\n\trequire.NoError(t, err)\n\trequire.Equal(t, http.StatusOK, resp.StatusCode)\n\trequire.True(t, strings.HasPrefix(resp.Request.URL.RawPath, streamMultiaddr.String()), \"expected redirect to stream transport\")\n}\n\nfunc TestImpliedHostIsSet(t *testing.T) {\n\tserverHost, err := libp2p.New(libp2p.ListenAddrStrings(\"/ip4/127.0.0.1/udp/0/quic-v1\"))\n\trequire.NoError(t, err)\n\tserverHttpHost := libp2phttp.Host{\n\t\tStreamHost:        serverHost,\n\t\tInsecureAllowHTTP: true,\n\t\tListenAddrs:       []ma.Multiaddr{ma.StringCast(\"/ip4/127.0.0.1/tcp/0/http\")},\n\t}\n\tgo serverHttpHost.Serve()\n\tdefer serverHttpHost.Close()\n\n\tserverHttpHost.SetHTTPHandlerAtPath(\"/hi\", \"/\", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tif strings.HasPrefix(r.Host, \"localhost\") && r.URL.Path == \"/\" {\n\t\t\tw.WriteHeader(http.StatusOK)\n\t\t\treturn\n\t\t}\n\t\tw.WriteHeader(http.StatusNotFound)\n\t}))\n\n\tclientStreamHost, err := libp2p.New(libp2p.NoListenAddrs, libp2p.Transport(libp2pquic.NewTransport))\n\trequire.NoError(t, err)\n\tclient := http.Client{Transport: &libp2phttp.Host{StreamHost: clientStreamHost}}\n\n\ttype testCase struct {\n\t\turi string\n\t}\n\tvar testCases []testCase\n\tfor _, a := range serverHttpHost.Addrs() {\n\t\tif _, err := a.ValueForProtocol(ma.P_HTTP); err == nil {\n\t\t\tport, err := a.ValueForProtocol(ma.P_TCP)\n\t\t\trequire.NoError(t, err)\n\t\t\tu := fmt.Sprintf(\"multiaddr:/dns/localhost/tcp/%s/http\", port)\n\t\t\ttestCases = append(testCases, testCase{u})\n\t\t} else {\n\t\t\tport, err := a.ValueForProtocol(ma.P_UDP)\n\t\t\trequire.NoError(t, err)\n\t\t\tu := fmt.Sprintf(\"multiaddr:/dns/localhost/udp/%s/quic-v1/p2p/%s\", port, serverHost.ID())\n\t\t\ttestCases = append(testCases, testCase{u})\n\t\t}\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.uri, func(t *testing.T) {\n\t\t\tresp, err := client.Get(tc.uri)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer resp.Body.Close()\n\t\t\trequire.Equal(t, http.StatusOK, resp.StatusCode)\n\t\t})\n\t}\n\n}\n\nfunc TestErrServerClosed(t *testing.T) {\n\tserver := libp2phttp.Host{\n\t\tInsecureAllowHTTP: true,\n\t\tListenAddrs:       []ma.Multiaddr{ma.StringCast(\"/ip4/127.0.0.1/tcp/0/http\")},\n\t}\n\n\tdone := make(chan struct{})\n\tgo func() {\n\t\terr := server.Serve()\n\t\tassert.Equal(t, http.ErrServerClosed, err)\n\t\tclose(done)\n\t}()\n\n\tserver.Close()\n\t<-done\n}\n\nfunc TestHTTPOverStreamsGetClientID(t *testing.T) {\n\tserverHost, err := libp2p.New(\n\t\tlibp2p.ListenAddrStrings(\"/ip4/127.0.0.1/udp/0/quic-v1\"),\n\t)\n\trequire.NoError(t, err)\n\n\thttpHost := libp2phttp.Host{StreamHost: serverHost}\n\n\thttpHost.SetHTTPHandler(\"/echo-id\", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tclientID := libp2phttp.ClientPeerID(r)\n\t\tw.Write([]byte(clientID.String()))\n\t}))\n\n\t// Start server\n\tgo httpHost.Serve()\n\tdefer httpHost.Close()\n\n\t// Start client\n\tclientHost, err := libp2p.New(libp2p.NoListenAddrs)\n\trequire.NoError(t, err)\n\tclientHost.Connect(context.Background(), peer.AddrInfo{\n\t\tID:    serverHost.ID(),\n\t\tAddrs: serverHost.Addrs(),\n\t})\n\n\tclient := http.Client{\n\t\tTransport: &libp2phttp.Host{StreamHost: clientHost},\n\t}\n\trequire.NoError(t, err)\n\n\tresp, err := client.Get(\"multiaddr:\" + serverHost.Addrs()[0].String() + \"/p2p/\" + serverHost.ID().String() + \"/http-path/echo-id\")\n\trequire.NoError(t, err)\n\tdefer resp.Body.Close()\n\n\tbody, err := io.ReadAll(resp.Body)\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, clientHost.ID().String(), string(body))\n}\n\nfunc TestAuthenticatedRequest(t *testing.T) {\n\tserverSK, _, err := crypto.GenerateEd25519Key(rand.Reader)\n\trequire.NoError(t, err)\n\tserverID, err := peer.IDFromPrivateKey(serverSK)\n\trequire.NoError(t, err)\n\n\tserverStreamHost, err := libp2p.New(\n\t\tlibp2p.Identity(serverSK),\n\t\tlibp2p.ListenAddrStrings(\"/ip4/127.0.0.1/udp/0/quic-v1\"),\n\t\tlibp2p.Transport(libp2pquic.NewTransport),\n\t)\n\trequire.NoError(t, err)\n\n\tserver := libp2phttp.Host{\n\t\tInsecureAllowHTTP: true,\n\t\tStreamHost:        serverStreamHost,\n\t\tListenAddrs:       []ma.Multiaddr{ma.StringCast(\"/ip4/127.0.0.1/tcp/0/http\")},\n\t\tServerPeerIDAuth: &httpauth.ServerPeerIDAuth{\n\t\t\tTokenTTL: time.Hour,\n\t\t\tPrivKey:  serverSK,\n\t\t\tNoTLS:    true,\n\t\t\tValidHostnameFn: func(hostname string) bool {\n\t\t\t\treturn strings.HasPrefix(hostname, \"127.0.0.1\")\n\t\t\t},\n\t\t},\n\t}\n\tserver.SetHTTPHandler(\"/echo-id\", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tclientID := libp2phttp.ClientPeerID(r)\n\t\tw.Write([]byte(clientID.String()))\n\t}))\n\n\tgo server.Serve()\n\n\tclientSK, _, err := crypto.GenerateEd25519Key(rand.Reader)\n\trequire.NoError(t, err)\n\n\tclientStreamHost, err := libp2p.New(\n\t\tlibp2p.Identity(clientSK),\n\t\tlibp2p.NoListenAddrs,\n\t\tlibp2p.Transport(libp2pquic.NewTransport))\n\trequire.NoError(t, err)\n\n\tclient := &http.Client{\n\t\tTransport: &libp2phttp.Host{\n\t\t\tStreamHost: clientStreamHost,\n\t\t\tClientPeerIDAuth: &httpauth.ClientPeerIDAuth{\n\t\t\t\tTokenTTL: time.Hour,\n\t\t\t\tPrivKey:  clientSK,\n\t\t\t},\n\t\t},\n\t}\n\n\tclientID, err := peer.IDFromPrivateKey(clientSK)\n\trequire.NoError(t, err)\n\n\tfor _, serverAddr := range server.Addrs() {\n\t\t_, tpt := ma.SplitLast(serverAddr)\n\t\tt.Run(tpt.String(), func(t *testing.T) {\n\t\t\turl := fmt.Sprintf(\"multiaddr:%s/p2p/%s/http-path/echo-id\", serverAddr, serverID)\n\t\t\tt.Log(\"Making a GET request to:\", url)\n\t\t\tresp, err := client.Get(url)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tobservedServerID := libp2phttp.ServerPeerID(resp)\n\t\t\trequire.Equal(t, serverID, observedServerID)\n\n\t\t\tbody, err := io.ReadAll(resp.Body)\n\t\t\trequire.NoError(t, err)\n\n\t\t\trequire.Equal(t, clientID.String(), string(body))\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "p2p/http/options.go",
    "content": "package libp2phttp\n\ntype RoundTripperOption func(o roundTripperOpts) roundTripperOpts\n\ntype roundTripperOpts struct {\n\tpreferHTTPTransport          bool\n\tserverMustAuthenticatePeerID bool\n}\n\n// PreferHTTPTransport tells the roundtripper constructor to prefer using an\n// HTTP transport (as opposed to a libp2p stream transport). Useful, for\n// example, if you want to attempt to leverage HTTP caching.\nfunc PreferHTTPTransport(o roundTripperOpts) roundTripperOpts {\n\to.preferHTTPTransport = true\n\treturn o\n}\n\n// ServerMustAuthenticatePeerID tells the roundtripper constructor that we MUST\n// authenticate the Server's PeerID. Note: this currently means we can not use a\n// native HTTP transport (HTTP peer id authentication is not yet implemented: https://github.com/libp2p/specs/pull/564).\nfunc ServerMustAuthenticatePeerID(o roundTripperOpts) roundTripperOpts {\n\to.serverMustAuthenticatePeerID = true\n\treturn o\n}\n"
  },
  {
    "path": "p2p/http/ping/ping.go",
    "content": "package httpping\n\nimport (\n\t\"bytes\"\n\t\"crypto/rand\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n)\n\nconst pingSize = 32\nconst PingProtocolID = \"/http-ping/1\"\n\ntype Ping struct{}\n\nvar _ http.Handler = Ping{}\n\n// ServeHTTP implements http.Handler.\nfunc (Ping) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\tbody := [pingSize]byte{}\n\t_, err := io.ReadFull(r.Body, body[:])\n\tif err != nil {\n\t\tw.WriteHeader(http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tw.Header().Set(\"Content-Type\", \"application/octet-stream\")\n\tw.Header().Set(\"Content-Length\", strconv.Itoa(pingSize))\n\tw.Write(body[:])\n}\n\n// SendPing send an ping request over HTTP. The provided client should be namespaced to the Ping protocol.\nfunc SendPing(client http.Client) error {\n\tbody := [pingSize]byte{}\n\t_, err := io.ReadFull(rand.Reader, body[:])\n\tif err != nil {\n\t\treturn err\n\t}\n\treq, err := http.NewRequest(\"POST\", \"/\", bytes.NewReader(body[:]))\n\treq.Header.Set(\"Content-Type\", \"application/octet-stream\")\n\treq.Header.Set(\"Content-Length\", strconv.Itoa(pingSize))\n\tif err != nil {\n\t\treturn err\n\t}\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n\n\trBody := [pingSize]byte{}\n\t_, err = io.ReadFull(resp.Body, rBody[:])\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !bytes.Equal(body[:], rBody[:]) {\n\t\treturn errors.New(\"ping body mismatch\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "p2p/metricshelper/conn.go",
    "content": "package metricshelper\n\nimport ma \"github.com/multiformats/go-multiaddr\"\n\nvar transports = [...]int{ma.P_CIRCUIT, ma.P_WEBRTC, ma.P_WEBRTC_DIRECT, ma.P_WEBTRANSPORT, ma.P_QUIC, ma.P_QUIC_V1, ma.P_WSS, ma.P_WS, ma.P_TCP}\n\nfunc GetTransport(a ma.Multiaddr) string {\n\tif a == nil {\n\t\treturn \"other\"\n\t}\n\tfor i := len(a) - 1; i >= 0; i-- {\n\t\tp := a[i].Protocol()\n\t\tfor _, t := range transports {\n\t\t\tif p.Code == t {\n\t\t\t\treturn p.Name\n\t\t\t}\n\t\t}\n\t}\n\treturn \"other\"\n}\n\nfunc GetIPVersion(addr ma.Multiaddr) string {\n\tversion := \"unknown\"\n\tif addr == nil {\n\t\treturn version\n\t}\n\tma.ForEach(addr, func(c ma.Component) bool {\n\t\tswitch c.Protocol().Code {\n\t\tcase ma.P_IP4, ma.P_DNS4:\n\t\t\tversion = \"ip4\"\n\t\tcase ma.P_IP6, ma.P_DNS6:\n\t\t\tversion = \"ip6\"\n\t\t}\n\t\treturn false\n\t})\n\treturn version\n}\n"
  },
  {
    "path": "p2p/metricshelper/conn_test.go",
    "content": "package metricshelper\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\nfunc TestGetTransport(t *testing.T) {\n\tcases := []struct {\n\t\taddr   ma.Multiaddr\n\t\tresult string\n\t}{\n\t\t{\n\t\t\taddr:   ma.StringCast(\"/ip4/1.1.1.1/tcp/1\"),\n\t\t\tresult: \"tcp\",\n\t\t},\n\t\t{\n\t\t\taddr:   ma.StringCast(\"/ip4/1.1.1.1/udp/10\"),\n\t\t\tresult: \"other\",\n\t\t},\n\t\t{\n\t\t\taddr:   nil,\n\t\t\tresult: \"other\",\n\t\t},\n\t}\n\tfor i, tc := range cases {\n\t\tt.Run(fmt.Sprintf(\"%d\", i), func(t *testing.T) {\n\t\t\tgot := GetTransport(tc.addr)\n\t\t\tif got != tc.result {\n\t\t\t\tt.Fatalf(\"invalid transport for %s\\ngot:%v\\nwant:%v\", tc.addr, got, tc.result)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestIPVersion(t *testing.T) {\n\tcases := []struct {\n\t\taddr   ma.Multiaddr\n\t\tresult string\n\t}{\n\t\t{\n\t\t\taddr:   ma.StringCast(\"/ip4/1.1.1.1/tcp/1\"),\n\t\t\tresult: \"ip4\",\n\t\t},\n\t\t{\n\t\t\taddr:   ma.StringCast(\"/ip4/1.1.1.1/udp/10\"),\n\t\t\tresult: \"ip4\",\n\t\t},\n\t\t{\n\t\t\taddr:   nil,\n\t\t\tresult: \"unknown\",\n\t\t},\n\t\t{\n\t\t\taddr:   ma.StringCast(\"/dns/hello.world/tcp/10\"),\n\t\t\tresult: \"unknown\",\n\t\t},\n\t}\n\tfor i, tc := range cases {\n\t\tt.Run(fmt.Sprintf(\"%d\", i), func(t *testing.T) {\n\t\t\tgot := GetIPVersion(tc.addr)\n\t\t\tif got != tc.result {\n\t\t\t\tt.Fatalf(\"invalid ip version for %s\\ngot:%v\\nwant:%v\", tc.addr, got, tc.result)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "p2p/metricshelper/dir.go",
    "content": "package metricshelper\n\nimport \"github.com/libp2p/go-libp2p/core/network\"\n\nfunc GetDirection(dir network.Direction) string {\n\tswitch dir {\n\tcase network.DirOutbound:\n\t\treturn \"outbound\"\n\tcase network.DirInbound:\n\t\treturn \"inbound\"\n\tdefault:\n\t\treturn \"unknown\"\n\t}\n}\n"
  },
  {
    "path": "p2p/metricshelper/pool.go",
    "content": "package metricshelper\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n)\n\nconst capacity = 8\n\nvar stringPool = sync.Pool{New: func() any {\n\ts := make([]string, 0, capacity)\n\treturn &s\n}}\n\nfunc GetStringSlice() *[]string {\n\ts := stringPool.Get().(*[]string)\n\t*s = (*s)[:0]\n\treturn s\n}\n\nfunc PutStringSlice(s *[]string) {\n\tif c := cap(*s); c < capacity {\n\t\tpanic(fmt.Sprintf(\"expected a string slice with capacity 8 or greater, got %d\", c))\n\t}\n\tstringPool.Put(s)\n}\n"
  },
  {
    "path": "p2p/metricshelper/pool_test.go",
    "content": "package metricshelper\n\nimport (\n\t\"math/rand\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestStringSlicePool(t *testing.T) {\n\tfor range int(1e5) {\n\t\ts := GetStringSlice()\n\t\trequire.Empty(t, *s)\n\t\trequire.Equal(t, 8, cap(*s))\n\t\t*s = append(*s, \"foo\")\n\t\t*s = append(*s, \"bar\")\n\t\tif rand.Int()%3 == 0 {\n\t\t\tPutStringSlice(s)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "p2p/metricshelper/registerer.go",
    "content": "package metricshelper\n\nimport (\n\t\"errors\"\n\n\t\"github.com/prometheus/client_golang/prometheus\"\n)\n\n// RegisterCollectors registers the collectors with reg ignoring\n// reregistration error and panics on any other error\nfunc RegisterCollectors(reg prometheus.Registerer, collectors ...prometheus.Collector) {\n\tfor _, c := range collectors {\n\t\terr := reg.Register(c)\n\t\tif err != nil {\n\t\t\tif ok := errors.As(err, &prometheus.AlreadyRegisteredError{}); !ok {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "p2p/metricshelper/registerer_test.go",
    "content": "package metricshelper\n\nimport (\n\t\"testing\"\n\n\t\"github.com/prometheus/client_golang/prometheus\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestRegisterCollectors(t *testing.T) {\n\treg := prometheus.NewRegistry()\n\tc1 := prometheus.NewCounter(\n\t\tprometheus.CounterOpts{\n\t\t\tName: \"counter\",\n\t\t},\n\t)\n\tc2 := prometheus.NewGauge(\n\t\tprometheus.GaugeOpts{\n\t\t\tNamespace: \"test\",\n\t\t\tName:      \"gauge\",\n\t\t},\n\t)\n\t// c3 == c1\n\tc3 := prometheus.NewCounter(\n\t\tprometheus.CounterOpts{\n\t\t\tName: \"counter\",\n\t\t},\n\t)\n\trequire.NotPanics(t, func() { RegisterCollectors(reg, c1, c2) })\n\trequire.NotPanics(t, func() { RegisterCollectors(reg, c3) }, \"should not panic on duplicate registration\")\n}\n"
  },
  {
    "path": "p2p/muxer/testsuite/mux.go",
    "content": "package mux\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\tcrand \"crypto/rand\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\tmrand \"math/rand\"\n\t\"net\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"runtime/debug\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p-testing/ci\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nvar randomness []byte\nvar Subtests map[string]TransportTest\n\nfunc init() {\n\t// read 1MB of randomness\n\trandomness = make([]byte, 1<<20)\n\tif _, err := crand.Read(randomness); err != nil {\n\t\tpanic(err)\n\t}\n\n\tSubtests = make(map[string]TransportTest)\n\tfor _, f := range subtests {\n\t\tSubtests[getFunctionName(f)] = f\n\t}\n}\n\nfunc getFunctionName(i any) string {\n\treturn runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()\n}\n\ntype peerScope struct {\n\tmx     sync.Mutex\n\tmemory int\n}\n\nfunc (p *peerScope) ReserveMemory(size int, _ uint8) error {\n\tp.mx.Lock()\n\tp.memory += size\n\tp.mx.Unlock()\n\treturn nil\n}\n\nfunc (p *peerScope) ReleaseMemory(size int) {\n\tp.mx.Lock()\n\tdefer p.mx.Unlock()\n\tif p.memory < size {\n\t\tpanic(fmt.Sprintf(\"tried to release too much memory: %d (current: %d)\", size, p.memory))\n\t}\n\tp.memory -= size\n}\n\n// Check checks that we don't have any more reserved memory.\nfunc (p *peerScope) Check(t *testing.T) {\n\tp.mx.Lock()\n\tdefer p.mx.Unlock()\n\trequire.Zero(t, p.memory, \"expected all reserved memory to have been released\")\n}\n\ntype peerScopeSpan struct {\n\tpeerScope\n}\n\nfunc (p *peerScopeSpan) Done() {\n\tp.mx.Lock()\n\tdefer p.mx.Unlock()\n\tp.memory = 0\n}\n\nfunc (p *peerScope) Stat() network.ScopeStat                       { return network.ScopeStat{} }\nfunc (p *peerScope) BeginSpan() (network.ResourceScopeSpan, error) { return &peerScopeSpan{}, nil }\nfunc (p *peerScope) Peer() peer.ID                                 { panic(\"implement me\") }\n\nvar _ network.PeerScope = &peerScope{}\n\ntype Options struct {\n\ttr        network.Multiplexer\n\tconnNum   int\n\tstreamNum int\n\tmsgNum    int\n\tmsgMin    int\n\tmsgMax    int\n}\n\nfunc randBuf(size int) []byte {\n\tn := len(randomness) - size\n\tif size < 1 {\n\t\tpanic(fmt.Errorf(\"requested too large buffer (%d). max is %d\", size, len(randomness)))\n\t}\n\n\tstart := mrand.Intn(n)\n\treturn randomness[start : start+size]\n}\n\nfunc checkErr(t *testing.T, err error) {\n\tif err != nil {\n\t\tdebug.PrintStack()\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc echoStream(s network.MuxedStream) {\n\tdefer s.Close()\n\tio.Copy(s, s) // echo everything\n}\n\nfunc GoServe(t *testing.T, tr network.Multiplexer, l net.Listener) (done func()) {\n\tclosed := make(chan struct{}, 1)\n\n\tgo func() {\n\t\tfor {\n\t\t\tc1, err := l.Accept()\n\t\t\tif err != nil {\n\t\t\t\tselect {\n\t\t\t\tcase <-closed:\n\t\t\t\t\treturn // closed naturally.\n\t\t\t\tdefault:\n\t\t\t\t\tcheckErr(t, err)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tsc1, err := tr.NewConn(c1, true, nil)\n\t\t\tcheckErr(t, err)\n\t\t\tgo func() {\n\t\t\t\tfor {\n\t\t\t\t\tstr, err := sc1.AcceptStream()\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t\tgo echoStream(str)\n\t\t\t\t}\n\t\t\t}()\n\t\t}\n\t}()\n\n\treturn func() {\n\t\tclosed <- struct{}{}\n\t}\n}\n\nfunc SubtestSimpleWrite(t *testing.T, tr network.Multiplexer) {\n\tl, err := net.Listen(\"tcp\", \"localhost:0\")\n\tcheckErr(t, err)\n\tdone := GoServe(t, tr, l)\n\tdefer done()\n\n\tnc1, err := net.Dial(\"tcp\", l.Addr().String())\n\tcheckErr(t, err)\n\tdefer nc1.Close()\n\n\tscope := &peerScope{}\n\tc1, err := tr.NewConn(nc1, false, scope)\n\tcheckErr(t, err)\n\tdefer func() {\n\t\tc1.Close()\n\t\tscope.Check(t)\n\t}()\n\n\t// serve the outgoing conn, because some muxers assume\n\t// that we _always_ call serve. (this is an error?)\n\tgo c1.AcceptStream()\n\n\ts1, err := c1.OpenStream(context.Background())\n\tcheckErr(t, err)\n\tdefer s1.Close()\n\n\tbuf1 := randBuf(4096)\n\t_, err = s1.Write(buf1)\n\tcheckErr(t, err)\n\n\tbuf2 := make([]byte, len(buf1))\n\t_, err = io.ReadFull(s1, buf2)\n\tcheckErr(t, err)\n\n\trequire.Equal(t, buf1, buf2)\n}\n\nfunc SubtestStress(t *testing.T, opt Options) {\n\tmsgsize := 1 << 11\n\terrs := make(chan error) // dont block anything.\n\n\trateLimitN := 5000 // max of 5k funcs, because -race has 8k max.\n\trateLimitChan := make(chan struct{}, rateLimitN)\n\tfor range rateLimitN {\n\t\trateLimitChan <- struct{}{}\n\t}\n\n\trateLimit := func(f func()) {\n\t\t<-rateLimitChan\n\t\tf()\n\t\trateLimitChan <- struct{}{}\n\t}\n\n\twriteStream := func(s network.MuxedStream, bufs chan<- []byte) {\n\t\tfor i := 0; i < opt.msgNum; i++ {\n\t\t\tbuf := randBuf(msgsize)\n\t\t\tbufs <- buf\n\t\t\tif _, err := s.Write(buf); err != nil {\n\t\t\t\terrs <- fmt.Errorf(\"s.Write(buf): %s\", err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t}\n\n\treadStream := func(s network.MuxedStream, bufs <-chan []byte) {\n\t\tbuf2 := make([]byte, msgsize)\n\t\tfor buf1 := range bufs {\n\t\t\tif _, err := io.ReadFull(s, buf2); err != nil {\n\t\t\t\terrs <- fmt.Errorf(\"io.ReadFull(s, buf2): %s\", err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif !bytes.Equal(buf1, buf2) {\n\t\t\t\terrs <- fmt.Errorf(\"buffers not equal (%x != %x)\", buf1[:3], buf2[:3])\n\t\t\t}\n\t\t}\n\t}\n\n\topenStreamAndRW := func(c network.MuxedConn) {\n\t\ts, err := c.OpenStream(context.Background())\n\t\tif err != nil {\n\t\t\terrs <- fmt.Errorf(\"failed to create NewStream: %s\", err)\n\t\t\treturn\n\t\t}\n\n\t\tbufs := make(chan []byte, opt.msgNum)\n\t\tgo func() {\n\t\t\twriteStream(s, bufs)\n\t\t\tclose(bufs)\n\t\t}()\n\n\t\treadStream(s, bufs)\n\t\ts.Close()\n\t}\n\n\topenConnAndRW := func() {\n\t\tl, err := net.Listen(\"tcp\", \"localhost:0\")\n\t\tcheckErr(t, err)\n\t\tdone := GoServe(t, opt.tr, l)\n\t\tdefer done()\n\n\t\tnla := l.Addr()\n\t\tnc, err := net.Dial(nla.Network(), nla.String())\n\t\tcheckErr(t, err)\n\t\tif err != nil {\n\t\t\tt.Fatal(fmt.Errorf(\"net.Dial(%s, %s): %s\", nla.Network(), nla.String(), err))\n\t\t\treturn\n\t\t}\n\n\t\tscope := &peerScope{}\n\t\tc, err := opt.tr.NewConn(nc, false, scope)\n\t\tif err != nil {\n\t\t\tt.Fatal(fmt.Errorf(\"a.AddConn(%s <--> %s): %s\", nc.LocalAddr(), nc.RemoteAddr(), err))\n\t\t\treturn\n\t\t}\n\n\t\t// serve the outgoing conn, because some muxers assume\n\t\t// that we _always_ call serve. (this is an error?)\n\t\tgo func() {\n\t\t\tfor {\n\t\t\t\tstr, err := c.AcceptStream()\n\t\t\t\tif err != nil {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tgo echoStream(str)\n\t\t\t}\n\t\t}()\n\n\t\tvar wg sync.WaitGroup\n\t\tfor i := 0; i < opt.streamNum; i++ {\n\t\t\twg.Add(1)\n\t\t\tgo rateLimit(func() {\n\t\t\t\tdefer wg.Done()\n\t\t\t\topenStreamAndRW(c)\n\t\t\t})\n\t\t}\n\t\twg.Wait()\n\t\tc.Close()\n\t\tscope.Check(t)\n\t}\n\n\topenConnsAndRW := func() {\n\t\tvar wg sync.WaitGroup\n\t\tfor i := 0; i < opt.connNum; i++ {\n\t\t\twg.Add(1)\n\t\t\tgo rateLimit(func() {\n\t\t\t\tdefer wg.Done()\n\t\t\t\topenConnAndRW()\n\t\t\t})\n\t\t}\n\t\twg.Wait()\n\t}\n\n\tgo func() {\n\t\topenConnsAndRW()\n\t\tclose(errs) // done\n\t}()\n\n\tfor err := range errs {\n\t\tt.Error(err)\n\t}\n\n}\n\nfunc tcpPipe(t *testing.T) (net.Conn, net.Conn) {\n\tlist, err := net.Listen(\"tcp\", \"0.0.0.0:0\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcon1, err := net.Dial(\"tcp\", list.Addr().String())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcon2, err := list.Accept()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn con1, con2\n}\n\nfunc SubtestStreamOpenStress(t *testing.T, tr network.Multiplexer) {\n\twg := new(sync.WaitGroup)\n\n\ta, b := tcpPipe(t)\n\tdefer a.Close()\n\tdefer b.Close()\n\n\tdefer wg.Wait()\n\n\twg.Add(1)\n\tcount := 10000\n\tworkers := 5\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tmuxa, err := tr.NewConn(a, true, nil)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\treturn\n\t\t}\n\t\tstress := func() {\n\t\t\tdefer wg.Done()\n\t\t\tfor range count {\n\t\t\t\ts, err := muxa.OpenStream(context.Background())\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Error(err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\terr = s.CloseWrite()\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Error(err)\n\t\t\t\t}\n\t\t\t\tn, err := s.Read([]byte{0})\n\t\t\t\tif n != 0 {\n\t\t\t\t\tt.Error(\"expected to read no bytes\")\n\t\t\t\t}\n\t\t\t\tif err != io.EOF {\n\t\t\t\t\tt.Errorf(\"expected an EOF, got %s\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor range workers {\n\t\t\twg.Add(1)\n\t\t\tgo stress()\n\t\t}\n\t}()\n\n\tscope := &peerScope{}\n\tmuxb, err := tr.NewConn(b, false, scope)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer func() {\n\t\tmuxb.Close()\n\t\tscope.Check(t)\n\t}()\n\n\ttime.Sleep(time.Millisecond * 50)\n\n\twg.Add(1)\n\trecv := make(chan struct{}, count*workers)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tfor i := 0; i < count*workers; i++ {\n\t\t\tstr, err := muxb.AcceptStream()\n\t\t\tif err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\twg.Add(1)\n\t\t\tgo func() {\n\t\t\t\tdefer wg.Done()\n\t\t\t\tstr.Close()\n\t\t\t\tselect {\n\t\t\t\tcase recv <- struct{}{}:\n\t\t\t\tdefault:\n\t\t\t\t\tt.Error(\"too many stream\")\n\t\t\t\t}\n\t\t\t}()\n\t\t}\n\t}()\n\n\ttimeout := time.Second * 10\n\tif ci.IsRunning() {\n\t\ttimeout *= 10\n\t}\n\n\tlimit := time.After(timeout)\n\tfor i := 0; i < count*workers; i++ {\n\t\tselect {\n\t\tcase <-recv:\n\t\tcase <-limit:\n\t\t\tt.Fatal(\"timed out receiving streams\")\n\t\t}\n\t}\n\n\twg.Wait()\n}\n\nfunc SubtestStreamReset(t *testing.T, tr network.Multiplexer) {\n\twg := new(sync.WaitGroup)\n\tdefer wg.Wait()\n\n\ta, b := tcpPipe(t)\n\tdefer a.Close()\n\tdefer b.Close()\n\n\twg.Add(1)\n\tscopea := &peerScope{}\n\tmuxa, err := tr.NewConn(a, true, scopea)\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\tdefer func() {\n\t\tmuxa.Close()\n\t\tscopea.Check(t)\n\t}()\n\n\tgo func() {\n\t\tdefer wg.Done()\n\t\ts, err := muxa.OpenStream(context.Background())\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\treturn\n\t\t}\n\t\ttime.Sleep(time.Millisecond * 50)\n\n\t\t_, err = s.Write([]byte(\"foo\"))\n\t\tif !errors.Is(err, network.ErrReset) {\n\t\t\tt.Error(\"should have been stream reset\")\n\t\t}\n\t\ts.Close()\n\t}()\n\n\tscopeb := &peerScope{}\n\tmuxb, err := tr.NewConn(b, false, scopeb)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer func() {\n\t\tmuxb.Close()\n\t\tscopeb.Check(t)\n\t}()\n\n\tstr, err := muxb.AcceptStream()\n\tcheckErr(t, err)\n\tstr.Reset()\n\n\twg.Wait()\n}\n\n// check that Close also closes the underlying net.Conn\nfunc SubtestWriteAfterClose(t *testing.T, tr network.Multiplexer) {\n\ta, b := tcpPipe(t)\n\n\tscopea := &peerScope{}\n\tmuxa, err := tr.NewConn(a, true, scopea)\n\tcheckErr(t, err)\n\n\tscopeb := &peerScope{}\n\tmuxb, err := tr.NewConn(b, false, scopeb)\n\tcheckErr(t, err)\n\n\tcheckErr(t, muxa.Close())\n\tscopea.Check(t)\n\tcheckErr(t, muxb.Close())\n\tscopeb.Check(t)\n\n\t// make sure the underlying net.Conn was closed\n\tif _, err := a.Write([]byte(\"foobar\")); err == nil || !strings.Contains(err.Error(), \"use of closed network connection\") {\n\t\tt.Fatal(\"write should have failed\")\n\t}\n\tif _, err := b.Write([]byte(\"foobar\")); err == nil || !strings.Contains(err.Error(), \"use of closed network connection\") {\n\t\tt.Fatal(\"write should have failed\")\n\t}\n}\n\nfunc SubtestStreamLeftOpen(t *testing.T, tr network.Multiplexer) {\n\ta, b := tcpPipe(t)\n\n\tconst numStreams = 10\n\tconst dataLen = 50 * 1024\n\n\tscopea := &peerScope{}\n\tmuxa, err := tr.NewConn(a, true, scopea)\n\tcheckErr(t, err)\n\n\tscopeb := &peerScope{}\n\tmuxb, err := tr.NewConn(b, false, scopeb)\n\tcheckErr(t, err)\n\n\tvar wg sync.WaitGroup\n\twg.Add(1 + numStreams)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tfor range numStreams {\n\t\t\tstra, err := muxa.OpenStream(context.Background())\n\t\t\tcheckErr(t, err)\n\t\t\tgo func() {\n\t\t\t\tdefer wg.Done()\n\t\t\t\t_, err = stra.Write(randBuf(dataLen))\n\t\t\t\tcheckErr(t, err)\n\t\t\t\t// do NOT close or reset the stream\n\t\t\t}()\n\t\t}\n\t}()\n\n\twg.Add(1 + numStreams)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tfor range numStreams {\n\t\t\tstr, err := muxb.AcceptStream()\n\t\t\tcheckErr(t, err)\n\t\t\tgo func() {\n\t\t\t\tdefer wg.Done()\n\t\t\t\t_, err = io.ReadFull(str, make([]byte, dataLen))\n\t\t\t\tcheckErr(t, err)\n\t\t\t}()\n\t\t}\n\t}()\n\n\t// Now we have a bunch of open streams.\n\t// Make sure that their memory is returned when we close the connection.\n\twg.Wait()\n\n\tmuxa.Close()\n\tscopea.Check(t)\n\tmuxb.Close()\n\tscopeb.Check(t)\n}\n\nfunc SubtestStress1Conn1Stream1Msg(t *testing.T, tr network.Multiplexer) {\n\tSubtestStress(t, Options{\n\t\ttr:        tr,\n\t\tconnNum:   1,\n\t\tstreamNum: 1,\n\t\tmsgNum:    1,\n\t\tmsgMax:    100,\n\t\tmsgMin:    100,\n\t})\n}\n\nfunc SubtestStress1Conn1Stream100Msg(t *testing.T, tr network.Multiplexer) {\n\tSubtestStress(t, Options{\n\t\ttr:        tr,\n\t\tconnNum:   1,\n\t\tstreamNum: 1,\n\t\tmsgNum:    100,\n\t\tmsgMax:    100,\n\t\tmsgMin:    100,\n\t})\n}\n\nfunc SubtestStress1Conn100Stream100Msg(t *testing.T, tr network.Multiplexer) {\n\tSubtestStress(t, Options{\n\t\ttr:        tr,\n\t\tconnNum:   1,\n\t\tstreamNum: 100,\n\t\tmsgNum:    100,\n\t\tmsgMax:    100,\n\t\tmsgMin:    100,\n\t})\n}\n\nfunc SubtestStress10Conn10Stream50Msg(t *testing.T, tr network.Multiplexer) {\n\tSubtestStress(t, Options{\n\t\ttr:        tr,\n\t\tconnNum:   10,\n\t\tstreamNum: 10,\n\t\tmsgNum:    50,\n\t\tmsgMax:    100,\n\t\tmsgMin:    100,\n\t})\n}\n\nfunc SubtestStress1Conn1000Stream10Msg(t *testing.T, tr network.Multiplexer) {\n\tSubtestStress(t, Options{\n\t\ttr:        tr,\n\t\tconnNum:   1,\n\t\tstreamNum: 1000,\n\t\tmsgNum:    10,\n\t\tmsgMax:    100,\n\t\tmsgMin:    100,\n\t})\n}\n\nfunc SubtestStress1Conn100Stream100Msg10MB(t *testing.T, tr network.Multiplexer) {\n\tSubtestStress(t, Options{\n\t\ttr:        tr,\n\t\tconnNum:   1,\n\t\tstreamNum: 100,\n\t\tmsgNum:    100,\n\t\tmsgMax:    10000,\n\t\tmsgMin:    1000,\n\t})\n}\n\n// Subtests are all the subtests run by SubtestAll\nvar subtests = []TransportTest{\n\tSubtestSimpleWrite,\n\tSubtestWriteAfterClose,\n\tSubtestStress1Conn1Stream1Msg,\n\tSubtestStress1Conn1Stream100Msg,\n\tSubtestStress1Conn100Stream100Msg,\n\tSubtestStress10Conn10Stream50Msg,\n\tSubtestStress1Conn1000Stream10Msg,\n\tSubtestStress1Conn100Stream100Msg10MB,\n\tSubtestStreamOpenStress,\n\tSubtestStreamReset,\n\tSubtestStreamLeftOpen,\n}\n\n// SubtestAll runs all the stream multiplexer tests against the target\n// transport.\nfunc SubtestAll(t *testing.T, tr network.Multiplexer) {\n\tfor name, f := range Subtests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tf(t, tr)\n\t\t})\n\t}\n}\n\n// TransportTest is a stream multiplex transport test case\ntype TransportTest func(t *testing.T, tr network.Multiplexer)\n"
  },
  {
    "path": "p2p/muxer/yamux/conn.go",
    "content": "package yamux\n\nimport (\n\t\"context\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\n\t\"github.com/libp2p/go-yamux/v5\"\n)\n\n// conn implements mux.MuxedConn over yamux.Session.\ntype conn yamux.Session\n\nvar _ network.MuxedConn = &conn{}\n\nfunc (c *conn) As(target any) bool {\n\tif t, ok := target.(**yamux.Session); ok {\n\t\t*t = (*yamux.Session)(c)\n\t\treturn true\n\t}\n\treturn false\n}\n\n// NewMuxedConn constructs a new MuxedConn from a yamux.Session.\nfunc NewMuxedConn(m *yamux.Session) network.MuxedConn {\n\treturn (*conn)(m)\n}\n\n// Close closes underlying yamux\nfunc (c *conn) Close() error {\n\treturn c.yamux().Close()\n}\n\nfunc (c *conn) CloseWithError(errCode network.ConnErrorCode) error {\n\treturn c.yamux().CloseWithError(uint32(errCode))\n}\n\n// IsClosed checks if yamux.Session is in closed state.\nfunc (c *conn) IsClosed() bool {\n\treturn c.yamux().IsClosed()\n}\n\n// OpenStream creates a new stream.\nfunc (c *conn) OpenStream(ctx context.Context) (network.MuxedStream, error) {\n\ts, err := c.yamux().OpenStream(ctx)\n\tif err != nil {\n\t\treturn nil, parseError(err)\n\t}\n\n\treturn (*stream)(s), nil\n}\n\n// AcceptStream accepts a stream opened by the other side.\nfunc (c *conn) AcceptStream() (network.MuxedStream, error) {\n\ts, err := c.yamux().AcceptStream()\n\treturn (*stream)(s), parseError(err)\n}\n\nfunc (c *conn) yamux() *yamux.Session {\n\treturn (*yamux.Session)(c)\n}\n"
  },
  {
    "path": "p2p/muxer/yamux/stream.go",
    "content": "package yamux\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\n\t\"github.com/libp2p/go-yamux/v5\"\n)\n\n// stream implements mux.MuxedStream over yamux.Stream.\ntype stream yamux.Stream\n\nvar _ network.MuxedStream = &stream{}\n\nfunc parseError(err error) error {\n\tif err == nil {\n\t\treturn err\n\t}\n\tse := &yamux.StreamError{}\n\tif errors.As(err, &se) {\n\t\treturn &network.StreamError{Remote: se.Remote, ErrorCode: network.StreamErrorCode(se.ErrorCode), TransportError: err}\n\t}\n\tce := &yamux.GoAwayError{}\n\tif errors.As(err, &ce) {\n\t\treturn &network.ConnError{Remote: ce.Remote, ErrorCode: network.ConnErrorCode(ce.ErrorCode), TransportError: err}\n\t}\n\tif errors.Is(err, yamux.ErrStreamReset) {\n\t\treturn fmt.Errorf(\"%w: %w\", network.ErrReset, err)\n\t}\n\treturn err\n}\n\nfunc (s *stream) Read(b []byte) (n int, err error) {\n\tn, err = s.yamux().Read(b)\n\treturn n, parseError(err)\n}\n\nfunc (s *stream) Write(b []byte) (n int, err error) {\n\tn, err = s.yamux().Write(b)\n\treturn n, parseError(err)\n}\n\nfunc (s *stream) Close() error {\n\treturn s.yamux().Close()\n}\n\nfunc (s *stream) Reset() error {\n\treturn s.yamux().Reset()\n}\n\nfunc (s *stream) ResetWithError(errCode network.StreamErrorCode) error {\n\treturn s.yamux().ResetWithError(uint32(errCode))\n}\n\nfunc (s *stream) CloseRead() error {\n\treturn s.yamux().CloseRead()\n}\n\nfunc (s *stream) CloseWrite() error {\n\treturn s.yamux().CloseWrite()\n}\n\nfunc (s *stream) SetDeadline(t time.Time) error {\n\treturn s.yamux().SetDeadline(t)\n}\n\nfunc (s *stream) SetReadDeadline(t time.Time) error {\n\treturn s.yamux().SetReadDeadline(t)\n}\n\nfunc (s *stream) SetWriteDeadline(t time.Time) error {\n\treturn s.yamux().SetWriteDeadline(t)\n}\n\nfunc (s *stream) yamux() *yamux.Stream {\n\treturn (*yamux.Stream)(s)\n}\n"
  },
  {
    "path": "p2p/muxer/yamux/transport.go",
    "content": "package yamux\n\nimport (\n\t\"io\"\n\t\"math\"\n\t\"net\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\n\t\"github.com/libp2p/go-yamux/v5\"\n)\n\nvar DefaultTransport *Transport\n\nconst ID = \"/yamux/1.0.0\"\n\nfunc init() {\n\tconfig := yamux.DefaultConfig()\n\t// We've bumped this to 16MiB as this critically limits throughput.\n\t//\n\t// 1MiB means a best case of 10MiB/s (83.89Mbps) on a connection with\n\t// 100ms latency. The default gave us 2.4MiB *best case* which was\n\t// totally unacceptable.\n\tconfig.MaxStreamWindowSize = uint32(16 * 1024 * 1024)\n\t// don't spam\n\tconfig.LogOutput = io.Discard\n\t// We always run over a security transport that buffers internally\n\t// (i.e., uses a block cipher).\n\tconfig.ReadBufSize = 0\n\t// Effectively disable the incoming streams limit.\n\t// This is now dynamically limited by the resource manager.\n\tconfig.MaxIncomingStreams = math.MaxUint32\n\tDefaultTransport = (*Transport)(config)\n}\n\n// Transport implements mux.Multiplexer that constructs\n// yamux-backed muxed connections.\ntype Transport yamux.Config\n\nvar _ network.Multiplexer = &Transport{}\n\nfunc (t *Transport) NewConn(nc net.Conn, isServer bool, scope network.PeerScope) (network.MuxedConn, error) {\n\tvar newSpan func() (yamux.MemoryManager, error)\n\tif scope != nil {\n\t\tnewSpan = func() (yamux.MemoryManager, error) { return scope.BeginSpan() }\n\t}\n\n\tvar s *yamux.Session\n\tvar err error\n\tif isServer {\n\t\ts, err = yamux.Server(nc, t.Config(), newSpan)\n\t} else {\n\t\ts, err = yamux.Client(nc, t.Config(), newSpan)\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn NewMuxedConn(s), nil\n}\n\nfunc (t *Transport) Config() *yamux.Config {\n\treturn (*yamux.Config)(t)\n}\n"
  },
  {
    "path": "p2p/muxer/yamux/transport_test.go",
    "content": "package yamux\n\nimport (\n\t\"testing\"\n\n\ttmux \"github.com/libp2p/go-libp2p/p2p/muxer/testsuite\"\n)\n\nfunc TestDefaultTransport(t *testing.T) {\n\t// Yamux doesn't have any backpressure when it comes to opening streams.\n\t// If the peer opens too many streams, those are just reset.\n\tdelete(tmux.Subtests, \"github.com/libp2p/go-libp2p-testing/suites/mux.SubtestStress1Conn1000Stream10Msg\")\n\n\ttmux.SubtestAll(t, DefaultTransport)\n}\n"
  },
  {
    "path": "p2p/net/README.md",
    "content": "# Network\n\nThe IPFS Network package handles all of the peer-to-peer networking. It connects to other hosts, it encrypts communications, it muxes messages between the network's client services and target hosts. It has multiple subcomponents:\n\n- `Conn` - a connection to a single Peer\n  - `MultiConn` - a set of connections to a single Peer\n  - `SecureConn` - an encrypted (TLS-like) connection\n- `Swarm` - holds connections to Peers, multiplexes from/to each `MultiConn`\n- `Muxer` - multiplexes between `Services` and `Swarm`. Handles `Request/Reply`.\n  - `Service` - connects between an outside client service and Network.\n  - `Handler` - the client service part that handles requests\n\nIt looks a bit like this:\n\n\n![](https://docs.google.com/drawings/d/1FvU7GImRsb9GvAWDDo1le85jIrnFJNVB_OTPXC15WwM/pub?h=480)\n\n"
  },
  {
    "path": "p2p/net/conngater/conngater.go",
    "content": "package conngater\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"sync\"\n\n\t\"github.com/libp2p/go-libp2p/core/connmgr\"\n\t\"github.com/libp2p/go-libp2p/core/control\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n\n\t\"github.com/ipfs/go-datastore\"\n\t\"github.com/ipfs/go-datastore/namespace\"\n\t\"github.com/ipfs/go-datastore/query\"\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n)\n\n// BasicConnectionGater implements a connection gater that allows the application to perform\n// access control on incoming and outgoing connections.\ntype BasicConnectionGater struct {\n\tsync.RWMutex\n\n\tblockedPeers   map[peer.ID]struct{}\n\tblockedAddrs   map[string]struct{}\n\tblockedSubnets map[string]*net.IPNet\n\n\tds datastore.Datastore\n}\n\nvar log = logging.Logger(\"net/conngater\")\n\nconst (\n\tns        = \"/libp2p/net/conngater\"\n\tkeyPeer   = \"/peer/\"\n\tkeyAddr   = \"/addr/\"\n\tkeySubnet = \"/subnet/\"\n)\n\n// NewBasicConnectionGater creates a new connection gater.\n// The ds argument is an (optional, can be nil) datastore to persist the connection gater\n// filters.\nfunc NewBasicConnectionGater(ds datastore.Datastore) (*BasicConnectionGater, error) {\n\tcg := &BasicConnectionGater{\n\t\tblockedPeers:   make(map[peer.ID]struct{}),\n\t\tblockedAddrs:   make(map[string]struct{}),\n\t\tblockedSubnets: make(map[string]*net.IPNet),\n\t}\n\n\tif ds != nil {\n\t\tcg.ds = namespace.Wrap(ds, datastore.NewKey(ns))\n\t\terr := cg.loadRules(context.Background())\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn cg, nil\n}\n\nfunc (cg *BasicConnectionGater) loadRules(ctx context.Context) error {\n\t// load blocked peers\n\tres, err := cg.ds.Query(ctx, query.Query{Prefix: keyPeer})\n\tif err != nil {\n\t\tlog.Error(\"error querying datastore for blocked peers\", \"err\", err)\n\t\treturn err\n\t}\n\n\tfor r := range res.Next() {\n\t\tif r.Error != nil {\n\t\t\tlog.Error(\"query result error\", \"err\", r.Error)\n\t\t\treturn r.Error\n\t\t}\n\n\t\tp := peer.ID(r.Entry.Value)\n\t\tcg.blockedPeers[p] = struct{}{}\n\t}\n\n\t// load blocked addrs\n\tres, err = cg.ds.Query(ctx, query.Query{Prefix: keyAddr})\n\tif err != nil {\n\t\tlog.Error(\"error querying datastore for blocked addrs\", \"err\", err)\n\t\treturn err\n\t}\n\n\tfor r := range res.Next() {\n\t\tif r.Error != nil {\n\t\t\tlog.Error(\"query result error\", \"err\", r.Error)\n\t\t\treturn r.Error\n\t\t}\n\n\t\tip := net.IP(r.Entry.Value)\n\t\tcg.blockedAddrs[ip.String()] = struct{}{}\n\t}\n\n\t// load blocked subnets\n\tres, err = cg.ds.Query(ctx, query.Query{Prefix: keySubnet})\n\tif err != nil {\n\t\tlog.Error(\"error querying datastore for blocked subnets\", \"err\", err)\n\t\treturn err\n\t}\n\n\tfor r := range res.Next() {\n\t\tif r.Error != nil {\n\t\t\tlog.Error(\"query result error\", \"err\", r.Error)\n\t\t\treturn r.Error\n\t\t}\n\n\t\tipnetStr := string(r.Entry.Value)\n\t\t_, ipnet, err := net.ParseCIDR(ipnetStr)\n\t\tif err != nil {\n\t\t\tlog.Error(\"error parsing CIDR subnet\", \"err\", err)\n\t\t\treturn err\n\t\t}\n\t\tcg.blockedSubnets[ipnetStr] = ipnet\n\t}\n\n\treturn nil\n}\n\n// BlockPeer adds a peer to the set of blocked peers.\n// Note: active connections to the peer are not automatically closed.\nfunc (cg *BasicConnectionGater) BlockPeer(p peer.ID) error {\n\tif cg.ds != nil {\n\t\terr := cg.ds.Put(context.Background(), datastore.NewKey(keyPeer+p.String()), []byte(p))\n\t\tif err != nil {\n\t\t\tlog.Error(\"error writing blocked peer to datastore\", \"err\", err)\n\t\t\treturn err\n\t\t}\n\t}\n\n\tcg.Lock()\n\tdefer cg.Unlock()\n\tcg.blockedPeers[p] = struct{}{}\n\n\treturn nil\n}\n\n// UnblockPeer removes a peer from the set of blocked peers\nfunc (cg *BasicConnectionGater) UnblockPeer(p peer.ID) error {\n\tif cg.ds != nil {\n\t\terr := cg.ds.Delete(context.Background(), datastore.NewKey(keyPeer+p.String()))\n\t\tif err != nil {\n\t\t\tlog.Error(\"error deleting blocked peer from datastore\", \"err\", err)\n\t\t\treturn err\n\t\t}\n\t}\n\n\tcg.Lock()\n\tdefer cg.Unlock()\n\n\tdelete(cg.blockedPeers, p)\n\n\treturn nil\n}\n\n// ListBlockedPeers return a list of blocked peers\nfunc (cg *BasicConnectionGater) ListBlockedPeers() []peer.ID {\n\tcg.RLock()\n\tdefer cg.RUnlock()\n\n\tresult := make([]peer.ID, 0, len(cg.blockedPeers))\n\tfor p := range cg.blockedPeers {\n\t\tresult = append(result, p)\n\t}\n\n\treturn result\n}\n\n// BlockAddr adds an IP address to the set of blocked addresses.\n// Note: active connections to the IP address are not automatically closed.\nfunc (cg *BasicConnectionGater) BlockAddr(ip net.IP) error {\n\tif cg.ds != nil {\n\t\terr := cg.ds.Put(context.Background(), datastore.NewKey(keyAddr+ip.String()), []byte(ip))\n\t\tif err != nil {\n\t\t\tlog.Error(\"error writing blocked addr to datastore\", \"err\", err)\n\t\t\treturn err\n\t\t}\n\t}\n\n\tcg.Lock()\n\tdefer cg.Unlock()\n\n\tcg.blockedAddrs[ip.String()] = struct{}{}\n\n\treturn nil\n}\n\n// UnblockAddr removes an IP address from the set of blocked addresses\nfunc (cg *BasicConnectionGater) UnblockAddr(ip net.IP) error {\n\tif cg.ds != nil {\n\t\terr := cg.ds.Delete(context.Background(), datastore.NewKey(keyAddr+ip.String()))\n\t\tif err != nil {\n\t\t\tlog.Error(\"error deleting blocked addr from datastore\", \"err\", err)\n\t\t\treturn err\n\t\t}\n\t}\n\n\tcg.Lock()\n\tdefer cg.Unlock()\n\n\tdelete(cg.blockedAddrs, ip.String())\n\n\treturn nil\n}\n\n// ListBlockedAddrs return a list of blocked IP addresses\nfunc (cg *BasicConnectionGater) ListBlockedAddrs() []net.IP {\n\tcg.RLock()\n\tdefer cg.RUnlock()\n\n\tresult := make([]net.IP, 0, len(cg.blockedAddrs))\n\tfor ipStr := range cg.blockedAddrs {\n\t\tip := net.ParseIP(ipStr)\n\t\tresult = append(result, ip)\n\t}\n\n\treturn result\n}\n\n// BlockSubnet adds an IP subnet to the set of blocked addresses.\n// Note: active connections to the IP subnet are not automatically closed.\nfunc (cg *BasicConnectionGater) BlockSubnet(ipnet *net.IPNet) error {\n\tif cg.ds != nil {\n\t\terr := cg.ds.Put(context.Background(), datastore.NewKey(keySubnet+ipnet.String()), []byte(ipnet.String()))\n\t\tif err != nil {\n\t\t\tlog.Error(\"error writing blocked addr to datastore\", \"err\", err)\n\t\t\treturn err\n\t\t}\n\t}\n\n\tcg.Lock()\n\tdefer cg.Unlock()\n\n\tcg.blockedSubnets[ipnet.String()] = ipnet\n\n\treturn nil\n}\n\n// UnblockSubnet removes an IP address from the set of blocked addresses\nfunc (cg *BasicConnectionGater) UnblockSubnet(ipnet *net.IPNet) error {\n\tif cg.ds != nil {\n\t\terr := cg.ds.Delete(context.Background(), datastore.NewKey(keySubnet+ipnet.String()))\n\t\tif err != nil {\n\t\t\tlog.Error(\"error deleting blocked subnet from datastore\", \"err\", err)\n\t\t\treturn err\n\t\t}\n\t}\n\n\tcg.Lock()\n\tdefer cg.Unlock()\n\n\tdelete(cg.blockedSubnets, ipnet.String())\n\n\treturn nil\n}\n\n// ListBlockedSubnets return a list of blocked IP subnets\nfunc (cg *BasicConnectionGater) ListBlockedSubnets() []*net.IPNet {\n\tcg.RLock()\n\tdefer cg.RUnlock()\n\n\tresult := make([]*net.IPNet, 0, len(cg.blockedSubnets))\n\tfor _, ipnet := range cg.blockedSubnets {\n\t\tresult = append(result, ipnet)\n\t}\n\n\treturn result\n}\n\n// ConnectionGater interface\nvar _ connmgr.ConnectionGater = (*BasicConnectionGater)(nil)\n\nfunc (cg *BasicConnectionGater) InterceptPeerDial(p peer.ID) (allow bool) {\n\tcg.RLock()\n\tdefer cg.RUnlock()\n\n\t_, block := cg.blockedPeers[p]\n\treturn !block\n}\n\nfunc (cg *BasicConnectionGater) InterceptAddrDial(_ peer.ID, a ma.Multiaddr) (allow bool) {\n\t// we have already filtered blocked peers in InterceptPeerDial, so we just check the IP\n\tcg.RLock()\n\tdefer cg.RUnlock()\n\n\tip, err := manet.ToIP(a)\n\tif err != nil {\n\t\tlog.Warn(\"error converting multiaddr to IP addr\", \"err\", err)\n\t\treturn true\n\t}\n\n\t_, block := cg.blockedAddrs[ip.String()]\n\tif block {\n\t\treturn false\n\t}\n\n\tfor _, ipnet := range cg.blockedSubnets {\n\t\tif ipnet.Contains(ip) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc (cg *BasicConnectionGater) InterceptAccept(cma network.ConnMultiaddrs) (allow bool) {\n\tcg.RLock()\n\tdefer cg.RUnlock()\n\n\ta := cma.RemoteMultiaddr()\n\n\tip, err := manet.ToIP(a)\n\tif err != nil {\n\t\tlog.Warn(\"error converting multiaddr to IP addr\", \"err\", err)\n\t\treturn true\n\t}\n\n\t_, block := cg.blockedAddrs[ip.String()]\n\tif block {\n\t\treturn false\n\t}\n\n\tfor _, ipnet := range cg.blockedSubnets {\n\t\tif ipnet.Contains(ip) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc (cg *BasicConnectionGater) InterceptSecured(dir network.Direction, p peer.ID, _ network.ConnMultiaddrs) (allow bool) {\n\tif dir == network.DirOutbound {\n\t\t// we have already filtered those in InterceptPeerDial/InterceptAddrDial\n\t\treturn true\n\t}\n\n\t// we have already filtered addrs in InterceptAccept, so we just check the peer ID\n\tcg.RLock()\n\tdefer cg.RUnlock()\n\n\t_, block := cg.blockedPeers[p]\n\treturn !block\n}\n\nfunc (cg *BasicConnectionGater) InterceptUpgraded(network.Conn) (allow bool, reason control.DisconnectReason) {\n\treturn true, 0\n}\n"
  },
  {
    "path": "p2p/net/conngater/conngater_test.go",
    "content": "package conngater\n\nimport (\n\t\"net\"\n\t\"testing\"\n\n\t\"github.com/ipfs/go-datastore\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\nfunc TestConnectionGater(t *testing.T) {\n\tds := datastore.NewMapDatastore()\n\n\tpeerA := peer.ID(\"A\")\n\tpeerB := peer.ID(\"B\")\n\n\tip1 := net.ParseIP(\"1.2.3.4\")\n\n\t_, ipNet1, err := net.ParseCIDR(\"1.2.3.0/24\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcg, err := NewBasicConnectionGater(ds)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// test peer blocking\n\tallow := cg.InterceptPeerDial(peerA)\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerA\")\n\t}\n\n\tallow = cg.InterceptPeerDial(peerB)\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerB\")\n\t}\n\n\tallow = cg.InterceptSecured(network.DirInbound, peerA, &mockConnMultiaddrs{local: nil, remote: nil})\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerA\")\n\t}\n\n\tallow = cg.InterceptSecured(network.DirInbound, peerB, &mockConnMultiaddrs{local: nil, remote: nil})\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerB\")\n\t}\n\n\terr = cg.BlockPeer(peerA)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tallow = cg.InterceptPeerDial(peerA)\n\tif allow {\n\t\tt.Fatal(\"expected gater to deny peerA\")\n\t}\n\n\tallow = cg.InterceptPeerDial(peerB)\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerB\")\n\t}\n\n\tallow = cg.InterceptSecured(network.DirInbound, peerA, &mockConnMultiaddrs{local: nil, remote: nil})\n\tif allow {\n\t\tt.Fatal(\"expected gater to deny peerA\")\n\t}\n\n\tallow = cg.InterceptSecured(network.DirInbound, peerB, &mockConnMultiaddrs{local: nil, remote: nil})\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerB\")\n\t}\n\n\t// test addr and subnet blocking\n\tallow = cg.InterceptAddrDial(peerB, ma.StringCast(\"/ip4/1.2.3.4/tcp/1234\"))\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerB in 1.2.3.4\")\n\t}\n\n\tallow = cg.InterceptAccept(&mockConnMultiaddrs{local: nil, remote: ma.StringCast(\"/ip4/1.2.3.4/tcp/1234\")})\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerB in 1.2.3.4\")\n\t}\n\n\tallow = cg.InterceptAddrDial(peerB, ma.StringCast(\"/ip4/1.2.3.5/tcp/1234\"))\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerB in 1.2.3.5\")\n\t}\n\n\tallow = cg.InterceptAccept(&mockConnMultiaddrs{local: nil, remote: ma.StringCast(\"/ip4/1.2.3.5/tcp/1234\")})\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerB in 1.2.3.5\")\n\t}\n\n\tallow = cg.InterceptAddrDial(peerB, ma.StringCast(\"/ip4/2.3.4.5/tcp/1234\"))\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerB in 2.3.4.5\")\n\t}\n\n\tallow = cg.InterceptAccept(&mockConnMultiaddrs{local: nil, remote: ma.StringCast(\"/ip4/2.3.4.5/tcp/1234\")})\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerB in 2.3.4.5\")\n\t}\n\n\terr = cg.BlockAddr(ip1)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tallow = cg.InterceptAddrDial(peerB, ma.StringCast(\"/ip4/1.2.3.4/tcp/1234\"))\n\tif allow {\n\t\tt.Fatal(\"expected gater to deny peerB in 1.2.3.4\")\n\t}\n\n\tallow = cg.InterceptAccept(&mockConnMultiaddrs{local: nil, remote: ma.StringCast(\"/ip4/1.2.3.4/tcp/1234\")})\n\tif allow {\n\t\tt.Fatal(\"expected gater to deny peerB in 1.2.3.4\")\n\t}\n\n\tallow = cg.InterceptAddrDial(peerB, ma.StringCast(\"/ip4/1.2.3.5/tcp/1234\"))\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerB in 1.2.3.5\")\n\t}\n\n\tallow = cg.InterceptAccept(&mockConnMultiaddrs{local: nil, remote: ma.StringCast(\"/ip4/1.2.3.5/tcp/1234\")})\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerB in 1.2.3.5\")\n\t}\n\n\tallow = cg.InterceptAddrDial(peerB, ma.StringCast(\"/ip4/2.3.4.5/tcp/1234\"))\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerB in 2.3.4.5\")\n\t}\n\n\tallow = cg.InterceptAccept(&mockConnMultiaddrs{local: nil, remote: ma.StringCast(\"/ip4/2.3.4.5/tcp/1234\")})\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerB in 2.3.4.5\")\n\t}\n\n\terr = cg.BlockSubnet(ipNet1)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tallow = cg.InterceptAddrDial(peerB, ma.StringCast(\"/ip4/1.2.3.5/tcp/1234\"))\n\tif allow {\n\t\tt.Fatal(\"expected gater to deny peerB in 1.2.3.5\")\n\t}\n\n\tallow = cg.InterceptAccept(&mockConnMultiaddrs{local: nil, remote: ma.StringCast(\"/ip4/1.2.3.5/tcp/1234\")})\n\tif allow {\n\t\tt.Fatal(\"expected gater to deny peerB in 1.2.3.5\")\n\t}\n\n\tallow = cg.InterceptAddrDial(peerB, ma.StringCast(\"/ip4/2.3.4.5/tcp/1234\"))\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerB in 2.3.4.5\")\n\t}\n\n\tallow = cg.InterceptAccept(&mockConnMultiaddrs{local: nil, remote: ma.StringCast(\"/ip4/2.3.4.5/tcp/1234\")})\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerB in 2.3.4.5\")\n\t}\n\n\t// make a new gater reusing the datastore to test persistence\n\tcg, err = NewBasicConnectionGater(ds)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// test the list methods while at it\n\tblockedPeers := cg.ListBlockedPeers()\n\tif len(blockedPeers) != 1 {\n\t\tt.Fatalf(\"expected 1 blocked peer, but got %d\", len(blockedPeers))\n\t}\n\n\tblockedAddrs := cg.ListBlockedAddrs()\n\tif len(blockedAddrs) != 1 {\n\t\tt.Fatalf(\"expected 1 blocked addr, but got %d\", len(blockedAddrs))\n\t}\n\n\tblockedSubnets := cg.ListBlockedSubnets()\n\tif len(blockedSubnets) != 1 {\n\t\tt.Fatalf(\"expected 1 blocked subnet, but got %d\", len(blockedSubnets))\n\t}\n\n\tallow = cg.InterceptPeerDial(peerA)\n\tif allow {\n\t\tt.Fatal(\"expected gater to deny peerA\")\n\t}\n\n\tallow = cg.InterceptPeerDial(peerB)\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerB\")\n\t}\n\n\tallow = cg.InterceptSecured(network.DirInbound, peerA, &mockConnMultiaddrs{local: nil, remote: nil})\n\tif allow {\n\t\tt.Fatal(\"expected gater to deny peerA\")\n\t}\n\n\tallow = cg.InterceptSecured(network.DirInbound, peerB, &mockConnMultiaddrs{local: nil, remote: nil})\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerB\")\n\t}\n\n\tallow = cg.InterceptAddrDial(peerB, ma.StringCast(\"/ip4/1.2.3.4/tcp/1234\"))\n\tif allow {\n\t\tt.Fatal(\"expected gater to deny peerB in 1.2.3.4\")\n\t}\n\n\tallow = cg.InterceptAccept(&mockConnMultiaddrs{local: nil, remote: ma.StringCast(\"/ip4/1.2.3.4/tcp/1234\")})\n\tif allow {\n\t\tt.Fatal(\"expected gater to deny peerB in 1.2.3.4\")\n\t}\n\n\tallow = cg.InterceptAddrDial(peerB, ma.StringCast(\"/ip4/1.2.3.5/tcp/1234\"))\n\tif allow {\n\t\tt.Fatal(\"expected gater to deny peerB in 1.2.3.5\")\n\t}\n\n\tallow = cg.InterceptAccept(&mockConnMultiaddrs{local: nil, remote: ma.StringCast(\"/ip4/1.2.3.5/tcp/1234\")})\n\tif allow {\n\t\tt.Fatal(\"expected gater to deny peerB in 1.2.3.5\")\n\t}\n\n\tallow = cg.InterceptAddrDial(peerB, ma.StringCast(\"/ip4/2.3.4.5/tcp/1234\"))\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerB in 2.3.4.5\")\n\t}\n\n\tallow = cg.InterceptAccept(&mockConnMultiaddrs{local: nil, remote: ma.StringCast(\"/ip4/2.3.4.5/tcp/1234\")})\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerB in 2.3.4.5\")\n\t}\n\n\t// undo the blocks to ensure that we can unblock stuff\n\terr = cg.UnblockPeer(peerA)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = cg.UnblockAddr(ip1)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = cg.UnblockSubnet(ipNet1)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tallow = cg.InterceptPeerDial(peerA)\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerA\")\n\t}\n\n\tallow = cg.InterceptPeerDial(peerB)\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerB\")\n\t}\n\n\tallow = cg.InterceptSecured(network.DirInbound, peerA, &mockConnMultiaddrs{local: nil, remote: nil})\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerA\")\n\t}\n\n\tallow = cg.InterceptSecured(network.DirInbound, peerB, &mockConnMultiaddrs{local: nil, remote: nil})\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerB\")\n\t}\n\n\tallow = cg.InterceptAddrDial(peerB, ma.StringCast(\"/ip4/1.2.3.4/tcp/1234\"))\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerB in 1.2.3.4\")\n\t}\n\n\tallow = cg.InterceptAccept(&mockConnMultiaddrs{local: nil, remote: ma.StringCast(\"/ip4/1.2.3.4/tcp/1234\")})\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerB in 1.2.3.4\")\n\t}\n\n\tallow = cg.InterceptAddrDial(peerB, ma.StringCast(\"/ip4/1.2.3.5/tcp/1234\"))\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerB in 1.2.3.5\")\n\t}\n\n\tallow = cg.InterceptAccept(&mockConnMultiaddrs{local: nil, remote: ma.StringCast(\"/ip4/1.2.3.5/tcp/1234\")})\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerB in 1.2.3.5\")\n\t}\n\n\tallow = cg.InterceptAddrDial(peerB, ma.StringCast(\"/ip4/2.3.4.5/tcp/1234\"))\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerB in 2.3.4.5\")\n\t}\n\n\tallow = cg.InterceptAccept(&mockConnMultiaddrs{local: nil, remote: ma.StringCast(\"/ip4/2.3.4.5/tcp/1234\")})\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerB in 2.3.4.5\")\n\t}\n\n\t// make a new gater reusing the datastore to test persistence of unblocks\n\tcg, err = NewBasicConnectionGater(ds)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tallow = cg.InterceptPeerDial(peerA)\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerA\")\n\t}\n\n\tallow = cg.InterceptPeerDial(peerB)\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerB\")\n\t}\n\n\tallow = cg.InterceptSecured(network.DirInbound, peerA, &mockConnMultiaddrs{local: nil, remote: nil})\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerA\")\n\t}\n\n\tallow = cg.InterceptSecured(network.DirInbound, peerB, &mockConnMultiaddrs{local: nil, remote: nil})\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerB\")\n\t}\n\n\tallow = cg.InterceptAddrDial(peerB, ma.StringCast(\"/ip4/1.2.3.4/tcp/1234\"))\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerB in 1.2.3.4\")\n\t}\n\n\tallow = cg.InterceptAccept(&mockConnMultiaddrs{local: nil, remote: ma.StringCast(\"/ip4/1.2.3.4/tcp/1234\")})\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerB in 1.2.3.4\")\n\t}\n\n\tallow = cg.InterceptAddrDial(peerB, ma.StringCast(\"/ip4/1.2.3.5/tcp/1234\"))\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerB in 1.2.3.5\")\n\t}\n\n\tallow = cg.InterceptAccept(&mockConnMultiaddrs{local: nil, remote: ma.StringCast(\"/ip4/1.2.3.5/tcp/1234\")})\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerB in 1.2.3.5\")\n\t}\n\n\tallow = cg.InterceptAddrDial(peerB, ma.StringCast(\"/ip4/2.3.4.5/tcp/1234\"))\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerB in 2.3.4.5\")\n\t}\n\n\tallow = cg.InterceptAccept(&mockConnMultiaddrs{local: nil, remote: ma.StringCast(\"/ip4/2.3.4.5/tcp/1234\")})\n\tif !allow {\n\t\tt.Fatal(\"expected gater to allow peerB in 2.3.4.5\")\n\t}\n}\n\ntype mockConnMultiaddrs struct {\n\tlocal, remote ma.Multiaddr\n}\n\nfunc (cma *mockConnMultiaddrs) LocalMultiaddr() ma.Multiaddr {\n\treturn cma.local\n}\n\nfunc (cma *mockConnMultiaddrs) RemoteMultiaddr() ma.Multiaddr {\n\treturn cma.remote\n}\n"
  },
  {
    "path": "p2p/net/connmgr/bench_test.go",
    "content": "package connmgr\n\nimport (\n\t\"math/rand\"\n\t\"sync\"\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc randomConns(tb testing.TB) (c [5000]network.Conn) {\n\tfor i := range c {\n\t\tc[i] = randConn(tb, nil)\n\t}\n\treturn c\n}\n\nfunc BenchmarkLockContention(b *testing.B) {\n\tconns := randomConns(b)\n\tcm, err := NewConnManager(1000, 1000, WithGracePeriod(0))\n\trequire.NoError(b, err)\n\tnot := cm.Notifee()\n\n\tkill := make(chan struct{})\n\tvar wg sync.WaitGroup\n\n\tfor range 16 {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tfor {\n\t\t\t\tselect {\n\t\t\t\tcase <-kill:\n\t\t\t\t\treturn\n\t\t\t\tdefault:\n\t\t\t\t\tcm.TagPeer(conns[rand.Intn(len(conns))].RemotePeer(), \"another-tag\", 1)\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\t}\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\trc := conns[rand.Intn(len(conns))]\n\t\tnot.Connected(nil, rc)\n\t\tcm.TagPeer(rc.RemotePeer(), \"tag\", 100)\n\t\tcm.UntagPeer(rc.RemotePeer(), \"tag\")\n\t\tnot.Disconnected(nil, rc)\n\t}\n\tclose(kill)\n\twg.Wait()\n}\n"
  },
  {
    "path": "p2p/net/connmgr/connmgr.go",
    "content": "package connmgr\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"maps\"\n\t\"sort\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/benbjohnson/clock\"\n\t\"github.com/libp2p/go-libp2p/core/connmgr\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\nvar log = logging.Logger(\"connmgr\")\n\n// BasicConnMgr is a ConnManager that trims connections whenever the count exceeds the\n// high watermark. New connections are given a grace period before they're subject\n// to trimming. Trims are automatically run on demand, only if the time from the\n// previous trim is higher than 10 seconds. Furthermore, trims can be explicitly\n// requested through the public interface of this struct (see TrimOpenConns).\n//\n// See configuration parameters in NewConnManager.\ntype BasicConnMgr struct {\n\t*decayer\n\n\tclock clock.Clock\n\n\tcfg      *config\n\tsegments segments\n\n\tplk       sync.RWMutex\n\tprotected map[peer.ID]map[string]struct{}\n\n\t// channel-based semaphore that enforces only a single trim is in progress\n\ttrimMutex sync.Mutex\n\tconnCount atomic.Int32\n\t// to be accessed atomically. This is mimicking the implementation of a sync.Once.\n\t// Take care of correct alignment when modifying this struct.\n\ttrimCount uint64\n\n\tlastTrimMu sync.RWMutex\n\tlastTrim   time.Time\n\n\trefCount                sync.WaitGroup\n\tctx                     context.Context\n\tcancel                  func()\n\tunregisterMemoryWatcher func()\n}\n\nvar (\n\t_ connmgr.ConnManager = (*BasicConnMgr)(nil)\n\t_ connmgr.Decayer     = (*BasicConnMgr)(nil)\n)\n\ntype segment struct {\n\tsync.Mutex\n\tpeers map[peer.ID]*peerInfo\n}\n\ntype segments struct {\n\t// bucketsMu is used to prevent deadlocks when concurrent processes try to\n\t// grab multiple segment locks at once. If you need multiple segment locks\n\t// at once, you should grab this lock first. You may release this lock once\n\t// you have the segment locks.\n\tbucketsMu sync.Mutex\n\tbuckets   [256]*segment\n}\n\nfunc (ss *segments) get(p peer.ID) *segment {\n\treturn ss.buckets[p[len(p)-1]]\n}\n\nfunc (ss *segments) countPeers() (count int) {\n\tfor _, seg := range ss.buckets {\n\t\tseg.Lock()\n\t\tcount += len(seg.peers)\n\t\tseg.Unlock()\n\t}\n\treturn count\n}\n\nfunc (s *segment) tagInfoFor(p peer.ID, now time.Time) *peerInfo {\n\tpi, ok := s.peers[p]\n\tif ok {\n\t\treturn pi\n\t}\n\t// create a temporary peer to buffer early tags before the Connected notification arrives.\n\tpi = &peerInfo{\n\t\tid:        p,\n\t\tfirstSeen: now, // this timestamp will be updated when the first Connected notification arrives.\n\t\ttemp:      true,\n\t\ttags:      make(map[string]int),\n\t\tdecaying:  make(map[*decayingTag]*connmgr.DecayingValue),\n\t\tconns:     make(map[network.Conn]time.Time),\n\t}\n\ts.peers[p] = pi\n\treturn pi\n}\n\n// NewConnManager creates a new BasicConnMgr with the provided params:\n// lo and hi are watermarks governing the number of connections that'll be maintained.\n// When the peer count exceeds the 'high watermark', as many peers will be pruned (and\n// their connections terminated) until 'low watermark' peers remain.\nfunc NewConnManager(low, hi int, opts ...Option) (*BasicConnMgr, error) {\n\tcfg := &config{\n\t\thighWater:     hi,\n\t\tlowWater:      low,\n\t\tgracePeriod:   time.Minute,\n\t\tsilencePeriod: 10 * time.Second,\n\t\tclock:         clock.New(),\n\t}\n\tfor _, o := range opts {\n\t\tif err := o(cfg); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif cfg.decayer == nil {\n\t\t// Set the default decayer config.\n\t\tcfg.decayer = (&DecayerCfg{}).WithDefaults()\n\t}\n\n\tcm := &BasicConnMgr{\n\t\tcfg:       cfg,\n\t\tclock:     cfg.clock,\n\t\tprotected: make(map[peer.ID]map[string]struct{}, 16),\n\t\tsegments:  segments{},\n\t}\n\n\tfor i := range cm.segments.buckets {\n\t\tcm.segments.buckets[i] = &segment{\n\t\t\tpeers: make(map[peer.ID]*peerInfo),\n\t\t}\n\t}\n\n\tcm.ctx, cm.cancel = context.WithCancel(context.Background())\n\n\tdecay, _ := NewDecayer(cfg.decayer, cm)\n\tcm.decayer = decay\n\n\tcm.refCount.Add(1)\n\tgo cm.background()\n\treturn cm, nil\n}\n\n// ForceTrim trims connections down to the low watermark ignoring silence period, grace period,\n// or protected status. It prioritizes closing Unprotected connections. If after closing all\n// unprotected connections, we still have more than lowWaterMark connections, it'll close\n// protected connections.\nfunc (cm *BasicConnMgr) ForceTrim() {\n\tconnCount := int(cm.connCount.Load())\n\ttarget := connCount - cm.cfg.lowWater\n\tif target < 0 {\n\t\tlog.Warn(\"Low on memory, but we only have a few connections\", \"num\", connCount, \"low_watermark\", cm.cfg.lowWater)\n\t\treturn\n\t} else {\n\t\tlog.Warn(\"Low on memory. Closing connections.\", \"count\", target)\n\t}\n\n\tcm.trimMutex.Lock()\n\tdefer atomic.AddUint64(&cm.trimCount, 1)\n\tdefer cm.trimMutex.Unlock()\n\n\t// Trim connections without paying attention to the silence period.\n\tfor _, c := range cm.getConnsToCloseEmergency(target) {\n\t\tlog.Info(\"low on memory. closing conn\", \"peer\", c.RemotePeer())\n\n\t\tc.CloseWithError(network.ConnGarbageCollected)\n\t}\n\n\t// finally, update the last trim time.\n\tcm.lastTrimMu.Lock()\n\tcm.lastTrim = cm.clock.Now()\n\tcm.lastTrimMu.Unlock()\n}\n\nfunc (cm *BasicConnMgr) Close() error {\n\tcm.cancel()\n\tif cm.unregisterMemoryWatcher != nil {\n\t\tcm.unregisterMemoryWatcher()\n\t}\n\tif err := cm.decayer.Close(); err != nil {\n\t\treturn err\n\t}\n\tcm.refCount.Wait()\n\treturn nil\n}\n\nfunc (cm *BasicConnMgr) Protect(id peer.ID, tag string) {\n\tcm.plk.Lock()\n\tdefer cm.plk.Unlock()\n\n\ttags, ok := cm.protected[id]\n\tif !ok {\n\t\ttags = make(map[string]struct{}, 2)\n\t\tcm.protected[id] = tags\n\t}\n\ttags[tag] = struct{}{}\n}\n\nfunc (cm *BasicConnMgr) Unprotect(id peer.ID, tag string) (protected bool) {\n\tcm.plk.Lock()\n\tdefer cm.plk.Unlock()\n\n\ttags, ok := cm.protected[id]\n\tif !ok {\n\t\treturn false\n\t}\n\tif delete(tags, tag); len(tags) == 0 {\n\t\tdelete(cm.protected, id)\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc (cm *BasicConnMgr) IsProtected(id peer.ID, tag string) (protected bool) {\n\tcm.plk.Lock()\n\tdefer cm.plk.Unlock()\n\n\ttags, ok := cm.protected[id]\n\tif !ok {\n\t\treturn false\n\t}\n\n\tif tag == \"\" {\n\t\treturn true\n\t}\n\n\t_, protected = tags[tag]\n\treturn protected\n}\n\nfunc (cm *BasicConnMgr) CheckLimit(systemLimit connmgr.GetConnLimiter) error {\n\tif cm.cfg.highWater > systemLimit.GetConnLimit() {\n\t\treturn fmt.Errorf(\n\t\t\t\"conn manager high watermark limit: %d, exceeds the system connection limit of: %d\",\n\t\t\tcm.cfg.highWater,\n\t\t\tsystemLimit.GetConnLimit(),\n\t\t)\n\t}\n\treturn nil\n}\n\n// peerInfo stores metadata for a given peer.\ntype peerInfo struct {\n\tid       peer.ID\n\ttags     map[string]int                          // value for each tag\n\tdecaying map[*decayingTag]*connmgr.DecayingValue // decaying tags\n\n\tvalue int  // cached sum of all tag values\n\ttemp  bool // this is a temporary entry holding early tags, and awaiting connections\n\n\tconns map[network.Conn]time.Time // start time of each connection\n\n\tfirstSeen time.Time // timestamp when we began tracking this peer.\n}\n\ntype peerInfos []*peerInfo\n\n// SortByValueAndStreams sorts peerInfos by their value and stream count. It\n// will sort peers with no streams before those with streams (all else being\n// equal). If `sortByMoreStreams` is true it will sort peers with more streams\n// before those with fewer streams. This is useful to prioritize freeing memory.\nfunc (p peerInfos) SortByValueAndStreams(segments *segments, sortByMoreStreams bool) {\n\tsort.Slice(p, func(i, j int) bool {\n\t\tleft, right := p[i], p[j]\n\n\t\t// Grab this lock so that we can grab both segment locks below without deadlocking.\n\t\tsegments.bucketsMu.Lock()\n\n\t\t// lock this to protect from concurrent modifications from connect/disconnect events\n\t\tleftSegment := segments.get(left.id)\n\t\tleftSegment.Lock()\n\t\tdefer leftSegment.Unlock()\n\n\t\trightSegment := segments.get(right.id)\n\t\tif leftSegment != rightSegment {\n\t\t\t// These two peers are not in the same segment, lets get the lock\n\t\t\trightSegment.Lock()\n\t\t\tdefer rightSegment.Unlock()\n\t\t}\n\t\tsegments.bucketsMu.Unlock()\n\n\t\t// temporary peers are preferred for pruning.\n\t\tif left.temp != right.temp {\n\t\t\treturn left.temp\n\t\t}\n\t\t// otherwise, compare by value.\n\t\tif left.value != right.value {\n\t\t\treturn left.value < right.value\n\t\t}\n\t\tincomingAndStreams := func(m map[network.Conn]time.Time) (incoming bool, numStreams int) {\n\t\t\tfor c := range m {\n\t\t\t\tstat := c.Stat()\n\t\t\t\tif stat.Direction == network.DirInbound {\n\t\t\t\t\tincoming = true\n\t\t\t\t}\n\t\t\t\tnumStreams += stat.NumStreams\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tleftIncoming, leftStreams := incomingAndStreams(left.conns)\n\t\trightIncoming, rightStreams := incomingAndStreams(right.conns)\n\t\t// prefer closing inactive connections (no streams open)\n\t\tif rightStreams != leftStreams && (leftStreams == 0 || rightStreams == 0) {\n\t\t\treturn leftStreams < rightStreams\n\t\t}\n\t\t// incoming connections are preferred for pruning\n\t\tif leftIncoming != rightIncoming {\n\t\t\treturn leftIncoming\n\t\t}\n\n\t\tif sortByMoreStreams {\n\t\t\t// prune connections with a higher number of streams first\n\t\t\treturn rightStreams < leftStreams\n\t\t} else {\n\t\t\treturn leftStreams < rightStreams\n\t\t}\n\t})\n}\n\n// TrimOpenConns closes the connections of as many peers as needed to make the peer count\n// equal the low watermark. Peers are sorted in ascending order based on their total value,\n// pruning those peers with the lowest scores first, as long as they are not within their\n// grace period.\n//\n// This function blocks until a trim is completed. If a trim is underway, a new\n// one won't be started, and instead it'll wait until that one is completed before\n// returning.\nfunc (cm *BasicConnMgr) TrimOpenConns(_ context.Context) {\n\t// TODO: error return value so we can cleanly signal we are aborting because:\n\t// (a) there's another trim in progress, or (b) the silence period is in effect.\n\n\tcm.doTrim()\n}\n\nfunc (cm *BasicConnMgr) background() {\n\tdefer cm.refCount.Done()\n\n\tinterval := cm.cfg.gracePeriod / 2\n\tif cm.cfg.silencePeriod != 0 {\n\t\tinterval = cm.cfg.silencePeriod\n\t}\n\n\tticker := cm.clock.Ticker(interval)\n\tdefer ticker.Stop()\n\n\tfor {\n\t\tselect {\n\t\tcase <-ticker.C:\n\t\t\tif cm.connCount.Load() < int32(cm.cfg.highWater) {\n\t\t\t\t// Below high water, skip.\n\t\t\t\tcontinue\n\t\t\t}\n\t\tcase <-cm.ctx.Done():\n\t\t\treturn\n\t\t}\n\t\tcm.trim()\n\t}\n}\n\nfunc (cm *BasicConnMgr) doTrim() {\n\t// This logic is mimicking the implementation of sync.Once in the standard library.\n\tcount := atomic.LoadUint64(&cm.trimCount)\n\tcm.trimMutex.Lock()\n\tdefer cm.trimMutex.Unlock()\n\tif count == atomic.LoadUint64(&cm.trimCount) {\n\t\tcm.trim()\n\t\tcm.lastTrimMu.Lock()\n\t\tcm.lastTrim = cm.clock.Now()\n\t\tcm.lastTrimMu.Unlock()\n\t\tatomic.AddUint64(&cm.trimCount, 1)\n\t}\n}\n\n// trim starts the trim, if the last trim happened before the configured silence period.\nfunc (cm *BasicConnMgr) trim() {\n\t// do the actual trim.\n\tfor _, c := range cm.getConnsToClose() {\n\t\tlog.Debug(\"closing conn\", \"peer\", c.RemotePeer())\n\t\tc.CloseWithError(network.ConnGarbageCollected)\n\t}\n}\n\nfunc (cm *BasicConnMgr) getConnsToCloseEmergency(target int) []network.Conn {\n\tcandidates := make(peerInfos, 0, cm.segments.countPeers())\n\n\tcm.plk.RLock()\n\tfor _, s := range cm.segments.buckets {\n\t\ts.Lock()\n\t\tfor id, inf := range s.peers {\n\t\t\tif _, ok := cm.protected[id]; ok {\n\t\t\t\t// skip over protected peer.\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tcandidates = append(candidates, inf)\n\t\t}\n\t\ts.Unlock()\n\t}\n\tcm.plk.RUnlock()\n\n\t// Sort peers according to their value.\n\tcandidates.SortByValueAndStreams(&cm.segments, true)\n\n\tselected := make([]network.Conn, 0, target+10)\n\tfor _, inf := range candidates {\n\t\tif target <= 0 {\n\t\t\tbreak\n\t\t}\n\t\ts := cm.segments.get(inf.id)\n\t\ts.Lock()\n\t\tfor c := range inf.conns {\n\t\t\tselected = append(selected, c)\n\t\t}\n\t\ttarget -= len(inf.conns)\n\t\ts.Unlock()\n\t}\n\tif len(selected) >= target {\n\t\t// We found enough connections that were not protected.\n\t\treturn selected\n\t}\n\n\t// We didn't find enough unprotected connections.\n\t// We have no choice but to kill some protected connections.\n\tcandidates = candidates[:0]\n\tcm.plk.RLock()\n\tfor _, s := range cm.segments.buckets {\n\t\ts.Lock()\n\t\tfor _, inf := range s.peers {\n\t\t\tcandidates = append(candidates, inf)\n\t\t}\n\t\ts.Unlock()\n\t}\n\tcm.plk.RUnlock()\n\n\tcandidates.SortByValueAndStreams(&cm.segments, true)\n\tfor _, inf := range candidates {\n\t\tif target <= 0 {\n\t\t\tbreak\n\t\t}\n\t\t// lock this to protect from concurrent modifications from connect/disconnect events\n\t\ts := cm.segments.get(inf.id)\n\t\ts.Lock()\n\t\tfor c := range inf.conns {\n\t\t\tselected = append(selected, c)\n\t\t}\n\t\ttarget -= len(inf.conns)\n\t\ts.Unlock()\n\t}\n\treturn selected\n}\n\n// getConnsToClose runs the heuristics described in TrimOpenConns and returns the\n// connections to close.\nfunc (cm *BasicConnMgr) getConnsToClose() []network.Conn {\n\tif cm.cfg.lowWater == 0 || cm.cfg.highWater == 0 {\n\t\t// disabled\n\t\treturn nil\n\t}\n\n\tif int(cm.connCount.Load()) <= cm.cfg.lowWater {\n\t\tlog.Info(\"open connection count below limit\")\n\t\treturn nil\n\t}\n\n\tcandidates := make(peerInfos, 0, cm.segments.countPeers())\n\tvar ncandidates int\n\tgracePeriodStart := cm.clock.Now().Add(-cm.cfg.gracePeriod)\n\n\tcm.plk.RLock()\n\tfor _, s := range cm.segments.buckets {\n\t\ts.Lock()\n\t\tfor id, inf := range s.peers {\n\t\t\tif _, ok := cm.protected[id]; ok {\n\t\t\t\t// skip over protected peer.\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif inf.firstSeen.After(gracePeriodStart) {\n\t\t\t\t// skip peers in the grace period.\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// note that we're copying the entry here,\n\t\t\t// but since inf.conns is a map, it will still point to the original object\n\t\t\tcandidates = append(candidates, inf)\n\t\t\tncandidates += len(inf.conns)\n\t\t}\n\t\ts.Unlock()\n\t}\n\tcm.plk.RUnlock()\n\n\tif ncandidates < cm.cfg.lowWater {\n\t\tlog.Info(\"open connection count above limit but too many are in the grace period\")\n\t\t// We have too many connections but fewer than lowWater\n\t\t// connections out of the grace period.\n\t\t//\n\t\t// If we trimmed now, we'd kill potentially useful connections.\n\t\treturn nil\n\t}\n\n\t// Sort peers according to their value.\n\tcandidates.SortByValueAndStreams(&cm.segments, false)\n\n\ttarget := ncandidates - cm.cfg.lowWater\n\n\t// slightly overallocate because we may have more than one conns per peer\n\tselected := make([]network.Conn, 0, target+10)\n\n\tfor _, inf := range candidates {\n\t\tif target <= 0 {\n\t\t\tbreak\n\t\t}\n\n\t\t// lock this to protect from concurrent modifications from connect/disconnect events\n\t\ts := cm.segments.get(inf.id)\n\t\ts.Lock()\n\t\tif len(inf.conns) == 0 && inf.temp {\n\t\t\t// handle temporary entries for early tags -- this entry has gone past the grace period\n\t\t\t// and still holds no connections, so prune it.\n\t\t\tdelete(s.peers, inf.id)\n\t\t} else {\n\t\t\tfor c := range inf.conns {\n\t\t\t\tselected = append(selected, c)\n\t\t\t}\n\t\t\ttarget -= len(inf.conns)\n\t\t}\n\t\ts.Unlock()\n\t}\n\n\treturn selected\n}\n\n// GetTagInfo is called to fetch the tag information associated with a given\n// peer, nil is returned if p refers to an unknown peer.\nfunc (cm *BasicConnMgr) GetTagInfo(p peer.ID) *connmgr.TagInfo {\n\ts := cm.segments.get(p)\n\ts.Lock()\n\tdefer s.Unlock()\n\n\tpi, ok := s.peers[p]\n\tif !ok {\n\t\treturn nil\n\t}\n\n\tout := &connmgr.TagInfo{\n\t\tFirstSeen: pi.firstSeen,\n\t\tValue:     pi.value,\n\t\tTags:      make(map[string]int),\n\t\tConns:     make(map[string]time.Time),\n\t}\n\n\tmaps.Copy(out.Tags, pi.tags)\n\tfor t, v := range pi.decaying {\n\t\tout.Tags[t.name] = v.Value\n\t}\n\tfor c, t := range pi.conns {\n\t\tout.Conns[c.RemoteMultiaddr().String()] = t\n\t}\n\n\treturn out\n}\n\n// TagPeer is called to associate a string and integer with a given peer.\nfunc (cm *BasicConnMgr) TagPeer(p peer.ID, tag string, val int) {\n\ts := cm.segments.get(p)\n\ts.Lock()\n\tdefer s.Unlock()\n\n\tpi := s.tagInfoFor(p, cm.clock.Now())\n\n\t// Update the total value of the peer.\n\tpi.value += val - pi.tags[tag]\n\tpi.tags[tag] = val\n}\n\n// UntagPeer is called to disassociate a string and integer from a given peer.\nfunc (cm *BasicConnMgr) UntagPeer(p peer.ID, tag string) {\n\ts := cm.segments.get(p)\n\ts.Lock()\n\tdefer s.Unlock()\n\n\tpi, ok := s.peers[p]\n\tif !ok {\n\t\tlog.Debug(\"tried to remove tag from untracked peer\", \"peer\", p, \"tag\", tag)\n\t\treturn\n\t}\n\n\t// Update the total value of the peer.\n\tpi.value -= pi.tags[tag]\n\tdelete(pi.tags, tag)\n}\n\n// UpsertTag is called to insert/update a peer tag\nfunc (cm *BasicConnMgr) UpsertTag(p peer.ID, tag string, upsert func(int) int) {\n\ts := cm.segments.get(p)\n\ts.Lock()\n\tdefer s.Unlock()\n\n\tpi := s.tagInfoFor(p, cm.clock.Now())\n\n\toldval := pi.tags[tag]\n\tnewval := upsert(oldval)\n\tpi.value += newval - oldval\n\tpi.tags[tag] = newval\n}\n\n// CMInfo holds the configuration for BasicConnMgr, as well as status data.\ntype CMInfo struct {\n\t// The low watermark, as described in NewConnManager.\n\tLowWater int\n\n\t// The high watermark, as described in NewConnManager.\n\tHighWater int\n\n\t// The timestamp when the last trim was triggered.\n\tLastTrim time.Time\n\n\t// The configured grace period, as described in NewConnManager.\n\tGracePeriod time.Duration\n\n\t// The current connection count.\n\tConnCount int\n}\n\n// GetInfo returns the configuration and status data for this connection manager.\nfunc (cm *BasicConnMgr) GetInfo() CMInfo {\n\tcm.lastTrimMu.RLock()\n\tlastTrim := cm.lastTrim\n\tcm.lastTrimMu.RUnlock()\n\n\treturn CMInfo{\n\t\tHighWater:   cm.cfg.highWater,\n\t\tLowWater:    cm.cfg.lowWater,\n\t\tLastTrim:    lastTrim,\n\t\tGracePeriod: cm.cfg.gracePeriod,\n\t\tConnCount:   int(cm.connCount.Load()),\n\t}\n}\n\n// Notifee returns a sink through which Notifiers can inform the BasicConnMgr when\n// events occur. Currently, the notifee only reacts upon connection events\n// {Connected, Disconnected}.\nfunc (cm *BasicConnMgr) Notifee() network.Notifiee {\n\treturn (*cmNotifee)(cm)\n}\n\ntype cmNotifee BasicConnMgr\n\nfunc (nn *cmNotifee) cm() *BasicConnMgr {\n\treturn (*BasicConnMgr)(nn)\n}\n\n// Connected is called by notifiers to inform that a new connection has been established.\n// The notifee updates the BasicConnMgr to start tracking the connection. If the new connection\n// count exceeds the high watermark, a trim may be triggered.\nfunc (nn *cmNotifee) Connected(_ network.Network, c network.Conn) {\n\tcm := nn.cm()\n\n\tp := c.RemotePeer()\n\ts := cm.segments.get(p)\n\ts.Lock()\n\tdefer s.Unlock()\n\n\tid := c.RemotePeer()\n\tpinfo, ok := s.peers[id]\n\tif !ok {\n\t\tpinfo = &peerInfo{\n\t\t\tid:        id,\n\t\t\tfirstSeen: cm.clock.Now(),\n\t\t\ttags:      make(map[string]int),\n\t\t\tdecaying:  make(map[*decayingTag]*connmgr.DecayingValue),\n\t\t\tconns:     make(map[network.Conn]time.Time),\n\t\t}\n\t\ts.peers[id] = pinfo\n\t} else if pinfo.temp {\n\t\t// we had created a temporary entry for this peer to buffer early tags before the\n\t\t// Connected notification arrived: flip the temporary flag, and update the firstSeen\n\t\t// timestamp to the real one.\n\t\tpinfo.temp = false\n\t\tpinfo.firstSeen = cm.clock.Now()\n\t}\n\n\t_, ok = pinfo.conns[c]\n\tif ok {\n\t\tlog.Error(\"received connected notification for conn we are already tracking\", \"peer\", p)\n\t\treturn\n\t}\n\n\tpinfo.conns[c] = cm.clock.Now()\n\tcm.connCount.Add(1)\n}\n\n// Disconnected is called by notifiers to inform that an existing connection has been closed or terminated.\n// The notifee updates the BasicConnMgr accordingly to stop tracking the connection, and performs housekeeping.\nfunc (nn *cmNotifee) Disconnected(_ network.Network, c network.Conn) {\n\tcm := nn.cm()\n\n\tp := c.RemotePeer()\n\ts := cm.segments.get(p)\n\ts.Lock()\n\tdefer s.Unlock()\n\n\tcinf, ok := s.peers[p]\n\tif !ok {\n\t\tlog.Error(\"received disconnected notification for peer we are not tracking\", \"peer\", p)\n\t\treturn\n\t}\n\n\t_, ok = cinf.conns[c]\n\tif !ok {\n\t\tlog.Error(\"received disconnected notification for conn we are not tracking\", \"peer\", p)\n\t\treturn\n\t}\n\n\tdelete(cinf.conns, c)\n\tif len(cinf.conns) == 0 {\n\t\tdelete(s.peers, p)\n\t}\n\tcm.connCount.Add(-1)\n}\n\n// Listen is no-op in this implementation.\nfunc (nn *cmNotifee) Listen(_ network.Network, _ ma.Multiaddr) {}\n\n// ListenClose is no-op in this implementation.\nfunc (nn *cmNotifee) ListenClose(_ network.Network, _ ma.Multiaddr) {}\n"
  },
  {
    "path": "p2p/net/connmgr/connmgr_test.go",
    "content": "package connmgr\n\nimport (\n\t\"context\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/benbjohnson/clock\"\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\ttu \"github.com/libp2p/go-libp2p/core/test\"\n\n\tswarmt \"github.com/libp2p/go-libp2p/p2p/net/swarm/testing\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype tconn struct {\n\tnetwork.Conn\n\n\tpeer             peer.ID\n\tclosed           uint32 // to be used atomically. Closed if 1\n\tdisconnectNotify func(net network.Network, conn network.Conn)\n}\n\nfunc (c *tconn) Close() error {\n\tatomic.StoreUint32(&c.closed, 1)\n\tif c.disconnectNotify != nil {\n\t\tc.disconnectNotify(nil, c)\n\t}\n\treturn nil\n}\n\nfunc (c *tconn) CloseWithError(_ network.ConnErrorCode) error {\n\tatomic.StoreUint32(&c.closed, 1)\n\tif c.disconnectNotify != nil {\n\t\tc.disconnectNotify(nil, c)\n\t}\n\treturn nil\n}\n\nfunc (c *tconn) isClosed() bool {\n\treturn atomic.LoadUint32(&c.closed) == 1\n}\n\nfunc (c *tconn) RemotePeer() peer.ID {\n\treturn c.peer\n}\n\nfunc (c *tconn) Stat() network.ConnStats {\n\treturn network.ConnStats{\n\t\tStats: network.Stats{\n\t\t\tDirection: network.DirOutbound,\n\t\t},\n\t\tNumStreams: 1,\n\t}\n}\n\nfunc (c *tconn) RemoteMultiaddr() ma.Multiaddr {\n\taddr, err := ma.NewMultiaddr(\"/ip4/127.0.0.1/udp/1234\")\n\tif err != nil {\n\t\tpanic(\"cannot create multiaddr\")\n\t}\n\treturn addr\n}\n\nfunc randConn(t testing.TB, discNotify func(network.Network, network.Conn)) network.Conn {\n\tpid := tu.RandPeerIDFatal(t)\n\treturn &tconn{peer: pid, disconnectNotify: discNotify}\n}\n\n// Make sure multiple trim calls block.\nfunc TestTrimBlocks(t *testing.T) {\n\tcm, err := NewConnManager(200, 300, WithGracePeriod(0))\n\trequire.NoError(t, err)\n\tdefer cm.Close()\n\n\tcm.lastTrimMu.RLock()\n\n\tdoneCh := make(chan struct{}, 2)\n\tgo func() {\n\t\tcm.TrimOpenConns(context.Background())\n\t\tdoneCh <- struct{}{}\n\t}()\n\tgo func() {\n\t\tcm.TrimOpenConns(context.Background())\n\t\tdoneCh <- struct{}{}\n\t}()\n\ttime.Sleep(time.Millisecond)\n\tselect {\n\tcase <-doneCh:\n\t\tcm.lastTrimMu.RUnlock()\n\t\tt.Fatal(\"expected trim to block\")\n\tdefault:\n\t\tcm.lastTrimMu.RUnlock()\n\t}\n\t<-doneCh\n\t<-doneCh\n}\n\n// Make sure trim returns when closed.\nfunc TestTrimClosed(t *testing.T) {\n\tcm, err := NewConnManager(200, 300, WithGracePeriod(0))\n\trequire.NoError(t, err)\n\trequire.NoError(t, cm.Close())\n\tcm.TrimOpenConns(context.Background())\n}\n\n// Make sure joining an existing trim works.\nfunc TestTrimJoin(t *testing.T) {\n\tcm, err := NewConnManager(200, 300, WithGracePeriod(0))\n\trequire.NoError(t, err)\n\tdefer cm.Close()\n\n\tcm.lastTrimMu.RLock()\n\tvar wg sync.WaitGroup\n\twg.Add(3)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tcm.TrimOpenConns(context.Background())\n\t}()\n\ttime.Sleep(time.Millisecond)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tcm.TrimOpenConns(context.Background())\n\t}()\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tcm.TrimOpenConns(context.Background())\n\t}()\n\ttime.Sleep(time.Millisecond)\n\tcm.lastTrimMu.RUnlock()\n\twg.Wait()\n}\n\nfunc TestConnTrimming(t *testing.T) {\n\tcm, err := NewConnManager(200, 300, WithGracePeriod(0))\n\trequire.NoError(t, err)\n\tdefer cm.Close()\n\tnot := cm.Notifee()\n\n\tconns := make([]network.Conn, 0, 300)\n\tfor range 300 {\n\t\trc := randConn(t, nil)\n\t\tconns = append(conns, rc)\n\t\tnot.Connected(nil, rc)\n\t}\n\n\tfor _, c := range conns {\n\t\tif c.(*tconn).isClosed() {\n\t\t\tt.Fatal(\"nothing should be closed yet\")\n\t\t}\n\t}\n\n\tfor i := range 100 {\n\t\tcm.TagPeer(conns[i].RemotePeer(), \"foo\", 10)\n\t}\n\n\tcm.TagPeer(conns[299].RemotePeer(), \"badfoo\", -5)\n\n\tcm.TrimOpenConns(context.Background())\n\n\tfor i := range 100 {\n\t\tc := conns[i]\n\t\tif c.(*tconn).isClosed() {\n\t\t\tt.Fatal(\"these shouldnt be closed\")\n\t\t}\n\t}\n\n\tif !conns[299].(*tconn).isClosed() {\n\t\tt.Fatal(\"conn with bad tag should have gotten closed\")\n\t}\n}\n\nfunc TestConnsToClose(t *testing.T) {\n\taddConns := func(cm *BasicConnMgr, n int) {\n\t\tnot := cm.Notifee()\n\t\tfor range n {\n\t\t\tconn := randConn(t, nil)\n\t\t\tnot.Connected(nil, conn)\n\t\t}\n\t}\n\n\tt.Run(\"below hi limit\", func(t *testing.T) {\n\t\tcm, err := NewConnManager(0, 10, WithGracePeriod(0))\n\t\trequire.NoError(t, err)\n\t\tdefer cm.Close()\n\t\taddConns(cm, 5)\n\t\trequire.Empty(t, cm.getConnsToClose())\n\t})\n\n\tt.Run(\"below low limit\", func(t *testing.T) {\n\t\tcm, err := NewConnManager(10, 0, WithGracePeriod(0))\n\t\trequire.NoError(t, err)\n\t\tdefer cm.Close()\n\t\taddConns(cm, 5)\n\t\trequire.Empty(t, cm.getConnsToClose())\n\t})\n\n\tt.Run(\"below low and hi limit\", func(t *testing.T) {\n\t\tcm, err := NewConnManager(1, 1, WithGracePeriod(0))\n\t\trequire.NoError(t, err)\n\t\tdefer cm.Close()\n\t\taddConns(cm, 1)\n\t\trequire.Empty(t, cm.getConnsToClose())\n\t})\n\n\tt.Run(\"within silence period\", func(t *testing.T) {\n\t\tcm, err := NewConnManager(1, 1, WithGracePeriod(10*time.Minute))\n\t\trequire.NoError(t, err)\n\t\tdefer cm.Close()\n\t\taddConns(cm, 1)\n\t\trequire.Empty(t, cm.getConnsToClose())\n\t})\n}\n\nfunc TestGetTagInfo(t *testing.T) {\n\tstart := time.Now()\n\tcm, err := NewConnManager(1, 1, WithGracePeriod(10*time.Minute))\n\trequire.NoError(t, err)\n\tdefer cm.Close()\n\n\tnot := cm.Notifee()\n\tconn := randConn(t, nil)\n\tnot.Connected(nil, conn)\n\tend := time.Now()\n\n\tother := tu.RandPeerIDFatal(t)\n\ttag := cm.GetTagInfo(other)\n\tif tag != nil {\n\t\tt.Fatal(\"expected no tag\")\n\t}\n\n\ttag = cm.GetTagInfo(conn.RemotePeer())\n\tif tag == nil {\n\t\tt.Fatal(\"expected tag\")\n\t}\n\tif tag.FirstSeen.Before(start) || tag.FirstSeen.After(end) {\n\t\tt.Fatal(\"expected first seen time\")\n\t}\n\tif tag.Value != 0 {\n\t\tt.Fatal(\"expected zero value\")\n\t}\n\tif len(tag.Tags) != 0 {\n\t\tt.Fatal(\"expected no tags\")\n\t}\n\tif len(tag.Conns) != 1 {\n\t\tt.Fatal(\"expected one connection\")\n\t}\n\tfor s, tm := range tag.Conns {\n\t\tif s != conn.RemoteMultiaddr().String() {\n\t\t\tt.Fatal(\"unexpected multiaddr\")\n\t\t}\n\t\tif tm.Before(start) || tm.After(end) {\n\t\t\tt.Fatal(\"unexpected connection time\")\n\t\t}\n\t}\n\n\tcm.TagPeer(conn.RemotePeer(), \"tag\", 5)\n\ttag = cm.GetTagInfo(conn.RemotePeer())\n\tif tag == nil {\n\t\tt.Fatal(\"expected tag\")\n\t}\n\tif tag.FirstSeen.Before(start) || tag.FirstSeen.After(end) {\n\t\tt.Fatal(\"expected first seen time\")\n\t}\n\tif tag.Value != 5 {\n\t\tt.Fatal(\"expected five value\")\n\t}\n\tif len(tag.Tags) != 1 {\n\t\tt.Fatal(\"expected no tags\")\n\t}\n\tfor tString, v := range tag.Tags {\n\t\tif tString != \"tag\" || v != 5 {\n\t\t\tt.Fatal(\"expected tag value\")\n\t\t}\n\t}\n\tif len(tag.Conns) != 1 {\n\t\tt.Fatal(\"expected one connection\")\n\t}\n\tfor s, tm := range tag.Conns {\n\t\tif s != conn.RemoteMultiaddr().String() {\n\t\t\tt.Fatal(\"unexpected multiaddr\")\n\t\t}\n\t\tif tm.Before(start) || tm.After(end) {\n\t\t\tt.Fatal(\"unexpected connection time\")\n\t\t}\n\t}\n}\n\nfunc TestTagPeerNonExistant(t *testing.T) {\n\tcm, err := NewConnManager(1, 1, WithGracePeriod(10*time.Minute))\n\trequire.NoError(t, err)\n\tdefer cm.Close()\n\n\tid := tu.RandPeerIDFatal(t)\n\tcm.TagPeer(id, \"test\", 1)\n\n\tif !cm.segments.get(id).peers[id].temp {\n\t\tt.Fatal(\"expected 1 temporary entry\")\n\t}\n}\n\nfunc TestUntagPeer(t *testing.T) {\n\tcm, err := NewConnManager(1, 1, WithGracePeriod(10*time.Minute))\n\trequire.NoError(t, err)\n\tdefer cm.Close()\n\tnot := cm.Notifee()\n\n\tconn := randConn(t, nil)\n\tnot.Connected(nil, conn)\n\trp := conn.RemotePeer()\n\tcm.TagPeer(rp, \"tag\", 5)\n\tcm.TagPeer(rp, \"tag two\", 5)\n\n\tid := tu.RandPeerIDFatal(t)\n\tcm.UntagPeer(id, \"test\")\n\tif len(cm.segments.get(rp).peers[rp].tags) != 2 {\n\t\tt.Fatal(\"expected tags to be uneffected\")\n\t}\n\n\tcm.UntagPeer(conn.RemotePeer(), \"test\")\n\tif len(cm.segments.get(rp).peers[rp].tags) != 2 {\n\t\tt.Fatal(\"expected tags to be uneffected\")\n\t}\n\n\tcm.UntagPeer(conn.RemotePeer(), \"tag\")\n\tif len(cm.segments.get(rp).peers[rp].tags) != 1 {\n\t\tt.Fatal(\"expected tag to be removed\")\n\t}\n\tif cm.segments.get(rp).peers[rp].value != 5 {\n\t\tt.Fatal(\"expected aggreagte tag value to be 5\")\n\t}\n}\n\nfunc TestGetInfo(t *testing.T) {\n\tstart := time.Now()\n\tconst gp = 10 * time.Minute\n\tcm, err := NewConnManager(1, 5, WithGracePeriod(gp))\n\trequire.NoError(t, err)\n\tdefer cm.Close()\n\tnot := cm.Notifee()\n\tconn := randConn(t, nil)\n\tnot.Connected(nil, conn)\n\tcm.TrimOpenConns(context.Background())\n\tend := time.Now()\n\n\tinfo := cm.GetInfo()\n\tif info.HighWater != 5 {\n\t\tt.Fatal(\"expected highwater to be 5\")\n\t}\n\tif info.LowWater != 1 {\n\t\tt.Fatal(\"expected highwater to be 1\")\n\t}\n\tif info.LastTrim.Before(start) || info.LastTrim.After(end) {\n\t\tt.Fatal(\"unexpected last trim time\")\n\t}\n\tif info.GracePeriod != gp {\n\t\tt.Fatal(\"unexpected grace period\")\n\t}\n\tif info.ConnCount != 1 {\n\t\tt.Fatal(\"unexpected number of connections\")\n\t}\n}\n\nfunc TestDoubleConnection(t *testing.T) {\n\tconst gp = 10 * time.Minute\n\tcm, err := NewConnManager(1, 5, WithGracePeriod(gp))\n\trequire.NoError(t, err)\n\tdefer cm.Close()\n\tnot := cm.Notifee()\n\tconn := randConn(t, nil)\n\tnot.Connected(nil, conn)\n\tcm.TagPeer(conn.RemotePeer(), \"foo\", 10)\n\tnot.Connected(nil, conn)\n\tif cm.connCount.Load() != 1 {\n\t\tt.Fatal(\"unexpected number of connections\")\n\t}\n\tif cm.segments.get(conn.RemotePeer()).peers[conn.RemotePeer()].value != 10 {\n\t\tt.Fatal(\"unexpected peer value\")\n\t}\n}\n\nfunc TestDisconnected(t *testing.T) {\n\tconst gp = 10 * time.Minute\n\tcm, err := NewConnManager(1, 5, WithGracePeriod(gp))\n\trequire.NoError(t, err)\n\tdefer cm.Close()\n\tnot := cm.Notifee()\n\tconn := randConn(t, nil)\n\tnot.Connected(nil, conn)\n\tcm.TagPeer(conn.RemotePeer(), \"foo\", 10)\n\n\tnot.Disconnected(nil, randConn(t, nil))\n\tif cm.connCount.Load() != 1 {\n\t\tt.Fatal(\"unexpected number of connections\")\n\t}\n\tif cm.segments.get(conn.RemotePeer()).peers[conn.RemotePeer()].value != 10 {\n\t\tt.Fatal(\"unexpected peer value\")\n\t}\n\n\tnot.Disconnected(nil, &tconn{peer: conn.RemotePeer()})\n\tif cm.connCount.Load() != 1 {\n\t\tt.Fatal(\"unexpected number of connections\")\n\t}\n\tif cm.segments.get(conn.RemotePeer()).peers[conn.RemotePeer()].value != 10 {\n\t\tt.Fatal(\"unexpected peer value\")\n\t}\n\n\tnot.Disconnected(nil, conn)\n\tif cm.connCount.Load() != 0 {\n\t\tt.Fatal(\"unexpected number of connections\")\n\t}\n\tif cm.segments.countPeers() != 0 {\n\t\tt.Fatal(\"unexpected number of peers\")\n\t}\n}\n\nfunc TestGracePeriod(t *testing.T) {\n\tconst gp = 100 * time.Millisecond\n\tmockClock := clock.NewMock()\n\tcm, err := NewConnManager(10, 20, WithGracePeriod(gp), WithSilencePeriod(time.Hour), WithClock(mockClock))\n\trequire.NoError(t, err)\n\tdefer cm.Close()\n\n\tnot := cm.Notifee()\n\n\tconns := make([]network.Conn, 0, 31)\n\n\t// Add a connection and wait the grace period.\n\t{\n\t\trc := randConn(t, not.Disconnected)\n\t\tconns = append(conns, rc)\n\t\tnot.Connected(nil, rc)\n\n\t\tmockClock.Add(2 * gp)\n\n\t\tif rc.(*tconn).isClosed() {\n\t\t\tt.Fatal(\"expected conn to remain open\")\n\t\t}\n\t}\n\n\t// quickly add 30 connections (sending us above the high watermark)\n\tfor range 30 {\n\t\trc := randConn(t, not.Disconnected)\n\t\tconns = append(conns, rc)\n\t\tnot.Connected(nil, rc)\n\t}\n\n\tcm.TrimOpenConns(context.Background())\n\n\tfor _, c := range conns {\n\t\tif c.(*tconn).isClosed() {\n\t\t\tt.Fatal(\"expected no conns to be closed\")\n\t\t}\n\t}\n\n\tmockClock.Add(200 * time.Millisecond)\n\n\tcm.TrimOpenConns(context.Background())\n\n\tclosed := 0\n\tfor _, c := range conns {\n\t\tif c.(*tconn).isClosed() {\n\t\t\tclosed++\n\t\t}\n\t}\n\n\tif closed != 21 {\n\t\tt.Fatal(\"expected to have closed 21 connections\")\n\t}\n}\n\n// see https://github.com/libp2p/go-libp2p-connmgr/issues/23\nfunc TestQuickBurstRespectsSilencePeriod(t *testing.T) {\n\tmockClock := clock.NewMock()\n\tcm, err := NewConnManager(10, 20, WithGracePeriod(0), WithClock(mockClock))\n\trequire.NoError(t, err)\n\tdefer cm.Close()\n\tnot := cm.Notifee()\n\n\tconns := make([]network.Conn, 0, 30)\n\n\t// quickly produce 30 connections (sending us above the high watermark)\n\tfor range 30 {\n\t\trc := randConn(t, not.Disconnected)\n\t\tconns = append(conns, rc)\n\t\tnot.Connected(nil, rc)\n\t}\n\n\t// wait for a few seconds\n\tmockClock.Add(3 * time.Second)\n\n\t// only the first trim is allowed in; make sure we close at most 20 connections, not all of them.\n\tvar closed int\n\tfor _, c := range conns {\n\t\tif c.(*tconn).isClosed() {\n\t\t\tclosed++\n\t\t}\n\t}\n\tif closed > 20 {\n\t\tt.Fatalf(\"should have closed at most 20 connections, closed: %d\", closed)\n\t}\n\tif total := closed + int(cm.connCount.Load()); total != 30 {\n\t\tt.Fatalf(\"expected closed connections + open conn count to equal 30, value: %d\", total)\n\t}\n}\n\nfunc TestPeerProtectionSingleTag(t *testing.T) {\n\tcm, err := NewConnManager(19, 20, WithGracePeriod(0), WithSilencePeriod(time.Hour))\n\trequire.NoError(t, err)\n\tdefer cm.Close()\n\tnot := cm.Notifee()\n\n\tvar conns []network.Conn\n\taddConn := func(value int) {\n\t\trc := randConn(t, not.Disconnected)\n\t\tconns = append(conns, rc)\n\t\tnot.Connected(nil, rc)\n\t\tcm.TagPeer(rc.RemotePeer(), \"test\", value)\n\t}\n\n\t// produce 20 connections with unique peers.\n\tfor range 20 {\n\t\taddConn(20)\n\t}\n\n\t// protect the first 5 peers.\n\tprotected := make([]network.Conn, 0, 5)\n\tfor _, c := range conns[0:5] {\n\t\tcm.Protect(c.RemotePeer(), \"global\")\n\t\tprotected = append(protected, c)\n\t\t// tag them negatively to make them preferred for pruning.\n\t\tcm.TagPeer(c.RemotePeer(), \"test\", -100)\n\t}\n\n\t// add 1 more conn, this shouldn't send us over the limit as protected conns don't count\n\taddConn(20)\n\n\ttime.Sleep(100 * time.Millisecond)\n\tcm.TrimOpenConns(context.Background())\n\n\tfor _, c := range conns {\n\t\tif c.(*tconn).isClosed() {\n\t\t\tt.Error(\"connection was closed by connection manager\")\n\t\t}\n\t}\n\n\t// add 5 more connection, sending the connection manager overboard.\n\tfor range 5 {\n\t\taddConn(20)\n\t}\n\n\tcm.TrimOpenConns(context.Background())\n\n\tfor _, c := range protected {\n\t\tif c.(*tconn).isClosed() {\n\t\t\tt.Error(\"protected connection was closed by connection manager\")\n\t\t}\n\t}\n\n\tclosed := 0\n\tfor _, c := range conns {\n\t\tif c.(*tconn).isClosed() {\n\t\t\tclosed++\n\t\t}\n\t}\n\tif closed != 2 {\n\t\tt.Errorf(\"expected 2 connection to be closed, found %d\", closed)\n\t}\n\n\t// unprotect the first peer.\n\tcm.Unprotect(protected[0].RemotePeer(), \"global\")\n\n\t// add 2 more connections, sending the connection manager overboard again.\n\tfor range 2 {\n\t\taddConn(20)\n\t}\n\n\tcm.TrimOpenConns(context.Background())\n\n\tif !protected[0].(*tconn).isClosed() {\n\t\tt.Error(\"unprotected connection was kept open by connection manager\")\n\t}\n\tfor _, c := range protected[1:] {\n\t\tif c.(*tconn).isClosed() {\n\t\t\tt.Error(\"protected connection was closed by connection manager\")\n\t\t}\n\t}\n}\n\nfunc TestPeerProtectionMultipleTags(t *testing.T) {\n\tcm, err := NewConnManager(19, 20, WithGracePeriod(0), WithSilencePeriod(time.Hour))\n\trequire.NoError(t, err)\n\tdefer cm.Close()\n\tnot := cm.Notifee()\n\n\t// produce 20 connections with unique peers.\n\tconns := make([]network.Conn, 0, 20)\n\tfor range 20 {\n\t\trc := randConn(t, not.Disconnected)\n\t\tconns = append(conns, rc)\n\t\tnot.Connected(nil, rc)\n\t\tcm.TagPeer(rc.RemotePeer(), \"test\", 20)\n\t}\n\n\t// protect the first 5 peers under two tags.\n\tprotected := make([]network.Conn, 0, 5)\n\tfor _, c := range conns[0:5] {\n\t\tcm.Protect(c.RemotePeer(), \"tag1\")\n\t\tcm.Protect(c.RemotePeer(), \"tag2\")\n\t\tprotected = append(protected, c)\n\t\t// tag them negatively to make them preferred for pruning.\n\t\tcm.TagPeer(c.RemotePeer(), \"test\", -100)\n\t}\n\n\t// add one more connection, sending the connection manager overboard.\n\tnot.Connected(nil, randConn(t, not.Disconnected))\n\n\tcm.TrimOpenConns(context.Background())\n\n\tfor _, c := range protected {\n\t\tif c.(*tconn).isClosed() {\n\t\t\tt.Error(\"protected connection was closed by connection manager\")\n\t\t}\n\t}\n\n\t// remove the protection from one tag.\n\tfor _, c := range protected {\n\t\tif !cm.Unprotect(c.RemotePeer(), \"tag1\") {\n\t\t\tt.Error(\"peer should still be protected\")\n\t\t}\n\t}\n\n\t// add 2 more connections, sending the connection manager overboard again.\n\tfor range 2 {\n\t\trc := randConn(t, not.Disconnected)\n\t\tnot.Connected(nil, rc)\n\t\tcm.TagPeer(rc.RemotePeer(), \"test\", 20)\n\t}\n\n\tcm.TrimOpenConns(context.Background())\n\n\t// connections should still remain open, as they were protected.\n\tfor _, c := range protected[0:] {\n\t\tif c.(*tconn).isClosed() {\n\t\t\tt.Error(\"protected connection was closed by connection manager\")\n\t\t}\n\t}\n\n\t// unprotect the first peer entirely.\n\tcm.Unprotect(protected[0].RemotePeer(), \"tag2\")\n\n\t// add 2 more connections, sending the connection manager overboard again.\n\tfor range 2 {\n\t\trc := randConn(t, not.Disconnected)\n\t\tnot.Connected(nil, rc)\n\t\tcm.TagPeer(rc.RemotePeer(), \"test\", 20)\n\t}\n\n\tcm.TrimOpenConns(context.Background())\n\n\tif !protected[0].(*tconn).isClosed() {\n\t\tt.Error(\"unprotected connection was kept open by connection manager\")\n\t}\n\tfor _, c := range protected[1:] {\n\t\tif c.(*tconn).isClosed() {\n\t\t\tt.Error(\"protected connection was closed by connection manager\")\n\t\t}\n\t}\n}\n\nfunc TestPeerProtectionIdempotent(t *testing.T) {\n\tcm, err := NewConnManager(10, 20, WithGracePeriod(0), WithSilencePeriod(time.Hour))\n\trequire.NoError(t, err)\n\tdefer cm.Close()\n\n\tid, _ := tu.RandPeerID()\n\tcm.Protect(id, \"global\")\n\tcm.Protect(id, \"global\")\n\tcm.Protect(id, \"global\")\n\tcm.Protect(id, \"global\")\n\n\tif len(cm.protected[id]) > 1 {\n\t\tt.Error(\"expected peer to be protected only once\")\n\t}\n\n\tif !cm.Unprotect(id, \"unused\") {\n\t\tt.Error(\"expected peer to continue to be protected\")\n\t}\n\n\tif !cm.Unprotect(id, \"unused2\") {\n\t\tt.Error(\"expected peer to continue to be protected\")\n\t}\n\n\tif cm.Unprotect(id, \"global\") {\n\t\tt.Error(\"expected peer to be unprotected\")\n\t}\n\n\tif len(cm.protected) > 0 {\n\t\tt.Error(\"expected no protections\")\n\t}\n}\n\nfunc TestUpsertTag(t *testing.T) {\n\tcm, err := NewConnManager(1, 1, WithGracePeriod(10*time.Minute))\n\trequire.NoError(t, err)\n\tdefer cm.Close()\n\tnot := cm.Notifee()\n\tconn := randConn(t, nil)\n\trp := conn.RemotePeer()\n\n\t// this is an early tag, before the Connected notification arrived.\n\tcm.UpsertTag(rp, \"tag\", func(v int) int { return v + 1 })\n\tif len(cm.segments.get(rp).peers[rp].tags) != 1 {\n\t\tt.Fatal(\"expected a tag\")\n\t}\n\tif cm.segments.get(rp).peers[rp].value != 1 {\n\t\tt.Fatal(\"expected a tag value of 1\")\n\t}\n\n\t// now let's notify the connection.\n\tnot.Connected(nil, conn)\n\n\tcm.UpsertTag(rp, \"tag\", func(v int) int { return v + 1 })\n\tif len(cm.segments.get(rp).peers[rp].tags) != 1 {\n\t\tt.Fatal(\"expected a tag\")\n\t}\n\tif cm.segments.get(rp).peers[rp].value != 2 {\n\t\tt.Fatal(\"expected a tag value of 2\")\n\t}\n\n\tcm.UpsertTag(rp, \"tag\", func(v int) int { return v - 1 })\n\tif len(cm.segments.get(rp).peers[rp].tags) != 1 {\n\t\tt.Fatal(\"expected a tag\")\n\t}\n\tif cm.segments.get(rp).peers[rp].value != 1 {\n\t\tt.Fatal(\"expected a tag value of 1\")\n\t}\n}\n\nfunc TestTemporaryEntriesClearedFirst(t *testing.T) {\n\tcm, err := NewConnManager(1, 1, WithGracePeriod(0))\n\trequire.NoError(t, err)\n\n\tid := tu.RandPeerIDFatal(t)\n\tcm.TagPeer(id, \"test\", 20)\n\n\tif cm.GetTagInfo(id).Value != 20 {\n\t\tt.Fatal(\"expected an early tag with value 20\")\n\t}\n\n\tnot := cm.Notifee()\n\tconn1, conn2 := randConn(t, nil), randConn(t, nil)\n\tnot.Connected(nil, conn1)\n\tnot.Connected(nil, conn2)\n\n\tcm.TrimOpenConns(context.Background())\n\tif cm.GetTagInfo(id) != nil {\n\t\tt.Fatal(\"expected no temporary tags after trimming\")\n\t}\n}\n\nfunc TestTemporaryEntryConvertedOnConnection(t *testing.T) {\n\tcm, err := NewConnManager(1, 1, WithGracePeriod(0))\n\trequire.NoError(t, err)\n\tdefer cm.Close()\n\n\tconn := randConn(t, nil)\n\tcm.TagPeer(conn.RemotePeer(), \"test\", 20)\n\n\tti := cm.segments.get(conn.RemotePeer()).peers[conn.RemotePeer()]\n\n\tif ti.value != 20 || !ti.temp {\n\t\tt.Fatal(\"expected a temporary tag with value 20\")\n\t}\n\n\tnot := cm.Notifee()\n\tnot.Connected(nil, conn)\n\n\tif ti.value != 20 || ti.temp {\n\t\tt.Fatal(\"expected a non-temporary tag with value 20\")\n\t}\n}\n\n// see https://github.com/libp2p/go-libp2p-connmgr/issues/82\nfunc TestConcurrentCleanupAndTagging(t *testing.T) {\n\tcm, err := NewConnManager(1, 1, WithGracePeriod(0), WithSilencePeriod(time.Millisecond))\n\trequire.NoError(t, err)\n\tdefer cm.Close()\n\n\tfor range 1000 {\n\t\tconn := randConn(t, nil)\n\t\tcm.TagPeer(conn.RemotePeer(), \"test\", 20)\n\t}\n}\n\ntype mockConn struct {\n\tstats network.ConnStats\n}\n\nfunc (m mockConn) Close() error                                        { panic(\"implement me\") }\nfunc (m mockConn) CloseWithError(_ network.ConnErrorCode) error        { panic(\"implement me\") }\nfunc (m mockConn) LocalPeer() peer.ID                                  { panic(\"implement me\") }\nfunc (m mockConn) RemotePeer() peer.ID                                 { panic(\"implement me\") }\nfunc (m mockConn) RemotePublicKey() crypto.PubKey                      { panic(\"implement me\") }\nfunc (m mockConn) LocalMultiaddr() ma.Multiaddr                        { panic(\"implement me\") }\nfunc (m mockConn) RemoteMultiaddr() ma.Multiaddr                       { panic(\"implement me\") }\nfunc (m mockConn) Stat() network.ConnStats                             { return m.stats }\nfunc (m mockConn) ID() string                                          { panic(\"implement me\") }\nfunc (m mockConn) IsClosed() bool                                      { panic(\"implement me\") }\nfunc (m mockConn) NewStream(_ context.Context) (network.Stream, error) { panic(\"implement me\") }\nfunc (m mockConn) GetStreams() []network.Stream                        { panic(\"implement me\") }\nfunc (m mockConn) Scope() network.ConnScope                            { panic(\"implement me\") }\nfunc (m mockConn) ConnState() network.ConnectionState                  { return network.ConnectionState{} }\nfunc (m mockConn) As(_ any) bool                                       { return false }\n\nfunc makeSegmentsWithPeerInfos(peerInfos peerInfos) *segments {\n\tvar s = func() *segments {\n\t\tret := segments{}\n\t\tfor i := range ret.buckets {\n\t\t\tret.buckets[i] = &segment{\n\t\t\t\tpeers: make(map[peer.ID]*peerInfo),\n\t\t\t}\n\t\t}\n\t\treturn &ret\n\t}()\n\n\tfor _, pi := range peerInfos {\n\t\tsegment := s.get(pi.id)\n\t\tsegment.Lock()\n\t\tsegment.peers[pi.id] = pi\n\t\tsegment.Unlock()\n\t}\n\n\treturn s\n}\n\nfunc TestPeerInfoSorting(t *testing.T) {\n\tt.Run(\"starts with temporary connections\", func(t *testing.T) {\n\t\tp1 := &peerInfo{id: peer.ID(\"peer1\")}\n\t\tp2 := &peerInfo{id: peer.ID(\"peer2\"), temp: true}\n\t\tpis := peerInfos{p1, p2}\n\t\tpis.SortByValueAndStreams(makeSegmentsWithPeerInfos(pis), false)\n\t\trequire.Equal(t, peerInfos{p2, p1}, pis)\n\t})\n\n\tt.Run(\"starts with low-value connections\", func(t *testing.T) {\n\t\tp1 := &peerInfo{id: peer.ID(\"peer1\"), value: 40}\n\t\tp2 := &peerInfo{id: peer.ID(\"peer2\"), value: 20}\n\t\tpis := peerInfos{p1, p2}\n\t\tpis.SortByValueAndStreams(makeSegmentsWithPeerInfos(pis), false)\n\t\trequire.Equal(t, peerInfos{p2, p1}, pis)\n\t})\n\n\tt.Run(\"prefer peers with no streams\", func(t *testing.T) {\n\t\tp1 := &peerInfo{id: peer.ID(\"peer1\"),\n\t\t\tconns: map[network.Conn]time.Time{\n\t\t\t\t&mockConn{stats: network.ConnStats{NumStreams: 0}}: time.Now(),\n\t\t\t},\n\t\t}\n\t\tp2 := &peerInfo{id: peer.ID(\"peer2\"),\n\t\t\tconns: map[network.Conn]time.Time{\n\t\t\t\t&mockConn{stats: network.ConnStats{NumStreams: 1}}: time.Now(),\n\t\t\t},\n\t\t}\n\t\tpis := peerInfos{p2, p1}\n\t\tpis.SortByValueAndStreams(makeSegmentsWithPeerInfos(pis), false)\n\t\trequire.Equal(t, peerInfos{p1, p2}, pis)\n\t})\n\n\tt.Run(\"in a memory emergency, starts with incoming connections and higher streams\", func(t *testing.T) {\n\t\tincoming := network.ConnStats{}\n\t\tincoming.Direction = network.DirInbound\n\t\toutgoing := network.ConnStats{}\n\t\toutgoing.Direction = network.DirOutbound\n\n\t\toutgoingSomeStreams := network.ConnStats{Stats: network.Stats{Direction: network.DirOutbound}, NumStreams: 1}\n\t\toutgoingMoreStreams := network.ConnStats{Stats: network.Stats{Direction: network.DirOutbound}, NumStreams: 2}\n\t\tp1 := &peerInfo{\n\t\t\tid: peer.ID(\"peer1\"),\n\t\t\tconns: map[network.Conn]time.Time{\n\t\t\t\t&mockConn{stats: outgoingSomeStreams}: time.Now(),\n\t\t\t},\n\t\t}\n\t\tp2 := &peerInfo{\n\t\t\tid: peer.ID(\"peer2\"),\n\t\t\tconns: map[network.Conn]time.Time{\n\t\t\t\t&mockConn{stats: outgoingSomeStreams}: time.Now(),\n\t\t\t\t&mockConn{stats: incoming}:            time.Now(),\n\t\t\t},\n\t\t}\n\t\tp3 := &peerInfo{\n\t\t\tid: peer.ID(\"peer3\"),\n\t\t\tconns: map[network.Conn]time.Time{\n\t\t\t\t&mockConn{stats: outgoing}: time.Now(),\n\t\t\t\t&mockConn{stats: incoming}: time.Now(),\n\t\t\t},\n\t\t}\n\t\tp4 := &peerInfo{\n\t\t\tid: peer.ID(\"peer4\"),\n\t\t\tconns: map[network.Conn]time.Time{\n\t\t\t\t&mockConn{stats: outgoingMoreStreams}: time.Now(),\n\t\t\t\t&mockConn{stats: incoming}:            time.Now(),\n\t\t\t},\n\t\t}\n\t\tpis := peerInfos{p1, p2, p3, p4}\n\t\tpis.SortByValueAndStreams(makeSegmentsWithPeerInfos(pis), true)\n\t\t// p3 is first because it is inactive (no streams).\n\t\t// p4 is second because it has the most streams and we priortize killing\n\t\t// connections with the higher number of streams.\n\t\trequire.Equal(t, peerInfos{p3, p4, p2, p1}, pis)\n\t})\n\n\tt.Run(\"in a memory emergency, starts with connections that have many streams\", func(t *testing.T) {\n\t\tp1 := &peerInfo{\n\t\t\tid: peer.ID(\"peer1\"),\n\t\t\tconns: map[network.Conn]time.Time{\n\t\t\t\t&mockConn{stats: network.ConnStats{NumStreams: 100}}: time.Now(),\n\t\t\t},\n\t\t}\n\t\tp2 := &peerInfo{\n\t\t\tid: peer.ID(\"peer2\"),\n\t\t\tconns: map[network.Conn]time.Time{\n\t\t\t\t&mockConn{stats: network.ConnStats{NumStreams: 80}}: time.Now(),\n\t\t\t\t&mockConn{stats: network.ConnStats{NumStreams: 40}}: time.Now(),\n\t\t\t},\n\t\t}\n\t\tpis := peerInfos{p1, p2}\n\t\tpis.SortByValueAndStreams(makeSegmentsWithPeerInfos(pis), true)\n\t\trequire.Equal(t, peerInfos{p2, p1}, pis)\n\t})\n}\n\nfunc TestSafeConcurrency(t *testing.T) {\n\tt.Run(\"Safe Concurrency\", func(t *testing.T) {\n\t\tcl := clock.NewMock()\n\n\t\tp1 := &peerInfo{id: peer.ID(\"peer1\"), conns: map[network.Conn]time.Time{}}\n\t\tp2 := &peerInfo{id: peer.ID(\"peer2\"), conns: map[network.Conn]time.Time{}}\n\t\tpis := peerInfos{p1, p2}\n\n\t\tss := makeSegmentsWithPeerInfos(pis)\n\n\t\tconst runs = 10\n\t\tconst concurrency = 10\n\t\tvar wg sync.WaitGroup\n\t\tfor range concurrency {\n\t\t\twg.Add(1)\n\t\t\tgo func() {\n\t\t\t\t// add conns. This mimics new connection events\n\t\t\t\tpis := peerInfos{p1, p2}\n\t\t\t\tfor i := range runs {\n\t\t\t\t\tpi := pis[i%len(pis)]\n\t\t\t\t\ts := ss.get(pi.id)\n\t\t\t\t\ts.Lock()\n\t\t\t\t\ts.peers[pi.id].conns[randConn(t, nil)] = cl.Now()\n\t\t\t\t\ts.Unlock()\n\t\t\t\t}\n\t\t\t\twg.Done()\n\t\t\t}()\n\n\t\t\twg.Add(1)\n\t\t\tgo func() {\n\t\t\t\tpis := peerInfos{p1, p2}\n\t\t\t\tfor range runs {\n\t\t\t\t\tpis.SortByValueAndStreams(ss, false)\n\t\t\t\t}\n\t\t\t\twg.Done()\n\t\t\t}()\n\t\t}\n\n\t\twg.Wait()\n\t})\n}\n\nfunc TestCheckLimit(t *testing.T) {\n\tlow, hi := 1, 2\n\tcm, err := NewConnManager(low, hi)\n\trequire.NoError(t, err)\n\n\terr = cm.CheckLimit(testLimitGetter{hi + 1})\n\trequire.NoError(t, err)\n\terr = cm.CheckLimit(testLimitGetter{hi})\n\trequire.NoError(t, err)\n\terr = cm.CheckLimit(testLimitGetter{hi - 1})\n\trequire.Error(t, err)\n}\n\ntype testLimitGetter struct {\n\tlimit int\n}\n\nfunc (g testLimitGetter) GetConnLimit() int {\n\treturn g.limit\n}\n\nfunc TestErrorCode(t *testing.T) {\n\tsw1, sw2, sw3 := swarmt.GenSwarm(t), swarmt.GenSwarm(t), swarmt.GenSwarm(t)\n\tdefer sw1.Close()\n\tdefer sw2.Close()\n\tdefer sw3.Close()\n\n\tcm, err := NewConnManager(1, 1, WithGracePeriod(0), WithSilencePeriod(10))\n\trequire.NoError(t, err)\n\tdefer cm.Close()\n\n\tsw1.Peerstore().AddAddrs(sw2.LocalPeer(), sw2.ListenAddresses(), peerstore.PermanentAddrTTL)\n\tsw1.Peerstore().AddAddrs(sw3.LocalPeer(), sw3.ListenAddresses(), peerstore.PermanentAddrTTL)\n\n\tc12, err := sw1.DialPeer(context.Background(), sw2.LocalPeer())\n\trequire.NoError(t, err)\n\n\tvar c21 network.Conn\n\trequire.Eventually(t, func() bool {\n\t\tconns := sw2.ConnsToPeer(sw1.LocalPeer())\n\t\tif len(conns) == 0 {\n\t\t\treturn false\n\t\t}\n\t\tc21 = conns[0]\n\t\treturn true\n\t}, 10*time.Second, 100*time.Millisecond)\n\n\tc13, err := sw1.DialPeer(context.Background(), sw3.LocalPeer())\n\trequire.NoError(t, err)\n\n\tvar c31 network.Conn\n\trequire.Eventually(t, func() bool {\n\t\tconns := sw3.ConnsToPeer(sw1.LocalPeer())\n\t\tif len(conns) == 0 {\n\t\t\treturn false\n\t\t}\n\t\tc31 = conns[0]\n\t\treturn true\n\t}, 10*time.Second, 100*time.Millisecond)\n\n\tnot := cm.Notifee()\n\tnot.Connected(sw1, c12)\n\tnot.Connected(sw1, c13)\n\n\tcm.TrimOpenConns(context.Background())\n\n\trequire.True(t, c12.IsClosed() || c13.IsClosed())\n\tvar c, cr network.Conn\n\tif c12.IsClosed() {\n\t\tc = c12\n\t\trequire.Eventually(t, func() bool {\n\t\t\tconns := sw2.ConnsToPeer(sw1.LocalPeer())\n\t\t\tif len(conns) == 0 {\n\t\t\t\tcr = c21\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn false\n\t\t}, 5*time.Second, 100*time.Millisecond)\n\t} else {\n\t\tc = c13\n\t\trequire.Eventually(t, func() bool {\n\t\t\tconns := sw3.ConnsToPeer(sw1.LocalPeer())\n\t\t\tif len(conns) == 0 {\n\t\t\t\tcr = c31\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn false\n\t\t}, 5*time.Second, 100*time.Millisecond)\n\t}\n\n\t_, err = c.NewStream(context.Background())\n\trequire.ErrorIs(t, err, &network.ConnError{ErrorCode: network.ConnGarbageCollected, Remote: false})\n\n\t_, err = cr.NewStream(context.Background())\n\trequire.ErrorIs(t, err, &network.ConnError{ErrorCode: network.ConnGarbageCollected, Remote: true})\n}\n"
  },
  {
    "path": "p2p/net/connmgr/decay.go",
    "content": "package connmgr\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/connmgr\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\n\t\"github.com/benbjohnson/clock\"\n)\n\n// DefaultResolution is the default resolution of the decay tracker.\nvar DefaultResolution = 1 * time.Minute\n\n// bumpCmd represents a bump command.\ntype bumpCmd struct {\n\tpeer  peer.ID\n\ttag   *decayingTag\n\tdelta int\n}\n\n// removeCmd represents a tag removal command.\ntype removeCmd struct {\n\tpeer peer.ID\n\ttag  *decayingTag\n}\n\n// decayer tracks and manages all decaying tags and their values.\ntype decayer struct {\n\tcfg   *DecayerCfg\n\tmgr   *BasicConnMgr\n\tclock clock.Clock // for testing.\n\n\ttagsMu    sync.Mutex\n\tknownTags map[string]*decayingTag\n\n\t// lastTick stores the last time the decayer ticked. Guarded by atomic.\n\tlastTick atomic.Pointer[time.Time]\n\n\t// bumpTagCh queues bump commands to be processed by the loop.\n\tbumpTagCh   chan bumpCmd\n\tremoveTagCh chan removeCmd\n\tcloseTagCh  chan *decayingTag\n\n\t// closure thingies.\n\tcloseCh chan struct{}\n\tdoneCh  chan struct{}\n\terr     error\n}\n\nvar _ connmgr.Decayer = (*decayer)(nil)\n\n// DecayerCfg is the configuration object for the Decayer.\ntype DecayerCfg struct {\n\tResolution time.Duration\n\tClock      clock.Clock\n}\n\n// WithDefaults writes the default values on this DecayerConfig instance,\n// and returns itself for chainability.\n//\n//\tcfg := (&DecayerCfg{}).WithDefaults()\n//\tcfg.Resolution = 30 * time.Second\n//\tt := NewDecayer(cfg, cm)\nfunc (cfg *DecayerCfg) WithDefaults() *DecayerCfg {\n\tcfg.Resolution = DefaultResolution\n\treturn cfg\n}\n\n// NewDecayer creates a new decaying tag registry.\nfunc NewDecayer(cfg *DecayerCfg, mgr *BasicConnMgr) (*decayer, error) {\n\t// use real time if the Clock in the config is nil.\n\tif cfg.Clock == nil {\n\t\tcfg.Clock = clock.New()\n\t}\n\n\td := &decayer{\n\t\tcfg:         cfg,\n\t\tmgr:         mgr,\n\t\tclock:       cfg.Clock,\n\t\tknownTags:   make(map[string]*decayingTag),\n\t\tbumpTagCh:   make(chan bumpCmd, 128),\n\t\tremoveTagCh: make(chan removeCmd, 128),\n\t\tcloseTagCh:  make(chan *decayingTag, 128),\n\t\tcloseCh:     make(chan struct{}),\n\t\tdoneCh:      make(chan struct{}),\n\t}\n\n\tnow := d.clock.Now()\n\td.lastTick.Store(&now)\n\n\t// kick things off.\n\tgo d.process()\n\n\treturn d, nil\n}\n\nfunc (d *decayer) RegisterDecayingTag(name string, interval time.Duration, decayFn connmgr.DecayFn, bumpFn connmgr.BumpFn) (connmgr.DecayingTag, error) {\n\td.tagsMu.Lock()\n\tdefer d.tagsMu.Unlock()\n\n\tif _, ok := d.knownTags[name]; ok {\n\t\treturn nil, fmt.Errorf(\"decaying tag with name %s already exists\", name)\n\t}\n\n\tif interval < d.cfg.Resolution {\n\t\tlog.Warn(\"decay interval was lower than tracker's resolution; overridden to resolution\",\n\t\t\t\"name\", name,\n\t\t\t\"interval\", interval,\n\t\t\t\"resolution\", d.cfg.Resolution)\n\t\tinterval = d.cfg.Resolution\n\t}\n\n\tif interval%d.cfg.Resolution != 0 {\n\t\tlog.Warn(\"decay interval for tag is not a multiple of tracker's resolution; some precision may be lost\",\n\t\t\t\"tag\", name, \"interval\", interval, \"resolution\", d.cfg.Resolution)\n\t}\n\n\tlastTick := d.lastTick.Load()\n\ttag := &decayingTag{\n\t\ttrkr:     d,\n\t\tname:     name,\n\t\tinterval: interval,\n\t\tnextTick: lastTick.Add(interval),\n\t\tdecayFn:  decayFn,\n\t\tbumpFn:   bumpFn,\n\t}\n\n\td.knownTags[name] = tag\n\treturn tag, nil\n}\n\n// Close closes the Decayer. It is idempotent.\nfunc (d *decayer) Close() error {\n\tselect {\n\tcase <-d.doneCh:\n\t\treturn d.err\n\tdefault:\n\t}\n\n\tclose(d.closeCh)\n\t<-d.doneCh\n\treturn d.err\n}\n\n// process is the heart of the tracker. It performs the following duties:\n//\n//  1. Manages decay.\n//  2. Applies score bumps.\n//  3. Yields when closed.\nfunc (d *decayer) process() {\n\tdefer close(d.doneCh)\n\n\tticker := d.clock.Ticker(d.cfg.Resolution)\n\tdefer ticker.Stop()\n\n\tvar (\n\t\tbmp   bumpCmd\n\t\tvisit = make(map[*decayingTag]struct{})\n\t)\n\n\tfor {\n\t\tselect {\n\t\tcase <-ticker.C:\n\t\t\tnow := d.clock.Now()\n\t\t\td.lastTick.Store(&now)\n\n\t\t\td.tagsMu.Lock()\n\t\t\tfor _, tag := range d.knownTags {\n\t\t\t\tif tag.nextTick.After(now) {\n\t\t\t\t\t// skip the tag.\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\t// Mark the tag to be updated in this round.\n\t\t\t\tvisit[tag] = struct{}{}\n\t\t\t}\n\t\t\td.tagsMu.Unlock()\n\n\t\t\t// Visit each peer, and decay tags that need to be decayed.\n\t\t\tfor _, s := range d.mgr.segments.buckets {\n\t\t\t\ts.Lock()\n\n\t\t\t\t// Entered a segment that contains peers. Process each peer.\n\t\t\t\tfor _, p := range s.peers {\n\t\t\t\t\tfor tag, v := range p.decaying {\n\t\t\t\t\t\tif _, ok := visit[tag]; !ok {\n\t\t\t\t\t\t\t// skip this tag.\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// ~ this value needs to be visited. ~\n\t\t\t\t\t\tvar delta int\n\t\t\t\t\t\tif after, rm := tag.decayFn(*v); rm {\n\t\t\t\t\t\t\t// delete the value and move on to the next tag.\n\t\t\t\t\t\t\tdelta -= v.Value\n\t\t\t\t\t\t\tdelete(p.decaying, tag)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// accumulate the delta, and apply the changes.\n\t\t\t\t\t\t\tdelta += after - v.Value\n\t\t\t\t\t\t\tv.Value, v.LastVisit = after, now\n\t\t\t\t\t\t}\n\t\t\t\t\t\tp.value += delta\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\ts.Unlock()\n\t\t\t}\n\n\t\t\t// Reset each tag's next visit round, and clear the visited set.\n\t\t\tfor tag := range visit {\n\t\t\t\ttag.nextTick = tag.nextTick.Add(tag.interval)\n\t\t\t\tdelete(visit, tag)\n\t\t\t}\n\n\t\tcase bmp = <-d.bumpTagCh:\n\t\t\tvar (\n\t\t\t\tnow       = d.clock.Now()\n\t\t\t\tpeer, tag = bmp.peer, bmp.tag\n\t\t\t)\n\n\t\t\ts := d.mgr.segments.get(peer)\n\t\t\ts.Lock()\n\n\t\t\tp := s.tagInfoFor(peer, d.clock.Now())\n\t\t\tv, ok := p.decaying[tag]\n\t\t\tif !ok {\n\t\t\t\tv = &connmgr.DecayingValue{\n\t\t\t\t\tTag:       tag,\n\t\t\t\t\tPeer:      peer,\n\t\t\t\t\tLastVisit: now,\n\t\t\t\t\tAdded:     now,\n\t\t\t\t\tValue:     0,\n\t\t\t\t}\n\t\t\t\tp.decaying[tag] = v\n\t\t\t}\n\n\t\t\tprev := v.Value\n\t\t\tv.Value, v.LastVisit = v.Tag.(*decayingTag).bumpFn(*v, bmp.delta), now\n\t\t\tp.value += v.Value - prev\n\n\t\t\ts.Unlock()\n\n\t\tcase rm := <-d.removeTagCh:\n\t\t\ts := d.mgr.segments.get(rm.peer)\n\t\t\ts.Lock()\n\n\t\t\tp := s.tagInfoFor(rm.peer, d.clock.Now())\n\t\t\tv, ok := p.decaying[rm.tag]\n\t\t\tif !ok {\n\t\t\t\ts.Unlock()\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tp.value -= v.Value\n\t\t\tdelete(p.decaying, rm.tag)\n\t\t\ts.Unlock()\n\n\t\tcase t := <-d.closeTagCh:\n\t\t\t// Stop tracking the tag.\n\t\t\td.tagsMu.Lock()\n\t\t\tdelete(d.knownTags, t.name)\n\t\t\td.tagsMu.Unlock()\n\n\t\t\t// Remove the tag from all peers that had it in the connmgr.\n\t\t\tfor _, s := range d.mgr.segments.buckets {\n\t\t\t\t// visit all segments, and attempt to remove the tag from all the peers it stores.\n\t\t\t\ts.Lock()\n\t\t\t\tfor _, p := range s.peers {\n\t\t\t\t\tif dt, ok := p.decaying[t]; ok {\n\t\t\t\t\t\t// decrease the value of the tagInfo, and delete the tag.\n\t\t\t\t\t\tp.value -= dt.Value\n\t\t\t\t\t\tdelete(p.decaying, t)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ts.Unlock()\n\t\t\t}\n\n\t\tcase <-d.closeCh:\n\t\t\treturn\n\t\t}\n\t}\n}\n\n// decayingTag represents a decaying tag, with an associated decay interval, a\n// decay function, and a bump function.\ntype decayingTag struct {\n\ttrkr     *decayer\n\tname     string\n\tinterval time.Duration\n\tnextTick time.Time\n\tdecayFn  connmgr.DecayFn\n\tbumpFn   connmgr.BumpFn\n\n\t// closed marks this tag as closed, so that if it's bumped after being\n\t// closed, we can return an error.\n\tclosed atomic.Bool\n}\n\nvar _ connmgr.DecayingTag = (*decayingTag)(nil)\n\nfunc (t *decayingTag) Name() string {\n\treturn t.name\n}\n\nfunc (t *decayingTag) Interval() time.Duration {\n\treturn t.interval\n}\n\n// Bump bumps a tag for this peer.\nfunc (t *decayingTag) Bump(p peer.ID, delta int) error {\n\tif t.closed.Load() {\n\t\treturn fmt.Errorf(\"decaying tag %s had been closed; no further bumps are accepted\", t.name)\n\t}\n\n\tbmp := bumpCmd{peer: p, tag: t, delta: delta}\n\n\tselect {\n\tcase t.trkr.bumpTagCh <- bmp:\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\n\t\t\t\"unable to bump decaying tag for peer %s, tag %s, delta %d; queue full (len=%d)\",\n\t\t\tp, t.name, delta, len(t.trkr.bumpTagCh))\n\t}\n}\n\nfunc (t *decayingTag) Remove(p peer.ID) error {\n\tif t.closed.Load() {\n\t\treturn fmt.Errorf(\"decaying tag %s had been closed; no further removals are accepted\", t.name)\n\t}\n\n\trm := removeCmd{peer: p, tag: t}\n\n\tselect {\n\tcase t.trkr.removeTagCh <- rm:\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\n\t\t\t\"unable to remove decaying tag for peer %s, tag %s; queue full (len=%d)\",\n\t\t\tp, t.name, len(t.trkr.removeTagCh))\n\t}\n}\n\nfunc (t *decayingTag) Close() error {\n\tif !t.closed.CompareAndSwap(false, true) {\n\t\tlog.Warn(\"duplicate decaying tag closure; skipping\", \"tag\", t.name)\n\t\treturn nil\n\t}\n\n\tselect {\n\tcase t.trkr.closeTagCh <- t:\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unable to close decaying tag %s; queue full (len=%d)\", t.name, len(t.trkr.closeTagCh))\n\t}\n}\n"
  },
  {
    "path": "p2p/net/connmgr/decay_test.go",
    "content": "package connmgr\n\nimport (\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/connmgr\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\ttu \"github.com/libp2p/go-libp2p/core/test\"\n\n\t\"github.com/benbjohnson/clock\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nconst TestResolution = 50 * time.Millisecond\n\nfunc waitForTag(t *testing.T, mgr *BasicConnMgr, id peer.ID) {\n\tt.Helper()\n\trequire.Eventually(t, func() bool { return mgr.GetTagInfo(id) != nil }, 500*time.Millisecond, 10*time.Millisecond)\n}\n\nfunc TestDecayExpire(t *testing.T) {\n\tid := tu.RandPeerIDFatal(t)\n\tmgr, decay, mockClock := testDecayTracker(t)\n\n\ttag, err := decay.RegisterDecayingTag(\"pop\", 250*time.Millisecond, connmgr.DecayExpireWhenInactive(1*time.Second), connmgr.BumpSumUnbounded())\n\trequire.NoError(t, err)\n\trequire.NoError(t, tag.Bump(id, 10))\n\n\twaitForTag(t, mgr, id)\n\trequire.Equal(t, 10, mgr.GetTagInfo(id).Value)\n\n\tmockClock.Add(250 * time.Millisecond)\n\tmockClock.Add(250 * time.Millisecond)\n\tmockClock.Add(250 * time.Millisecond)\n\tmockClock.Add(250 * time.Millisecond)\n\n\teventuallyEqual(t, func() int { return mgr.GetTagInfo(id).Value }, 0)\n}\n\nfunc TestMultipleBumps(t *testing.T) {\n\tid := tu.RandPeerIDFatal(t)\n\tmgr, decay, _ := testDecayTracker(t)\n\n\ttag, err := decay.RegisterDecayingTag(\"pop\", 250*time.Millisecond, connmgr.DecayExpireWhenInactive(1*time.Second), connmgr.BumpSumBounded(10, 20))\n\trequire.NoError(t, err)\n\n\trequire.NoError(t, tag.Bump(id, 5))\n\n\twaitForTag(t, mgr, id)\n\trequire.Equal(t, 10, mgr.GetTagInfo(id).Value)\n\n\trequire.NoError(t, tag.Bump(id, 100))\n\trequire.Eventually(t, func() bool { return mgr.GetTagInfo(id).Value == 20 }, 100*time.Millisecond, 10*time.Millisecond, \"expected tag value to decay to 20\")\n}\n\nfunc TestMultipleTagsNoDecay(t *testing.T) {\n\tid := tu.RandPeerIDFatal(t)\n\tmgr, decay, _ := testDecayTracker(t)\n\n\ttag1, err := decay.RegisterDecayingTag(\"beep\", 250*time.Millisecond, connmgr.DecayNone(), connmgr.BumpSumBounded(0, 100))\n\trequire.NoError(t, err)\n\ttag2, err := decay.RegisterDecayingTag(\"bop\", 250*time.Millisecond, connmgr.DecayNone(), connmgr.BumpSumBounded(0, 100))\n\trequire.NoError(t, err)\n\ttag3, err := decay.RegisterDecayingTag(\"foo\", 250*time.Millisecond, connmgr.DecayNone(), connmgr.BumpSumBounded(0, 100))\n\trequire.NoError(t, err)\n\n\t_ = tag1.Bump(id, 100)\n\t_ = tag2.Bump(id, 100)\n\t_ = tag3.Bump(id, 100)\n\t_ = tag1.Bump(id, 100)\n\t_ = tag2.Bump(id, 100)\n\t_ = tag3.Bump(id, 100)\n\n\twaitForTag(t, mgr, id)\n\n\t// all tags are upper-bounded, so the score must be 300\n\tti := mgr.GetTagInfo(id)\n\trequire.Equal(t, 300, ti.Value)\n\n\tfor _, s := range []string{\"beep\", \"bop\", \"foo\"} {\n\t\tif v, ok := ti.Tags[s]; !ok || v != 100 {\n\t\t\tt.Fatalf(\"expected tag %s to be 100; was = %d\", s, v)\n\t\t}\n\t}\n}\n\nfunc TestCustomFunctions(t *testing.T) {\n\tid := tu.RandPeerIDFatal(t)\n\tmgr, decay, mockClock := testDecayTracker(t)\n\n\ttag1, err := decay.RegisterDecayingTag(\"beep\", 250*time.Millisecond, connmgr.DecayFixed(10), connmgr.BumpSumUnbounded())\n\trequire.NoError(t, err)\n\ttag2, err := decay.RegisterDecayingTag(\"bop\", 100*time.Millisecond, connmgr.DecayFixed(5), connmgr.BumpSumUnbounded())\n\trequire.NoError(t, err)\n\ttag3, err := decay.RegisterDecayingTag(\"foo\", 50*time.Millisecond, connmgr.DecayFixed(1), connmgr.BumpSumUnbounded())\n\trequire.NoError(t, err)\n\n\t_ = tag1.Bump(id, 1000)\n\t_ = tag2.Bump(id, 1000)\n\t_ = tag3.Bump(id, 1000)\n\n\twaitForTag(t, mgr, id)\n\n\t// no decay has occurred yet, so score must be 3000.\n\trequire.Equal(t, 3000, mgr.GetTagInfo(id).Value)\n\n\t// only tag3 should tick.\n\tmockClock.Add(50 * time.Millisecond)\n\teventuallyEqual(t, func() int { return mgr.GetTagInfo(id).Value }, 2999)\n\n\t// tag3 will tick thrice, tag2 will tick twice.\n\tmockClock.Add(150 * time.Millisecond)\n\teventuallyEqual(t, func() int { return mgr.GetTagInfo(id).Value }, 2986)\n\n\t// tag3 will tick once, tag1 will tick once.\n\tmockClock.Add(50 * time.Millisecond)\n\teventuallyEqual(t, func() int { return mgr.GetTagInfo(id).Value }, 2975)\n}\n\nfunc TestMultiplePeers(t *testing.T) {\n\tids := []peer.ID{tu.RandPeerIDFatal(t), tu.RandPeerIDFatal(t), tu.RandPeerIDFatal(t)}\n\tmgr, decay, mockClock := testDecayTracker(t)\n\n\ttag1, err := decay.RegisterDecayingTag(\"beep\", 250*time.Millisecond, connmgr.DecayFixed(10), connmgr.BumpSumUnbounded())\n\trequire.NoError(t, err)\n\ttag2, err := decay.RegisterDecayingTag(\"bop\", 100*time.Millisecond, connmgr.DecayFixed(5), connmgr.BumpSumUnbounded())\n\trequire.NoError(t, err)\n\ttag3, err := decay.RegisterDecayingTag(\"foo\", 50*time.Millisecond, connmgr.DecayFixed(1), connmgr.BumpSumUnbounded())\n\trequire.NoError(t, err)\n\n\t_ = tag1.Bump(ids[0], 1000)\n\t_ = tag2.Bump(ids[0], 1000)\n\t_ = tag3.Bump(ids[0], 1000)\n\n\t_ = tag1.Bump(ids[1], 500)\n\t_ = tag2.Bump(ids[1], 500)\n\t_ = tag3.Bump(ids[1], 500)\n\n\t_ = tag1.Bump(ids[2], 100)\n\t_ = tag2.Bump(ids[2], 100)\n\t_ = tag3.Bump(ids[2], 100)\n\n\t// allow the background goroutine to process bumps.\n\twaitFor := 100 * time.Millisecond\n\ttick := 10 * time.Millisecond\n\tif os.Getenv(\"CI\") != \"\" {\n\t\twaitFor *= 10\n\t\ttick *= 10\n\t}\n\n\trequire.Eventually(t, func() bool {\n\t\treturn mgr.GetTagInfo(ids[0]) != nil && mgr.GetTagInfo(ids[1]) != nil && mgr.GetTagInfo(ids[2]) != nil\n\t}, waitFor, tick)\n\n\tmockClock.Add(3 * time.Second)\n\n\twaitFor = 500 * time.Millisecond\n\tif os.Getenv(\"CI\") != \"\" {\n\t\twaitFor *= 10\n\t}\n\n\trequire.Eventually(t, func() bool { return mgr.GetTagInfo(ids[0]).Value == 2670 }, waitFor, tick)\n\trequire.Equal(t, 1170, mgr.GetTagInfo(ids[1]).Value)\n\trequire.Equal(t, 40, mgr.GetTagInfo(ids[2]).Value)\n}\n\nfunc eventuallyEqual(t *testing.T, f func() int, val int) {\n\tt.Helper()\n\trequire.Eventually(t, func() bool {\n\t\tv := f()\n\t\tif v == val {\n\t\t\treturn true\n\t\t}\n\t\tt.Log(\"f() was\", v, \"expected\", val, \"retrying...\")\n\t\treturn false\n\t}, 1*time.Second, 10*time.Millisecond)\n}\n\nfunc TestLinearDecayOverwrite(t *testing.T) {\n\tid := tu.RandPeerIDFatal(t)\n\tmgr, decay, mockClock := testDecayTracker(t)\n\n\ttag1, err := decay.RegisterDecayingTag(\"beep\", 250*time.Millisecond, connmgr.DecayLinear(0.5), connmgr.BumpOverwrite())\n\trequire.NoError(t, err)\n\n\t_ = tag1.Bump(id, 1000)\n\twaitForTag(t, mgr, id)\n\n\teventuallyEqual(t, func() int { return mgr.GetTagInfo(id).Value }, 1000)\n\tmockClock.Add(250 * time.Millisecond)\n\teventuallyEqual(t, func() int { return mgr.GetTagInfo(id).Value }, 500)\n\n\tmockClock.Add(250 * time.Millisecond)\n\teventuallyEqual(t, func() int { return mgr.GetTagInfo(id).Value }, 250)\n\n\t_ = tag1.Bump(id, 1000)\n\teventuallyEqual(t, func() int { return mgr.GetTagInfo(id).Value }, 1000)\n}\n\nfunc TestResolutionMisaligned(t *testing.T) {\n\tvar (\n\t\tid                    = tu.RandPeerIDFatal(t)\n\t\tmgr, decay, mockClock = testDecayTracker(t)\n\t\trequire               = require.New(t)\n\t)\n\n\ttag1, err := decay.RegisterDecayingTag(\"beep\", time.Duration(float64(TestResolution)*1.4), connmgr.DecayFixed(1), connmgr.BumpOverwrite())\n\trequire.NoError(err)\n\n\ttag2, err := decay.RegisterDecayingTag(\"bop\", time.Duration(float64(TestResolution)*2.4), connmgr.DecayFixed(1), connmgr.BumpOverwrite())\n\trequire.NoError(err)\n\n\t_ = tag1.Bump(id, 1000)\n\t_ = tag2.Bump(id, 1000)\n\t// allow the background goroutine to process bumps.\n\t<-time.After(500 * time.Millisecond)\n\n\t// first tick.\n\tmockClock.Add(TestResolution)\n\teventuallyEqual(t, func() int { return mgr.GetTagInfo(id).Tags[\"beep\"] }, 1000)\n\trequire.Equal(1000, mgr.GetTagInfo(id).Tags[\"bop\"])\n\n\t// next tick; tag1 would've ticked.\n\tmockClock.Add(TestResolution)\n\teventuallyEqual(t, func() int { return mgr.GetTagInfo(id).Tags[\"beep\"] }, 999)\n\trequire.Equal(1000, mgr.GetTagInfo(id).Tags[\"bop\"])\n\n\t// next tick; tag1 would've ticked twice, tag2 once.\n\tmockClock.Add(TestResolution)\n\teventuallyEqual(t, func() int { return mgr.GetTagInfo(id).Tags[\"beep\"] }, 998)\n\trequire.Equal(999, mgr.GetTagInfo(id).Tags[\"bop\"])\n\n\trequire.Equal(1997, mgr.GetTagInfo(id).Value)\n}\n\nfunc TestTagRemoval(t *testing.T) {\n\tid1, id2 := tu.RandPeerIDFatal(t), tu.RandPeerIDFatal(t)\n\tmgr, decay, mockClock := testDecayTracker(t)\n\n\ttag1, err := decay.RegisterDecayingTag(\"beep\", TestResolution, connmgr.DecayFixed(1), connmgr.BumpOverwrite())\n\trequire.NoError(t, err)\n\n\ttag2, err := decay.RegisterDecayingTag(\"bop\", TestResolution, connmgr.DecayFixed(1), connmgr.BumpOverwrite())\n\trequire.NoError(t, err)\n\n\t// id1 has both tags; id2 only has the first tag.\n\t_ = tag1.Bump(id1, 1000)\n\t_ = tag2.Bump(id1, 1000)\n\t_ = tag1.Bump(id2, 1000)\n\n\twaitForTag(t, mgr, id1)\n\twaitForTag(t, mgr, id2)\n\n\t// first tick.\n\tmockClock.Add(TestResolution)\n\teventuallyEqual(t, func() int { return mgr.GetTagInfo(id1).Tags[\"beep\"] }, 999)\n\trequire.Equal(t, 999, mgr.GetTagInfo(id1).Tags[\"bop\"])\n\trequire.Equal(t, 999, mgr.GetTagInfo(id2).Tags[\"beep\"])\n\n\trequire.Equal(t, 999*2, mgr.GetTagInfo(id1).Value)\n\trequire.Equal(t, 999, mgr.GetTagInfo(id2).Value)\n\n\t// remove tag1 from p1.\n\trequire.NoError(t, tag1.Remove(id1))\n\n\t// next tick. both peers only have 1 tag, both at 998 value.\n\tmockClock.Add(TestResolution)\n\trequire.Eventually(t, func() bool { return mgr.GetTagInfo(id1).Tags[\"beep\"] == 0 }, 500*time.Millisecond, 10*time.Millisecond)\n\trequire.Equal(t, 998, mgr.GetTagInfo(id1).Tags[\"bop\"])\n\trequire.Equal(t, 998, mgr.GetTagInfo(id2).Tags[\"beep\"])\n\n\trequire.Equal(t, 998, mgr.GetTagInfo(id1).Value)\n\trequire.Equal(t, 998, mgr.GetTagInfo(id2).Value)\n\n\t// remove tag1 from p1 again; no error.\n\trequire.NoError(t, tag1.Remove(id1))\n}\n\nfunc TestTagClosure(t *testing.T) {\n\tid := tu.RandPeerIDFatal(t)\n\tmgr, decay, mockClock := testDecayTracker(t)\n\n\ttag1, err := decay.RegisterDecayingTag(\"beep\", TestResolution, connmgr.DecayFixed(1), connmgr.BumpOverwrite())\n\trequire.NoError(t, err)\n\ttag2, err := decay.RegisterDecayingTag(\"bop\", TestResolution, connmgr.DecayFixed(1), connmgr.BumpOverwrite())\n\trequire.NoError(t, err)\n\n\t_ = tag1.Bump(id, 1000)\n\t_ = tag2.Bump(id, 1000)\n\twaitForTag(t, mgr, id)\n\n\t// nothing has happened.\n\tmockClock.Add(TestResolution)\n\teventuallyEqual(t, func() int { return mgr.GetTagInfo(id).Tags[\"beep\"] }, 999)\n\trequire.Equal(t, 999, mgr.GetTagInfo(id).Tags[\"bop\"])\n\trequire.Equal(t, 999*2, mgr.GetTagInfo(id).Value)\n\n\t// next tick; tag1 would've ticked.\n\tmockClock.Add(TestResolution)\n\teventuallyEqual(t, func() int { return mgr.GetTagInfo(id).Tags[\"beep\"] }, 998)\n\trequire.Equal(t, 998, mgr.GetTagInfo(id).Tags[\"bop\"])\n\trequire.Equal(t, 998*2, mgr.GetTagInfo(id).Value)\n\n\t// close the tag.\n\trequire.NoError(t, tag1.Close())\n\n\t// allow the background goroutine to process the closure.\n\trequire.Eventually(t, func() bool { return mgr.GetTagInfo(id).Value == 998 }, 500*time.Millisecond, 10*time.Millisecond)\n\n\t// a second closure should not error.\n\trequire.NoError(t, tag1.Close())\n\n\t// bumping a tag after it's been closed should error.\n\trequire.Error(t, tag1.Bump(id, 5))\n}\n\nfunc testDecayTracker(tb testing.TB) (*BasicConnMgr, connmgr.Decayer, *clock.Mock) {\n\tmockClock := clock.NewMock()\n\tcfg := &DecayerCfg{\n\t\tResolution: TestResolution,\n\t\tClock:      mockClock,\n\t}\n\n\tmgr, err := NewConnManager(10, 10, WithGracePeriod(time.Second), DecayerConfig(cfg))\n\trequire.NoError(tb, err)\n\tdecay, ok := connmgr.SupportsDecay(mgr)\n\tif !ok {\n\t\ttb.Fatalf(\"connmgr does not support decay\")\n\t}\n\ttb.Cleanup(func() {\n\t\tmgr.Close()\n\t\tdecay.Close()\n\t})\n\n\treturn mgr, decay, mockClock\n}\n"
  },
  {
    "path": "p2p/net/connmgr/options.go",
    "content": "package connmgr\n\nimport (\n\t\"errors\"\n\t\"time\"\n\n\t\"github.com/benbjohnson/clock\"\n)\n\n// config is the configuration struct for the basic connection manager.\ntype config struct {\n\thighWater     int\n\tlowWater      int\n\tgracePeriod   time.Duration\n\tsilencePeriod time.Duration\n\tdecayer       *DecayerCfg\n\tclock         clock.Clock\n}\n\n// Option represents an option for the basic connection manager.\ntype Option func(*config) error\n\n// DecayerConfig applies a configuration for the decayer.\nfunc DecayerConfig(opts *DecayerCfg) Option {\n\treturn func(cfg *config) error {\n\t\tcfg.decayer = opts\n\t\treturn nil\n\t}\n}\n\n// WithClock sets the internal clock impl\nfunc WithClock(c clock.Clock) Option {\n\treturn func(cfg *config) error {\n\t\tcfg.clock = c\n\t\treturn nil\n\t}\n}\n\n// WithGracePeriod sets the grace period.\n// The grace period is the time a newly opened connection is given before it becomes\n// subject to pruning.\nfunc WithGracePeriod(p time.Duration) Option {\n\treturn func(cfg *config) error {\n\t\tif p < 0 {\n\t\t\treturn errors.New(\"grace period must be non-negative\")\n\t\t}\n\t\tcfg.gracePeriod = p\n\t\treturn nil\n\t}\n}\n\n// WithSilencePeriod sets the silence period.\n// The connection manager will perform a cleanup once per silence period\n// if the number of connections surpasses the high watermark.\nfunc WithSilencePeriod(p time.Duration) Option {\n\treturn func(cfg *config) error {\n\t\tif p <= 0 {\n\t\t\treturn errors.New(\"silence period must be non-zero\")\n\t\t}\n\t\tcfg.silencePeriod = p\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "p2p/net/gostream/addr.go",
    "content": "package gostream\n\nimport \"github.com/libp2p/go-libp2p/core/peer\"\n\n// addr implements net.Addr and holds a libp2p peer ID.\ntype addr struct{ id peer.ID }\n\n// Network returns the name of the network that this address belongs to\n// (libp2p).\nfunc (a *addr) Network() string { return Network }\n\n// String returns the peer ID of this address in string form\n// (B58-encoded).\nfunc (a *addr) String() string { return a.id.String() }\n"
  },
  {
    "path": "p2p/net/gostream/conn.go",
    "content": "package gostream\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net\"\n\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n)\n\n// conn is an implementation of net.Conn which wraps\n// libp2p streams.\ntype conn struct {\n\tnetwork.Stream\n\tignoreEOF bool\n}\n\nfunc (c *conn) Read(b []byte) (int, error) {\n\tn, err := c.Stream.Read(b)\n\tif err != nil && c.ignoreEOF && err == io.EOF {\n\t\treturn n, nil\n\t}\n\treturn n, err\n}\n\n// newConn creates a conn given a libp2p stream\nfunc newConn(s network.Stream, ignoreEOF bool) net.Conn {\n\treturn &conn{s, ignoreEOF}\n}\n\n// LocalAddr returns the local network address.\nfunc (c *conn) LocalAddr() net.Addr {\n\treturn &addr{c.Stream.Conn().LocalPeer()}\n}\n\n// RemoteAddr returns the remote network address.\nfunc (c *conn) RemoteAddr() net.Addr {\n\treturn &addr{c.Stream.Conn().RemotePeer()}\n}\n\n// Dial opens a stream to the destination address\n// (which should parseable to a peer ID) using the given\n// host and returns it as a standard net.Conn.\nfunc Dial(ctx context.Context, h host.Host, pid peer.ID, tag protocol.ID) (net.Conn, error) {\n\ts, err := h.NewStream(ctx, pid, tag)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn newConn(s, false), nil\n}\n"
  },
  {
    "path": "p2p/net/gostream/gostream.go",
    "content": "// Package gostream allows to replace the standard net stack in Go\n// with [LibP2P](https://github.com/libp2p/libp2p) streams.\n//\n// Given a libp2p.Host, gostream provides Dial() and Listen() methods which\n// return implementations of net.Conn and net.Listener.\n//\n// Instead of the regular \"host:port\" addressing, `gostream` uses a Peer ID,\n// and rather than a raw TCP connection, gostream will use libp2p's net.Stream.\n// This means your connections will take advantage of  LibP2P's multi-routes,\n// NAT transversal and stream multiplexing.\n//\n// Note that LibP2P hosts cannot dial to themselves, so there is no possibility\n// of using the same Host as server and as client.\npackage gostream\n\n// Network is the \"net.Addr.Network()\" name returned by\n// addresses used by gostream connections. In turn, the \"net.Addr.String()\" will\n// be a peer ID.\nvar Network = \"libp2p\"\n"
  },
  {
    "path": "p2p/net/gostream/gostream_test.go",
    "content": "package gostream\n\nimport (\n\t\"bufio\"\n\t\"io\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p\"\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\t\"github.com/multiformats/go-multiaddr\"\n)\n\n// newHost illustrates how to build a libp2p host with secio using\n// a randomly generated key-pair\nfunc newHost(t *testing.T, listen multiaddr.Multiaddr) host.Host {\n\th, err := libp2p.New(\n\t\tlibp2p.ListenAddrs(listen),\n\t)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\treturn h\n}\n\nfunc TestServerClient(t *testing.T) {\n\tm1, _ := multiaddr.NewMultiaddr(\"/ip4/127.0.0.1/tcp/10000\")\n\tm2, _ := multiaddr.NewMultiaddr(\"/ip4/127.0.0.1/tcp/10001\")\n\tsrvHost := newHost(t, m1)\n\tclientHost := newHost(t, m2)\n\tdefer srvHost.Close()\n\tdefer clientHost.Close()\n\n\tsrvHost.Peerstore().AddAddrs(clientHost.ID(), clientHost.Addrs(), peerstore.PermanentAddrTTL)\n\tclientHost.Peerstore().AddAddrs(srvHost.ID(), srvHost.Addrs(), peerstore.PermanentAddrTTL)\n\n\tvar tag protocol.ID = \"/testitytest\"\n\tctx := t.Context()\n\n\tdone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(done)\n\t\tlistener, err := Listen(srvHost, tag)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\treturn\n\t\t}\n\t\tdefer listener.Close()\n\n\t\tif listener.Addr().String() != srvHost.ID().String() {\n\t\t\tt.Error(\"bad listener address\")\n\t\t\treturn\n\t\t}\n\n\t\tservConn, err := listener.Accept()\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\treturn\n\t\t}\n\t\tdefer servConn.Close()\n\n\t\treader := bufio.NewReader(servConn)\n\t\tfor {\n\t\t\tmsg, err := reader.ReadString('\\n')\n\t\t\tif err == io.EOF {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif msg != \"is libp2p awesome?\\n\" {\n\t\t\t\tt.Errorf(\"Bad incoming message: %s\", msg)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t_, err = servConn.Write([]byte(\"yes it is\\n\"))\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\n\tclientConn, err := Dial(ctx, clientHost, srvHost.ID(), tag)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif clientConn.LocalAddr().String() != clientHost.ID().String() {\n\t\tt.Fatal(\"Bad LocalAddr\")\n\t}\n\n\tif clientConn.RemoteAddr().String() != srvHost.ID().String() {\n\t\tt.Fatal(\"Bad RemoteAddr\")\n\t}\n\n\tif clientConn.LocalAddr().Network() != Network {\n\t\tt.Fatal(\"Bad Network()\")\n\t}\n\n\terr = clientConn.SetDeadline(time.Now().Add(time.Second))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = clientConn.SetReadDeadline(time.Now().Add(time.Second))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = clientConn.SetWriteDeadline(time.Now().Add(time.Second))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t_, err = clientConn.Write([]byte(\"is libp2p awesome?\\n\"))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treader := bufio.NewReader(clientConn)\n\tresp, err := reader.ReadString('\\n')\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif resp != \"yes it is\\n\" {\n\t\tt.Errorf(\"Bad response: %s\", resp)\n\t}\n\n\terr = clientConn.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t<-done\n}\n"
  },
  {
    "path": "p2p/net/gostream/listener.go",
    "content": "package gostream\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n)\n\n// listener is an implementation of net.Listener which handles\n// http-tagged streams from a libp2p connection.\n// A listener can be built with Listen()\ntype listener struct {\n\thost     host.Host\n\tctx      context.Context\n\ttag      protocol.ID\n\tcancel   func()\n\tstreamCh chan network.Stream\n\t// ignoreEOF is a flag that tells the listener to return conns that ignore EOF errors.\n\t// Necessary because the default responsewriter will consider a connection closed if it reads EOF.\n\t// But when on streams, it's fine for us to read EOF, but still be able to write.\n\tignoreEOF bool\n}\n\n// Accept returns the next a connection to this listener.\n// It blocks if there are no connections. Under the hood,\n// connections are libp2p streams.\nfunc (l *listener) Accept() (net.Conn, error) {\n\tselect {\n\tcase s := <-l.streamCh:\n\t\treturn newConn(s, l.ignoreEOF), nil\n\tcase <-l.ctx.Done():\n\t\treturn nil, l.ctx.Err()\n\t}\n}\n\n// Close terminates this listener. It will no longer handle any\n// incoming streams\nfunc (l *listener) Close() error {\n\tl.cancel()\n\tl.host.RemoveStreamHandler(l.tag)\n\treturn nil\n}\n\n// Addr returns the address for this listener, which is its libp2p Peer ID.\nfunc (l *listener) Addr() net.Addr {\n\treturn &addr{l.host.ID()}\n}\n\n// Listen provides a standard net.Listener ready to accept \"connections\".\n// Under the hood, these connections are libp2p streams tagged with the\n// given protocol.ID.\nfunc Listen(h host.Host, tag protocol.ID, opts ...ListenerOption) (net.Listener, error) {\n\tctx, cancel := context.WithCancel(context.Background())\n\n\tl := &listener{\n\t\thost:     h,\n\t\tctx:      ctx,\n\t\tcancel:   cancel,\n\t\ttag:      tag,\n\t\tstreamCh: make(chan network.Stream),\n\t}\n\tfor _, opt := range opts {\n\t\tif err := opt(l); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\th.SetStreamHandler(tag, func(s network.Stream) {\n\t\tselect {\n\t\tcase l.streamCh <- s:\n\t\tcase <-ctx.Done():\n\t\t\ts.Reset()\n\t\t}\n\t})\n\n\treturn l, nil\n}\n\ntype ListenerOption func(*listener) error\n\nfunc IgnoreEOF() ListenerOption {\n\treturn func(l *listener) error {\n\t\tl.ignoreEOF = true\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "p2p/net/mock/complement.go",
    "content": "package mocknet\n\nimport (\n\t\"github.com/libp2p/go-libp2p/core/network\"\n)\n\n// StreamComplement returns the other end of the given stream. This function\n// panics when passed a non-mocknet stream.\nfunc StreamComplement(s network.Stream) network.Stream {\n\treturn s.(*stream).rstream\n}\n\n// ConnComplement returns the other end of the given connection. This function\n// panics when passed a non-mocknet connection.\nfunc ConnComplement(c network.Conn) network.Conn {\n\treturn c.(*conn).rconn\n}\n"
  },
  {
    "path": "p2p/net/mock/interface.go",
    "content": "// Package mocknet provides a mock net.Network to test with.\n//\n// - a Mocknet has many network.Networks\n// - a Mocknet has many Links\n// - a Link joins two network.Networks\n// - network.Conns and network.Streams are created by network.Networks\npackage mocknet\n\nimport (\n\t\"io\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/connmgr\"\n\tic \"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\ntype PeerOptions struct {\n\t// ps is the Peerstore to use when adding peer. If nil, a default peerstore will be created.\n\tps peerstore.Peerstore\n\n\t// gater is the ConnectionGater to use when adding a peer. If nil, no connection gater will be used.\n\tgater connmgr.ConnectionGater\n}\n\ntype Mocknet interface {\n\t// GenPeer generates a peer and its network.Network in the Mocknet\n\tGenPeer() (host.Host, error)\n\tGenPeerWithOptions(PeerOptions) (host.Host, error)\n\n\t// AddPeer adds an existing peer. we need both a privkey and addr.\n\t// ID is derived from PrivKey\n\tAddPeer(ic.PrivKey, ma.Multiaddr) (host.Host, error)\n\tAddPeerWithPeerstore(peer.ID, peerstore.Peerstore) (host.Host, error)\n\tAddPeerWithOptions(peer.ID, PeerOptions) (host.Host, error)\n\n\t// retrieve things (with randomized iteration order)\n\tPeers() []peer.ID\n\tNet(peer.ID) network.Network\n\tNets() []network.Network\n\tHost(peer.ID) host.Host\n\tHosts() []host.Host\n\tLinks() LinkMap\n\tLinksBetweenPeers(a, b peer.ID) []Link\n\tLinksBetweenNets(a, b network.Network) []Link\n\n\t// Links are the **ability to connect**.\n\t// think of Links as the physical medium.\n\t// For p1 and p2 to connect, a link must exist between them.\n\t// (this makes it possible to test dial failures, and\n\t// things like relaying traffic)\n\tLinkPeers(peer.ID, peer.ID) (Link, error)\n\tLinkNets(network.Network, network.Network) (Link, error)\n\tUnlink(Link) error\n\tUnlinkPeers(peer.ID, peer.ID) error\n\tUnlinkNets(network.Network, network.Network) error\n\n\t// LinkDefaults are the default options that govern links\n\t// if they do not have their own option set.\n\tSetLinkDefaults(LinkOptions)\n\tLinkDefaults() LinkOptions\n\n\t// Connections are the usual. Connecting means Dialing.\n\t// **to succeed, peers must be linked beforehand**\n\tConnectPeers(peer.ID, peer.ID) (network.Conn, error)\n\tConnectNets(network.Network, network.Network) (network.Conn, error)\n\tDisconnectPeers(peer.ID, peer.ID) error\n\tDisconnectNets(network.Network, network.Network) error\n\tLinkAll() error\n\tConnectAllButSelf() error\n\n\tio.Closer\n}\n\n// LinkOptions are used to change aspects of the links.\n// Sorry but they dont work yet :(\ntype LinkOptions struct {\n\tLatency   time.Duration\n\tBandwidth float64 // in bytes-per-second\n\t// we can make these values distributions down the road.\n}\n\n// Link represents the **possibility** of a connection between\n// two peers. Think of it like physical network links. Without\n// them, the peers can try and try but they won't be able to\n// connect. This allows constructing topologies where specific\n// nodes cannot talk to each other directly. :)\ntype Link interface {\n\tNetworks() []network.Network\n\tPeers() []peer.ID\n\n\tSetOptions(LinkOptions)\n\tOptions() LinkOptions\n\n\t// Metrics() Metrics\n}\n\n// LinkMap is a 3D map to give us an easy way to track links.\n// (wow, much map. so data structure. how compose. ahhh pointer)\ntype LinkMap map[string]map[string]map[Link]struct{}\n\n// Printer lets you inspect things :)\ntype Printer interface {\n\t// MocknetLinks shows the entire Mocknet's link table :)\n\tMocknetLinks(mn Mocknet)\n\tNetworkConns(ni network.Network)\n}\n\n// PrinterTo returns a Printer ready to write to w.\nfunc PrinterTo(w io.Writer) Printer {\n\treturn &printer{w}\n}\n"
  },
  {
    "path": "p2p/net/mock/log2.txt",
    "content": ""
  },
  {
    "path": "p2p/net/mock/mock.go",
    "content": "package mocknet\n\nimport (\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n)\n\nvar log = logging.Logger(\"mocknet\")\n\n// WithNPeers constructs a Mocknet with N peers.\nfunc WithNPeers(n int) (Mocknet, error) {\n\tm := New()\n\tfor range n {\n\t\tif _, err := m.GenPeer(); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn m, nil\n}\n\n// FullMeshLinked constructs a Mocknet with full mesh of Links.\n// This means that all the peers **can** connect to each other\n// (not that they already are connected. you can use m.ConnectAll())\nfunc FullMeshLinked(n int) (Mocknet, error) {\n\tm, err := WithNPeers(n)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := m.LinkAll(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\n// FullMeshConnected constructs a Mocknet with full mesh of Connections.\n// This means that all the peers have dialed and are ready to talk to\n// each other.\nfunc FullMeshConnected(n int) (Mocknet, error) {\n\tm, err := FullMeshLinked(n)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := m.ConnectAllButSelf(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n"
  },
  {
    "path": "p2p/net/mock/mock_conn.go",
    "content": "package mocknet\n\nimport (\n\t\"container/list\"\n\t\"context\"\n\t\"strconv\"\n\t\"sync\"\n\t\"sync/atomic\"\n\n\tic \"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\nvar connCounter atomic.Int64\n\n// conn represents one side's perspective of a\n// live connection between two peers.\n// it goes over a particular link.\ntype conn struct {\n\tnotifLk sync.Mutex\n\n\tid int64\n\n\tlocal  peer.ID\n\tremote peer.ID\n\n\tlocalAddr  ma.Multiaddr\n\tremoteAddr ma.Multiaddr\n\n\tlocalPrivKey ic.PrivKey\n\tremotePubKey ic.PubKey\n\n\tnet     *peernet\n\tlink    *link\n\trconn   *conn // counterpart\n\tstreams list.List\n\tstat    network.ConnStats\n\n\tcloseOnce sync.Once\n\n\tisClosed atomic.Bool\n\n\tsync.RWMutex\n}\n\nfunc newConn(ln, rn *peernet, l *link, dir network.Direction) *conn {\n\tc := &conn{net: ln, link: l}\n\tc.local = ln.peer\n\tc.remote = rn.peer\n\tc.stat.Direction = dir\n\tc.id = connCounter.Add(1)\n\n\tc.localAddr = ln.ps.Addrs(ln.peer)[0]\n\tfor _, a := range rn.ps.Addrs(rn.peer) {\n\t\tif !manet.IsIPUnspecified(a) {\n\t\t\tc.remoteAddr = a\n\t\t\tbreak\n\t\t}\n\t}\n\tif c.remoteAddr == nil {\n\t\tc.remoteAddr = rn.ps.Addrs(rn.peer)[0]\n\t}\n\n\tc.localPrivKey = ln.ps.PrivKey(ln.peer)\n\tc.remotePubKey = rn.ps.PubKey(rn.peer)\n\treturn c\n}\n\nfunc (c *conn) IsClosed() bool {\n\treturn c.isClosed.Load()\n}\n\nfunc (c *conn) ID() string {\n\treturn strconv.FormatInt(c.id, 10)\n}\n\nfunc (c *conn) Close() error {\n\tc.closeOnce.Do(func() {\n\t\tc.isClosed.Store(true)\n\t\tgo c.rconn.Close()\n\t\tc.teardown()\n\t})\n\treturn nil\n}\n\nfunc (c *conn) As(_ any) bool {\n\treturn false\n}\n\nfunc (c *conn) teardown() {\n\tfor _, s := range c.allStreams() {\n\t\ts.Reset()\n\t}\n\n\tc.net.removeConn(c)\n}\n\nfunc (c *conn) addStream(s *stream) {\n\tc.Lock()\n\tdefer c.Unlock()\n\ts.conn = c\n\tc.streams.PushBack(s)\n}\n\nfunc (c *conn) removeStream(s *stream) {\n\tc.Lock()\n\tdefer c.Unlock()\n\tfor e := c.streams.Front(); e != nil; e = e.Next() {\n\t\tif s == e.Value {\n\t\t\tc.streams.Remove(e)\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (c *conn) allStreams() []network.Stream {\n\tc.RLock()\n\tdefer c.RUnlock()\n\n\tstrs := make([]network.Stream, 0, c.streams.Len())\n\tfor e := c.streams.Front(); e != nil; e = e.Next() {\n\t\ts := e.Value.(*stream)\n\t\tstrs = append(strs, s)\n\t}\n\treturn strs\n}\n\nfunc (c *conn) remoteOpenedStream(s *stream) {\n\tc.addStream(s)\n\tc.net.handleNewStream(s)\n}\n\nfunc (c *conn) openStream() *stream {\n\tsl, sr := newStreamPair()\n\tgo c.rconn.remoteOpenedStream(sr)\n\tc.addStream(sl)\n\treturn sl\n}\n\nfunc (c *conn) NewStream(context.Context) (network.Stream, error) {\n\tlog.Debug(\"Conn.NewStreamWithProtocol\", \"source_peer\", c.local, \"destination_peer\", c.remote)\n\n\ts := c.openStream()\n\treturn s, nil\n}\n\nfunc (c *conn) GetStreams() []network.Stream {\n\treturn c.allStreams()\n}\n\n// LocalMultiaddr is the Multiaddr on this side\nfunc (c *conn) LocalMultiaddr() ma.Multiaddr {\n\treturn c.localAddr\n}\n\n// LocalPeer is the Peer on our side of the connection\nfunc (c *conn) LocalPeer() peer.ID {\n\treturn c.local\n}\n\n// RemoteMultiaddr is the Multiaddr on the remote side\nfunc (c *conn) RemoteMultiaddr() ma.Multiaddr {\n\treturn c.remoteAddr\n}\n\n// RemotePeer is the Peer on the remote side\nfunc (c *conn) RemotePeer() peer.ID {\n\treturn c.remote\n}\n\n// RemotePublicKey is the private key of the peer on our side.\nfunc (c *conn) RemotePublicKey() ic.PubKey {\n\treturn c.remotePubKey\n}\n\n// ConnState of security connection. Empty if not supported.\nfunc (c *conn) ConnState() network.ConnectionState {\n\treturn network.ConnectionState{}\n}\n\n// Stat returns metadata about the connection\nfunc (c *conn) Stat() network.ConnStats {\n\treturn c.stat\n}\n\nfunc (c *conn) Scope() network.ConnScope {\n\treturn &network.NullScope{}\n}\n\nfunc (c *conn) CloseWithError(_ network.ConnErrorCode) error {\n\treturn c.Close()\n}\n"
  },
  {
    "path": "p2p/net/mock/mock_link.go",
    "content": "package mocknet\n\nimport (\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n)\n\n// link implements mocknet.Link\n// and, for simplicity, network.Conn\ntype link struct {\n\tmock        *mocknet\n\tnets        []*peernet\n\topts        LinkOptions\n\tratelimiter *RateLimiter\n\t// this could have addresses on both sides.\n\n\tsync.RWMutex\n}\n\nfunc newLink(mn *mocknet, opts LinkOptions) *link {\n\tl := &link{mock: mn,\n\t\topts:        opts,\n\t\tratelimiter: NewRateLimiter(opts.Bandwidth)}\n\treturn l\n}\n\nfunc (l *link) newConnPair(dialer *peernet) (*conn, *conn) {\n\tl.RLock()\n\tdefer l.RUnlock()\n\n\ttarget := l.nets[0]\n\tif target == dialer {\n\t\ttarget = l.nets[1]\n\t}\n\tdc := newConn(dialer, target, l, network.DirOutbound)\n\ttc := newConn(target, dialer, l, network.DirInbound)\n\tdc.rconn = tc\n\ttc.rconn = dc\n\treturn dc, tc\n}\n\nfunc (l *link) Networks() []network.Network {\n\tl.RLock()\n\tdefer l.RUnlock()\n\n\tcp := make([]network.Network, len(l.nets))\n\tfor i, n := range l.nets {\n\t\tcp[i] = n\n\t}\n\treturn cp\n}\n\nfunc (l *link) Peers() []peer.ID {\n\tl.RLock()\n\tdefer l.RUnlock()\n\n\tcp := make([]peer.ID, len(l.nets))\n\tfor i, n := range l.nets {\n\t\tcp[i] = n.peer\n\t}\n\treturn cp\n}\n\nfunc (l *link) SetOptions(o LinkOptions) {\n\tl.Lock()\n\tdefer l.Unlock()\n\tl.opts = o\n\tl.ratelimiter.UpdateBandwidth(l.opts.Bandwidth)\n}\n\nfunc (l *link) Options() LinkOptions {\n\tl.RLock()\n\tdefer l.RUnlock()\n\treturn l.opts\n}\n\nfunc (l *link) GetLatency() time.Duration {\n\tl.RLock()\n\tdefer l.RUnlock()\n\treturn l.opts.Latency\n}\n\nfunc (l *link) RateLimit(dataSize int) time.Duration {\n\treturn l.ratelimiter.Limit(dataSize)\n}\n"
  },
  {
    "path": "p2p/net/mock/mock_net.go",
    "content": "package mocknet\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"fmt\"\n\t\"net\"\n\t\"sort\"\n\t\"sync\"\n\n\tic \"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\tbhost \"github.com/libp2p/go-libp2p/p2p/host/basic\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/eventbus\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoremem\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\n// IP6 range that gets blackholed (in case our traffic ever makes it out onto\n// the internet).\nvar blackholeIP6 = net.ParseIP(\"100::\")\n\n// mocknet implements mocknet.Mocknet\ntype mocknet struct {\n\tnets  map[peer.ID]*peernet\n\thosts map[peer.ID]host.Host\n\n\t// links make it possible to connect two peers.\n\t// think of links as the physical medium.\n\t// usually only one, but there could be multiple\n\t// **links are shared between peers**\n\tlinks map[peer.ID]map[peer.ID]map[*link]struct{}\n\n\tlinkDefaults LinkOptions\n\n\tctxCancel context.CancelFunc\n\tctx       context.Context\n\tsync.Mutex\n}\n\nfunc New() Mocknet {\n\tmn := &mocknet{\n\t\tnets:  map[peer.ID]*peernet{},\n\t\thosts: map[peer.ID]host.Host{},\n\t\tlinks: map[peer.ID]map[peer.ID]map[*link]struct{}{},\n\t}\n\tmn.ctx, mn.ctxCancel = context.WithCancel(context.Background())\n\treturn mn\n}\n\nfunc (mn *mocknet) Close() error {\n\tmn.ctxCancel()\n\tfor _, h := range mn.hosts {\n\t\th.Close()\n\t}\n\tfor _, n := range mn.nets {\n\t\tn.Close()\n\t}\n\treturn nil\n}\n\nfunc (mn *mocknet) GenPeer() (host.Host, error) {\n\treturn mn.GenPeerWithOptions(PeerOptions{})\n}\n\nfunc (mn *mocknet) GenPeerWithOptions(opts PeerOptions) (host.Host, error) {\n\tif err := mn.addDefaults(&opts); err != nil {\n\t\treturn nil, err\n\t}\n\tsk, _, err := ic.GenerateECDSAKeyPair(rand.Reader)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tid, err := peer.IDFromPrivateKey(sk)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsuffix := id\n\tif len(id) > 8 {\n\t\tsuffix = id[len(id)-8:]\n\t}\n\tip := append(net.IP{}, blackholeIP6...)\n\tcopy(ip[net.IPv6len-len(suffix):], suffix)\n\ta, err := ma.NewMultiaddr(fmt.Sprintf(\"/ip6/%s/tcp/4242\", ip))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create test multiaddr: %s\", err)\n\t}\n\n\tvar ps peerstore.Peerstore\n\tif opts.ps == nil {\n\t\tps, err = pstoremem.NewPeerstore()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t} else {\n\t\tps = opts.ps\n\t}\n\tp, err := mn.updatePeerstore(sk, a, ps)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\th, err := mn.AddPeerWithOptions(p, opts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn h, nil\n}\n\nfunc (mn *mocknet) AddPeer(k ic.PrivKey, a ma.Multiaddr) (host.Host, error) {\n\tps, err := pstoremem.NewPeerstore()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tp, err := mn.updatePeerstore(k, a, ps)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn mn.AddPeerWithPeerstore(p, ps)\n}\n\nfunc (mn *mocknet) AddPeerWithPeerstore(p peer.ID, ps peerstore.Peerstore) (host.Host, error) {\n\treturn mn.AddPeerWithOptions(p, PeerOptions{ps: ps})\n}\n\nfunc (mn *mocknet) AddPeerWithOptions(p peer.ID, opts PeerOptions) (host.Host, error) {\n\tbus := eventbus.NewBus()\n\tif err := mn.addDefaults(&opts); err != nil {\n\t\treturn nil, err\n\t}\n\tn, err := newPeernet(mn, p, opts, bus)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\thostOpts := &bhost.HostOpts{\n\t\tNegotiationTimeout:      -1,\n\t\tDisableSignedPeerRecord: true,\n\t\tEventBus:                bus,\n\t}\n\n\th, err := bhost.NewHost(n, hostOpts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\th.Start()\n\n\tmn.Lock()\n\tmn.nets[n.peer] = n\n\tmn.hosts[n.peer] = h\n\tmn.Unlock()\n\treturn h, nil\n}\n\nfunc (mn *mocknet) addDefaults(opts *PeerOptions) error {\n\tif opts.ps == nil {\n\t\tps, err := pstoremem.NewPeerstore()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\topts.ps = ps\n\t}\n\treturn nil\n}\n\nfunc (mn *mocknet) updatePeerstore(k ic.PrivKey, a ma.Multiaddr, ps peerstore.Peerstore) (peer.ID, error) {\n\tp, err := peer.IDFromPublicKey(k.GetPublic())\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tps.AddAddr(p, a, peerstore.PermanentAddrTTL)\n\terr = ps.AddPrivKey(p, k)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\terr = ps.AddPubKey(p, k.GetPublic())\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn p, nil\n}\n\nfunc (mn *mocknet) Peers() []peer.ID {\n\tmn.Lock()\n\tdefer mn.Unlock()\n\n\tcp := make([]peer.ID, 0, len(mn.nets))\n\tfor _, n := range mn.nets {\n\t\tcp = append(cp, n.peer)\n\t}\n\tsort.Sort(peer.IDSlice(cp))\n\treturn cp\n}\n\nfunc (mn *mocknet) Host(pid peer.ID) host.Host {\n\tmn.Lock()\n\thost := mn.hosts[pid]\n\tmn.Unlock()\n\treturn host\n}\n\nfunc (mn *mocknet) Net(pid peer.ID) network.Network {\n\tmn.Lock()\n\tn := mn.nets[pid]\n\tmn.Unlock()\n\treturn n\n}\n\nfunc (mn *mocknet) Hosts() []host.Host {\n\tmn.Lock()\n\tdefer mn.Unlock()\n\n\tcp := make([]host.Host, 0, len(mn.hosts))\n\tfor _, h := range mn.hosts {\n\t\tcp = append(cp, h)\n\t}\n\n\tsort.Sort(hostSlice(cp))\n\treturn cp\n}\n\nfunc (mn *mocknet) Nets() []network.Network {\n\tmn.Lock()\n\tdefer mn.Unlock()\n\n\tcp := make([]network.Network, 0, len(mn.nets))\n\tfor _, n := range mn.nets {\n\t\tcp = append(cp, n)\n\t}\n\tsort.Sort(netSlice(cp))\n\treturn cp\n}\n\n// Links returns a copy of the internal link state map.\n// (wow, much map. so data structure. how compose. ahhh pointer)\nfunc (mn *mocknet) Links() LinkMap {\n\tmn.Lock()\n\tdefer mn.Unlock()\n\n\tlinks := map[string]map[string]map[Link]struct{}{}\n\tfor p1, lm := range mn.links {\n\t\tsp1 := string(p1)\n\t\tlinks[sp1] = map[string]map[Link]struct{}{}\n\t\tfor p2, ls := range lm {\n\t\t\tsp2 := string(p2)\n\t\t\tlinks[sp1][sp2] = map[Link]struct{}{}\n\t\t\tfor l := range ls {\n\t\t\t\tlinks[sp1][sp2][l] = struct{}{}\n\t\t\t}\n\t\t}\n\t}\n\treturn links\n}\n\nfunc (mn *mocknet) LinkAll() error {\n\tnets := mn.Nets()\n\tfor _, n1 := range nets {\n\t\tfor _, n2 := range nets {\n\t\t\tif _, err := mn.LinkNets(n1, n2); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (mn *mocknet) LinkPeers(p1, p2 peer.ID) (Link, error) {\n\tmn.Lock()\n\tn1 := mn.nets[p1]\n\tn2 := mn.nets[p2]\n\tmn.Unlock()\n\n\tif n1 == nil {\n\t\treturn nil, fmt.Errorf(\"network for p1 not in mocknet\")\n\t}\n\n\tif n2 == nil {\n\t\treturn nil, fmt.Errorf(\"network for p2 not in mocknet\")\n\t}\n\n\treturn mn.LinkNets(n1, n2)\n}\n\nfunc (mn *mocknet) validate(n network.Network) (*peernet, error) {\n\t// WARNING: assumes locks acquired\n\n\tnr, ok := n.(*peernet)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"network not supported (use mock package nets only)\")\n\t}\n\n\tif _, found := mn.nets[nr.peer]; !found {\n\t\treturn nil, fmt.Errorf(\"network not on mocknet. is it from another mocknet?\")\n\t}\n\n\treturn nr, nil\n}\n\nfunc (mn *mocknet) LinkNets(n1, n2 network.Network) (Link, error) {\n\tmn.Lock()\n\tn1r, err1 := mn.validate(n1)\n\tn2r, err2 := mn.validate(n2)\n\tld := mn.linkDefaults\n\tmn.Unlock()\n\n\tif err1 != nil {\n\t\treturn nil, err1\n\t}\n\tif err2 != nil {\n\t\treturn nil, err2\n\t}\n\n\tl := newLink(mn, ld)\n\tl.nets = append(l.nets, n1r, n2r)\n\tmn.addLink(l)\n\treturn l, nil\n}\n\nfunc (mn *mocknet) Unlink(l2 Link) error {\n\n\tl, ok := l2.(*link)\n\tif !ok {\n\t\treturn fmt.Errorf(\"only links from mocknet are supported\")\n\t}\n\n\tmn.removeLink(l)\n\treturn nil\n}\n\nfunc (mn *mocknet) UnlinkPeers(p1, p2 peer.ID) error {\n\tls := mn.LinksBetweenPeers(p1, p2)\n\tif ls == nil {\n\t\treturn fmt.Errorf(\"no link between p1 and p2\")\n\t}\n\n\tfor _, l := range ls {\n\t\tif err := mn.Unlink(l); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (mn *mocknet) UnlinkNets(n1, n2 network.Network) error {\n\treturn mn.UnlinkPeers(n1.LocalPeer(), n2.LocalPeer())\n}\n\n// get from the links map. and lazily construct.\nfunc (mn *mocknet) linksMapGet(p1, p2 peer.ID) map[*link]struct{} {\n\n\tl1, found := mn.links[p1]\n\tif !found {\n\t\tmn.links[p1] = map[peer.ID]map[*link]struct{}{}\n\t\tl1 = mn.links[p1] // so we make sure it's there.\n\t}\n\n\tl2, found := l1[p2]\n\tif !found {\n\t\tm := map[*link]struct{}{}\n\t\tl1[p2] = m\n\t\tl2 = l1[p2]\n\t}\n\n\treturn l2\n}\n\nfunc (mn *mocknet) addLink(l *link) {\n\tmn.Lock()\n\tdefer mn.Unlock()\n\n\tn1, n2 := l.nets[0], l.nets[1]\n\tmn.linksMapGet(n1.peer, n2.peer)[l] = struct{}{}\n\tmn.linksMapGet(n2.peer, n1.peer)[l] = struct{}{}\n}\n\nfunc (mn *mocknet) removeLink(l *link) {\n\tmn.Lock()\n\tdefer mn.Unlock()\n\n\tn1, n2 := l.nets[0], l.nets[1]\n\tdelete(mn.linksMapGet(n1.peer, n2.peer), l)\n\tdelete(mn.linksMapGet(n2.peer, n1.peer), l)\n}\n\nfunc (mn *mocknet) ConnectAllButSelf() error {\n\tnets := mn.Nets()\n\tfor _, n1 := range nets {\n\t\tfor _, n2 := range nets {\n\t\t\tif n1 == n2 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif _, err := mn.ConnectNets(n1, n2); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (mn *mocknet) ConnectPeers(a, b peer.ID) (network.Conn, error) {\n\treturn mn.Net(a).DialPeer(mn.ctx, b)\n}\n\nfunc (mn *mocknet) ConnectNets(a, b network.Network) (network.Conn, error) {\n\treturn a.DialPeer(mn.ctx, b.LocalPeer())\n}\n\nfunc (mn *mocknet) DisconnectPeers(p1, p2 peer.ID) error {\n\treturn mn.Net(p1).ClosePeer(p2)\n}\n\nfunc (mn *mocknet) DisconnectNets(n1, n2 network.Network) error {\n\treturn n1.ClosePeer(n2.LocalPeer())\n}\n\nfunc (mn *mocknet) LinksBetweenPeers(p1, p2 peer.ID) []Link {\n\tmn.Lock()\n\tdefer mn.Unlock()\n\n\tls2 := mn.linksMapGet(p1, p2)\n\tcp := make([]Link, 0, len(ls2))\n\tfor l := range ls2 {\n\t\tcp = append(cp, l)\n\t}\n\treturn cp\n}\n\nfunc (mn *mocknet) LinksBetweenNets(n1, n2 network.Network) []Link {\n\treturn mn.LinksBetweenPeers(n1.LocalPeer(), n2.LocalPeer())\n}\n\nfunc (mn *mocknet) SetLinkDefaults(o LinkOptions) {\n\tmn.Lock()\n\tmn.linkDefaults = o\n\tmn.Unlock()\n}\n\nfunc (mn *mocknet) LinkDefaults() LinkOptions {\n\tmn.Lock()\n\tdefer mn.Unlock()\n\treturn mn.linkDefaults\n}\n\n// netSlice for sorting by peer\ntype netSlice []network.Network\n\nfunc (es netSlice) Len() int           { return len(es) }\nfunc (es netSlice) Swap(i, j int)      { es[i], es[j] = es[j], es[i] }\nfunc (es netSlice) Less(i, j int) bool { return string(es[i].LocalPeer()) < string(es[j].LocalPeer()) }\n\n// hostSlice for sorting by peer\ntype hostSlice []host.Host\n\nfunc (es hostSlice) Len() int           { return len(es) }\nfunc (es hostSlice) Swap(i, j int)      { es[i], es[j] = es[j], es[i] }\nfunc (es hostSlice) Less(i, j int) bool { return string(es[i].ID()) < string(es[j].ID()) }\n"
  },
  {
    "path": "p2p/net/mock/mock_notif_test.go",
    "content": "package mocknet\n\nimport (\n\t\"context\"\n\t\"maps\"\n\t\"slices\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestNotifications(t *testing.T) {\n\tconst swarmSize = 5\n\tconst timeout = 10 * time.Second\n\n\tmn, err := FullMeshLinked(swarmSize)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer mn.Close()\n\n\t// signup notifs\n\tnets := mn.Nets()\n\tnotifiees := make(map[peer.ID]*netNotifiee, len(nets))\n\tfor _, pn := range nets {\n\t\tdefer pn.Close()\n\n\t\tn := newNetNotifiee(t, swarmSize)\n\t\tpn.Notify(n)\n\t\tnotifiees[pn.LocalPeer()] = n\n\t}\n\n\t// connect all but self\n\tif err := mn.ConnectAllButSelf(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// test everyone got the correct connection opened calls\n\tfor _, s1 := range nets {\n\t\tn := notifiees[s1.LocalPeer()]\n\t\tnotifs := make(map[peer.ID][]network.Conn)\n\t\tfor _, s2 := range nets {\n\t\t\tif s2 == s1 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// this feels a little sketchy, but its probably okay\n\t\t\tfor len(s1.ConnsToPeer(s2.LocalPeer())) != len(notifs[s2.LocalPeer()]) {\n\t\t\t\tselect {\n\t\t\t\tcase c := <-n.connected:\n\t\t\t\t\tnfp := notifs[c.RemotePeer()]\n\t\t\t\t\tnotifs[c.RemotePeer()] = append(nfp, c)\n\t\t\t\tcase <-time.After(timeout):\n\t\t\t\t\tt.Fatal(\"timeout\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor p, cons := range notifs {\n\t\t\texpect := s1.ConnsToPeer(p)\n\t\t\tif len(expect) != len(cons) {\n\t\t\t\tt.Fatal(\"got different number of connections\")\n\t\t\t}\n\n\t\t\tfor _, c := range cons {\n\t\t\t\tvar found bool\n\t\t\t\tif slices.Contains(expect, c) {\n\t\t\t\t\tfound = true\n\t\t\t\t}\n\n\t\t\t\tif !found {\n\t\t\t\t\tt.Fatal(\"connection not found!\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tacceptedStream := make(chan struct{}, 1000)\n\tfor _, s := range nets {\n\t\ts.SetStreamHandler(func(s network.Stream) {\n\t\t\tacceptedStream <- struct{}{}\n\t\t\ts.Close()\n\t\t})\n\t}\n\n\t// Make sure we've received at last one stream per conn.\n\tfor _, s := range nets {\n\t\tconns := s.Conns()\n\t\tfor _, c := range conns {\n\t\t\tst1, err := c.NewStream(context.Background())\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tt.Logf(\"%s %s <--%p--> %s %s\", c.LocalPeer(), c.LocalMultiaddr(), st1, c.RemotePeer(), c.RemoteMultiaddr())\n\t\t\tst1.Close()\n\t\t}\n\t}\n\n\t// close conns\n\tfor _, s1 := range nets {\n\t\tn1 := notifiees[s1.LocalPeer()]\n\t\tfor _, c1 := range s1.Conns() {\n\t\t\tc2 := ConnComplement(c1)\n\n\t\t\tn2 := notifiees[c2.LocalPeer()]\n\t\t\tc1.Close()\n\n\t\t\tvar c3, c4 network.Conn\n\t\t\tselect {\n\t\t\tcase c3 = <-n1.disconnected:\n\t\t\tcase <-time.After(timeout):\n\t\t\t\tt.Fatal(\"timeout\")\n\t\t\t}\n\t\t\tif c1 != c3 {\n\t\t\t\tt.Fatal(\"got incorrect conn\", c1, c3)\n\t\t\t}\n\n\t\t\tselect {\n\t\t\tcase c4 = <-n2.disconnected:\n\t\t\tcase <-time.After(timeout):\n\t\t\t\tt.Fatal(\"timeout\")\n\t\t\t}\n\t\t\tif c2 != c4 {\n\t\t\t\tt.Fatal(\"got incorrect conn\", c1, c2)\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, n1 := range notifiees {\n\t\t// Avoid holding this lock while waiting, otherwise we can deadlock.\n\t\tstreamStateCopy := map[network.Stream]chan struct{}{}\n\t\tn1.streamState.Lock()\n\t\tmaps.Copy(streamStateCopy, n1.streamState.m)\n\t\tn1.streamState.Unlock()\n\n\t\tfor str1, ch1 := range streamStateCopy {\n\t\t\t<-ch1\n\t\t\tstr2 := StreamComplement(str1)\n\t\t\tn2 := notifiees[str1.Conn().RemotePeer()]\n\n\t\t\t// make sure the OpenedStream notification was processed first\n\t\t\tvar ch2 chan struct{}\n\t\t\trequire.Eventually(t, func() bool {\n\t\t\t\tn2.streamState.Lock()\n\t\t\t\tdefer n2.streamState.Unlock()\n\t\t\t\tch, ok := n2.streamState.m[str2]\n\t\t\t\tif ok {\n\t\t\t\t\tch2 = ch\n\t\t\t\t}\n\t\t\t\treturn ok\n\t\t\t}, time.Second, 10*time.Millisecond)\n\n\t\t\t<-ch2\n\t\t}\n\t}\n}\n\ntype netNotifiee struct {\n\tt *testing.T\n\n\tlisten       chan ma.Multiaddr\n\tlistenClose  chan ma.Multiaddr\n\tconnected    chan network.Conn\n\tdisconnected chan network.Conn\n\n\tstreamState struct {\n\t\tsync.Mutex\n\t\tm map[network.Stream]chan struct{}\n\t}\n}\n\nfunc newNetNotifiee(t *testing.T, buffer int) *netNotifiee {\n\tnn := &netNotifiee{\n\t\tt:            t,\n\t\tlisten:       make(chan ma.Multiaddr, 1),\n\t\tlistenClose:  make(chan ma.Multiaddr, 1),\n\t\tconnected:    make(chan network.Conn, buffer*2),\n\t\tdisconnected: make(chan network.Conn, buffer*2),\n\t}\n\tnn.streamState.m = make(map[network.Stream]chan struct{})\n\treturn nn\n}\n\nfunc (nn *netNotifiee) Listen(_ network.Network, a ma.Multiaddr) {\n\tnn.listen <- a\n}\nfunc (nn *netNotifiee) ListenClose(_ network.Network, a ma.Multiaddr) {\n\tnn.listenClose <- a\n}\nfunc (nn *netNotifiee) Connected(_ network.Network, v network.Conn) {\n\tnn.connected <- v\n}\nfunc (nn *netNotifiee) Disconnected(_ network.Network, v network.Conn) {\n\tnn.disconnected <- v\n}\n"
  },
  {
    "path": "p2p/net/mock/mock_peernet.go",
    "content": "package mocknet\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"sync\"\n\n\t\"github.com/libp2p/go-libp2p/core/connmgr\"\n\t\"github.com/libp2p/go-libp2p/core/event\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\n// peernet implements network.Network\ntype peernet struct {\n\tmocknet *mocknet // parent\n\n\tpeer    peer.ID\n\tps      peerstore.Peerstore\n\temitter event.Emitter\n\n\t// conns are actual live connections between peers.\n\t// many conns could run over each link.\n\t// **conns are NOT shared between peers**\n\tconnsByPeer map[peer.ID]map[*conn]struct{}\n\tconnsByLink map[*link]map[*conn]struct{}\n\n\t// connection gater to check before dialing or accepting connections. May be nil to allow all.\n\tgater connmgr.ConnectionGater\n\n\t// implement network.Network\n\tstreamHandler network.StreamHandler\n\n\tnotifmu sync.Mutex\n\tnotifs  map[network.Notifiee]struct{}\n\n\tsync.RWMutex\n}\n\n// newPeernet constructs a new peernet\nfunc newPeernet(m *mocknet, p peer.ID, opts PeerOptions, bus event.Bus) (*peernet, error) {\n\temitter, err := bus.Emitter(&event.EvtPeerConnectednessChanged{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tn := &peernet{\n\t\tmocknet: m,\n\t\tpeer:    p,\n\t\tps:      opts.ps,\n\t\tgater:   opts.gater,\n\t\temitter: emitter,\n\n\t\tconnsByPeer: map[peer.ID]map[*conn]struct{}{},\n\t\tconnsByLink: map[*link]map[*conn]struct{}{},\n\n\t\tnotifs: make(map[network.Notifiee]struct{}),\n\t}\n\n\treturn n, nil\n}\n\nfunc (pn *peernet) Close() error {\n\t// close the connections\n\tfor _, c := range pn.allConns() {\n\t\tc.Close()\n\t}\n\tpn.emitter.Close()\n\treturn pn.ps.Close()\n}\n\n// allConns returns all the connections between this peer and others\nfunc (pn *peernet) allConns() []*conn {\n\tpn.RLock()\n\tvar cs []*conn\n\tfor _, csl := range pn.connsByPeer {\n\t\tfor c := range csl {\n\t\t\tcs = append(cs, c)\n\t\t}\n\t}\n\tpn.RUnlock()\n\treturn cs\n}\n\nfunc (pn *peernet) Peerstore() peerstore.Peerstore {\n\treturn pn.ps\n}\n\nfunc (pn *peernet) String() string {\n\treturn fmt.Sprintf(\"<mock.peernet %s - %d conns>\", pn.peer, len(pn.allConns()))\n}\n\n// handleNewStream is an internal function to trigger the client's handler\nfunc (pn *peernet) handleNewStream(s network.Stream) {\n\tpn.RLock()\n\thandler := pn.streamHandler\n\tpn.RUnlock()\n\tif handler != nil {\n\t\tgo handler(s)\n\t}\n}\n\n// DialPeer attempts to establish a connection to a given peer.\n// Respects the context.\nfunc (pn *peernet) DialPeer(_ context.Context, p peer.ID) (network.Conn, error) {\n\treturn pn.connect(p)\n}\n\nfunc (pn *peernet) connect(p peer.ID) (*conn, error) {\n\tif p == pn.peer {\n\t\treturn nil, fmt.Errorf(\"attempted to dial self %s\", p)\n\t}\n\n\t// first, check if we already have live connections\n\tpn.RLock()\n\tcs, found := pn.connsByPeer[p]\n\tif found && len(cs) > 0 {\n\t\tvar chosen *conn\n\t\tfor c := range cs { // because cs is a map\n\t\t\tchosen = c // select first\n\t\t\tbreak\n\t\t}\n\t\tpn.RUnlock()\n\t\treturn chosen, nil\n\t}\n\tpn.RUnlock()\n\n\tif pn.gater != nil && !pn.gater.InterceptPeerDial(p) {\n\t\tlog.Debug(\"gater disallowed outbound connection to peer\", \"peer\", p)\n\t\treturn nil, fmt.Errorf(\"%v connection gater disallowed connection to %v\", pn.peer, p)\n\t}\n\tlog.Debug(\"(newly) dialing peer\", \"source_peer\", pn.peer, \"destination_peer\", p)\n\n\t// ok, must create a new connection. we need a link\n\tlinks := pn.mocknet.LinksBetweenPeers(pn.peer, p)\n\tif len(links) < 1 {\n\t\treturn nil, fmt.Errorf(\"%s cannot connect to %s\", pn.peer, p)\n\t}\n\n\t// if many links found, how do we select? for now, randomly...\n\t// this would be an interesting place to test logic that can measure\n\t// links (network interfaces) and select properly\n\tl := links[rand.Intn(len(links))]\n\n\tlog.Debug(\"dialing peer openingConn\", \"source_peer\", pn.peer, \"destination_peer\", p)\n\t// create a new connection with link\n\treturn pn.openConn(p, l.(*link))\n}\n\nfunc (pn *peernet) openConn(_ peer.ID, l *link) (*conn, error) {\n\tlc, rc := l.newConnPair(pn)\n\taddConnPair(pn, rc.net, lc, rc)\n\tlog.Debug(\"opening connection\", \"source_peer\", pn.LocalPeer(), \"destination_peer\", lc.RemotePeer())\n\tabort := func() {\n\t\t_ = lc.Close()\n\t\t_ = rc.Close()\n\t}\n\tif pn.gater != nil && !pn.gater.InterceptAddrDial(lc.remote, lc.remoteAddr) {\n\t\tabort()\n\t\treturn nil, fmt.Errorf(\"%v rejected dial to %v on addr %v\", lc.local, lc.remote, lc.remoteAddr)\n\t}\n\tif rc.net.gater != nil && !rc.net.gater.InterceptAccept(rc) {\n\t\tabort()\n\t\treturn nil, fmt.Errorf(\"%v rejected connection from %v\", rc.local, rc.remote)\n\t}\n\tif err := checkSecureAndUpgrade(network.DirOutbound, pn.gater, lc); err != nil {\n\t\tabort()\n\t\treturn nil, err\n\t}\n\tif err := checkSecureAndUpgrade(network.DirInbound, rc.net.gater, rc); err != nil {\n\t\tabort()\n\t\treturn nil, err\n\t}\n\n\tgo rc.net.remoteOpenedConn(rc)\n\tpn.addConn(lc)\n\treturn lc, nil\n}\n\nfunc checkSecureAndUpgrade(dir network.Direction, gater connmgr.ConnectionGater, c *conn) error {\n\tif gater == nil {\n\t\treturn nil\n\t}\n\tif !gater.InterceptSecured(dir, c.remote, c) {\n\t\treturn fmt.Errorf(\"%v rejected secure handshake with %v\", c.local, c.remote)\n\t}\n\tallow, _ := gater.InterceptUpgraded(c)\n\tif !allow {\n\t\treturn fmt.Errorf(\"%v rejected upgrade with %v\", c.local, c.remote)\n\t}\n\treturn nil\n}\n\n// addConnPair adds connection to both peernets at the same time\n// must be followerd by pn1.addConn(c1) and pn2.addConn(c2)\nfunc addConnPair(pn1, pn2 *peernet, c1, c2 *conn) {\n\tvar l1, l2 = pn1, pn2 // peernets in lock order\n\t// bytes compare as string compare is lexicographical\n\tif bytes.Compare([]byte(l1.LocalPeer()), []byte(l2.LocalPeer())) > 0 {\n\t\tl1, l2 = l2, l1\n\t}\n\n\tl1.Lock()\n\tl2.Lock()\n\n\tadd := func(pn *peernet, c *conn) {\n\t\t_, found := pn.connsByPeer[c.RemotePeer()]\n\t\tif !found {\n\t\t\tpn.connsByPeer[c.RemotePeer()] = map[*conn]struct{}{}\n\t\t}\n\t\tpn.connsByPeer[c.RemotePeer()][c] = struct{}{}\n\n\t\t_, found = pn.connsByLink[c.link]\n\t\tif !found {\n\t\t\tpn.connsByLink[c.link] = map[*conn]struct{}{}\n\t\t}\n\t\tpn.connsByLink[c.link][c] = struct{}{}\n\t}\n\tadd(pn1, c1)\n\tadd(pn2, c2)\n\n\tc1.notifLk.Lock()\n\tc2.notifLk.Lock()\n\tl2.Unlock()\n\tl1.Unlock()\n}\n\nfunc (pn *peernet) remoteOpenedConn(c *conn) {\n\tlog.Debug(\"accepting connection\", \"source_peer\", pn.LocalPeer(), \"destination_peer\", c.RemotePeer())\n\tpn.addConn(c)\n}\n\n// addConn constructs and adds a connection\n// to given remote peer over given link\nfunc (pn *peernet) addConn(c *conn) {\n\tdefer c.notifLk.Unlock()\n\n\tpn.notifyAll(func(n network.Notifiee) {\n\t\tn.Connected(pn, c)\n\t})\n\n\tpn.emitter.Emit(event.EvtPeerConnectednessChanged{\n\t\tPeer:          c.remote,\n\t\tConnectedness: network.Connected,\n\t})\n}\n\n// removeConn removes a given conn\nfunc (pn *peernet) removeConn(c *conn) {\n\tpn.Lock()\n\tcs, found := pn.connsByLink[c.link]\n\tif !found || len(cs) < 1 {\n\t\tpanic(fmt.Sprintf(\"attempting to remove a conn that doesnt exist %p\", c.link))\n\t}\n\tdelete(cs, c)\n\n\tcs, found = pn.connsByPeer[c.remote]\n\tif !found {\n\t\tpanic(fmt.Sprintf(\"attempting to remove a conn that doesnt exist %v\", c.remote))\n\t}\n\tdelete(cs, c)\n\tpn.Unlock()\n\n\t// notify asynchronously to mimic Swarm\n\t// FIXME: IIRC, we wanted to make notify for Close synchronous\n\tgo func() {\n\t\tc.notifLk.Lock()\n\t\tdefer c.notifLk.Unlock()\n\t\tpn.notifyAll(func(n network.Notifiee) {\n\t\t\tn.Disconnected(c.net, c)\n\t\t})\n\t}()\n\n\tc.net.emitter.Emit(event.EvtPeerConnectednessChanged{\n\t\tPeer:          c.remote,\n\t\tConnectedness: network.NotConnected,\n\t})\n}\n\n// LocalPeer the network's LocalPeer\nfunc (pn *peernet) LocalPeer() peer.ID {\n\treturn pn.peer\n}\n\n// Peers returns the connected peers\nfunc (pn *peernet) Peers() []peer.ID {\n\tpn.RLock()\n\tdefer pn.RUnlock()\n\n\tpeers := make([]peer.ID, 0, len(pn.connsByPeer))\n\tfor _, cs := range pn.connsByPeer {\n\t\tfor c := range cs {\n\t\t\tpeers = append(peers, c.remote)\n\t\t\tbreak\n\t\t}\n\t}\n\treturn peers\n}\n\n// Conns returns all the connections of this peer\nfunc (pn *peernet) Conns() []network.Conn {\n\tpn.RLock()\n\tdefer pn.RUnlock()\n\n\tout := make([]network.Conn, 0, len(pn.connsByPeer))\n\tfor _, cs := range pn.connsByPeer {\n\t\tfor c := range cs {\n\t\t\tout = append(out, c)\n\t\t}\n\t}\n\treturn out\n}\n\nfunc (pn *peernet) ConnsToPeer(p peer.ID) []network.Conn {\n\tpn.RLock()\n\tdefer pn.RUnlock()\n\n\tcs, found := pn.connsByPeer[p]\n\tif !found || len(cs) == 0 {\n\t\treturn nil\n\t}\n\n\tcs2 := make([]network.Conn, 0, len(cs))\n\tfor c := range cs {\n\t\tcs2 = append(cs2, c)\n\t}\n\treturn cs2\n}\n\n// ClosePeer connections to peer\nfunc (pn *peernet) ClosePeer(p peer.ID) error {\n\tpn.RLock()\n\tcs, found := pn.connsByPeer[p]\n\tif !found {\n\t\tpn.RUnlock()\n\t\treturn nil\n\t}\n\n\tconns := make([]*conn, 0, len(cs))\n\tfor c := range cs {\n\t\tconns = append(conns, c)\n\t}\n\tpn.RUnlock()\n\tfor _, c := range conns {\n\t\tc.Close()\n\t}\n\treturn nil\n}\n\n// BandwidthTotals returns the total amount of bandwidth transferred\nfunc (pn *peernet) BandwidthTotals() (in uint64, out uint64) {\n\t// need to implement this. probably best to do it in swarm this time.\n\t// need a \"metrics\" object\n\treturn 0, 0\n}\n\n// Listen tells the network to start listening on given multiaddrs.\nfunc (pn *peernet) Listen(addrs ...ma.Multiaddr) error {\n\tpn.Peerstore().AddAddrs(pn.LocalPeer(), addrs, peerstore.PermanentAddrTTL)\n\tfor _, a := range addrs {\n\t\tpn.notifyAll(func(n network.Notifiee) {\n\t\t\tn.Listen(pn, a)\n\t\t})\n\t}\n\treturn nil\n}\n\n// ListenAddresses returns a list of addresses at which this network listens.\nfunc (pn *peernet) ListenAddresses() []ma.Multiaddr {\n\treturn pn.Peerstore().Addrs(pn.LocalPeer())\n}\n\n// InterfaceListenAddresses returns a list of addresses at which this network\n// listens. It expands \"any interface\" addresses (/ip4/0.0.0.0, /ip6/::) to\n// use the known local interfaces.\nfunc (pn *peernet) InterfaceListenAddresses() ([]ma.Multiaddr, error) {\n\treturn pn.ListenAddresses(), nil\n}\n\n// Connectedness returns a state signaling connection capabilities\n// For now only returns Connecter || NotConnected. Expand into more later.\nfunc (pn *peernet) Connectedness(p peer.ID) network.Connectedness {\n\tpn.Lock()\n\tdefer pn.Unlock()\n\n\tcs, found := pn.connsByPeer[p]\n\tif found && len(cs) > 0 {\n\t\treturn network.Connected\n\t}\n\treturn network.NotConnected\n}\n\n// NewStream returns a new stream to given peer p.\n// If there is no connection to p, attempts to create one.\nfunc (pn *peernet) NewStream(ctx context.Context, p peer.ID) (network.Stream, error) {\n\tc, err := pn.DialPeer(ctx, p)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn c.NewStream(ctx)\n}\n\n// SetStreamHandler sets the new stream handler on the Network.\n// This operation is thread-safe.\nfunc (pn *peernet) SetStreamHandler(h network.StreamHandler) {\n\tpn.Lock()\n\tpn.streamHandler = h\n\tpn.Unlock()\n}\n\n// Notify signs up Notifiee to receive signals when events happen\nfunc (pn *peernet) Notify(f network.Notifiee) {\n\tpn.notifmu.Lock()\n\tpn.notifs[f] = struct{}{}\n\tpn.notifmu.Unlock()\n}\n\n// StopNotify unregisters Notifiee from receiving signals\nfunc (pn *peernet) StopNotify(f network.Notifiee) {\n\tpn.notifmu.Lock()\n\tdelete(pn.notifs, f)\n\tpn.notifmu.Unlock()\n}\n\n// notifyAll runs the notification function on all Notifiees\nfunc (pn *peernet) notifyAll(notification func(f network.Notifiee)) {\n\tpn.notifmu.Lock()\n\t// notify synchronously to mimic Swarm\n\tfor n := range pn.notifs {\n\t\tnotification(n)\n\t}\n\tpn.notifmu.Unlock()\n}\n\nfunc (pn *peernet) ResourceManager() network.ResourceManager {\n\treturn &network.NullResourceManager{}\n}\n\nfunc (pn *peernet) CanDial(_ peer.ID, _ ma.Multiaddr) bool {\n\treturn true\n}\n"
  },
  {
    "path": "p2p/net/mock/mock_printer.go",
    "content": "package mocknet\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n)\n\n// separate object so our interfaces are separate :)\ntype printer struct {\n\tw io.Writer\n}\n\nfunc (p *printer) MocknetLinks(mn Mocknet) {\n\tlinks := mn.Links()\n\n\tfmt.Fprintf(p.w, \"Mocknet link map:\\n\")\n\tfor p1, lm := range links {\n\t\tfmt.Fprintf(p.w, \"\\t%s linked to:\\n\", peer.ID(p1))\n\t\tfor p2, l := range lm {\n\t\t\tfmt.Fprintf(p.w, \"\\t\\t%s (%d links)\\n\", peer.ID(p2), len(l))\n\t\t}\n\t}\n\tfmt.Fprintf(p.w, \"\\n\")\n}\n\nfunc (p *printer) NetworkConns(ni network.Network) {\n\n\tfmt.Fprintf(p.w, \"%s connected to:\\n\", ni.LocalPeer())\n\tfor _, c := range ni.Conns() {\n\t\tfmt.Fprintf(p.w, \"\\t%s (addr: %s)\\n\", c.RemotePeer(), c.RemoteMultiaddr())\n\t}\n\tfmt.Fprintf(p.w, \"\\n\")\n}\n"
  },
  {
    "path": "p2p/net/mock/mock_stream.go",
    "content": "package mocknet\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"io\"\n\t\"strconv\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n)\n\nvar streamCounter atomic.Int64\n\n// stream implements network.Stream\ntype stream struct {\n\trstream *stream\n\tconn    *conn\n\tid      int64\n\n\twrite     *io.PipeWriter\n\tread      *io.PipeReader\n\ttoDeliver chan *transportObject\n\n\treset  chan struct{}\n\tclose  chan struct{}\n\tclosed chan struct{}\n\n\twriteErr error\n\n\tprotocol atomic.Pointer[protocol.ID]\n\tstat     network.Stats\n}\n\nvar ErrClosed = errors.New(\"stream closed\")\n\ntype transportObject struct {\n\tmsg         []byte\n\tarrivalTime time.Time\n}\n\nfunc newStreamPair() (*stream, *stream) {\n\tra, wb := io.Pipe()\n\trb, wa := io.Pipe()\n\n\tsa := newStream(wa, ra, network.DirOutbound)\n\tsb := newStream(wb, rb, network.DirInbound)\n\tsa.rstream = sb\n\tsb.rstream = sa\n\treturn sa, sb\n}\n\nfunc newStream(w *io.PipeWriter, r *io.PipeReader, dir network.Direction) *stream {\n\ts := &stream{\n\t\tread:      r,\n\t\twrite:     w,\n\t\tid:        streamCounter.Add(1),\n\t\treset:     make(chan struct{}, 1),\n\t\tclose:     make(chan struct{}, 1),\n\t\tclosed:    make(chan struct{}),\n\t\ttoDeliver: make(chan *transportObject),\n\t\tstat:      network.Stats{Direction: dir},\n\t}\n\n\tgo s.transport()\n\treturn s\n}\n\n// How to handle errors with writes?\nfunc (s *stream) Write(p []byte) (n int, err error) {\n\tl := s.conn.link\n\tdelay := l.GetLatency() + l.RateLimit(len(p))\n\tt := time.Now().Add(delay)\n\n\t// Copy it.\n\tcpy := make([]byte, len(p))\n\tcopy(cpy, p)\n\n\tselect {\n\tcase <-s.closed: // bail out if we're closing.\n\t\treturn 0, s.writeErr\n\tcase s.toDeliver <- &transportObject{msg: cpy, arrivalTime: t}:\n\t}\n\treturn len(p), nil\n}\n\nfunc (s *stream) ID() string {\n\treturn strconv.FormatInt(s.id, 10)\n}\n\nfunc (s *stream) Protocol() protocol.ID {\n\tp := s.protocol.Load()\n\tif p == nil {\n\t\treturn \"\"\n\t}\n\treturn *p\n}\n\nfunc (s *stream) Stat() network.Stats {\n\treturn s.stat\n}\n\nfunc (s *stream) SetProtocol(proto protocol.ID) error {\n\ts.protocol.Store(&proto)\n\treturn nil\n}\n\nfunc (s *stream) CloseWrite() error {\n\tselect {\n\tcase s.close <- struct{}{}:\n\tdefault:\n\t}\n\t<-s.closed\n\tif s.writeErr != ErrClosed {\n\t\treturn s.writeErr\n\t}\n\treturn nil\n}\n\nfunc (s *stream) CloseRead() error {\n\treturn s.read.CloseWithError(ErrClosed)\n}\n\nfunc (s *stream) Close() error {\n\t_ = s.CloseRead()\n\treturn s.CloseWrite()\n}\n\nfunc (s *stream) Reset() error {\n\t// Cancel any pending reads/writes with an error.\n\ts.write.CloseWithError(network.ErrReset)\n\ts.read.CloseWithError(network.ErrReset)\n\n\tselect {\n\tcase s.reset <- struct{}{}:\n\tdefault:\n\t}\n\t<-s.closed\n\n\t// No meaningful error case here.\n\treturn nil\n}\n\n// ResetWithError resets the stream. It ignores the provided error code.\n// TODO: Implement error code support.\nfunc (s *stream) ResetWithError(_ network.StreamErrorCode) error {\n\t// Cancel any pending reads/writes with an error.\n\n\ts.write.CloseWithError(network.ErrReset)\n\ts.read.CloseWithError(network.ErrReset)\n\n\tselect {\n\tcase s.reset <- struct{}{}:\n\tdefault:\n\t}\n\t<-s.closed\n\n\t// No meaningful error case here.\n\treturn nil\n}\n\nfunc (s *stream) teardown() {\n\t// at this point, no streams are writing.\n\ts.conn.removeStream(s)\n\n\t// Mark as closed.\n\tclose(s.closed)\n}\n\nfunc (s *stream) Conn() network.Conn {\n\treturn s.conn\n}\n\n// SetDeadline is a noop for mocknet streams since the underlying pipe\n// transport does not support deadlines. Callers should not treat the\n// absence of deadline support as an error.\nfunc (s *stream) SetDeadline(_ time.Time) error      { return nil }\nfunc (s *stream) SetReadDeadline(_ time.Time) error  { return nil }\nfunc (s *stream) SetWriteDeadline(_ time.Time) error { return nil }\n\nfunc (s *stream) Read(b []byte) (int, error) {\n\treturn s.read.Read(b)\n}\n\n// transport will grab message arrival times, wait until that time, and\n// then write the message out when it is scheduled to arrive\nfunc (s *stream) transport() {\n\tdefer s.teardown()\n\n\tbufsize := 256\n\tbuf := new(bytes.Buffer)\n\ttimer := time.NewTimer(0)\n\tif !timer.Stop() {\n\t\tselect {\n\t\tcase <-timer.C:\n\t\tdefault:\n\t\t}\n\t}\n\n\t// cleanup\n\tdefer timer.Stop()\n\n\t// writeBuf writes the contents of buf through to the s.Writer.\n\t// done only when arrival time makes sense.\n\tdrainBuf := func() error {\n\t\tif buf.Len() > 0 {\n\t\t\t_, err := s.write.Write(buf.Bytes())\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tbuf.Reset()\n\t\t}\n\t\treturn nil\n\t}\n\n\t// deliverOrWait is a helper func that processes\n\t// an incoming packet. it waits until the arrival time,\n\t// and then writes things out.\n\tdeliverOrWait := func(o *transportObject) error {\n\t\tbuffered := len(o.msg) + buf.Len()\n\n\t\t// Yes, we can end up extending a timer multiple times if we\n\t\t// keep on making small writes but that shouldn't be too much of an\n\t\t// issue. Fixing that would be painful.\n\t\tif !timer.Stop() {\n\t\t\t// FIXME: So, we *shouldn't* need to do this but we hang\n\t\t\t// here if we don't... Go bug?\n\t\t\tselect {\n\t\t\tcase <-timer.C:\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t\tdelay := time.Until(o.arrivalTime)\n\t\tif delay >= 0 {\n\t\t\ttimer.Reset(delay)\n\t\t} else {\n\t\t\ttimer.Reset(0)\n\t\t}\n\n\t\tif buffered >= bufsize {\n\t\t\tselect {\n\t\t\tcase <-timer.C:\n\t\t\tcase <-s.reset:\n\t\t\t\tselect {\n\t\t\t\tcase s.reset <- struct{}{}:\n\t\t\t\tdefault:\n\t\t\t\t}\n\t\t\t\treturn network.ErrReset\n\t\t\t}\n\t\t\tif err := drainBuf(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t// write this message.\n\t\t\t_, err := s.write.Write(o.msg)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t} else {\n\t\t\tbuf.Write(o.msg)\n\t\t}\n\t\treturn nil\n\t}\n\n\tfor {\n\t\t// Reset takes precedent.\n\t\tselect {\n\t\tcase <-s.reset:\n\t\t\ts.writeErr = network.ErrReset\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\n\t\tselect {\n\t\tcase <-s.reset:\n\t\t\ts.writeErr = network.ErrReset\n\t\t\treturn\n\t\tcase <-s.close:\n\t\t\tif err := drainBuf(); err != nil {\n\t\t\t\ts.cancelWrite(err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\ts.writeErr = s.write.Close()\n\t\t\tif s.writeErr == nil {\n\t\t\t\ts.writeErr = ErrClosed\n\t\t\t}\n\t\t\treturn\n\t\tcase o := <-s.toDeliver:\n\t\t\tif err := deliverOrWait(o); err != nil {\n\t\t\t\ts.cancelWrite(err)\n\t\t\t\treturn\n\t\t\t}\n\t\tcase <-timer.C: // ok, due to write it out.\n\t\t\tif err := drainBuf(); err != nil {\n\t\t\t\ts.cancelWrite(err)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (s *stream) Scope() network.StreamScope {\n\treturn &network.NullScope{}\n}\n\nfunc (s *stream) cancelWrite(err error) {\n\ts.write.CloseWithError(err)\n\ts.writeErr = err\n}\n"
  },
  {
    "path": "p2p/net/mock/mock_test.go",
    "content": "package mocknet\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/rand\"\n\t\"fmt\"\n\t\"io\"\n\t\"math\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/event\"\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\t\"github.com/libp2p/go-libp2p/p2p/net/conngater\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n\n\t\"github.com/libp2p/go-libp2p-testing/ci\"\n\ttetc \"github.com/libp2p/go-libp2p-testing/etc\"\n\t\"github.com/libp2p/go-libp2p-testing/race\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nvar lastPort = struct {\n\tport int\n\tsync.Mutex\n}{}\n\n// randLocalTCPAddress returns a random multiaddr. it suppresses errors\n// for nice composability-- do check the address isn't nil.\n//\n// NOTE: for real network tests, use a :0 address, so the kernel\n// assigns an unused TCP port. otherwise you may get clashes.\nfunc randLocalTCPAddress() ma.Multiaddr {\n\t// chances are it will work out, but it **might** fail if the port is in use\n\t// most ports above 10000 aren't in use by long running processes, so yay.\n\t// (maybe there should be a range of \"loopback\" ports that are guaranteed\n\t// to be open for the process, but naturally can only talk to self.)\n\n\tlastPort.Lock()\n\tif lastPort.port == 0 {\n\t\tlastPort.port = 10000 + tetc.SeededRand.Intn(50000)\n\t}\n\tport := lastPort.port\n\tlastPort.port++\n\tlastPort.Unlock()\n\n\taddr := fmt.Sprintf(\"/ip4/127.0.0.1/tcp/%d\", port)\n\tmaddr, _ := ma.NewMultiaddr(addr)\n\treturn maddr\n}\n\nfunc TestNetworkSetup(t *testing.T) {\n\tctx := context.Background()\n\tpriv1, _, err := crypto.GenerateEd25519Key(rand.Reader)\n\trequire.NoError(t, err)\n\tpriv2, _, err := crypto.GenerateEd25519Key(rand.Reader)\n\trequire.NoError(t, err)\n\tpriv3, _, err := crypto.GenerateEd25519Key(rand.Reader)\n\trequire.NoError(t, err)\n\tmn := New()\n\tdefer mn.Close()\n\n\t// add peers to mock net\n\n\ta1 := randLocalTCPAddress()\n\ta2 := randLocalTCPAddress()\n\ta3 := randLocalTCPAddress()\n\n\th1, err := mn.AddPeer(priv1, a1)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tp1 := h1.ID()\n\n\th2, err := mn.AddPeer(priv2, a2)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tp2 := h2.ID()\n\n\th3, err := mn.AddPeer(priv3, a3)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tp3 := h3.ID()\n\n\t// check peers and net\n\tif mn.Host(p1) != h1 {\n\t\tt.Error(\"host for p1.ID != h1\")\n\t}\n\tif mn.Host(p2) != h2 {\n\t\tt.Error(\"host for p2.ID != h2\")\n\t}\n\tif mn.Host(p3) != h3 {\n\t\tt.Error(\"host for p3.ID != h3\")\n\t}\n\n\tn1 := h1.Network()\n\tif mn.Net(p1) != n1 {\n\t\tt.Error(\"net for p1.ID != n1\")\n\t}\n\tn2 := h2.Network()\n\tif mn.Net(p2) != n2 {\n\t\tt.Error(\"net for p2.ID != n1\")\n\t}\n\tn3 := h3.Network()\n\tif mn.Net(p3) != n3 {\n\t\tt.Error(\"net for p3.ID != n1\")\n\t}\n\n\t// link p1<-->p2, p1<-->p1, p2<-->p3, p3<-->p2\n\n\tl12, err := mn.LinkPeers(p1, p2)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif !(l12.Networks()[0] == n1 && l12.Networks()[1] == n2) &&\n\t\t!(l12.Networks()[0] == n2 && l12.Networks()[1] == n1) {\n\t\tt.Error(\"l12 networks incorrect\")\n\t}\n\n\tl11, err := mn.LinkPeers(p1, p1)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif !(l11.Networks()[0] == n1 && l11.Networks()[1] == n1) {\n\t\tt.Error(\"l11 networks incorrect\")\n\t}\n\n\tl23, err := mn.LinkPeers(p2, p3)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif !(l23.Networks()[0] == n2 && l23.Networks()[1] == n3) &&\n\t\t!(l23.Networks()[0] == n3 && l23.Networks()[1] == n2) {\n\t\tt.Error(\"l23 networks incorrect\")\n\t}\n\n\tl32, err := mn.LinkPeers(p3, p2)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif !(l32.Networks()[0] == n2 && l32.Networks()[1] == n3) &&\n\t\t!(l32.Networks()[0] == n3 && l32.Networks()[1] == n2) {\n\t\tt.Error(\"l32 networks incorrect\")\n\t}\n\n\t// check things\n\n\tlinks12 := mn.LinksBetweenPeers(p1, p2)\n\tif len(links12) != 1 {\n\t\tt.Errorf(\"should be 1 link bt. p1 and p2 (found %d)\", len(links12))\n\t}\n\tif links12[0] != l12 {\n\t\tt.Error(\"links 1-2 should be l12.\")\n\t}\n\n\tlinks11 := mn.LinksBetweenPeers(p1, p1)\n\tif len(links11) != 1 {\n\t\tt.Errorf(\"should be 1 link bt. p1 and p1 (found %d)\", len(links11))\n\t}\n\tif links11[0] != l11 {\n\t\tt.Error(\"links 1-1 should be l11.\")\n\t}\n\n\tlinks23 := mn.LinksBetweenPeers(p2, p3)\n\tif len(links23) != 2 {\n\t\tt.Errorf(\"should be 2 link bt. p2 and p3 (found %d)\", len(links23))\n\t}\n\tif !((links23[0] == l23 && links23[1] == l32) ||\n\t\t(links23[0] == l32 && links23[1] == l23)) {\n\t\tt.Error(\"links 2-3 should be l23 and l32.\")\n\t}\n\n\t// unlinking\n\n\tif err := mn.UnlinkPeers(p2, p1); err != nil {\n\t\tt.Error(err)\n\t}\n\n\t// check only one link affected:\n\n\tlinks12 = mn.LinksBetweenPeers(p1, p2)\n\tif len(links12) != 0 {\n\t\tt.Error(\"should be 0 now...\", len(links12))\n\t}\n\n\tlinks11 = mn.LinksBetweenPeers(p1, p1)\n\tif len(links11) != 1 {\n\t\tt.Errorf(\"should be 1 link bt. p1 and p1 (found %d)\", len(links11))\n\t}\n\tif links11[0] != l11 {\n\t\tt.Error(\"links 1-1 should be l11.\")\n\t}\n\n\tlinks23 = mn.LinksBetweenPeers(p2, p3)\n\tif len(links23) != 2 {\n\t\tt.Errorf(\"should be 2 link bt. p2 and p3 (found %d)\", len(links23))\n\t}\n\tif !((links23[0] == l23 && links23[1] == l32) ||\n\t\t(links23[0] == l32 && links23[1] == l23)) {\n\t\tt.Error(\"links 2-3 should be l23 and l32.\")\n\t}\n\n\t// check connecting\n\n\t// first, no conns\n\tif len(n2.Conns()) > 0 || len(n3.Conns()) > 0 {\n\t\tt.Errorf(\"should have 0 conn. Got: (%d, %d)\", len(n2.Conns()), len(n3.Conns()))\n\t}\n\n\t// connect p2->p3\n\tif _, err := n2.DialPeer(ctx, p3); err != nil {\n\t\tt.Error(err)\n\t}\n\n\t// should immediately have a conn on peer 1\n\tif len(n2.Conns()) != 1 {\n\t\tt.Errorf(\"should have 1 conn on initiator. Got: %d)\", len(n2.Conns()))\n\t}\n\n\t// wait for reciever to see the conn.\n\tfor i := 0; i < 10 && len(n3.Conns()) == 0; i++ {\n\t\ttime.Sleep(time.Duration(10*i) * time.Millisecond)\n\t}\n\n\tif len(n3.Conns()) != 1 {\n\t\tt.Errorf(\"should have 1 conn on reciever. Got: %d\", len(n3.Conns()))\n\t}\n\n\t// p := PrinterTo(os.Stdout)\n\t// p.NetworkConns(n1)\n\t// p.NetworkConns(n2)\n\t// p.NetworkConns(n3)\n\n\t// can create a stream 2->3, 3->2,\n\tif _, err := n2.NewStream(ctx, p3); err != nil {\n\t\tt.Error(err)\n\t}\n\tif _, err := n3.NewStream(ctx, p2); err != nil {\n\t\tt.Error(err)\n\t}\n\n\t// but not 1->2 nor 2->2 (not linked), nor 1->1 (not connected)\n\tif _, err := n1.NewStream(ctx, p2); err == nil {\n\t\tt.Error(\"should not be able to connect\")\n\t}\n\tif _, err := n2.NewStream(ctx, p2); err == nil {\n\t\tt.Error(\"should not be able to connect\")\n\t}\n\tif _, err := n1.NewStream(ctx, p1); err == nil {\n\t\tt.Error(\"should not be able to connect\")\n\t}\n\n\t// connect p1->p1 (should fail)\n\tif _, err := n1.DialPeer(ctx, p1); err == nil {\n\t\tt.Error(\"p1 shouldn't be able to dial self\")\n\t}\n\n\t// and a stream too\n\tif _, err := n1.NewStream(ctx, p1); err == nil {\n\t\tt.Error(\"p1 shouldn't be able to dial self\")\n\t}\n\n\t// connect p1->p2\n\tif _, err := n1.DialPeer(ctx, p2); err == nil {\n\t\tt.Error(\"p1 should not be able to dial p2, not connected...\")\n\t}\n\n\t// connect p3->p1\n\tif _, err := n3.DialPeer(ctx, p1); err == nil {\n\t\tt.Error(\"p3 should not be able to dial p1, not connected...\")\n\t}\n\n\t// relink p1->p2\n\n\tl12, err = mn.LinkPeers(p1, p2)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif !(l12.Networks()[0] == n1 && l12.Networks()[1] == n2) &&\n\t\t!(l12.Networks()[0] == n2 && l12.Networks()[1] == n1) {\n\t\tt.Error(\"l12 networks incorrect\")\n\t}\n\n\t// should now be able to connect\n\n\t// connect p1->p2\n\tif _, err := n1.DialPeer(ctx, p2); err != nil {\n\t\tt.Error(err)\n\t}\n\n\t// and a stream should work now too :)\n\tif _, err := n2.NewStream(ctx, p3); err != nil {\n\t\tt.Error(err)\n\t}\n\n}\n\nfunc TestStreams(t *testing.T) {\n\tctx := context.Background()\n\n\tmn, err := FullMeshConnected(3)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer mn.Close()\n\n\thandler := func(s network.Stream) {\n\t\tb := make([]byte, 4)\n\t\tif _, err := io.ReadFull(s, b); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tif !bytes.Equal(b, []byte(\"beep\")) {\n\t\t\tpanic(\"bytes mismatch\")\n\t\t}\n\t\tif _, err := s.Write([]byte(\"boop\")); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\ts.Close()\n\t}\n\n\thosts := mn.Hosts()\n\tfor _, h := range mn.Hosts() {\n\t\th.SetStreamHandler(protocol.TestingID, handler)\n\t}\n\n\ts, err := hosts[0].NewStream(ctx, hosts[1].ID(), protocol.TestingID)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif _, err := s.Write([]byte(\"beep\")); err != nil {\n\t\tpanic(err)\n\t}\n\tb := make([]byte, 4)\n\tif _, err := io.ReadFull(s, b); err != nil {\n\t\tpanic(err)\n\t}\n\tif !bytes.Equal(b, []byte(\"boop\")) {\n\t\tpanic(\"bytes mismatch 2\")\n\t}\n\n}\n\nfunc TestAdding(t *testing.T) {\n\tmn := New()\n\tdefer mn.Close()\n\n\tpeers := make([]peer.ID, 0, 3)\n\tfor range 3 {\n\t\tpriv, _, err := crypto.GenerateEd25519Key(rand.Reader)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\ta := randLocalTCPAddress()\n\t\th, err := mn.AddPeer(priv, a)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tpeers = append(peers, h.ID())\n\t}\n\n\tp1 := peers[0]\n\tp2 := peers[1]\n\n\t// link them\n\tfor _, p1 := range peers {\n\t\tfor _, p2 := range peers {\n\t\t\tif _, err := mn.LinkPeers(p1, p2); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}\n\t}\n\n\t// set the new stream handler on p2\n\th2 := mn.Host(p2)\n\tif h2 == nil {\n\t\tt.Fatalf(\"no host for %s\", p2)\n\t}\n\th2.SetStreamHandler(protocol.TestingID, func(s network.Stream) {\n\t\tdefer s.Close()\n\n\t\tb := make([]byte, 4)\n\t\tif _, err := io.ReadFull(s, b); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tif string(b) != \"beep\" {\n\t\t\tpanic(\"did not beep!\")\n\t\t}\n\n\t\tif _, err := s.Write([]byte(\"boop\")); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t})\n\n\t// connect p1 to p2\n\tif _, err := mn.ConnectPeers(p1, p2); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// talk to p2\n\th1 := mn.Host(p1)\n\tif h1 == nil {\n\t\tt.Fatalf(\"no network for %s\", p1)\n\t}\n\n\tctx := context.Background()\n\ts, err := h1.NewStream(ctx, p2, protocol.TestingID)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif _, err := s.Write([]byte(\"beep\")); err != nil {\n\t\tt.Error(err)\n\t}\n\tb := make([]byte, 4)\n\tif _, err := io.ReadFull(s, b); err != nil {\n\t\tt.Error(err)\n\t}\n\tif !bytes.Equal(b, []byte(\"boop\")) {\n\t\tt.Error(\"bytes mismatch 2\")\n\t}\n\n}\n\nfunc TestRateLimiting(t *testing.T) {\n\tif ci.IsRunning() {\n\t\tt.Skip(\"buggy in CI\")\n\t}\n\n\trl := NewRateLimiter(10)\n\n\tif !within(rl.Limit(10), time.Duration(float32(time.Second)), time.Millisecond) {\n\t\tt.Fatal()\n\t}\n\tif !within(rl.Limit(10), time.Duration(float32(time.Second*2)), time.Millisecond) {\n\t\tt.Fatal()\n\t}\n\tif !within(rl.Limit(10), time.Duration(float32(time.Second*3)), time.Millisecond) {\n\t\tt.Fatal()\n\t}\n\n\tif within(rl.Limit(10), time.Duration(float32(time.Second*3)), time.Millisecond) {\n\t\tt.Fatal()\n\t}\n\n\trl.UpdateBandwidth(50)\n\tif !within(rl.Limit(75), time.Duration(float32(time.Second)*1.5), time.Millisecond) {\n\t\tt.Fatal()\n\t}\n\n\tif within(rl.Limit(75), time.Duration(float32(time.Second)*1.5), time.Millisecond) {\n\t\tt.Fatal()\n\t}\n\n\trl.UpdateBandwidth(100)\n\tif !within(rl.Limit(1), time.Millisecond*10, time.Millisecond) {\n\t\tt.Fatal()\n\t}\n\n\tif within(rl.Limit(1), time.Millisecond*10, time.Millisecond) {\n\t\tt.Fatal()\n\t}\n}\n\nfunc within(t1 time.Duration, t2 time.Duration, tolerance time.Duration) bool {\n\treturn math.Abs(float64(t1)-float64(t2)) < float64(tolerance)\n}\n\nfunc TestLimitedStreams(t *testing.T) {\n\tmn, err := FullMeshConnected(2)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer mn.Close()\n\n\tvar wg sync.WaitGroup\n\tmessages := 4\n\tmessageSize := 500\n\thandler := func(s network.Stream) {\n\t\tb := make([]byte, messageSize)\n\t\tfor range messages {\n\t\t\tif _, err := io.ReadFull(s, b); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif !bytes.Equal(b[:4], []byte(\"ping\")) {\n\t\t\t\tt.Fatal(\"bytes mismatch\")\n\t\t\t}\n\t\t\twg.Done()\n\t\t}\n\t\ts.Close()\n\t}\n\n\thosts := mn.Hosts()\n\tfor _, h := range mn.Hosts() {\n\t\th.SetStreamHandler(protocol.TestingID, handler)\n\t}\n\n\tpeers := mn.Peers()\n\tlinks := mn.LinksBetweenPeers(peers[0], peers[1])\n\t//  1000 byte per second bandwidth\n\tbps := float64(1000)\n\topts := links[0].Options()\n\topts.Bandwidth = bps\n\tfor _, link := range links {\n\t\tlink.SetOptions(opts)\n\t}\n\n\tctx := context.Background()\n\ts, err := hosts[0].NewStream(ctx, hosts[1].ID(), protocol.TestingID)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfiller := make([]byte, messageSize-4)\n\tdata := append([]byte(\"ping\"), filler...)\n\tbefore := time.Now()\n\tfor range messages {\n\t\twg.Add(1)\n\t\tif _, err := s.Write(data); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}\n\n\twg.Wait()\n\tif !within(time.Since(before), time.Second*5/2, time.Second) {\n\t\tt.Fatal(\"Expected 2ish seconds but got \", time.Since(before))\n\t}\n}\nfunc TestFuzzManyPeers(t *testing.T) {\n\tpeerCount := 500\n\tif race.WithRace() {\n\t\tpeerCount = 100\n\t}\n\tfor i := 0; i < peerCount; i++ {\n\t\tmn, err := FullMeshConnected(2)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tmn.Close()\n\t}\n}\n\nfunc TestStreamsWithLatency(t *testing.T) {\n\tlatency := time.Millisecond * 500\n\n\tmn, err := WithNPeers(2)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer mn.Close()\n\n\t// configure the Mocknet with some latency and link/connect its peers\n\tmn.SetLinkDefaults(LinkOptions{Latency: latency})\n\tmn.LinkAll()\n\tmn.ConnectAllButSelf()\n\n\tmsg := []byte(\"ping\")\n\tmln := len(msg)\n\n\tvar wg sync.WaitGroup\n\n\t// we'll write once to a single stream\n\twg.Add(1)\n\n\thandler := func(s network.Stream) {\n\t\tb := make([]byte, mln)\n\n\t\tif _, err := io.ReadFull(s, b); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\twg.Done()\n\t\ts.Close()\n\t}\n\n\tmn.Hosts()[0].SetStreamHandler(protocol.TestingID, handler)\n\tmn.Hosts()[1].SetStreamHandler(protocol.TestingID, handler)\n\n\ts, err := mn.Hosts()[0].NewStream(context.Background(), mn.Hosts()[1].ID(), protocol.TestingID)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// writing to the stream will be subject to our configured latency\n\tcheckpoint := time.Now()\n\tif _, err := s.Write(msg); err != nil {\n\t\tt.Fatal(err)\n\t}\n\twg.Wait()\n\n\tdelta := time.Since(checkpoint)\n\ttolerance := time.Second\n\tif !within(delta, latency, tolerance) {\n\t\tt.Fatalf(\"Expected write to take ~%s (+/- %s), but took %s\", latency.String(), tolerance.String(), delta.String())\n\t}\n}\n\nfunc TestEventBus(t *testing.T) {\n\tconst peers = 2\n\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second)\n\tdefer cancel()\n\n\tmn, err := FullMeshLinked(peers)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer mn.Close()\n\n\tsub0, err := mn.Hosts()[0].EventBus().Subscribe(new(event.EvtPeerConnectednessChanged))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer sub0.Close()\n\tsub1, err := mn.Hosts()[1].EventBus().Subscribe(new(event.EvtPeerConnectednessChanged))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer sub1.Close()\n\n\tid0, id1 := mn.Hosts()[0].ID(), mn.Hosts()[1].ID()\n\n\t_, err = mn.ConnectPeers(id0, id1)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tfor range make([]int, peers) {\n\t\tselect {\n\t\tcase evt := <-sub0.Out():\n\t\t\tevnt := evt.(event.EvtPeerConnectednessChanged)\n\t\t\tif evnt.Peer != id1 {\n\t\t\t\tt.Fatal(\"wrong remote peer\")\n\t\t\t}\n\t\t\tif evnt.Connectedness != network.Connected {\n\t\t\t\tt.Fatal(\"wrong connectedness type\")\n\t\t\t}\n\t\tcase evt := <-sub1.Out():\n\t\t\tevnt := evt.(event.EvtPeerConnectednessChanged)\n\t\t\tif evnt.Peer != id0 {\n\t\t\t\tt.Fatal(\"wrong remote peer\")\n\t\t\t}\n\t\t\tif evnt.Connectedness != network.Connected {\n\t\t\t\tt.Fatal(\"wrong connectedness type\")\n\t\t\t}\n\t\tcase <-ctx.Done():\n\t\t\tt.Fatal(\"didn't get connectedness events in time\")\n\t\t}\n\t}\n\n\terr = mn.DisconnectPeers(id0, id1)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tfor range make([]int, peers) {\n\t\tselect {\n\t\tcase evt := <-sub0.Out():\n\t\t\tevnt := evt.(event.EvtPeerConnectednessChanged)\n\t\t\tif evnt.Peer != id1 {\n\t\t\t\tt.Fatal(\"wrong remote peer\")\n\t\t\t}\n\t\t\tif evnt.Connectedness != network.NotConnected {\n\t\t\t\tt.Fatal(\"wrong connectedness type\")\n\t\t\t}\n\t\tcase evt := <-sub1.Out():\n\t\t\tevnt := evt.(event.EvtPeerConnectednessChanged)\n\t\t\tif evnt.Peer != id0 {\n\t\t\t\tt.Fatal(\"wrong remote peer\")\n\t\t\t}\n\t\t\tif evnt.Connectedness != network.NotConnected {\n\t\t\t\tt.Fatal(\"wrong connectedness type\")\n\t\t\t}\n\t\tcase <-ctx.Done():\n\t\t\tt.Fatal(\"didn't get connectedness events in time\")\n\t\t}\n\t}\n}\n\nfunc TestBlockByPeerID(t *testing.T) {\n\tm, gater1, host1, _, host2 := WithConnectionGaters(t)\n\n\terr := gater1.BlockPeer(host2.ID())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t_, err = m.ConnectPeers(host1.ID(), host2.ID())\n\tif err == nil {\n\t\tt.Fatal(\"Should have blocked connection to banned peer\")\n\t}\n\n\t_, err = m.ConnectPeers(host2.ID(), host1.ID())\n\tif err == nil {\n\t\tt.Fatal(\"Should have blocked connection from banned peer\")\n\t}\n}\n\nfunc TestBlockByIP(t *testing.T) {\n\tm, gater1, host1, _, host2 := WithConnectionGaters(t)\n\n\tip, err := manet.ToIP(host2.Addrs()[0])\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\terr = gater1.BlockAddr(ip)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t_, err = m.ConnectPeers(host1.ID(), host2.ID())\n\tif err == nil {\n\t\tt.Fatal(\"Should have blocked connection to banned IP\")\n\t}\n\n\t_, err = m.ConnectPeers(host2.ID(), host1.ID())\n\tif err == nil {\n\t\tt.Fatal(\"Should have blocked connection from banned IP\")\n\t}\n}\n\nfunc WithConnectionGaters(t *testing.T) (Mocknet, *conngater.BasicConnectionGater, host.Host, *conngater.BasicConnectionGater, host.Host) {\n\tm := New()\n\taddPeer := func() (*conngater.BasicConnectionGater, host.Host) {\n\t\tgater, err := conngater.NewBasicConnectionGater(nil)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\th, err := m.GenPeerWithOptions(PeerOptions{gater: gater})\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\treturn gater, h\n\t}\n\tgater1, host1 := addPeer()\n\tgater2, host2 := addPeer()\n\n\terr := m.LinkAll()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\treturn m, gater1, host1, gater2, host2\n}\n"
  },
  {
    "path": "p2p/net/mock/ratelimiter.go",
    "content": "package mocknet\n\nimport (\n\t\"sync\"\n\t\"time\"\n)\n\n// A RateLimiter is used by a link to determine how long to wait before sending\n// data given a bandwidth cap.\ntype RateLimiter struct {\n\tlock         sync.Mutex\n\tbandwidth    float64       // bytes per nanosecond\n\tallowance    float64       // in bytes\n\tmaxAllowance float64       // in bytes\n\tlastUpdate   time.Time     // when allowance was updated last\n\tcount        int           // number of times rate limiting was applied\n\tduration     time.Duration // total delay introduced due to rate limiting\n}\n\n// Creates a new RateLimiter with bandwidth (in bytes/sec)\nfunc NewRateLimiter(bandwidth float64) *RateLimiter {\n\t//  convert bandwidth to bytes per nanosecond\n\tb := bandwidth / float64(time.Second)\n\treturn &RateLimiter{\n\t\tbandwidth:    b,\n\t\tallowance:    0,\n\t\tmaxAllowance: bandwidth,\n\t\tlastUpdate:   time.Now(),\n\t}\n}\n\n// Changes bandwidth of a RateLimiter and resets its allowance\nfunc (r *RateLimiter) UpdateBandwidth(bandwidth float64) {\n\tr.lock.Lock()\n\tdefer r.lock.Unlock()\n\t//  Convert bandwidth from bytes/second to bytes/nanosecond\n\tb := bandwidth / float64(time.Second)\n\tr.bandwidth = b\n\t//  Reset allowance\n\tr.allowance = 0\n\tr.maxAllowance = bandwidth\n\tr.lastUpdate = time.Now()\n}\n\n// Returns how long to wait before sending data with length 'dataSize' bytes\nfunc (r *RateLimiter) Limit(dataSize int) time.Duration {\n\tr.lock.Lock()\n\tdefer r.lock.Unlock()\n\t//  update time\n\tvar duration time.Duration = time.Duration(0)\n\tif r.bandwidth == 0 {\n\t\treturn duration\n\t}\n\tcurrent := time.Now()\n\telapsedTime := current.Sub(r.lastUpdate)\n\tr.lastUpdate = current\n\n\tallowance := r.allowance + float64(elapsedTime)*r.bandwidth\n\t//  allowance can't exceed bandwidth\n\tif allowance > r.maxAllowance {\n\t\tallowance = r.maxAllowance\n\t}\n\n\tallowance -= float64(dataSize)\n\tif allowance < 0 {\n\t\t//  sleep until allowance is back to 0\n\t\tduration = time.Duration(-allowance / r.bandwidth)\n\t\t//  rate limiting was applied, record stats\n\t\tr.count++\n\t\tr.duration += duration\n\t}\n\n\tr.allowance = allowance\n\treturn duration\n}\n"
  },
  {
    "path": "p2p/net/nat/internal/nat/LICENSE",
    "content": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction, and\ndistribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by the copyright\nowner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all other entities\nthat control, are controlled by, or are under common control with that entity.\nFor the purposes of this definition, \"control\" means (i) the power, direct or\nindirect, to cause the direction or management of such entity, whether by\ncontract or otherwise, or (ii) ownership of fifty percent (50%) or more of the\noutstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising\npermissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications, including\nbut not limited to software source code, documentation source, and configuration\nfiles.\n\n\"Object\" form shall mean any form resulting from mechanical transformation or\ntranslation of a Source form, including but not limited to compiled object code,\ngenerated documentation, and conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or Object form, made\navailable under the License, as indicated by a copyright notice that is included\nin or attached to the work (an example is provided in the Appendix below).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object form, that\nis based on (or derived from) the Work and for which the editorial revisions,\nannotations, elaborations, or other modifications represent, as a whole, an\noriginal work of authorship. For the purposes of this License, Derivative Works\nshall not include works that remain separable from, or merely link (or bind by\nname) to the interfaces of, the Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including the original version\nof the Work and any modifications or additions to that Work or Derivative Works\nthereof, that is intentionally submitted to Licensor for inclusion in the Work\nby the copyright owner or by an individual or Legal Entity authorized to submit\non behalf of the copyright owner. For the purposes of this definition,\n\"submitted\" means any form of electronic, verbal, or written communication sent\nto the Licensor or its representatives, including but not limited to\ncommunication on electronic mailing lists, source code control systems, and\nissue tracking systems that are managed by, or on behalf of, the Licensor for\nthe purpose of discussing and improving the Work, but excluding communication\nthat is conspicuously marked or otherwise designated in writing by the copyright\nowner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf\nof whom a Contribution has been received by Licensor and subsequently\nincorporated within the Work.\n\n2. Grant of Copyright License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable copyright license to reproduce, prepare Derivative Works of,\npublicly display, publicly perform, sublicense, and distribute the Work and such\nDerivative Works in Source or Object form.\n\n3. Grant of Patent License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable (except as stated in this section) patent license to make, have\nmade, use, offer to sell, sell, import, and otherwise transfer the Work, where\nsuch license applies only to those patent claims licensable by such Contributor\nthat are necessarily infringed by their Contribution(s) alone or by combination\nof their Contribution(s) with the Work to which such Contribution(s) was\nsubmitted. If You institute patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Work or a\nContribution incorporated within the Work constitutes direct or contributory\npatent infringement, then any patent licenses granted to You under this License\nfor that Work shall terminate as of the date such litigation is filed.\n\n4. Redistribution.\n\nYou may reproduce and distribute copies of the Work or Derivative Works thereof\nin any medium, with or without modifications, and in Source or Object form,\nprovided that You meet the following conditions:\n\nYou must give any other recipients of the Work or Derivative Works a copy of\nthis License; and\nYou must cause any modified files to carry prominent notices stating that You\nchanged the files; and\nYou must retain, in the Source form of any Derivative Works that You distribute,\nall copyright, patent, trademark, and attribution notices from the Source form\nof the Work, excluding those notices that do not pertain to any part of the\nDerivative Works; and\nIf the Work includes a \"NOTICE\" text file as part of its distribution, then any\nDerivative Works that You distribute must include a readable copy of the\nattribution notices contained within such NOTICE file, excluding those notices\nthat do not pertain to any part of the Derivative Works, in at least one of the\nfollowing places: within a NOTICE text file distributed as part of the\nDerivative Works; within the Source form or documentation, if provided along\nwith the Derivative Works; or, within a display generated by the Derivative\nWorks, if and wherever such third-party notices normally appear. The contents of\nthe NOTICE file are for informational purposes only and do not modify the\nLicense. You may add Your own attribution notices within Derivative Works that\nYou distribute, alongside or as an addendum to the NOTICE text from the Work,\nprovided that such additional attribution notices cannot be construed as\nmodifying the License.\nYou may add Your own copyright statement to Your modifications and may provide\nadditional or different license terms and conditions for use, reproduction, or\ndistribution of Your modifications, or for any such Derivative Works as a whole,\nprovided Your use, reproduction, and distribution of the Work otherwise complies\nwith the conditions stated in this License.\n\n5. Submission of Contributions.\n\nUnless You explicitly state otherwise, any Contribution intentionally submitted\nfor inclusion in the Work by You to the Licensor shall be under the terms and\nconditions of this License, without any additional terms or conditions.\nNotwithstanding the above, nothing herein shall supersede or modify the terms of\nany separate license agreement you may have executed with Licensor regarding\nsuch Contributions.\n\n6. Trademarks.\n\nThis License does not grant permission to use the trade names, trademarks,\nservice marks, or product names of the Licensor, except as required for\nreasonable and customary use in describing the origin of the Work and\nreproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty.\n\nUnless required by applicable law or agreed to in writing, Licensor provides the\nWork (and each Contributor provides its Contributions) on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,\nincluding, without limitation, any warranties or conditions of TITLE,\nNON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are\nsolely responsible for determining the appropriateness of using or\nredistributing the Work and assume any risks associated with Your exercise of\npermissions under this License.\n\n8. Limitation of Liability.\n\nIn no event and under no legal theory, whether in tort (including negligence),\ncontract, or otherwise, unless required by applicable law (such as deliberate\nand grossly negligent acts) or agreed to in writing, shall any Contributor be\nliable to You for damages, including any direct, indirect, special, incidental,\nor consequential damages of any character arising as a result of this License or\nout of the use or inability to use the Work (including but not limited to\ndamages for loss of goodwill, work stoppage, computer failure or malfunction, or\nany and all other commercial damages or losses), even if such Contributor has\nbeen advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability.\n\nWhile redistributing the Work or Derivative Works thereof, You may choose to\noffer, and charge a fee for, acceptance of support, warranty, indemnity, or\nother liability obligations and/or rights consistent with this License. However,\nin accepting such obligations, You may act only on Your own behalf and on Your\nsole responsibility, not on behalf of any other Contributor, and only if You\nagree to indemnify, defend, and hold each Contributor harmless for any liability\nincurred by, or claims asserted against, such Contributor by reason of your\naccepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work\n\nTo apply the Apache License to your work, attach the following boilerplate\nnotice, with the fields enclosed by brackets \"[]\" replaced with your own\nidentifying information. (Don't include the brackets!) The text should be\nenclosed in the appropriate comment syntax for the file format. We also\nrecommend that a file or class name and description of purpose be included on\nthe same \"printed page\" as the copyright notice for easier identification within\nthird-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n     http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "p2p/net/nat/internal/nat/README.md",
    "content": "Originally forked from: [fd/go-nat](https://github.com/fd/go-nat).\n"
  },
  {
    "path": "p2p/net/nat/internal/nat/nat.go",
    "content": "// Package nat implements NAT handling facilities\npackage nat\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"math/rand\"\n\t\"net\"\n\t\"strings\"\n\t\"time\"\n\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n\t\"github.com/libp2p/go-netroute\"\n)\n\nvar log = logging.Logger(\"internal/nat\")\n\nvar ErrNoExternalAddress = errors.New(\"no external address\")\nvar ErrNoInternalAddress = errors.New(\"no internal address\")\n\ntype ErrNoNATFound struct {\n\tErrs []error\n}\n\nfunc (e ErrNoNATFound) Unwrap() []error {\n\treturn e.Errs\n}\n\nfunc (e ErrNoNATFound) Error() string {\n\terrStrs := make([]string, 0, len(e.Errs))\n\tfor _, err := range e.Errs {\n\t\terrStrs = append(errStrs, err.Error())\n\t}\n\treturn fmt.Sprintf(\"no NAT found: [%s]\", strings.Join(errStrs, \"; \"))\n}\n\n// protocol is either \"udp\" or \"tcp\"\ntype NAT interface {\n\t// Type returns the kind of NAT port mapping service that is used\n\tType() string\n\n\t// GetDeviceAddress returns the internal address of the gateway device.\n\tGetDeviceAddress() (addr net.IP, err error)\n\n\t// GetExternalAddress returns the external address of the gateway device.\n\tGetExternalAddress() (addr net.IP, err error)\n\n\t// GetInternalAddress returns the address of the local host.\n\tGetInternalAddress() (addr net.IP, err error)\n\n\t// AddPortMapping maps a port on the local host to an external port.\n\tAddPortMapping(ctx context.Context, protocol string, internalPort int, description string, timeout time.Duration) (mappedExternalPort int, err error)\n\n\t// DeletePortMapping removes a port mapping.\n\tDeletePortMapping(ctx context.Context, protocol string, internalPort int) (err error)\n}\n\n// discoverNATs returns all NATs discovered in the network.\nfunc discoverNATs(ctx context.Context) ([]NAT, []error) {\n\ttype natsAndErrs struct {\n\t\tnats []NAT\n\t\terrs []error\n\t}\n\tupnpCh := make(chan natsAndErrs)\n\tpmpCh := make(chan natsAndErrs)\n\n\tgo func() {\n\t\tdefer close(upnpCh)\n\n\t\t// We do these UPNP queries sequentially because some routers will fail to handle parallel requests.\n\t\tnats, errs := discoverUPNP_IG1(ctx)\n\n\t\t// Do IG2 after IG1 so that its NAT devices will appear as \"better\" when we\n\t\t// find the best NAT to return below.\n\t\tn, e := discoverUPNP_IG2(ctx)\n\t\tnats = append(nats, n...)\n\t\terrs = append(errs, e...)\n\n\t\tif len(nats) == 0 {\n\t\t\t// We don't have a NAT. We should try querying all devices over\n\t\t\t// SSDP to find a InternetGatewayDevice. This shouldn't be necessary for\n\t\t\t// a well behaved router.\n\t\t\tn, e = discoverUPNP_GenIGDev(ctx)\n\t\t\tnats = append(nats, n...)\n\t\t\terrs = append(errs, e...)\n\t\t}\n\n\t\tselect {\n\t\tcase upnpCh <- natsAndErrs{nats, errs}:\n\t\tcase <-ctx.Done():\n\t\t}\n\t}()\n\n\tgo func() {\n\t\tdefer close(pmpCh)\n\t\tnat, err := discoverNATPMP(ctx)\n\t\tvar nats []NAT\n\t\tvar errs []error\n\t\tif err != nil {\n\t\t\terrs = append(errs, err)\n\t\t} else {\n\t\t\tnats = append(nats, nat)\n\t\t}\n\t\tselect {\n\t\tcase pmpCh <- natsAndErrs{nats, errs}:\n\t\tcase <-ctx.Done():\n\t\t}\n\t}()\n\n\tvar nats []NAT\n\tvar errs []error\n\n\tfor upnpCh != nil || pmpCh != nil {\n\t\tselect {\n\t\tcase res := <-pmpCh:\n\t\t\tpmpCh = nil\n\t\t\tnats = append(nats, res.nats...)\n\t\t\terrs = append(errs, res.errs...)\n\t\tcase res := <-upnpCh:\n\t\t\tupnpCh = nil\n\t\t\tnats = append(nats, res.nats...)\n\t\t\terrs = append(errs, res.errs...)\n\t\tcase <-ctx.Done():\n\t\t\terrs = append(errs, ctx.Err())\n\t\t\treturn nats, errs\n\t\t}\n\t}\n\treturn nats, errs\n}\n\n// DiscoverGateway attempts to find a gateway device.\nfunc DiscoverGateway(ctx context.Context) (NAT, error) {\n\tnats, errs := discoverNATs(ctx)\n\n\tswitch len(nats) {\n\tcase 0:\n\t\treturn nil, ErrNoNATFound{Errs: errs}\n\tcase 1:\n\t\tif len(errs) > 0 {\n\t\t\tlog.Debug(\"NAT found, but some potentially unrelated errors occurred\", \"errors\", errs)\n\t\t}\n\n\t\treturn nats[0], nil\n\t}\n\tgw, _ := getDefaultGateway()\n\tbestNAT := nats[0]\n\tnatGw, _ := bestNAT.GetDeviceAddress()\n\tbestNATIsGw := gw != nil && natGw.Equal(gw)\n\t// 1. Prefer gateways discovered _last_. This is an OK heuristic for\n\t// discovering the most-upstream (furthest) NAT.\n\t// 2. Prefer gateways that actually match our known gateway address.\n\t// Some relays like to claim to be NATs even if they aren't.\n\tfor _, nat := range nats[1:] {\n\t\tnatGw, _ := nat.GetDeviceAddress()\n\t\tnatIsGw := gw != nil && natGw.Equal(gw)\n\n\t\tif bestNATIsGw && !natIsGw {\n\t\t\tcontinue\n\t\t}\n\n\t\tbestNATIsGw = natIsGw\n\t\tbestNAT = nat\n\t}\n\n\tif len(errs) > 0 {\n\t\tlog.Debug(\"NAT found, but some potentially unrelated errors occurred\", \"errors\", errs)\n\t}\n\treturn bestNAT, nil\n}\n\nvar random = rand.New(rand.NewSource(time.Now().UnixNano()))\n\nfunc randomPort() int {\n\treturn random.Intn(math.MaxUint16-10000) + 10000\n}\n\nfunc getDefaultGateway() (net.IP, error) {\n\trouter, err := netroute.New()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t_, ip, _, err := router.Route(net.IPv4zero)\n\treturn ip, err\n}\n"
  },
  {
    "path": "p2p/net/nat/internal/nat/natpmp.go",
    "content": "package nat\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"time\"\n\n\tnatpmp \"github.com/jackpal/go-nat-pmp\"\n)\n\nvar (\n\t_ NAT = (*natpmpNAT)(nil)\n)\n\nfunc discoverNATPMP(ctx context.Context) (NAT, error) {\n\tip, err := getDefaultGateway()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tclientCh := make(chan *natpmp.Client, 1)\n\terrCh := make(chan error, 1)\n\n\t// We can't cancel the natpmp library, but we can at least still return\n\t// on context cancellation by putting this in a goroutine\n\tgo func() {\n\t\tclient, err := discoverNATPMPWithAddr(ctx, ip)\n\t\tif err != nil {\n\t\t\terrCh <- err\n\t\t\treturn\n\t\t}\n\t\tclientCh <- client\n\t}()\n\n\tselect {\n\tcase client := <-clientCh:\n\t\treturn &natpmpNAT{client, ip, make(map[int]int)}, nil\n\tcase err := <-errCh:\n\t\treturn nil, err\n\tcase <-ctx.Done():\n\t\treturn nil, ctx.Err()\n\t}\n}\n\nfunc discoverNATPMPWithAddr(ctx context.Context, ip net.IP) (*natpmp.Client, error) {\n\tvar client *natpmp.Client\n\tif deadline, ok := ctx.Deadline(); ok {\n\t\tclient = natpmp.NewClientWithTimeout(ip, time.Until(deadline))\n\t} else {\n\t\tclient = natpmp.NewClient(ip)\n\t}\n\t_, err := client.GetExternalAddress()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn client, nil\n}\n\ntype natpmpNAT struct {\n\tc       *natpmp.Client\n\tgateway net.IP\n\tports   map[int]int\n}\n\nfunc (n *natpmpNAT) GetDeviceAddress() (addr net.IP, err error) {\n\treturn n.gateway, nil\n}\n\nfunc (n *natpmpNAT) GetInternalAddress() (addr net.IP, err error) {\n\tifaces, err := net.Interfaces()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, iface := range ifaces {\n\t\taddrs, err := iface.Addrs()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tfor _, addr := range addrs {\n\t\t\tswitch x := addr.(type) {\n\t\t\tcase *net.IPNet:\n\t\t\t\tif x.Contains(n.gateway) {\n\t\t\t\t\treturn x.IP, nil\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil, ErrNoInternalAddress\n}\n\nfunc (n *natpmpNAT) GetExternalAddress() (addr net.IP, err error) {\n\tres, err := n.c.GetExternalAddress()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\td := res.ExternalIPAddress\n\treturn net.IPv4(d[0], d[1], d[2], d[3]), nil\n}\n\nfunc (n *natpmpNAT) AddPortMapping(_ context.Context, protocol string, internalPort int, _ string, timeout time.Duration) (int, error) {\n\tvar (\n\t\terr error\n\t)\n\n\ttimeoutInSeconds := int(timeout / time.Second)\n\n\tif externalPort := n.ports[internalPort]; externalPort > 0 {\n\t\t_, err = n.c.AddPortMapping(protocol, internalPort, externalPort, timeoutInSeconds)\n\t\tif err == nil {\n\t\t\tn.ports[internalPort] = externalPort\n\t\t\treturn externalPort, nil\n\t\t}\n\t}\n\n\tfor range 3 {\n\t\texternalPort := randomPort()\n\t\t_, err = n.c.AddPortMapping(protocol, internalPort, externalPort, timeoutInSeconds)\n\t\tif err == nil {\n\t\t\tn.ports[internalPort] = externalPort\n\t\t\treturn externalPort, nil\n\t\t}\n\t}\n\n\treturn 0, err\n}\n\nfunc (n *natpmpNAT) DeletePortMapping(_ context.Context, _ string, internalPort int) (err error) {\n\tdelete(n.ports, internalPort)\n\treturn nil\n}\n\nfunc (n *natpmpNAT) Type() string {\n\treturn \"NAT-PMP\"\n}\n"
  },
  {
    "path": "p2p/net/nat/internal/nat/upnp.go",
    "content": "package nat\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/huin/goupnp\"\n\t\"github.com/huin/goupnp/dcps/internetgateway1\"\n\t\"github.com/huin/goupnp/dcps/internetgateway2\"\n\n\t\"github.com/koron/go-ssdp\"\n)\n\nvar _ NAT = (*upnp_NAT)(nil)\n\nfunc discoverUPNP_IG1(ctx context.Context) ([]NAT, []error) {\n\treturn discoverSearchTarget(ctx, internetgateway1.URN_WANConnectionDevice_1)\n}\n\nfunc discoverUPNP_IG2(ctx context.Context) ([]NAT, []error) {\n\treturn discoverSearchTarget(ctx, internetgateway2.URN_WANConnectionDevice_2)\n}\n\nfunc discoverSearchTarget(ctx context.Context, target string) (nats []NAT, errs []error) {\n\t// find devices\n\tdevs, err := goupnp.DiscoverDevicesCtx(ctx, target)\n\tif err != nil {\n\t\terrs = append(errs, err)\n\t\treturn\n\t}\n\n\tfor _, dev := range devs {\n\t\tif dev.Err != nil {\n\t\t\terrs = append(errs, dev.Err)\n\t\t\tcontinue\n\t\t}\n\t\tdev.Root.Device.VisitServices(serviceVisitor(ctx, dev.Root, &nats, &errs))\n\t}\n\treturn\n}\n\n// discoverUPNP_GenIGDev is a fallback for routers that fail to respond to our\n// targetted SSDP queries. It will query all devices and try to find any\n// InternetGatewayDevice.\nfunc discoverUPNP_GenIGDev(ctx context.Context) (nats []NAT, errs []error) {\n\tDeviceList, err := ssdp.Search(ssdp.All, 5, \"\")\n\tif err != nil {\n\t\terrs = append(errs, err)\n\t\treturn\n\t}\n\n\t// Limit the number of InternetGateways we'll query. Normally we'd only\n\t// expect 1 or 2, but in case of a weird network we also don't want to do\n\t// too much work.\n\tconst maxIGDevs = 3\n\tfoundIGDevs := 0\n\tfor _, Service := range DeviceList {\n\t\tif !strings.Contains(Service.Type, \"InternetGatewayDevice\") {\n\t\t\tcontinue\n\t\t}\n\t\tif foundIGDevs >= maxIGDevs {\n\t\t\tlog.Debug(\"found more than maxIGDevs UPnP devices, stopping search\")\n\t\t\tbreak\n\t\t}\n\t\tfoundIGDevs++\n\n\t\tDeviceURL, err := url.Parse(Service.Location)\n\t\tif err != nil {\n\t\t\terrs = append(errs, err)\n\t\t\tcontinue\n\t\t}\n\t\tRootDevice, err := goupnp.DeviceByURLCtx(ctx, DeviceURL)\n\t\tif err != nil {\n\t\t\terrs = append(errs, err)\n\t\t\tcontinue\n\t\t}\n\n\t\tRootDevice.Device.VisitServices(serviceVisitor(ctx, RootDevice, &nats, &errs))\n\t}\n\treturn\n}\n\n// serviceVisitor is a vistor function that visits all services of a root\n// device and collects NATs.\n//\n// It works on InternetGateway V1 and V2 devices. For V1 devices, V2 services should not be encountered, and the visitor will collect an error in that case.\nfunc serviceVisitor(ctx context.Context, rootDevice *goupnp.RootDevice, outNats *[]NAT, outErrs *[]error) func(srv *goupnp.Service) {\n\treturn func(srv *goupnp.Service) {\n\t\tif ctx.Err() != nil {\n\t\t\treturn\n\t\t}\n\t\tswitch srv.ServiceType {\n\t\tcase internetgateway2.URN_WANIPConnection_1:\n\t\t\tclient := &internetgateway2.WANIPConnection1{ServiceClient: goupnp.ServiceClient{\n\t\t\t\tSOAPClient: srv.NewSOAPClient(),\n\t\t\t\tRootDevice: rootDevice,\n\t\t\t\tService:    srv,\n\t\t\t}}\n\t\t\t_, isNat, err := client.GetNATRSIPStatusCtx(ctx)\n\t\t\tif err != nil {\n\t\t\t\t*outErrs = append(*outErrs, err)\n\t\t\t} else if isNat {\n\t\t\t\t*outNats = append(*outNats, &upnp_NAT{client, make(map[int]int), \"UPNP (IP1)\", rootDevice})\n\t\t\t}\n\n\t\tcase internetgateway2.URN_WANIPConnection_2:\n\t\t\tif rootDevice.Device.DeviceType == internetgateway2.URN_WANConnectionDevice_1 {\n\t\t\t\t*outErrs = append(*outErrs, fmt.Errorf(\"found V2 service on V1 device\"))\n\t\t\t\treturn\n\t\t\t}\n\t\t\tclient := &internetgateway2.WANIPConnection2{ServiceClient: goupnp.ServiceClient{\n\t\t\t\tSOAPClient: srv.NewSOAPClient(),\n\t\t\t\tRootDevice: rootDevice,\n\t\t\t\tService:    srv,\n\t\t\t}}\n\t\t\t_, isNat, err := client.GetNATRSIPStatusCtx(ctx)\n\t\t\tif err != nil {\n\t\t\t\t*outErrs = append(*outErrs, err)\n\t\t\t} else if isNat {\n\t\t\t\t*outNats = append(*outNats, &upnp_NAT{client, make(map[int]int), \"UPNP (IP2)\", rootDevice})\n\t\t\t}\n\n\t\tcase internetgateway2.URN_WANPPPConnection_1:\n\t\t\tclient := &internetgateway2.WANPPPConnection1{ServiceClient: goupnp.ServiceClient{\n\t\t\t\tSOAPClient: srv.NewSOAPClient(),\n\t\t\t\tRootDevice: rootDevice,\n\t\t\t\tService:    srv,\n\t\t\t}}\n\t\t\t_, isNat, err := client.GetNATRSIPStatusCtx(ctx)\n\t\t\tif err != nil {\n\t\t\t\t*outErrs = append(*outErrs, err)\n\t\t\t} else if isNat {\n\t\t\t\t*outNats = append(*outNats, &upnp_NAT{client, make(map[int]int), \"UPNP (PPP1)\", rootDevice})\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype upnp_NAT_Client interface {\n\tGetExternalIPAddress() (string, error)\n\tAddPortMappingCtx(context.Context, string, uint16, string, uint16, string, bool, string, uint32) error\n\tDeletePortMappingCtx(context.Context, string, uint16, string) error\n}\n\ntype upnp_NAT struct {\n\tc          upnp_NAT_Client\n\tports      map[int]int\n\ttyp        string\n\trootDevice *goupnp.RootDevice\n}\n\nfunc (u *upnp_NAT) GetExternalAddress() (addr net.IP, err error) {\n\tipString, err := u.c.GetExternalIPAddress()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tip := net.ParseIP(ipString)\n\tif ip == nil {\n\t\treturn nil, ErrNoExternalAddress\n\t}\n\n\treturn ip, nil\n}\n\nfunc mapProtocol(s string) string {\n\tswitch s {\n\tcase \"udp\":\n\t\treturn \"UDP\"\n\tcase \"tcp\":\n\t\treturn \"TCP\"\n\tdefault:\n\t\tpanic(\"invalid protocol: \" + s)\n\t}\n}\n\nfunc (u *upnp_NAT) AddPortMapping(ctx context.Context, protocol string, internalPort int, description string, timeout time.Duration) (int, error) {\n\tip, err := u.GetInternalAddress()\n\tif err != nil {\n\t\treturn 0, nil\n\t}\n\n\ttimeoutInSeconds := uint32(timeout / time.Second)\n\n\tif externalPort := u.ports[internalPort]; externalPort > 0 {\n\t\terr = u.c.AddPortMappingCtx(ctx, \"\", uint16(externalPort), mapProtocol(protocol), uint16(internalPort), ip.String(), true, description, timeoutInSeconds)\n\t\tif err == nil {\n\t\t\treturn externalPort, nil\n\t\t}\n\t}\n\n\tfor range 3 {\n\t\texternalPort := randomPort()\n\t\terr = u.c.AddPortMappingCtx(ctx, \"\", uint16(externalPort), mapProtocol(protocol), uint16(internalPort), ip.String(), true, description, timeoutInSeconds)\n\t\tif err == nil {\n\t\t\tu.ports[internalPort] = externalPort\n\t\t\treturn externalPort, nil\n\t\t}\n\t}\n\n\treturn 0, err\n}\n\nfunc (u *upnp_NAT) DeletePortMapping(ctx context.Context, protocol string, internalPort int) error {\n\tif externalPort := u.ports[internalPort]; externalPort > 0 {\n\t\tdelete(u.ports, internalPort)\n\t\treturn u.c.DeletePortMappingCtx(ctx, \"\", uint16(externalPort), mapProtocol(protocol))\n\t}\n\n\treturn nil\n}\n\nfunc (u *upnp_NAT) GetDeviceAddress() (net.IP, error) {\n\taddr, err := net.ResolveUDPAddr(\"udp4\", u.rootDevice.URLBase.Host)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn addr.IP, nil\n}\n\nfunc (u *upnp_NAT) GetInternalAddress() (net.IP, error) {\n\tdevAddr, err := u.GetDeviceAddress()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tifaces, err := net.Interfaces()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, iface := range ifaces {\n\t\taddrs, err := iface.Addrs()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tfor _, addr := range addrs {\n\t\t\tswitch x := addr.(type) {\n\t\t\tcase *net.IPNet:\n\t\t\t\tif x.Contains(devAddr) {\n\t\t\t\t\treturn x.IP, nil\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil, ErrNoInternalAddress\n}\n\nfunc (n *upnp_NAT) Type() string { return n.typ }\n"
  },
  {
    "path": "p2p/net/nat/mock_nat_test.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github.com/libp2p/go-libp2p/p2p/net/nat/internal/nat (interfaces: NAT)\n//\n// Generated by this command:\n//\n//\tmockgen -package nat -destination mock_nat_test.go github.com/libp2p/go-libp2p/p2p/net/nat/internal/nat NAT\n//\n\n// Package nat is a generated GoMock package.\npackage nat\n\nimport (\n\tcontext \"context\"\n\tnet \"net\"\n\treflect \"reflect\"\n\ttime \"time\"\n\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockNAT is a mock of NAT interface.\ntype MockNAT struct {\n\tctrl     *gomock.Controller\n\trecorder *MockNATMockRecorder\n\tisgomock struct{}\n}\n\n// MockNATMockRecorder is the mock recorder for MockNAT.\ntype MockNATMockRecorder struct {\n\tmock *MockNAT\n}\n\n// NewMockNAT creates a new mock instance.\nfunc NewMockNAT(ctrl *gomock.Controller) *MockNAT {\n\tmock := &MockNAT{ctrl: ctrl}\n\tmock.recorder = &MockNATMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockNAT) EXPECT() *MockNATMockRecorder {\n\treturn m.recorder\n}\n\n// AddPortMapping mocks base method.\nfunc (m *MockNAT) AddPortMapping(ctx context.Context, protocol string, internalPort int, description string, timeout time.Duration) (int, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"AddPortMapping\", ctx, protocol, internalPort, description, timeout)\n\tret0, _ := ret[0].(int)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// AddPortMapping indicates an expected call of AddPortMapping.\nfunc (mr *MockNATMockRecorder) AddPortMapping(ctx, protocol, internalPort, description, timeout any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"AddPortMapping\", reflect.TypeOf((*MockNAT)(nil).AddPortMapping), ctx, protocol, internalPort, description, timeout)\n}\n\n// DeletePortMapping mocks base method.\nfunc (m *MockNAT) DeletePortMapping(ctx context.Context, protocol string, internalPort int) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeletePortMapping\", ctx, protocol, internalPort)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// DeletePortMapping indicates an expected call of DeletePortMapping.\nfunc (mr *MockNATMockRecorder) DeletePortMapping(ctx, protocol, internalPort any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeletePortMapping\", reflect.TypeOf((*MockNAT)(nil).DeletePortMapping), ctx, protocol, internalPort)\n}\n\n// GetDeviceAddress mocks base method.\nfunc (m *MockNAT) GetDeviceAddress() (net.IP, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetDeviceAddress\")\n\tret0, _ := ret[0].(net.IP)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetDeviceAddress indicates an expected call of GetDeviceAddress.\nfunc (mr *MockNATMockRecorder) GetDeviceAddress() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetDeviceAddress\", reflect.TypeOf((*MockNAT)(nil).GetDeviceAddress))\n}\n\n// GetExternalAddress mocks base method.\nfunc (m *MockNAT) GetExternalAddress() (net.IP, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetExternalAddress\")\n\tret0, _ := ret[0].(net.IP)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetExternalAddress indicates an expected call of GetExternalAddress.\nfunc (mr *MockNATMockRecorder) GetExternalAddress() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetExternalAddress\", reflect.TypeOf((*MockNAT)(nil).GetExternalAddress))\n}\n\n// GetInternalAddress mocks base method.\nfunc (m *MockNAT) GetInternalAddress() (net.IP, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetInternalAddress\")\n\tret0, _ := ret[0].(net.IP)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetInternalAddress indicates an expected call of GetInternalAddress.\nfunc (mr *MockNATMockRecorder) GetInternalAddress() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetInternalAddress\", reflect.TypeOf((*MockNAT)(nil).GetInternalAddress))\n}\n\n// Type mocks base method.\nfunc (m *MockNAT) Type() string {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Type\")\n\tret0, _ := ret[0].(string)\n\treturn ret0\n}\n\n// Type indicates an expected call of Type.\nfunc (mr *MockNATMockRecorder) Type() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Type\", reflect.TypeOf((*MockNAT)(nil).Type))\n}\n"
  },
  {
    "path": "p2p/net/nat/nat.go",
    "content": "package nat\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/netip\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n\n\t\"github.com/libp2p/go-libp2p/p2p/net/nat/internal/nat\"\n)\n\n// ErrNoMapping signals no mapping exists for an address\nvar ErrNoMapping = errors.New(\"mapping not established\")\n\nvar log = logging.Logger(\"nat\")\n\n// MappingDuration is a default port mapping duration.\n// Port mappings are renewed every (MappingDuration / 3)\nconst MappingDuration = time.Minute\n\n// CacheTime is the time a mapping will cache an external address for\nconst CacheTime = 15 * time.Second\n\n// DiscoveryTimeout is the maximum time to wait for NAT discovery.\n// This is based on the underlying UPnP and NAT-PMP/PCP protocols:\n//   - SSDP (UPnP discovery) waits 5 seconds for responses\n//   - NAT-PMP uses exponential backoff starting at 250ms, up to 9 retries\n//     (total ~32 seconds if exhausted, but typically responds in 1-2 seconds)\n//   - PCP follows similar timing to NAT-PMP\n//   - 10 seconds covers common cases while failing fast when no NAT exists\nconst DiscoveryTimeout = 10 * time.Second\n\n// rediscoveryThreshold is the number of consecutive connection failures\n// before triggering NAT rediscovery. We ignore first few failures to\n// distinguish between transient network issues and persistent router\n// problems like restarts or port changes that require finding the NAT device again.\nconst rediscoveryThreshold = 3\n\ntype entry struct {\n\tprotocol string\n\tport     int\n}\n\n// so we can mock it in tests\nvar discoverGateway = nat.DiscoverGateway\n\n// DiscoverNAT looks for a NAT device in the network and returns an object that can manage port mappings.\nfunc DiscoverNAT(ctx context.Context) (*NAT, error) {\n\tnatInstance, err := discoverGateway(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\textAddr := getExternalAddress(natInstance)\n\n\t// Log the device addr.\n\taddr, err := natInstance.GetDeviceAddress()\n\tif err != nil {\n\t\tlog.Warn(\"DiscoverGateway address error\", \"err\", err)\n\t} else {\n\t\tlog.Info(\"DiscoverGateway address\", \"address\", addr)\n\t}\n\n\tctx, cancel := context.WithCancel(context.Background())\n\tnat := &NAT{\n\t\tnat:       natInstance,\n\t\tmappings:  make(map[entry]int),\n\t\tctx:       ctx,\n\t\tctxCancel: cancel,\n\t}\n\tnat.extAddr.Store(&extAddr)\n\tnat.refCount.Add(1)\n\tgo func() {\n\t\tdefer nat.refCount.Done()\n\t\tnat.background()\n\t}()\n\treturn nat, nil\n}\n\n// NAT is an object that manages address port mappings in\n// NATs (Network Address Translators). It is a long-running\n// service that will periodically renew port mappings,\n// and keep an up-to-date list of all the external addresses.\n//\n// Locking strategy:\n//   - natmu: Protects nat instance and rediscovery state (nat, consecutiveFailures, rediscovering)\n//   - mappingmu: Protects port mappings table and closed flag (mappings, closed)\n//   - Lock ordering: When both locks are needed, always acquire mappingmu before natmu\n//     to prevent deadlocks\n//   - We use separate mutexes because the NAT instance may change (e.g., when router\n//     restarts and UPnP port changes), but the port mappings must persist and be\n//     re-applied across all instances. This separation allows the mappings table to\n//     remain stable while the underlying NAT device changes.\ntype NAT struct {\n\tnatmu sync.Mutex\n\tnat   nat.NAT\n\n\t// Track connection failures for auto-rediscovery\n\tconsecutiveFailures int  // protected by natmu\n\trediscovering       bool // protected by natmu\n\n\t// External IP of the NAT. Will be renewed periodically (every CacheTime).\n\textAddr atomic.Pointer[netip.Addr]\n\n\trefCount  sync.WaitGroup\n\tctx       context.Context\n\tctxCancel context.CancelFunc\n\n\t// Port mappings that should persist across NAT instance changes\n\tmappingmu sync.RWMutex\n\tclosed    bool          // protected by mappingmu\n\tmappings  map[entry]int // protected by mappingmu\n}\n\n// Close shuts down all port mappings. NAT can no longer be used.\nfunc (nat *NAT) Close() error {\n\tnat.mappingmu.Lock()\n\tnat.closed = true\n\tnat.mappingmu.Unlock()\n\n\tnat.ctxCancel()\n\tnat.refCount.Wait()\n\treturn nil\n}\n\nfunc (nat *NAT) GetMapping(protocol string, port int) (addr netip.AddrPort, found bool) {\n\tnat.mappingmu.Lock()\n\tdefer nat.mappingmu.Unlock()\n\n\tif !nat.extAddr.Load().IsValid() {\n\t\treturn netip.AddrPort{}, false\n\t}\n\textPort, found := nat.mappings[entry{protocol: protocol, port: port}]\n\t// The mapping may have an invalid port.\n\tif !found || extPort == 0 {\n\t\treturn netip.AddrPort{}, false\n\t}\n\treturn netip.AddrPortFrom(*nat.extAddr.Load(), uint16(extPort)), true\n}\n\n// AddMapping attempts to construct a mapping on protocol and internal port.\n// It blocks until a mapping was established. Once added, it periodically renews the mapping.\n//\n// May not succeed, and mappings may change over time;\n// NAT devices may not respect our port requests, and even lie.\nfunc (nat *NAT) AddMapping(ctx context.Context, protocol string, port int) error {\n\tswitch protocol {\n\tcase \"tcp\", \"udp\":\n\tdefault:\n\t\treturn fmt.Errorf(\"invalid protocol: %s\", protocol)\n\t}\n\n\tnat.mappingmu.Lock()\n\tdefer nat.mappingmu.Unlock()\n\n\tif nat.closed {\n\t\treturn errors.New(\"closed\")\n\t}\n\n\t// Check if the mapping already exists to avoid duplicate work\n\te := entry{protocol: protocol, port: port}\n\tif _, exists := nat.mappings[e]; exists {\n\t\treturn nil\n\t}\n\n\tlog.Info(\"Starting maintenance of port mapping\", \"protocol\", protocol, \"port\", port)\n\n\t// do it once synchronously, so first mapping is done right away, and before exiting,\n\t// allowing users -- in the optimistic case -- to use results right after.\n\textPort := nat.establishMapping(ctx, protocol, port)\n\t// Don't validate the mapping here, we refresh the mappings based on this map.\n\t// We can try getting a port again in case it succeeds. In the worst case,\n\t// this is one extra LAN request every few minutes.\n\tnat.mappings[e] = extPort\n\treturn nil\n}\n\n// RemoveMapping removes a port mapping.\n// It blocks until the NAT has removed the mapping.\nfunc (nat *NAT) RemoveMapping(ctx context.Context, protocol string, port int) error {\n\tnat.mappingmu.Lock()\n\tdefer nat.mappingmu.Unlock()\n\tnat.natmu.Lock()\n\tdefer nat.natmu.Unlock()\n\n\tswitch protocol {\n\tcase \"tcp\", \"udp\":\n\t\te := entry{protocol: protocol, port: port}\n\t\tif _, ok := nat.mappings[e]; ok {\n\t\t\tlog.Info(\"Stopping maintenance of port mapping\", \"protocol\", protocol, \"port\", port)\n\t\t\tdelete(nat.mappings, e)\n\t\t\treturn nat.nat.DeletePortMapping(ctx, protocol, port)\n\t\t}\n\t\treturn errors.New(\"unknown mapping\")\n\tdefault:\n\t\treturn fmt.Errorf(\"invalid protocol: %s\", protocol)\n\t}\n}\n\nfunc (nat *NAT) background() {\n\t// Renew port mappings every 20 seconds (1/3 of 60s lifetime).\n\t// - NAT-PMP RFC 6886 recommends renewing at 50% of lifetime\n\t// - We use 33% for added safety against silent lifetime reductions\n\t// NOTE: This aggressive 60s/20s pattern may be outdated for modern routers\n\t// but provides quick cleanup and fast failure detection for our rediscovery.\n\t// TODO: Research longer durations (e.g. 30min/10min) to reduce router load\n\tconst mappingUpdate = MappingDuration / 3\n\n\tnow := time.Now()\n\tnextMappingUpdate := now.Add(mappingUpdate)\n\tnextAddrUpdate := now.Add(CacheTime)\n\n\tt := time.NewTimer(minTime(nextMappingUpdate, nextAddrUpdate).Sub(now)) // don't use a ticker here. We don't know how long establishing the mappings takes.\n\tdefer t.Stop()\n\n\tvar in []entry\n\tvar out []int // port numbers\n\tfor {\n\t\tselect {\n\t\tcase now := <-t.C:\n\t\t\tif now.After(nextMappingUpdate) {\n\t\t\t\tin = in[:0]\n\t\t\t\tout = out[:0]\n\t\t\t\tnat.mappingmu.Lock()\n\t\t\t\tfor e := range nat.mappings {\n\t\t\t\t\tin = append(in, e)\n\t\t\t\t}\n\t\t\t\tnat.mappingmu.Unlock()\n\t\t\t\t// Establishing the mapping involves network requests.\n\t\t\t\t// Don't hold the mutex, just save the ports.\n\t\t\t\tfor _, e := range in {\n\t\t\t\t\tout = append(out, nat.establishMapping(nat.ctx, e.protocol, e.port))\n\t\t\t\t}\n\t\t\t\tnat.mappingmu.Lock()\n\t\t\t\tfor i, p := range in {\n\t\t\t\t\tif _, ok := nat.mappings[p]; !ok {\n\t\t\t\t\t\tcontinue // entry might have been deleted\n\t\t\t\t\t}\n\t\t\t\t\tnat.mappings[p] = out[i]\n\t\t\t\t}\n\t\t\t\tnat.mappingmu.Unlock()\n\t\t\t\tnextMappingUpdate = time.Now().Add(mappingUpdate)\n\t\t\t}\n\t\t\tif now.After(nextAddrUpdate) {\n\t\t\t\tnat.natmu.Lock()\n\t\t\t\textIP, err := nat.nat.GetExternalAddress()\n\t\t\t\tnat.natmu.Unlock()\n\n\t\t\t\tvar extAddr netip.Addr\n\t\t\t\tif err == nil {\n\t\t\t\t\textAddr, _ = netip.AddrFromSlice(extIP)\n\t\t\t\t}\n\t\t\t\tnat.extAddr.Store(&extAddr)\n\t\t\t\tnextAddrUpdate = time.Now().Add(CacheTime)\n\t\t\t}\n\t\t\tt.Reset(time.Until(minTime(nextAddrUpdate, nextMappingUpdate)))\n\t\tcase <-nat.ctx.Done():\n\t\t\tnat.mappingmu.Lock()\n\t\t\tdefer nat.mappingmu.Unlock()\n\t\t\tnat.natmu.Lock()\n\t\t\tdefer nat.natmu.Unlock()\n\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)\n\t\t\tdefer cancel()\n\t\t\tfor e := range nat.mappings {\n\t\t\t\tnat.nat.DeletePortMapping(ctx, e.protocol, e.port)\n\t\t\t}\n\t\t\tclear(nat.mappings)\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (nat *NAT) establishMapping(ctx context.Context, protocol string, internalPort int) (externalPort int) {\n\tlog.Debug(\"Attempting port map\", \"protocol\", protocol, \"internal_port\", internalPort)\n\tconst comment = \"libp2p\"\n\n\t// Try to establish the mapping with both NAT calls under the same lock\n\tnat.natmu.Lock()\n\tdefer nat.natmu.Unlock()\n\n\tvar err error\n\texternalPort, err = nat.nat.AddPortMapping(ctx, protocol, internalPort, comment, MappingDuration)\n\tif err != nil {\n\t\t// Some hardware does not support mappings with timeout, so try that\n\t\texternalPort, err = nat.nat.AddPortMapping(ctx, protocol, internalPort, comment, 0)\n\t}\n\n\t// Handle success\n\tif err == nil && externalPort != 0 {\n\t\tnat.consecutiveFailures = 0\n\t\tlog.Debug(\"NAT port mapping established\", \"protocol\", protocol, \"internal_port\", internalPort, \"external_port\", externalPort)\n\t\treturn externalPort\n\t}\n\n\t// Handle failures\n\tif err != nil {\n\t\tlog.Warn(\"NAT port mapping failed\", \"protocol\", protocol, \"internal_port\", internalPort, \"err\", err)\n\n\t\t// Check if this is a connection error that might indicate router restart\n\t\t// See: https://github.com/libp2p/go-libp2p/issues/3224#issuecomment-2866844723\n\t\t// Note: We use string matching because goupnp doesn't preserve error chains (uses %v instead of %w)\n\t\tif strings.Contains(err.Error(), \"connection refused\") {\n\t\t\tnat.consecutiveFailures++\n\t\t\tif nat.consecutiveFailures >= rediscoveryThreshold && !nat.rediscovering {\n\t\t\t\tnat.rediscovering = true\n\t\t\t\t// Spawn in goroutine to avoid blocking the caller while we\n\t\t\t\t// perform network discovery, which can take up to 30 seconds.\n\t\t\t\t// The rediscovering flag prevents multiple concurrent attempts.\n\t\t\t\tgo nat.rediscoverNAT()\n\t\t\t}\n\t\t} else {\n\t\t\t// Reset counter for non-connection errors (transient failures)\n\t\t\tnat.consecutiveFailures = 0\n\t\t}\n\t\treturn 0\n\t}\n\n\t// externalPort is 0 but no error was returned\n\tlog.Warn(\"NAT port mapping failed\", \"protocol\", protocol, \"internal_port\", internalPort, \"external_port\", 0)\n\treturn 0\n}\n\n// rediscoverNAT attempts to rediscover the NAT device after connection failures\nfunc (nat *NAT) rediscoverNAT() {\n\tlog.Info(\"NAT rediscovery triggered due to repeated connection failures\")\n\n\tctx, cancel := context.WithTimeout(nat.ctx, DiscoveryTimeout)\n\tdefer cancel()\n\n\tnewNATInstance, err := discoverGateway(ctx)\n\tif err != nil {\n\t\tlog.Warn(\"NAT rediscovery failed\", \"err\", err)\n\t\tnat.natmu.Lock()\n\t\tdefer nat.natmu.Unlock()\n\t\tnat.rediscovering = false\n\t\treturn\n\t}\n\n\textAddr := getExternalAddress(newNATInstance)\n\n\t// Replace the NAT instance\n\t// No cleanup of the old instance needed because:\n\t// - Router restart has already wiped all mappings\n\t// - Old UPnP endpoint is dead (connection refused)\n\t// - If router didn't actually restart (false positive), any stale mappings\n\t//   on the router expire naturally (60 second UPnP timeout)\n\tnat.natmu.Lock()\n\tnat.nat = newNATInstance\n\tnat.extAddr.Store(&extAddr)\n\tnat.consecutiveFailures = 0\n\tnat.rediscovering = false\n\tnat.natmu.Unlock()\n\n\t// Re-establish all existing mappings on the new NAT instance\n\tnat.mappingmu.Lock()\n\tfor e := range nat.mappings {\n\t\textPort := nat.establishMapping(nat.ctx, e.protocol, e.port)\n\t\tnat.mappings[e] = extPort\n\t\tif extPort != 0 {\n\t\t\tlog.Info(\"NAT mapping restored after rediscovery\", \"protocol\", e.protocol, \"internal_port\", e.port, \"external_port\", extPort)\n\t\t}\n\t}\n\tnat.mappingmu.Unlock()\n\n\tlog.Info(\"NAT rediscovery successful\")\n}\n\nfunc minTime(a, b time.Time) time.Time {\n\tif a.Before(b) {\n\t\treturn a\n\t}\n\treturn b\n}\n\n// getExternalAddress retrieves and parses the external address from a NAT instance\nfunc getExternalAddress(natInstance nat.NAT) netip.Addr {\n\textIP, err := natInstance.GetExternalAddress()\n\tif err != nil {\n\t\tlog.Debug(\"Failed to get external address\", \"err\", err)\n\t\treturn netip.Addr{}\n\t}\n\textAddr, ok := netip.AddrFromSlice(extIP)\n\tif !ok {\n\t\tlog.Debug(\"Failed to parse external address\", \"ip\", extIP)\n\t\treturn netip.Addr{}\n\t}\n\treturn extAddr\n}\n"
  },
  {
    "path": "p2p/net/nat/nat_test.go",
    "content": "package nat\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net\"\n\t\"net/netip\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/p2p/net/nat/internal/nat\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/mock/gomock\"\n)\n\n//go:generate sh -c \"go run go.uber.org/mock/mockgen -package nat -destination mock_nat_test.go github.com/libp2p/go-libp2p/p2p/net/nat/internal/nat NAT\"\n\n// Helper functions for test setup\n\n// expectPortMappingFailure sets up mock expectations for a port mapping failure\nfunc expectPortMappingFailure(mockNAT *MockNAT, protocol string, port int, err error) {\n\tmockNAT.EXPECT().AddPortMapping(gomock.Any(), protocol, port, gomock.Any(), MappingDuration).Return(0, err).Times(1)\n\tmockNAT.EXPECT().AddPortMapping(gomock.Any(), protocol, port, gomock.Any(), time.Duration(0)).Return(0, err).Times(1)\n}\n\n// expectPortMappingSuccess sets up mock expectations for a successful port mapping\nfunc expectPortMappingSuccess(mockNAT *MockNAT, protocol string, internalPort, externalPort int) {\n\tmockNAT.EXPECT().AddPortMapping(gomock.Any(), protocol, internalPort, gomock.Any(), MappingDuration).Return(externalPort, nil).Times(1)\n}\n\n// setupMockNATWithAddress creates a mock NAT with the given external address\nfunc setupMockNATWithAddress(ctrl *gomock.Controller, addr net.IP) *MockNAT {\n\tmockNAT := NewMockNAT(ctrl)\n\tmockNAT.EXPECT().GetDeviceAddress().Return(nil, errors.New(\"nope\")).AnyTimes()\n\tmockNAT.EXPECT().GetExternalAddress().Return(addr, nil).AnyTimes()\n\treturn mockNAT\n}\n\nfunc setupMockNAT(t *testing.T) (mockNAT *MockNAT, reset func()) {\n\tt.Helper()\n\tctrl := gomock.NewController(t)\n\tmockNAT = NewMockNAT(ctrl)\n\tmockNAT.EXPECT().GetDeviceAddress().Return(nil, errors.New(\"nope\")) // is only used for logging\n\torigDiscoverGateway := discoverGateway\n\tdiscoverGateway = func(_ context.Context) (nat.NAT, error) { return mockNAT, nil }\n\treturn mockNAT, func() {\n\t\tdiscoverGateway = origDiscoverGateway\n\t\tctrl.Finish()\n\t}\n}\n\n// TestAddMapping tests basic port mapping creation and retrieval to ensure mappings are stored correctly.\nfunc TestAddMapping(t *testing.T) {\n\tmockNAT, reset := setupMockNAT(t)\n\tdefer reset()\n\n\tmockNAT.EXPECT().GetExternalAddress().Return(net.IPv4(1, 2, 3, 4), nil)\n\tnat, err := DiscoverNAT(context.Background())\n\trequire.NoError(t, err)\n\n\texpectPortMappingSuccess(mockNAT, \"tcp\", 10000, 1234)\n\trequire.NoError(t, nat.AddMapping(context.Background(), \"tcp\", 10000))\n\n\t_, found := nat.GetMapping(\"tcp\", 9999)\n\trequire.False(t, found, \"didn't expect a port mapping for unmapped port\")\n\t_, found = nat.GetMapping(\"udp\", 10000)\n\trequire.False(t, found, \"didn't expect a port mapping for unmapped protocol\")\n\tmapped, found := nat.GetMapping(\"tcp\", 10000)\n\trequire.True(t, found, \"expected port mapping\")\n\taddr, _ := netip.AddrFromSlice(net.IPv4(1, 2, 3, 4))\n\trequire.Equal(t, netip.AddrPortFrom(addr, 1234), mapped)\n}\n\n// TestRemoveMapping tests deletion of port mappings to ensure cleanup works and unknown mappings error.\nfunc TestRemoveMapping(t *testing.T) {\n\tmockNAT, reset := setupMockNAT(t)\n\tdefer reset()\n\n\tmockNAT.EXPECT().GetExternalAddress().Return(net.IPv4(1, 2, 3, 4), nil)\n\tnat, err := DiscoverNAT(context.Background())\n\trequire.NoError(t, err)\n\texpectPortMappingSuccess(mockNAT, \"tcp\", 10000, 1234)\n\trequire.NoError(t, nat.AddMapping(context.Background(), \"tcp\", 10000))\n\t_, found := nat.GetMapping(\"tcp\", 10000)\n\trequire.True(t, found, \"expected port mapping\")\n\n\trequire.Error(t, nat.RemoveMapping(context.Background(), \"tcp\", 9999), \"expected error for unknown mapping\")\n\tmockNAT.EXPECT().DeletePortMapping(gomock.Any(), \"tcp\", 10000)\n\trequire.NoError(t, nat.RemoveMapping(context.Background(), \"tcp\", 10000))\n\n\t_, found = nat.GetMapping(\"tcp\", 10000)\n\trequire.False(t, found, \"didn't expect port mapping for deleted mapping\")\n}\n\n// TestAddMappingInvalidPort tests NAT returning port 0 to ensure invalid mappings are not stored.\nfunc TestAddMappingInvalidPort(t *testing.T) {\n\tmockNAT, reset := setupMockNAT(t)\n\tdefer reset()\n\n\tmockNAT.EXPECT().GetExternalAddress().Return(net.IPv4(1, 2, 3, 4), nil)\n\tnat, err := DiscoverNAT(context.Background())\n\trequire.NoError(t, err)\n\n\texpectPortMappingSuccess(mockNAT, \"tcp\", 10000, 0)\n\trequire.NoError(t, nat.AddMapping(context.Background(), \"tcp\", 10000))\n\n\t_, found := nat.GetMapping(\"tcp\", 10000)\n\trequire.False(t, found, \"didn't expect a port mapping for invalid nat-ed port\")\n}\n\n// TestAddMappingDeduplication tests that duplicate AddMapping calls don't trigger duplicate NAT operations.\n// This is a regression test for a bug where multiple libp2p listeners sharing the same port\n// (e.g., TCP, QUIC, WebTransport, WebRTC-direct all on the same port) would cause duplicate NAT\n// port mapping requests - resulting in 5+ duplicate mapping attempts for the same protocol/port combination.\nfunc TestAddMappingDeduplication(t *testing.T) {\n\tmockNAT, reset := setupMockNAT(t)\n\tdefer reset()\n\n\tmockNAT.EXPECT().GetExternalAddress().Return(net.IPv4(1, 2, 3, 4), nil)\n\tnat, err := DiscoverNAT(context.Background())\n\trequire.NoError(t, err)\n\n\t// Expect only ONE call to AddPortMapping despite multiple AddMapping calls\n\texpectPortMappingSuccess(mockNAT, \"tcp\", 10000, 1234)\n\n\t// First call should trigger NAT operation\n\trequire.NoError(t, nat.AddMapping(context.Background(), \"tcp\", 10000))\n\n\t// Verify mapping was created\n\tmapped, found := nat.GetMapping(\"tcp\", 10000)\n\trequire.True(t, found, \"expected port mapping\")\n\taddr, _ := netip.AddrFromSlice(net.IPv4(1, 2, 3, 4))\n\trequire.Equal(t, netip.AddrPortFrom(addr, 1234), mapped)\n\n\t// Second and third calls should NOT trigger NAT operations (no additional expectations)\n\t// This simulates what happens when multiple transports use the same port\n\trequire.NoError(t, nat.AddMapping(context.Background(), \"tcp\", 10000))\n\trequire.NoError(t, nat.AddMapping(context.Background(), \"tcp\", 10000))\n\n\t// Mapping should still exist\n\tmapped, found = nat.GetMapping(\"tcp\", 10000)\n\trequire.True(t, found, \"expected port mapping\")\n\trequire.Equal(t, netip.AddrPortFrom(addr, 1234), mapped)\n}\n\n// TestNATRediscoveryOnConnectionError tests automatic NAT rediscovery after router restart\n// to ensure mappings are restored when router's NAT service (e.g. miniupnpd) changes its listening port\n// (a regression test for https://github.com/libp2p/go-libp2p/issues/3224#issuecomment-2866844723).\nfunc TestNATRediscoveryOnConnectionError(t *testing.T) {\n\tctrl := gomock.NewController(t)\n\tdefer ctrl.Finish()\n\n\t// Setup initial mock NAT\n\tmockNAT := NewMockNAT(ctrl)\n\tmockNAT.EXPECT().GetDeviceAddress().Return(nil, errors.New(\"nope\")).AnyTimes()\n\tmockNAT.EXPECT().GetExternalAddress().Return(net.IPv4(1, 2, 3, 4), nil).Times(1)\n\n\t// Setup new mock NAT for rediscovery\n\tnewMockNAT := setupMockNATWithAddress(ctrl, net.IPv4(5, 6, 7, 8))\n\n\t// Track discovery calls with atomic counter\n\tvar discoveryCalls atomic.Int32\n\torigDiscoverGateway := discoverGateway\n\tdiscoverGateway = func(_ context.Context) (nat.NAT, error) {\n\t\tcount := discoveryCalls.Add(1)\n\t\tif count == 1 {\n\t\t\treturn mockNAT, nil\n\t\t}\n\t\treturn newMockNAT, nil\n\t}\n\tdefer func() {\n\t\tdiscoverGateway = origDiscoverGateway\n\t}()\n\n\t// Create NAT instance\n\tn, err := DiscoverNAT(context.Background())\n\trequire.NoError(t, err)\n\n\t// Expect cleanup on close\n\tdefer func() {\n\t\t// The final NAT instance is newMockNAT, which will have these mappings\n\t\tnewMockNAT.EXPECT().DeletePortMapping(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()\n\t\tn.Close()\n\t}()\n\n\t// Add some existing mappings that should be restored after rediscovery\n\texpectPortMappingSuccess(mockNAT, \"tcp\", 4001, 4001)\n\trequire.NoError(t, n.AddMapping(context.Background(), \"tcp\", 4001))\n\texpectPortMappingSuccess(mockNAT, \"udp\", 4002, 4002)\n\trequire.NoError(t, n.AddMapping(context.Background(), \"udp\", 4002))\n\n\t// Simulate connection refused error that happens when router's UPnP port changes\n\terrConnectionRefused := errors.New(\"goupnp: error performing SOAP HTTP request: Post \\\"http://192.168.1.1:1234/ctl/IPConn\\\": dial tcp 192.168.1.1:1234: connect: connection refused\")\n\n\t// Set up expectations for the failures that will trigger rediscovery\n\tfor i := range 3 {\n\t\texpectPortMappingFailure(mockNAT, \"tcp\", 10000+i, errConnectionRefused)\n\t}\n\n\t// Expect the existing mappings to be restored on the new NAT instance\n\texpectPortMappingSuccess(newMockNAT, \"tcp\", 4001, 4001)\n\texpectPortMappingSuccess(newMockNAT, \"udp\", 4002, 4002)\n\n\t// Now trigger the failures\n\tfor i := range 3 {\n\t\texternalPort := n.establishMapping(context.Background(), \"tcp\", 10000+i)\n\t\trequire.Equal(t, 0, externalPort)\n\t}\n\n\t// Give time for async rediscovery and mapping restoration\n\ttime.Sleep(100 * time.Millisecond)\n\n\t// Verify mappings were restored\n\tmapped, found := n.GetMapping(\"tcp\", 4001)\n\trequire.True(t, found, \"expected tcp/4001 mapping to be restored\")\n\taddr, _ := netip.AddrFromSlice(net.IPv4(5, 6, 7, 8)) // new NAT's external IP\n\trequire.Equal(t, netip.AddrPortFrom(addr, 4001), mapped)\n\n\tmapped, found = n.GetMapping(\"udp\", 4002)\n\trequire.True(t, found, \"expected udp/4002 mapping to be restored\")\n\trequire.Equal(t, netip.AddrPortFrom(addr, 4002), mapped)\n\n\t// Next mapping should use the new NAT\n\texpectPortMappingSuccess(newMockNAT, \"tcp\", 10003, 12345)\n\n\texternalPort := n.establishMapping(context.Background(), \"tcp\", 10003)\n\trequire.Equal(t, 12345, externalPort)\n\trequire.Equal(t, int32(2), discoveryCalls.Load()) // Initial + rediscovery\n}\n\n// TestNATRediscoveryOldRouterReturns tests rediscovery when router comes back on same IP/port\n// to ensure we handle transient failures without losing the NAT instance.\nfunc TestNATRediscoveryOldRouterReturns(t *testing.T) {\n\tctrl := gomock.NewController(t)\n\tdefer ctrl.Finish()\n\n\t// Setup mock NAT\n\tmockNAT := NewMockNAT(ctrl)\n\tmockNAT.EXPECT().GetDeviceAddress().Return(nil, errors.New(\"nope\")).AnyTimes()\n\tmockNAT.EXPECT().GetExternalAddress().Return(net.IPv4(1, 2, 3, 4), nil).AnyTimes()\n\n\t// Track discovery calls with atomic counter\n\tvar discoveryCalls atomic.Int32\n\torigDiscoverGateway := discoverGateway\n\tdiscoverGateway = func(_ context.Context) (nat.NAT, error) {\n\t\tcount := discoveryCalls.Add(1)\n\t\tif count == 2 {\n\t\t\t// During rediscovery, return the same NAT (router came back)\n\t\t\treturn mockNAT, nil\n\t\t}\n\t\treturn mockNAT, nil\n\t}\n\tdefer func() {\n\t\tdiscoverGateway = origDiscoverGateway\n\t}()\n\n\tn, err := DiscoverNAT(context.Background())\n\trequire.NoError(t, err)\n\tdefer func() {\n\t\tmockNAT.EXPECT().DeletePortMapping(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()\n\t\tn.Close()\n\t}()\n\n\t// Add existing mapping\n\tmockNAT.EXPECT().AddPortMapping(gomock.Any(), \"tcp\", 4001, gomock.Any(), MappingDuration).Return(4001, nil).Times(1)\n\trequire.NoError(t, n.AddMapping(context.Background(), \"tcp\", 4001))\n\n\terrConnectionRefused := errors.New(\"goupnp: error performing SOAP HTTP request: dial tcp 192.168.1.1:1234: connect: connection refused\")\n\n\t// Set up expectations for the first two failures\n\tfor i := range 2 {\n\t\tmockNAT.EXPECT().AddPortMapping(gomock.Any(), \"tcp\", 10000+i, gomock.Any(), MappingDuration).Return(0, errConnectionRefused).Times(1)\n\t\tmockNAT.EXPECT().AddPortMapping(gomock.Any(), \"tcp\", 10000+i, gomock.Any(), time.Duration(0)).Return(0, errConnectionRefused).Times(1)\n\t}\n\n\t// Third failure triggers rediscovery, but router is back now\n\tmockNAT.EXPECT().AddPortMapping(gomock.Any(), \"tcp\", 10002, gomock.Any(), MappingDuration).Return(0, errConnectionRefused).Times(1)\n\tmockNAT.EXPECT().AddPortMapping(gomock.Any(), \"tcp\", 10002, gomock.Any(), time.Duration(0)).Return(0, errConnectionRefused).Times(1)\n\n\t// Expect mapping restoration on the same NAT\n\tmockNAT.EXPECT().AddPortMapping(gomock.Any(), \"tcp\", 4001, gomock.Any(), MappingDuration).Return(4001, nil).Times(1)\n\n\t// Trigger the failures\n\tfor i := range 2 {\n\t\tn.establishMapping(context.Background(), \"tcp\", 10000+i)\n\t}\n\tn.establishMapping(context.Background(), \"tcp\", 10002)\n\n\ttime.Sleep(100 * time.Millisecond)\n\n\t// Verify we still have our mapping\n\tmapped, found := n.GetMapping(\"tcp\", 4001)\n\trequire.True(t, found)\n\taddr, _ := netip.AddrFromSlice(net.IPv4(1, 2, 3, 4))\n\trequire.Equal(t, netip.AddrPortFrom(addr, 4001), mapped)\n\trequire.Equal(t, int32(2), discoveryCalls.Load()) // Initial + rediscovery\n}\n\n// TestNATRediscoveryFailureThreshold tests the 3-failure threshold and counter reset behavior\n// to ensure we don't trigger rediscovery on transient errors.\nfunc TestNATRediscoveryFailureThreshold(t *testing.T) {\n\tctrl := gomock.NewController(t)\n\tdefer ctrl.Finish()\n\n\tmockNAT := NewMockNAT(ctrl)\n\tmockNAT.EXPECT().GetDeviceAddress().Return(nil, errors.New(\"nope\")).AnyTimes()\n\tmockNAT.EXPECT().GetExternalAddress().Return(net.IPv4(1, 2, 3, 4), nil).AnyTimes()\n\n\t// Track discovery calls with atomic counter\n\tvar discoveryCalls atomic.Int32\n\torigDiscoverGateway := discoverGateway\n\tdiscoverGateway = func(_ context.Context) (nat.NAT, error) {\n\t\tdiscoveryCalls.Add(1)\n\t\treturn mockNAT, nil\n\t}\n\tdefer func() {\n\t\tdiscoverGateway = origDiscoverGateway\n\t}()\n\n\tn, err := DiscoverNAT(context.Background())\n\trequire.NoError(t, err)\n\tdefer func() {\n\t\tmockNAT.EXPECT().DeletePortMapping(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()\n\t\tn.Close()\n\t}()\n\n\terrConnectionRefused := errors.New(\"connection refused\")\n\terrOther := errors.New(\"some other error\")\n\n\t// Test 1: Only 2 failures - should NOT trigger rediscovery\n\tfor i := range 2 {\n\t\tmockNAT.EXPECT().AddPortMapping(gomock.Any(), \"tcp\", 10000+i, gomock.Any(), MappingDuration).Return(0, errConnectionRefused).Times(1)\n\t\tmockNAT.EXPECT().AddPortMapping(gomock.Any(), \"tcp\", 10000+i, gomock.Any(), time.Duration(0)).Return(0, errConnectionRefused).Times(1)\n\t\tn.establishMapping(context.Background(), \"tcp\", 10000+i)\n\t}\n\n\ttime.Sleep(50 * time.Millisecond)\n\trequire.Equal(t, int32(1), discoveryCalls.Load(), \"should not trigger rediscovery with only 2 failures\")\n\n\t// Test 2: Non-connection error resets counter\n\tmockNAT.EXPECT().AddPortMapping(gomock.Any(), \"tcp\", 10002, gomock.Any(), MappingDuration).Return(0, errOther).Times(1)\n\tmockNAT.EXPECT().AddPortMapping(gomock.Any(), \"tcp\", 10002, gomock.Any(), time.Duration(0)).Return(0, errOther).Times(1)\n\tn.establishMapping(context.Background(), \"tcp\", 10002)\n\n\t// Now even 2 more connection failures shouldn't trigger (counter was reset)\n\tfor i := range 2 {\n\t\tmockNAT.EXPECT().AddPortMapping(gomock.Any(), \"tcp\", 10003+i, gomock.Any(), MappingDuration).Return(0, errConnectionRefused).Times(1)\n\t\tmockNAT.EXPECT().AddPortMapping(gomock.Any(), \"tcp\", 10003+i, gomock.Any(), time.Duration(0)).Return(0, errConnectionRefused).Times(1)\n\t\tn.establishMapping(context.Background(), \"tcp\", 10003+i)\n\t}\n\n\ttime.Sleep(50 * time.Millisecond)\n\trequire.Equal(t, int32(1), discoveryCalls.Load(), \"counter should reset on non-connection error\")\n\n\t// Test 3: Success resets counter\n\tmockNAT.EXPECT().AddPortMapping(gomock.Any(), \"tcp\", 10005, gomock.Any(), MappingDuration).Return(10005, nil).Times(1)\n\tn.establishMapping(context.Background(), \"tcp\", 10005)\n\n\t// Again, 2 failures shouldn't trigger\n\tfor i := range 2 {\n\t\tmockNAT.EXPECT().AddPortMapping(gomock.Any(), \"tcp\", 10006+i, gomock.Any(), MappingDuration).Return(0, errConnectionRefused).Times(1)\n\t\tmockNAT.EXPECT().AddPortMapping(gomock.Any(), \"tcp\", 10006+i, gomock.Any(), time.Duration(0)).Return(0, errConnectionRefused).Times(1)\n\t\tn.establishMapping(context.Background(), \"tcp\", 10006+i)\n\t}\n\n\ttime.Sleep(50 * time.Millisecond)\n\trequire.Equal(t, int32(1), discoveryCalls.Load(), \"counter should reset on success\")\n}\n\n// TestNATRediscoveryConcurrency tests concurrent connection failures to ensure only one\n// rediscovery happens even with multiple goroutines hitting errors.\nfunc TestNATRediscoveryConcurrency(t *testing.T) {\n\tctrl := gomock.NewController(t)\n\tdefer ctrl.Finish()\n\n\tmockNAT := NewMockNAT(ctrl)\n\tmockNAT.EXPECT().GetDeviceAddress().Return(nil, errors.New(\"nope\")).AnyTimes()\n\tmockNAT.EXPECT().GetExternalAddress().Return(net.IPv4(1, 2, 3, 4), nil).AnyTimes()\n\n\tnewMockNAT := NewMockNAT(ctrl)\n\tnewMockNAT.EXPECT().GetDeviceAddress().Return(nil, errors.New(\"nope\")).AnyTimes()\n\tnewMockNAT.EXPECT().GetExternalAddress().Return(net.IPv4(5, 6, 7, 8), nil).AnyTimes()\n\n\t// Track discovery calls with atomic counter\n\tvar discoveryCalls atomic.Int32\n\torigDiscoverGateway := discoverGateway\n\tdiscoverGateway = func(_ context.Context) (nat.NAT, error) {\n\t\tcount := discoveryCalls.Add(1)\n\t\tif count == 1 {\n\t\t\treturn mockNAT, nil\n\t\t}\n\t\t// Simulate slow discovery to test concurrent calls\n\t\ttime.Sleep(200 * time.Millisecond)\n\t\treturn newMockNAT, nil\n\t}\n\tdefer func() {\n\t\tdiscoverGateway = origDiscoverGateway\n\t}()\n\n\tn, err := DiscoverNAT(context.Background())\n\trequire.NoError(t, err)\n\tdefer func() {\n\t\tnewMockNAT.EXPECT().DeletePortMapping(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()\n\t\tn.Close()\n\t}()\n\n\terrConnectionRefused := errors.New(\"connection refused\")\n\n\t// Simulate multiple goroutines hitting failures after threshold\n\t// First get to threshold\n\tfor i := range 3 {\n\t\tmockNAT.EXPECT().AddPortMapping(gomock.Any(), \"tcp\", 10000+i, gomock.Any(), MappingDuration).Return(0, errConnectionRefused).Times(1)\n\t\tmockNAT.EXPECT().AddPortMapping(gomock.Any(), \"tcp\", 10000+i, gomock.Any(), time.Duration(0)).Return(0, errConnectionRefused).Times(1)\n\t\tn.establishMapping(context.Background(), \"tcp\", 10000+i)\n\t}\n\n\t// Set up expectations for concurrent failure attempts\n\tfor i := range 5 {\n\t\tport := 10003 + i\n\t\tmockNAT.EXPECT().AddPortMapping(gomock.Any(), \"tcp\", port, gomock.Any(), MappingDuration).Return(0, errConnectionRefused).AnyTimes()\n\t\tmockNAT.EXPECT().AddPortMapping(gomock.Any(), \"tcp\", port, gomock.Any(), time.Duration(0)).Return(0, errConnectionRefused).AnyTimes()\n\t}\n\n\t// Now launch multiple goroutines that would all try to trigger rediscovery\n\tvar wg sync.WaitGroup\n\tfor i := range 5 {\n\t\twg.Add(1)\n\t\tgo func(port int) {\n\t\t\tdefer wg.Done()\n\t\t\t// These would all try to trigger rediscovery if not protected\n\t\t\tn.establishMapping(context.Background(), \"tcp\", port)\n\t\t}(10003 + i)\n\t}\n\n\twg.Wait()\n\ttime.Sleep(300 * time.Millisecond) // Wait for rediscovery to complete\n\n\t// Should only have triggered one rediscovery despite multiple concurrent failures\n\trequire.Equal(t, int32(2), discoveryCalls.Load(), \"should only trigger one rediscovery\")\n}\n"
  },
  {
    "path": "p2p/net/pnet/protector.go",
    "content": "package pnet\n\nimport (\n\t\"errors\"\n\t\"net\"\n\n\tipnet \"github.com/libp2p/go-libp2p/core/pnet\"\n)\n\n// NewProtectedConn creates a new protected connection\nfunc NewProtectedConn(psk ipnet.PSK, conn net.Conn) (net.Conn, error) {\n\tif len(psk) != 32 {\n\t\treturn nil, errors.New(\"expected 32 byte PSK\")\n\t}\n\tvar p [32]byte\n\tcopy(p[:], psk)\n\treturn newPSKConn(&p, conn)\n}\n"
  },
  {
    "path": "p2p/net/pnet/psk_conn.go",
    "content": "package pnet\n\nimport (\n\t\"crypto/cipher\"\n\t\"crypto/rand\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\n\t\"github.com/libp2p/go-libp2p/core/pnet\"\n\n\t\"github.com/davidlazar/go-crypto/salsa20\"\n\tpool \"github.com/libp2p/go-buffer-pool\"\n)\n\n// we are using buffer pool as user needs their slice back\n// so we can't do XOR cripter in place\nvar (\n\terrShortNonce  = pnet.NewError(\"could not read full nonce\")\n\terrInsecureNil = pnet.NewError(\"insecure is nil\")\n\terrPSKNil      = pnet.NewError(\"pre-shread key is nil\")\n)\n\ntype pskConn struct {\n\tnet.Conn\n\tpsk *[32]byte\n\n\twriteS20 cipher.Stream\n\treadS20  cipher.Stream\n}\n\nfunc (c *pskConn) Read(out []byte) (int, error) {\n\tif c.readS20 == nil {\n\t\tnonce := make([]byte, 24)\n\t\t_, err := io.ReadFull(c.Conn, nonce)\n\t\tif err != nil {\n\t\t\treturn 0, fmt.Errorf(\"%w: %w\", errShortNonce, err)\n\t\t}\n\t\tc.readS20 = salsa20.New(c.psk, nonce)\n\t}\n\n\tn, err := c.Conn.Read(out) // read to in\n\tif n > 0 {\n\t\tc.readS20.XORKeyStream(out[:n], out[:n]) // decrypt to out buffer\n\t}\n\treturn n, err\n}\n\nfunc (c *pskConn) Write(in []byte) (int, error) {\n\tif c.writeS20 == nil {\n\t\tnonce := make([]byte, 24)\n\t\t_, err := rand.Read(nonce)\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t\t_, err = c.Conn.Write(nonce)\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\n\t\tc.writeS20 = salsa20.New(c.psk, nonce)\n\t}\n\tout := pool.Get(len(in))\n\tdefer pool.Put(out)\n\n\tc.writeS20.XORKeyStream(out, in) // encrypt\n\n\treturn c.Conn.Write(out) // send\n}\n\nvar _ net.Conn = (*pskConn)(nil)\n\nfunc newPSKConn(psk *[32]byte, insecure net.Conn) (net.Conn, error) {\n\tif insecure == nil {\n\t\treturn nil, errInsecureNil\n\t}\n\tif psk == nil {\n\t\treturn nil, errPSKNil\n\t}\n\treturn &pskConn{\n\t\tConn: insecure,\n\t\tpsk:  psk,\n\t}, nil\n}\n"
  },
  {
    "path": "p2p/net/pnet/psk_conn_test.go",
    "content": "package pnet\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/rand\"\n\t\"net\"\n\t\"testing\"\n)\n\nfunc setupPSKConns(_ context.Context, t *testing.T) (net.Conn, net.Conn) {\n\ttestPSK := make([]byte, 32) // null bytes are as good test key as any other key\n\tconn1, conn2 := net.Pipe()\n\n\tpsk1, err := NewProtectedConn(testPSK, conn1)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tpsk2, err := NewProtectedConn(testPSK, conn2)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\treturn psk1, psk2\n}\n\nfunc TestPSKSimpelMessges(t *testing.T) {\n\tctx := t.Context()\n\n\tpsk1, psk2 := setupPSKConns(ctx, t)\n\tmsg1 := []byte(\"hello world\")\n\tout1 := make([]byte, len(msg1))\n\n\twch := make(chan error)\n\tgo func() {\n\t\t_, err := psk1.Write(msg1)\n\t\twch <- err\n\t}()\n\tn, err := psk2.Read(out1)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = <-wch\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif n != len(out1) {\n\t\tt.Fatalf(\"expected to read %d bytes, read: %d\", len(out1), n)\n\t}\n\n\tif !bytes.Equal(msg1, out1) {\n\t\tt.Fatalf(\"input and output are not the same\")\n\t}\n}\n\nfunc TestPSKFragmentation(t *testing.T) {\n\tctx := t.Context()\n\n\tpsk1, psk2 := setupPSKConns(ctx, t)\n\n\tin := make([]byte, 1000)\n\tif _, err := rand.Read(in); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tout := make([]byte, 100)\n\n\twch := make(chan error)\n\tgo func() {\n\t\t_, err := psk1.Write(in)\n\t\twch <- err\n\t}()\n\n\tfor range 10 {\n\t\tif _, err := psk2.Read(out); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif !bytes.Equal(in[:100], out) {\n\t\t\tt.Fatalf(\"input and output are not the same\")\n\t\t}\n\t\tin = in[100:]\n\t}\n\n\tif err := <-wch; err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n"
  },
  {
    "path": "p2p/net/reuseport/dial.go",
    "content": "package reuseport\n\nimport (\n\t\"context\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\n// Dial dials the given multiaddr, reusing ports we're currently listening on if\n// possible.\n//\n// Dial attempts to be smart about choosing the source port. For example, If\n// we're dialing a loopback address and we're listening on one or more loopback\n// ports, Dial will randomly choose one of the loopback ports and addresses and\n// reuse it.\nfunc (t *Transport) Dial(raddr ma.Multiaddr) (manet.Conn, error) {\n\treturn t.DialContext(context.Background(), raddr)\n}\n\n// DialContext is like Dial but takes a context.\nfunc (t *Transport) DialContext(ctx context.Context, raddr ma.Multiaddr) (manet.Conn, error) {\n\tnetwork, addr, err := manet.DialArgs(raddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar d *dialer\n\tswitch network {\n\tcase \"tcp4\":\n\t\td = t.v4.getDialer(network)\n\tcase \"tcp6\":\n\t\td = t.v6.getDialer(network)\n\tdefault:\n\t\treturn nil, ErrWrongProto\n\t}\n\tconn, err := d.DialContext(ctx, network, addr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tmaconn, err := manet.WrapNetConn(conn)\n\tif err != nil {\n\t\tconn.Close()\n\t\treturn nil, err\n\t}\n\treturn maconn, nil\n}\n\nfunc (n *network) getDialer(_ string) *dialer {\n\tn.mu.RLock()\n\td := n.dialer\n\tn.mu.RUnlock()\n\tif d == nil {\n\t\tn.mu.Lock()\n\t\tdefer n.mu.Unlock()\n\n\t\tif n.dialer == nil {\n\t\t\tn.dialer = newDialer(n.listeners)\n\t\t}\n\t\td = n.dialer\n\t}\n\treturn d\n}\n"
  },
  {
    "path": "p2p/net/reuseport/dialer.go",
    "content": "package reuseport\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"net\"\n\n\t\"github.com/libp2p/go-netroute\"\n)\n\ntype dialer struct {\n\t// All address that are _not_ loopback or unspecified (0.0.0.0 or ::).\n\tspecific []*net.TCPAddr\n\t// All loopback addresses (127.*.*.*, ::1).\n\tloopback []*net.TCPAddr\n\t// Unspecified addresses (0.0.0.0, ::)\n\tunspecified []*net.TCPAddr\n}\n\nfunc (d *dialer) Dial(network, addr string) (net.Conn, error) {\n\treturn d.DialContext(context.Background(), network, addr)\n}\n\nfunc randAddr(addrs []*net.TCPAddr) *net.TCPAddr {\n\tif len(addrs) > 0 {\n\t\treturn addrs[rand.Intn(len(addrs))]\n\t}\n\treturn nil\n}\n\n// DialContext dials a target addr.\n//\n// In-order:\n//\n//  1. If we're _explicitly_ listening on the preferred source address for the destination address\n//     (per the system's routes), we'll use that listener's port as the source port.\n//  2. If we're listening on one or more _unspecified_ addresses (zero address), we'll pick a source\n//     port from one of these listener's.\n//  3. Otherwise, we'll let the system pick the source port.\nfunc (d *dialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) {\n\t// We only check this case if the user is listening on a specific address (loopback or\n\t// otherwise). Generally, users will listen on the \"unspecified\" address (0.0.0.0 or ::) and\n\t// we can skip this section.\n\t//\n\t// This lets us avoid resolving the address twice, in most cases.\n\tif len(d.specific) > 0 || len(d.loopback) > 0 {\n\t\ttcpAddr, err := net.ResolveTCPAddr(network, addr)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tip := tcpAddr.IP\n\t\tif !ip.IsLoopback() && !ip.IsGlobalUnicast() {\n\t\t\treturn nil, fmt.Errorf(\"undialable IP: %s\", ip)\n\t\t}\n\n\t\t// If we're listening on some specific address and that specific address happens to\n\t\t// be the preferred source address for the target destination address, we try to\n\t\t// dial with that address/port.\n\t\t//\n\t\t// We skip this check if we _aren't_ listening on any specific addresses, because\n\t\t// checking routing tables can be expensive and users rarely listen on specific IP\n\t\t// addresses.\n\t\tif len(d.specific) > 0 {\n\t\t\tif router, err := netroute.New(); err == nil {\n\t\t\t\tif _, _, preferredSrc, err := router.Route(ip); err == nil {\n\t\t\t\t\tfor _, optAddr := range d.specific {\n\t\t\t\t\t\tif optAddr.IP.Equal(preferredSrc) {\n\t\t\t\t\t\t\treturn reuseDial(ctx, optAddr, network, addr)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Otherwise, if we are listening on a loopback address and the destination is also\n\t\t// a loopback address, use the port from our loopback listener.\n\t\tif len(d.loopback) > 0 && ip.IsLoopback() {\n\t\t\treturn reuseDial(ctx, randAddr(d.loopback), network, addr)\n\t\t}\n\t}\n\n\t// If we're listening on any uspecified addresses, use a randomly chosen port from one of\n\t// these listeners.\n\tif len(d.unspecified) > 0 {\n\t\treturn reuseDial(ctx, randAddr(d.unspecified), network, addr)\n\t}\n\n\t// Finally, just pick a random port.\n\tvar dialer net.Dialer\n\treturn dialer.DialContext(ctx, network, addr)\n}\n\nfunc newDialer(listeners map[*listener]struct{}) *dialer {\n\tspecific := make([]*net.TCPAddr, 0)\n\tloopback := make([]*net.TCPAddr, 0)\n\tunspecified := make([]*net.TCPAddr, 0)\n\n\tfor l := range listeners {\n\t\taddr := l.Addr().(*net.TCPAddr)\n\t\tif addr.IP.IsLoopback() {\n\t\t\tloopback = append(loopback, addr)\n\t\t} else if addr.IP.IsUnspecified() {\n\t\t\tunspecified = append(unspecified, addr)\n\t\t} else {\n\t\t\tspecific = append(specific, addr)\n\t\t}\n\t}\n\treturn &dialer{\n\t\tspecific:    specific,\n\t\tloopback:    loopback,\n\t\tunspecified: unspecified,\n\t}\n}\n"
  },
  {
    "path": "p2p/net/reuseport/listen.go",
    "content": "package reuseport\n\nimport (\n\t\"net\"\n\n\t\"github.com/libp2p/go-reuseport\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\ntype listener struct {\n\tmanet.Listener\n\tnetwork *network\n}\n\nfunc (l *listener) Close() error {\n\tl.network.mu.Lock()\n\tdelete(l.network.listeners, l)\n\tl.network.dialer = nil\n\tl.network.mu.Unlock()\n\treturn l.Listener.Close()\n}\n\n// Listen listens on the given multiaddr.\n//\n// If reuseport is supported, it will be enabled for this listener and future\n// dials from this transport may reuse the port.\n//\n// Note: You can listen on the same multiaddr as many times as you want\n// (although only *one* listener will end up handling the inbound connection).\nfunc (t *Transport) Listen(laddr ma.Multiaddr) (manet.Listener, error) {\n\tnw, naddr, err := manet.DialArgs(laddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar n *network\n\tswitch nw {\n\tcase \"tcp4\":\n\t\tn = &t.v4\n\tcase \"tcp6\":\n\t\tn = &t.v6\n\tdefault:\n\t\treturn nil, ErrWrongProto\n\t}\n\n\tif !reuseport.Available() {\n\t\treturn manet.Listen(laddr)\n\t}\n\tnl, err := reuseport.Listen(nw, naddr)\n\tif err != nil {\n\t\treturn manet.Listen(laddr)\n\t}\n\n\tif _, ok := nl.Addr().(*net.TCPAddr); !ok {\n\t\tnl.Close()\n\t\treturn nil, ErrWrongProto\n\t}\n\n\tmalist, err := manet.WrapNetListener(nl)\n\tif err != nil {\n\t\tnl.Close()\n\t\treturn nil, err\n\t}\n\n\tlist := &listener{\n\t\tListener: malist,\n\t\tnetwork:  n,\n\t}\n\n\tn.mu.Lock()\n\tdefer n.mu.Unlock()\n\n\tif n.listeners == nil {\n\t\tn.listeners = make(map[*listener]struct{})\n\t}\n\tn.listeners[list] = struct{}{}\n\tn.dialer = nil\n\n\treturn list, nil\n}\n"
  },
  {
    "path": "p2p/net/reuseport/reuseport.go",
    "content": "package reuseport\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"github.com/libp2p/go-reuseport\"\n)\n\nvar fallbackDialer net.Dialer\n\n// Dials using reuseport and then redials normally if that fails.\nfunc reuseDial(ctx context.Context, laddr *net.TCPAddr, network, raddr string) (con net.Conn, err error) {\n\tif laddr == nil {\n\t\treturn fallbackDialer.DialContext(ctx, network, raddr)\n\t}\n\n\td := net.Dialer{\n\t\tLocalAddr: laddr,\n\t\tControl:   reuseport.Control,\n\t}\n\n\tcon, err = d.DialContext(ctx, network, raddr)\n\tif err == nil {\n\t\treturn con, nil\n\t}\n\n\tif reuseErrShouldRetry(err) && ctx.Err() == nil {\n\t\t// We could have an existing socket open or we could have one\n\t\t// stuck in TIME-WAIT.\n\t\tlog.Debug(\"failed to reuse port, will try again with a random port\", \"err\", err)\n\t\tcon, err = fallbackDialer.DialContext(ctx, network, raddr)\n\t}\n\treturn con, err\n}\n"
  },
  {
    "path": "p2p/net/reuseport/reuseport_plan9.go",
    "content": "package reuseport\n\nimport (\n\t\"net\"\n\t\"os\"\n)\n\nconst (\n\tEADDRINUSE   = \"address in use\"\n\tECONNREFUSED = \"connection refused\"\n)\n\n// reuseErrShouldRetry diagnoses whether to retry after a reuse error.\n// if we failed to bind, we should retry. if bind worked and this is a\n// real dial error (remote end didnt answer) then we should not retry.\nfunc reuseErrShouldRetry(err error) bool {\n\tif err == nil {\n\t\treturn false // hey, it worked! no need to retry.\n\t}\n\n\t// if it's a network timeout error, it's a legitimate failure.\n\tif nerr, ok := err.(net.Error); ok && nerr.Timeout() {\n\t\treturn false\n\t}\n\n\te, ok := err.(*net.OpError)\n\tif !ok {\n\t\treturn true\n\t}\n\n\te1, ok := e.Err.(*os.PathError)\n\tif !ok {\n\t\treturn true\n\t}\n\n\tswitch e1.Err.Error() {\n\tcase EADDRINUSE:\n\t\treturn true\n\tcase ECONNREFUSED:\n\t\treturn false\n\tdefault:\n\t\treturn true // optimistically default to retry.\n\t}\n}\n"
  },
  {
    "path": "p2p/net/reuseport/reuseport_posix.go",
    "content": "//go:build !plan9\n\npackage reuseport\n\nimport (\n\t\"net\"\n\t\"syscall\"\n)\n\n// reuseErrShouldRetry diagnoses whether to retry after a reuse error.\n// if we failed to bind, we should retry. if bind worked and this is a\n// real dial error (remote end didnt answer) then we should not retry.\nfunc reuseErrShouldRetry(err error) bool {\n\tif err == nil {\n\t\treturn false // hey, it worked! no need to retry.\n\t}\n\n\t// if it's a network timeout error, it's a legitimate failure.\n\tif nerr, ok := err.(net.Error); ok && nerr.Timeout() {\n\t\treturn false\n\t}\n\n\terrno, ok := err.(syscall.Errno)\n\tif !ok { // not an errno? who knows what this is. retry.\n\t\treturn true\n\t}\n\n\tswitch errno {\n\tcase syscall.EADDRINUSE, syscall.EADDRNOTAVAIL:\n\t\treturn true // failure to bind. retry.\n\tcase syscall.ECONNREFUSED:\n\t\treturn false // real dial error\n\tdefault:\n\t\treturn true // optimistically default to retry.\n\t}\n}\n"
  },
  {
    "path": "p2p/net/reuseport/reuseport_test.go",
    "content": "//go:build !plan9\n\npackage reuseport\n\nimport (\n\t\"net\"\n\t\"syscall\"\n\t\"testing\"\n)\n\ntype netTimeoutErr struct {\n\ttimeout bool\n}\n\nfunc (e netTimeoutErr) Error() string {\n\treturn \"\"\n}\n\nfunc (e netTimeoutErr) Timeout() bool {\n\treturn e.timeout\n}\n\nfunc (e netTimeoutErr) Temporary() bool {\n\tpanic(\"not checked\")\n}\n\nfunc TestReuseError(t *testing.T) {\n\tvar nte1 net.Error = &netTimeoutErr{true}\n\tvar nte2 net.Error = &netTimeoutErr{false}\n\n\tcases := map[error]bool{\n\t\tnil:                   false,\n\t\tsyscall.EADDRINUSE:    true,\n\t\tsyscall.EADDRNOTAVAIL: true,\n\t\tsyscall.ECONNREFUSED:  false,\n\n\t\tnte1: false,\n\t\tnte2: true, // this ones a little weird... we should check neterror.Temporary() too\n\n\t\t// test 'default' to true\n\t\tsyscall.EBUSY: true,\n\t}\n\n\tfor k, v := range cases {\n\t\tif reuseErrShouldRetry(k) != v {\n\t\t\tt.Fatalf(\"expected %t for %#v\", v, k)\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "p2p/net/reuseport/transport.go",
    "content": "// Package reuseport provides a basic transport for automatically (and intelligently) reusing TCP ports.\n//\n// To use, construct a new Transport and configure listeners tr.Listen(...).\n// When dialing (tr.Dial(...)), the transport will attempt to reuse the ports it's currently listening on,\n// choosing the best one depending on the destination address.\n//\n// It is recommended to set SO_LINGER to 0 for all connections, otherwise\n// reusing the port may fail when re-dialing a recently closed connection.\n// See https://hea-www.harvard.edu/~fine/Tech/addrinuse.html for details.\npackage reuseport\n\nimport (\n\t\"errors\"\n\t\"sync\"\n\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n)\n\nvar log = logging.Logger(\"reuseport-transport\")\n\n// ErrWrongProto is returned when dialing a protocol other than tcp.\nvar ErrWrongProto = errors.New(\"can only dial TCP over IPv4 or IPv6\")\n\n// Transport is a TCP reuse transport that reuses listener ports.\n// The zero value is safe to use.\ntype Transport struct {\n\tv4 network\n\tv6 network\n}\n\ntype network struct {\n\tmu        sync.RWMutex\n\tlisteners map[*listener]struct{}\n\tdialer    *dialer\n}\n"
  },
  {
    "path": "p2p/net/reuseport/transport_test.go",
    "content": "package reuseport\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"runtime\"\n\t\"slices\"\n\t\"testing\"\n\t\"time\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\nvar loopbackV4, _ = ma.NewMultiaddr(\"/ip4/127.0.0.1/tcp/0\")\nvar loopbackV6, _ = ma.NewMultiaddr(\"/ip6/::1/tcp/0\")\nvar unspecV6, _ = ma.NewMultiaddr(\"/ip6/::/tcp/0\")\nvar unspecV4, _ = ma.NewMultiaddr(\"/ip4/0.0.0.0/tcp/0\")\n\nvar globalV4 ma.Multiaddr\nvar globalV6 ma.Multiaddr\n\nfunc init() {\n\taddrs, err := manet.InterfaceMultiaddrs()\n\tif err != nil {\n\t\treturn\n\t}\n\tfor _, addr := range addrs {\n\t\tif !manet.IsIP6LinkLocal(addr) && !manet.IsIPLoopback(addr) {\n\t\t\ttcp, _ := ma.NewMultiaddr(\"/tcp/0\")\n\t\t\tswitch addr.Protocols()[0].Code {\n\t\t\tcase ma.P_IP4:\n\t\t\t\tif globalV4 == nil {\n\t\t\t\t\tglobalV4 = addr.Encapsulate(tcp)\n\t\t\t\t}\n\t\t\tcase ma.P_IP6:\n\t\t\t\tif globalV6 == nil {\n\t\t\t\t\tglobalV6 = addr.Encapsulate(tcp)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc setLingerZero(c manet.Conn) {\n\tif runtime.GOOS == \"darwin\" {\n\t\tc.(interface{ SetLinger(int) error }).SetLinger(0)\n\t}\n}\n\nfunc acceptOne(t *testing.T, listener manet.Listener) <-chan manet.Conn {\n\tt.Helper()\n\tdone := make(chan manet.Conn, 1)\n\tgo func() {\n\t\tdefer close(done)\n\t\tc, err := listener.Accept()\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\treturn\n\t\t}\n\t\tsetLingerZero(c)\n\t\tdone <- c\n\t}()\n\treturn done\n}\n\nfunc dialOne(t *testing.T, tr *Transport, listener manet.Listener, expected ...int) int {\n\tt.Helper()\n\n\tctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)\n\tdefer cancel()\n\n\tconnChan := acceptOne(t, listener)\n\tc, err := tr.DialContext(ctx, listener.Multiaddr())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tsetLingerZero(c)\n\tport := c.LocalAddr().(*net.TCPAddr).Port\n\tserverConn := <-connChan\n\tserverConn.Close()\n\tc.Close()\n\tif len(expected) == 0 {\n\t\treturn port\n\t}\n\tif slices.Contains(expected, port) {\n\t\treturn port\n\t}\n\tt.Errorf(\"dialed %s from %v. expected to dial from port %v\", listener.Multiaddr(), c.LocalAddr(), expected)\n\treturn 0\n}\n\nfunc TestNoneAndSingle(t *testing.T) {\n\tvar trA Transport\n\tvar trB Transport\n\tlistenerA, err := trA.Listen(loopbackV4)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer listenerA.Close()\n\n\tdialOne(t, &trB, listenerA)\n\n\tlistenerB, err := trB.Listen(loopbackV4)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer listenerB.Close()\n\n\tdialOne(t, &trB, listenerA, listenerB.Addr().(*net.TCPAddr).Port)\n}\n\nfunc TestTwoLocal(t *testing.T) {\n\tvar trA Transport\n\tvar trB Transport\n\tlistenerA, err := trA.Listen(loopbackV4)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer listenerA.Close()\n\n\tlistenerB1, err := trB.Listen(loopbackV4)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer listenerB1.Close()\n\n\tlistenerB2, err := trB.Listen(loopbackV4)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer listenerB2.Close()\n\n\tdialOne(t, &trB, listenerA,\n\t\tlistenerB1.Addr().(*net.TCPAddr).Port,\n\t\tlistenerB2.Addr().(*net.TCPAddr).Port)\n}\n\nfunc TestGlobalPreferenceV4(t *testing.T) {\n\tif globalV4 == nil {\n\t\tt.Skip(\"no global IPv4 addresses configured\")\n\t\treturn\n\t}\n\tt.Logf(\"when listening on %v, should prefer %v over %v\", loopbackV4, loopbackV4, globalV4)\n\ttestPrefer(t, loopbackV4, loopbackV4, globalV4)\n\tt.Logf(\"when listening on %v, should prefer %v over %v\", loopbackV4, unspecV4, globalV4)\n\ttestPrefer(t, loopbackV4, unspecV4, globalV4)\n\tt.Logf(\"when listening on %v, should prefer %v over %v\", globalV4, unspecV4, loopbackV4)\n\ttestPrefer(t, globalV4, unspecV4, loopbackV4)\n}\n\nfunc TestGlobalPreferenceV6(t *testing.T) {\n\tif globalV6 == nil {\n\t\tt.Skip(\"no global IPv6 addresses configured\")\n\t\treturn\n\t}\n\ttestPrefer(t, loopbackV6, loopbackV6, globalV6)\n\ttestPrefer(t, loopbackV6, unspecV6, globalV6)\n\n\ttestPrefer(t, globalV6, unspecV6, loopbackV6)\n}\n\nfunc TestLoopbackPreference(t *testing.T) {\n\ttestPrefer(t, loopbackV4, loopbackV4, unspecV4)\n\ttestPrefer(t, loopbackV6, loopbackV6, unspecV6)\n}\n\nfunc testPrefer(t *testing.T, listen, prefer, avoid ma.Multiaddr) {\n\tvar trA Transport\n\tvar trB Transport\n\tlistenerA, err := trA.Listen(listen)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer listenerA.Close()\n\n\tlistenerB1, err := trB.Listen(avoid)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer listenerB1.Close()\n\n\tlistenerB2, err := trB.Listen(prefer)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer listenerB2.Close()\n\n\tdialOne(t, &trB, listenerA, listenerB2.Addr().(*net.TCPAddr).Port)\n}\n\nfunc TestV6V4(t *testing.T) {\n\tif runtime.GOOS == \"darwin\" {\n\t\tt.Skip(\"This test is failing on OSX: https://github.com/libp2p/go-reuseport-transport/issues/40\")\n\t}\n\ttestUseFirst(t, loopbackV4, loopbackV4, loopbackV6)\n\ttestUseFirst(t, loopbackV6, loopbackV6, loopbackV4)\n}\n\nfunc TestGlobalToGlobal(t *testing.T) {\n\tif globalV4 == nil {\n\t\tt.Skip(\"no globalV4 addresses configured\")\n\t\treturn\n\t}\n\ttestUseFirst(t, globalV4, globalV4, loopbackV4)\n\ttestUseFirst(t, globalV6, globalV6, loopbackV6)\n}\n\nfunc testUseFirst(t *testing.T, _, _, _ ma.Multiaddr) {\n\tvar trA Transport\n\tvar trB Transport\n\tlistenerA, err := trA.Listen(globalV4)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer listenerA.Close()\n\n\tlistenerB1, err := trB.Listen(loopbackV4)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer listenerB1.Close()\n\n\t// It works (random port)\n\tdialOne(t, &trB, listenerA)\n\n\tlistenerB2, err := trB.Listen(globalV4)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer listenerB2.Close()\n\n\t// Uses globalV4 port.\n\tdialOne(t, &trB, listenerA, listenerB2.Addr().(*net.TCPAddr).Port)\n\n\t// Closing the listener should reset the dialer.\n\tlistenerB2.Close()\n\n\t// It still works.\n\tdialOne(t, &trB, listenerA)\n}\n\nfunc TestDuplicateGlobal(t *testing.T) {\n\tif globalV4 == nil {\n\t\tt.Skip(\"no globalV4 addresses configured\")\n\t\treturn\n\t}\n\n\tvar trA Transport\n\tvar trB Transport\n\tlistenerA, err := trA.Listen(globalV4)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer listenerA.Close()\n\n\tlistenerB1, err := trB.Listen(globalV4)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer listenerB1.Close()\n\n\tlistenerB2, err := trB.Listen(globalV4)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer listenerB2.Close()\n\n\t// Check which port we're using\n\tport := dialOne(t, &trB, listenerA)\n\n\t// Check consistency\n\tfor range 10 {\n\t\tdialOne(t, &trB, listenerA, port)\n\t}\n}\n"
  },
  {
    "path": "p2p/net/swarm/black_hole_detector.go",
    "content": "package swarm\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\ntype BlackHoleState int\n\nconst (\n\tblackHoleStateProbing BlackHoleState = iota\n\tblackHoleStateAllowed\n\tblackHoleStateBlocked\n)\n\nfunc (st BlackHoleState) String() string {\n\tswitch st {\n\tcase blackHoleStateProbing:\n\t\treturn \"Probing\"\n\tcase blackHoleStateAllowed:\n\t\treturn \"Allowed\"\n\tcase blackHoleStateBlocked:\n\t\treturn \"Blocked\"\n\tdefault:\n\t\treturn fmt.Sprintf(\"Unknown %d\", st)\n\t}\n}\n\n// BlackHoleSuccessCounter provides black hole filtering for dials. This filter should be used in concert\n// with a UDP or IPv6 address filter to detect UDP or IPv6 black hole. In a black holed environment,\n// dial requests are refused Requests are blocked if the number of successes in the last N dials is\n// less than MinSuccesses.\n// If a request succeeds in Blocked state, the filter state is reset and N subsequent requests are\n// allowed before reevaluating black hole state. Dials cancelled when some other concurrent dial\n// succeeded are counted as failures. A sufficiently large N prevents false negatives in such cases.\ntype BlackHoleSuccessCounter struct {\n\t// N is\n\t// 1. The minimum number of completed dials required before evaluating black hole state\n\t// 2. the minimum number of requests after which we probe the state of the black hole in\n\t// blocked state\n\tN int\n\t// MinSuccesses is the minimum number of Success required in the last n dials\n\t// to consider we are not blocked.\n\tMinSuccesses int\n\t// Name for the detector.\n\tName string\n\n\tmu sync.Mutex\n\t// requests counts number of dial requests to peers. We handle request at a peer\n\t// level and record results at individual address dial level.\n\trequests int\n\t// dialResults of the last `n` dials. A successful dial is true.\n\tdialResults []bool\n\t// successes is the count of successful dials in outcomes\n\tsuccesses int\n\t// state is the current state of the detector\n\tstate BlackHoleState\n}\n\n// RecordResult records the outcome of a dial. A successful dial in Blocked state will change the\n// state of the filter to Probing. A failed dial only blocks subsequent requests if the success\n// fraction over the last n outcomes is less than the minSuccessFraction of the filter.\nfunc (b *BlackHoleSuccessCounter) RecordResult(success bool) {\n\tb.mu.Lock()\n\tdefer b.mu.Unlock()\n\n\tif b.state == blackHoleStateBlocked && success {\n\t\t// If the call succeeds in a blocked state we reset to allowed.\n\t\t// This is better than slowly accumulating values till we cross the minSuccessFraction\n\t\t// threshold since a black hole is a binary property.\n\t\tb.reset()\n\t\treturn\n\t}\n\n\tif success {\n\t\tb.successes++\n\t}\n\tb.dialResults = append(b.dialResults, success)\n\n\tif len(b.dialResults) > b.N {\n\t\tif b.dialResults[0] {\n\t\t\tb.successes--\n\t\t}\n\t\tb.dialResults = b.dialResults[1:]\n\t}\n\n\tb.updateState()\n}\n\n// HandleRequest returns the result of applying the black hole filter for the request.\nfunc (b *BlackHoleSuccessCounter) HandleRequest() BlackHoleState {\n\tb.mu.Lock()\n\tdefer b.mu.Unlock()\n\n\tb.requests++\n\n\tif b.state == blackHoleStateAllowed {\n\t\treturn blackHoleStateAllowed\n\t} else if b.state == blackHoleStateProbing || b.requests%b.N == 0 {\n\t\treturn blackHoleStateProbing\n\t} else {\n\t\treturn blackHoleStateBlocked\n\t}\n}\n\nfunc (b *BlackHoleSuccessCounter) reset() {\n\tb.successes = 0\n\tb.dialResults = b.dialResults[:0]\n\tb.requests = 0\n\tb.updateState()\n}\n\nfunc (b *BlackHoleSuccessCounter) updateState() {\n\tst := b.state\n\n\tif len(b.dialResults) < b.N {\n\t\tb.state = blackHoleStateProbing\n\t} else if b.successes >= b.MinSuccesses {\n\t\tb.state = blackHoleStateAllowed\n\t} else {\n\t\tb.state = blackHoleStateBlocked\n\t}\n\n\tif st != b.state {\n\t\tlog.Debug(\"blackHoleDetector state changed\", \"name\", b.Name, \"from\", st, \"to\", b.state)\n\t}\n}\n\nfunc (b *BlackHoleSuccessCounter) State() BlackHoleState {\n\tb.mu.Lock()\n\tdefer b.mu.Unlock()\n\n\treturn b.state\n}\n\ntype blackHoleInfo struct {\n\tname            string\n\tstate           BlackHoleState\n\tnextProbeAfter  int\n\tsuccessFraction float64\n}\n\nfunc (b *BlackHoleSuccessCounter) info() blackHoleInfo {\n\tb.mu.Lock()\n\tdefer b.mu.Unlock()\n\n\tnextProbeAfter := 0\n\tif b.state == blackHoleStateBlocked {\n\t\tnextProbeAfter = b.N - (b.requests % b.N)\n\t}\n\n\tsuccessFraction := 0.0\n\tif len(b.dialResults) > 0 {\n\t\tsuccessFraction = float64(b.successes) / float64(len(b.dialResults))\n\t}\n\n\treturn blackHoleInfo{\n\t\tname:            b.Name,\n\t\tstate:           b.state,\n\t\tnextProbeAfter:  nextProbeAfter,\n\t\tsuccessFraction: successFraction,\n\t}\n}\n\n// blackHoleDetector provides UDP and IPv6 black hole detection using a `BlackHoleSuccessCounter` for each.\n// For details of the black hole detection logic see `BlackHoleSuccessCounter`.\n// In Read Only mode, detector doesn't update the state of underlying filters and refuses requests\n// when black hole state is unknown. This is useful for Swarms made specifically for services like\n// AutoNAT where we care about accurately reporting the reachability of a peer.\n//\n// Black hole filtering is done at a peer dial level to ensure that periodic probes to detect change\n// of the black hole state are actually dialed and are not skipped because of dial prioritisation\n// logic.\ntype blackHoleDetector struct {\n\tudp, ipv6 *BlackHoleSuccessCounter\n\tmt        MetricsTracer\n\treadOnly  bool\n}\n\n// FilterAddrs filters the peer's addresses removing black holed addresses\nfunc (d *blackHoleDetector) FilterAddrs(addrs []ma.Multiaddr) (valid []ma.Multiaddr, blackHoled []ma.Multiaddr) {\n\thasUDP, hasIPv6 := false, false\n\tfor _, a := range addrs {\n\t\tif !manet.IsPublicAddr(a) {\n\t\t\tcontinue\n\t\t}\n\t\tif isProtocolAddr(a, ma.P_UDP) {\n\t\t\thasUDP = true\n\t\t}\n\t\tif isProtocolAddr(a, ma.P_IP6) {\n\t\t\thasIPv6 = true\n\t\t}\n\t}\n\n\tudpRes := blackHoleStateAllowed\n\tif d.udp != nil && hasUDP {\n\t\tudpRes = d.getFilterState(d.udp)\n\t\td.trackMetrics(d.udp)\n\t}\n\n\tipv6Res := blackHoleStateAllowed\n\tif d.ipv6 != nil && hasIPv6 {\n\t\tipv6Res = d.getFilterState(d.ipv6)\n\t\td.trackMetrics(d.ipv6)\n\t}\n\n\tblackHoled = make([]ma.Multiaddr, 0, len(addrs))\n\treturn ma.FilterAddrs(\n\t\taddrs,\n\t\tfunc(a ma.Multiaddr) bool {\n\t\t\tif !manet.IsPublicAddr(a) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\t// allow all UDP addresses while probing irrespective of IPv6 black hole state\n\t\t\tif udpRes == blackHoleStateProbing && isProtocolAddr(a, ma.P_UDP) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\t// allow all IPv6 addresses while probing irrespective of UDP black hole state\n\t\t\tif ipv6Res == blackHoleStateProbing && isProtocolAddr(a, ma.P_IP6) {\n\t\t\t\treturn true\n\t\t\t}\n\n\t\t\tif udpRes == blackHoleStateBlocked && isProtocolAddr(a, ma.P_UDP) {\n\t\t\t\tblackHoled = append(blackHoled, a)\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tif ipv6Res == blackHoleStateBlocked && isProtocolAddr(a, ma.P_IP6) {\n\t\t\t\tblackHoled = append(blackHoled, a)\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn true\n\t\t},\n\t), blackHoled\n}\n\n// RecordResult updates the state of the relevant BlackHoleSuccessCounters for addr\nfunc (d *blackHoleDetector) RecordResult(addr ma.Multiaddr, success bool) {\n\tif d.readOnly || !manet.IsPublicAddr(addr) {\n\t\treturn\n\t}\n\tif d.udp != nil && isProtocolAddr(addr, ma.P_UDP) {\n\t\td.udp.RecordResult(success)\n\t\td.trackMetrics(d.udp)\n\t}\n\tif d.ipv6 != nil && isProtocolAddr(addr, ma.P_IP6) {\n\t\td.ipv6.RecordResult(success)\n\t\td.trackMetrics(d.ipv6)\n\t}\n}\n\nfunc (d *blackHoleDetector) getFilterState(f *BlackHoleSuccessCounter) BlackHoleState {\n\tif d.readOnly {\n\t\tif f.State() != blackHoleStateAllowed {\n\t\t\treturn blackHoleStateBlocked\n\t\t}\n\t\treturn blackHoleStateAllowed\n\t}\n\treturn f.HandleRequest()\n}\n\nfunc (d *blackHoleDetector) trackMetrics(f *BlackHoleSuccessCounter) {\n\tif d.readOnly || d.mt == nil {\n\t\treturn\n\t}\n\t// Track metrics only in non readOnly state\n\tinfo := f.info()\n\td.mt.UpdatedBlackHoleSuccessCounter(info.name, info.state, info.nextProbeAfter, info.successFraction)\n}\n"
  },
  {
    "path": "p2p/net/swarm/black_hole_detector_test.go",
    "content": "package swarm\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestBlackHoleSuccessCounterReset(t *testing.T) {\n\tn := 10\n\tbhf := &BlackHoleSuccessCounter{N: n, MinSuccesses: 2, Name: \"test\"}\n\t// calls up to n should be probing\n\tfor i := 1; i <= n; i++ {\n\t\tif bhf.HandleRequest() != blackHoleStateProbing {\n\t\t\tt.Fatalf(\"expected calls up to n to be probes\")\n\t\t}\n\t\tif bhf.State() != blackHoleStateProbing {\n\t\t\tt.Fatalf(\"expected state to be probing got %s\", bhf.State())\n\t\t}\n\t\tbhf.RecordResult(false)\n\t}\n\n\t// after threshold calls every nth call should be a probe\n\tfor i := n + 1; i < 42; i++ {\n\t\tresult := bhf.HandleRequest()\n\t\tif (i%n == 0 && result != blackHoleStateProbing) || (i%n != 0 && result != blackHoleStateBlocked) {\n\t\t\tt.Fatalf(\"expected every nth dial to be a probe\")\n\t\t}\n\t\tif bhf.State() != blackHoleStateBlocked {\n\t\t\tt.Fatalf(\"expected state to be blocked, got %s\", bhf.State())\n\t\t}\n\t}\n\n\tbhf.RecordResult(true)\n\t// check if calls up to n are probes again\n\tfor range n {\n\t\tif bhf.HandleRequest() != blackHoleStateProbing {\n\t\t\tt.Fatalf(\"expected black hole detector state to reset after success\")\n\t\t}\n\t\tif bhf.State() != blackHoleStateProbing {\n\t\t\tt.Fatalf(\"expected state to be probing got %s\", bhf.State())\n\t\t}\n\t\tbhf.RecordResult(false)\n\t}\n\n\t// next call should be blocked\n\tif bhf.HandleRequest() != blackHoleStateBlocked {\n\t\tt.Fatalf(\"expected dial to be blocked\")\n\t\tif bhf.State() != blackHoleStateBlocked {\n\t\t\tt.Fatalf(\"expected state to be blocked, got %s\", bhf.State())\n\t\t}\n\t}\n}\n\nfunc TestBlackHoleSuccessCounterSuccessFraction(t *testing.T) {\n\tn := 10\n\ttests := []struct {\n\t\tminSuccesses, successes int\n\t\tresult                  BlackHoleState\n\t}{\n\t\t{minSuccesses: 5, successes: 5, result: blackHoleStateAllowed},\n\t\t{minSuccesses: 3, successes: 3, result: blackHoleStateAllowed},\n\t\t{minSuccesses: 5, successes: 4, result: blackHoleStateBlocked},\n\t\t{minSuccesses: 5, successes: 7, result: blackHoleStateAllowed},\n\t\t{minSuccesses: 3, successes: 1, result: blackHoleStateBlocked},\n\t\t{minSuccesses: 0, successes: 0, result: blackHoleStateAllowed},\n\t\t{minSuccesses: 10, successes: 10, result: blackHoleStateAllowed},\n\t}\n\tfor i, tc := range tests {\n\t\tt.Run(fmt.Sprintf(\"case-%d\", i), func(t *testing.T) {\n\t\t\tbhf := BlackHoleSuccessCounter{N: n, MinSuccesses: tc.minSuccesses}\n\t\t\tfor i := 0; i < tc.successes; i++ {\n\t\t\t\tbhf.RecordResult(true)\n\t\t\t}\n\t\t\tfor i := 0; i < n-tc.successes; i++ {\n\t\t\t\tbhf.RecordResult(false)\n\t\t\t}\n\t\t\tgot := bhf.HandleRequest()\n\t\t\tif got != tc.result {\n\t\t\t\tt.Fatalf(\"expected %d got %d\", tc.result, got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBlackHoleDetectorInApplicableAddress(t *testing.T) {\n\tudpF := &BlackHoleSuccessCounter{N: 10, MinSuccesses: 5}\n\tipv6F := &BlackHoleSuccessCounter{N: 10, MinSuccesses: 5}\n\tbhd := &blackHoleDetector{udp: udpF, ipv6: ipv6F}\n\taddrs := []ma.Multiaddr{\n\t\tma.StringCast(\"/ip4/1.2.3.4/tcp/1234\"),\n\t\tma.StringCast(\"/ip4/1.2.3.4/tcp/1233\"),\n\t\tma.StringCast(\"/ip6/::1/udp/1234/quic-v1\"),\n\t\tma.StringCast(\"/ip4/192.168.1.5/udp/1234/quic-v1\"),\n\t}\n\tfor range 1000 {\n\t\tfilteredAddrs, _ := bhd.FilterAddrs(addrs)\n\t\trequire.ElementsMatch(t, addrs, filteredAddrs)\n\t\tfor j := range addrs {\n\t\t\tbhd.RecordResult(addrs[j], false)\n\t\t}\n\t}\n}\n\nfunc TestBlackHoleDetectorUDPDisabled(t *testing.T) {\n\tipv6F := &BlackHoleSuccessCounter{N: 10, MinSuccesses: 5}\n\tbhd := &blackHoleDetector{ipv6: ipv6F}\n\tpublicAddr := ma.StringCast(\"/ip4/1.2.3.4/udp/1234/quic-v1\")\n\tprivAddr := ma.StringCast(\"/ip4/192.168.1.5/udp/1234/quic-v1\")\n\tfor range 100 {\n\t\tbhd.RecordResult(publicAddr, false)\n\t}\n\twantAddrs := []ma.Multiaddr{publicAddr, privAddr}\n\twantRemovedAddrs := make([]ma.Multiaddr, 0)\n\n\tgotAddrs, gotRemovedAddrs := bhd.FilterAddrs(wantAddrs)\n\trequire.ElementsMatch(t, wantAddrs, gotAddrs)\n\trequire.ElementsMatch(t, wantRemovedAddrs, gotRemovedAddrs)\n}\n\nfunc TestBlackHoleDetectorIPv6Disabled(t *testing.T) {\n\tudpF := &BlackHoleSuccessCounter{N: 10, MinSuccesses: 5}\n\tbhd := &blackHoleDetector{udp: udpF}\n\tpublicAddr := ma.StringCast(\"/ip6/2001::1/tcp/1234\")\n\tprivAddr := ma.StringCast(\"/ip6/::1/tcp/1234\")\n\tfor range 100 {\n\t\tbhd.RecordResult(publicAddr, false)\n\t}\n\n\twantAddrs := []ma.Multiaddr{publicAddr, privAddr}\n\twantRemovedAddrs := make([]ma.Multiaddr, 0)\n\n\tgotAddrs, gotRemovedAddrs := bhd.FilterAddrs(wantAddrs)\n\trequire.ElementsMatch(t, wantAddrs, gotAddrs)\n\trequire.ElementsMatch(t, wantRemovedAddrs, gotRemovedAddrs)\n}\n\nfunc TestBlackHoleDetectorProbes(t *testing.T) {\n\tbhd := &blackHoleDetector{\n\t\tudp:  &BlackHoleSuccessCounter{N: 2, MinSuccesses: 1, Name: \"udp\"},\n\t\tipv6: &BlackHoleSuccessCounter{N: 3, MinSuccesses: 1, Name: \"ipv6\"},\n\t}\n\tudp6Addr := ma.StringCast(\"/ip6/2001::1/udp/1234/quic-v1\")\n\taddrs := []ma.Multiaddr{udp6Addr}\n\tfor range 3 {\n\t\tbhd.RecordResult(udp6Addr, false)\n\t}\n\tfor i := 1; i < 100; i++ {\n\t\tfilteredAddrs, _ := bhd.FilterAddrs(addrs)\n\t\tif i%2 == 0 || i%3 == 0 {\n\t\t\tif len(filteredAddrs) == 0 {\n\t\t\t\tt.Fatalf(\"expected probe to be allowed irrespective of the state of other black hole filter\")\n\t\t\t}\n\t\t} else {\n\t\t\tif len(filteredAddrs) != 0 {\n\t\t\t\tt.Fatalf(\"expected dial to be blocked %s\", filteredAddrs)\n\t\t\t}\n\t\t}\n\t}\n\n}\n\nfunc TestBlackHoleDetectorAddrFiltering(t *testing.T) {\n\tudp6Pub := ma.StringCast(\"/ip6/2001::1/udp/1234/quic-v1\")\n\tudp6Pri := ma.StringCast(\"/ip6/::1/udp/1234/quic-v1\")\n\tudp4Pub := ma.StringCast(\"/ip4/1.2.3.4/udp/1234/quic-v1\")\n\tudp4Pri := ma.StringCast(\"/ip4/192.168.1.5/udp/1234/quic-v1\")\n\ttcp6Pub := ma.StringCast(\"/ip6/2001::1/tcp/1234/quic-v1\")\n\ttcp6Pri := ma.StringCast(\"/ip6/::1/tcp/1234/quic-v1\")\n\ttcp4Pub := ma.StringCast(\"/ip4/1.2.3.4/tcp/1234/quic-v1\")\n\ttcp4Pri := ma.StringCast(\"/ip4/192.168.1.5/tcp/1234/quic-v1\")\n\n\tmakeBHD := func(udpBlocked, ipv6Blocked bool) *blackHoleDetector {\n\t\tbhd := &blackHoleDetector{\n\t\t\tudp:  &BlackHoleSuccessCounter{N: 100, MinSuccesses: 10, Name: \"udp\"},\n\t\t\tipv6: &BlackHoleSuccessCounter{N: 100, MinSuccesses: 10, Name: \"ipv6\"},\n\t\t}\n\t\tfor range 100 {\n\t\t\tbhd.RecordResult(udp4Pub, !udpBlocked)\n\t\t}\n\t\tfor range 100 {\n\t\t\tbhd.RecordResult(tcp6Pub, !ipv6Blocked)\n\t\t}\n\t\treturn bhd\n\t}\n\n\tallInput := []ma.Multiaddr{udp6Pub, udp6Pri, udp4Pub, udp4Pri, tcp6Pub, tcp6Pri,\n\t\ttcp4Pub, tcp4Pri}\n\n\tudpBlockedOutput := []ma.Multiaddr{udp6Pri, udp4Pri, tcp6Pub, tcp6Pri, tcp4Pub, tcp4Pri}\n\tudpPublicAddrs := []ma.Multiaddr{udp6Pub, udp4Pub}\n\tbhd := makeBHD(true, false)\n\tgotAddrs, gotRemovedAddrs := bhd.FilterAddrs(allInput)\n\trequire.ElementsMatch(t, udpBlockedOutput, gotAddrs)\n\trequire.ElementsMatch(t, udpPublicAddrs, gotRemovedAddrs)\n\n\tip6BlockedOutput := []ma.Multiaddr{udp6Pri, udp4Pub, udp4Pri, tcp6Pri, tcp4Pub, tcp4Pri}\n\tip6PublicAddrs := []ma.Multiaddr{udp6Pub, tcp6Pub}\n\tbhd = makeBHD(false, true)\n\tgotAddrs, gotRemovedAddrs = bhd.FilterAddrs(allInput)\n\trequire.ElementsMatch(t, ip6BlockedOutput, gotAddrs)\n\trequire.ElementsMatch(t, ip6PublicAddrs, gotRemovedAddrs)\n\n\tbothBlockedOutput := []ma.Multiaddr{udp6Pri, udp4Pri, tcp6Pri, tcp4Pub, tcp4Pri}\n\tbothPublicAddrs := []ma.Multiaddr{udp6Pub, tcp6Pub, udp4Pub}\n\tbhd = makeBHD(true, true)\n\tgotAddrs, gotRemovedAddrs = bhd.FilterAddrs(allInput)\n\trequire.ElementsMatch(t, bothBlockedOutput, gotAddrs)\n\trequire.ElementsMatch(t, bothPublicAddrs, gotRemovedAddrs)\n}\n\nfunc TestBlackHoleDetectorReadOnlyMode(t *testing.T) {\n\tudpF := &BlackHoleSuccessCounter{N: 10, MinSuccesses: 5}\n\tipv6F := &BlackHoleSuccessCounter{N: 10, MinSuccesses: 5}\n\tbhd := &blackHoleDetector{udp: udpF, ipv6: ipv6F, readOnly: true}\n\tpublicAddr := ma.StringCast(\"/ip4/1.2.3.4/udp/1234/quic-v1\")\n\tprivAddr := ma.StringCast(\"/ip6/::1/tcp/1234\")\n\tfor range 100 {\n\t\tbhd.RecordResult(publicAddr, true)\n\t}\n\tallAddr := []ma.Multiaddr{privAddr, publicAddr}\n\t// public addr filtered because state is probing\n\twantAddrs := []ma.Multiaddr{privAddr}\n\twantRemovedAddrs := []ma.Multiaddr{publicAddr}\n\n\tgotAddrs, gotRemovedAddrs := bhd.FilterAddrs(allAddr)\n\trequire.ElementsMatch(t, wantAddrs, gotAddrs)\n\trequire.ElementsMatch(t, wantRemovedAddrs, gotRemovedAddrs)\n\n\t// a non readonly shared state black hole detector\n\tnbhd := &blackHoleDetector{udp: bhd.udp, ipv6: bhd.ipv6, readOnly: false}\n\tfor range 100 {\n\t\tnbhd.RecordResult(publicAddr, true)\n\t}\n\t// no addresses filtered because state is allowed\n\twantAddrs = []ma.Multiaddr{privAddr, publicAddr}\n\twantRemovedAddrs = []ma.Multiaddr{}\n\n\tgotAddrs, gotRemovedAddrs = bhd.FilterAddrs(allAddr)\n\trequire.ElementsMatch(t, wantAddrs, gotAddrs)\n\trequire.ElementsMatch(t, wantRemovedAddrs, gotRemovedAddrs)\n}\n"
  },
  {
    "path": "p2p/net/swarm/clock.go",
    "content": "package swarm\n\nimport \"time\"\n\n// InstantTimer is a timer that triggers at some instant rather than some duration\ntype InstantTimer interface {\n\tReset(d time.Time) bool\n\tStop() bool\n\tCh() <-chan time.Time\n}\n\n// Clock is a clock that can create timers that trigger at some\n// instant rather than some duration\ntype Clock interface {\n\tNow() time.Time\n\tSince(t time.Time) time.Duration\n\tInstantTimer(when time.Time) InstantTimer\n}\n\ntype RealTimer struct{ t *time.Timer }\n\nvar _ InstantTimer = (*RealTimer)(nil)\n\nfunc (t RealTimer) Ch() <-chan time.Time {\n\treturn t.t.C\n}\n\nfunc (t RealTimer) Reset(d time.Time) bool {\n\treturn t.t.Reset(time.Until(d))\n}\n\nfunc (t RealTimer) Stop() bool {\n\treturn t.t.Stop()\n}\n\ntype RealClock struct{}\n\nvar _ Clock = RealClock{}\n\nfunc (RealClock) Now() time.Time {\n\treturn time.Now()\n}\nfunc (RealClock) Since(t time.Time) time.Duration {\n\treturn time.Since(t)\n}\nfunc (RealClock) InstantTimer(when time.Time) InstantTimer {\n\tt := time.NewTimer(time.Until(when))\n\treturn &RealTimer{t}\n}\n"
  },
  {
    "path": "p2p/net/swarm/connectedness_event_emitter.go",
    "content": "package swarm\n\nimport (\n\t\"context\"\n\t\"sync\"\n\n\t\"github.com/libp2p/go-libp2p/core/event\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n)\n\n// connectednessEventEmitter emits PeerConnectednessChanged events.\n// We ensure that for any peer we connected to we always sent atleast 1 NotConnected Event after\n// the peer disconnects. This is because peers can observe a connection before they are notified\n// of the connection by a peer connectedness changed event.\ntype connectednessEventEmitter struct {\n\tmx sync.RWMutex\n\t// newConns is the channel that holds the peerIDs we recently connected to\n\tnewConns      chan peer.ID\n\tremoveConnsMx sync.Mutex\n\t// removeConns is a slice of peerIDs we have recently closed connections to\n\tremoveConns []peer.ID\n\t// lastEvent is the last connectedness event sent for a particular peer.\n\tlastEvent map[peer.ID]network.Connectedness\n\t// connectedness is the function that gives the peers current connectedness state\n\tconnectedness func(peer.ID) network.Connectedness\n\t// emitter is the PeerConnectednessChanged event emitter\n\temitter         event.Emitter\n\twg              sync.WaitGroup\n\tremoveConnNotif chan struct{}\n\tctx             context.Context\n\tcancel          context.CancelFunc\n}\n\nfunc newConnectednessEventEmitter(connectedness func(peer.ID) network.Connectedness, emitter event.Emitter) *connectednessEventEmitter {\n\tctx, cancel := context.WithCancel(context.Background())\n\tc := &connectednessEventEmitter{\n\t\tnewConns:        make(chan peer.ID, 32),\n\t\tlastEvent:       make(map[peer.ID]network.Connectedness),\n\t\tremoveConnNotif: make(chan struct{}, 1),\n\t\tconnectedness:   connectedness,\n\t\temitter:         emitter,\n\t\tctx:             ctx,\n\t\tcancel:          cancel,\n\t}\n\tc.wg.Add(1)\n\tgo c.runEmitter()\n\treturn c\n}\n\nfunc (c *connectednessEventEmitter) AddConn(p peer.ID) {\n\tc.mx.RLock()\n\tdefer c.mx.RUnlock()\n\tif c.ctx.Err() != nil {\n\t\treturn\n\t}\n\n\tc.newConns <- p\n}\n\nfunc (c *connectednessEventEmitter) RemoveConn(p peer.ID) {\n\tc.mx.RLock()\n\tdefer c.mx.RUnlock()\n\tif c.ctx.Err() != nil {\n\t\treturn\n\t}\n\n\tc.removeConnsMx.Lock()\n\t// This queue is roughly bounded by the total number of added connections we\n\t// have. If consumers of connectedness events are slow, we apply\n\t// backpressure to AddConn operations.\n\t//\n\t// We purposefully don't block/backpressure here to avoid deadlocks, since it's\n\t// reasonable for a consumer of the event to want to remove a connection.\n\tc.removeConns = append(c.removeConns, p)\n\n\tc.removeConnsMx.Unlock()\n\n\tselect {\n\tcase c.removeConnNotif <- struct{}{}:\n\tdefault:\n\t}\n}\n\nfunc (c *connectednessEventEmitter) Close() {\n\tc.cancel()\n\tc.wg.Wait()\n}\n\nfunc (c *connectednessEventEmitter) runEmitter() {\n\tdefer c.wg.Done()\n\tfor {\n\t\tselect {\n\t\tcase p := <-c.newConns:\n\t\t\tc.notifyPeer(p, true)\n\t\tcase <-c.removeConnNotif:\n\t\t\tc.sendConnRemovedNotifications()\n\t\tcase <-c.ctx.Done():\n\t\t\tc.mx.Lock() // Wait for all pending AddConn & RemoveConn operations to complete\n\t\t\tdefer c.mx.Unlock()\n\t\t\tfor {\n\t\t\t\tselect {\n\t\t\t\tcase p := <-c.newConns:\n\t\t\t\t\tc.notifyPeer(p, true)\n\t\t\t\tcase <-c.removeConnNotif:\n\t\t\t\t\tc.sendConnRemovedNotifications()\n\t\t\t\tdefault:\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n// notifyPeer sends the peer connectedness event using the emitter.\n// Use forceNotConnectedEvent = true to send a NotConnected event even if\n// no Connected event was sent for this peer.\n// In case a peer is disconnected before we sent the Connected event, we still\n// send the Disconnected event because a connection to the peer can be observed\n// in such cases.\nfunc (c *connectednessEventEmitter) notifyPeer(p peer.ID, forceNotConnectedEvent bool) {\n\toldState := c.lastEvent[p]\n\tc.lastEvent[p] = c.connectedness(p)\n\tif c.lastEvent[p] == network.NotConnected {\n\t\tdelete(c.lastEvent, p)\n\t}\n\tif (forceNotConnectedEvent && c.lastEvent[p] == network.NotConnected) || c.lastEvent[p] != oldState {\n\t\tc.emitter.Emit(event.EvtPeerConnectednessChanged{\n\t\t\tPeer:          p,\n\t\t\tConnectedness: c.lastEvent[p],\n\t\t})\n\t}\n}\n\nfunc (c *connectednessEventEmitter) sendConnRemovedNotifications() {\n\tc.removeConnsMx.Lock()\n\tremoveConns := c.removeConns\n\tc.removeConns = nil\n\tc.removeConnsMx.Unlock()\n\tfor _, p := range removeConns {\n\t\tc.notifyPeer(p, false)\n\t}\n}\n"
  },
  {
    "path": "p2p/net/swarm/dial_error.go",
    "content": "package swarm\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\n// maxDialDialErrors is the maximum number of dial errors we record\nconst maxDialDialErrors = 16\n\n// DialError is the error type returned when dialing.\ntype DialError struct {\n\tPeer       peer.ID\n\tDialErrors []TransportError\n\tCause      error\n\tSkipped    int\n}\n\nfunc (e *DialError) Timeout() bool {\n\treturn os.IsTimeout(e.Cause)\n}\n\nfunc (e *DialError) recordErr(addr ma.Multiaddr, err error) {\n\tif len(e.DialErrors) >= maxDialDialErrors {\n\t\te.Skipped++\n\t\treturn\n\t}\n\te.DialErrors = append(e.DialErrors, TransportError{Address: addr, Cause: err})\n}\n\nfunc (e *DialError) Error() string {\n\tvar builder strings.Builder\n\tfmt.Fprintf(&builder, \"failed to dial %s:\", e.Peer)\n\tif e.Cause != nil {\n\t\tfmt.Fprintf(&builder, \" %s\", e.Cause)\n\t}\n\tfor _, te := range e.DialErrors {\n\t\tfmt.Fprintf(&builder, \"\\n  * [%s] %s\", te.Address, te.Cause)\n\t}\n\tif e.Skipped > 0 {\n\t\tfmt.Fprintf(&builder, \"\\n    ... skipping %d errors ...\", e.Skipped)\n\t}\n\treturn builder.String()\n}\n\nfunc (e *DialError) Unwrap() []error {\n\tif e == nil {\n\t\treturn nil\n\t}\n\n\terrs := make([]error, 0, len(e.DialErrors)+1)\n\tif e.Cause != nil {\n\t\terrs = append(errs, e.Cause)\n\t}\n\tfor i := 0; i < len(e.DialErrors); i++ {\n\t\terrs = append(errs, &e.DialErrors[i])\n\t}\n\treturn errs\n}\n\nvar _ error = (*DialError)(nil)\n\n// TransportError is the error returned when dialing a specific address.\ntype TransportError struct {\n\tAddress ma.Multiaddr\n\tCause   error\n}\n\nfunc (e *TransportError) Error() string {\n\treturn fmt.Sprintf(\"failed to dial %s: %s\", e.Address, e.Cause)\n}\n\nfunc (e *TransportError) Unwrap() error {\n\treturn e.Cause\n}\n\nvar _ error = (*TransportError)(nil)\n"
  },
  {
    "path": "p2p/net/swarm/dial_error_test.go",
    "content": "package swarm\n\nimport (\n\t\"net\"\n\t\"os\"\n\t\"testing\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestTransportError(t *testing.T) {\n\taa := ma.StringCast(\"/ip4/1.2.3.4/tcp/1234\")\n\tte := &TransportError{Address: aa, Cause: ErrDialBackoff}\n\trequire.ErrorIs(t, te, ErrDialBackoff, \"TransportError should implement Unwrap\")\n}\n\nfunc TestDialError(t *testing.T) {\n\tde := &DialError{Peer: \"pid\", Cause: ErrGaterDisallowedConnection}\n\trequire.ErrorIs(t, de, ErrGaterDisallowedConnection,\n\t\t\"DialError Unwrap should handle DialError.Cause\")\n\trequire.ErrorIs(t, de, de, \"DialError Unwrap should handle match to self\")\n\n\taa := ma.StringCast(\"/ip4/1.2.3.4/tcp/1234\")\n\tab := ma.StringCast(\"/ip6/1::1/udp/1234/quic-v1\")\n\tde = &DialError{\n\t\tPeer: \"pid\",\n\t\tDialErrors: []TransportError{\n\t\t\t{Address: aa, Cause: ErrDialBackoff}, {Address: ab, Cause: ErrNoTransport},\n\t\t},\n\t}\n\trequire.ErrorIs(t, de, ErrDialBackoff, \"DialError.Unwrap should traverse TransportErrors\")\n\trequire.ErrorIs(t, de, ErrNoTransport, \"DialError.Unwrap should traverse TransportErrors\")\n\n\tde = &DialError{\n\t\tPeer: \"pid\",\n\t\tDialErrors: []TransportError{{Address: ab, Cause: ErrNoTransport},\n\t\t\t// wrapped error 2 levels deep\n\t\t\t{Address: aa, Cause: &net.OpError{\n\t\t\t\tOp:  \"write\",\n\t\t\t\tNet: \"tcp\",\n\t\t\t\tErr: &os.SyscallError{\n\t\t\t\t\tSyscall: \"connect\",\n\t\t\t\t\tErr:     os.ErrPermission,\n\t\t\t\t},\n\t\t\t}},\n\t\t},\n\t}\n\trequire.ErrorIs(t, de, os.ErrPermission, \"DialError.Unwrap should traverse TransportErrors\")\n\n}\n"
  },
  {
    "path": "p2p/net/swarm/dial_ranker.go",
    "content": "package swarm\n\nimport (\n\t\"sort\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\n// The 250ms value is from happy eyeballs RFC 8305. This is a rough estimate of 1 RTT\nconst (\n\t// duration by which TCP dials are delayed relative to the last QUIC dial\n\tPublicTCPDelay  = 250 * time.Millisecond\n\tPrivateTCPDelay = 30 * time.Millisecond\n\n\t// duration by which QUIC dials are delayed relative to previous QUIC dial\n\tPublicQUICDelay  = 250 * time.Millisecond\n\tPrivateQUICDelay = 30 * time.Millisecond\n\n\t// RelayDelay is the duration by which relay dials are delayed relative to direct addresses\n\tRelayDelay = 500 * time.Millisecond\n\n\t// delay for other transport addresses. This will apply to /webrtc-direct.\n\tPublicOtherDelay  = 1 * time.Second\n\tPrivateOtherDelay = 100 * time.Millisecond\n)\n\n// NoDelayDialRanker ranks addresses with no delay. This is useful for simultaneous connect requests.\nfunc NoDelayDialRanker(addrs []ma.Multiaddr) []network.AddrDelay {\n\treturn getAddrDelay(addrs, 0, 0, 0, 0)\n}\n\n// DefaultDialRanker determines the ranking of outgoing connection attempts.\n//\n// Addresses are grouped into three distinct groups:\n//\n//   - private addresses (localhost and local networks (RFC 1918))\n//   - public addresses\n//   - relay addresses\n//\n// Within each group, the addresses are ranked according to the ranking logic described below.\n// We then dial addresses according to this ranking, with short timeouts applied between dial attempts.\n// This ranking logic dramatically reduces the number of simultaneous dial attempts, while introducing\n// no additional latency in the vast majority of cases.\n//\n// Private and public address groups are dialed in parallel.\n// Dialing relay addresses is delayed by 500 ms, if we have any non-relay alternatives.\n//\n// Within each group (private, public, relay addresses) we apply the following ranking logic:\n//\n//  1. If both IPv6 QUIC and IPv4 QUIC addresses are present, we do a Happy Eyeballs RFC 8305 style ranking.\n//     First dial the IPv6 QUIC address with the lowest port. After this we dial the IPv4 QUIC address with\n//     the lowest port delayed by 250ms (PublicQUICDelay) for public addresses, and 30ms (PrivateQUICDelay)\n//     for local addresses. After this we dial all the rest of the addresses delayed by 250ms (PublicQUICDelay)\n//     for public addresses, and 30ms (PrivateQUICDelay) for local addresses.\n//  2. If only one of QUIC IPv6 or QUIC IPv4 addresses are present, dial the QUIC address with the lowest port\n//     first. After this we dial the rest of the QUIC addresses delayed by 250ms (PublicQUICDelay) for public\n//     addresses, and 30ms (PrivateQUICDelay) for local addresses.\n//  3. If a QUIC or WebTransport address is present, TCP addresses dials are delayed relative to the last QUIC dial:\n//     We prefer to end up with a QUIC connection. For public addresses, the delay introduced is 250ms (PublicTCPDelay),\n//     and for private addresses 30ms (PrivateTCPDelay).\n//  4. For the TCP addresses we follow a strategy similar to QUIC with an optimisation for handling the long TCP\n//     handshake time described in 6. If both IPv6 TCP and IPv4 TCP addresses are present, we do a Happy Eyeballs\n//     style ranking. First dial the IPv6 TCP address with the lowest port. After this, dial the IPv4 TCP address\n//     with the lowest port delayed by 250ms (PublicTCPDelay) for public addresses, and 30ms (PrivateTCPDelay)\n//     for local addresses. After this we dial all the rest of the addresses delayed by 250ms (PublicTCPDelay) for\n//     public addresses, and 30ms (PrivateTCPDelay) for local addresses.\n//  5. If only one of TCP IPv6 or TCP IPv4 addresses are present, dial the TCP address with the lowest port\n//     first. After this we dial the rest of the TCP addresses delayed by 250ms (PublicTCPDelay) for public\n//     addresses, and 30ms (PrivateTCPDelay) for local addresses.\n//  6. When a TCP socket is connected and awaiting security and muxer upgrade, we stop new dials for 2*PublicTCPDelay\n//     to allow for the upgrade to complete.\n//  7. WebRTC Direct, and other IP transport addresses are dialed 1 second after the last QUIC or TCP dial.\n//     We only ever need to dial these if the peer doesn't have any other transport available, in which\n//     case these are dialed immediately.\n//\n// We dial lowest ports first as they are more likely to be the listen port.\nfunc DefaultDialRanker(addrs []ma.Multiaddr) []network.AddrDelay {\n\trelay, addrs := filterAddrs(addrs, isRelayAddr)\n\tpvt, addrs := filterAddrs(addrs, manet.IsPrivateAddr)\n\tpublic, addrs := filterAddrs(addrs, func(a ma.Multiaddr) bool { return isProtocolAddr(a, ma.P_IP4) || isProtocolAddr(a, ma.P_IP6) })\n\n\tvar relayOffset time.Duration\n\tif len(public) > 0 {\n\t\t// if there is a public direct address available delay relay dials\n\t\trelayOffset = RelayDelay\n\t}\n\n\tres := make([]network.AddrDelay, 0, len(addrs))\n\tres = append(res, getAddrDelay(pvt, PrivateTCPDelay, PrivateQUICDelay, PrivateOtherDelay, 0)...)\n\tres = append(res, getAddrDelay(public, PublicTCPDelay, PublicQUICDelay, PublicOtherDelay, 0)...)\n\tres = append(res, getAddrDelay(relay, PublicTCPDelay, PublicQUICDelay, PublicOtherDelay, relayOffset)...)\n\tvar maxDelay time.Duration\n\tif len(res) > 0 {\n\t\tmaxDelay = res[len(res)-1].Delay\n\t}\n\n\tfor i := range addrs {\n\t\tres = append(res, network.AddrDelay{Addr: addrs[i], Delay: maxDelay + PublicOtherDelay})\n\t}\n\n\treturn res\n}\n\n// getAddrDelay ranks a group of addresses according to the ranking logic explained in\n// documentation for defaultDialRanker.\n// offset is used to delay all addresses by a fixed duration. This is useful for delaying all relay\n// addresses relative to direct addresses.\nfunc getAddrDelay(addrs []ma.Multiaddr, tcpDelay time.Duration, quicDelay time.Duration,\n\totherDelay time.Duration, offset time.Duration) []network.AddrDelay {\n\tif len(addrs) == 0 {\n\t\treturn nil\n\t}\n\n\tsort.Slice(addrs, func(i, j int) bool { return score(addrs[i]) < score(addrs[j]) })\n\n\t// addrs is now sorted by (Transport, IPVersion). Reorder addrs for happy eyeballs dialing.\n\t// For QUIC and TCP, if we have both IPv6 and IPv4 addresses, move the\n\t// highest priority IPv4 address to the second position.\n\thappyEyeballsQUIC := false\n\thappyEyeballsTCP := false\n\t// tcpStartIdx is the index of the first TCP Address\n\tvar tcpStartIdx int\n\t{\n\t\ti := 0\n\t\t// If the first QUIC address is IPv6 move the first QUIC IPv4 address to second position\n\t\tif isQUICAddr(addrs[0]) && isProtocolAddr(addrs[0], ma.P_IP6) {\n\t\t\tfor j := 1; j < len(addrs); j++ {\n\t\t\t\tif isQUICAddr(addrs[j]) && isProtocolAddr(addrs[j], ma.P_IP4) {\n\t\t\t\t\t// The first IPv4 address is at position j\n\t\t\t\t\t// Move the jth element at position 1 shifting the affected elements\n\t\t\t\t\tif j > 1 {\n\t\t\t\t\t\ta := addrs[j]\n\t\t\t\t\t\tcopy(addrs[2:], addrs[1:j])\n\t\t\t\t\t\taddrs[1] = a\n\t\t\t\t\t}\n\t\t\t\t\thappyEyeballsQUIC = true\n\t\t\t\t\ti = j + 1\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor tcpStartIdx = i; tcpStartIdx < len(addrs); tcpStartIdx++ {\n\t\t\tif isProtocolAddr(addrs[tcpStartIdx], ma.P_TCP) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\t// If the first TCP address is IPv6 move the first TCP IPv4 address to second position\n\t\tif tcpStartIdx < len(addrs) && isProtocolAddr(addrs[tcpStartIdx], ma.P_IP6) {\n\t\t\tfor j := tcpStartIdx + 1; j < len(addrs); j++ {\n\t\t\t\tif isProtocolAddr(addrs[j], ma.P_TCP) && isProtocolAddr(addrs[j], ma.P_IP4) {\n\t\t\t\t\t// First TCP IPv4 address is at position j, move it to position tcpStartIdx+1\n\t\t\t\t\t// which is the second priority TCP address\n\t\t\t\t\tif j > tcpStartIdx+1 {\n\t\t\t\t\t\ta := addrs[j]\n\t\t\t\t\t\tcopy(addrs[tcpStartIdx+2:], addrs[tcpStartIdx+1:j])\n\t\t\t\t\t\taddrs[tcpStartIdx+1] = a\n\t\t\t\t\t}\n\t\t\t\t\thappyEyeballsTCP = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tres := make([]network.AddrDelay, 0, len(addrs))\n\tvar tcpFirstDialDelay time.Duration\n\tvar lastQUICOrTCPDelay time.Duration\n\tfor i, addr := range addrs {\n\t\tvar delay time.Duration\n\t\tswitch {\n\t\tcase isQUICAddr(addr):\n\t\t\t// We dial an IPv6 address, then after quicDelay an IPv4\n\t\t\t// address, then after a further quicDelay we dial the rest of the addresses.\n\t\t\tif i == 1 {\n\t\t\t\tdelay = quicDelay\n\t\t\t}\n\t\t\tif i > 1 {\n\t\t\t\t// If we have happy eyeballs for QUIC, dials after the second position\n\t\t\t\t// will be delayed by 2*quicDelay\n\t\t\t\tif happyEyeballsQUIC {\n\t\t\t\t\tdelay = 2 * quicDelay\n\t\t\t\t} else {\n\t\t\t\t\tdelay = quicDelay\n\t\t\t\t}\n\t\t\t}\n\t\t\tlastQUICOrTCPDelay = delay\n\t\t\ttcpFirstDialDelay = delay + tcpDelay\n\t\tcase isProtocolAddr(addr, ma.P_TCP):\n\t\t\t// We dial an IPv6 address, then after tcpDelay an IPv4\n\t\t\t// address, then after a further tcpDelay we dial the rest of the addresses.\n\t\t\tif i == tcpStartIdx+1 {\n\t\t\t\tdelay = tcpDelay\n\t\t\t}\n\t\t\tif i > tcpStartIdx+1 {\n\t\t\t\t// If we have happy eyeballs for TCP, dials after the second position\n\t\t\t\t// will be delayed by 2*tcpDelay\n\t\t\t\tif happyEyeballsTCP {\n\t\t\t\t\tdelay = 2 * tcpDelay\n\t\t\t\t} else {\n\t\t\t\t\tdelay = tcpDelay\n\t\t\t\t}\n\t\t\t}\n\t\t\tdelay += tcpFirstDialDelay\n\t\t\tlastQUICOrTCPDelay = delay\n\t\t// if it's neither quic, webtransport, tcp, or websocket address\n\t\tdefault:\n\t\t\tdelay = lastQUICOrTCPDelay + otherDelay\n\t\t}\n\t\tres = append(res, network.AddrDelay{Addr: addr, Delay: offset + delay})\n\t}\n\treturn res\n}\n\n// score scores a multiaddress for dialing delay. Lower is better.\n// The lower 16 bits of the result are the port. Low ports are ranked higher because they're\n// more likely to be listen addresses.\n// The addresses are ranked as:\n// QUICv1 IPv6 > QUICdraft29 IPv6 > QUICv1 IPv4 > QUICdraft29 IPv4 >\n// WebTransport IPv6 > WebTransport IPv4 > TCP IPv6 > TCP IPv4\nfunc score(a ma.Multiaddr) int {\n\tip4Weight := 0\n\tif isProtocolAddr(a, ma.P_IP4) {\n\t\tip4Weight = 1 << 18\n\t}\n\n\tif _, err := a.ValueForProtocol(ma.P_WEBTRANSPORT); err == nil {\n\t\tp, _ := a.ValueForProtocol(ma.P_UDP)\n\t\tpi, _ := strconv.Atoi(p)\n\t\treturn ip4Weight + (1 << 19) + pi\n\t}\n\tif _, err := a.ValueForProtocol(ma.P_QUIC); err == nil {\n\t\tp, _ := a.ValueForProtocol(ma.P_UDP)\n\t\tpi, _ := strconv.Atoi(p)\n\t\treturn ip4Weight + pi + (1 << 17)\n\t}\n\tif _, err := a.ValueForProtocol(ma.P_QUIC_V1); err == nil {\n\t\tp, _ := a.ValueForProtocol(ma.P_UDP)\n\t\tpi, _ := strconv.Atoi(p)\n\t\treturn ip4Weight + pi\n\t}\n\tif p, err := a.ValueForProtocol(ma.P_TCP); err == nil {\n\t\tpi, _ := strconv.Atoi(p)\n\t\treturn ip4Weight + pi + (1 << 20)\n\t}\n\tif _, err := a.ValueForProtocol(ma.P_WEBRTC_DIRECT); err == nil {\n\t\treturn 1 << 21\n\t}\n\treturn (1 << 30)\n}\n\nfunc isProtocolAddr(a ma.Multiaddr, p int) bool {\n\tfound := false\n\tma.ForEach(a, func(c ma.Component) bool {\n\t\tif c.Protocol().Code == p {\n\t\t\tfound = true\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t})\n\treturn found\n}\n\nfunc isQUICAddr(a ma.Multiaddr) bool {\n\treturn isProtocolAddr(a, ma.P_QUIC) || isProtocolAddr(a, ma.P_QUIC_V1)\n}\n\n// filterAddrs filters an address slice in place\nfunc filterAddrs(addrs []ma.Multiaddr, f func(a ma.Multiaddr) bool) (filtered, rest []ma.Multiaddr) {\n\tj := 0\n\tfor i := range addrs {\n\t\tif f(addrs[i]) {\n\t\t\taddrs[i], addrs[j] = addrs[j], addrs[i]\n\t\t\tj++\n\t\t}\n\t}\n\treturn addrs[:j], addrs[j:]\n}\n"
  },
  {
    "path": "p2p/net/swarm/dial_ranker_test.go",
    "content": "package swarm\n\nimport (\n\t\"fmt\"\n\t\"sort\"\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/test\"\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\nfunc sortAddrDelays(addrDelays []network.AddrDelay) {\n\tsort.Slice(addrDelays, func(i, j int) bool {\n\t\tif addrDelays[i].Delay == addrDelays[j].Delay {\n\t\t\treturn addrDelays[i].Addr.String() < addrDelays[j].Addr.String()\n\t\t}\n\t\treturn addrDelays[i].Delay < addrDelays[j].Delay\n\t})\n}\n\nfunc TestNoDelayDialRanker(t *testing.T) {\n\tq1 := ma.StringCast(\"/ip4/1.2.3.4/udp/1/quic-v1\")\n\tq1v1 := ma.StringCast(\"/ip4/1.2.3.4/udp/1/quic-v1\")\n\twt1 := ma.StringCast(\"/ip4/1.2.3.4/udp/1/quic-v1/webtransport/\")\n\tq2 := ma.StringCast(\"/ip4/1.2.3.4/udp/2/quic-v1\")\n\tq2v1 := ma.StringCast(\"/ip4/1.2.3.4/udp/2/quic-v1\")\n\tq3 := ma.StringCast(\"/ip4/1.2.3.4/udp/3/quic-v1\")\n\tq3v1 := ma.StringCast(\"/ip4/1.2.3.4/udp/3/quic-v1\")\n\tq4 := ma.StringCast(\"/ip4/1.2.3.4/udp/4/quic-v1\")\n\tt1 := ma.StringCast(\"/ip4/1.2.3.5/tcp/1/\")\n\twrtc1 := ma.StringCast(\"/ip4/1.1.1.1/udp/1/webrtc-direct\")\n\n\ttestCase := []struct {\n\t\tname   string\n\t\taddrs  []ma.Multiaddr\n\t\toutput []network.AddrDelay\n\t}{\n\t\t{\n\t\t\tname:  \"quic+webtransport filtered when quicv1\",\n\t\t\taddrs: []ma.Multiaddr{q1, q2, q3, q4, q1v1, q2v1, q3v1, wt1, t1, wrtc1},\n\t\t\toutput: []network.AddrDelay{\n\t\t\t\t{Addr: q1, Delay: 0},\n\t\t\t\t{Addr: q2, Delay: 0},\n\t\t\t\t{Addr: q3, Delay: 0},\n\t\t\t\t{Addr: q4, Delay: 0},\n\t\t\t\t{Addr: q1v1, Delay: 0},\n\t\t\t\t{Addr: q2v1, Delay: 0},\n\t\t\t\t{Addr: q3v1, Delay: 0},\n\t\t\t\t{Addr: wt1, Delay: 0},\n\t\t\t\t{Addr: t1, Delay: 0},\n\t\t\t\t{Addr: wrtc1, Delay: 0},\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tc := range testCase {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tres := NoDelayDialRanker(tc.addrs)\n\t\t\tif len(res) != len(tc.output) {\n\t\t\t\tlog.Error(\"expected output mismatch\", \"expected\", tc.output, \"got\", res)\n\t\t\t\tt.Errorf(\"expected elems: %d got: %d\", len(tc.output), len(res))\n\t\t\t}\n\t\t\tsortAddrDelays(res)\n\t\t\tsortAddrDelays(tc.output)\n\t\t\tfor i := 0; i < len(tc.output); i++ {\n\t\t\t\tif !tc.output[i].Addr.Equal(res[i].Addr) || tc.output[i].Delay != res[i].Delay {\n\t\t\t\t\tt.Fatalf(\"expected %+v got %+v\", tc.output, res)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDelayRankerQUICDelay(t *testing.T) {\n\tq1v1 := ma.StringCast(\"/ip4/1.2.3.4/udp/1/quic-v1\")\n\twt1 := ma.StringCast(\"/ip4/1.2.3.4/udp/1/quic-v1/webtransport/\")\n\tq2v1 := ma.StringCast(\"/ip4/1.2.3.4/udp/2/quic-v1\")\n\tq3v1 := ma.StringCast(\"/ip4/1.2.3.4/udp/3/quic-v1\")\n\n\tq1v16 := ma.StringCast(\"/ip6/1::2/udp/1/quic-v1\")\n\tq2v16 := ma.StringCast(\"/ip6/1::2/udp/2/quic-v1\")\n\tq3v16 := ma.StringCast(\"/ip6/1::2/udp/3/quic-v1\")\n\n\ttestCase := []struct {\n\t\tname   string\n\t\taddrs  []ma.Multiaddr\n\t\toutput []network.AddrDelay\n\t}{\n\t\t{\n\t\t\tname:  \"quic-ipv4\",\n\t\t\taddrs: []ma.Multiaddr{q1v1, q2v1, q3v1},\n\t\t\toutput: []network.AddrDelay{\n\t\t\t\t{Addr: q1v1, Delay: 0},\n\t\t\t\t{Addr: q2v1, Delay: PublicQUICDelay},\n\t\t\t\t{Addr: q3v1, Delay: PublicQUICDelay},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"quic-ipv6\",\n\t\t\taddrs: []ma.Multiaddr{q1v16, q2v16, q3v16},\n\t\t\toutput: []network.AddrDelay{\n\t\t\t\t{Addr: q1v16, Delay: 0},\n\t\t\t\t{Addr: q2v16, Delay: PublicQUICDelay},\n\t\t\t\t{Addr: q3v16, Delay: PublicQUICDelay},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"quic-ip4-ip6\",\n\t\t\taddrs: []ma.Multiaddr{q1v16, q2v1},\n\t\t\toutput: []network.AddrDelay{\n\t\t\t\t{Addr: q1v16, Delay: 0},\n\t\t\t\t{Addr: q2v1, Delay: PublicQUICDelay},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"quic-quic-v1-webtransport\",\n\t\t\taddrs: []ma.Multiaddr{q1v16, q1v1, q2v1, q3v1, wt1},\n\t\t\toutput: []network.AddrDelay{\n\t\t\t\t{Addr: q1v16, Delay: 0},\n\t\t\t\t{Addr: q1v1, Delay: PublicQUICDelay},\n\t\t\t\t{Addr: q2v1, Delay: 2 * PublicQUICDelay},\n\t\t\t\t{Addr: q3v1, Delay: 2 * PublicQUICDelay},\n\t\t\t\t{Addr: wt1, Delay: 2 * PublicQUICDelay},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"wt-ranking\",\n\t\t\taddrs: []ma.Multiaddr{q1v16, q2v16, q3v16, wt1},\n\t\t\toutput: []network.AddrDelay{\n\t\t\t\t{Addr: q1v16, Delay: 0},\n\t\t\t\t{Addr: wt1, Delay: PublicQUICDelay},\n\t\t\t\t{Addr: q2v16, Delay: 2 * PublicQUICDelay},\n\t\t\t\t{Addr: q3v16, Delay: 2 * PublicQUICDelay},\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tc := range testCase {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tres := DefaultDialRanker(tc.addrs)\n\t\t\tif len(res) != len(tc.output) {\n\t\t\t\tlog.Error(\"expected output mismatch\", \"expected\", tc.output, \"got\", res)\n\t\t\t\tt.Errorf(\"expected elems: %d got: %d\", len(tc.output), len(res))\n\t\t\t}\n\t\t\tsortAddrDelays(res)\n\t\t\tsortAddrDelays(tc.output)\n\t\t\tfor i := 0; i < len(tc.output); i++ {\n\t\t\t\tif !tc.output[i].Addr.Equal(res[i].Addr) || tc.output[i].Delay != res[i].Delay {\n\t\t\t\t\tt.Fatalf(\"expected %+v got %+v\", tc.output, res)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDelayRankerTCPDelay(t *testing.T) {\n\tq1v1 := ma.StringCast(\"/ip4/1.2.3.4/udp/1/quic-v1\")\n\tq2v1 := ma.StringCast(\"/ip4/1.2.3.4/udp/2/quic-v1\")\n\n\tq1v16 := ma.StringCast(\"/ip6/1::2/udp/1/quic-v1\")\n\tq2v16 := ma.StringCast(\"/ip6/1::2/udp/2/quic-v1\")\n\tq3v16 := ma.StringCast(\"/ip6/1::2/udp/3/quic-v1\")\n\n\tt1 := ma.StringCast(\"/ip4/1.2.3.5/tcp/1/\")\n\tt1v6 := ma.StringCast(\"/ip6/1::2/tcp/1\")\n\tt2 := ma.StringCast(\"/ip4/1.2.3.4/tcp/2\")\n\tt3 := ma.StringCast(\"/ip4/1.2.3.4/tcp/3\")\n\n\ttestCase := []struct {\n\t\tname   string\n\t\taddrs  []ma.Multiaddr\n\t\toutput []network.AddrDelay\n\t}{\n\t\t{\n\t\t\tname:  \"quic-with-tcp-ip6-ip4\",\n\t\t\taddrs: []ma.Multiaddr{q1v1, q1v16, q2v16, q3v16, q2v1, t1, t1v6, t2, t3},\n\t\t\toutput: []network.AddrDelay{\n\t\t\t\t{Addr: q1v16, Delay: 0},\n\t\t\t\t{Addr: q1v1, Delay: PublicQUICDelay},\n\t\t\t\t{Addr: q2v16, Delay: 2 * PublicQUICDelay},\n\t\t\t\t{Addr: q3v16, Delay: 2 * PublicQUICDelay},\n\t\t\t\t{Addr: q2v1, Delay: 2 * PublicQUICDelay},\n\t\t\t\t{Addr: t1v6, Delay: 3 * PublicQUICDelay},\n\t\t\t\t{Addr: t1, Delay: 4 * PublicQUICDelay},\n\t\t\t\t{Addr: t2, Delay: 5 * PublicQUICDelay},\n\t\t\t\t{Addr: t3, Delay: 5 * PublicQUICDelay},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"quic-ip4-with-tcp\",\n\t\t\taddrs: []ma.Multiaddr{q1v1, t2, t1v6, t1},\n\t\t\toutput: []network.AddrDelay{\n\t\t\t\t{Addr: q1v1, Delay: 0},\n\t\t\t\t{Addr: t1v6, Delay: PublicQUICDelay},\n\t\t\t\t{Addr: t1, Delay: 2 * PublicQUICDelay},\n\t\t\t\t{Addr: t2, Delay: 3 * PublicQUICDelay},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"quic-ip4-with-tcp-ipv4\",\n\t\t\taddrs: []ma.Multiaddr{q1v1, t2, t3, t1},\n\t\t\toutput: []network.AddrDelay{\n\t\t\t\t{Addr: q1v1, Delay: 0},\n\t\t\t\t{Addr: t1, Delay: PublicTCPDelay},\n\t\t\t\t{Addr: t2, Delay: 2 * PublicQUICDelay},\n\t\t\t\t{Addr: t3, Delay: 2 * PublicTCPDelay},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"quic-ip4-with-two-tcp\",\n\t\t\taddrs: []ma.Multiaddr{q1v1, t1v6, t2},\n\t\t\toutput: []network.AddrDelay{\n\t\t\t\t{Addr: q1v1, Delay: 0},\n\t\t\t\t{Addr: t1v6, Delay: PublicTCPDelay},\n\t\t\t\t{Addr: t2, Delay: 2 * PublicTCPDelay},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"tcp-ip4-ip6\",\n\t\t\taddrs: []ma.Multiaddr{t1, t2, t1v6, t3},\n\t\t\toutput: []network.AddrDelay{\n\t\t\t\t{Addr: t1v6, Delay: 0},\n\t\t\t\t{Addr: t1, Delay: PublicTCPDelay},\n\t\t\t\t{Addr: t2, Delay: 2 * PublicTCPDelay},\n\t\t\t\t{Addr: t3, Delay: 2 * PublicTCPDelay},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:   \"empty\",\n\t\t\taddrs:  []ma.Multiaddr{},\n\t\t\toutput: []network.AddrDelay{},\n\t\t},\n\t}\n\tfor _, tc := range testCase {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tres := DefaultDialRanker(tc.addrs)\n\t\t\tif len(res) != len(tc.output) {\n\t\t\t\tlog.Error(\"expected output mismatch\", \"expected\", tc.output, \"got\", res)\n\t\t\t\tt.Errorf(\"expected elems: %d got: %d\", len(tc.output), len(res))\n\t\t\t}\n\t\t\tsortAddrDelays(res)\n\t\t\tsortAddrDelays(tc.output)\n\t\t\tfor i := 0; i < len(tc.output); i++ {\n\t\t\t\tif !tc.output[i].Addr.Equal(res[i].Addr) || tc.output[i].Delay != res[i].Delay {\n\t\t\t\t\tt.Fatalf(\"expected %+v got %+v\", tc.output, res)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDelayRankerRelay(t *testing.T) {\n\tq1 := ma.StringCast(\"/ip4/1.2.3.4/udp/1/quic-v1\")\n\tq2 := ma.StringCast(\"/ip4/1.2.3.4/udp/2/quic-v1\")\n\n\tpid := test.RandPeerIDFatal(t)\n\tr1 := ma.StringCast(fmt.Sprintf(\"/ip4/1.2.3.4/tcp/1/p2p-circuit/p2p/%s\", pid))\n\tr2 := ma.StringCast(fmt.Sprintf(\"/ip4/1.2.3.4/udp/1/quic/p2p-circuit/p2p/%s\", pid))\n\n\ttestCase := []struct {\n\t\tname   string\n\t\taddrs  []ma.Multiaddr\n\t\toutput []network.AddrDelay\n\t}{\n\t\t{\n\t\t\tname:  \"relay address delayed\",\n\t\t\taddrs: []ma.Multiaddr{q1, q2, r1, r2},\n\t\t\toutput: []network.AddrDelay{\n\t\t\t\t{Addr: q1, Delay: 0},\n\t\t\t\t{Addr: q2, Delay: PublicQUICDelay},\n\t\t\t\t{Addr: r2, Delay: RelayDelay},\n\t\t\t\t{Addr: r1, Delay: PublicTCPDelay + RelayDelay},\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tc := range testCase {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tres := DefaultDialRanker(tc.addrs)\n\t\t\tif len(res) != len(tc.output) {\n\t\t\t\tlog.Error(\"expected output mismatch\", \"expected\", tc.output, \"got\", res)\n\t\t\t\tt.Errorf(\"expected elems: %d got: %d\", len(tc.output), len(res))\n\t\t\t}\n\t\t\tsortAddrDelays(res)\n\t\t\tsortAddrDelays(tc.output)\n\t\t\tfor i := 0; i < len(tc.output); i++ {\n\t\t\t\tif !tc.output[i].Addr.Equal(res[i].Addr) || tc.output[i].Delay != res[i].Delay {\n\t\t\t\t\tt.Fatalf(\"expected %+v got %+v\", tc.output, res)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDelayRankerOtherTransportDelay(t *testing.T) {\n\tq1v1 := ma.StringCast(\"/ip4/1.2.3.4/udp/1/quic-v1\")\n\tq1v16 := ma.StringCast(\"/ip6/1::2/udp/1/quic-v1\")\n\tt1 := ma.StringCast(\"/ip4/1.2.3.5/tcp/1/\")\n\tt1v6 := ma.StringCast(\"/ip6/1::2/tcp/1\")\n\twrtc1 := ma.StringCast(\"/ip4/1.2.3.4/udp/1/webrtc-direct\")\n\twrtc1v6 := ma.StringCast(\"/ip6/1::2/udp/1/webrtc-direct\")\n\tonion1 := ma.StringCast(\"/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:1234\")\n\tonlyIP := ma.StringCast(\"/ip4/1.2.3.4/\")\n\ttestCase := []struct {\n\t\tname   string\n\t\taddrs  []ma.Multiaddr\n\t\toutput []network.AddrDelay\n\t}{\n\t\t{\n\t\t\tname:  \"quic-with-other\",\n\t\t\taddrs: []ma.Multiaddr{q1v1, q1v16, wrtc1, wrtc1v6, onion1, onlyIP},\n\t\t\toutput: []network.AddrDelay{\n\t\t\t\t{Addr: q1v16, Delay: 0},\n\t\t\t\t{Addr: q1v1, Delay: PublicQUICDelay},\n\t\t\t\t{Addr: wrtc1, Delay: PublicQUICDelay + PublicOtherDelay},\n\t\t\t\t{Addr: wrtc1v6, Delay: PublicQUICDelay + PublicOtherDelay},\n\t\t\t\t{Addr: onlyIP, Delay: PublicQUICDelay + PublicOtherDelay},\n\t\t\t\t{Addr: onion1, Delay: PublicQUICDelay + 2*PublicOtherDelay},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"quic-and-tcp-with-other\",\n\t\t\taddrs: []ma.Multiaddr{q1v1, t1, t1v6, wrtc1, wrtc1v6, onion1, onlyIP},\n\t\t\toutput: []network.AddrDelay{\n\t\t\t\t{Addr: q1v1, Delay: 0},\n\t\t\t\t{Addr: t1v6, Delay: PublicQUICDelay},\n\t\t\t\t{Addr: t1, Delay: 2 * PublicQUICDelay},\n\t\t\t\t{Addr: wrtc1, Delay: 2*PublicQUICDelay + PublicOtherDelay},\n\t\t\t\t{Addr: wrtc1v6, Delay: 2*PublicQUICDelay + PublicOtherDelay},\n\t\t\t\t{Addr: onlyIP, Delay: 2*PublicQUICDelay + PublicOtherDelay},\n\t\t\t\t{Addr: onion1, Delay: 2*PublicQUICDelay + 2*PublicOtherDelay},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"only-non-ip-addr\",\n\t\t\taddrs: []ma.Multiaddr{onion1},\n\t\t\toutput: []network.AddrDelay{\n\t\t\t\t{Addr: onion1, Delay: PublicOtherDelay},\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tc := range testCase {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tres := DefaultDialRanker(tc.addrs)\n\t\t\tif len(res) != len(tc.output) {\n\t\t\t\tlog.Error(\"expected output mismatch\", \"expected\", tc.output, \"got\", res)\n\t\t\t\tt.Errorf(\"expected elems: %d got: %d\", len(tc.output), len(res))\n\t\t\t\treturn\n\t\t\t}\n\t\t\tsortAddrDelays(res)\n\t\t\tsortAddrDelays(tc.output)\n\t\t\tfor i := 0; i < len(tc.output); i++ {\n\t\t\t\tif !tc.output[i].Addr.Equal(res[i].Addr) || tc.output[i].Delay != res[i].Delay {\n\t\t\t\t\tt.Fatalf(\"expected %+v got %+v\", tc.output, res)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "p2p/net/swarm/dial_sync.go",
    "content": "package swarm\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"sync\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n)\n\n// dialWorkerFunc is used by dialSync to spawn a new dial worker\ntype dialWorkerFunc func(peer.ID, <-chan dialRequest)\n\n// errConcurrentDialSuccessful is used to signal that a concurrent dial succeeded\nvar errConcurrentDialSuccessful = errors.New(\"concurrent dial successful\")\n\n// newDialSync constructs a new dialSync\nfunc newDialSync(worker dialWorkerFunc) *dialSync {\n\treturn &dialSync{\n\t\tdials:      make(map[peer.ID]*activeDial),\n\t\tdialWorker: worker,\n\t}\n}\n\n// dialSync is a dial synchronization helper that ensures that at most one dial\n// to any given peer is active at any given time.\ntype dialSync struct {\n\tmutex      sync.Mutex\n\tdials      map[peer.ID]*activeDial\n\tdialWorker dialWorkerFunc\n}\n\ntype activeDial struct {\n\trefCnt int\n\n\tctx         context.Context\n\tcancelCause func(error)\n\n\treqch chan dialRequest\n}\n\nfunc (ad *activeDial) dial(ctx context.Context) (*Conn, error) {\n\tdialCtx := ad.ctx\n\n\tif forceDirect, reason := network.GetForceDirectDial(ctx); forceDirect {\n\t\tdialCtx = network.WithForceDirectDial(dialCtx, reason)\n\t}\n\tif simConnect, isClient, reason := network.GetSimultaneousConnect(ctx); simConnect {\n\t\tdialCtx = network.WithSimultaneousConnect(dialCtx, isClient, reason)\n\t}\n\n\tresch := make(chan dialResponse, 1)\n\tselect {\n\tcase ad.reqch <- dialRequest{ctx: dialCtx, resch: resch}:\n\tcase <-ctx.Done():\n\t\treturn nil, ctx.Err()\n\t}\n\n\tselect {\n\tcase res := <-resch:\n\t\treturn res.conn, res.err\n\tcase <-ctx.Done():\n\t\treturn nil, ctx.Err()\n\t}\n}\n\nfunc (ds *dialSync) getActiveDial(p peer.ID) (*activeDial, error) {\n\tds.mutex.Lock()\n\tdefer ds.mutex.Unlock()\n\n\tactd, ok := ds.dials[p]\n\tif !ok {\n\t\t// This code intentionally uses the background context. Otherwise, if the first call\n\t\t// to Dial is canceled, subsequent dial calls will also be canceled.\n\t\tctx, cancel := context.WithCancelCause(context.Background())\n\t\tactd = &activeDial{\n\t\t\tctx:         ctx,\n\t\t\tcancelCause: cancel,\n\t\t\treqch:       make(chan dialRequest),\n\t\t}\n\t\tgo ds.dialWorker(p, actd.reqch)\n\t\tds.dials[p] = actd\n\t}\n\t// increase ref count before dropping mutex\n\tactd.refCnt++\n\treturn actd, nil\n}\n\n// Dial initiates a dial to the given peer if there are none in progress\n// then waits for the dial to that peer to complete.\nfunc (ds *dialSync) Dial(ctx context.Context, p peer.ID) (*Conn, error) {\n\tad, err := ds.getActiveDial(p)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tconn, err := ad.dial(ctx)\n\n\tds.mutex.Lock()\n\tdefer ds.mutex.Unlock()\n\n\tad.refCnt--\n\tif ad.refCnt == 0 {\n\t\tif err == nil {\n\t\t\tad.cancelCause(errConcurrentDialSuccessful)\n\t\t} else {\n\t\t\tad.cancelCause(err)\n\t\t}\n\t\tclose(ad.reqch)\n\t\tdelete(ds.dials, p)\n\t}\n\n\treturn conn, err\n}\n"
  },
  {
    "path": "p2p/net/swarm/dial_sync_test.go",
    "content": "package swarm\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc getMockDialFunc() (dialWorkerFunc, func(), context.Context, <-chan struct{}) {\n\tdfcalls := make(chan struct{}, 512) // buffer it large enough that we won't care\n\tdialctx, cancel := context.WithCancel(context.Background())\n\tch := make(chan struct{})\n\tf := func(_ peer.ID, reqch <-chan dialRequest) {\n\t\tdefer cancel()\n\t\tdfcalls <- struct{}{}\n\t\tgo func() {\n\t\t\tfor req := range reqch {\n\t\t\t\t<-ch\n\t\t\t\treq.resch <- dialResponse{conn: new(Conn)}\n\t\t\t}\n\t\t}()\n\t}\n\n\tvar once sync.Once\n\treturn f, func() { once.Do(func() { close(ch) }) }, dialctx, dfcalls\n}\n\nfunc TestBasicDialSync(t *testing.T) {\n\tdf, done, _, callsch := getMockDialFunc()\n\tdsync := newDialSync(df)\n\tp := peer.ID(\"testpeer\")\n\n\tfinished := make(chan struct{}, 2)\n\tgo func() {\n\t\tif _, err := dsync.Dial(context.Background(), p); err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t\tfinished <- struct{}{}\n\t}()\n\n\tgo func() {\n\t\tif _, err := dsync.Dial(context.Background(), p); err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t\tfinished <- struct{}{}\n\t}()\n\n\t// short sleep just to make sure we've moved around in the scheduler\n\ttime.Sleep(time.Millisecond * 20)\n\tdone()\n\n\t<-finished\n\t<-finished\n\n\tif len(callsch) > 1 {\n\t\tt.Fatal(\"should only have called dial func once!\")\n\t}\n}\n\nfunc TestDialSyncCancel(t *testing.T) {\n\tdf, done, _, dcall := getMockDialFunc()\n\n\tdsync := newDialSync(df)\n\n\tp := peer.ID(\"testpeer\")\n\n\tctx1, cancel1 := context.WithCancel(context.Background())\n\n\tfinished := make(chan struct{})\n\tgo func() {\n\t\t_, err := dsync.Dial(ctx1, p)\n\t\tif err != ctx1.Err() {\n\t\t\tt.Error(\"should have gotten context error\")\n\t\t}\n\t\tfinished <- struct{}{}\n\t}()\n\n\t// make sure the above makes it through the wait code first\n\tselect {\n\tcase <-dcall:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timed out waiting for dial to start\")\n\t}\n\n\t// Add a second dialwait in so two actors are waiting on the same dial\n\tgo func() {\n\t\t_, err := dsync.Dial(context.Background(), p)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t\tfinished <- struct{}{}\n\t}()\n\n\ttime.Sleep(time.Millisecond * 20)\n\n\t// cancel the first dialwait, it should not affect the second at all\n\tcancel1()\n\tselect {\n\tcase <-finished:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timed out waiting for wait to exit\")\n\t}\n\n\t// short sleep just to make sure we've moved around in the scheduler\n\ttime.Sleep(time.Millisecond * 20)\n\tdone()\n\n\t<-finished\n}\n\nfunc TestDialSyncAllCancel(t *testing.T) {\n\tdf, done, dctx, _ := getMockDialFunc()\n\n\tdsync := newDialSync(df)\n\tp := peer.ID(\"testpeer\")\n\tctx, cancel := context.WithCancel(context.Background())\n\n\tfinished := make(chan struct{})\n\tgo func() {\n\t\tif _, err := dsync.Dial(ctx, p); err != ctx.Err() {\n\t\t\tt.Error(\"should have gotten context error\")\n\t\t}\n\t\tfinished <- struct{}{}\n\t}()\n\n\t// Add a second dialwait in so two actors are waiting on the same dial\n\tgo func() {\n\t\tif _, err := dsync.Dial(ctx, p); err != ctx.Err() {\n\t\t\tt.Error(\"should have gotten context error\")\n\t\t}\n\t\tfinished <- struct{}{}\n\t}()\n\n\tcancel()\n\tfor range 2 {\n\t\tselect {\n\t\tcase <-finished:\n\t\tcase <-time.After(time.Second):\n\t\t\tt.Fatal(\"timed out waiting for wait to exit\")\n\t\t}\n\t}\n\n\t// the dial should have exited now\n\tselect {\n\tcase <-dctx.Done():\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timed out waiting for dial to return\")\n\t}\n\n\t// should be able to successfully dial that peer again\n\tdone()\n\tif _, err := dsync.Dial(context.Background(), p); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestFailFirst(t *testing.T) {\n\tvar handledFirst atomic.Bool\n\tdialErr := fmt.Errorf(\"gophers ate the modem\")\n\tf := func(_ peer.ID, reqch <-chan dialRequest) {\n\t\tgo func() {\n\t\t\tfor {\n\t\t\t\treq, ok := <-reqch\n\t\t\t\tif !ok {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif handledFirst.CompareAndSwap(false, true) {\n\t\t\t\t\treq.resch <- dialResponse{err: dialErr}\n\t\t\t\t} else {\n\t\t\t\t\treq.resch <- dialResponse{conn: new(Conn)}\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\t}\n\n\tds := newDialSync(f)\n\tp := peer.ID(\"testing\")\n\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\n\t_, err := ds.Dial(ctx, p)\n\trequire.ErrorIs(t, err, dialErr, \"expected gophers to have eaten the modem\")\n\n\tc, err := ds.Dial(ctx, p)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, c, \"should have gotten a 'real' conn back\")\n}\n\nfunc TestStressActiveDial(_ *testing.T) {\n\tds := newDialSync(func(_ peer.ID, reqch <-chan dialRequest) {\n\t\tgo func() {\n\t\t\tfor {\n\t\t\t\treq, ok := <-reqch\n\t\t\t\tif !ok {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\treq.resch <- dialResponse{}\n\t\t\t}\n\t\t}()\n\t})\n\n\twg := sync.WaitGroup{}\n\n\tpid := peer.ID(\"foo\")\n\n\tmakeDials := func() {\n\t\tfor range 10000 {\n\t\t\tds.Dial(context.Background(), pid)\n\t\t}\n\t\twg.Done()\n\t}\n\n\tfor range 100 {\n\t\twg.Add(1)\n\t\tgo makeDials()\n\t}\n\n\twg.Wait()\n}\n"
  },
  {
    "path": "p2p/net/swarm/dial_test.go",
    "content": "package swarm_test\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\ttestutil \"github.com/libp2p/go-libp2p/core/test\"\n\t\"github.com/libp2p/go-libp2p/p2p/net/swarm\"\n\tswarmt \"github.com/libp2p/go-libp2p/p2p/net/swarm/testing\"\n\n\t\"github.com/libp2p/go-libp2p-testing/ci\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmadns \"github.com/multiformats/go-multiaddr-dns\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc closeSwarms(swarms []*swarm.Swarm) {\n\tfor _, s := range swarms {\n\t\ts.Close()\n\t}\n}\n\nfunc TestBasicDialPeer(t *testing.T) {\n\tswarms := makeSwarms(t, 2)\n\tdefer closeSwarms(swarms)\n\ts1 := swarms[0]\n\ts2 := swarms[1]\n\n\ts1.Peerstore().AddAddrs(s2.LocalPeer(), s2.ListenAddresses(), peerstore.PermanentAddrTTL)\n\n\tc, err := s1.DialPeer(context.Background(), s2.LocalPeer())\n\trequire.NoError(t, err)\n\n\ts, err := c.NewStream(context.Background())\n\trequire.NoError(t, err)\n\ts.Close()\n}\n\nfunc TestBasicDialPeerWithResolver(t *testing.T) {\n\tmockResolver := madns.MockResolver{IP: make(map[string][]net.IPAddr)}\n\tipaddr, err := net.ResolveIPAddr(\"ip4\", \"127.0.0.1\")\n\trequire.NoError(t, err)\n\tmockResolver.IP[\"example.com\"] = []net.IPAddr{*ipaddr}\n\tresolver, err := madns.NewResolver(madns.WithDomainResolver(\"example.com\", &mockResolver))\n\trequire.NoError(t, err)\n\n\tswarms := makeSwarms(t, 2, swarmt.WithSwarmOpts(swarm.WithMultiaddrResolver(swarm.ResolverFromMaDNS{resolver})))\n\tdefer closeSwarms(swarms)\n\ts1 := swarms[0]\n\ts2 := swarms[1]\n\n\t// Change the multiaddr from /ip4/127.0.0.1/... to /dns4/example.com/... so\n\t// that the resovler has to resolve this\n\tvar s2Addrs []ma.Multiaddr\n\tfor _, a := range s2.ListenAddresses() {\n\t\t_, rest := ma.SplitFunc(a, func(c ma.Component) bool {\n\t\t\treturn c.Protocol().Code == ma.P_TCP || c.Protocol().Code == ma.P_UDP\n\t\t},\n\t\t)\n\t\tif rest != nil {\n\t\t\ts2Addrs = append(s2Addrs, ma.StringCast(\"/dns4/example.com\").Encapsulate(rest))\n\t\t}\n\t}\n\n\ts1.Peerstore().AddAddrs(s2.LocalPeer(), s2Addrs, peerstore.PermanentAddrTTL)\n\n\tc, err := s1.DialPeer(context.Background(), s2.LocalPeer())\n\trequire.NoError(t, err)\n\n\ts, err := c.NewStream(context.Background())\n\trequire.NoError(t, err)\n\ts.Close()\n}\n\nfunc TestDialWithNoListeners(t *testing.T) {\n\ts1 := makeDialOnlySwarm(t)\n\tswarms := makeSwarms(t, 1)\n\tdefer closeSwarms(swarms)\n\ts2 := swarms[0]\n\n\ts1.Peerstore().AddAddrs(s2.LocalPeer(), s2.ListenAddresses(), peerstore.PermanentAddrTTL)\n\n\tc, err := s1.DialPeer(context.Background(), s2.LocalPeer())\n\trequire.NoError(t, err)\n\n\ts, err := c.NewStream(context.Background())\n\trequire.NoError(t, err)\n\ts.Close()\n}\n\nfunc acceptAndHang(l net.Listener) {\n\tconns := make([]net.Conn, 0, 10)\n\tfor {\n\t\tc, err := l.Accept()\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t\tif c != nil {\n\t\t\tconns = append(conns, c)\n\t\t}\n\t}\n\tfor _, c := range conns {\n\t\tc.Close()\n\t}\n}\n\nfunc TestSimultDials(t *testing.T) {\n\tctx := context.Background()\n\tswarms := makeSwarms(t, 2, swarmt.OptDisableReuseport)\n\tdefer closeSwarms(swarms)\n\n\t// connect everyone\n\t{\n\t\tvar wg sync.WaitGroup\n\t\terrs := make(chan error, 20) // 2 connect calls in each of the 10 for-loop iterations\n\t\tconnect := func(s *swarm.Swarm, dst peer.ID, addr ma.Multiaddr) {\n\t\t\t// copy for other peer\n\t\t\tlog.Debug(\"TestSimultOpen: connecting\", \"local\", s.LocalPeer(), \"remote\", dst, \"addr\", addr)\n\t\t\ts.Peerstore().AddAddr(dst, addr, peerstore.TempAddrTTL)\n\t\t\tif _, err := s.DialPeer(ctx, dst); err != nil {\n\t\t\t\terrs <- err\n\t\t\t}\n\t\t\twg.Done()\n\t\t}\n\n\t\tifaceAddrs0, err := swarms[0].InterfaceListenAddresses()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tifaceAddrs1, err := swarms[1].InterfaceListenAddresses()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tlog.Info(\"Connecting swarms simultaneously.\")\n\t\tfor range 10 { // connect 10x for each.\n\t\t\twg.Add(2)\n\t\t\tgo connect(swarms[0], swarms[1].LocalPeer(), ifaceAddrs1[0])\n\t\t\tgo connect(swarms[1], swarms[0].LocalPeer(), ifaceAddrs0[0])\n\t\t}\n\t\twg.Wait()\n\t\tclose(errs)\n\n\t\tfor err := range errs {\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(\"error swarm dialing to peer\", err)\n\t\t\t}\n\t\t}\n\t}\n\n\t// should still just have 1, at most 2 connections :)\n\tc01l := len(swarms[0].ConnsToPeer(swarms[1].LocalPeer()))\n\tif c01l > 2 {\n\t\tt.Error(\"0->1 has\", c01l)\n\t}\n\tc10l := len(swarms[1].ConnsToPeer(swarms[0].LocalPeer()))\n\tif c10l > 2 {\n\t\tt.Error(\"1->0 has\", c10l)\n\t}\n}\n\nfunc newSilentPeer(t *testing.T) (peer.ID, ma.Multiaddr, net.Listener) {\n\tdst := testutil.RandPeerIDFatal(t)\n\tlst, err := net.Listen(\"tcp4\", \"localhost:0\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\taddr, err := manet.FromNetAddr(lst.Addr())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\taddrs, err := manet.ResolveUnspecifiedAddresses([]ma.Multiaddr{addr}, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Log(\"new silent peer:\", dst, addrs[0])\n\treturn dst, addrs[0], lst\n}\n\nfunc TestDialWait(t *testing.T) {\n\tconst dialTimeout = 5 * time.Second\n\n\tswarms := makeSwarms(t, 1, swarmt.WithSwarmOpts(swarm.WithDialTimeout(dialTimeout)))\n\ts1 := swarms[0]\n\tdefer s1.Close()\n\n\t// dial to a non-existent peer.\n\ts2p, s2addr, s2l := newSilentPeer(t)\n\tgo acceptAndHang(s2l)\n\tdefer s2l.Close()\n\ts1.Peerstore().AddAddr(s2p, s2addr, peerstore.PermanentAddrTTL)\n\n\tbefore := time.Now()\n\tif c, err := s1.DialPeer(context.Background(), s2p); err == nil {\n\t\tdefer c.Close()\n\t\tt.Fatal(\"error swarm dialing to unknown peer worked...\", err)\n\t} else {\n\t\tt.Log(\"correctly got error:\", err)\n\t}\n\tduration := time.Since(before)\n\n\tif duration < dialTimeout*swarm.DialAttempts {\n\t\tt.Error(\"< dialTimeout * DialAttempts not being respected\", duration, dialTimeout*swarm.DialAttempts)\n\t}\n\tif duration > 2*dialTimeout*swarm.DialAttempts {\n\t\tt.Error(\"> 2*dialTimeout * DialAttempts not being respected\", duration, 2*dialTimeout*swarm.DialAttempts)\n\t}\n\n\tif !s1.Backoff().Backoff(s2p, s2addr) {\n\t\tt.Error(\"s2 should now be on backoff\")\n\t}\n}\n\nfunc TestDialBackoff(t *testing.T) {\n\tif ci.IsRunning() {\n\t\tt.Skip(\"travis will never have fun with this test\")\n\t}\n\tconst dialTimeout = 100 * time.Millisecond\n\n\tctx := context.Background()\n\tswarms := makeSwarms(t, 2, swarmt.WithSwarmOpts(swarm.WithDialTimeout(dialTimeout)))\n\tdefer closeSwarms(swarms)\n\ts1 := swarms[0]\n\ts2 := swarms[1]\n\n\ts2addrs, err := s2.InterfaceListenAddresses()\n\trequire.NoError(t, err)\n\ts1.Peerstore().AddAddrs(s2.LocalPeer(), s2addrs, peerstore.PermanentAddrTTL)\n\n\t// dial to a non-existent peer.\n\ts3p, s3addr, s3l := newSilentPeer(t)\n\tgo acceptAndHang(s3l)\n\tdefer s3l.Close()\n\ts1.Peerstore().AddAddr(s3p, s3addr, peerstore.PermanentAddrTTL)\n\n\t// in this test we will:\n\t//   1) dial 10x to each node.\n\t//   2) all dials should hang\n\t//   3) s1->s2 should succeed.\n\t//   4) s1->s3 should not (and should place s3 on backoff)\n\t//   5) disconnect entirely\n\t//   6) dial 10x to each node again\n\t//   7) s3 dials should all return immediately (except 1)\n\t//   8) s2 dials should all hang, and succeed\n\t//   9) last s3 dial ends, unsuccessful\n\n\tdialOnlineNode := func(dst peer.ID, times int) <-chan bool {\n\t\tch := make(chan bool)\n\t\tfor range times {\n\t\t\tgo func() {\n\t\t\t\tif _, err := s1.DialPeer(ctx, dst); err != nil {\n\t\t\t\t\tt.Error(\"error dialing\", dst, err)\n\t\t\t\t\tch <- false\n\t\t\t\t} else {\n\t\t\t\t\tch <- true\n\t\t\t\t}\n\t\t\t}()\n\t\t}\n\t\treturn ch\n\t}\n\n\tdialOfflineNode := func(dst peer.ID, times int) <-chan bool {\n\t\tch := make(chan bool)\n\t\tfor range times {\n\t\t\tgo func() {\n\t\t\t\tif c, err := s1.DialPeer(ctx, dst); err != nil {\n\t\t\t\t\tch <- false\n\t\t\t\t} else {\n\t\t\t\t\tt.Error(\"succeeded in dialing\", dst)\n\t\t\t\t\tch <- true\n\t\t\t\t\tc.Close()\n\t\t\t\t}\n\t\t\t}()\n\t\t}\n\t\treturn ch\n\t}\n\n\t{\n\t\t// 1) dial 10x to each node.\n\t\tN := 10\n\t\ts2done := dialOnlineNode(s2.LocalPeer(), N)\n\t\ts3done := dialOfflineNode(s3p, N)\n\n\t\t// when all dials should be done by:\n\t\tdialTimeout1x := time.After(dialTimeout)\n\t\tdialTimeout10Ax := time.After(dialTimeout * 2 * 10) // DialAttempts * 10)\n\n\t\t// 2) all dials should hang\n\t\tselect {\n\t\tcase <-s2done:\n\t\t\tt.Error(\"s2 should not happen immediately\")\n\t\tcase <-s3done:\n\t\t\tt.Error(\"s3 should not happen yet\")\n\t\tcase <-time.After(time.Millisecond):\n\t\t\t// s2 may finish very quickly, so let's get out.\n\t\t}\n\n\t\t// 3) s1->s2 should succeed.\n\t\tfor range N {\n\t\t\tselect {\n\t\t\tcase r := <-s2done:\n\t\t\t\tif !r {\n\t\t\t\t\tt.Error(\"s2 should not fail\")\n\t\t\t\t}\n\t\t\tcase <-s3done:\n\t\t\t\tt.Error(\"s3 should not happen yet\")\n\t\t\tcase <-dialTimeout1x:\n\t\t\t\tt.Error(\"s2 took too long\")\n\t\t\t}\n\t\t}\n\n\t\tselect {\n\t\tcase <-s2done:\n\t\t\tt.Error(\"s2 should have no more\")\n\t\tcase <-s3done:\n\t\t\tt.Error(\"s3 should not happen yet\")\n\t\tcase <-dialTimeout1x: // let it pass\n\t\t}\n\n\t\t// 4) s1->s3 should not (and should place s3 on backoff)\n\t\t// N-1 should finish before dialTimeout1x * 2\n\t\tfor i := range N {\n\t\t\tselect {\n\t\t\tcase <-s2done:\n\t\t\t\tt.Error(\"s2 should have no more\")\n\t\t\tcase r := <-s3done:\n\t\t\t\tif r {\n\t\t\t\t\tt.Error(\"s3 should not succeed\")\n\t\t\t\t}\n\t\t\tcase <-(dialTimeout1x):\n\t\t\t\tif i < (N - 1) {\n\t\t\t\t\tt.Fatal(\"s3 took too long\")\n\t\t\t\t}\n\t\t\t\tt.Log(\"dialTimeout1x * 1.3 hit for last peer\")\n\t\t\tcase <-dialTimeout10Ax:\n\t\t\t\tt.Fatal(\"s3 took too long\")\n\t\t\t}\n\t\t}\n\n\t\t// check backoff state\n\t\tif s1.Backoff().Backoff(s2.LocalPeer(), s2addrs[0]) {\n\t\t\tt.Error(\"s2 should not be on backoff\")\n\t\t}\n\t\tif !s1.Backoff().Backoff(s3p, s3addr) {\n\t\t\tt.Error(\"s3 should be on backoff\")\n\t\t}\n\n\t\t// 5) disconnect entirely\n\n\t\tfor _, c := range s1.Conns() {\n\t\t\tc.Close()\n\t\t}\n\t\tfor i := 0; i < 100 && len(s1.Conns()) > 0; i++ {\n\t\t\t<-time.After(time.Millisecond)\n\t\t}\n\t\tif len(s1.Conns()) > 0 {\n\t\t\tt.Fatal(\"s1 conns must exit\")\n\t\t}\n\t}\n\n\t{\n\t\t// 6) dial 10x to each node again\n\t\tN := 10\n\t\ts2done := dialOnlineNode(s2.LocalPeer(), N)\n\t\ts3done := dialOfflineNode(s3p, N)\n\n\t\t// when all dials should be done by:\n\t\tdialTimeout1x := time.After(dialTimeout)\n\t\tdialTimeout10Ax := time.After(dialTimeout * 2 * 10) // DialAttempts * 10)\n\n\t\t// 7) s3 dials should all return immediately (except 1)\n\t\tfor i := 0; i < N-1; i++ {\n\t\t\tselect {\n\t\t\tcase <-s2done:\n\t\t\t\tt.Error(\"s2 should not succeed yet\")\n\t\t\tcase r := <-s3done:\n\t\t\t\tif r {\n\t\t\t\t\tt.Error(\"s3 should not succeed\")\n\t\t\t\t}\n\t\t\tcase <-dialTimeout1x:\n\t\t\t\tt.Fatal(\"s3 took too long\")\n\t\t\t}\n\t\t}\n\n\t\t// 8) s2 dials should all hang, and succeed\n\t\tfor range N {\n\t\t\tselect {\n\t\t\tcase r := <-s2done:\n\t\t\t\tif !r {\n\t\t\t\t\tt.Error(\"s2 should succeed\")\n\t\t\t\t}\n\t\t\t// case <-s3done:\n\t\t\tcase <-(dialTimeout1x):\n\t\t\t\tt.Fatal(\"s3 took too long\")\n\t\t\t}\n\t\t}\n\n\t\t// 9) the last s3 should return, failed.\n\t\tselect {\n\t\tcase <-s2done:\n\t\t\tt.Error(\"s2 should have no more\")\n\t\tcase r := <-s3done:\n\t\t\tif r {\n\t\t\t\tt.Error(\"s3 should not succeed\")\n\t\t\t}\n\t\tcase <-dialTimeout10Ax:\n\t\t\tt.Fatal(\"s3 took too long\")\n\t\t}\n\n\t\t// check backoff state (the same)\n\t\tif s1.Backoff().Backoff(s2.LocalPeer(), s2addrs[0]) {\n\t\t\tt.Error(\"s2 should not be on backoff\")\n\t\t}\n\t\tif !s1.Backoff().Backoff(s3p, s3addr) {\n\t\t\tt.Error(\"s3 should be on backoff\")\n\t\t}\n\t}\n}\n\nfunc TestDialBackoffClears(t *testing.T) {\n\tconst dialTimeout = 3 * time.Second\n\tswarms := makeSwarms(t, 2, swarmt.WithSwarmOpts(swarm.WithDialTimeout(dialTimeout)))\n\tdefer closeSwarms(swarms)\n\ts1 := swarms[0]\n\ts2 := swarms[1]\n\n\t// use another address first, that accept and hang on conns\n\t_, s2bad, s2l := newSilentPeer(t)\n\tgo acceptAndHang(s2l)\n\tdefer s2l.Close()\n\n\t// phase 1 -- dial to non-operational addresses\n\ts1.Peerstore().AddAddr(s2.LocalPeer(), s2bad, peerstore.PermanentAddrTTL)\n\n\tbefore := time.Now()\n\t_, err := s1.DialPeer(context.Background(), s2.LocalPeer())\n\trequire.Error(t, err, \"dialing to broken addr worked...\")\n\tduration := time.Since(before)\n\n\tif duration < dialTimeout*swarm.DialAttempts {\n\t\tt.Error(\"< dialTimeout * DialAttempts not being respected\", duration, dialTimeout*swarm.DialAttempts)\n\t}\n\tif duration > 2*dialTimeout*swarm.DialAttempts {\n\t\tt.Error(\"> 2*dialTimeout * DialAttempts not being respected\", duration, 2*dialTimeout*swarm.DialAttempts)\n\t}\n\trequire.True(t, s1.Backoff().Backoff(s2.LocalPeer(), s2bad), \"s2 should now be on backoff\")\n\n\t// phase 2 -- add the working address. dial should succeed.\n\tifaceAddrs1, err := s2.InterfaceListenAddresses()\n\trequire.NoError(t, err)\n\ts1.Peerstore().AddAddrs(s2.LocalPeer(), ifaceAddrs1, peerstore.PermanentAddrTTL)\n\n\t// backoffs are per address, not peer\n\tc, err := s1.DialPeer(context.Background(), s2.LocalPeer())\n\trequire.NoError(t, err)\n\tdefer c.Close()\n\trequire.False(t, s1.Backoff().Backoff(s2.LocalPeer(), s2bad), \"s2 should no longer be on backoff\")\n}\n\nfunc TestDialPeerFailed(t *testing.T) {\n\tswarms := makeSwarms(t, 2, swarmt.WithSwarmOpts(swarm.WithDialTimeout(100*time.Millisecond)))\n\tdefer closeSwarms(swarms)\n\ttestedSwarm, targetSwarm := swarms[0], swarms[1]\n\n\tconst expectedErrorsCount = 5\n\tfor range expectedErrorsCount {\n\t\t_, silentPeerAddress, silentPeerListener := newSilentPeer(t)\n\t\tgo acceptAndHang(silentPeerListener)\n\t\tdefer silentPeerListener.Close()\n\n\t\ttestedSwarm.Peerstore().AddAddr(targetSwarm.LocalPeer(), silentPeerAddress, peerstore.PermanentAddrTTL)\n\t}\n\n\t_, err := testedSwarm.DialPeer(context.Background(), targetSwarm.LocalPeer())\n\trequire.Error(t, err)\n\n\t// dial_test.go:508: correctly get a combined error: failed to dial PEER: all dials failed\n\t//   * [/ip4/127.0.0.1/tcp/46485] failed to negotiate security protocol: context deadline exceeded\n\t//   * [/ip4/127.0.0.1/tcp/34881] failed to negotiate security protocol: context deadline exceeded\n\t// ...\n\n\tdialErr, ok := err.(*swarm.DialError)\n\tif !ok {\n\t\tt.Fatalf(\"expected *DialError, got %T\", err)\n\t}\n\n\tif len(dialErr.DialErrors) != expectedErrorsCount {\n\t\tt.Errorf(\"expected %d errors, got %d\", expectedErrorsCount, len(dialErr.DialErrors))\n\t}\n}\n\nfunc TestDialExistingConnection(t *testing.T) {\n\tswarms := makeSwarms(t, 2)\n\tdefer closeSwarms(swarms)\n\ts1 := swarms[0]\n\ts2 := swarms[1]\n\n\t// Only use one of the addresses here.\n\t// Otherwise, we might dial TCP and QUIC simultaneously here, and end up with two connections,\n\t// if the handshake latencies line up exactly.\n\ts1.Peerstore().AddAddrs(s2.LocalPeer(), s2.ListenAddresses()[:1], peerstore.PermanentAddrTTL)\n\n\tc1, err := s1.DialPeer(context.Background(), s2.LocalPeer())\n\trequire.NoError(t, err)\n\n\tc2, err := s1.DialPeer(context.Background(), s2.LocalPeer())\n\trequire.NoError(t, err)\n\n\t// can't use require.Equal here, as this does a deep comparison\n\tif c1 != c2 {\n\t\tt.Fatalf(\"expecting the same connection from both dials, got %s <-> %s vs %s <-> %s\", c1.LocalMultiaddr(), c1.RemoteMultiaddr(), c2.LocalMultiaddr(), c2.RemoteMultiaddr())\n\t}\n}\n\nfunc newSilentListener(t *testing.T) ([]ma.Multiaddr, net.Listener) {\n\tlst, err := net.Listen(\"tcp4\", \"localhost:0\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\taddr, err := manet.FromNetAddr(lst.Addr())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\taddrs, err := manet.ResolveUnspecifiedAddresses([]ma.Multiaddr{addr}, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\treturn addrs, lst\n\n}\n\nfunc TestDialSimultaneousJoin(t *testing.T) {\n\tconst dialTimeout = 3 * time.Second\n\n\tswarms := makeSwarms(t, 2, swarmt.WithSwarmOpts(swarm.WithDialTimeout(dialTimeout)))\n\tdefer closeSwarms(swarms)\n\ts1 := swarms[0]\n\ts2 := swarms[1]\n\n\ts2silentAddrs, s2silentListener := newSilentListener(t)\n\tgo acceptAndHang(s2silentListener)\n\n\tconnch := make(chan network.Conn, 512)\n\terrs := make(chan error, 2)\n\n\t// start a dial to s2 through the silent addr\n\tgo func() {\n\t\ts1.Peerstore().AddAddrs(s2.LocalPeer(), s2silentAddrs, peerstore.PermanentAddrTTL)\n\n\t\tc, err := s1.DialPeer(context.Background(), s2.LocalPeer())\n\t\tif err != nil {\n\t\t\terrs <- err\n\t\t\tconnch <- nil\n\t\t\treturn\n\t\t}\n\n\t\tt.Logf(\"first dial succeeded; conn: %+v\", c)\n\n\t\tconnch <- c\n\t\terrs <- nil\n\t}()\n\n\t// wait a bit for the dial to take hold\n\ttime.Sleep(100 * time.Millisecond)\n\n\t// start a second dial to s2 that uses the real s2 addrs\n\tgo func() {\n\t\ts2addrs, err := s2.InterfaceListenAddresses()\n\t\tif err != nil {\n\t\t\terrs <- err\n\t\t\treturn\n\t\t}\n\t\ts1.Peerstore().AddAddrs(s2.LocalPeer(), s2addrs[:1], peerstore.PermanentAddrTTL)\n\n\t\tc, err := s1.DialPeer(context.Background(), s2.LocalPeer())\n\t\tif err != nil {\n\t\t\terrs <- err\n\t\t\tconnch <- nil\n\t\t\treturn\n\t\t}\n\n\t\tt.Logf(\"second dial succeeded; conn: %+v\", c)\n\n\t\tconnch <- c\n\t\terrs <- nil\n\t}()\n\n\t// wait for the second dial to finish\n\tc2 := <-connch\n\n\t// start a third dial to s2, this should get the existing connection from the successful dial\n\tgo func() {\n\t\tc, err := s1.DialPeer(context.Background(), s2.LocalPeer())\n\t\tif err != nil {\n\t\t\terrs <- err\n\t\t\tconnch <- nil\n\t\t\treturn\n\t\t}\n\n\t\tt.Logf(\"third dial succeeded; conn: %+v\", c)\n\n\t\tconnch <- c\n\t\terrs <- nil\n\t}()\n\n\tc3 := <-connch\n\n\t// raise any errors from the previous goroutines\n\tfor range 3 {\n\t\trequire.NoError(t, <-errs)\n\t}\n\n\tif c2 != c3 {\n\t\tt.Fatal(\"expected c2 and c3 to be the same\")\n\t}\n\n\t// next, the first dial to s2, using the silent addr should timeout; at this point the dial\n\t// will error but the last chance check will see the existing connection and return it\n\tselect {\n\tcase c1 := <-connch:\n\t\tif c1 != c2 {\n\t\t\tt.Fatal(\"expected c1 and c2 to be the same\")\n\t\t}\n\tcase <-time.After(2 * dialTimeout):\n\t\tt.Fatal(\"no connection from first dial\")\n\t}\n}\n\nfunc TestDialSelf(t *testing.T) {\n\tswarms := makeSwarms(t, 2)\n\tdefer closeSwarms(swarms)\n\ts1 := swarms[0]\n\n\t_, err := s1.DialPeer(context.Background(), s1.LocalPeer())\n\trequire.ErrorIs(t, err, swarm.ErrDialToSelf, \"expected error from self dial\")\n}\n\nfunc TestDialQUICDraft29(t *testing.T) {\n\ts := makeDialOnlySwarm(t)\n\tid := testutil.RandPeerIDFatal(t)\n\ts.Peerstore().AddAddr(id, ma.StringCast(\"/ip4/127.0.0.1/udp/1234/quic\"), time.Hour)\n\t_, err := s.DialPeer(context.Background(), id)\n\trequire.ErrorIs(t, err, swarm.ErrQUICDraft29)\n\trequire.ErrorIs(t, err, swarm.ErrNoTransport)\n}\n"
  },
  {
    "path": "p2p/net/swarm/dial_worker.go",
    "content": "package swarm\n\nimport (\n\t\"context\"\n\t\"math\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\ttpt \"github.com/libp2p/go-libp2p/core/transport\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\n// dialRequest is structure used to request dials to the peer associated with a\n// worker loop\ntype dialRequest struct {\n\t// ctx is the context that may be used for the request\n\t// if another concurrent request is made, any of the concurrent request's ctx may be used for\n\t// dials to the peer's addresses\n\t// ctx for simultaneous connect requests have higher priority than normal requests\n\tctx context.Context\n\t// resch is the channel used to send the response for this query\n\tresch chan dialResponse\n}\n\n// dialResponse is the response sent to dialRequests on the request's resch channel\ntype dialResponse struct {\n\t// conn is the connection to the peer on success\n\tconn *Conn\n\t// err is the error in dialing the peer\n\t// nil on connection success\n\terr error\n}\n\n// pendRequest is used to track progress on a dialRequest.\ntype pendRequest struct {\n\t// req is the original dialRequest\n\treq dialRequest\n\t// err comprises errors of all failed dials\n\terr *DialError\n\t// addrs are the addresses on which we are waiting for pending dials\n\t// At the time of creation addrs is initialised to all the addresses of the peer. On a failed dial,\n\t// the addr is removed from the map and err is updated. On a successful dial, the dialRequest is\n\t// completed and response is sent with the connection\n\taddrs map[string]struct{}\n}\n\n// addrDial tracks dials to a particular multiaddress.\ntype addrDial struct {\n\t// addr is the address dialed\n\taddr ma.Multiaddr\n\t// ctx is the context used for dialing the address\n\tctx context.Context\n\t// conn is the established connection on success\n\tconn *Conn\n\t// err is the err on dialing the address\n\terr error\n\t// dialed indicates whether we have triggered the dial to the address\n\tdialed bool\n\t// createdAt is the time this struct was created\n\tcreatedAt time.Time\n\t// dialRankingDelay is the delay in dialing this address introduced by the ranking logic\n\tdialRankingDelay time.Duration\n\t// expectedTCPUpgradeTime is the expected time by which security upgrade will complete\n\texpectedTCPUpgradeTime time.Time\n}\n\n// dialWorker synchronises concurrent dials to a peer. It ensures that we make at most one dial to a\n// peer's address\ntype dialWorker struct {\n\ts    *Swarm\n\tpeer peer.ID\n\t// reqch is used to send dial requests to the worker. close reqch to end the worker loop\n\treqch <-chan dialRequest\n\t// pendingRequests is the set of pendingRequests\n\tpendingRequests map[*pendRequest]struct{}\n\t// trackedDials tracks dials to the peer's addresses. An entry here is used to ensure that\n\t// we dial an address at most once\n\ttrackedDials map[string]*addrDial\n\t// resch is used to receive response for dials to the peers addresses.\n\tresch chan tpt.DialUpdate\n\n\tconnected bool // true when a connection has been successfully established\n\n\t// for testing\n\twg sync.WaitGroup\n\tcl Clock\n}\n\nfunc newDialWorker(s *Swarm, p peer.ID, reqch <-chan dialRequest, cl Clock) *dialWorker {\n\tif cl == nil {\n\t\tcl = RealClock{}\n\t}\n\treturn &dialWorker{\n\t\ts:               s,\n\t\tpeer:            p,\n\t\treqch:           reqch,\n\t\tpendingRequests: make(map[*pendRequest]struct{}),\n\t\ttrackedDials:    make(map[string]*addrDial),\n\t\tresch:           make(chan tpt.DialUpdate),\n\t\tcl:              cl,\n\t}\n}\n\n// loop implements the core dial worker loop. Requests are received on w.reqch.\n// The loop exits when w.reqch is closed.\nfunc (w *dialWorker) loop() {\n\tw.wg.Add(1)\n\tdefer w.wg.Done()\n\tdefer w.s.limiter.clearAllPeerDials(w.peer)\n\n\t// dq is used to pace dials to different addresses of the peer\n\tdq := newDialQueue()\n\t// dialsInFlight is the number of dials in flight.\n\tdialsInFlight := 0\n\n\tstartTime := w.cl.Now()\n\t// dialTimer is the dialTimer used to trigger dials\n\tdialTimer := w.cl.InstantTimer(startTime.Add(math.MaxInt64))\n\tdefer dialTimer.Stop()\n\n\ttimerRunning := true\n\t// scheduleNextDial updates timer for triggering the next dial\n\tscheduleNextDial := func() {\n\t\tif timerRunning && !dialTimer.Stop() {\n\t\t\t<-dialTimer.Ch()\n\t\t}\n\t\ttimerRunning = false\n\t\tif dq.Len() > 0 {\n\t\t\tif dialsInFlight == 0 && !w.connected {\n\t\t\t\t// if there are no dials in flight, trigger the next dials immediately\n\t\t\t\tdialTimer.Reset(startTime)\n\t\t\t} else {\n\t\t\t\tresetTime := startTime.Add(dq.top().Delay)\n\t\t\t\tfor _, ad := range w.trackedDials {\n\t\t\t\t\tif !ad.expectedTCPUpgradeTime.IsZero() && ad.expectedTCPUpgradeTime.After(resetTime) {\n\t\t\t\t\t\tresetTime = ad.expectedTCPUpgradeTime\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tdialTimer.Reset(resetTime)\n\t\t\t}\n\t\t\ttimerRunning = true\n\t\t}\n\t}\n\n\t// totalDials is used to track number of dials made by this worker for metrics\n\ttotalDials := 0\nloop:\n\tfor {\n\t\t// The loop has three parts\n\t\t//  1. Input requests are received on w.reqch. If a suitable connection is not available we create\n\t\t//     a pendRequest object to track the dialRequest and add the addresses to dq.\n\t\t//  2. Addresses from the dialQueue are dialed at appropriate time intervals depending on delay logic.\n\t\t//     We are notified of the completion of these dials on w.resch.\n\t\t//  3. Responses for dials are received on w.resch. On receiving a response, we updated the pendRequests\n\t\t//     interested in dials on this address.\n\n\t\tselect {\n\t\tcase req, ok := <-w.reqch:\n\t\t\tif !ok {\n\t\t\t\tif w.s.metricsTracer != nil {\n\t\t\t\t\tw.s.metricsTracer.DialCompleted(w.connected, totalDials, time.Since(startTime))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// We have received a new request. If we do not have a suitable connection,\n\t\t\t// track this dialRequest with a pendRequest.\n\t\t\t// Enqueue the peer's addresses relevant to this request in dq and\n\t\t\t// track dials to the addresses relevant to this request.\n\n\t\t\tc := w.s.bestAcceptableConnToPeer(req.ctx, w.peer)\n\t\t\tif c != nil {\n\t\t\t\treq.resch <- dialResponse{conn: c}\n\t\t\t\tcontinue loop\n\t\t\t}\n\n\t\t\taddrs, addrErrs, err := w.s.addrsForDial(req.ctx, w.peer)\n\t\t\tif err != nil {\n\t\t\t\treq.resch <- dialResponse{\n\t\t\t\t\terr: &DialError{\n\t\t\t\t\t\tPeer:       w.peer,\n\t\t\t\t\t\tDialErrors: addrErrs,\n\t\t\t\t\t\tCause:      err,\n\t\t\t\t\t}}\n\t\t\t\tcontinue loop\n\t\t\t}\n\n\t\t\t// get the delays to dial these addrs from the swarms dialRanker\n\t\t\tsimConnect, _, _ := network.GetSimultaneousConnect(req.ctx)\n\t\t\taddrRanking := w.rankAddrs(addrs, simConnect)\n\t\t\taddrDelay := make(map[string]time.Duration, len(addrRanking))\n\n\t\t\t// create the pending request object\n\t\t\tpr := &pendRequest{\n\t\t\t\treq:   req,\n\t\t\t\taddrs: make(map[string]struct{}, len(addrRanking)),\n\t\t\t\terr:   &DialError{Peer: w.peer, DialErrors: addrErrs},\n\t\t\t}\n\t\t\tfor _, adelay := range addrRanking {\n\t\t\t\tpr.addrs[string(adelay.Addr.Bytes())] = struct{}{}\n\t\t\t\taddrDelay[string(adelay.Addr.Bytes())] = adelay.Delay\n\t\t\t}\n\n\t\t\t// Check if dials to any of the addrs have completed already\n\t\t\t// If they have errored, record the error in pr. If they have succeeded,\n\t\t\t// respond with the connection.\n\t\t\t// If they are pending, add them to tojoin.\n\t\t\t// If we haven't seen any of the addresses before, add them to todial.\n\t\t\tvar todial []ma.Multiaddr\n\t\t\tvar tojoin []*addrDial\n\n\t\t\tfor _, adelay := range addrRanking {\n\t\t\t\tad, ok := w.trackedDials[string(adelay.Addr.Bytes())]\n\t\t\t\tif !ok {\n\t\t\t\t\ttodial = append(todial, adelay.Addr)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tif ad.conn != nil {\n\t\t\t\t\t// dial to this addr was successful, complete the request\n\t\t\t\t\treq.resch <- dialResponse{conn: ad.conn}\n\t\t\t\t\tcontinue loop\n\t\t\t\t}\n\n\t\t\t\tif ad.err != nil {\n\t\t\t\t\t// dial to this addr errored, accumulate the error\n\t\t\t\t\tpr.err.recordErr(ad.addr, ad.err)\n\t\t\t\t\tdelete(pr.addrs, string(ad.addr.Bytes()))\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\t// dial is still pending, add to the join list\n\t\t\t\ttojoin = append(tojoin, ad)\n\t\t\t}\n\n\t\t\tif len(todial) == 0 && len(tojoin) == 0 {\n\t\t\t\t// all request applicable addrs have been dialed, we must have errored\n\t\t\t\tpr.err.Cause = ErrAllDialsFailed\n\t\t\t\treq.resch <- dialResponse{err: pr.err}\n\t\t\t\tcontinue loop\n\t\t\t}\n\n\t\t\t// The request has some pending or new dials\n\t\t\tw.pendingRequests[pr] = struct{}{}\n\n\t\t\tfor _, ad := range tojoin {\n\t\t\t\tif !ad.dialed {\n\t\t\t\t\t// we haven't dialed this address. update the ad.ctx to have simultaneous connect values\n\t\t\t\t\t// set correctly\n\t\t\t\t\tif simConnect, isClient, reason := network.GetSimultaneousConnect(req.ctx); simConnect {\n\t\t\t\t\t\tif simConnect, _, _ := network.GetSimultaneousConnect(ad.ctx); !simConnect {\n\t\t\t\t\t\t\tad.ctx = network.WithSimultaneousConnect(ad.ctx, isClient, reason)\n\t\t\t\t\t\t\t// update the element in dq to use the simultaneous connect delay.\n\t\t\t\t\t\t\tdq.UpdateOrAdd(network.AddrDelay{\n\t\t\t\t\t\t\t\tAddr:  ad.addr,\n\t\t\t\t\t\t\t\tDelay: addrDelay[string(ad.addr.Bytes())],\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// add the request to the addrDial\n\t\t\t}\n\n\t\t\tif len(todial) > 0 {\n\t\t\t\tnow := time.Now()\n\t\t\t\t// these are new addresses, track them and add them to dq\n\t\t\t\tfor _, a := range todial {\n\t\t\t\t\tw.trackedDials[string(a.Bytes())] = &addrDial{\n\t\t\t\t\t\taddr:      a,\n\t\t\t\t\t\tctx:       req.ctx,\n\t\t\t\t\t\tcreatedAt: now,\n\t\t\t\t\t}\n\t\t\t\t\tdq.Add(network.AddrDelay{Addr: a, Delay: addrDelay[string(a.Bytes())]})\n\t\t\t\t}\n\t\t\t}\n\t\t\t// setup dialTimer for updates to dq\n\t\t\tscheduleNextDial()\n\n\t\tcase <-dialTimer.Ch():\n\t\t\t// It's time to dial the next batch of addresses.\n\t\t\t// We don't check the delay of the addresses received from the queue here\n\t\t\t// because if the timer triggered before the delay, it means that all\n\t\t\t// the inflight dials have errored and we should dial the next batch of\n\t\t\t// addresses\n\t\t\tnow := time.Now()\n\t\t\tfor _, adelay := range dq.NextBatch() {\n\t\t\t\t// spawn the dial\n\t\t\t\tad, ok := w.trackedDials[string(adelay.Addr.Bytes())]\n\t\t\t\tif !ok {\n\t\t\t\t\tlog.Error(\"SWARM BUG: no entry for address in trackedDials\", \"addr\", adelay.Addr)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tad.dialed = true\n\t\t\t\tad.dialRankingDelay = now.Sub(ad.createdAt)\n\t\t\t\terr := w.s.dialNextAddr(ad.ctx, w.peer, ad.addr, w.resch)\n\t\t\t\tif err != nil {\n\t\t\t\t\t// Errored without attempting a dial. This happens in case of\n\t\t\t\t\t// backoff or black hole.\n\t\t\t\t\tw.dispatchError(ad, err)\n\t\t\t\t} else {\n\t\t\t\t\t// the dial was successful. update inflight dials\n\t\t\t\t\tdialsInFlight++\n\t\t\t\t\ttotalDials++\n\t\t\t\t}\n\t\t\t}\n\t\t\ttimerRunning = false\n\t\t\t// schedule more dials\n\t\t\tscheduleNextDial()\n\n\t\tcase res := <-w.resch:\n\t\t\t// A dial to an address has completed.\n\t\t\t// Update all requests waiting on this address. On success, complete the request.\n\t\t\t// On error, record the error\n\n\t\t\tad, ok := w.trackedDials[string(res.Addr.Bytes())]\n\t\t\tif !ok {\n\t\t\t\tlog.Error(\"SWARM BUG: no entry for address in trackedDials\", \"addr\", res.Addr)\n\t\t\t\tif res.Conn != nil {\n\t\t\t\t\tres.Conn.Close()\n\t\t\t\t}\n\t\t\t\tdialsInFlight--\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// TCP Connection has been established. Wait for connection upgrade on this address\n\t\t\t// before making new dials.\n\t\t\tif res.Kind == tpt.UpdateKindHandshakeProgressed {\n\t\t\t\t// Only wait for public addresses to complete dialing since private dials\n\t\t\t\t// are quick any way\n\t\t\t\tif manet.IsPublicAddr(res.Addr) {\n\t\t\t\t\tad.expectedTCPUpgradeTime = w.cl.Now().Add(PublicTCPDelay)\n\t\t\t\t}\n\t\t\t\tscheduleNextDial()\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tdialsInFlight--\n\t\t\tad.expectedTCPUpgradeTime = time.Time{}\n\t\t\tif res.Conn != nil {\n\t\t\t\t// we got a connection, add it to the swarm\n\t\t\t\tconn, err := w.s.addConn(res.Conn, network.DirOutbound)\n\t\t\t\tif err != nil {\n\t\t\t\t\t// oops no, we failed to add it to the swarm\n\t\t\t\t\tres.Conn.Close()\n\t\t\t\t\tw.dispatchError(ad, err)\n\t\t\t\t\tcontinue loop\n\t\t\t\t}\n\n\t\t\t\tfor pr := range w.pendingRequests {\n\t\t\t\t\tif _, ok := pr.addrs[string(ad.addr.Bytes())]; ok {\n\t\t\t\t\t\tpr.req.resch <- dialResponse{conn: conn}\n\t\t\t\t\t\tdelete(w.pendingRequests, pr)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tad.conn = conn\n\t\t\t\tif !w.connected {\n\t\t\t\t\tw.connected = true\n\t\t\t\t\tif w.s.metricsTracer != nil {\n\t\t\t\t\t\tw.s.metricsTracer.DialRankingDelay(ad.dialRankingDelay)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tcontinue loop\n\t\t\t}\n\n\t\t\t// it must be an error -- add backoff if applicable and dispatch\n\t\t\t// ErrDialRefusedBlackHole shouldn't end up here, just a safety check\n\t\t\tif res.Err != ErrDialRefusedBlackHole && res.Err != context.Canceled && !w.connected {\n\t\t\t\t// we only add backoff if there has not been a successful connection\n\t\t\t\t// for consistency with the old dialer behavior.\n\t\t\t\tw.s.backf.AddBackoff(w.peer, res.Addr)\n\t\t\t} else if res.Err == ErrDialRefusedBlackHole {\n\t\t\t\tlog.Error(\"SWARM BUG: unexpected ErrDialRefusedBlackHole while dialing peer to addr\",\n\t\t\t\t\t\"peer\", w.peer, \"addr\", res.Addr)\n\t\t\t}\n\n\t\t\tw.dispatchError(ad, res.Err)\n\t\t\t// Only schedule next dial on error.\n\t\t\t// If we scheduleNextDial on success, we will end up making one dial more than\n\t\t\t// required because the final successful dial will spawn one more dial\n\t\t\tscheduleNextDial()\n\t\t}\n\t}\n}\n\n// dispatches an error to a specific addr dial\nfunc (w *dialWorker) dispatchError(ad *addrDial, err error) {\n\tad.err = err\n\tfor pr := range w.pendingRequests {\n\t\t// accumulate the error\n\t\tif _, ok := pr.addrs[string(ad.addr.Bytes())]; ok {\n\t\t\tpr.err.recordErr(ad.addr, err)\n\t\t\tdelete(pr.addrs, string(ad.addr.Bytes()))\n\t\t\tif len(pr.addrs) == 0 {\n\t\t\t\t// all addrs have erred, dispatch dial error\n\t\t\t\t// but first do a last one check in case an acceptable connection has landed from\n\t\t\t\t// a simultaneous dial that started later and added new acceptable addrs\n\t\t\t\tc := w.s.bestAcceptableConnToPeer(pr.req.ctx, w.peer)\n\t\t\t\tif c != nil {\n\t\t\t\t\tpr.req.resch <- dialResponse{conn: c}\n\t\t\t\t} else {\n\t\t\t\t\tpr.err.Cause = ErrAllDialsFailed\n\t\t\t\t\tpr.req.resch <- dialResponse{err: pr.err}\n\t\t\t\t}\n\t\t\t\tdelete(w.pendingRequests, pr)\n\t\t\t}\n\t\t}\n\t}\n\n\t// if it was a backoff, clear the address dial so that it doesn't inhibit new dial requests.\n\t// this is necessary to support active listen scenarios, where a new dial comes in while\n\t// another dial is in progress, and needs to do a direct connection without inhibitions from\n\t// dial backoff.\n\tif err == ErrDialBackoff {\n\t\tdelete(w.trackedDials, string(ad.addr.Bytes()))\n\t}\n}\n\n// rankAddrs ranks addresses for dialing. if it's a simConnect request we\n// dial all addresses immediately without any delay\nfunc (w *dialWorker) rankAddrs(addrs []ma.Multiaddr, isSimConnect bool) []network.AddrDelay {\n\tif isSimConnect {\n\t\treturn NoDelayDialRanker(addrs)\n\t}\n\treturn w.s.dialRanker(addrs)\n}\n\n// dialQueue is a priority queue used to schedule dials\ntype dialQueue struct {\n\t// q contains dials ordered by delay\n\tq []network.AddrDelay\n}\n\n// newDialQueue returns a new dialQueue\nfunc newDialQueue() *dialQueue {\n\treturn &dialQueue{\n\t\tq: make([]network.AddrDelay, 0, 16),\n\t}\n}\n\n// Add adds a new element to the dialQueue. To update an element use UpdateOrAdd.\nfunc (dq *dialQueue) Add(adelay network.AddrDelay) {\n\tfor i := dq.Len() - 1; i >= 0; i-- {\n\t\tif dq.q[i].Delay <= adelay.Delay {\n\t\t\t// insert at pos i+1\n\t\t\tdq.q = append(dq.q, network.AddrDelay{}) // extend the slice\n\t\t\tcopy(dq.q[i+2:], dq.q[i+1:])\n\t\t\tdq.q[i+1] = adelay\n\t\t\treturn\n\t\t}\n\t}\n\t// insert at position 0\n\tdq.q = append(dq.q, network.AddrDelay{}) // extend the slice\n\tcopy(dq.q[1:], dq.q[0:])\n\tdq.q[0] = adelay\n}\n\n// UpdateOrAdd updates the elements with address adelay.Addr to the new delay\n// Useful when hole punching\nfunc (dq *dialQueue) UpdateOrAdd(adelay network.AddrDelay) {\n\tfor i := 0; i < dq.Len(); i++ {\n\t\tif dq.q[i].Addr.Equal(adelay.Addr) {\n\t\t\tif dq.q[i].Delay == adelay.Delay {\n\t\t\t\t// existing element is the same. nothing to do\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// remove the element\n\t\t\tcopy(dq.q[i:], dq.q[i+1:])\n\t\t\tdq.q = dq.q[:len(dq.q)-1]\n\t\t}\n\t}\n\tdq.Add(adelay)\n}\n\n// NextBatch returns all the elements in the queue with the highest priority\nfunc (dq *dialQueue) NextBatch() []network.AddrDelay {\n\tif dq.Len() == 0 {\n\t\treturn nil\n\t}\n\n\t// i is the index of the second highest priority element\n\tvar i int\n\tfor i = 0; i < dq.Len(); i++ {\n\t\tif dq.q[i].Delay != dq.q[0].Delay {\n\t\t\tbreak\n\t\t}\n\t}\n\tres := dq.q[:i]\n\tdq.q = dq.q[i:]\n\treturn res\n}\n\n// top returns the top element of the queue\nfunc (dq *dialQueue) top() network.AddrDelay {\n\treturn dq.q[0]\n}\n\n// Len returns the number of elements in the queue\nfunc (dq *dialQueue) Len() int {\n\treturn len(dq.q)\n}\n"
  },
  {
    "path": "p2p/net/swarm/dial_worker_test.go",
    "content": "package swarm\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\tmrand \"math/rand\"\n\t\"reflect\"\n\t\"sort\"\n\t\"sync\"\n\t\"testing\"\n\t\"testing/quick\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\t\"github.com/libp2p/go-libp2p/core/sec\"\n\t\"github.com/libp2p/go-libp2p/core/test\"\n\t\"github.com/libp2p/go-libp2p/core/transport\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/eventbus\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoremem\"\n\t\"github.com/libp2p/go-libp2p/p2p/muxer/yamux\"\n\ttptu \"github.com/libp2p/go-libp2p/p2p/net/upgrader\"\n\t\"github.com/libp2p/go-libp2p/p2p/security/insecure\"\n\tlibp2pquic \"github.com/libp2p/go-libp2p/p2p/transport/quic\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/quicreuse\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/tcp\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n\t\"github.com/quic-go/quic-go\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype mockClock struct {\n\t*test.MockClock\n}\n\nfunc (m *mockClock) InstantTimer(when time.Time) InstantTimer {\n\treturn m.MockClock.InstantTimer(when)\n}\n\nfunc newMockClock() *mockClock {\n\treturn &mockClock{test.NewMockClock()}\n}\n\nfunc newPeer(t *testing.T) (crypto.PrivKey, peer.ID) {\n\tpriv, _, err := crypto.GenerateEd25519Key(rand.Reader)\n\trequire.NoError(t, err)\n\tid, err := peer.IDFromPrivateKey(priv)\n\trequire.NoError(t, err)\n\treturn priv, id\n}\n\nfunc makeSwarm(t *testing.T) *Swarm {\n\ts := makeSwarmWithNoListenAddrs(t, WithDialTimeout(1*time.Second))\n\tif err := s.Listen(ma.StringCast(\"/ip4/127.0.0.1/tcp/0\")); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif err := s.Listen(ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1\")); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn s\n}\n\nfunc makeSwarmWithNoListenAddrs(t *testing.T, opts ...Option) *Swarm {\n\tpriv, id := newPeer(t)\n\n\tps, err := pstoremem.NewPeerstore()\n\trequire.NoError(t, err)\n\tps.AddPubKey(id, priv.GetPublic())\n\tps.AddPrivKey(id, priv)\n\tt.Cleanup(func() { ps.Close() })\n\n\ts, err := NewSwarm(id, ps, eventbus.NewBus(), opts...)\n\trequire.NoError(t, err)\n\n\tupgrader := makeUpgrader(t, s)\n\tvar tcpOpts []tcp.Option\n\ttcpOpts = append(tcpOpts, tcp.DisableReuseport())\n\ttcpTransport, err := tcp.NewTCPTransport(upgrader, nil, nil, tcpOpts...)\n\trequire.NoError(t, err)\n\tif err := s.AddTransport(tcpTransport); err != nil {\n\t\tt.Fatal(err)\n\t}\n\treuse, err := quicreuse.NewConnManager(quic.StatelessResetKey{}, quic.TokenGeneratorKey{})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tquicTransport, err := libp2pquic.NewTransport(priv, reuse, nil, nil, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := s.AddTransport(quicTransport); err != nil {\n\t\tt.Fatal(err)\n\t}\n\treturn s\n}\n\nfunc makeUpgrader(t *testing.T, n *Swarm) transport.Upgrader {\n\tid := n.LocalPeer()\n\tpk := n.Peerstore().PrivKey(id)\n\tst := insecure.NewWithIdentity(insecure.ID, id, pk)\n\n\tu, err := tptu.New([]sec.SecureTransport{st}, []tptu.StreamMuxer{{ID: yamux.ID, Muxer: yamux.DefaultTransport}}, nil, nil, nil)\n\trequire.NoError(t, err)\n\treturn u\n}\n\n// makeTCPListener listens on tcp address a. On accepting a connection it notifies recvCh. Sending a message to\n// channel ch will close an accepted connection\nfunc makeTCPListener(t *testing.T, a ma.Multiaddr, recvCh chan struct{}) (list manet.Listener, ch chan struct{}) {\n\tt.Helper()\n\tlist, err := manet.Listen(a)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tch = make(chan struct{})\n\tgo func() {\n\t\tfor {\n\t\t\tc, err := list.Accept()\n\t\t\tif err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\trecvCh <- struct{}{}\n\t\t\t<-ch\n\t\t\terr = c.Close()\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\n\t\t}\n\t}()\n\treturn list, ch\n}\n\nfunc TestDialWorkerLoopBasic(t *testing.T) {\n\ts1 := makeSwarm(t)\n\ts2 := makeSwarm(t)\n\tdefer s1.Close()\n\tdefer s2.Close()\n\n\t// Only pass in a single address here, otherwise we might end up with a TCP and QUIC connection dialed.\n\ts1.Peerstore().AddAddrs(s2.LocalPeer(), []ma.Multiaddr{s2.ListenAddresses()[0]}, peerstore.PermanentAddrTTL)\n\n\treqch := make(chan dialRequest)\n\tresch := make(chan dialResponse)\n\tworker := newDialWorker(s1, s2.LocalPeer(), reqch, nil)\n\tgo worker.loop()\n\n\tvar conn *Conn\n\treqch <- dialRequest{ctx: context.Background(), resch: resch}\n\tselect {\n\tcase res := <-resch:\n\t\trequire.NoError(t, res.err)\n\t\tconn = res.conn\n\tcase <-time.After(10 * time.Second):\n\t\tt.Fatal(\"dial didn't complete\")\n\t}\n\n\ts, err := conn.NewStream(context.Background())\n\trequire.NoError(t, err)\n\ts.Close()\n\n\tvar conn2 *Conn\n\treqch <- dialRequest{ctx: context.Background(), resch: resch}\n\tselect {\n\tcase res := <-resch:\n\t\trequire.NoError(t, res.err)\n\t\tconn2 = res.conn\n\tcase <-time.After(10 * time.Second):\n\t\tt.Fatal(\"dial didn't complete\")\n\t}\n\n\t// can't use require.Equal here, as this does a deep comparison\n\tif conn != conn2 {\n\t\tt.Fatalf(\"expecting the same connection from both dials. %s <-> %s vs. %s <-> %s\", conn.LocalMultiaddr(), conn.RemoteMultiaddr(), conn2.LocalMultiaddr(), conn2.RemoteMultiaddr())\n\t}\n\n\tclose(reqch)\n\tworker.wg.Wait()\n}\n\nfunc TestDialWorkerLoopConcurrent(t *testing.T) {\n\ts1 := makeSwarm(t)\n\ts2 := makeSwarm(t)\n\tdefer s1.Close()\n\tdefer s2.Close()\n\n\ts1.Peerstore().AddAddrs(s2.LocalPeer(), s2.ListenAddresses(), peerstore.PermanentAddrTTL)\n\n\treqch := make(chan dialRequest)\n\tworker := newDialWorker(s1, s2.LocalPeer(), reqch, nil)\n\tgo worker.loop()\n\n\tconst dials = 100\n\tvar wg sync.WaitGroup\n\tresch := make(chan dialResponse, dials)\n\tfor range dials {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\treschgo := make(chan dialResponse, 1)\n\t\t\treqch <- dialRequest{ctx: context.Background(), resch: reschgo}\n\t\t\tselect {\n\t\t\tcase res := <-reschgo:\n\t\t\t\tresch <- res\n\t\t\tcase <-time.After(time.Minute):\n\t\t\t\tresch <- dialResponse{err: errors.New(\"timed out!\")}\n\t\t\t}\n\t\t}()\n\t}\n\twg.Wait()\n\n\tfor range dials {\n\t\tres := <-resch\n\t\trequire.NoError(t, res.err)\n\t}\n\n\tt.Log(\"all concurrent dials done\")\n\n\tclose(reqch)\n\tworker.wg.Wait()\n}\n\nfunc TestDialWorkerLoopFailure(t *testing.T) {\n\ts1 := makeSwarm(t)\n\tdefer s1.Close()\n\n\t_, p2 := newPeer(t)\n\n\ts1.Peerstore().AddAddrs(p2, []ma.Multiaddr{ma.StringCast(\"/ip4/11.0.0.1/tcp/1234\"), ma.StringCast(\"/ip4/11.0.0.1/udp/1234/quic-v1\")}, peerstore.PermanentAddrTTL)\n\n\treqch := make(chan dialRequest)\n\tresch := make(chan dialResponse)\n\tworker := newDialWorker(s1, p2, reqch, nil)\n\tgo worker.loop()\n\n\treqch <- dialRequest{ctx: context.Background(), resch: resch}\n\tselect {\n\tcase res := <-resch:\n\t\trequire.Error(t, res.err)\n\tcase <-time.After(time.Minute):\n\t\tt.Fatal(\"dial didn't complete\")\n\t}\n\n\tclose(reqch)\n\tworker.wg.Wait()\n}\n\nfunc TestDialWorkerLoopConcurrentFailure(t *testing.T) {\n\ts1 := makeSwarm(t)\n\tdefer s1.Close()\n\n\t_, p2 := newPeer(t)\n\n\ts1.Peerstore().AddAddrs(p2, []ma.Multiaddr{ma.StringCast(\"/ip4/11.0.0.1/tcp/1234\"), ma.StringCast(\"/ip4/11.0.0.1/udp/1234/quic-v1\")}, peerstore.PermanentAddrTTL)\n\n\treqch := make(chan dialRequest)\n\tworker := newDialWorker(s1, p2, reqch, nil)\n\tgo worker.loop()\n\n\tconst dials = 100\n\tvar errTimeout = errors.New(\"timed out!\")\n\tvar wg sync.WaitGroup\n\tresch := make(chan dialResponse, dials)\n\tfor range dials {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\treschgo := make(chan dialResponse, 1)\n\t\t\treqch <- dialRequest{ctx: context.Background(), resch: reschgo}\n\n\t\t\tselect {\n\t\t\tcase res := <-reschgo:\n\t\t\t\tresch <- res\n\t\t\tcase <-time.After(time.Minute):\n\t\t\t\tresch <- dialResponse{err: errTimeout}\n\t\t\t}\n\t\t}()\n\t}\n\twg.Wait()\n\n\tfor range dials {\n\t\tres := <-resch\n\t\trequire.Error(t, res.err)\n\t\tif res.err == errTimeout {\n\t\t\tt.Fatal(\"dial response timed out\")\n\t\t}\n\t}\n\n\tt.Log(\"all concurrent dials done\")\n\n\tclose(reqch)\n\tworker.wg.Wait()\n}\n\nfunc TestDialWorkerLoopConcurrentMix(t *testing.T) {\n\ts1 := makeSwarm(t)\n\ts2 := makeSwarm(t)\n\tdefer s1.Close()\n\tdefer s2.Close()\n\n\ts1.Peerstore().AddAddrs(s2.LocalPeer(), s2.ListenAddresses(), peerstore.PermanentAddrTTL)\n\ts1.Peerstore().AddAddrs(s2.LocalPeer(), []ma.Multiaddr{ma.StringCast(\"/ip4/11.0.0.1/tcp/1234\"), ma.StringCast(\"/ip4/11.0.0.1/udp/1234/quic-v1\")}, peerstore.PermanentAddrTTL)\n\n\treqch := make(chan dialRequest)\n\tworker := newDialWorker(s1, s2.LocalPeer(), reqch, nil)\n\tgo worker.loop()\n\n\tconst dials = 100\n\tvar wg sync.WaitGroup\n\tresch := make(chan dialResponse, dials)\n\tfor range dials {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\treschgo := make(chan dialResponse, 1)\n\t\t\treqch <- dialRequest{ctx: context.Background(), resch: reschgo}\n\t\t\tselect {\n\t\t\tcase res := <-reschgo:\n\t\t\t\tresch <- res\n\t\t\tcase <-time.After(time.Minute):\n\t\t\t\tresch <- dialResponse{err: errors.New(\"timed out!\")}\n\t\t\t}\n\t\t}()\n\t}\n\twg.Wait()\n\n\tfor range dials {\n\t\tres := <-resch\n\t\trequire.NoError(t, res.err)\n\t}\n\n\tt.Log(\"all concurrent dials done\")\n\n\tclose(reqch)\n\tworker.wg.Wait()\n}\n\nfunc TestDialWorkerLoopConcurrentFailureStress(t *testing.T) {\n\ts1 := makeSwarm(t)\n\tdefer s1.Close()\n\n\t_, p2 := newPeer(t)\n\n\taddrs := make([]ma.Multiaddr, 0, 16)\n\tfor i := range 16 {\n\t\taddrs = append(addrs, ma.StringCast(fmt.Sprintf(\"/ip4/11.0.0.%d/tcp/%d\", i%256, 1234+i)))\n\t}\n\ts1.Peerstore().AddAddrs(p2, addrs, peerstore.PermanentAddrTTL)\n\n\treqch := make(chan dialRequest)\n\tworker := newDialWorker(s1, p2, reqch, nil)\n\tgo worker.loop()\n\n\tconst dials = 100\n\tvar errTimeout = errors.New(\"timed out!\")\n\tvar wg sync.WaitGroup\n\tresch := make(chan dialResponse, dials)\n\tfor range dials {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\treschgo := make(chan dialResponse, 1)\n\t\t\treqch <- dialRequest{ctx: context.Background(), resch: reschgo}\n\t\t\tselect {\n\t\t\tcase res := <-reschgo:\n\t\t\t\tt.Log(\"received result\")\n\t\t\t\tresch <- res\n\t\t\tcase <-time.After(15 * time.Second):\n\t\t\t\tresch <- dialResponse{err: errTimeout}\n\t\t\t}\n\t\t}()\n\t}\n\twg.Wait()\n\n\tfor range dials {\n\t\tres := <-resch\n\t\trequire.Error(t, res.err)\n\t\tif res.err == errTimeout {\n\t\t\tt.Fatal(\"dial response timed out\")\n\t\t}\n\t}\n\n\tt.Log(\"all concurrent dials done\")\n\n\tclose(reqch)\n\tworker.wg.Wait()\n}\n\nfunc TestDialQueueNextBatch(t *testing.T) {\n\taddrs := make([]ma.Multiaddr, 0)\n\tfor i := range 10 {\n\t\taddrs = append(addrs, ma.StringCast(fmt.Sprintf(\"/ip4/1.2.3.4/tcp/%d\", i)))\n\t}\n\ttestcase := []struct {\n\t\tname       string\n\t\tinput      []network.AddrDelay\n\t\toutput     [][]ma.Multiaddr\n\t\thasUpdates bool\n\t}{\n\t\t{\n\t\t\tname: \"next batch\",\n\t\t\tinput: []network.AddrDelay{\n\t\t\t\t{Addr: addrs[0], Delay: 3},\n\t\t\t\t{Addr: addrs[1], Delay: 2},\n\t\t\t\t{Addr: addrs[2], Delay: 1},\n\t\t\t\t{Addr: addrs[3], Delay: 1},\n\t\t\t},\n\t\t\toutput: [][]ma.Multiaddr{\n\t\t\t\t{addrs[2], addrs[3]},\n\t\t\t\t{addrs[1]},\n\t\t\t\t{addrs[0]},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"priority queue property 2\",\n\t\t\tinput: []network.AddrDelay{\n\t\t\t\t{Addr: addrs[0], Delay: 5},\n\t\t\t\t{Addr: addrs[1], Delay: 3},\n\t\t\t\t{Addr: addrs[2], Delay: 2},\n\t\t\t\t{Addr: addrs[3], Delay: 1},\n\t\t\t\t{Addr: addrs[4], Delay: 1},\n\t\t\t},\n\n\t\t\toutput: [][]ma.Multiaddr{\n\t\t\t\t{addrs[3], addrs[4]},\n\t\t\t\t{addrs[2]},\n\t\t\t\t{addrs[1]},\n\t\t\t\t{addrs[0]},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"updates\",\n\t\t\tinput: []network.AddrDelay{\n\t\t\t\t{Addr: addrs[0], Delay: 3}, // decreasing order\n\t\t\t\t{Addr: addrs[1], Delay: 3},\n\t\t\t\t{Addr: addrs[2], Delay: 2},\n\t\t\t\t{Addr: addrs[3], Delay: 2},\n\t\t\t\t{Addr: addrs[4], Delay: 1},\n\t\t\t\t{Addr: addrs[0], Delay: 1}, // increasing order\n\t\t\t\t{Addr: addrs[1], Delay: 1},\n\t\t\t\t{Addr: addrs[2], Delay: 2},\n\t\t\t\t{Addr: addrs[3], Delay: 2},\n\t\t\t\t{Addr: addrs[4], Delay: 3},\n\t\t\t},\n\t\t\toutput: [][]ma.Multiaddr{\n\t\t\t\t{addrs[0], addrs[1]},\n\t\t\t\t{addrs[2], addrs[3]},\n\t\t\t\t{addrs[4]},\n\t\t\t\t{},\n\t\t\t},\n\t\t\thasUpdates: true,\n\t\t},\n\t\t{\n\t\t\tname:  \"null input\",\n\t\t\tinput: []network.AddrDelay{},\n\t\t\toutput: [][]ma.Multiaddr{\n\t\t\t\t{},\n\t\t\t\t{},\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tc := range testcase {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tq := newDialQueue()\n\t\t\tfor i := 0; i < len(tc.input); i++ {\n\t\t\t\tif tc.hasUpdates {\n\t\t\t\t\tq.UpdateOrAdd(tc.input[i])\n\t\t\t\t} else {\n\t\t\t\t\tq.Add(tc.input[i])\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor _, batch := range tc.output {\n\t\t\t\tb := q.NextBatch()\n\t\t\t\tif len(batch) != len(b) {\n\t\t\t\t\tt.Errorf(\"expected %d elements got %d\", len(batch), len(b))\n\t\t\t\t}\n\t\t\t\tsort.Slice(b, func(i, j int) bool { return b[i].Addr.String() < b[j].Addr.String() })\n\t\t\t\tsort.Slice(batch, func(i, j int) bool { return batch[i].String() < batch[j].String() })\n\t\t\t\tfor i := range b {\n\t\t\t\t\tif !b[i].Addr.Equal(batch[i]) {\n\t\t\t\t\t\tlog.Error(\"expected address mismatch\", \"expected\", batch[i], \"got\", b[i].Addr)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif q.Len() != 0 {\n\t\t\t\tt.Errorf(\"expected queue to be empty at end. got: %d\", q.Len())\n\t\t\t}\n\t\t})\n\t}\n}\n\n// timedDial is a dial to a single address of the peer\ntype timedDial struct {\n\t// addr is the address to dial\n\taddr ma.Multiaddr\n\t// delay is the delay after which this address should be dialed\n\tdelay time.Duration\n\t// success indicates whether the dial should succeed\n\tsuccess bool\n\t// failAfter is how long this dial should take to fail after it is dialed\n\tfailAfter time.Duration\n}\n\n// schedulingTestCase is used to test dialWorker loop scheduler logic\n// a ranker is made according to `input` which provides the addresses to\n// dial worker loop with the specified delays\n// checkDialWorkerLoopScheduling then verifies that the different dial calls are\n// made at the right moments\ntype schedulingTestCase struct {\n\tname        string\n\tinput       []timedDial\n\tmaxDuration time.Duration\n}\n\n// schedulingTestCase generates a random test case\nfunc (s schedulingTestCase) Generate(rand *mrand.Rand, size int) reflect.Value {\n\tif size > 20 {\n\t\tsize = 20\n\t}\n\tinput := make([]timedDial, size)\n\tdelays := make(map[time.Duration]struct{})\n\tfor i := 0; i < size; i++ {\n\t\tinput[i] = timedDial{\n\t\t\taddr:      ma.StringCast(fmt.Sprintf(\"/ip4/127.0.0.1/tcp/%d\", i+10550)),\n\t\t\tdelay:     time.Duration(mrand.Intn(100)) * 10 * time.Millisecond, // max 1 second\n\t\t\tsuccess:   false,\n\t\t\tfailAfter: time.Duration(mrand.Intn(100)) * 10 * time.Millisecond, // max 1 second\n\t\t}\n\t\tdelays[input[i].delay] = struct{}{}\n\t}\n\tsuccessIdx := rand.Intn(size)\n\tfor {\n\t\t// set a unique delay for success. This is required to test the property that\n\t\t// no extra dials are made after success\n\t\td := time.Duration(rand.Intn(100)) * 10 * time.Millisecond\n\t\tif _, ok := delays[d]; !ok {\n\t\t\tinput[successIdx].delay = d\n\t\t\tinput[successIdx].success = true\n\t\t\tbreak\n\t\t}\n\t}\n\treturn reflect.ValueOf(schedulingTestCase{\n\t\tname:        \"\",\n\t\tinput:       input,\n\t\tmaxDuration: 10 * time.Second, // not tested here\n\t})\n}\n\n// dialState is used to track the dials for testing dialWorker ranking logic\ntype dialState struct {\n\t// ch is the chan used to trigger dial failure.\n\tch chan struct{}\n\t// addr is the address of the dial\n\taddr ma.Multiaddr\n\t// delay is the delay after which this address should be dialed\n\tdelay time.Duration\n\t// success indicates whether the dial should succeed\n\tsuccess bool\n\t// failAfter is how long this dial should take to fail after it is dialed\n\tfailAfter time.Duration\n\t// failAt is the instant at which this dial should fail if success is false\n\tfailAt time.Time\n}\n\n// checkDialWorkerLoopScheduling verifies whether s1 dials s2 according to the\n// schedule specified by the test case tc\nfunc checkDialWorkerLoopScheduling(t *testing.T, s1, s2 *Swarm, tc schedulingTestCase) error {\n\tt.Helper()\n\t// failDials is used to track dials which should fail in the future\n\t// at appropriate moment a message is sent to dialState.ch to trigger\n\t// failure\n\tfailDials := make(map[*ma.Multiaddr]dialState)\n\t// recvCh is used to receive dial notifications for dials that will fail\n\trecvCh := make(chan struct{}, 100)\n\t// allDials tracks all pending dials\n\tallDials := make(map[*ma.Multiaddr]dialState)\n\t// addrs are the peer addresses the swarm will use for dialing\n\taddrs := make([]ma.Multiaddr, 0)\n\t// create pending dials\n\t// we add success cases as a listen address on swarm\n\t// failed cases are created using makeTCPListener\n\tfor _, inp := range tc.input {\n\t\tvar failCh chan struct{}\n\t\tif inp.success {\n\t\t\t// add the address as a listen address if this dial should succeed\n\t\t\terr := s2.AddListenAddr(inp.addr)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to listen on addr: %s: err: %w\", inp.addr, err)\n\t\t\t}\n\t\t} else {\n\t\t\t// make a listener which will fail on sending a message to ch\n\t\t\tl, ch := makeTCPListener(t, inp.addr, recvCh)\n\t\t\tfailCh = ch\n\t\t\tf := func() {\n\t\t\t\terr := l.Close()\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Error(err)\n\t\t\t\t}\n\t\t\t}\n\t\t\tdefer f()\n\t\t}\n\t\taddrs = append(addrs, inp.addr)\n\t\t// add to pending dials\n\t\tallDials[&inp.addr] = dialState{\n\t\t\tch:        failCh,\n\t\t\taddr:      inp.addr,\n\t\t\tdelay:     inp.delay,\n\t\t\tsuccess:   inp.success,\n\t\t\tfailAfter: inp.failAfter,\n\t\t}\n\t}\n\t// setup the peers addresses\n\ts1.Peerstore().AddAddrs(s2.LocalPeer(), addrs, peerstore.PermanentAddrTTL)\n\n\t// create worker\n\treqch := make(chan dialRequest)\n\tresch := make(chan dialResponse)\n\tcl := newMockClock()\n\tst := cl.Now()\n\tworker1 := newDialWorker(s1, s2.LocalPeer(), reqch, cl)\n\tgo worker1.loop()\n\tdefer worker1.wg.Wait()\n\tdefer close(reqch)\n\n\t// trigger the request\n\treqch <- dialRequest{ctx: context.Background(), resch: resch}\n\n\tconnected := false\n\n\t// Advance the clock by 10 ms every iteration\n\t// At every iteration:\n\t//   Check if any dial should fail. if it should, trigger the failure by sending a message on the\n\t//   listener failCh\n\t//   If there are no dials in flight check the most urgent dials have been triggered\n\t//   If there are dials in flight check that the relevant dials have been triggered\n\t//   Before next iteration ensure that no unexpected dials are received\nloop:\n\tfor {\n\t\t// fail any dials that should fail at this instant\n\t\tfor a, p := range failDials {\n\t\t\tif p.failAt.Before(cl.Now()) || p.failAt == cl.Now() {\n\t\t\t\tp.ch <- struct{}{}\n\t\t\t\tdelete(failDials, a)\n\t\t\t}\n\t\t}\n\t\t// if there are no pending dials, next dial should have been triggered\n\t\ttrigger := len(failDials) == 0\n\n\t\t// mi is the minimum delay of pending dials\n\t\t// if trigger is true, all dials with miDelay should have been triggered\n\t\tmi := time.Duration(math.MaxInt64)\n\t\tfor _, ds := range allDials {\n\t\t\tif ds.delay < mi {\n\t\t\t\tmi = ds.delay\n\t\t\t}\n\t\t}\n\t\tfor a, ds := range allDials {\n\t\t\tif (trigger && mi == ds.delay) ||\n\t\t\t\tcl.Now().After(st.Add(ds.delay)) ||\n\t\t\t\tcl.Now() == st.Add(ds.delay) {\n\t\t\t\tif ds.success {\n\t\t\t\t\t// check for success and exit\n\t\t\t\t\tselect {\n\t\t\t\t\tcase r := <-resch:\n\t\t\t\t\t\tif r.conn == nil {\n\t\t\t\t\t\t\treturn errors.New(\"expected connection to succeed\")\n\t\t\t\t\t\t}\n\t\t\t\t\t// High timeout here is okay. We will exit whenever the other branch\n\t\t\t\t\t// is triggered\n\t\t\t\t\tcase <-time.After(10 * time.Second):\n\t\t\t\t\t\treturn errors.New(\"expected to receive a response\")\n\t\t\t\t\t}\n\t\t\t\t\tconnected = true\n\t\t\t\t\tbreak loop\n\t\t\t\t} else {\n\t\t\t\t\t// ensure that a failing dial attempt happened but didn't succeed\n\t\t\t\t\tselect {\n\t\t\t\t\tcase <-recvCh:\n\t\t\t\t\tcase <-resch:\n\t\t\t\t\t\treturn errors.New(\"didn't expect a response\")\n\t\t\t\t\t// High timeout here is okay. We will exit whenever the other branch\n\t\t\t\t\t// is triggered\n\t\t\t\t\tcase <-time.After(10 * time.Second):\n\t\t\t\t\t\treturn errors.New(\"didn't receive a dial attempt notification\")\n\t\t\t\t\t}\n\t\t\t\t\tfailDials[a] = dialState{\n\t\t\t\t\t\tch:     ds.ch,\n\t\t\t\t\t\tfailAt: cl.Now().Add(ds.failAfter),\n\t\t\t\t\t\taddr:   *a,\n\t\t\t\t\t\tdelay:  ds.delay,\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tdelete(allDials, a)\n\t\t\t}\n\t\t}\n\t\t// check for unexpected dials\n\t\tselect {\n\t\tcase <-recvCh:\n\t\t\treturn errors.New(\"no dial should have succeeded at this instant\")\n\t\tdefault:\n\t\t}\n\n\t\t// advance the clock\n\t\tcl.AdvanceBy(10 * time.Millisecond)\n\t\t// nothing more to do. exit\n\t\tif len(failDials) == 0 && len(allDials) == 0 {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif connected {\n\t\t// ensure we don't receive any extra connections\n\t\tselect {\n\t\tcase <-recvCh:\n\t\t\treturn errors.New(\"didn't expect a dial attempt\")\n\t\tcase <-time.After(100 * time.Millisecond):\n\t\t}\n\t} else {\n\t\t// ensure that we do receive the final error response\n\t\tselect {\n\t\tcase r := <-resch:\n\t\t\trequire.Error(t, r.err)\n\t\tcase <-time.After(100 * time.Millisecond):\n\t\t\treturn errors.New(\"expected to receive response\")\n\t\t}\n\t}\n\t// check if this test didn't take too much time\n\tif cl.Now().Sub(st) > tc.maxDuration {\n\t\treturn fmt.Errorf(\"expected test to finish early: expected %d, took: %d\", tc.maxDuration, cl.Now().Sub(st))\n\t}\n\treturn nil\n}\n\n// makeRanker takes a slice of timedDial objects and returns a DialRanker\n// which will trigger dials to addresses at the specified delays in the timedDials\nfunc makeRanker(tc []timedDial) network.DialRanker {\n\treturn func(_ []ma.Multiaddr) []network.AddrDelay {\n\t\tres := make([]network.AddrDelay, len(tc))\n\t\tfor i := range tc {\n\t\t\tres[i] = network.AddrDelay{Addr: tc[i].addr, Delay: tc[i].delay}\n\t\t}\n\t\treturn res\n\t}\n}\n\n// TestCheckDialWorkerLoopScheduling will check the checker\nfunc TestCheckDialWorkerLoopScheduling(t *testing.T) {\n\taddrs := make([]ma.Multiaddr, 0)\n\tfor i := range 10 {\n\t\tfor {\n\t\t\tp := 20000 + i\n\t\t\taddrs = append(addrs, ma.StringCast(fmt.Sprintf(\"/ip4/127.0.0.1/tcp/%d\", p)))\n\t\t\tbreak\n\t\t}\n\t}\n\n\ttc := schedulingTestCase{\n\t\tinput: []timedDial{\n\t\t\t{\n\t\t\t\taddr:    addrs[1],\n\t\t\t\tdelay:   0,\n\t\t\t\tsuccess: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\taddr:      addrs[0],\n\t\t\t\tdelay:     100 * time.Millisecond,\n\t\t\t\tsuccess:   false,\n\t\t\t\tfailAfter: 50 * time.Millisecond,\n\t\t\t},\n\t\t},\n\t\tmaxDuration: 20 * time.Millisecond,\n\t}\n\ts1 := makeSwarmWithNoListenAddrs(t)\n\ts2 := makeSwarmWithNoListenAddrs(t)\n\t// valid ranking logic, so it shouldn't error\n\ts1.dialRanker = makeRanker(tc.input)\n\terr := checkDialWorkerLoopScheduling(t, s1, s2, tc)\n\trequire.NoError(t, err)\n\t// close swarms to remove address binding\n\ts1.Close()\n\ts2.Close()\n\n\ts3 := makeSwarmWithNoListenAddrs(t)\n\tdefer s3.Close()\n\ts4 := makeSwarmWithNoListenAddrs(t)\n\tdefer s4.Close()\n\t// invalid ranking logic to trigger an error\n\ts3.dialRanker = NoDelayDialRanker\n\terr = checkDialWorkerLoopScheduling(t, s3, s4, tc)\n\trequire.Error(t, err)\n}\n\nfunc TestDialWorkerLoopRanking(t *testing.T) {\n\taddrs := make([]ma.Multiaddr, 0)\n\tfor i := range 10 {\n\t\tfor {\n\t\t\tp := 20000 + i\n\t\t\taddrs = append(addrs, ma.StringCast(fmt.Sprintf(\"/ip4/127.0.0.1/tcp/%d\", p)))\n\t\t\tbreak\n\t\t}\n\t}\n\n\ttestcases := []schedulingTestCase{\n\t\t{\n\t\t\tname: \"first success\",\n\t\t\tinput: []timedDial{\n\t\t\t\t{\n\t\t\t\t\taddr:    addrs[1],\n\t\t\t\t\tdelay:   0,\n\t\t\t\t\tsuccess: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\taddr:      addrs[0],\n\t\t\t\t\tdelay:     100 * time.Millisecond,\n\t\t\t\t\tsuccess:   false,\n\t\t\t\t\tfailAfter: 50 * time.Millisecond,\n\t\t\t\t},\n\t\t\t},\n\t\t\tmaxDuration: 20 * time.Millisecond,\n\t\t},\n\t\t{\n\t\t\tname: \"delayed dials\",\n\t\t\tinput: []timedDial{\n\t\t\t\t{\n\t\t\t\t\taddr:      addrs[0],\n\t\t\t\t\tdelay:     0,\n\t\t\t\t\tsuccess:   false,\n\t\t\t\t\tfailAfter: 200 * time.Millisecond,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\taddr:      addrs[1],\n\t\t\t\t\tdelay:     100 * time.Millisecond,\n\t\t\t\t\tsuccess:   false,\n\t\t\t\t\tfailAfter: 100 * time.Millisecond,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\taddr:      addrs[2],\n\t\t\t\t\tdelay:     300 * time.Millisecond,\n\t\t\t\t\tsuccess:   false,\n\t\t\t\t\tfailAfter: 100 * time.Millisecond,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\taddr:    addrs[3],\n\t\t\t\t\tdelay:   2 * time.Second,\n\t\t\t\t\tsuccess: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\taddr:      addrs[4],\n\t\t\t\t\tdelay:     2*time.Second + 1*time.Millisecond,\n\t\t\t\t\tsuccess:   false, // this call will never happened\n\t\t\t\t\tfailAfter: 100 * time.Millisecond,\n\t\t\t\t},\n\t\t\t},\n\t\t\tmaxDuration: 310 * time.Millisecond,\n\t\t},\n\t\t{\n\t\t\tname: \"failed dials\",\n\t\t\tinput: []timedDial{\n\t\t\t\t{\n\t\t\t\t\taddr:      addrs[0],\n\t\t\t\t\tdelay:     0,\n\t\t\t\t\tsuccess:   false,\n\t\t\t\t\tfailAfter: 105 * time.Millisecond,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\taddr:      addrs[1],\n\t\t\t\t\tdelay:     100 * time.Millisecond,\n\t\t\t\t\tsuccess:   false,\n\t\t\t\t\tfailAfter: 20 * time.Millisecond,\n\t\t\t\t},\n\t\t\t},\n\t\t\tmaxDuration: 200 * time.Millisecond,\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ts1 := makeSwarmWithNoListenAddrs(t)\n\t\t\tdefer s1.Close()\n\t\t\ts2 := makeSwarmWithNoListenAddrs(t)\n\t\t\tdefer s2.Close()\n\t\t\t// setup the ranker to trigger dials according to the test case\n\t\t\ts1.dialRanker = makeRanker(tc.input)\n\t\t\terr := checkDialWorkerLoopScheduling(t, s1, s2, tc)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDialWorkerLoopSchedulingProperty(t *testing.T) {\n\tf := func(tc schedulingTestCase) bool {\n\t\ts1 := makeSwarmWithNoListenAddrs(t)\n\t\tdefer s1.Close()\n\t\t// ignore limiter delays just check scheduling\n\t\ts1.limiter.perPeerLimit = 10000\n\t\ts2 := makeSwarmWithNoListenAddrs(t)\n\t\tdefer s2.Close()\n\t\t// setup the ranker to trigger dials according to the test case\n\t\ts1.dialRanker = makeRanker(tc.input)\n\t\terr := checkDialWorkerLoopScheduling(t, s1, s2, tc)\n\t\tif err != nil {\n\t\t\tt.Log(err)\n\t\t}\n\t\treturn err == nil\n\t}\n\n\tif err := quick.Check(f, &quick.Config{MaxCount: 50}); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestDialWorkerLoopQuicOverTCP(t *testing.T) {\n\ttc := schedulingTestCase{\n\t\tinput: []timedDial{\n\t\t\t{\n\t\t\t\taddr:    ma.StringCast(\"/ip4/127.0.0.1/udp/20000/quic-v1\"),\n\t\t\t\tdelay:   0,\n\t\t\t\tsuccess: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\taddr:    ma.StringCast(\"/ip4/127.0.0.1/tcp/20000\"),\n\t\t\t\tdelay:   30 * time.Millisecond,\n\t\t\t\tsuccess: true,\n\t\t\t},\n\t\t},\n\t\tmaxDuration: 20 * time.Millisecond,\n\t}\n\ts1 := makeSwarmWithNoListenAddrs(t)\n\tdefer s1.Close()\n\n\ts2 := makeSwarmWithNoListenAddrs(t)\n\tdefer s2.Close()\n\n\t// we use the default ranker here\n\n\terr := checkDialWorkerLoopScheduling(t, s1, s2, tc)\n\trequire.NoError(t, err)\n}\n\nfunc TestDialWorkerLoopHolePunching(t *testing.T) {\n\ts1 := makeSwarmWithNoListenAddrs(t)\n\tdefer s1.Close()\n\n\ts2 := makeSwarmWithNoListenAddrs(t)\n\tdefer s2.Close()\n\n\t// t1 will accept and keep the other end waiting\n\tt1 := ma.StringCast(\"/ip4/127.0.0.1/tcp/10000\")\n\trecvCh := make(chan struct{})\n\tlist, ch := makeTCPListener(t, t1, recvCh) // ignore ch because we want to hang forever\n\tdefer list.Close()\n\tdefer func() { ch <- struct{}{} }() // close listener\n\n\t// t2 will succeed\n\tt2 := ma.StringCast(\"/ip4/127.0.0.1/tcp/10001\")\n\n\terr := s2.AddListenAddr(t2)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\ts1.dialRanker = func(addrs []ma.Multiaddr) (res []network.AddrDelay) {\n\t\tres = make([]network.AddrDelay, len(addrs))\n\t\tfor i := range addrs {\n\t\t\tdelay := 10 * time.Second\n\t\t\tif addrs[i].Equal(t1) {\n\t\t\t\t// fire t1 immediately\n\t\t\t\tdelay = 0\n\t\t\t} else if addrs[i].Equal(t2) {\n\t\t\t\t// delay t2 by 100ms\n\t\t\t\t// without holepunch this call will not happen\n\t\t\t\tdelay = 100 * time.Millisecond\n\t\t\t}\n\t\t\tres[i] = network.AddrDelay{Addr: addrs[i], Delay: delay}\n\t\t}\n\t\treturn\n\t}\n\ts1.Peerstore().AddAddrs(s2.LocalPeer(), []ma.Multiaddr{t1, t2}, peerstore.PermanentAddrTTL)\n\n\treqch := make(chan dialRequest)\n\tresch := make(chan dialResponse, 2)\n\n\tcl := newMockClock()\n\tworker := newDialWorker(s1, s2.LocalPeer(), reqch, cl)\n\tgo worker.loop()\n\tdefer worker.wg.Wait()\n\tdefer close(reqch)\n\n\treqch <- dialRequest{ctx: context.Background(), resch: resch}\n\t<-recvCh // received connection on t1\n\n\tselect {\n\tcase <-resch:\n\t\tt.Errorf(\"didn't expect connection to succeed\")\n\tcase <-time.After(100 * time.Millisecond):\n\t}\n\n\thpCtx := network.WithSimultaneousConnect(context.Background(), true, \"testing\")\n\t// with holepunch request, t2 will be dialed immediately\n\treqch <- dialRequest{ctx: hpCtx, resch: resch}\n\tselect {\n\tcase r := <-resch:\n\t\trequire.NoError(t, r.err)\n\tcase <-time.After(5 * time.Second):\n\t\tt.Errorf(\"expected conn to succeed\")\n\t}\n\n\tselect {\n\tcase r := <-resch:\n\t\trequire.NoError(t, r.err)\n\tcase <-time.After(5 * time.Second):\n\t\tt.Errorf(\"expected conn to succeed\")\n\t}\n}\n\nfunc TestDialWorkerLoopAddrDedup(t *testing.T) {\n\ts1 := makeSwarm(t)\n\ts2 := makeSwarm(t)\n\tdefer s1.Close()\n\tdefer s2.Close()\n\tt1 := ma.StringCast(fmt.Sprintf(\"/ip4/127.0.0.1/tcp/%d\", 10000))\n\tt2 := ma.StringCast(fmt.Sprintf(\"/ip4/127.0.0.1/tcp/%d\", 10000))\n\n\t// acceptAndClose accepts a connection and closes it\n\tacceptAndClose := func(a ma.Multiaddr, ch chan struct{}, closech chan struct{}) {\n\t\tlist, err := manet.Listen(a)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\treturn\n\t\t}\n\t\tgo func() {\n\t\t\tch <- struct{}{}\n\t\t\tfor {\n\t\t\t\tconn, err := list.Accept()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tch <- struct{}{}\n\t\t\t\tconn.Close()\n\t\t\t}\n\t\t}()\n\t\t<-closech\n\t\tlist.Close()\n\t}\n\tch := make(chan struct{}, 1)\n\tcloseCh := make(chan struct{})\n\tgo acceptAndClose(t1, ch, closeCh)\n\tdefer close(closeCh)\n\t<-ch // the routine has started listening on addr\n\n\ts1.Peerstore().AddAddrs(s2.LocalPeer(), []ma.Multiaddr{t1}, peerstore.PermanentAddrTTL)\n\n\treqch := make(chan dialRequest)\n\tresch := make(chan dialResponse, 2)\n\n\tworker := newDialWorker(s1, s2.LocalPeer(), reqch, nil)\n\tgo worker.loop()\n\tdefer worker.wg.Wait()\n\tdefer close(reqch)\n\n\treqch <- dialRequest{ctx: context.Background(), resch: resch}\n\t<-ch\n\t<-resch\n\t// Need to clear backoff otherwise the dial attempt would not be made\n\ts1.Backoff().Clear(s2.LocalPeer())\n\n\ts1.Peerstore().ClearAddrs(s2.LocalPeer())\n\ts1.Peerstore().AddAddrs(s2.LocalPeer(), []ma.Multiaddr{t2}, peerstore.PermanentAddrTTL)\n\n\treqch <- dialRequest{ctx: context.Background(), resch: resch}\n\tselect {\n\tcase r := <-resch:\n\t\trequire.Error(t, r.err)\n\tcase <-ch:\n\t\tt.Errorf(\"didn't expect a connection attempt\")\n\tcase <-time.After(5 * time.Second):\n\t\tt.Errorf(\"expected a fail response\")\n\t}\n}\n\nfunc TestDialWorkerLoopTCPConnUpgradeWait(t *testing.T) {\n\ts1 := makeSwarmWithNoListenAddrs(t, WithDialTimeout(10*time.Second))\n\ts2 := makeSwarmWithNoListenAddrs(t, WithDialTimeout(10*time.Second))\n\tdefer s1.Close()\n\tdefer s2.Close()\n\t// Connection to a1 will fail but a1 is a public address so we can test waiting for tcp\n\t// connection established dial update. ipv4only.arpa reserved address.\n\ta1 := ma.StringCast(fmt.Sprintf(\"/ip4/192.0.0.170/tcp/%d\", 10001))\n\t// Connection to a2 will succeed.\n\ta2 := ma.StringCast(fmt.Sprintf(\"/ip4/127.0.0.1/tcp/%d\", 10002))\n\ts2.Listen(a2)\n\n\ts1.Peerstore().AddAddrs(s2.LocalPeer(), []ma.Multiaddr{a1, a2}, peerstore.PermanentAddrTTL)\n\n\trankerCalled := make(chan struct{})\n\ts1.dialRanker = func(_ []ma.Multiaddr) []network.AddrDelay {\n\t\tdefer close(rankerCalled)\n\t\treturn []network.AddrDelay{{Addr: a1, Delay: 0}, {Addr: a2, Delay: 100 * time.Millisecond}}\n\t}\n\n\treqch := make(chan dialRequest)\n\tresch := make(chan dialResponse, 2)\n\tcl := newMockClock()\n\tworker := newDialWorker(s1, s2.LocalPeer(), reqch, cl)\n\tgo worker.loop()\n\tdefer worker.wg.Wait()\n\tdefer close(reqch)\n\n\treqch <- dialRequest{ctx: context.Background(), resch: resch}\n\n\t<-rankerCalled\n\t// Wait a bit to let the loop make the dial attempt to a1\n\ttime.Sleep(1 * time.Second)\n\t// Send conn established for a1\n\tworker.resch <- transport.DialUpdate{Kind: transport.UpdateKindHandshakeProgressed, Addr: a1}\n\t// Dial to a2 shouldn't happen even if a2 is scheduled to dial by now\n\tcl.AdvanceBy(200 * time.Millisecond)\n\tselect {\n\tcase r := <-resch:\n\t\tt.Fatalf(\"didn't expect any event on resch %s %s\", r.err, r.conn)\n\tcase <-time.After(500 * time.Millisecond):\n\t}\n\n\t// Dial to a2 should happen now\n\t// This number is high because there's a race between this goroutine advancing the clock\n\t// and the worker loop goroutine processing the TCPConnectionEstablished event.\n\t// In case it processes the event after the previous clock advancement we need to wait\n\t// 2 * PublicTCPDelay.\n\tcl.AdvanceBy(2 * PublicTCPDelay)\n\tselect {\n\tcase r := <-resch:\n\t\trequire.NoError(t, r.err)\n\t\trequire.NotNil(t, r.conn)\n\tcase <-time.After(3 * time.Second):\n\t\tt.Errorf(\"expected a fail response\")\n\t}\n}\n\nfunc BenchmarkDialRanker(b *testing.B) {\n\tconst N = 10000\n\n\tbenchDialQueue := func(adelays []network.AddrDelay) {\n\t\tdq := newDialQueue()\n\t\tfor _, a := range adelays {\n\t\t\tdq.Add(a)\n\t\t}\n\t\tfor {\n\t\t\tbatch := dq.NextBatch()\n\t\t\tif len(batch) == 0 {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\taddrs := make([]ma.Multiaddr, N)\n\tfor i := range N {\n\t\taddrs[i] = ma.StringCast(fmt.Sprintf(\"/ip4/127.0.0.1/tcp/%d\", i))\n\t}\n\n\tb.Run(\"equal delay\", func(b *testing.B) {\n\t\tb.ReportAllocs()\n\t\taddrDelays := make([]network.AddrDelay, N)\n\t\tfor i := range N {\n\t\t\taddrDelays[i] = network.AddrDelay{\n\t\t\t\tAddr:  addrs[i],\n\t\t\t\tDelay: 0,\n\t\t\t}\n\t\t}\n\t\tfor i := 0; i < b.N; i++ {\n\t\t\tbenchDialQueue(addrDelays)\n\t\t}\n\t})\n\tb.Run(\"sorted delay\", func(b *testing.B) {\n\t\tb.ReportAllocs()\n\t\taddrDelays := make([]network.AddrDelay, N)\n\t\tfor i := range N {\n\t\t\taddrDelays[i] = network.AddrDelay{\n\t\t\t\tAddr:  addrs[i],\n\t\t\t\tDelay: time.Millisecond * time.Duration(i),\n\t\t\t}\n\t\t}\n\t\tfor i := 0; i < b.N; i++ {\n\t\t\tbenchDialQueue(addrDelays)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "p2p/net/swarm/limiter.go",
    "content": "package swarm\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"strconv\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/transport\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\ntype dialJob struct {\n\taddr    ma.Multiaddr\n\tpeer    peer.ID\n\tctx     context.Context\n\tresp    chan transport.DialUpdate\n\ttimeout time.Duration\n}\n\nfunc (dj *dialJob) cancelled() bool {\n\treturn dj.ctx.Err() != nil\n}\n\ntype dialLimiter struct {\n\tlk sync.Mutex\n\n\tfdConsuming int\n\tfdLimit     int\n\twaitingOnFd []*dialJob\n\n\tdialFunc dialfunc\n\n\tactivePerPeer      map[peer.ID]int\n\tperPeerLimit       int\n\twaitingOnPeerLimit map[peer.ID][]*dialJob\n}\n\ntype dialfunc func(context.Context, peer.ID, ma.Multiaddr, chan<- transport.DialUpdate) (transport.CapableConn, error)\n\nfunc newDialLimiter(df dialfunc) *dialLimiter {\n\tfd := ConcurrentFdDials\n\tif env := os.Getenv(\"LIBP2P_SWARM_FD_LIMIT\"); env != \"\" {\n\t\tif n, err := strconv.ParseInt(env, 10, 32); err == nil {\n\t\t\tfd = int(n)\n\t\t}\n\t}\n\treturn newDialLimiterWithParams(df, fd, DefaultPerPeerRateLimit)\n}\n\nfunc newDialLimiterWithParams(df dialfunc, fdLimit, perPeerLimit int) *dialLimiter {\n\treturn &dialLimiter{\n\t\tfdLimit:            fdLimit,\n\t\tperPeerLimit:       perPeerLimit,\n\t\twaitingOnPeerLimit: make(map[peer.ID][]*dialJob),\n\t\tactivePerPeer:      make(map[peer.ID]int),\n\t\tdialFunc:           df,\n\t}\n}\n\n// freeFDToken frees FD token and if there are any schedules another waiting dialJob\n// in it's place\nfunc (dl *dialLimiter) freeFDToken() {\n\tlog.Debug(\"[limiter] freeing FD token\", \"waiting\", len(dl.waitingOnFd), \"fd_consuming\", dl.fdConsuming)\n\tdl.fdConsuming--\n\n\tfor len(dl.waitingOnFd) > 0 {\n\t\tnext := dl.waitingOnFd[0]\n\t\tdl.waitingOnFd[0] = nil // clear out memory\n\t\tdl.waitingOnFd = dl.waitingOnFd[1:]\n\n\t\tif len(dl.waitingOnFd) == 0 {\n\t\t\t// clear out memory.\n\t\t\tdl.waitingOnFd = nil\n\t\t}\n\n\t\t// Skip over canceled dials instead of queuing up a goroutine.\n\t\tif next.cancelled() {\n\t\t\tdl.freePeerToken(next)\n\t\t\tcontinue\n\t\t}\n\t\tdl.fdConsuming++\n\n\t\t// we already have activePerPeer token at this point so we can just dial\n\t\tgo dl.executeDial(next)\n\t\treturn\n\t}\n}\n\nfunc (dl *dialLimiter) freePeerToken(dj *dialJob) {\n\tlog.Debug(\"[limiter] freeing peer token\",\n\t\t\"peer\", dj.peer,\n\t\t\"addr\", dj.addr,\n\t\t\"active_for_peer\", dl.activePerPeer[dj.peer],\n\t\t\"waiting_on_peer_limit\", len(dl.waitingOnPeerLimit[dj.peer]))\n\t// release tokens in reverse order than we take them\n\tdl.activePerPeer[dj.peer]--\n\tif dl.activePerPeer[dj.peer] == 0 {\n\t\tdelete(dl.activePerPeer, dj.peer)\n\t}\n\n\twaitlist := dl.waitingOnPeerLimit[dj.peer]\n\tfor len(waitlist) > 0 {\n\t\tnext := waitlist[0]\n\t\twaitlist[0] = nil // clear out memory\n\t\twaitlist = waitlist[1:]\n\n\t\tif len(waitlist) == 0 {\n\t\t\tdelete(dl.waitingOnPeerLimit, next.peer)\n\t\t} else {\n\t\t\tdl.waitingOnPeerLimit[next.peer] = waitlist\n\t\t}\n\n\t\tif next.cancelled() {\n\t\t\tcontinue\n\t\t}\n\n\t\tdl.activePerPeer[next.peer]++ // just kidding, we still want this token\n\n\t\tdl.addCheckFdLimit(next)\n\t\treturn\n\t}\n}\n\nfunc (dl *dialLimiter) finishedDial(dj *dialJob) {\n\tdl.lk.Lock()\n\tdefer dl.lk.Unlock()\n\tif dl.shouldConsumeFd(dj.addr) {\n\t\tdl.freeFDToken()\n\t}\n\n\tdl.freePeerToken(dj)\n}\n\nfunc (dl *dialLimiter) shouldConsumeFd(addr ma.Multiaddr) bool {\n\t// we don't consume FD's for relay addresses for now as they will be consumed when the Relay Transport\n\t// actually dials the Relay server. That dial call will also pass through this limiter with\n\t// the address of the relay server i.e. non-relay address.\n\t_, err := addr.ValueForProtocol(ma.P_CIRCUIT)\n\n\tisRelay := err == nil\n\n\treturn !isRelay && isFdConsumingAddr(addr)\n}\n\nfunc (dl *dialLimiter) addCheckFdLimit(dj *dialJob) {\n\tif dl.shouldConsumeFd(dj.addr) {\n\t\tif dl.fdConsuming >= dl.fdLimit {\n\t\t\tlog.Debug(\"[limiter] blocked dial waiting on FD token\",\n\t\t\t\t\"peer\", dj.peer,\n\t\t\t\t\"addr\", dj.addr,\n\t\t\t\t\"fd_consuming\", dl.fdConsuming,\n\t\t\t\t\"fd_limit\", dl.fdLimit,\n\t\t\t\t\"waiting\", len(dl.waitingOnFd))\n\t\t\tdl.waitingOnFd = append(dl.waitingOnFd, dj)\n\t\t\treturn\n\t\t}\n\n\t\tlog.Debug(\"[limiter] taking FD token\",\n\t\t\t\"peer\", dj.peer,\n\t\t\t\"addr\", dj.addr,\n\t\t\t\"prev_consuming\", dl.fdConsuming)\n\t\t// take token\n\t\tdl.fdConsuming++\n\t}\n\n\tlog.Debug(\"[limiter] executing dial\",\n\t\t\"peer\", dj.peer,\n\t\t\"addr\", dj.addr,\n\t\t\"fd_consuming\", dl.fdConsuming,\n\t\t\"waiting\", len(dl.waitingOnFd))\n\tgo dl.executeDial(dj)\n}\n\nfunc (dl *dialLimiter) addCheckPeerLimit(dj *dialJob) {\n\tif dl.activePerPeer[dj.peer] >= dl.perPeerLimit {\n\t\tlog.Debug(\"[limiter] blocked dial waiting on peer limit\",\n\t\t\t\"peer\", dj.peer,\n\t\t\t\"addr\", dj.addr,\n\t\t\t\"active\", dl.activePerPeer[dj.peer],\n\t\t\t\"peer_limit\", dl.perPeerLimit,\n\t\t\t\"waiting\", len(dl.waitingOnPeerLimit[dj.peer]))\n\t\twlist := dl.waitingOnPeerLimit[dj.peer]\n\t\tdl.waitingOnPeerLimit[dj.peer] = append(wlist, dj)\n\t\treturn\n\t}\n\tdl.activePerPeer[dj.peer]++\n\n\tdl.addCheckFdLimit(dj)\n}\n\n// AddDialJob tries to take the needed tokens for starting the given dial job.\n// If it acquires all needed tokens, it immediately starts the dial, otherwise\n// it will put it on the waitlist for the requested token.\nfunc (dl *dialLimiter) AddDialJob(dj *dialJob) {\n\tdl.lk.Lock()\n\tdefer dl.lk.Unlock()\n\n\tlog.Debug(\"[limiter] adding a dial job through limiter\", \"addr\", dj.addr)\n\tdl.addCheckPeerLimit(dj)\n}\n\nfunc (dl *dialLimiter) clearAllPeerDials(p peer.ID) {\n\tdl.lk.Lock()\n\tdefer dl.lk.Unlock()\n\tdelete(dl.waitingOnPeerLimit, p)\n\tlog.Debug(\"[limiter] clearing all peer dials\", \"peer\", p)\n\t// NB: the waitingOnFd list doesn't need to be cleaned out here, we will\n\t// remove them as we encounter them because they are 'cancelled' at this\n\t// point\n}\n\n// executeDial calls the dialFunc, and reports the result through the response\n// channel when finished. Once the response is sent it also releases all tokens\n// it held during the dial.\nfunc (dl *dialLimiter) executeDial(j *dialJob) {\n\tdefer dl.finishedDial(j)\n\tif j.cancelled() {\n\t\treturn\n\t}\n\n\tdctx, cancel := context.WithTimeout(j.ctx, j.timeout)\n\tdefer cancel()\n\n\tcon, err := dl.dialFunc(dctx, j.peer, j.addr, j.resp)\n\tkind := transport.UpdateKindDialSuccessful\n\tif err != nil {\n\t\tkind = transport.UpdateKindDialFailed\n\t}\n\tselect {\n\tcase j.resp <- transport.DialUpdate{Kind: kind, Conn: con, Addr: j.addr, Err: err}:\n\tcase <-j.ctx.Done():\n\t\tif con != nil {\n\t\t\tcon.Close()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "p2p/net/swarm/limiter_test.go",
    "content": "package swarm\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"strconv\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/test\"\n\t\"github.com/libp2p/go-libp2p/core/transport\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmafmt \"github.com/multiformats/go-multiaddr-fmt\"\n)\n\nfunc addrWithPort(p int) ma.Multiaddr {\n\treturn ma.StringCast(fmt.Sprintf(\"/ip4/127.0.0.1/tcp/%d\", p))\n}\n\n// in these tests I use addresses with tcp ports over a certain number to\n// signify 'good' addresses that will succeed, and addresses below that number\n// will fail. This lets us more easily test these different scenarios.\nfunc tcpPortOver(a ma.Multiaddr, n int) bool {\n\tport, err := a.ValueForProtocol(ma.P_TCP)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tpnum, err := strconv.Atoi(port)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn pnum > n\n}\n\nfunc tryDialAddrs(ctx context.Context, l *dialLimiter, p peer.ID, addrs []ma.Multiaddr, res chan transport.DialUpdate) {\n\tfor _, a := range addrs {\n\t\tl.AddDialJob(&dialJob{\n\t\t\tctx:  ctx,\n\t\t\tpeer: p,\n\t\t\taddr: a,\n\t\t\tresp: res,\n\t\t})\n\t}\n}\n\nfunc hangDialFunc(hang chan struct{}) dialfunc {\n\treturn func(_ context.Context, _ peer.ID, a ma.Multiaddr, _ chan<- transport.DialUpdate) (transport.CapableConn, error) {\n\t\tif mafmt.UTP.Matches(a) {\n\t\t\treturn transport.CapableConn(nil), nil\n\t\t}\n\n\t\t_, err := a.ValueForProtocol(ma.P_CIRCUIT)\n\t\tif err == nil {\n\t\t\treturn transport.CapableConn(nil), nil\n\t\t}\n\n\t\tif tcpPortOver(a, 10) {\n\t\t\treturn transport.CapableConn(nil), nil\n\t\t}\n\n\t\t<-hang\n\t\treturn nil, fmt.Errorf(\"test bad dial\")\n\t}\n}\n\nfunc TestLimiterBasicDials(t *testing.T) {\n\thang := make(chan struct{})\n\tdefer close(hang)\n\n\tl := newDialLimiterWithParams(hangDialFunc(hang), ConcurrentFdDials, 4)\n\n\tbads := []ma.Multiaddr{addrWithPort(1), addrWithPort(2), addrWithPort(3), addrWithPort(4)}\n\tgood := addrWithPort(20)\n\n\tresch := make(chan transport.DialUpdate)\n\tpid := peer.ID(\"testpeer\")\n\tctx := t.Context()\n\n\ttryDialAddrs(ctx, l, pid, bads, resch)\n\n\tl.AddDialJob(&dialJob{\n\t\tctx:  ctx,\n\t\tpeer: pid,\n\t\taddr: good,\n\t\tresp: resch,\n\t})\n\n\tselect {\n\tcase <-resch:\n\t\tt.Fatal(\"no dials should have completed!\")\n\tcase <-time.After(time.Millisecond * 100):\n\t}\n\n\t// complete a single hung dial\n\thang <- struct{}{}\n\n\tselect {\n\tcase r := <-resch:\n\t\tif r.Err == nil {\n\t\t\tt.Fatal(\"should have gotten failed dial result\")\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timed out waiting for dial completion\")\n\t}\n\n\tselect {\n\tcase r := <-resch:\n\t\tif r.Err != nil {\n\t\t\tt.Fatal(\"expected second result to be success!\")\n\t\t}\n\tcase <-time.After(time.Second):\n\t}\n}\n\nfunc TestFDLimiting(t *testing.T) {\n\thang := make(chan struct{})\n\tdefer close(hang)\n\tl := newDialLimiterWithParams(hangDialFunc(hang), 16, 5)\n\n\tbads := []ma.Multiaddr{addrWithPort(1), addrWithPort(2), addrWithPort(3), addrWithPort(4)}\n\tpids := []peer.ID{\"testpeer1\", \"testpeer2\", \"testpeer3\", \"testpeer4\"}\n\tgoodTCP := addrWithPort(20)\n\n\tctx := context.Background()\n\tresch := make(chan transport.DialUpdate)\n\n\t// take all fd limit tokens with hang dials\n\tfor _, pid := range pids {\n\t\ttryDialAddrs(ctx, l, pid, bads, resch)\n\t}\n\n\t// these dials should work normally, but will hang because we have taken\n\t// up all the fd limiting\n\tfor _, pid := range pids {\n\t\tl.AddDialJob(&dialJob{\n\t\t\tctx:  ctx,\n\t\t\tpeer: pid,\n\t\t\taddr: goodTCP,\n\t\t\tresp: resch,\n\t\t})\n\t}\n\n\tselect {\n\tcase <-resch:\n\t\tt.Fatal(\"no dials should have completed!\")\n\tcase <-time.After(time.Millisecond * 100):\n\t}\n\n\tpid5 := peer.ID(\"testpeer5\")\n\tutpaddr := ma.StringCast(\"/ip4/127.0.0.1/udp/7777/utp\")\n\n\t// This should complete immediately since utp addresses arent blocked by fd rate limiting\n\tl.AddDialJob(&dialJob{ctx: ctx, peer: pid5, addr: utpaddr, resp: resch})\n\n\tselect {\n\tcase res := <-resch:\n\t\tif res.Err != nil {\n\t\t\tt.Fatal(\"should have gotten successful response\")\n\t\t}\n\tcase <-time.After(time.Second * 5):\n\t\tt.Fatal(\"timeout waiting for utp addr success\")\n\t}\n\n\t// A relay address with tcp transport will complete because we do not consume fds for dials\n\t// with relay addresses as the fd will be consumed when we actually dial the relay server.\n\tpid6 := test.RandPeerIDFatal(t)\n\trelayAddr := ma.StringCast(fmt.Sprintf(\"/ip4/127.0.0.1/tcp/20/p2p-circuit/p2p/%s\", pid6))\n\tl.AddDialJob(&dialJob{ctx: ctx, peer: pid6, addr: relayAddr, resp: resch})\n\n\tselect {\n\tcase res := <-resch:\n\t\tif res.Err != nil {\n\t\t\tt.Fatal(\"should have gotten successful response\")\n\t\t}\n\tcase <-time.After(time.Second * 5):\n\t\tt.Fatal(\"timeout waiting for relay addr success\")\n\t}\n}\n\nfunc TestTokenRedistribution(t *testing.T) {\n\tvar lk sync.Mutex\n\thangchs := make(map[peer.ID]chan struct{})\n\tdf := func(_ context.Context, p peer.ID, a ma.Multiaddr, _ chan<- transport.DialUpdate) (transport.CapableConn, error) {\n\t\tif tcpPortOver(a, 10) {\n\t\t\treturn (transport.CapableConn)(nil), nil\n\t\t}\n\n\t\tlk.Lock()\n\t\tch := hangchs[p]\n\t\tlk.Unlock()\n\t\t<-ch\n\t\treturn nil, fmt.Errorf(\"test bad dial\")\n\t}\n\tl := newDialLimiterWithParams(df, 8, 4)\n\n\tbads := []ma.Multiaddr{addrWithPort(1), addrWithPort(2), addrWithPort(3), addrWithPort(4)}\n\tpids := []peer.ID{\"testpeer1\", \"testpeer2\"}\n\n\tctx := context.Background()\n\tresch := make(chan transport.DialUpdate)\n\n\t// take all fd limit tokens with hang dials\n\tfor _, pid := range pids {\n\t\thangchs[pid] = make(chan struct{})\n\t}\n\n\tfor _, pid := range pids {\n\t\ttryDialAddrs(ctx, l, pid, bads, resch)\n\t}\n\n\t// add a good dial job for peer 1\n\tl.AddDialJob(&dialJob{\n\t\tctx:  ctx,\n\t\tpeer: pids[1],\n\t\taddr: ma.StringCast(\"/ip4/127.0.0.1/tcp/1001\"),\n\t\tresp: resch,\n\t})\n\n\tselect {\n\tcase <-resch:\n\t\tt.Fatal(\"no dials should have completed!\")\n\tcase <-time.After(time.Millisecond * 100):\n\t}\n\n\t// unblock one dial for peer 0\n\thangchs[pids[0]] <- struct{}{}\n\n\tselect {\n\tcase res := <-resch:\n\t\tif res.Err == nil {\n\t\t\tt.Fatal(\"should have only been a failure here\")\n\t\t}\n\tcase <-time.After(time.Millisecond * 100):\n\t\tt.Fatal(\"expected a dial failure here\")\n\t}\n\n\tselect {\n\tcase <-resch:\n\t\tt.Fatal(\"no more dials should have completed!\")\n\tcase <-time.After(time.Millisecond * 100):\n\t}\n\n\t// add a bad dial job to peer 0 to fill their rate limiter\n\t// and test that more dials for this peer won't interfere with peer 1's successful dial incoming\n\tl.AddDialJob(&dialJob{\n\t\tctx:  ctx,\n\t\tpeer: pids[0],\n\t\taddr: addrWithPort(7),\n\t\tresp: resch,\n\t})\n\n\thangchs[pids[1]] <- struct{}{}\n\n\t// now one failed dial from peer 1 should get through and fail\n\t// which will in turn unblock the successful dial on peer 1\n\tselect {\n\tcase res := <-resch:\n\t\tif res.Err == nil {\n\t\t\tt.Fatal(\"should have only been a failure here\")\n\t\t}\n\tcase <-time.After(time.Millisecond * 100):\n\t\tt.Fatal(\"expected a dial failure here\")\n\t}\n\n\tselect {\n\tcase res := <-resch:\n\t\tif res.Err != nil {\n\t\t\tt.Fatal(\"should have succeeded!\")\n\t\t}\n\tcase <-time.After(time.Millisecond * 100):\n\t\tt.Fatal(\"should have gotten successful dial\")\n\t}\n}\n\nfunc TestStressLimiter(t *testing.T) {\n\tdf := func(_ context.Context, _ peer.ID, a ma.Multiaddr, _ chan<- transport.DialUpdate) (transport.CapableConn, error) {\n\t\tif tcpPortOver(a, 1000) {\n\t\t\treturn transport.CapableConn(nil), nil\n\t\t}\n\n\t\ttime.Sleep(time.Millisecond * time.Duration(5+rand.Intn(100)))\n\t\treturn nil, fmt.Errorf(\"test bad dial\")\n\t}\n\n\tl := newDialLimiterWithParams(df, 20, 5)\n\n\tbads := make([]ma.Multiaddr, 0, 101)\n\tfor i := range 100 {\n\t\tbads = append(bads, addrWithPort(i))\n\t}\n\n\taddresses := append(bads, addrWithPort(2000))\n\tsuccess := make(chan struct{})\n\n\tfor i := range 20 {\n\t\tgo func(id peer.ID) {\n\t\t\tctx, cancel := context.WithCancel(context.Background())\n\t\t\tdefer cancel()\n\n\t\t\tresp := make(chan transport.DialUpdate)\n\t\t\ttime.Sleep(time.Duration(rand.Intn(10)) * time.Millisecond)\n\t\t\tfor _, i := range rand.Perm(len(addresses)) {\n\t\t\t\tl.AddDialJob(&dialJob{\n\t\t\t\t\taddr: addresses[i],\n\t\t\t\t\tctx:  ctx,\n\t\t\t\t\tpeer: id,\n\t\t\t\t\tresp: resp,\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tfor res := range resp {\n\t\t\t\tif res.Err == nil {\n\t\t\t\t\tsuccess <- struct{}{}\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}(peer.ID(fmt.Sprintf(\"testpeer%d\", i)))\n\t}\n\n\tfor range 20 {\n\t\tselect {\n\t\tcase <-success:\n\t\tcase <-time.After(time.Minute):\n\t\t\tt.Fatal(\"expected a success within five seconds\")\n\t\t}\n\t}\n}\n\nfunc TestFDLimitUnderflow(t *testing.T) {\n\tdf := func(ctx context.Context, _ peer.ID, _ ma.Multiaddr, _ chan<- transport.DialUpdate) (transport.CapableConn, error) {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\tcase <-time.After(5 * time.Second):\n\t\t}\n\t\treturn nil, fmt.Errorf(\"df timed out\")\n\t}\n\n\tconst fdLimit = 20\n\tl := newDialLimiterWithParams(df, fdLimit, 3)\n\n\tvar addrs []ma.Multiaddr\n\tfor i := 0; i <= 1000; i++ {\n\t\taddrs = append(addrs, addrWithPort(i))\n\t}\n\n\twg := sync.WaitGroup{}\n\tconst num = 3 * fdLimit\n\twg.Add(num)\n\terrs := make(chan error, num)\n\tfor i := range num {\n\t\tgo func(id peer.ID, i int) {\n\t\t\tdefer wg.Done()\n\t\t\tctx, cancel := context.WithCancel(context.Background())\n\t\t\tdefer cancel()\n\n\t\t\tresp := make(chan transport.DialUpdate)\n\t\t\tl.AddDialJob(&dialJob{\n\t\t\t\taddr: addrs[i],\n\t\t\t\tctx:  ctx,\n\t\t\t\tpeer: id,\n\t\t\t\tresp: resp,\n\t\t\t})\n\n\t\t\tfor res := range resp {\n\t\t\t\tif res.Err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\terrs <- errors.New(\"got dial res, but shouldn't\")\n\t\t\t}\n\t\t}(peer.ID(fmt.Sprintf(\"testpeer%d\", i%20)), i)\n\t}\n\n\tgo func() {\n\t\twg.Wait()\n\t\tclose(errs)\n\t}()\n\n\tfor err := range errs {\n\t\tt.Fatal(err)\n\t}\n\n\tl.lk.Lock()\n\tfdConsuming := l.fdConsuming\n\tl.lk.Unlock()\n\n\tif fdConsuming < 0 {\n\t\tt.Fatalf(\"l.fdConsuming < 0\")\n\t}\n}\n"
  },
  {
    "path": "p2p/net/swarm/peers_test.go",
    "content": "package swarm_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\t. \"github.com/libp2p/go-libp2p/p2p/net/swarm\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestPeers(t *testing.T) {\n\tctx := context.Background()\n\tswarms := makeSwarms(t, 2)\n\ts1 := swarms[0]\n\ts2 := swarms[1]\n\n\tconnect := func(s *Swarm, dst peer.ID, addr ma.Multiaddr) {\n\t\t// TODO: make a DialAddr func.\n\t\ts.Peerstore().AddAddr(dst, addr, peerstore.PermanentAddrTTL)\n\t\t// t.Logf(\"connections from %s\", s.LocalPeer())\n\t\t// for _, c := range s.ConnsToPeer(dst) {\n\t\t// \tt.Logf(\"connection from %s to %s: %v\", s.LocalPeer(), dst, c)\n\t\t// }\n\t\t// t.Logf(\"\")\n\t\tif _, err := s.DialPeer(ctx, dst); err != nil {\n\t\t\tt.Fatal(\"error swarm dialing to peer\", err)\n\t\t}\n\t\t// t.Log(s.swarm.Dump())\n\t}\n\n\tconnect(s1, s2.LocalPeer(), s2.ListenAddresses()[0])\n\trequire.Eventually(t, func() bool { return len(s2.Peers()) > 0 }, 3*time.Second, 50*time.Millisecond)\n\tconnect(s2, s1.LocalPeer(), s1.ListenAddresses()[0])\n\n\tfor range 100 {\n\t\tconnect(s1, s2.LocalPeer(), s2.ListenAddresses()[0])\n\t\tconnect(s2, s1.LocalPeer(), s1.ListenAddresses()[0])\n\t}\n\n\tfor _, s := range swarms {\n\t\tlog.Info(\"swarm routing table\", \"peer\", s.LocalPeer(), \"peers\", s.Peers())\n\t}\n\n\ttest := func(s *Swarm) {\n\t\texpect := 1\n\t\tactual := len(s.Peers())\n\t\tif actual != expect {\n\t\t\tt.Errorf(\"%s has %d peers, not %d: %v\", s.LocalPeer(), actual, expect, s.Peers())\n\t\t}\n\t\tactual = len(s.Conns())\n\t\tif actual != expect {\n\t\t\tt.Errorf(\"%s has %d conns, not %d: %v\", s.LocalPeer(), actual, expect, s.Conns())\n\t\t}\n\t}\n\n\ttest(s1)\n\ttest(s2)\n}\n"
  },
  {
    "path": "p2p/net/swarm/resolve_test.go",
    "content": "package swarm\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"strconv\"\n\t\"testing\"\n\n\t\"github.com/multiformats/go-multiaddr\"\n\tmadns \"github.com/multiformats/go-multiaddr-dns\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSwarmResolver(t *testing.T) {\n\tmockResolver := madns.MockResolver{IP: make(map[string][]net.IPAddr)}\n\tipaddr, err := net.ResolveIPAddr(\"ip4\", \"127.0.0.1\")\n\trequire.NoError(t, err)\n\tmockResolver.IP[\"example.com\"] = []net.IPAddr{*ipaddr}\n\tmockResolver.TXT = map[string][]string{\n\t\t\"_dnsaddr.example.com\": {\"dnsaddr=/ip4/127.0.0.1\"},\n\t}\n\tmadnsResolver, err := madns.NewResolver(madns.WithDomainResolver(\"example.com\", &mockResolver))\n\trequire.NoError(t, err)\n\tswarmResolver := ResolverFromMaDNS{madnsResolver}\n\n\tctx := context.Background()\n\tres, err := swarmResolver.ResolveDNSComponent(ctx, multiaddr.StringCast(\"/dns/example.com\"), 10)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 1, len(res))\n\trequire.Equal(t, \"/ip4/127.0.0.1\", res[0].String())\n\n\tres, err = swarmResolver.ResolveDNSAddr(ctx, \"\", multiaddr.StringCast(\"/dnsaddr/example.com\"), 1, 10)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 1, len(res))\n\trequire.Equal(t, \"/ip4/127.0.0.1\", res[0].String())\n\n\tt.Run(\"Test Limits\", func(t *testing.T) {\n\t\tvar ipaddrs []net.IPAddr\n\t\tvar manyDNSAddrs []string\n\t\tfor i := range 255 {\n\t\t\tip := \"1.2.3.\" + strconv.Itoa(i)\n\t\t\tipaddrs = append(ipaddrs, net.IPAddr{IP: net.ParseIP(ip)})\n\t\t\tmanyDNSAddrs = append(manyDNSAddrs, \"dnsaddr=/ip4/\"+ip)\n\t\t}\n\n\t\tmockResolver.IP = map[string][]net.IPAddr{\n\t\t\t\"example.com\": ipaddrs,\n\t\t}\n\t\tmockResolver.TXT = map[string][]string{\n\t\t\t\"_dnsaddr.example.com\": manyDNSAddrs,\n\t\t}\n\n\t\tres, err := swarmResolver.ResolveDNSComponent(ctx, multiaddr.StringCast(\"/dns/example.com\"), 10)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 10, len(res))\n\t\tfor i := range 10 {\n\t\t\trequire.Equal(t, \"/ip4/1.2.3.\"+strconv.Itoa(i), res[i].String())\n\t\t}\n\n\t\tres, err = swarmResolver.ResolveDNSAddr(ctx, \"\", multiaddr.StringCast(\"/dnsaddr/example.com\"), 1, 10)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 10, len(res))\n\t\tfor i := range 10 {\n\t\t\trequire.Equal(t, \"/ip4/1.2.3.\"+strconv.Itoa(i), res[i].String())\n\t\t}\n\t})\n\n\tt.Run(\"Test Recursive Limits\", func(t *testing.T) {\n\t\trecursiveDNSAddr := make(map[string][]string)\n\t\tfor i := range 255 {\n\t\t\trecursiveDNSAddr[\"_dnsaddr.\"+strconv.Itoa(i)+\".example.com\"] = []string{\"dnsaddr=/dnsaddr/\" + strconv.Itoa(i+1) + \".example.com\"}\n\t\t}\n\t\trecursiveDNSAddr[\"_dnsaddr.255.example.com\"] = []string{\"dnsaddr=/ip4/127.0.0.1\"}\n\t\tmockResolver.TXT = recursiveDNSAddr\n\n\t\tres, err = swarmResolver.ResolveDNSAddr(ctx, \"\", multiaddr.StringCast(\"/dnsaddr/0.example.com\"), 256, 10)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 1, len(res))\n\t\trequire.Equal(t, \"/ip4/127.0.0.1\", res[0].String())\n\n\t\tres, err = swarmResolver.ResolveDNSAddr(ctx, \"\", multiaddr.StringCast(\"/dnsaddr/0.example.com\"), 255, 10)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 1, len(res))\n\t\trequire.Equal(t, \"/dnsaddr/255.example.com\", res[0].String())\n\t})\n\n\tt.Run(\"Test Resolve at output limit\", func(t *testing.T) {\n\t\trecursiveDNSAddr := make(map[string][]string)\n\t\trecursiveDNSAddr[\"_dnsaddr.example.com\"] = []string{\n\t\t\t\"dnsaddr=/dnsaddr/0.example.com\",\n\t\t\t\"dnsaddr=/dnsaddr/1.example.com\",\n\t\t\t\"dnsaddr=/dnsaddr/2.example.com\",\n\t\t\t\"dnsaddr=/dnsaddr/3.example.com\",\n\t\t\t\"dnsaddr=/dnsaddr/4.example.com\",\n\t\t\t\"dnsaddr=/dnsaddr/5.example.com\",\n\t\t\t\"dnsaddr=/dnsaddr/6.example.com\",\n\t\t\t\"dnsaddr=/dnsaddr/7.example.com\",\n\t\t\t\"dnsaddr=/dnsaddr/8.example.com\",\n\t\t\t\"dnsaddr=/dnsaddr/9.example.com\",\n\t\t}\n\t\trecursiveDNSAddr[\"_dnsaddr.0.example.com\"] = []string{\"dnsaddr=/ip4/127.0.0.1\"}\n\t\trecursiveDNSAddr[\"_dnsaddr.1.example.com\"] = []string{\"dnsaddr=/ip4/127.0.0.1\"}\n\t\trecursiveDNSAddr[\"_dnsaddr.2.example.com\"] = []string{\"dnsaddr=/ip4/127.0.0.1\"}\n\t\trecursiveDNSAddr[\"_dnsaddr.3.example.com\"] = []string{\"dnsaddr=/ip4/127.0.0.1\"}\n\t\trecursiveDNSAddr[\"_dnsaddr.4.example.com\"] = []string{\"dnsaddr=/ip4/127.0.0.1\"}\n\t\trecursiveDNSAddr[\"_dnsaddr.5.example.com\"] = []string{\"dnsaddr=/ip4/127.0.0.1\"}\n\t\trecursiveDNSAddr[\"_dnsaddr.6.example.com\"] = []string{\"dnsaddr=/ip4/127.0.0.1\"}\n\t\trecursiveDNSAddr[\"_dnsaddr.7.example.com\"] = []string{\"dnsaddr=/ip4/127.0.0.1\"}\n\t\trecursiveDNSAddr[\"_dnsaddr.8.example.com\"] = []string{\"dnsaddr=/ip4/127.0.0.1\"}\n\t\trecursiveDNSAddr[\"_dnsaddr.9.example.com\"] = []string{\"dnsaddr=/ip4/127.0.0.1\"}\n\t\tmockResolver.TXT = recursiveDNSAddr\n\n\t\tres, err = swarmResolver.ResolveDNSAddr(ctx, \"\", multiaddr.StringCast(\"/dnsaddr/example.com\"), 256, 10)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 10, len(res))\n\t\tfor _, r := range res {\n\t\t\trequire.Equal(t, \"/ip4/127.0.0.1\", r.String())\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "p2p/net/swarm/simul_test.go",
    "content": "package swarm_test\n\nimport (\n\t\"context\"\n\t\"runtime\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\t. \"github.com/libp2p/go-libp2p/p2p/net/swarm\"\n\tswarmt \"github.com/libp2p/go-libp2p/p2p/net/swarm/testing\"\n\n\t\"github.com/libp2p/go-libp2p-testing/ci\"\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\nfunc TestSimultOpen(t *testing.T) {\n\tt.Parallel()\n\tswarms := makeSwarms(t, 2, swarmt.OptDisableReuseport)\n\n\t// connect everyone\n\t{\n\t\tvar wg sync.WaitGroup\n\t\tconnect := func(s *Swarm, dst peer.ID, addr ma.Multiaddr) {\n\t\t\tdefer wg.Done()\n\t\t\t// copy for other peer\n\t\t\tlog.Debug(\"TestSimultOpen: connecting\", \"local\", s.LocalPeer(), \"remote\", dst, \"addr\", addr)\n\t\t\ts.Peerstore().AddAddr(dst, addr, peerstore.PermanentAddrTTL)\n\t\t\tif _, err := s.DialPeer(context.Background(), dst); err != nil {\n\t\t\t\tt.Error(\"error swarm dialing to peer\", err)\n\t\t\t}\n\t\t}\n\n\t\tlog.Info(\"Connecting swarms simultaneously.\")\n\t\twg.Add(2)\n\t\tgo connect(swarms[0], swarms[1].LocalPeer(), swarms[1].ListenAddresses()[0])\n\t\tgo connect(swarms[1], swarms[0].LocalPeer(), swarms[0].ListenAddresses()[0])\n\t\twg.Wait()\n\t}\n\n\tfor _, s := range swarms {\n\t\ts.Close()\n\t}\n}\n\nfunc TestSimultOpenMany(t *testing.T) {\n\t// t.Skip(\"very very slow\")\n\n\taddrs := 20\n\trounds := 10\n\tif ci.IsRunning() || runtime.GOOS == \"darwin\" {\n\t\t// osx has a limit of 256 file descriptors\n\t\taddrs = 10\n\t\trounds = 5\n\t}\n\tsubtestSwarm(t, addrs, rounds)\n}\n\nfunc TestSimultOpenFewStress(t *testing.T) {\n\tif testing.Short() {\n\t\tt.SkipNow()\n\t}\n\t// t.Skip(\"skipping for another test\")\n\tt.Parallel()\n\n\tmsgs := 40\n\tswarms := 2\n\trounds := 10\n\t// rounds := 100\n\n\tfor range rounds {\n\t\tsubtestSwarm(t, swarms, msgs)\n\t\t<-time.After(10 * time.Millisecond)\n\t}\n}\n"
  },
  {
    "path": "p2p/net/swarm/swarm.go",
    "content": "package swarm\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"slices\"\n\n\t\"github.com/libp2p/go-libp2p/core/connmgr\"\n\t\"github.com/libp2p/go-libp2p/core/event\"\n\t\"github.com/libp2p/go-libp2p/core/metrics\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\t\"github.com/libp2p/go-libp2p/core/transport\"\n\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmadns \"github.com/multiformats/go-multiaddr-dns\"\n)\n\nconst (\n\tdefaultDialTimeout = 15 * time.Second\n\n\t// defaultDialTimeoutLocal is the maximum duration a Dial to local network address\n\t// is allowed to take.\n\t// This includes the time between dialing the raw network connection,\n\t// protocol selection as well the handshake, if applicable.\n\tdefaultDialTimeoutLocal = 5 * time.Second\n\n\tdefaultNewStreamTimeout = 15 * time.Second\n)\n\nvar log = logging.Logger(\"swarm2\")\n\n// ErrSwarmClosed is returned when one attempts to operate on a closed swarm.\nvar ErrSwarmClosed = errors.New(\"swarm closed\")\n\n// ErrAddrFiltered is returned when trying to register a connection to a\n// filtered address. You shouldn't see this error unless some underlying\n// transport is misbehaving.\nvar ErrAddrFiltered = errors.New(\"address filtered\")\n\n// ErrDialTimeout is returned when one a dial times out due to the global timeout\nvar ErrDialTimeout = errors.New(\"dial timed out\")\n\ntype Option func(*Swarm) error\n\n// WithConnectionGater sets a connection gater\nfunc WithConnectionGater(gater connmgr.ConnectionGater) Option {\n\treturn func(s *Swarm) error {\n\t\ts.gater = gater\n\t\treturn nil\n\t}\n}\n\n// WithMultiaddrResolver sets a custom multiaddress resolver\nfunc WithMultiaddrResolver(resolver network.MultiaddrDNSResolver) Option {\n\treturn func(s *Swarm) error {\n\t\ts.multiaddrResolver = resolver\n\t\treturn nil\n\t}\n}\n\n// WithMetrics sets a metrics reporter\nfunc WithMetrics(reporter metrics.Reporter) Option {\n\treturn func(s *Swarm) error {\n\t\ts.bwc = reporter\n\t\treturn nil\n\t}\n}\n\nfunc WithMetricsTracer(t MetricsTracer) Option {\n\treturn func(s *Swarm) error {\n\t\ts.metricsTracer = t\n\t\treturn nil\n\t}\n}\n\nfunc WithDialTimeout(t time.Duration) Option {\n\treturn func(s *Swarm) error {\n\t\ts.dialTimeout = t\n\t\treturn nil\n\t}\n}\n\nfunc WithDialTimeoutLocal(t time.Duration) Option {\n\treturn func(s *Swarm) error {\n\t\ts.dialTimeoutLocal = t\n\t\treturn nil\n\t}\n}\n\nfunc WithResourceManager(m network.ResourceManager) Option {\n\treturn func(s *Swarm) error {\n\t\ts.rcmgr = m\n\t\treturn nil\n\t}\n}\n\n// WithDialRanker configures swarm to use d as the DialRanker\nfunc WithDialRanker(d network.DialRanker) Option {\n\treturn func(s *Swarm) error {\n\t\tif d == nil {\n\t\t\treturn errors.New(\"swarm: dial ranker cannot be nil\")\n\t\t}\n\t\ts.dialRanker = d\n\t\treturn nil\n\t}\n}\n\n// WithUDPBlackHoleSuccessCounter configures swarm to use the provided config for UDP black hole detection\n// n is the size of the sliding window used to evaluate black hole state\n// min is the minimum number of successes out of n required to not block requests\nfunc WithUDPBlackHoleSuccessCounter(f *BlackHoleSuccessCounter) Option {\n\treturn func(s *Swarm) error {\n\t\ts.udpBHF = f\n\t\treturn nil\n\t}\n}\n\n// WithIPv6BlackHoleSuccessCounter configures swarm to use the provided config for IPv6 black hole detection\n// n is the size of the sliding window used to evaluate black hole state\n// min is the minimum number of successes out of n required to not block requests\nfunc WithIPv6BlackHoleSuccessCounter(f *BlackHoleSuccessCounter) Option {\n\treturn func(s *Swarm) error {\n\t\ts.ipv6BHF = f\n\t\treturn nil\n\t}\n}\n\n// WithReadOnlyBlackHoleDetector configures the swarm to use the black hole detector in\n// read only mode. In Read Only mode dial requests are refused in unknown state and\n// no updates to the detector state are made. This is useful for services like AutoNAT that\n// care about accurately providing reachability info.\nfunc WithReadOnlyBlackHoleDetector() Option {\n\treturn func(s *Swarm) error {\n\t\ts.readOnlyBHD = true\n\t\treturn nil\n\t}\n}\n\n// Swarm is a connection muxer, allowing connections to other peers to\n// be opened and closed, while still using the same Chan for all\n// communication. The Chan sends/receives Messages, which note the\n// destination or source Peer.\ntype Swarm struct {\n\tnextConnID   atomic.Uint64\n\tnextStreamID atomic.Uint64\n\n\t// Close refcount. This allows us to fully wait for the swarm to be torn\n\t// down before continuing.\n\trefs sync.WaitGroup\n\n\temitter event.Emitter\n\n\trcmgr network.ResourceManager\n\n\tlocal peer.ID\n\tpeers peerstore.Peerstore\n\n\tdialTimeout      time.Duration\n\tdialTimeoutLocal time.Duration\n\n\tconns struct {\n\t\tsync.RWMutex\n\t\tm map[peer.ID][]*Conn\n\t}\n\n\tlisteners struct {\n\t\tsync.RWMutex\n\n\t\tifaceListenAddres []ma.Multiaddr\n\t\tcacheEOL          time.Time\n\n\t\tm map[transport.Listener]struct{}\n\t}\n\n\tnotifs struct {\n\t\tsync.RWMutex\n\t\tm map[network.Notifiee]struct{}\n\t}\n\n\tdirectConnNotifs struct {\n\t\tsync.Mutex\n\t\tm map[peer.ID][]chan struct{}\n\t}\n\n\ttransports struct {\n\t\tsync.RWMutex\n\t\tm map[int]transport.Transport\n\t}\n\n\tmultiaddrResolver network.MultiaddrDNSResolver\n\n\t// stream handlers\n\tstreamh atomic.Pointer[network.StreamHandler]\n\n\t// dialing helpers\n\tdsync   *dialSync\n\tbackf   DialBackoff\n\tlimiter *dialLimiter\n\tgater   connmgr.ConnectionGater\n\n\tcloseOnce sync.Once\n\tctx       context.Context // is canceled when Close is called\n\tctxCancel context.CancelFunc\n\n\tbwc           metrics.Reporter\n\tmetricsTracer MetricsTracer\n\n\tdialRanker network.DialRanker\n\n\tconnectednessEventEmitter *connectednessEventEmitter\n\tudpBHF                    *BlackHoleSuccessCounter\n\tipv6BHF                   *BlackHoleSuccessCounter\n\tbhd                       *blackHoleDetector\n\treadOnlyBHD               bool\n}\n\n// NewSwarm constructs a Swarm.\nfunc NewSwarm(local peer.ID, peers peerstore.Peerstore, eventBus event.Bus, opts ...Option) (*Swarm, error) {\n\temitter, err := eventBus.Emitter(new(event.EvtPeerConnectednessChanged))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tctx, cancel := context.WithCancel(context.Background())\n\ts := &Swarm{\n\t\tlocal:             local,\n\t\tpeers:             peers,\n\t\temitter:           emitter,\n\t\tctx:               ctx,\n\t\tctxCancel:         cancel,\n\t\tdialTimeout:       defaultDialTimeout,\n\t\tdialTimeoutLocal:  defaultDialTimeoutLocal,\n\t\tmultiaddrResolver: ResolverFromMaDNS{madns.DefaultResolver},\n\t\tdialRanker:        DefaultDialRanker,\n\n\t\t// A black hole is a binary property. On a network if UDP dials are blocked or there is\n\t\t// no IPv6 connectivity, all dials will fail. So a low success rate of 5 out 100 dials\n\t\t// is good enough.\n\t\tudpBHF:  &BlackHoleSuccessCounter{N: 100, MinSuccesses: 5, Name: \"UDP\"},\n\t\tipv6BHF: &BlackHoleSuccessCounter{N: 100, MinSuccesses: 5, Name: \"IPv6\"},\n\t}\n\n\ts.conns.m = make(map[peer.ID][]*Conn)\n\ts.listeners.m = make(map[transport.Listener]struct{})\n\ts.transports.m = make(map[int]transport.Transport)\n\ts.notifs.m = make(map[network.Notifiee]struct{})\n\ts.directConnNotifs.m = make(map[peer.ID][]chan struct{})\n\ts.connectednessEventEmitter = newConnectednessEventEmitter(s.Connectedness, emitter)\n\n\tfor _, opt := range opts {\n\t\tif err := opt(s); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tif s.rcmgr == nil {\n\t\ts.rcmgr = &network.NullResourceManager{}\n\t}\n\n\ts.dsync = newDialSync(s.dialWorkerLoop)\n\n\ts.limiter = newDialLimiter(s.dialAddr)\n\ts.backf.init(s.ctx)\n\n\ts.bhd = &blackHoleDetector{\n\t\tudp:      s.udpBHF,\n\t\tipv6:     s.ipv6BHF,\n\t\tmt:       s.metricsTracer,\n\t\treadOnly: s.readOnlyBHD,\n\t}\n\treturn s, nil\n}\n\nfunc (s *Swarm) Close() error {\n\ts.closeOnce.Do(s.close)\n\treturn nil\n}\n\n// Done returns a channel that is closed when the swarm is closed.\nfunc (s *Swarm) Done() <-chan struct{} {\n\treturn s.ctx.Done()\n}\n\nfunc (s *Swarm) close() {\n\ts.ctxCancel()\n\n\t// Prevents new connections and/or listeners from being added to the swarm.\n\ts.listeners.Lock()\n\tlisteners := s.listeners.m\n\ts.listeners.m = nil\n\ts.listeners.Unlock()\n\n\ts.conns.Lock()\n\tconns := s.conns.m\n\ts.conns.m = nil\n\ts.conns.Unlock()\n\n\t// Lots of goroutines but we might as well do this in parallel. We want to shut down as fast as\n\t// possible.\n\ts.refs.Add(len(listeners))\n\tfor l := range listeners {\n\t\tgo func(l transport.Listener) {\n\t\t\tdefer s.refs.Done()\n\t\t\tif err := l.Close(); err != nil && err != transport.ErrListenerClosed {\n\t\t\t\tlog.Error(\"error when shutting down listener\", \"err\", err)\n\t\t\t}\n\t\t}(l)\n\t}\n\n\tfor _, cs := range conns {\n\t\tfor _, c := range cs {\n\t\t\tgo func(c *Conn) {\n\t\t\t\tif err := c.Close(); err != nil {\n\t\t\t\t\tlog.Error(\"error when shutting down connection\", \"err\", err)\n\t\t\t\t}\n\t\t\t}(c)\n\t\t}\n\t}\n\n\t// Wait for everything to finish.\n\ts.refs.Wait()\n\ts.connectednessEventEmitter.Close()\n\ts.emitter.Close()\n\n\t// Now close out any transports (if necessary). Do this after closing\n\t// all connections/listeners.\n\ts.transports.Lock()\n\ttransports := s.transports.m\n\ts.transports.m = nil\n\ts.transports.Unlock()\n\n\t// Dedup transports that may be listening on multiple protocols\n\ttransportsToClose := make(map[transport.Transport]struct{}, len(transports))\n\tfor _, t := range transports {\n\t\ttransportsToClose[t] = struct{}{}\n\t}\n\n\tvar wg sync.WaitGroup\n\tfor t := range transportsToClose {\n\t\tif closer, ok := t.(io.Closer); ok {\n\t\t\twg.Add(1)\n\t\t\tgo func(c io.Closer) {\n\t\t\t\tdefer wg.Done()\n\t\t\t\tif err := c.Close(); err != nil {\n\t\t\t\t\tlog.Error(\"error when closing down transport\", \"transport_type\", fmt.Sprintf(\"%T\", c), \"err\", err)\n\t\t\t\t}\n\t\t\t}(closer)\n\t\t}\n\t}\n\twg.Wait()\n}\n\nfunc (s *Swarm) addConn(tc transport.CapableConn, dir network.Direction) (*Conn, error) {\n\tvar (\n\t\tp    = tc.RemotePeer()\n\t\taddr = tc.RemoteMultiaddr()\n\t)\n\n\t// create the Stat object, initializing with the underlying connection Stat if available\n\tvar stat network.ConnStats\n\tif cs, ok := tc.(network.ConnStat); ok {\n\t\tstat = cs.Stat()\n\t}\n\tstat.Direction = dir\n\tstat.Opened = time.Now()\n\tisLimited := stat.Limited\n\n\t// Wrap and register the connection.\n\tc := &Conn{\n\t\tconn:  tc,\n\t\tswarm: s,\n\t\tstat:  stat,\n\t\tid:    s.nextConnID.Add(1),\n\t}\n\n\t// we ONLY check upgraded connections here so we can send them a Disconnect message.\n\t// If we do this in the Upgrader, we will not be able to do this.\n\tif s.gater != nil {\n\t\tif allow, _ := s.gater.InterceptUpgraded(c); !allow {\n\t\t\terr := tc.CloseWithError(network.ConnGated)\n\t\t\tif err != nil {\n\t\t\t\tlog.Warn(\"failed to close connection with peer and addr\", \"peer\", p, \"addr\", addr, \"err\", err)\n\t\t\t}\n\t\t\treturn nil, ErrGaterDisallowedConnection\n\t\t}\n\t}\n\n\t// Add the public key.\n\tif pk := tc.RemotePublicKey(); pk != nil {\n\t\ts.peers.AddPubKey(p, pk)\n\t}\n\n\t// Clear any backoffs\n\ts.backf.Clear(p)\n\n\t// Finally, add the peer.\n\ts.conns.Lock()\n\t// Check if we're still online\n\tif s.conns.m == nil {\n\t\ts.conns.Unlock()\n\t\ttc.Close()\n\t\treturn nil, ErrSwarmClosed\n\t}\n\n\tc.streams.m = make(map[*Stream]struct{})\n\ts.conns.m[p] = append(s.conns.m[p], c)\n\t// Add two swarm refs:\n\t// * One will be decremented after the close notifications fire in Conn.doClose\n\t// * The other will be decremented when Conn.start exits.\n\ts.refs.Add(2)\n\t// Take the notification lock before releasing the conns lock to block\n\t// Disconnect notifications until after the Connect notifications done.\n\t// This lock also ensures that swarm.refs.Wait() exits after we have\n\t// enqueued the peer connectedness changed notification.\n\t// TODO: Fix this fragility by taking a swarm ref for dial worker loop\n\tc.notifyLk.Lock()\n\ts.conns.Unlock()\n\n\ts.connectednessEventEmitter.AddConn(p)\n\n\tif !isLimited {\n\t\t// Notify goroutines waiting for a direct connection\n\t\t//\n\t\t// Go routines interested in waiting for direct connection first acquire this lock\n\t\t// and then acquire s.conns.RLock. Do not acquire this lock before conns.Unlock to\n\t\t// prevent deadlock.\n\t\ts.directConnNotifs.Lock()\n\t\tfor _, ch := range s.directConnNotifs.m[p] {\n\t\t\tclose(ch)\n\t\t}\n\t\tdelete(s.directConnNotifs.m, p)\n\t\ts.directConnNotifs.Unlock()\n\t}\n\ts.notifyAll(func(f network.Notifiee) {\n\t\tf.Connected(s, c)\n\t})\n\tc.notifyLk.Unlock()\n\n\tc.start()\n\treturn c, nil\n}\n\n// Peerstore returns this swarms internal Peerstore.\nfunc (s *Swarm) Peerstore() peerstore.Peerstore {\n\treturn s.peers\n}\n\n// SetStreamHandler assigns the handler for new streams.\nfunc (s *Swarm) SetStreamHandler(handler network.StreamHandler) {\n\ts.streamh.Store(&handler)\n}\n\n// StreamHandler gets the handler for new streams.\nfunc (s *Swarm) StreamHandler() network.StreamHandler {\n\thandler := s.streamh.Load()\n\tif handler == nil {\n\t\treturn nil\n\t}\n\treturn *handler\n}\n\n// NewStream creates a new stream on any available connection to peer, dialing\n// if necessary.\n// Use network.WithAllowLimitedConn to open a stream over a limited(relayed)\n// connection.\nfunc (s *Swarm) NewStream(ctx context.Context, p peer.ID) (network.Stream, error) {\n\tlog.Debug(\"opening stream to peer\", \"source_peer\", s.local, \"destination_peer\", p)\n\n\t// Algorithm:\n\t// 1. Find the best connection, otherwise, dial.\n\t// 2. If the best connection is limited, wait for a direct conn via conn\n\t//    reversal or hole punching.\n\t// 3. Try opening a stream.\n\t// 4. If the underlying connection is, in fact, closed, close the outer\n\t//    connection and try again. We do this in case we have a closed\n\t//    connection but don't notice it until we actually try to open a\n\t//    stream.\n\t//\n\t// TODO: Try all connections even if we get an error opening a stream on\n\t// a non-closed connection.\n\tnumDials := 0\n\tfor {\n\t\tc := s.bestConnToPeer(p)\n\t\tif c == nil {\n\t\t\tif nodial, _ := network.GetNoDial(ctx); !nodial {\n\t\t\t\tnumDials++\n\t\t\t\tif numDials > DialAttempts {\n\t\t\t\t\treturn nil, errors.New(\"max dial attempts exceeded\")\n\t\t\t\t}\n\t\t\t\tvar err error\n\t\t\t\tc, err = s.dialPeer(ctx, p)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn nil, network.ErrNoConn\n\t\t\t}\n\t\t}\n\n\t\tlimitedAllowed, _ := network.GetAllowLimitedConn(ctx)\n\t\tif !limitedAllowed && c.Stat().Limited {\n\t\t\tvar err error\n\t\t\tc, err = s.waitForDirectConn(ctx, p)\n\t\t\tif err != nil {\n\t\t\t\tlog.Debug(\"failed to get direct connection to a limited peer\", \"destination_peer\", p, \"err\", err)\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\n\t\tstr, err := c.NewStream(ctx)\n\t\tif err != nil {\n\t\t\tif c.conn.IsClosed() {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn nil, err\n\t\t}\n\t\treturn str, nil\n\t}\n}\n\n// waitForDirectConn waits for a direct connection established through hole punching or connection reversal.\nfunc (s *Swarm) waitForDirectConn(ctx context.Context, p peer.ID) (*Conn, error) {\n\ts.directConnNotifs.Lock()\n\tc := s.bestConnToPeer(p)\n\tif c == nil {\n\t\ts.directConnNotifs.Unlock()\n\t\treturn nil, network.ErrNoConn\n\t} else if !c.Stat().Limited {\n\t\ts.directConnNotifs.Unlock()\n\t\treturn c, nil\n\t}\n\n\t// Wait for limited connection to upgrade to a direct connection either by\n\t// connection reversal or hole punching.\n\tch := make(chan struct{})\n\ts.directConnNotifs.m[p] = append(s.directConnNotifs.m[p], ch)\n\ts.directConnNotifs.Unlock()\n\n\t// apply the DialPeer timeout\n\tctx, cancel := context.WithTimeout(ctx, network.GetDialPeerTimeout(ctx))\n\tdefer cancel()\n\n\t// Wait for notification.\n\tselect {\n\tcase <-ctx.Done():\n\t\t// Remove ourselves from the notification list\n\t\ts.directConnNotifs.Lock()\n\t\tdefer s.directConnNotifs.Unlock()\n\n\t\ts.directConnNotifs.m[p] = slices.DeleteFunc(\n\t\t\ts.directConnNotifs.m[p],\n\t\t\tfunc(c chan struct{}) bool { return c == ch },\n\t\t)\n\t\tif len(s.directConnNotifs.m[p]) == 0 {\n\t\t\tdelete(s.directConnNotifs.m, p)\n\t\t}\n\t\treturn nil, ctx.Err()\n\tcase <-ch:\n\t\t// We do not need to remove ourselves from the list here as the notifier\n\t\t// clears the map entry\n\t\tc := s.bestConnToPeer(p)\n\t\tif c == nil {\n\t\t\treturn nil, network.ErrNoConn\n\t\t}\n\t\tif c.Stat().Limited {\n\t\t\treturn nil, network.ErrLimitedConn\n\t\t}\n\t\treturn c, nil\n\t}\n}\n\n// ConnsToPeer returns all the live connections to peer.\nfunc (s *Swarm) ConnsToPeer(p peer.ID) []network.Conn {\n\t// TODO: Consider sorting the connection list best to worst. Currently,\n\t// it's sorted oldest to newest.\n\ts.conns.RLock()\n\tdefer s.conns.RUnlock()\n\tconns := s.conns.m[p]\n\toutput := make([]network.Conn, len(conns))\n\tfor i, c := range conns {\n\t\toutput[i] = c\n\t}\n\treturn output\n}\n\nfunc isBetterConn(a, b *Conn) bool {\n\t// If one is limited and not the other, prefer the unlimited connection.\n\taLimited := a.Stat().Limited\n\tbLimited := b.Stat().Limited\n\tif aLimited != bLimited {\n\t\treturn !aLimited\n\t}\n\n\t// If one is direct and not the other, prefer the direct connection.\n\taDirect := isDirectConn(a)\n\tbDirect := isDirectConn(b)\n\tif aDirect != bDirect {\n\t\treturn aDirect\n\t}\n\n\t// Otherwise, prefer the connection with more open streams.\n\ta.streams.Lock()\n\taLen := len(a.streams.m)\n\ta.streams.Unlock()\n\n\tb.streams.Lock()\n\tbLen := len(b.streams.m)\n\tb.streams.Unlock()\n\n\tif aLen != bLen {\n\t\treturn aLen > bLen\n\t}\n\n\t// finally, pick the last connection.\n\treturn true\n}\n\n// bestConnToPeer returns the best connection to peer.\nfunc (s *Swarm) bestConnToPeer(p peer.ID) *Conn {\n\t// TODO: Prefer some transports over others.\n\t// For now, prefers direct connections over Relayed connections.\n\t// For tie-breaking, select the newest non-closed connection with the most streams.\n\ts.conns.RLock()\n\tdefer s.conns.RUnlock()\n\n\tvar best *Conn\n\tfor _, c := range s.conns.m[p] {\n\t\tif c.conn.IsClosed() {\n\t\t\t// We *will* garbage collect this soon anyways.\n\t\t\tcontinue\n\t\t}\n\t\tif best == nil || isBetterConn(c, best) {\n\t\t\tbest = c\n\t\t}\n\t}\n\treturn best\n}\n\n// bestAcceptableConnToPeer returns the best acceptable connection, considering the passed in ctx.\n// If network.WithForceDirectDial is used, it only returns a direct connections, ignoring\n// any limited (relayed) connections to the peer.\nfunc (s *Swarm) bestAcceptableConnToPeer(ctx context.Context, p peer.ID) *Conn {\n\tconn := s.bestConnToPeer(p)\n\n\tforceDirect, _ := network.GetForceDirectDial(ctx)\n\tif forceDirect && !isDirectConn(conn) {\n\t\treturn nil\n\t}\n\treturn conn\n}\n\nfunc isDirectConn(c *Conn) bool {\n\treturn c != nil && !c.conn.Transport().Proxy()\n}\n\n// Connectedness returns our \"connectedness\" state with the given peer.\n//\n// To check if we have an open connection, use `s.Connectedness(p) ==\n// network.Connected`.\nfunc (s *Swarm) Connectedness(p peer.ID) network.Connectedness {\n\ts.conns.RLock()\n\tdefer s.conns.RUnlock()\n\n\treturn s.connectednessUnlocked(p)\n}\n\n// connectednessUnlocked returns the connectedness of a peer.\nfunc (s *Swarm) connectednessUnlocked(p peer.ID) network.Connectedness {\n\tvar haveLimited bool\n\tfor _, c := range s.conns.m[p] {\n\t\tif c.IsClosed() {\n\t\t\t// These will be garbage collected soon\n\t\t\tcontinue\n\t\t}\n\t\tif c.Stat().Limited {\n\t\t\thaveLimited = true\n\t\t} else {\n\t\t\treturn network.Connected\n\t\t}\n\t}\n\tif haveLimited {\n\t\treturn network.Limited\n\t}\n\treturn network.NotConnected\n}\n\n// Conns returns a slice of all connections.\nfunc (s *Swarm) Conns() []network.Conn {\n\ts.conns.RLock()\n\tdefer s.conns.RUnlock()\n\n\tconns := make([]network.Conn, 0, len(s.conns.m))\n\tfor _, cs := range s.conns.m {\n\t\tfor _, c := range cs {\n\t\t\tconns = append(conns, c)\n\t\t}\n\t}\n\treturn conns\n}\n\n// ClosePeer closes all connections to the given peer.\nfunc (s *Swarm) ClosePeer(p peer.ID) error {\n\tconns := s.ConnsToPeer(p)\n\tswitch len(conns) {\n\tcase 0:\n\t\treturn nil\n\tcase 1:\n\t\treturn conns[0].Close()\n\tdefault:\n\t\terrCh := make(chan error)\n\t\tfor _, c := range conns {\n\t\t\tgo func(c network.Conn) {\n\t\t\t\terrCh <- c.Close()\n\t\t\t}(c)\n\t\t}\n\n\t\tvar errs []string\n\t\tfor range conns {\n\t\t\terr := <-errCh\n\t\t\tif err != nil {\n\t\t\t\terrs = append(errs, err.Error())\n\t\t\t}\n\t\t}\n\t\tif len(errs) > 0 {\n\t\t\treturn fmt.Errorf(\"when disconnecting from peer %s: %s\", p, strings.Join(errs, \", \"))\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// Peers returns a copy of the set of peers swarm is connected to.\nfunc (s *Swarm) Peers() []peer.ID {\n\ts.conns.RLock()\n\tdefer s.conns.RUnlock()\n\tpeers := make([]peer.ID, 0, len(s.conns.m))\n\tfor p := range s.conns.m {\n\t\tpeers = append(peers, p)\n\t}\n\n\treturn peers\n}\n\n// LocalPeer returns the local peer swarm is associated to.\nfunc (s *Swarm) LocalPeer() peer.ID {\n\treturn s.local\n}\n\n// Backoff returns the DialBackoff object for this swarm.\nfunc (s *Swarm) Backoff() *DialBackoff {\n\treturn &s.backf\n}\n\n// notifyAll sends a signal to all Notifiees\nfunc (s *Swarm) notifyAll(notify func(network.Notifiee)) {\n\ts.notifs.RLock()\n\tfor f := range s.notifs.m {\n\t\tnotify(f)\n\t}\n\ts.notifs.RUnlock()\n}\n\n// Notify signs up Notifiee to receive signals when events happen\nfunc (s *Swarm) Notify(f network.Notifiee) {\n\ts.notifs.Lock()\n\ts.notifs.m[f] = struct{}{}\n\ts.notifs.Unlock()\n}\n\n// StopNotify unregisters Notifiee fromr receiving signals\nfunc (s *Swarm) StopNotify(f network.Notifiee) {\n\ts.notifs.Lock()\n\tdelete(s.notifs.m, f)\n\ts.notifs.Unlock()\n}\n\nfunc (s *Swarm) removeConn(c *Conn) {\n\tp := c.RemotePeer()\n\n\ts.conns.Lock()\n\tcs := s.conns.m[p]\n\tfor i, ci := range cs {\n\t\tif ci == c {\n\t\t\t// NOTE: We're intentionally preserving order.\n\t\t\t// This way, connections to a peer are always\n\t\t\t// sorted oldest to newest.\n\t\t\tcopy(cs[i:], cs[i+1:])\n\t\t\tcs[len(cs)-1] = nil\n\t\t\ts.conns.m[p] = cs[:len(cs)-1]\n\t\t\tbreak\n\t\t}\n\t}\n\tif len(s.conns.m[p]) == 0 {\n\t\tdelete(s.conns.m, p)\n\t}\n\ts.conns.Unlock()\n}\n\n// String returns a string representation of Network.\nfunc (s *Swarm) String() string {\n\treturn fmt.Sprintf(\"<Swarm %s>\", s.LocalPeer())\n}\n\nfunc (s *Swarm) ResourceManager() network.ResourceManager {\n\treturn s.rcmgr\n}\n\n// Swarm is a Network.\nvar (\n\t_ network.Network            = (*Swarm)(nil)\n\t_ transport.TransportNetwork = (*Swarm)(nil)\n)\n\ntype connWithMetrics struct {\n\ttransport.CapableConn\n\topened        time.Time\n\tdir           network.Direction\n\tmetricsTracer MetricsTracer\n\tonce          sync.Once\n\tcloseErr      error\n}\n\nfunc wrapWithMetrics(capableConn transport.CapableConn, metricsTracer MetricsTracer, opened time.Time, dir network.Direction) *connWithMetrics {\n\tc := &connWithMetrics{CapableConn: capableConn, opened: opened, dir: dir, metricsTracer: metricsTracer}\n\tc.metricsTracer.OpenedConnection(c.dir, capableConn.RemotePublicKey(), capableConn.ConnState(), capableConn.LocalMultiaddr())\n\treturn c\n}\n\nfunc (c *connWithMetrics) As(target any) bool {\n\treturn c.CapableConn.As(target)\n}\n\nfunc (c *connWithMetrics) completedHandshake() {\n\tc.metricsTracer.CompletedHandshake(time.Since(c.opened), c.ConnState(), c.LocalMultiaddr())\n}\n\nfunc (c *connWithMetrics) Close() error {\n\tc.once.Do(func() {\n\t\tc.metricsTracer.ClosedConnection(c.dir, time.Since(c.opened), c.ConnState(), c.LocalMultiaddr())\n\t\tc.closeErr = c.CapableConn.Close()\n\t})\n\treturn c.closeErr\n}\n\nfunc (c *connWithMetrics) CloseWithError(errCode network.ConnErrorCode) error {\n\tc.once.Do(func() {\n\t\tc.metricsTracer.ClosedConnection(c.dir, time.Since(c.opened), c.ConnState(), c.LocalMultiaddr())\n\t\tc.closeErr = c.CapableConn.CloseWithError(errCode)\n\t})\n\treturn c.closeErr\n}\n\nfunc (c *connWithMetrics) Stat() network.ConnStats {\n\tif cs, ok := c.CapableConn.(network.ConnStat); ok {\n\t\treturn cs.Stat()\n\t}\n\treturn network.ConnStats{}\n}\n\nvar _ network.ConnStat = &connWithMetrics{}\n\ntype ResolverFromMaDNS struct {\n\t*madns.Resolver\n}\n\nvar _ network.MultiaddrDNSResolver = ResolverFromMaDNS{}\n\nfunc startsWithDNSADDR(m ma.Multiaddr) bool {\n\tif m == nil {\n\t\treturn false\n\t}\n\n\tstartsWithDNSADDR := false\n\t// Using ForEach to avoid allocating\n\tma.ForEach(m, func(c ma.Component) bool {\n\t\tstartsWithDNSADDR = c.Protocol().Code == ma.P_DNSADDR\n\t\treturn false\n\t})\n\treturn startsWithDNSADDR\n}\n\n// ResolveDNSAddr implements MultiaddrDNSResolver\nfunc (r ResolverFromMaDNS) ResolveDNSAddr(ctx context.Context, expectedPeerID peer.ID, maddr ma.Multiaddr, recursionLimit int, outputLimit int) ([]ma.Multiaddr, error) {\n\tif outputLimit <= 0 {\n\t\treturn nil, nil\n\t}\n\tif recursionLimit <= 0 {\n\t\treturn []ma.Multiaddr{maddr}, nil\n\t}\n\tvar resolved, toResolve []ma.Multiaddr\n\taddrs, err := r.Resolve(ctx, maddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(addrs) > outputLimit {\n\t\taddrs = addrs[:outputLimit]\n\t}\n\n\tfor _, addr := range addrs {\n\t\tif startsWithDNSADDR(addr) {\n\t\t\ttoResolve = append(toResolve, addr)\n\t\t} else {\n\t\t\tresolved = append(resolved, addr)\n\t\t}\n\t}\n\n\tfor i, addr := range toResolve {\n\t\t// Set the nextOutputLimit to:\n\t\t//   outputLimit\n\t\t//   - len(resolved)          // What we already have resolved\n\t\t//   - (len(toResolve) - i)   // How many addresses we have left to resolve\n\t\t//   + 1                      // The current address we are resolving\n\t\t// This assumes that each DNSADDR address will resolve to at least one multiaddr.\n\t\t// This assumption lets us bound the space we reserve for resolving.\n\t\tnextOutputLimit := outputLimit - len(resolved) - (len(toResolve) - i) + 1\n\t\tresolvedAddrs, err := r.ResolveDNSAddr(ctx, expectedPeerID, addr, recursionLimit-1, nextOutputLimit)\n\t\tif err != nil {\n\t\t\tlog.Warn(\"failed to resolve dnsaddr\", \"addr\", addr, \"err\", err)\n\t\t\t// Dropping this address\n\t\t\tcontinue\n\t\t}\n\t\tresolved = append(resolved, resolvedAddrs...)\n\t}\n\n\tif len(resolved) > outputLimit {\n\t\tresolved = resolved[:outputLimit]\n\t}\n\n\t// If the address contains a peer id, make sure it matches our expectedPeerID\n\tif expectedPeerID != \"\" {\n\t\tremoveMismatchPeerID := func(a ma.Multiaddr) bool {\n\t\t\tid, err := peer.IDFromP2PAddr(a)\n\t\t\tif err == peer.ErrInvalidAddr {\n\t\t\t\t// This multiaddr didn't contain a peer id, assume it's for this peer.\n\t\t\t\t// Handshake will fail later if it's not.\n\t\t\t\treturn false\n\t\t\t} else if err != nil {\n\t\t\t\t// This multiaddr is invalid, drop it.\n\t\t\t\treturn true\n\t\t\t}\n\n\t\t\treturn id != expectedPeerID\n\t\t}\n\t\tresolved = slices.DeleteFunc(resolved, removeMismatchPeerID)\n\t}\n\n\treturn resolved, nil\n}\n\n// ResolveDNSComponent implements MultiaddrDNSResolver\nfunc (r ResolverFromMaDNS) ResolveDNSComponent(ctx context.Context, maddr ma.Multiaddr, outputLimit int) ([]ma.Multiaddr, error) {\n\taddrs, err := r.Resolve(ctx, maddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(addrs) > outputLimit {\n\t\taddrs = addrs[:outputLimit]\n\t}\n\treturn addrs, nil\n}\n\n// AddCertHashes adds certificate hashes to relevant transport addresses, if there\n// are no certhashes already present on the method. It mutates `listenAddrs`.\n// This method is useful for adding certhashes to public addresses discovered\n// via identify, nat mapping, or provided by the user.\nfunc (s *Swarm) AddCertHashes(listenAddrs []ma.Multiaddr) []ma.Multiaddr {\n\ttype addCertHasher interface {\n\t\tAddCertHashes(m ma.Multiaddr) (ma.Multiaddr, bool)\n\t}\n\n\tfor i, addr := range listenAddrs {\n\t\tt := s.TransportForListening(addr)\n\t\tif t == nil {\n\t\t\tcontinue\n\t\t}\n\t\ttpt, ok := t.(addCertHasher)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\taddrWithCerthash, added := tpt.AddCertHashes(addr)\n\t\tif !added {\n\t\t\tlog.Warn(\"Couldn't add certhashes to multiaddr\", \"addr\", addr)\n\t\t\tcontinue\n\t\t}\n\t\tlistenAddrs[i] = addrWithCerthash\n\t}\n\treturn listenAddrs\n}\n"
  },
  {
    "path": "p2p/net/swarm/swarm_addr.go",
    "content": "package swarm\n\nimport (\n\t\"time\"\n\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\n// ListenAddresses returns a list of addresses at which this swarm listens.\nfunc (s *Swarm) ListenAddresses() []ma.Multiaddr {\n\ts.listeners.RLock()\n\tdefer s.listeners.RUnlock()\n\treturn s.listenAddressesNoLock()\n}\n\nfunc (s *Swarm) listenAddressesNoLock() []ma.Multiaddr {\n\taddrs := make([]ma.Multiaddr, 0, len(s.listeners.m)+10) // A bit extra so we may avoid an extra allocation in the for loop below.\n\tfor l := range s.listeners.m {\n\t\taddrs = append(addrs, l.Multiaddr())\n\t}\n\treturn addrs\n}\n\nconst ifaceAddrsCacheDuration = 1 * time.Minute\n\n// InterfaceListenAddresses returns a list of addresses at which this swarm\n// listens. It expands \"any interface\" addresses (/ip4/0.0.0.0, /ip6/::) to\n// use the known local interfaces.\nfunc (s *Swarm) InterfaceListenAddresses() ([]ma.Multiaddr, error) {\n\ts.listeners.RLock() // RLock start\n\n\tifaceListenAddres := s.listeners.ifaceListenAddres\n\tisEOL := time.Now().After(s.listeners.cacheEOL)\n\ts.listeners.RUnlock() // RLock end\n\n\tif !isEOL {\n\t\t// Cache is valid, clone the slice\n\t\treturn append(ifaceListenAddres[:0:0], ifaceListenAddres...), nil\n\t}\n\n\t// Cache is not valid\n\t// Perfrom double checked locking\n\n\ts.listeners.Lock() // Lock start\n\n\tifaceListenAddres = s.listeners.ifaceListenAddres\n\tisEOL = time.Now().After(s.listeners.cacheEOL)\n\tif isEOL {\n\t\t// Cache is still invalid\n\t\tlistenAddres := s.listenAddressesNoLock()\n\t\tif len(listenAddres) > 0 {\n\t\t\t// We're actually listening on addresses.\n\t\t\tvar err error\n\t\t\tifaceListenAddres, err = manet.ResolveUnspecifiedAddresses(listenAddres, nil)\n\t\t\tif err != nil {\n\t\t\t\ts.listeners.Unlock() // Lock early exit\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t} else {\n\t\t\tifaceListenAddres = nil\n\t\t}\n\n\t\ts.listeners.ifaceListenAddres = ifaceListenAddres\n\t\ts.listeners.cacheEOL = time.Now().Add(ifaceAddrsCacheDuration)\n\t}\n\n\ts.listeners.Unlock() // Lock end\n\n\treturn append(ifaceListenAddres[:0:0], ifaceListenAddres...), nil\n}\n"
  },
  {
    "path": "p2p/net/swarm/swarm_addr_test.go",
    "content": "package swarm_test\n\nimport (\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"fmt\"\n\t\"testing\"\n\n\tic \"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\t\"github.com/libp2p/go-libp2p/core/test\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/eventbus\"\n\t\"github.com/libp2p/go-libp2p/p2p/net/swarm\"\n\tswarmt \"github.com/libp2p/go-libp2p/p2p/net/swarm/testing\"\n\tcircuitv2 \"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/client\"\n\tlibp2pquic \"github.com/libp2p/go-libp2p/p2p/transport/quic\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/quicreuse\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/tcp\"\n\twebtransport \"github.com/libp2p/go-libp2p/p2p/transport/webtransport\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/multiformats/go-multibase\"\n\t\"github.com/multiformats/go-multihash\"\n\t\"github.com/quic-go/quic-go\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestDialBadAddrs(t *testing.T) {\n\tm := func(s string) ma.Multiaddr {\n\t\tmaddr, err := ma.NewMultiaddr(s)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\treturn maddr\n\t}\n\n\ts := makeSwarms(t, 1)[0]\n\n\ttest := func(a ma.Multiaddr) {\n\t\tp := test.RandPeerIDFatal(t)\n\t\ts.Peerstore().AddAddr(p, a, peerstore.PermanentAddrTTL)\n\t\tif _, err := s.DialPeer(context.Background(), p); err == nil {\n\t\t\tt.Errorf(\"swarm should not dial: %s\", p)\n\t\t}\n\t}\n\n\ttest(m(\"/ip6/fe80::1\"))                // link local\n\ttest(m(\"/ip6/fe80::100\"))              // link local\n\ttest(m(\"/ip4/127.0.0.1/udp/1234/utp\")) // utp\n}\n\nfunc TestAddrRace(t *testing.T) {\n\ts := makeSwarms(t, 1)[0]\n\tdefer s.Close()\n\n\ta1, err := s.InterfaceListenAddresses()\n\trequire.NoError(t, err)\n\ta2, err := s.InterfaceListenAddresses()\n\trequire.NoError(t, err)\n\n\tif len(a1) > 0 && len(a2) > 0 && &a1[0] == &a2[0] {\n\t\tt.Fatal(\"got the exact same address set twice; this could lead to data races\")\n\t}\n}\n\nfunc TestAddressesWithoutListening(t *testing.T) {\n\ts := swarmt.GenSwarm(t, swarmt.OptDialOnly)\n\ta1, err := s.InterfaceListenAddresses()\n\trequire.NoError(t, err)\n\trequire.Empty(t, a1, \"expected to be listening on no addresses\")\n}\n\nfunc TestDialAddressSelection(t *testing.T) {\n\tpriv, _, err := test.RandTestKeyPair(ic.Ed25519, 256)\n\trequire.NoError(t, err)\n\tid, err := peer.IDFromPrivateKey(priv)\n\trequire.NoError(t, err)\n\ts, err := swarm.NewSwarm(\"local\", nil, eventbus.NewBus())\n\trequire.NoError(t, err)\n\n\ttcpTr, err := tcp.NewTCPTransport(nil, nil, nil)\n\trequire.NoError(t, err)\n\trequire.NoError(t, s.AddTransport(tcpTr))\n\treuse, err := quicreuse.NewConnManager(quic.StatelessResetKey{}, quic.TokenGeneratorKey{})\n\trequire.NoError(t, err)\n\tdefer reuse.Close()\n\tquicTr, err := libp2pquic.NewTransport(priv, reuse, nil, nil, nil)\n\trequire.NoError(t, err)\n\trequire.NoError(t, s.AddTransport(quicTr))\n\twebtransportTr, err := webtransport.New(priv, nil, reuse, nil, nil)\n\trequire.NoError(t, err)\n\trequire.NoError(t, s.AddTransport(webtransportTr))\n\th := sha256.Sum256([]byte(\"foo\"))\n\thash, err := multihash.Encode(h[:], multihash.SHA2_256)\n\trequire.NoError(t, err)\n\tcertHash, err := multibase.Encode(multibase.Base58BTC, hash)\n\trequire.NoError(t, err)\n\tcircuitTr, err := circuitv2.New(nil, nil)\n\trequire.NoError(t, err)\n\trequire.NoError(t, s.AddTransport(circuitTr))\n\n\trequire.Equal(t, tcpTr, s.TransportForDialing(ma.StringCast(\"/ip4/127.0.0.1/tcp/1234\")))\n\trequire.Equal(t, quicTr, s.TransportForDialing(ma.StringCast(\"/ip4/127.0.0.1/udp/1234/quic-v1\")))\n\trequire.Equal(t, circuitTr, s.TransportForDialing(ma.StringCast(fmt.Sprintf(\"/ip4/127.0.0.1/udp/1234/quic/p2p-circuit/p2p/%s\", id))))\n\trequire.Equal(t, webtransportTr, s.TransportForDialing(ma.StringCast(fmt.Sprintf(\"/ip4/127.0.0.1/udp/1234/quic-v1/webtransport/certhash/%s\", certHash))))\n\trequire.Nil(t, s.TransportForDialing(ma.StringCast(\"/ip4/1.2.3.4\")))\n\trequire.Nil(t, s.TransportForDialing(ma.StringCast(\"/ip4/1.2.3.4/tcp/443/ws\")))\n}\n"
  },
  {
    "path": "p2p/net/swarm/swarm_conn.go",
    "content": "package swarm\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n\n\tic \"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/transport\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\n// TODO: Put this elsewhere.\n\n// ErrConnClosed is returned when operating on a closed connection.\nvar ErrConnClosed = errors.New(\"connection closed\")\n\n// Conn is the connection type used by swarm. In general, you won't use this\n// type directly.\ntype Conn struct {\n\tid    uint64\n\tconn  transport.CapableConn\n\tswarm *Swarm\n\n\tcloseOnce sync.Once\n\terr       error\n\n\tnotifyLk sync.Mutex\n\n\tstreams struct {\n\t\tsync.Mutex\n\t\tm map[*Stream]struct{}\n\t}\n\n\tstat network.ConnStats\n}\n\nvar _ network.Conn = &Conn{}\n\nfunc (c *Conn) As(target any) bool {\n\treturn c.conn.As(target)\n}\n\nfunc (c *Conn) IsClosed() bool {\n\treturn c.conn.IsClosed()\n}\n\nfunc (c *Conn) ID() string {\n\t// format: <first 10 chars of peer id>-<global conn ordinal>\n\treturn fmt.Sprintf(\"%s-%d\", c.RemotePeer().String()[:10], c.id)\n}\n\n// Close closes this connection.\n//\n// Note: This method won't wait for the close notifications to finish as that\n// would create a deadlock when called from an open notification (because all\n// open notifications must finish before we can fire off the close\n// notifications).\nfunc (c *Conn) Close() error {\n\tc.closeOnce.Do(func() {\n\t\tc.doClose(0)\n\t})\n\treturn c.err\n}\n\nfunc (c *Conn) CloseWithError(errCode network.ConnErrorCode) error {\n\tc.closeOnce.Do(func() {\n\t\tc.doClose(errCode)\n\t})\n\treturn c.err\n}\n\nfunc (c *Conn) doClose(errCode network.ConnErrorCode) {\n\tc.swarm.removeConn(c)\n\n\t// Prevent new streams from opening.\n\tc.streams.Lock()\n\tstreams := c.streams.m\n\tc.streams.m = nil\n\tc.streams.Unlock()\n\n\tif errCode != 0 {\n\t\tc.err = c.conn.CloseWithError(errCode)\n\t} else {\n\t\tc.err = c.conn.Close()\n\t}\n\n\t// Send the connectedness event after closing the connection.\n\t// This ensures that both remote connection close and local connection\n\t// close events are sent after the underlying transport connection is closed.\n\tc.swarm.connectednessEventEmitter.RemoveConn(c.RemotePeer())\n\n\t// This is just for cleaning up state. The connection has already been closed.\n\t// We *could* optimize this but it really isn't worth it.\n\tfor s := range streams {\n\t\ts.Reset()\n\t}\n\n\t// do this in a goroutine to avoid deadlocking if we call close in an open notification.\n\tgo func() {\n\t\t// prevents us from issuing close notifications before finishing the open notifications\n\t\tc.notifyLk.Lock()\n\t\tdefer c.notifyLk.Unlock()\n\n\t\t// Only notify for disconnection if we notified for connection\n\t\tc.swarm.notifyAll(func(f network.Notifiee) {\n\t\t\tf.Disconnected(c.swarm, c)\n\t\t})\n\t\tc.swarm.refs.Done()\n\t}()\n}\n\nfunc (c *Conn) removeStream(s *Stream) {\n\tc.streams.Lock()\n\tc.stat.NumStreams--\n\tdelete(c.streams.m, s)\n\tc.streams.Unlock()\n\ts.scope.Done()\n}\n\n// listens for new streams.\n//\n// The caller must take a swarm ref before calling. This function decrements the\n// swarm ref count.\nfunc (c *Conn) start() {\n\tgo func() {\n\t\tdefer c.swarm.refs.Done()\n\t\tdefer c.Close()\n\t\tfor {\n\t\t\tts, err := c.conn.AcceptStream()\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tscope, err := c.swarm.ResourceManager().OpenStream(c.RemotePeer(), network.DirInbound)\n\t\t\tif err != nil {\n\t\t\t\tts.ResetWithError(network.StreamResourceLimitExceeded)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tc.swarm.refs.Add(1)\n\t\t\tgo func() {\n\t\t\t\ts, err := c.addStream(ts, network.DirInbound, scope)\n\n\t\t\t\t// Don't defer this. We don't want to block\n\t\t\t\t// swarm shutdown on the connection handler.\n\t\t\t\tc.swarm.refs.Done()\n\n\t\t\t\t// We only get an error here when the swarm is closed or closing.\n\t\t\t\tif err != nil {\n\t\t\t\t\tscope.Done()\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif h := c.swarm.StreamHandler(); h != nil {\n\t\t\t\t\th(s)\n\t\t\t\t}\n\t\t\t\ts.completeAcceptStreamGoroutine()\n\t\t\t}()\n\t\t}\n\t}()\n}\n\nfunc (c *Conn) String() string {\n\treturn fmt.Sprintf(\n\t\t\"<swarm.Conn[%T] %s (%s) <-> %s (%s)>\",\n\t\tc.conn.Transport(),\n\t\tc.conn.LocalMultiaddr(),\n\t\tc.conn.LocalPeer(),\n\t\tc.conn.RemoteMultiaddr(),\n\t\tc.conn.RemotePeer(),\n\t)\n}\n\n// LocalMultiaddr is the Multiaddr on this side\nfunc (c *Conn) LocalMultiaddr() ma.Multiaddr {\n\treturn c.conn.LocalMultiaddr()\n}\n\n// LocalPeer is the Peer on our side of the connection\nfunc (c *Conn) LocalPeer() peer.ID {\n\treturn c.conn.LocalPeer()\n}\n\n// RemoteMultiaddr is the Multiaddr on the remote side\nfunc (c *Conn) RemoteMultiaddr() ma.Multiaddr {\n\treturn c.conn.RemoteMultiaddr()\n}\n\n// RemotePeer is the Peer on the remote side\nfunc (c *Conn) RemotePeer() peer.ID {\n\treturn c.conn.RemotePeer()\n}\n\n// RemotePublicKey is the public key of the peer on the remote side\nfunc (c *Conn) RemotePublicKey() ic.PubKey {\n\treturn c.conn.RemotePublicKey()\n}\n\n// ConnState is the security connection state. including early data result.\n// Empty if not supported.\nfunc (c *Conn) ConnState() network.ConnectionState {\n\treturn c.conn.ConnState()\n}\n\n// Stat returns metadata pertaining to this connection\nfunc (c *Conn) Stat() network.ConnStats {\n\tc.streams.Lock()\n\tdefer c.streams.Unlock()\n\treturn c.stat\n}\n\n// NewStream returns a new Stream from this connection\nfunc (c *Conn) NewStream(ctx context.Context) (network.Stream, error) {\n\tif c.Stat().Limited {\n\t\tif useLimited, _ := network.GetAllowLimitedConn(ctx); !useLimited {\n\t\t\treturn nil, network.ErrLimitedConn\n\t\t}\n\t}\n\n\tscope, err := c.swarm.ResourceManager().OpenStream(c.RemotePeer(), network.DirOutbound)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif _, ok := ctx.Deadline(); !ok {\n\t\tvar cancel context.CancelFunc\n\t\tctx, cancel = context.WithTimeout(ctx, defaultNewStreamTimeout)\n\t\tdefer cancel()\n\t}\n\n\ts, err := c.openAndAddStream(ctx, scope)\n\tif err != nil {\n\t\tscope.Done()\n\t\tif errors.Is(err, context.DeadlineExceeded) {\n\t\t\terr = fmt.Errorf(\"timed out: %w\", err)\n\t\t}\n\t\treturn nil, err\n\t}\n\treturn s, nil\n}\n\nfunc (c *Conn) openAndAddStream(ctx context.Context, scope network.StreamManagementScope) (network.Stream, error) {\n\tts, err := c.conn.OpenStream(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn c.addStream(ts, network.DirOutbound, scope)\n}\n\nfunc (c *Conn) addStream(ts network.MuxedStream, dir network.Direction, scope network.StreamManagementScope) (*Stream, error) {\n\tc.streams.Lock()\n\t// Are we still online?\n\tif c.streams.m == nil {\n\t\tc.streams.Unlock()\n\t\tts.Reset()\n\t\treturn nil, ErrConnClosed\n\t}\n\n\t// Wrap and register the stream.\n\ts := &Stream{\n\t\tstream: ts,\n\t\tconn:   c,\n\t\tscope:  scope,\n\t\tstat: network.Stats{\n\t\t\tDirection: dir,\n\t\t\tOpened:    time.Now(),\n\t\t},\n\t\tid:                             c.swarm.nextStreamID.Add(1),\n\t\tacceptStreamGoroutineCompleted: dir != network.DirInbound,\n\t}\n\tc.stat.NumStreams++\n\tc.streams.m[s] = struct{}{}\n\n\t// Released once the stream disconnect notifications have finished\n\t// firing (in Swarm.remove).\n\tc.swarm.refs.Add(1)\n\n\tc.streams.Unlock()\n\treturn s, nil\n}\n\n// GetStreams returns the streams associated with this connection.\nfunc (c *Conn) GetStreams() []network.Stream {\n\tc.streams.Lock()\n\tdefer c.streams.Unlock()\n\tstreams := make([]network.Stream, 0, len(c.streams.m))\n\tfor s := range c.streams.m {\n\t\tstreams = append(streams, s)\n\t}\n\treturn streams\n}\n\nfunc (c *Conn) Scope() network.ConnScope {\n\treturn c.conn.Scope()\n}\n"
  },
  {
    "path": "p2p/net/swarm/swarm_dial.go",
    "content": "package swarm\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/netip\"\n\t\"strconv\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\t\"github.com/libp2p/go-libp2p/core/transport\"\n\t\"github.com/libp2p/go-libp2p/p2p/canonicallog\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmafmt \"github.com/multiformats/go-multiaddr-fmt\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\n// The maximum number of addresses we'll return when resolving all of a peer's\n// address\nconst maximumResolvedAddresses = 100\n\nconst maximumDNSADDRRecursion = 4\n\n// Diagram of dial sync:\n//\n//   many callers of Dial()   synched w.  dials many addrs       results to callers\n//  ----------------------\\    dialsync    use earliest            /--------------\n//  -----------------------\\              |----------\\           /----------------\n//  ------------------------>------------<-------     >---------<-----------------\n//  -----------------------|              \\----x                 \\----------------\n//  ----------------------|                \\-----x                \\---------------\n//                                         any may fail          if no addr at end\n//                                                             retry dialAttempt x\n\nvar (\n\t// ErrDialBackoff is returned by the backoff code when a given peer has\n\t// been dialed too frequently\n\tErrDialBackoff = errors.New(\"dial backoff\")\n\n\t// ErrDialRefusedBlackHole is returned when we are in a black holed environment\n\tErrDialRefusedBlackHole = errors.New(\"dial refused because of black hole\")\n\n\t// ErrDialToSelf is returned if we attempt to dial our own peer\n\tErrDialToSelf = errors.New(\"dial to self attempted\")\n\n\t// ErrNoTransport is returned when we don't know a transport for the\n\t// given multiaddr.\n\tErrNoTransport = errors.New(\"no transport for protocol\")\n\n\t// ErrAllDialsFailed is returned when connecting to a peer has ultimately failed\n\tErrAllDialsFailed = errors.New(\"all dials failed\")\n\n\t// ErrNoAddresses is returned when we fail to find any addresses for a\n\t// peer we're trying to dial.\n\tErrNoAddresses = errors.New(\"no addresses\")\n\n\t// ErrNoGoodAddresses is returned when we find addresses for a peer but\n\t// can't use any of them.\n\tErrNoGoodAddresses = errors.New(\"no good addresses\")\n\n\t// ErrGaterDisallowedConnection is returned when the gater prevents us from\n\t// forming a connection with a peer.\n\tErrGaterDisallowedConnection = errors.New(\"gater disallows connection to peer\")\n)\n\n// ErrQUICDraft29 wraps ErrNoTransport and provide a more meaningful error message\nvar ErrQUICDraft29 errQUICDraft29\n\ntype errQUICDraft29 struct{}\n\nfunc (errQUICDraft29) Error() string {\n\treturn \"QUIC draft-29 has been removed, QUIC (RFC 9000) is accessible with /quic-v1\"\n}\n\nfunc (errQUICDraft29) Unwrap() error {\n\treturn ErrNoTransport\n}\n\n// DialAttempts governs how many times a goroutine will try to dial a given peer.\n// Note: this is down to one, as we have _too many dials_ atm. To add back in,\n// add loop back in Dial(.)\nconst DialAttempts = 1\n\n// ConcurrentFdDials is the number of concurrent outbound dials over transports\n// that consume file descriptors\nconst ConcurrentFdDials = 160\n\n// DefaultPerPeerRateLimit is the number of concurrent outbound dials to make\n// per peer\nvar DefaultPerPeerRateLimit = 8\n\n// DialBackoff is a type for tracking peer dial backoffs. Dialbackoff is used to\n// avoid over-dialing the same, dead peers. Whenever we totally time out on all\n// addresses of a peer, we add the addresses to DialBackoff. Then, whenever we\n// attempt to dial the peer again, we check each address for backoff. If it's on\n// backoff, we don't dial the address and exit promptly. If a dial is\n// successful, the peer and all its addresses are removed from backoff.\n//\n// * It's safe to use its zero value.\n// * It's thread-safe.\n// * It's *not* safe to move this type after using.\ntype DialBackoff struct {\n\tentries map[peer.ID]map[string]*backoffAddr\n\tlock    sync.RWMutex\n}\n\ntype backoffAddr struct {\n\ttries int\n\tuntil time.Time\n}\n\nfunc (db *DialBackoff) init(ctx context.Context) {\n\tif db.entries == nil {\n\t\tdb.entries = make(map[peer.ID]map[string]*backoffAddr)\n\t}\n\tgo db.background(ctx)\n}\n\nfunc (db *DialBackoff) background(ctx context.Context) {\n\tticker := time.NewTicker(BackoffMax)\n\tdefer ticker.Stop()\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase <-ticker.C:\n\t\t\tdb.cleanup()\n\t\t}\n\t}\n}\n\n// Backoff returns whether the client should backoff from dialing\n// peer p at address addr\nfunc (db *DialBackoff) Backoff(p peer.ID, addr ma.Multiaddr) (backoff bool) {\n\tdb.lock.RLock()\n\tdefer db.lock.RUnlock()\n\n\tap, found := db.entries[p][string(addr.Bytes())]\n\treturn found && time.Now().Before(ap.until)\n}\n\n// BackoffBase is the base amount of time to backoff (default: 5s).\nvar BackoffBase = time.Second * 5\n\n// BackoffCoef is the backoff coefficient (default: 1s).\nvar BackoffCoef = time.Second\n\n// BackoffMax is the maximum backoff time (default: 5m).\nvar BackoffMax = time.Minute * 5\n\n// AddBackoff adds peer's address to backoff.\n//\n// Backoff is not exponential, it's quadratic and computed according to the\n// following formula:\n//\n//\tBackoffBase + BakoffCoef * PriorBackoffs^2\n//\n// Where PriorBackoffs is the number of previous backoffs.\nfunc (db *DialBackoff) AddBackoff(p peer.ID, addr ma.Multiaddr) {\n\tsaddr := string(addr.Bytes())\n\tdb.lock.Lock()\n\tdefer db.lock.Unlock()\n\tbp, ok := db.entries[p]\n\tif !ok {\n\t\tbp = make(map[string]*backoffAddr, 1)\n\t\tdb.entries[p] = bp\n\t}\n\tba, ok := bp[saddr]\n\tif !ok {\n\t\tbp[saddr] = &backoffAddr{\n\t\t\ttries: 1,\n\t\t\tuntil: time.Now().Add(BackoffBase),\n\t\t}\n\t\treturn\n\t}\n\n\tbackoffTime := min(BackoffBase+BackoffCoef*time.Duration(ba.tries*ba.tries), BackoffMax)\n\tba.until = time.Now().Add(backoffTime)\n\tba.tries++\n}\n\n// Clear removes a backoff record. Clients should call this after a\n// successful Dial.\nfunc (db *DialBackoff) Clear(p peer.ID) {\n\tdb.lock.Lock()\n\tdefer db.lock.Unlock()\n\tdelete(db.entries, p)\n}\n\nfunc (db *DialBackoff) cleanup() {\n\tdb.lock.Lock()\n\tdefer db.lock.Unlock()\n\tnow := time.Now()\n\tfor p, e := range db.entries {\n\t\tgood := false\n\t\tfor _, backoff := range e {\n\t\t\tbackoffTime := min(BackoffBase+BackoffCoef*time.Duration(backoff.tries*backoff.tries), BackoffMax)\n\t\t\tif now.Before(backoff.until.Add(backoffTime)) {\n\t\t\t\tgood = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !good {\n\t\t\tdelete(db.entries, p)\n\t\t}\n\t}\n}\n\n// DialPeer connects to a peer. Use network.WithForceDirectDial to force a\n// direct connection.\n//\n// The idea is that the client of Swarm does not need to know what network\n// the connection will happen over. Swarm can use whichever it choses.\n// This allows us to use various transport protocols, do NAT traversal/relay,\n// etc. to achieve connection.\nfunc (s *Swarm) DialPeer(ctx context.Context, p peer.ID) (network.Conn, error) {\n\t// Avoid typed nil issues.\n\tc, err := s.dialPeer(ctx, p)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn c, nil\n}\n\n// internal dial method that returns an unwrapped conn\n//\n// It is gated by the swarm's dial synchronization systems: dialsync and\n// dialbackoff.\nfunc (s *Swarm) dialPeer(ctx context.Context, p peer.ID) (*Conn, error) {\n\tlog.Debug(\"dialing peer\", \"source_peer\", s.local, \"destination_peer\", p)\n\terr := p.Validate()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif p == s.local {\n\t\treturn nil, ErrDialToSelf\n\t}\n\n\t// check if we already have an open (usable) connection.\n\tconn := s.bestAcceptableConnToPeer(ctx, p)\n\tif conn != nil {\n\t\treturn conn, nil\n\t}\n\n\tif s.gater != nil && !s.gater.InterceptPeerDial(p) {\n\t\tlog.Debug(\"gater disallowed outbound connection to peer\", \"peer\", p)\n\t\treturn nil, &DialError{Peer: p, Cause: ErrGaterDisallowedConnection}\n\t}\n\n\t// apply the DialPeer timeout\n\tctx, cancel := context.WithTimeout(ctx, network.GetDialPeerTimeout(ctx))\n\tdefer cancel()\n\n\tconn, err = s.dsync.Dial(ctx, p)\n\tif err == nil {\n\t\t// Ensure we connected to the correct peer.\n\t\t// This was most likely already checked by the security protocol, but it doesn't hurt do it again here.\n\t\tif conn.RemotePeer() != p {\n\t\t\tconn.Close()\n\t\t\tlog.Error(\"Handshake failed to properly authenticate peer\", \"authenticated\", conn.RemotePeer(), \"expected\", p)\n\t\t\treturn nil, fmt.Errorf(\"unexpected peer\")\n\t\t}\n\t\treturn conn, nil\n\t}\n\n\tlog.Debug(\"network finished dialing peer\", \"local\", s.local, \"remote\", p)\n\n\tif ctx.Err() != nil {\n\t\t// Context error trumps any dial errors as it was likely the ultimate cause.\n\t\treturn nil, ctx.Err()\n\t}\n\n\tif s.ctx.Err() != nil {\n\t\t// Ok, so the swarm is shutting down.\n\t\treturn nil, ErrSwarmClosed\n\t}\n\n\treturn nil, err\n}\n\n// dialWorkerLoop synchronizes and executes concurrent dials to a single peer\nfunc (s *Swarm) dialWorkerLoop(p peer.ID, reqch <-chan dialRequest) {\n\tw := newDialWorker(s, p, reqch, nil)\n\tw.loop()\n}\n\nfunc (s *Swarm) addrsForDial(ctx context.Context, p peer.ID) (goodAddrs []ma.Multiaddr, addrErrs []TransportError, err error) {\n\tpeerAddrs := s.peers.Addrs(p)\n\tif len(peerAddrs) == 0 {\n\t\treturn nil, nil, ErrNoAddresses\n\t}\n\n\t// Resolve dns or dnsaddrs\n\tresolved := s.resolveAddrs(ctx, peer.AddrInfo{ID: p, Addrs: peerAddrs})\n\n\tgoodAddrs = ma.Unique(resolved)\n\tgoodAddrs, addrErrs = s.filterKnownUndialables(p, goodAddrs)\n\tif forceDirect, _ := network.GetForceDirectDial(ctx); forceDirect {\n\t\tgoodAddrs = ma.FilterAddrs(goodAddrs, s.nonProxyAddr)\n\t}\n\n\tif len(goodAddrs) == 0 {\n\t\treturn nil, addrErrs, ErrNoGoodAddresses\n\t}\n\n\ts.peers.AddAddrs(p, goodAddrs, peerstore.TempAddrTTL)\n\n\treturn goodAddrs, addrErrs, nil\n}\n\nfunc startsWithDNSComponent(m ma.Multiaddr) bool {\n\tif m == nil {\n\t\treturn false\n\t}\n\tstartsWithDNS := false\n\t// Using ForEach to avoid allocating\n\tma.ForEach(m, func(c ma.Component) bool {\n\t\tswitch c.Protocol().Code {\n\t\tcase ma.P_DNS, ma.P_DNS4, ma.P_DNS6:\n\t\t\tstartsWithDNS = true\n\t\t}\n\n\t\treturn false\n\t})\n\treturn startsWithDNS\n}\n\nfunc stripP2PComponent(addrs []ma.Multiaddr) []ma.Multiaddr {\n\tfor i, addr := range addrs {\n\t\tif id, _ := peer.IDFromP2PAddr(addr); id != \"\" {\n\t\t\taddrs[i], _ = ma.SplitLast(addr)\n\t\t}\n\t}\n\treturn addrs\n}\n\ntype resolver struct {\n\tcanResolve func(ma.Multiaddr) bool\n\tresolve    func(ctx context.Context, maddr ma.Multiaddr, outputLimit int) ([]ma.Multiaddr, error)\n}\n\ntype resolveErr struct {\n\taddr ma.Multiaddr\n\terr  error\n}\n\nfunc chainResolvers(ctx context.Context, addrs []ma.Multiaddr, outputLimit int, resolvers []resolver) ([]ma.Multiaddr, []resolveErr) {\n\tnextAddrs := make([]ma.Multiaddr, 0, len(addrs))\n\terrs := make([]resolveErr, 0)\n\tfor _, r := range resolvers {\n\t\tfor _, a := range addrs {\n\t\t\tif !r.canResolve(a) {\n\t\t\t\tnextAddrs = append(nextAddrs, a)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif len(nextAddrs) >= outputLimit {\n\t\t\t\tnextAddrs = nextAddrs[:outputLimit]\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tnext, err := r.resolve(ctx, a, outputLimit-len(nextAddrs))\n\t\t\tif err != nil {\n\t\t\t\terrs = append(errs, resolveErr{addr: a, err: err})\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tnextAddrs = append(nextAddrs, next...)\n\t\t}\n\t\taddrs, nextAddrs = nextAddrs, addrs\n\t\tnextAddrs = nextAddrs[:0]\n\t}\n\treturn addrs, errs\n}\n\n// resolveAddrs resolves DNS/DNSADDR components in the given peer's addresses.\n// We want to resolve the DNS components to IP addresses becase we want the\n// swarm to manage ranking and dialing multiple connections, and a single DNS\n// address can resolve to multiple IP addresses.\nfunc (s *Swarm) resolveAddrs(ctx context.Context, pi peer.AddrInfo) []ma.Multiaddr {\n\tdnsAddrResolver := resolver{\n\t\tcanResolve: startsWithDNSADDR,\n\t\tresolve: func(ctx context.Context, maddr ma.Multiaddr, outputLimit int) ([]ma.Multiaddr, error) {\n\t\t\treturn s.multiaddrResolver.ResolveDNSAddr(ctx, pi.ID, maddr, maximumDNSADDRRecursion, outputLimit)\n\t\t},\n\t}\n\n\tvar skipped []ma.Multiaddr\n\tskipResolver := resolver{\n\t\tcanResolve: func(addr ma.Multiaddr) bool {\n\t\t\ttpt := s.TransportForDialing(addr)\n\t\t\tif tpt == nil {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\t_, ok := tpt.(transport.SkipResolver)\n\t\t\treturn ok\n\n\t\t},\n\t\tresolve: func(ctx context.Context, addr ma.Multiaddr, _ int) ([]ma.Multiaddr, error) {\n\t\t\ttpt := s.TransportForDialing(addr)\n\t\t\tresolver, ok := tpt.(transport.SkipResolver)\n\t\t\tif !ok {\n\t\t\t\treturn []ma.Multiaddr{addr}, nil\n\t\t\t}\n\t\t\tif resolver.SkipResolve(ctx, addr) {\n\t\t\t\tskipped = append(skipped, addr)\n\t\t\t\treturn nil, nil\n\t\t\t}\n\t\t\treturn []ma.Multiaddr{addr}, nil\n\t\t},\n\t}\n\n\ttptResolver := resolver{\n\t\tcanResolve: func(addr ma.Multiaddr) bool {\n\t\t\ttpt := s.TransportForDialing(addr)\n\t\t\tif tpt == nil {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\t_, ok := tpt.(transport.Resolver)\n\t\t\treturn ok\n\t\t},\n\t\tresolve: func(ctx context.Context, addr ma.Multiaddr, outputLimit int) ([]ma.Multiaddr, error) {\n\t\t\ttpt := s.TransportForDialing(addr)\n\t\t\tresolver, ok := tpt.(transport.Resolver)\n\t\t\tif !ok {\n\t\t\t\treturn []ma.Multiaddr{addr}, nil\n\t\t\t}\n\t\t\taddrs, err := resolver.Resolve(ctx, addr)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif len(addrs) > outputLimit {\n\t\t\t\taddrs = addrs[:outputLimit]\n\t\t\t}\n\t\t\treturn addrs, nil\n\t\t},\n\t}\n\n\tdnsResolver := resolver{\n\t\tcanResolve: startsWithDNSComponent,\n\t\tresolve:    s.multiaddrResolver.ResolveDNSComponent,\n\t}\n\taddrs, errs := chainResolvers(ctx, pi.Addrs, maximumResolvedAddresses, []resolver{dnsAddrResolver, skipResolver, tptResolver, dnsResolver})\n\tfor _, err := range errs {\n\t\tlog.Warn(\"Failed to resolve addr\", \"addr\", err.addr, \"err\", err.err)\n\t}\n\t// Add skipped addresses back to the resolved addresses\n\taddrs = append(addrs, skipped...)\n\treturn stripP2PComponent(addrs)\n}\n\nfunc (s *Swarm) dialNextAddr(ctx context.Context, p peer.ID, addr ma.Multiaddr, resch chan transport.DialUpdate) error {\n\t// check the dial backoff\n\tif forceDirect, _ := network.GetForceDirectDial(ctx); !forceDirect {\n\t\tif s.backf.Backoff(p, addr) {\n\t\t\treturn ErrDialBackoff\n\t\t}\n\t}\n\n\t// start the dial\n\ts.limitedDial(ctx, p, addr, resch)\n\n\treturn nil\n}\n\nfunc (s *Swarm) CanDial(p peer.ID, addr ma.Multiaddr) bool {\n\tdialable, _ := s.filterKnownUndialables(p, []ma.Multiaddr{addr})\n\treturn len(dialable) > 0\n}\n\nfunc (s *Swarm) nonProxyAddr(addr ma.Multiaddr) bool {\n\tt := s.TransportForDialing(addr)\n\treturn !t.Proxy()\n}\n\nvar quicDraft29DialMatcher = mafmt.And(mafmt.IP, mafmt.Base(ma.P_UDP), mafmt.Base(ma.P_QUIC))\n\n// filterKnownUndialables takes a list of multiaddrs, and removes those\n// that we definitely don't want to dial: addresses configured to be blocked,\n// IPv6 link-local addresses, addresses without a dial-capable transport,\n// addresses that we know to be our own, and addresses with a better transport\n// available. This is an optimization to avoid wasting time on dials that we\n// know are going to fail or for which we have a better alternative.\nfunc (s *Swarm) filterKnownUndialables(p peer.ID, addrs []ma.Multiaddr) (goodAddrs []ma.Multiaddr, addrErrs []TransportError) {\n\tlisAddrs, _ := s.InterfaceListenAddresses()\n\tvar ourAddrs []ma.Multiaddr\n\tfor _, addr := range lisAddrs {\n\t\t// we're only sure about filtering out /ip4 and /ip6 addresses, so far\n\t\tma.ForEach(addr, func(c ma.Component) bool {\n\t\t\tif c.Protocol().Code == ma.P_IP4 || c.Protocol().Code == ma.P_IP6 {\n\t\t\t\tourAddrs = append(ourAddrs, addr)\n\t\t\t}\n\t\t\treturn false\n\t\t})\n\t}\n\n\taddrErrs = make([]TransportError, 0, len(addrs))\n\n\t// The order of checking for transport and filtering low priority addrs is important. If we\n\t// can only dial /webtransport, we don't want to filter /webtransport addresses out because\n\t// the peer had a /quic-v1 address\n\n\t// filter addresses with no transport\n\taddrs = ma.FilterAddrs(addrs, func(a ma.Multiaddr) bool {\n\t\tif s.TransportForDialing(a) == nil {\n\t\t\te := ErrNoTransport\n\t\t\t// We used to support QUIC draft-29 for a long time.\n\t\t\t// Provide a more useful error when attempting to dial a QUIC draft-29 address.\n\t\t\tif quicDraft29DialMatcher.Matches(a) {\n\t\t\t\te = ErrQUICDraft29\n\t\t\t}\n\t\t\taddrErrs = append(addrErrs, TransportError{Address: a, Cause: e})\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t})\n\n\t// filter low priority addresses among the addresses we can dial\n\t// We don't return an error for these addresses\n\taddrs = filterLowPriorityAddresses(addrs)\n\n\t// remove black holed addrs\n\taddrs, blackHoledAddrs := s.bhd.FilterAddrs(addrs)\n\tfor _, a := range blackHoledAddrs {\n\t\taddrErrs = append(addrErrs, TransportError{Address: a, Cause: ErrDialRefusedBlackHole})\n\t}\n\n\treturn ma.FilterAddrs(addrs,\n\t\t// Linux and BSD treat an unspecified address when dialing as a localhost address.\n\t\t// Windows doesn't support this. We filter all such addresses out because peers\n\t\t// listening on unspecified addresses will advertise more specific addresses.\n\t\t// https://unix.stackexchange.com/a/419881\n\t\t// https://superuser.com/a/1755455\n\t\tfunc(addr ma.Multiaddr) bool {\n\t\t\treturn !manet.IsIPUnspecified(addr)\n\t\t},\n\t\tfunc(addr ma.Multiaddr) bool {\n\t\t\tif ma.Contains(ourAddrs, addr) {\n\t\t\t\taddrErrs = append(addrErrs, TransportError{Address: addr, Cause: ErrDialToSelf})\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn true\n\t\t},\n\t\t// TODO: Consider allowing link-local addresses\n\t\tfunc(addr ma.Multiaddr) bool { return !manet.IsIP6LinkLocal(addr) },\n\t\tfunc(addr ma.Multiaddr) bool {\n\t\t\tif s.gater != nil && !s.gater.InterceptAddrDial(p, addr) {\n\t\t\t\taddrErrs = append(addrErrs, TransportError{Address: addr, Cause: ErrGaterDisallowedConnection})\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn true\n\t\t},\n\t), addrErrs\n}\n\n// limitedDial will start a dial to the given peer when\n// it is able, respecting the various different types of rate\n// limiting that occur without using extra goroutines per addr\nfunc (s *Swarm) limitedDial(ctx context.Context, p peer.ID, a ma.Multiaddr, resp chan transport.DialUpdate) {\n\ttimeout := s.dialTimeout\n\tif manet.IsPrivateAddr(a) && s.dialTimeoutLocal < s.dialTimeout {\n\t\ttimeout = s.dialTimeoutLocal\n\t}\n\ts.limiter.AddDialJob(&dialJob{\n\t\taddr:    a,\n\t\tpeer:    p,\n\t\tresp:    resp,\n\t\tctx:     ctx,\n\t\ttimeout: timeout,\n\t})\n}\n\n// dialAddr is the actual dial for an addr, indirectly invoked through the limiter\nfunc (s *Swarm) dialAddr(ctx context.Context, p peer.ID, addr ma.Multiaddr, updCh chan<- transport.DialUpdate) (transport.CapableConn, error) {\n\t// Just to double check. Costs nothing.\n\tif s.local == p {\n\t\treturn nil, ErrDialToSelf\n\t}\n\t// Check before we start work\n\tif err := ctx.Err(); err != nil {\n\t\tlog.Debug(\"swarm not dialing. Context cancelled\", \"source_peer\", s.local, \"err\", err, \"destination_peer\", p, \"addr\", addr)\n\t\treturn nil, err\n\t}\n\tlog.Debug(\"swarm dialing peer\", \"source_peer\", s.local, \"destination_peer\", p, \"addr\", addr)\n\n\ttpt := s.TransportForDialing(addr)\n\tif tpt == nil {\n\t\treturn nil, ErrNoTransport\n\t}\n\n\tstart := time.Now()\n\tvar connC transport.CapableConn\n\tvar err error\n\tif du, ok := tpt.(transport.DialUpdater); ok {\n\t\tconnC, err = du.DialWithUpdates(ctx, addr, p, updCh)\n\t} else {\n\t\tconnC, err = tpt.Dial(ctx, addr, p)\n\t}\n\n\t// We're recording any error as a failure here.\n\t// Notably, this also applies to cancellations (i.e. if another dial attempt was faster).\n\t// This is ok since the black hole detector uses a very low threshold (5%).\n\ts.bhd.RecordResult(addr, err == nil)\n\n\tif err != nil {\n\t\tif s.metricsTracer != nil {\n\t\t\ts.metricsTracer.FailedDialing(addr, err, context.Cause(ctx))\n\t\t}\n\t\treturn nil, err\n\t}\n\tcanonicallog.LogPeerStatus(100, connC.RemotePeer(), connC.RemoteMultiaddr(), \"connection_status\", \"established\", \"dir\", \"outbound\")\n\tif s.metricsTracer != nil {\n\t\tconnWithMetrics := wrapWithMetrics(connC, s.metricsTracer, start, network.DirOutbound)\n\t\tconnWithMetrics.completedHandshake()\n\t\tconnC = connWithMetrics\n\t}\n\n\t// Trust the transport? Yeah... right.\n\tif connC.RemotePeer() != p {\n\t\tconnC.Close()\n\t\terr = fmt.Errorf(\"BUG in transport %T: tried to dial %s, dialed %s\", tpt, p, connC.RemotePeer())\n\t\tlog.Error(\"BUG in transport: peer mismatch\", \"transport_type\", fmt.Sprintf(\"%T\", tpt), \"expected_peer\", p, \"observed_peer\", connC.RemotePeer())\n\t\treturn nil, err\n\t}\n\n\t// success! we got one!\n\treturn connC, nil\n}\n\n// TODO We should have a `IsFdConsuming() bool` method on the `Transport` interface in go-libp2p/core/transport.\n// This function checks if any of the transport protocols in the address requires a file descriptor.\n// For now:\n// A Non-circuit address which has the TCP/UNIX protocol is deemed FD consuming.\n// For a circuit-relay address, we look at the address of the relay server/proxy\n// and use the same logic as above to decide.\nfunc isFdConsumingAddr(addr ma.Multiaddr) bool {\n\tfirst, _ := ma.SplitFunc(addr, func(c ma.Component) bool {\n\t\treturn c.Protocol().Code == ma.P_CIRCUIT\n\t})\n\n\t// for safety\n\tif first == nil {\n\t\treturn true\n\t}\n\n\t_, err1 := first.ValueForProtocol(ma.P_TCP)\n\t_, err2 := first.ValueForProtocol(ma.P_UNIX)\n\treturn err1 == nil || err2 == nil\n}\n\nfunc isRelayAddr(addr ma.Multiaddr) bool {\n\t_, err := addr.ValueForProtocol(ma.P_CIRCUIT)\n\treturn err == nil\n}\n\n// filterLowPriorityAddresses removes addresses inplace for which we have a better alternative\n//  1. If a /quic-v1 address is present, filter out /quic and /webtransport address on the same 2-tuple:\n//     QUIC v1 is preferred over the deprecated QUIC draft-29, and given the choice, we prefer using\n//     raw QUIC over using WebTransport.\n//  2. If a /tcp address is present, filter out /ws or /wss addresses on the same 2-tuple:\n//     We prefer using raw TCP over using WebSocket.\nfunc filterLowPriorityAddresses(addrs []ma.Multiaddr) []ma.Multiaddr {\n\t// make a map of QUIC v1 and TCP AddrPorts.\n\tquicV1Addr := make(map[netip.AddrPort]struct{})\n\ttcpAddr := make(map[netip.AddrPort]struct{})\n\tfor _, a := range addrs {\n\t\tswitch {\n\t\tcase isProtocolAddr(a, ma.P_WEBTRANSPORT):\n\t\tcase isProtocolAddr(a, ma.P_QUIC_V1):\n\t\t\tap, err := addrPort(a, ma.P_UDP)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tquicV1Addr[ap] = struct{}{}\n\t\tcase isProtocolAddr(a, ma.P_WS) || isProtocolAddr(a, ma.P_WSS):\n\t\tcase isProtocolAddr(a, ma.P_TCP):\n\t\t\tap, err := addrPort(a, ma.P_TCP)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\ttcpAddr[ap] = struct{}{}\n\t\t}\n\t}\n\n\ti := 0\n\tfor _, a := range addrs {\n\t\tswitch {\n\t\tcase isProtocolAddr(a, ma.P_WEBTRANSPORT) || isProtocolAddr(a, ma.P_QUIC):\n\t\t\tap, err := addrPort(a, ma.P_UDP)\n\t\t\tif err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif _, ok := quicV1Addr[ap]; ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\tcase isProtocolAddr(a, ma.P_WS) || isProtocolAddr(a, ma.P_WSS):\n\t\t\tap, err := addrPort(a, ma.P_TCP)\n\t\t\tif err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif _, ok := tcpAddr[ap]; ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\taddrs[i] = a\n\t\ti++\n\t}\n\treturn addrs[:i]\n}\n\n// addrPort returns the ip and port for a. p should be either ma.P_TCP or ma.P_UDP.\n// a must be an (ip, TCP) or (ip, udp) address.\nfunc addrPort(a ma.Multiaddr, p int) (netip.AddrPort, error) {\n\tip, err := manet.ToIP(a)\n\tif err != nil {\n\t\treturn netip.AddrPort{}, err\n\t}\n\tport, err := a.ValueForProtocol(p)\n\tif err != nil {\n\t\treturn netip.AddrPort{}, err\n\t}\n\tpi, err := strconv.Atoi(port)\n\tif err != nil {\n\t\treturn netip.AddrPort{}, err\n\t}\n\taddr, ok := netip.AddrFromSlice(ip)\n\tif !ok {\n\t\treturn netip.AddrPort{}, fmt.Errorf(\"failed to parse IP %s\", ip)\n\t}\n\treturn netip.AddrPortFrom(addr, uint16(pi)), nil\n}\n"
  },
  {
    "path": "p2p/net/swarm/swarm_dial_test.go",
    "content": "package swarm\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/rand\"\n\t\"errors\"\n\t\"net\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\t\"github.com/libp2p/go-libp2p/core/test\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/eventbus\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoremem\"\n\tlibp2pquic \"github.com/libp2p/go-libp2p/p2p/transport/quic\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/quicreuse\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/tcp\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/websocket\"\n\tlibp2pwebtransport \"github.com/libp2p/go-libp2p/p2p/transport/webtransport\"\n\t\"github.com/quic-go/quic-go\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmadns \"github.com/multiformats/go-multiaddr-dns\"\n\tmatest \"github.com/multiformats/go-multiaddr/matest\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestAddrsForDial(t *testing.T) {\n\tmockResolver := madns.MockResolver{IP: make(map[string][]net.IPAddr)}\n\tipaddr, err := net.ResolveIPAddr(\"ip4\", \"1.2.3.4\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tmockResolver.IP[\"example.com\"] = []net.IPAddr{*ipaddr}\n\n\tresolver, err := madns.NewResolver(madns.WithDomainResolver(\"example.com\", &mockResolver))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tpriv, _, err := crypto.GenerateEd25519Key(rand.Reader)\n\trequire.NoError(t, err)\n\tid, err := peer.IDFromPrivateKey(priv)\n\trequire.NoError(t, err)\n\n\tps, err := pstoremem.NewPeerstore()\n\trequire.NoError(t, err)\n\tps.AddPubKey(id, priv.GetPublic())\n\tps.AddPrivKey(id, priv)\n\tt.Cleanup(func() { ps.Close() })\n\n\ttpt, err := websocket.New(nil, &network.NullResourceManager{}, nil)\n\trequire.NoError(t, err)\n\ts, err := NewSwarm(id, ps, eventbus.NewBus(), WithMultiaddrResolver(ResolverFromMaDNS{resolver}))\n\trequire.NoError(t, err)\n\tdefer s.Close()\n\terr = s.AddTransport(tpt)\n\trequire.NoError(t, err)\n\n\totherPeer := test.RandPeerIDFatal(t)\n\n\tps.AddAddr(otherPeer, ma.StringCast(\"/dns4/example.com/tcp/1234/wss\"), time.Hour)\n\n\tctx := context.Background()\n\tmas, _, err := s.addrsForDial(ctx, otherPeer)\n\trequire.NoError(t, err)\n\n\trequire.NotZero(t, len(mas))\n}\n\nfunc TestDedupAddrsForDial(t *testing.T) {\n\tmockResolver := madns.MockResolver{IP: make(map[string][]net.IPAddr)}\n\tipaddr, err := net.ResolveIPAddr(\"ip4\", \"1.2.3.4\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tmockResolver.IP[\"example.com\"] = []net.IPAddr{*ipaddr}\n\n\tresolver, err := madns.NewResolver(madns.WithDomainResolver(\"example.com\", &mockResolver))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tpriv, _, err := crypto.GenerateEd25519Key(rand.Reader)\n\trequire.NoError(t, err)\n\tid, err := peer.IDFromPrivateKey(priv)\n\trequire.NoError(t, err)\n\n\tps, err := pstoremem.NewPeerstore()\n\trequire.NoError(t, err)\n\tps.AddPubKey(id, priv.GetPublic())\n\tps.AddPrivKey(id, priv)\n\tt.Cleanup(func() { ps.Close() })\n\n\ts, err := NewSwarm(id, ps, eventbus.NewBus(), WithMultiaddrResolver(ResolverFromMaDNS{resolver}))\n\trequire.NoError(t, err)\n\tdefer s.Close()\n\n\ttpt, err := tcp.NewTCPTransport(nil, &network.NullResourceManager{}, nil)\n\trequire.NoError(t, err)\n\terr = s.AddTransport(tpt)\n\trequire.NoError(t, err)\n\n\totherPeer := test.RandPeerIDFatal(t)\n\n\tps.AddAddr(otherPeer, ma.StringCast(\"/dns4/example.com/tcp/1234\"), time.Hour)\n\tps.AddAddr(otherPeer, ma.StringCast(\"/ip4/1.2.3.4/tcp/1234\"), time.Hour)\n\n\tctx := context.Background()\n\tmas, _, err := s.addrsForDial(ctx, otherPeer)\n\trequire.NoError(t, err)\n\n\trequire.Len(t, mas, 1)\n}\n\nfunc newTestSwarmWithResolver(t *testing.T, resolver *madns.Resolver) *Swarm {\n\tpriv, _, err := crypto.GenerateEd25519Key(rand.Reader)\n\trequire.NoError(t, err)\n\tid, err := peer.IDFromPrivateKey(priv)\n\trequire.NoError(t, err)\n\tps, err := pstoremem.NewPeerstore()\n\trequire.NoError(t, err)\n\tps.AddPubKey(id, priv.GetPublic())\n\tps.AddPrivKey(id, priv)\n\tt.Cleanup(func() { ps.Close() })\n\ts, err := NewSwarm(id, ps, eventbus.NewBus(), WithMultiaddrResolver(ResolverFromMaDNS{resolver}))\n\trequire.NoError(t, err)\n\tt.Cleanup(func() {\n\t\ts.Close()\n\t})\n\n\t// Add a tcp transport so that we know we can dial a tcp multiaddr and we don't filter it out.\n\ttpt, err := tcp.NewTCPTransport(nil, &network.NullResourceManager{}, nil)\n\trequire.NoError(t, err)\n\terr = s.AddTransport(tpt)\n\trequire.NoError(t, err)\n\n\tconnmgr, err := quicreuse.NewConnManager(quic.StatelessResetKey{}, quic.TokenGeneratorKey{})\n\trequire.NoError(t, err)\n\tquicTpt, err := libp2pquic.NewTransport(priv, connmgr, nil, nil, &network.NullResourceManager{})\n\trequire.NoError(t, err)\n\terr = s.AddTransport(quicTpt)\n\trequire.NoError(t, err)\n\n\twtTpt, err := libp2pwebtransport.New(priv, nil, connmgr, nil, &network.NullResourceManager{})\n\trequire.NoError(t, err)\n\terr = s.AddTransport(wtTpt)\n\trequire.NoError(t, err)\n\n\twsTpt, err := websocket.New(nil, &network.NullResourceManager{}, nil)\n\trequire.NoError(t, err)\n\terr = s.AddTransport(wsTpt)\n\trequire.NoError(t, err)\n\n\treturn s\n}\n\nfunc TestAddrResolution(t *testing.T) {\n\tctx := context.Background()\n\n\tp1 := test.RandPeerIDFatal(t)\n\tp2 := test.RandPeerIDFatal(t)\n\taddr1 := ma.StringCast(\"/dnsaddr/example.com\")\n\taddr2 := ma.StringCast(\"/ip4/192.0.2.1/tcp/123\")\n\n\tp2paddr2 := ma.StringCast(\"/ip4/192.0.2.1/tcp/123/p2p/\" + p1.String())\n\tp2paddr3 := ma.StringCast(\"/ip4/192.0.2.1/tcp/123/p2p/\" + p2.String())\n\n\tbackend := &madns.MockResolver{\n\t\tTXT: map[string][]string{\"_dnsaddr.example.com\": {\n\t\t\t\"dnsaddr=\" + p2paddr2.String(), \"dnsaddr=\" + p2paddr3.String(),\n\t\t}},\n\t}\n\tresolver, err := madns.NewResolver(madns.WithDefaultResolver(backend))\n\trequire.NoError(t, err)\n\n\ts := newTestSwarmWithResolver(t, resolver)\n\n\ts.peers.AddAddr(p1, addr1, time.Hour)\n\n\ttctx, cancel := context.WithTimeout(ctx, time.Millisecond*100)\n\tdefer cancel()\n\tmas, _, err := s.addrsForDial(tctx, p1)\n\trequire.NoError(t, err)\n\n\trequire.Len(t, mas, 1)\n\tmatest.AssertMultiaddrsContain(t, mas, addr2)\n\n\taddrs := s.peers.Addrs(p1)\n\trequire.Len(t, addrs, 2)\n\tmatest.AssertMultiaddrsContain(t, addrs, addr1)\n\tmatest.AssertMultiaddrsContain(t, addrs, addr2)\n}\n\nfunc TestAddrResolutionRecursive(t *testing.T) {\n\tp1 := test.RandPeerIDFatal(t)\n\tp2 := test.RandPeerIDFatal(t)\n\n\taddr1 := ma.StringCast(\"/dnsaddr/example.com\")\n\taddr2 := ma.StringCast(\"/ip4/192.0.2.1/tcp/123\")\n\tp2paddr1 := ma.StringCast(\"/dnsaddr/example.com/p2p/\" + p1.String())\n\tp2paddr2 := ma.StringCast(\"/dnsaddr/example.com/p2p/\" + p2.String())\n\tp2paddr1i := ma.StringCast(\"/dnsaddr/foo.example.com/p2p/\" + p1.String())\n\tp2paddr2i := ma.StringCast(\"/dnsaddr/bar.example.com/p2p/\" + p2.String())\n\tp2paddr1f := ma.StringCast(\"/ip4/192.0.2.1/tcp/123/p2p/\" + p1.String())\n\n\tbackend := &madns.MockResolver{\n\t\tTXT: map[string][]string{\n\t\t\t\"_dnsaddr.example.com\": {\n\t\t\t\t\"dnsaddr=\" + p2paddr1i.String(),\n\t\t\t\t\"dnsaddr=\" + p2paddr2i.String(),\n\t\t\t},\n\t\t\t\"_dnsaddr.foo.example.com\": {\"dnsaddr=\" + p2paddr1f.String()},\n\t\t\t\"_dnsaddr.bar.example.com\": {\"dnsaddr=\" + p2paddr2i.String()},\n\t\t},\n\t}\n\tresolver, err := madns.NewResolver(madns.WithDefaultResolver(backend))\n\trequire.NoError(t, err)\n\n\ts := newTestSwarmWithResolver(t, resolver)\n\n\tpi1, err := peer.AddrInfoFromP2pAddr(p2paddr1)\n\trequire.NoError(t, err)\n\n\ttctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*100)\n\tdefer cancel()\n\ts.Peerstore().AddAddrs(pi1.ID, pi1.Addrs, peerstore.TempAddrTTL)\n\t_, _, err = s.addrsForDial(tctx, p1)\n\trequire.NoError(t, err)\n\n\taddrs1 := s.Peerstore().Addrs(pi1.ID)\n\trequire.Len(t, addrs1, 2)\n\tmatest.AssertMultiaddrsContain(t, addrs1, addr1)\n\tmatest.AssertMultiaddrsContain(t, addrs1, addr2)\n\n\tpi2, err := peer.AddrInfoFromP2pAddr(p2paddr2)\n\trequire.NoError(t, err)\n\n\ts.Peerstore().AddAddrs(pi2.ID, pi2.Addrs, peerstore.TempAddrTTL)\n\t_, _, err = s.addrsForDial(tctx, p2)\n\t// This never resolves to a good address\n\trequire.Equal(t, ErrNoGoodAddresses, err)\n\n\taddrs2 := s.Peerstore().Addrs(pi2.ID)\n\trequire.Len(t, addrs2, 1)\n\tmatest.AssertMultiaddrsContain(t, addrs2, addr1)\n}\n\n// see https://github.com/libp2p/go-libp2p/issues/2562\nfunc TestAddrResolutionRecursiveTransportSpecific(t *testing.T) {\n\tp := test.RandPeerIDFatal(t)\n\n\tbackend := &madns.MockResolver{\n\t\tIP: map[string][]net.IPAddr{\n\t\t\t\"sub.example.com\": {net.IPAddr{IP: net.IPv4(1, 2, 3, 4)}},\n\t\t},\n\t\tTXT: map[string][]string{\n\t\t\t\"_dnsaddr.example.com\": {\"dnsaddr=/dns4/sub.example.com/tcp/443/wss/p2p/\" + p.String()},\n\t\t},\n\t}\n\tresolver, err := madns.NewResolver(madns.WithDefaultResolver(backend))\n\trequire.NoError(t, err)\n\n\ts := newTestSwarmWithResolver(t, resolver)\n\tpi1, err := peer.AddrInfoFromP2pAddr(ma.StringCast(\"/dnsaddr/example.com/p2p/\" + p.String()))\n\trequire.NoError(t, err)\n\n\ttctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*100)\n\tdefer cancel()\n\ts.Peerstore().AddAddrs(pi1.ID, pi1.Addrs, peerstore.TempAddrTTL)\n\taddrs, _, err := s.addrsForDial(tctx, p)\n\trequire.NoError(t, err)\n\trequire.Len(t, addrs, 1)\n\trequire.Equal(t, \"/ip4/1.2.3.4/tcp/443/tls/sni/sub.example.com/ws\", addrs[0].String())\n}\n\nfunc TestAddrsForDialFiltering(t *testing.T) {\n\tq1 := ma.StringCast(\"/ip4/1.2.3.4/udp/1/quic-v1\")\n\tq1v1 := ma.StringCast(\"/ip4/1.2.3.4/udp/1/quic-v1\")\n\twt1 := ma.StringCast(\"/ip4/1.2.3.4/udp/1/quic-v1/webtransport/\")\n\n\tq2 := ma.StringCast(\"/ip4/1.2.3.4/udp/2/quic-v1\")\n\tq2v1 := ma.StringCast(\"/ip4/1.2.3.4/udp/2/quic-v1\")\n\twt2 := ma.StringCast(\"/ip4/1.2.3.4/udp/2/quic-v1/webtransport/\")\n\n\tq3 := ma.StringCast(\"/ip4/1.2.3.4/udp/3/quic-v1\")\n\n\tt1 := ma.StringCast(\"/ip4/1.2.3.4/tcp/1\")\n\tws1 := ma.StringCast(\"/ip4/1.2.3.4/tcp/1/ws\")\n\n\tunSpecQ := ma.StringCast(\"/ip4/0.0.0.0/udp/2/quic-v1\")\n\tunSpecT := ma.StringCast(\"/ip6/::/tcp/2/\")\n\n\tresolver, err := madns.NewResolver(madns.WithDefaultResolver(&madns.MockResolver{}))\n\trequire.NoError(t, err)\n\ts := newTestSwarmWithResolver(t, resolver)\n\tourAddrs := s.ListenAddresses()\n\n\ttestCases := []struct {\n\t\tname   string\n\t\tinput  []ma.Multiaddr\n\t\toutput []ma.Multiaddr\n\t}{\n\t\t{\n\t\t\tname:   \"quic-filtered\",\n\t\t\tinput:  []ma.Multiaddr{q1, q1v1, q2, q2v1, q3},\n\t\t\toutput: []ma.Multiaddr{q1v1, q2v1, q3},\n\t\t},\n\t\t{\n\t\t\tname:   \"webtransport-filtered\",\n\t\t\tinput:  []ma.Multiaddr{q1, q1v1, wt1, wt2},\n\t\t\toutput: []ma.Multiaddr{q1v1, wt2},\n\t\t},\n\t\t{\n\t\t\tname:   \"all\",\n\t\t\tinput:  []ma.Multiaddr{q1, q1v1, wt1, q2, q2v1, wt2, t1, ws1},\n\t\t\toutput: []ma.Multiaddr{q1v1, q2v1, t1},\n\t\t},\n\t\t{\n\t\t\tname:   \"our-addrs-filtered\",\n\t\t\tinput:  append([]ma.Multiaddr{q1}, ourAddrs...),\n\t\t\toutput: []ma.Multiaddr{q1},\n\t\t},\n\t\t{\n\t\t\tname:   \"unspecified-filtered\",\n\t\t\tinput:  []ma.Multiaddr{q1v1, t1, unSpecQ, unSpecT},\n\t\t\toutput: []ma.Multiaddr{q1v1, t1},\n\t\t},\n\t}\n\n\tctx := context.Background()\n\tp1 := test.RandPeerIDFatal(t)\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ts.Peerstore().ClearAddrs(p1)\n\t\t\ts.Peerstore().AddAddrs(p1, tc.input, peerstore.PermanentAddrTTL)\n\t\t\tresult, _, err := s.addrsForDial(ctx, p1)\n\t\t\trequire.NoError(t, err)\n\t\t\tsort.Slice(result, func(i, j int) bool { return bytes.Compare(result[i].Bytes(), result[j].Bytes()) < 0 })\n\t\t\tsort.Slice(tc.output, func(i, j int) bool { return bytes.Compare(tc.output[i].Bytes(), tc.output[j].Bytes()) < 0 })\n\t\t\tif len(result) != len(tc.output) {\n\t\t\t\tt.Fatalf(\"output mismatch got: %s want: %s\", result, tc.output)\n\t\t\t}\n\t\t\tfor i := range result {\n\t\t\t\tif !result[i].Equal(tc.output[i]) {\n\t\t\t\t\tt.Fatalf(\"output mismatch got: %s want: %s\", result, tc.output)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBlackHoledAddrBlocked(t *testing.T) {\n\tresolver, err := madns.NewResolver()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\ts := newTestSwarmWithResolver(t, resolver)\n\tdefer s.Close()\n\n\tn := 3\n\ts.bhd.ipv6 = &BlackHoleSuccessCounter{N: n, MinSuccesses: 1, Name: \"IPv6\"}\n\n\t// All dials to this addr will fail.\n\t// manet.IsPublic is aggressive for IPv6 addresses. Use a NAT64 address.\n\taddr := ma.StringCast(\"/ip6/64:ff9b::1.2.3.4/tcp/54321/\")\n\n\tp, err := test.RandPeerID()\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\ts.Peerstore().AddAddr(p, addr, peerstore.PermanentAddrTTL)\n\n\t// do 1 extra dial to ensure that the blackHoleDetector state is updated since it\n\t// happens in a different goroutine\n\tfor i := 0; i < n+1; i++ {\n\t\ts.backf.Clear(p)\n\t\tctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)\n\t\tconn, err := s.DialPeer(ctx, p)\n\t\tif err == nil || conn != nil {\n\t\t\tt.Fatalf(\"expected dial to fail\")\n\t\t}\n\t\tcancel()\n\t}\n\ts.backf.Clear(p)\n\tctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)\n\tdefer cancel()\n\tconn, err := s.DialPeer(ctx, p)\n\trequire.Nil(t, conn)\n\tvar de *DialError\n\tif !errors.As(err, &de) {\n\t\tt.Fatalf(\"expected to receive an error of type *DialError, got %s of type %T\", err, err)\n\t}\n\trequire.ErrorIs(t, err, ErrDialRefusedBlackHole)\n}\n\ntype mockDNSResolver struct {\n\tipsToReturn  []net.IPAddr\n\ttxtsToReturn []string\n}\n\nvar _ madns.BasicResolver = (*mockDNSResolver)(nil)\n\nfunc (m *mockDNSResolver) LookupIPAddr(_ context.Context, _ string) ([]net.IPAddr, error) {\n\treturn m.ipsToReturn, nil\n}\n\nfunc (m *mockDNSResolver) LookupTXT(_ context.Context, _ string) ([]string, error) {\n\treturn m.txtsToReturn, nil\n}\n\nfunc TestSkipDialingManyDNS(t *testing.T) {\n\tresolver, err := madns.NewResolver(madns.WithDefaultResolver(&mockDNSResolver{ipsToReturn: []net.IPAddr{{IP: net.ParseIP(\"1.2.3.4\")}, {IP: net.ParseIP(\"1.2.3.5\")}}}))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\ts := newTestSwarmWithResolver(t, resolver)\n\tdefer s.Close()\n\tid := test.RandPeerIDFatal(t)\n\taddr := ma.StringCast(\"/dns/example.com/udp/1234/p2p-circuit/dns/example.com/p2p-circuit/dns/example.com\")\n\n\tresolved := s.resolveAddrs(context.Background(), peer.AddrInfo{ID: id, Addrs: []ma.Multiaddr{addr}})\n\trequire.NoError(t, err)\n\trequire.Less(t, len(resolved), 3, \"got: %v\", resolved)\n}\n"
  },
  {
    "path": "p2p/net/swarm/swarm_event_test.go",
    "content": "package swarm_test\n\nimport (\n\t\"context\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/event\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/eventbus\"\n\t. \"github.com/libp2p/go-libp2p/p2p/net/swarm\"\n\tswarmt \"github.com/libp2p/go-libp2p/p2p/net/swarm/testing\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc newSwarmWithSubscription(t *testing.T) (*Swarm, event.Subscription) {\n\tt.Helper()\n\tbus := eventbus.NewBus()\n\tsw := swarmt.GenSwarm(t, swarmt.EventBus(bus))\n\tt.Cleanup(func() { sw.Close() })\n\tsub, err := bus.Subscribe(new(event.EvtPeerConnectednessChanged))\n\trequire.NoError(t, err)\n\tt.Cleanup(func() { sub.Close() })\n\treturn sw, sub\n}\n\nfunc checkEvent(t *testing.T, sub event.Subscription, expected event.EvtPeerConnectednessChanged) {\n\tt.Helper()\n\tselect {\n\tcase ev, ok := <-sub.Out():\n\t\trequire.True(t, ok)\n\t\tevt := ev.(event.EvtPeerConnectednessChanged)\n\t\trequire.Equal(t, expected.Connectedness, evt.Connectedness, \"wrong connectedness state\")\n\t\trequire.Equal(t, expected.Peer, evt.Peer)\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"didn't get PeerConnectedness event\")\n\t}\n\n\t// check that there are no more events\n\tselect {\n\tcase <-sub.Out():\n\t\tt.Fatal(\"didn't expect any more events\")\n\tcase <-time.After(100 * time.Millisecond):\n\t\treturn\n\t}\n}\n\nfunc TestConnectednessEventsSingleConn(t *testing.T) {\n\ts1, sub1 := newSwarmWithSubscription(t)\n\ts2, sub2 := newSwarmWithSubscription(t)\n\n\ts1.Peerstore().AddAddrs(s2.LocalPeer(), []ma.Multiaddr{s2.ListenAddresses()[0]}, time.Hour)\n\t_, err := s1.DialPeer(context.Background(), s2.LocalPeer())\n\trequire.NoError(t, err)\n\n\tcheckEvent(t, sub1, event.EvtPeerConnectednessChanged{Peer: s2.LocalPeer(), Connectedness: network.Connected})\n\tcheckEvent(t, sub2, event.EvtPeerConnectednessChanged{Peer: s1.LocalPeer(), Connectedness: network.Connected})\n\n\tfor _, c := range s2.ConnsToPeer(s1.LocalPeer()) {\n\t\trequire.NoError(t, c.Close())\n\t}\n\tcheckEvent(t, sub1, event.EvtPeerConnectednessChanged{Peer: s2.LocalPeer(), Connectedness: network.NotConnected})\n\tcheckEvent(t, sub2, event.EvtPeerConnectednessChanged{Peer: s1.LocalPeer(), Connectedness: network.NotConnected})\n}\n\nfunc TestNoDeadlockWhenConsumingConnectednessEvents(t *testing.T) {\n\tctx := context.Background()\n\tctx, cancel := context.WithCancel(ctx)\n\tdefer cancel()\n\n\tdialerEventBus := eventbus.NewBus()\n\tdialer := swarmt.GenSwarm(t, swarmt.OptDialOnly, swarmt.EventBus(dialerEventBus))\n\tdefer dialer.Close()\n\n\tlistener := swarmt.GenSwarm(t, swarmt.OptDialOnly)\n\taddrsToListen := []ma.Multiaddr{\n\t\tma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1\"),\n\t}\n\n\tif err := listener.Listen(addrsToListen...); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tlistenedAddrs := listener.ListenAddresses()\n\n\tdialer.Peerstore().AddAddrs(listener.LocalPeer(), listenedAddrs, time.Hour)\n\n\tsub, err := dialerEventBus.Subscribe(new(event.EvtPeerConnectednessChanged))\n\trequire.NoError(t, err)\n\n\t// A slow consumer\n\tgo func() {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn\n\t\t\tcase <-sub.Out():\n\t\t\t\ttime.Sleep(100 * time.Millisecond)\n\t\t\t\t// Do something with the swarm that needs the conns lock\n\t\t\t\t_ = dialer.ConnsToPeer(listener.LocalPeer())\n\t\t\t\ttime.Sleep(100 * time.Millisecond)\n\t\t\t}\n\t\t}\n\t}()\n\n\tfor range 10 {\n\t\t// Connect and disconnect to trigger a bunch of events\n\t\t_, err := dialer.DialPeer(context.Background(), listener.LocalPeer())\n\t\trequire.NoError(t, err)\n\t\tdialer.ClosePeer(listener.LocalPeer())\n\t}\n\n\t// The test should finish without deadlocking\n}\n\nfunc TestConnectednessEvents(t *testing.T) {\n\ts1, sub1 := newSwarmWithSubscription(t)\n\tconst N = 100\n\tpeers := make([]*Swarm, N)\n\tfor i := range N {\n\t\tpeers[i] = swarmt.GenSwarm(t)\n\t}\n\n\t// First check all connected events\n\tdone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(done)\n\t\tfor range N {\n\t\t\te := <-sub1.Out()\n\t\t\tevt, ok := e.(event.EvtPeerConnectednessChanged)\n\t\t\tif !ok {\n\t\t\t\tt.Error(\"invalid event received\", e)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif evt.Connectedness != network.Connected {\n\t\t\t\tt.Errorf(\"invalid event received: expected: Connected, got: %s\", evt)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\tfor i := range N {\n\t\ts1.Peerstore().AddAddrs(peers[i].LocalPeer(), []ma.Multiaddr{peers[i].ListenAddresses()[0]}, time.Hour)\n\t\t_, err := s1.DialPeer(context.Background(), peers[i].LocalPeer())\n\t\trequire.NoError(t, err)\n\t}\n\tselect {\n\tcase <-done:\n\tcase <-time.After(10 * time.Second):\n\t\tt.Fatal(\"expected all connectedness events to be completed\")\n\t}\n\n\t// Disconnect some peers\n\tdone = make(chan struct{})\n\tgo func() {\n\t\tdefer close(done)\n\t\tfor range N / 2 {\n\t\t\te := <-sub1.Out()\n\t\t\tevt, ok := e.(event.EvtPeerConnectednessChanged)\n\t\t\tif !ok {\n\t\t\t\tt.Error(\"invalid event received\", e)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif evt.Connectedness != network.NotConnected {\n\t\t\t\tt.Errorf(\"invalid event received: expected: NotConnected, got: %s\", evt)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\tfor i := range N / 2 {\n\t\terr := s1.ClosePeer(peers[i].LocalPeer())\n\t\trequire.NoError(t, err)\n\t}\n\tselect {\n\tcase <-done:\n\tcase <-time.After(10 * time.Second):\n\t\tt.Fatal(\"expected all disconnected events to be completed\")\n\t}\n\n\t// Check for disconnected events on swarm close\n\tdone = make(chan struct{})\n\tgo func() {\n\t\tdefer close(done)\n\t\tfor i := N / 2; i < N; i++ {\n\t\t\te := <-sub1.Out()\n\t\t\tevt, ok := e.(event.EvtPeerConnectednessChanged)\n\t\t\tif !ok {\n\t\t\t\tt.Error(\"invalid event received\", e)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif evt.Connectedness != network.NotConnected {\n\t\t\t\tt.Errorf(\"invalid event received: expected: NotConnected, got: %s\", evt)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\ts1.Close()\n\tselect {\n\tcase <-done:\n\tcase <-time.After(10 * time.Second):\n\t\tt.Fatal(\"expected all disconnected events after swarm close to be completed\")\n\t}\n}\n\nfunc TestConnectednessEventDeadlock(t *testing.T) {\n\ts1, sub1 := newSwarmWithSubscription(t)\n\tconst N = 100\n\tpeers := make([]*Swarm, N)\n\tfor i := range N {\n\t\tpeers[i] = swarmt.GenSwarm(t)\n\t}\n\n\t// First check all connected events\n\tdone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(done)\n\t\tcount := 0\n\t\tfor count < N {\n\t\t\te := <-sub1.Out()\n\t\t\t// sleep to simulate a slow consumer\n\t\t\tevt, ok := e.(event.EvtPeerConnectednessChanged)\n\t\t\tif !ok {\n\t\t\t\tt.Error(\"invalid event received\", e)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif evt.Connectedness != network.Connected {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tcount++\n\t\t\ts1.ClosePeer(evt.Peer)\n\t\t}\n\t}()\n\tfor i := range N {\n\t\ts1.Peerstore().AddAddrs(peers[i].LocalPeer(), []ma.Multiaddr{peers[i].ListenAddresses()[0]}, time.Hour)\n\t\tgo func(i int) {\n\t\t\t_, err := s1.DialPeer(context.Background(), peers[i].LocalPeer())\n\t\t\tassert.NoError(t, err)\n\t\t}(i)\n\t}\n\tselect {\n\tcase <-done:\n\tcase <-time.After(100 * time.Second):\n\t\tt.Fatal(\"expected all connectedness events to be completed\")\n\t}\n}\n\nfunc TestConnectednessEventDeadlockWithDial(t *testing.T) {\n\ts1, sub1 := newSwarmWithSubscription(t)\n\tconst N = 200\n\tpeers := make([]*Swarm, N)\n\tfor i := range N {\n\t\tpeers[i] = swarmt.GenSwarm(t)\n\t}\n\tpeers2 := make([]*Swarm, N)\n\tfor i := range N {\n\t\tpeers2[i] = swarmt.GenSwarm(t)\n\t}\n\n\t// First check all connected events\n\tdone := make(chan struct{})\n\tvar subWG sync.WaitGroup\n\tsubWG.Add(1)\n\tgo func() {\n\t\tdefer subWG.Done()\n\t\tcount := 0\n\t\tfor {\n\t\t\tvar e any\n\t\t\tselect {\n\t\t\tcase e = <-sub1.Out():\n\t\t\tcase <-done:\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// sleep to simulate a slow consumer\n\t\t\tevt, ok := e.(event.EvtPeerConnectednessChanged)\n\t\t\tif !ok {\n\t\t\t\tt.Error(\"invalid event received\", e)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif evt.Connectedness != network.Connected {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif count < N {\n\t\t\t\ttime.Sleep(10 * time.Millisecond)\n\t\t\t\tctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond)\n\t\t\t\ts1.Peerstore().AddAddrs(peers2[count].LocalPeer(), []ma.Multiaddr{peers2[count].ListenAddresses()[0]}, time.Hour)\n\t\t\t\ts1.DialPeer(ctx, peers2[count].LocalPeer())\n\t\t\t\tcount++\n\t\t\t\tcancel()\n\t\t\t}\n\t\t}\n\t}()\n\tvar wg sync.WaitGroup\n\twg.Add(N)\n\tfor i := range N {\n\t\ts1.Peerstore().AddAddrs(peers[i].LocalPeer(), []ma.Multiaddr{peers[i].ListenAddresses()[0]}, time.Hour)\n\t\tgo func(i int) {\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)\n\t\t\ts1.DialPeer(ctx, peers[i].LocalPeer())\n\t\t\tcancel()\n\t\t\twg.Done()\n\t\t}(i)\n\t}\n\twg.Wait()\n\ts1.Close()\n\n\tclose(done)\n\tsubWG.Wait()\n}\n"
  },
  {
    "path": "p2p/net/swarm/swarm_listen.go",
    "content": "package swarm\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"slices\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/transport\"\n\t\"github.com/libp2p/go-libp2p/p2p/canonicallog\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\ntype OrderedListener interface {\n\t// Transports optionally implement this interface to indicate the relative\n\t// ordering that listeners should be setup. Some transports may optionally\n\t// make use of other listeners if they are setup. e.g. WebRTC may reuse the\n\t// same UDP port as QUIC, but only when QUIC is setup first.\n\t// lower values are setup first.\n\tListenOrder() int\n}\n\n// Listen sets up listeners for all of the given addresses.\n// It returns as long as we successfully listen on at least *one* address.\nfunc (s *Swarm) Listen(addrs ...ma.Multiaddr) error {\n\terrs := make([]error, len(addrs))\n\tvar succeeded int\n\n\ttype addrAndListener struct {\n\t\taddr ma.Multiaddr\n\t\tlTpt transport.Transport\n\t}\n\tsortedAddrsAndTpts := make([]addrAndListener, 0, len(addrs))\n\tfor _, a := range addrs {\n\t\tt := s.TransportForListening(a)\n\t\tsortedAddrsAndTpts = append(sortedAddrsAndTpts, addrAndListener{addr: a, lTpt: t})\n\t}\n\tslices.SortFunc(sortedAddrsAndTpts, func(a, b addrAndListener) int {\n\t\taOrder := 0\n\t\tbOrder := 0\n\t\tif l, ok := a.lTpt.(OrderedListener); ok {\n\t\t\taOrder = l.ListenOrder()\n\t\t}\n\t\tif l, ok := b.lTpt.(OrderedListener); ok {\n\t\t\tbOrder = l.ListenOrder()\n\t\t}\n\t\treturn aOrder - bOrder\n\t})\n\n\tfor i, a := range sortedAddrsAndTpts {\n\t\tif err := s.AddListenAddr(a.addr); err != nil {\n\t\t\terrs[i] = err\n\t\t} else {\n\t\t\tsucceeded++\n\t\t}\n\t}\n\n\tfor i, e := range errs {\n\t\tif e != nil {\n\t\t\tlog.Warn(\"listening failed\", \"on\", sortedAddrsAndTpts[i].addr, \"err\", errs[i])\n\t\t}\n\t}\n\n\tif succeeded == 0 && len(sortedAddrsAndTpts) > 0 {\n\t\treturn fmt.Errorf(\"failed to listen on any addresses: %s\", errs)\n\t}\n\n\treturn nil\n}\n\n// ListenClose stop and delete listeners for all of the given addresses. If an\n// any address belongs to one of the addreses a Listener provides, then the\n// Listener will close for *all* addresses it provides. For example if you close\n// and address with `/quic`, then the QUIC listener will close and also close\n// any `/quic-v1` address.\nfunc (s *Swarm) ListenClose(addrs ...ma.Multiaddr) {\n\tlistenersToClose := make(map[transport.Listener]struct{}, len(addrs))\n\n\ts.listeners.Lock()\n\tfor l := range s.listeners.m {\n\t\tif !containsMultiaddr(addrs, l.Multiaddr()) {\n\t\t\tcontinue\n\t\t}\n\n\t\tdelete(s.listeners.m, l)\n\t\tlistenersToClose[l] = struct{}{}\n\t}\n\ts.listeners.cacheEOL = time.Time{}\n\ts.listeners.Unlock()\n\n\tfor l := range listenersToClose {\n\t\tl.Close()\n\t}\n}\n\n// AddListenAddr tells the swarm to listen on a single address. Unlike Listen,\n// this method does not attempt to filter out bad addresses.\nfunc (s *Swarm) AddListenAddr(a ma.Multiaddr) error {\n\ttpt := s.TransportForListening(a)\n\tif tpt == nil {\n\t\t// TransportForListening will return nil if either:\n\t\t// 1. No transport has been registered.\n\t\t// 2. We're closed (so we've nulled out the transport map.\n\t\t//\n\t\t// Distinguish between these two cases to avoid confusing users.\n\t\tselect {\n\t\tcase <-s.ctx.Done():\n\t\t\treturn ErrSwarmClosed\n\t\tdefault:\n\t\t\treturn ErrNoTransport\n\t\t}\n\t}\n\n\tlist, err := tpt.Listen(a)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ts.listeners.Lock()\n\tif s.listeners.m == nil {\n\t\ts.listeners.Unlock()\n\t\tlist.Close()\n\t\treturn ErrSwarmClosed\n\t}\n\ts.refs.Add(1)\n\ts.listeners.m[list] = struct{}{}\n\ts.listeners.cacheEOL = time.Time{}\n\ts.listeners.Unlock()\n\n\tmaddr := list.Multiaddr()\n\n\t// signal to our notifiees on listen.\n\ts.notifyAll(func(n network.Notifiee) {\n\t\tn.Listen(s, maddr)\n\t})\n\n\tgo func() {\n\t\tdefer func() {\n\t\t\ts.listeners.Lock()\n\t\t\t_, ok := s.listeners.m[list]\n\t\t\tif ok {\n\t\t\t\tdelete(s.listeners.m, list)\n\t\t\t\ts.listeners.cacheEOL = time.Time{}\n\t\t\t}\n\t\t\ts.listeners.Unlock()\n\n\t\t\tif ok {\n\t\t\t\tlist.Close()\n\t\t\t\tlog.Error(\"swarm listener unintentionally closed\")\n\t\t\t}\n\n\t\t\t// signal to our notifiees on listen close.\n\t\t\ts.notifyAll(func(n network.Notifiee) {\n\t\t\t\tn.ListenClose(s, maddr)\n\t\t\t})\n\t\t\ts.refs.Done()\n\t\t}()\n\t\tfor {\n\t\t\tc, err := list.Accept()\n\t\t\tif err != nil {\n\t\t\t\tif !errors.Is(err, transport.ErrListenerClosed) {\n\t\t\t\t\tlog.Error(\"swarm listener accept error\", \"addr\", a, \"err\", err)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tcanonicallog.LogPeerStatus(100, c.RemotePeer(), c.RemoteMultiaddr(), \"connection_status\", \"established\", \"dir\", \"inbound\")\n\t\t\tif s.metricsTracer != nil {\n\t\t\t\tc = wrapWithMetrics(c, s.metricsTracer, time.Now(), network.DirInbound)\n\t\t\t}\n\n\t\t\tlog.Debug(\"swarm listener accepted connection\", \"local_multiaddr\", c.LocalMultiaddr(), \"remote_multiaddr\", c.RemoteMultiaddr())\n\t\t\ts.refs.Add(1)\n\t\t\tgo func() {\n\t\t\t\tdefer s.refs.Done()\n\t\t\t\t_, err := s.addConn(c, network.DirInbound)\n\t\t\t\tswitch err {\n\t\t\t\tcase nil:\n\t\t\t\tcase ErrSwarmClosed:\n\t\t\t\t\t// ignore.\n\t\t\t\t\treturn\n\t\t\t\tdefault:\n\t\t\t\t\tlog.Warn(\"adding connection failed\", \"to\", a, \"err\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}()\n\t\t}\n\t}()\n\treturn nil\n}\n\nfunc containsMultiaddr(addrs []ma.Multiaddr, addr ma.Multiaddr) bool {\n\treturn slices.ContainsFunc(addrs, addr.Equal)\n}\n"
  },
  {
    "path": "p2p/net/swarm/swarm_metrics.go",
    "content": "package swarm\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/p2p/metricshelper\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\n\t\"github.com/prometheus/client_golang/prometheus\"\n)\n\nconst metricNamespace = \"libp2p_swarm\"\n\nvar (\n\tconnsOpened = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"connections_opened_total\",\n\t\t\tHelp:      \"Connections Opened\",\n\t\t},\n\t\t[]string{\"dir\", \"transport\", \"security\", \"muxer\", \"early_muxer\", \"ip_version\"},\n\t)\n\tkeyTypes = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"key_types_total\",\n\t\t\tHelp:      \"key type\",\n\t\t},\n\t\t[]string{\"dir\", \"key_type\"},\n\t)\n\tconnsClosed = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"connections_closed_total\",\n\t\t\tHelp:      \"Connections Closed\",\n\t\t},\n\t\t[]string{\"dir\", \"transport\", \"security\", \"muxer\", \"early_muxer\", \"ip_version\"},\n\t)\n\tdialError = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"dial_errors_total\",\n\t\t\tHelp:      \"Dial Error\",\n\t\t},\n\t\t[]string{\"transport\", \"error\", \"ip_version\"},\n\t)\n\tconnDuration = prometheus.NewHistogramVec(\n\t\tprometheus.HistogramOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"connection_duration_seconds\",\n\t\t\tHelp:      \"Duration of a Connection\",\n\t\t\tBuckets:   prometheus.ExponentialBuckets(1.0/16, 2, 25), // up to 24 days\n\t\t},\n\t\t[]string{\"dir\", \"transport\", \"security\", \"muxer\", \"early_muxer\", \"ip_version\"},\n\t)\n\tconnHandshakeLatency = prometheus.NewHistogramVec(\n\t\tprometheus.HistogramOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"handshake_latency_seconds\",\n\t\t\tHelp:      \"Duration of the libp2p Handshake\",\n\t\t\tBuckets:   prometheus.ExponentialBuckets(0.001, 1.3, 35),\n\t\t},\n\t\t[]string{\"transport\", \"security\", \"muxer\", \"early_muxer\", \"ip_version\"},\n\t)\n\tdialsPerPeer = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"dials_per_peer_total\",\n\t\t\tHelp:      \"Number of addresses dialed per peer\",\n\t\t},\n\t\t[]string{\"outcome\", \"num_dials\"},\n\t)\n\tdialLatency = prometheus.NewHistogramVec(\n\t\tprometheus.HistogramOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"dial_latency_seconds\",\n\t\t\tHelp:      \"time taken to establish connection with the peer\",\n\t\t\tBuckets:   []float64{0.001, 0.01, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.75, 1, 2},\n\t\t},\n\t\t[]string{\"outcome\", \"num_dials\"},\n\t)\n\tdialRankingDelay = prometheus.NewHistogram(\n\t\tprometheus.HistogramOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"dial_ranking_delay_seconds\",\n\t\t\tHelp:      \"delay introduced by the dial ranking logic\",\n\t\t\tBuckets:   []float64{0.001, 0.01, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.75, 1, 2},\n\t\t},\n\t)\n\tblackHoleSuccessCounterState = prometheus.NewGaugeVec(\n\t\tprometheus.GaugeOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"black_hole_filter_state\",\n\t\t\tHelp:      \"State of the black hole filter\",\n\t\t},\n\t\t[]string{\"name\"},\n\t)\n\tblackHoleSuccessCounterSuccessFraction = prometheus.NewGaugeVec(\n\t\tprometheus.GaugeOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"black_hole_filter_success_fraction\",\n\t\t\tHelp:      \"Fraction of successful dials among the last n requests\",\n\t\t},\n\t\t[]string{\"name\"},\n\t)\n\tblackHoleSuccessCounterNextRequestAllowedAfter = prometheus.NewGaugeVec(\n\t\tprometheus.GaugeOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"black_hole_filter_next_request_allowed_after\",\n\t\t\tHelp:      \"Number of requests after which the next request will be allowed\",\n\t\t},\n\t\t[]string{\"name\"},\n\t)\n\tcollectors = []prometheus.Collector{\n\t\tconnsOpened,\n\t\tkeyTypes,\n\t\tconnsClosed,\n\t\tdialError,\n\t\tconnDuration,\n\t\tconnHandshakeLatency,\n\t\tdialsPerPeer,\n\t\tdialRankingDelay,\n\t\tdialLatency,\n\t\tblackHoleSuccessCounterSuccessFraction,\n\t\tblackHoleSuccessCounterState,\n\t\tblackHoleSuccessCounterNextRequestAllowedAfter,\n\t}\n)\n\ntype MetricsTracer interface {\n\tOpenedConnection(network.Direction, crypto.PubKey, network.ConnectionState, ma.Multiaddr)\n\tClosedConnection(network.Direction, time.Duration, network.ConnectionState, ma.Multiaddr)\n\tCompletedHandshake(time.Duration, network.ConnectionState, ma.Multiaddr)\n\tFailedDialing(ma.Multiaddr, error, error)\n\tDialCompleted(success bool, totalDials int, latency time.Duration)\n\tDialRankingDelay(d time.Duration)\n\tUpdatedBlackHoleSuccessCounter(name string, state BlackHoleState, nextProbeAfter int, successFraction float64)\n}\n\ntype metricsTracer struct{}\n\nvar _ MetricsTracer = &metricsTracer{}\n\ntype metricsTracerSetting struct {\n\treg prometheus.Registerer\n}\n\ntype MetricsTracerOption func(*metricsTracerSetting)\n\nfunc WithRegisterer(reg prometheus.Registerer) MetricsTracerOption {\n\treturn func(s *metricsTracerSetting) {\n\t\tif reg != nil {\n\t\t\ts.reg = reg\n\t\t}\n\t}\n}\n\nfunc NewMetricsTracer(opts ...MetricsTracerOption) MetricsTracer {\n\tsetting := &metricsTracerSetting{reg: prometheus.DefaultRegisterer}\n\tfor _, opt := range opts {\n\t\topt(setting)\n\t}\n\tmetricshelper.RegisterCollectors(setting.reg, collectors...)\n\treturn &metricsTracer{}\n}\n\nfunc appendConnectionState(tags []string, cs network.ConnectionState) []string {\n\tif cs.Transport == \"\" {\n\t\t// This shouldn't happen, unless the transport doesn't properly set the Transport field in the ConnectionState.\n\t\ttags = append(tags, \"unknown\")\n\t} else {\n\t\ttags = append(tags, cs.Transport)\n\t}\n\t// These might be empty, depending on the transport.\n\t// For example, QUIC doesn't set security nor muxer.\n\ttags = append(tags, string(cs.Security))\n\ttags = append(tags, string(cs.StreamMultiplexer))\n\n\tearlyMuxer := \"false\"\n\tif cs.UsedEarlyMuxerNegotiation {\n\t\tearlyMuxer = \"true\"\n\t}\n\ttags = append(tags, earlyMuxer)\n\treturn tags\n}\n\nfunc (m *metricsTracer) OpenedConnection(dir network.Direction, p crypto.PubKey, cs network.ConnectionState, laddr ma.Multiaddr) {\n\ttags := metricshelper.GetStringSlice()\n\tdefer metricshelper.PutStringSlice(tags)\n\n\t*tags = append(*tags, metricshelper.GetDirection(dir))\n\t*tags = appendConnectionState(*tags, cs)\n\t*tags = append(*tags, metricshelper.GetIPVersion(laddr))\n\tconnsOpened.WithLabelValues(*tags...).Inc()\n\n\t*tags = (*tags)[:0]\n\t*tags = append(*tags, metricshelper.GetDirection(dir))\n\t*tags = append(*tags, p.Type().String())\n\tkeyTypes.WithLabelValues(*tags...).Inc()\n}\n\nfunc (m *metricsTracer) ClosedConnection(dir network.Direction, duration time.Duration, cs network.ConnectionState, laddr ma.Multiaddr) {\n\ttags := metricshelper.GetStringSlice()\n\tdefer metricshelper.PutStringSlice(tags)\n\n\t*tags = append(*tags, metricshelper.GetDirection(dir))\n\t*tags = appendConnectionState(*tags, cs)\n\t*tags = append(*tags, metricshelper.GetIPVersion(laddr))\n\tconnsClosed.WithLabelValues(*tags...).Inc()\n\tconnDuration.WithLabelValues(*tags...).Observe(duration.Seconds())\n}\n\nfunc (m *metricsTracer) CompletedHandshake(t time.Duration, cs network.ConnectionState, laddr ma.Multiaddr) {\n\ttags := metricshelper.GetStringSlice()\n\tdefer metricshelper.PutStringSlice(tags)\n\n\t*tags = appendConnectionState(*tags, cs)\n\t*tags = append(*tags, metricshelper.GetIPVersion(laddr))\n\tconnHandshakeLatency.WithLabelValues(*tags...).Observe(t.Seconds())\n}\n\nfunc (m *metricsTracer) FailedDialing(addr ma.Multiaddr, dialErr error, cause error) {\n\ttransport := metricshelper.GetTransport(addr)\n\te := \"other\"\n\t// dial deadline exceeded or the the parent contexts deadline exceeded\n\tif errors.Is(dialErr, context.DeadlineExceeded) || errors.Is(cause, context.DeadlineExceeded) {\n\t\te = \"deadline\"\n\t} else if errors.Is(dialErr, context.Canceled) {\n\t\t// dial was cancelled.\n\t\tif errors.Is(cause, context.Canceled) {\n\t\t\t// parent context was canceled\n\t\t\te = \"application canceled\"\n\t\t} else if errors.Is(cause, errConcurrentDialSuccessful) {\n\t\t\te = \"canceled: concurrent dial successful\"\n\t\t} else {\n\t\t\t// something else\n\t\t\te = \"canceled: other\"\n\t\t}\n\t} else {\n\t\tnerr, ok := dialErr.(net.Error)\n\t\tif ok && nerr.Timeout() {\n\t\t\te = \"timeout\"\n\t\t} else if strings.Contains(dialErr.Error(), \"connect: connection refused\") {\n\t\t\te = \"connection refused\"\n\t\t}\n\t}\n\n\ttags := metricshelper.GetStringSlice()\n\tdefer metricshelper.PutStringSlice(tags)\n\n\t*tags = append(*tags, transport, e)\n\t*tags = append(*tags, metricshelper.GetIPVersion(addr))\n\tdialError.WithLabelValues(*tags...).Inc()\n}\n\nfunc (m *metricsTracer) DialCompleted(success bool, totalDials int, latency time.Duration) {\n\ttags := metricshelper.GetStringSlice()\n\tdefer metricshelper.PutStringSlice(tags)\n\tif success {\n\t\t*tags = append(*tags, \"success\")\n\t} else {\n\t\t*tags = append(*tags, \"failed\")\n\t}\n\n\tnumDialLabels := [...]string{\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \">=6\"}\n\tvar numDials string\n\tif totalDials < len(numDialLabels) {\n\t\tnumDials = numDialLabels[totalDials]\n\t} else {\n\t\tnumDials = numDialLabels[len(numDialLabels)-1]\n\t}\n\t*tags = append(*tags, numDials)\n\tdialsPerPeer.WithLabelValues(*tags...).Inc()\n\tdialLatency.WithLabelValues(*tags...).Observe(latency.Seconds())\n}\n\nfunc (m *metricsTracer) DialRankingDelay(d time.Duration) {\n\tdialRankingDelay.Observe(d.Seconds())\n}\n\nfunc (m *metricsTracer) UpdatedBlackHoleSuccessCounter(name string, state BlackHoleState,\n\tnextProbeAfter int, successFraction float64) {\n\ttags := metricshelper.GetStringSlice()\n\tdefer metricshelper.PutStringSlice(tags)\n\n\t*tags = append(*tags, name)\n\n\tblackHoleSuccessCounterState.WithLabelValues(*tags...).Set(float64(state))\n\tblackHoleSuccessCounterSuccessFraction.WithLabelValues(*tags...).Set(successFraction)\n\tblackHoleSuccessCounterNextRequestAllowedAfter.WithLabelValues(*tags...).Set(float64(nextProbeAfter))\n}\n"
  },
  {
    "path": "p2p/net/swarm/swarm_metrics_test.go",
    "content": "//go:build nocover\n\npackage swarm\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"net\"\n\t\"syscall\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\n\tmrand \"math/rand\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc BenchmarkMetricsConnOpen(b *testing.B) {\n\tb.ReportAllocs()\n\tquicConnState := network.ConnectionState{Transport: \"quic\"}\n\ttcpConnState := network.ConnectionState{\n\t\tStreamMultiplexer: \"yamux\",\n\t\tSecurity:          \"tls\",\n\t\tTransport:         \"tcp\",\n\t}\n\t_, pub, err := crypto.GenerateEd25519Key(rand.Reader)\n\trequire.NoError(b, err)\n\tquicAddr := ma.StringCast(\"/ip4/1.2.3.4/udp/1/quic-v1\")\n\ttcpAddr := ma.StringCast(\"/ip4/1.2.3.4/tcp/1/\")\n\ttr := NewMetricsTracer()\n\tfor i := 0; i < b.N; i++ {\n\t\tswitch i % 2 {\n\t\tcase 0:\n\t\t\ttr.OpenedConnection(network.DirInbound, pub, quicConnState, quicAddr)\n\t\tcase 1:\n\t\t\ttr.OpenedConnection(network.DirInbound, pub, tcpConnState, tcpAddr)\n\t\t}\n\t}\n}\n\nfunc randItem[T any](items []T) T {\n\treturn items[mrand.Intn(len(items))]\n}\n\nfunc TestMetricsNoAllocNoCover(t *testing.T) {\n\tmt := NewMetricsTracer()\n\n\tconnections := []network.ConnectionState{\n\t\t{StreamMultiplexer: \"yamux\", Security: \"tls\", Transport: \"tcp\", UsedEarlyMuxerNegotiation: true},\n\t\t{StreamMultiplexer: \"yamux\", Security: \"noise\", Transport: \"tcp\", UsedEarlyMuxerNegotiation: false},\n\t\t{StreamMultiplexer: \"\", Security: \"\", Transport: \"quic\"},\n\t\t{StreamMultiplexer: \"another-yamux\", Security: \"noise\", Transport: \"tcp\"},\n\t}\n\n\tdirections := []network.Direction{network.DirInbound, network.DirOutbound}\n\n\t_, pub1, err := crypto.GenerateEd25519Key(rand.Reader)\n\trequire.NoError(t, err)\n\t_, pub2, err := crypto.GenerateEd25519Key(rand.Reader)\n\trequire.NoError(t, err)\n\t_, pub3, err := crypto.GenerateEd25519Key(rand.Reader)\n\trequire.NoError(t, err)\n\tkeys := []crypto.PubKey{pub1, pub2, pub3}\n\n\terrors := []error{\n\t\tcontext.Canceled,\n\t\tcontext.DeadlineExceeded,\n\t\t&net.OpError{Err: syscall.ETIMEDOUT},\n\t}\n\n\taddrs := []ma.Multiaddr{\n\t\tma.StringCast(\"/ip4/1.2.3.4/tcp/1\"),\n\t\tma.StringCast(\"/ip4/1.2.3.4/tcp/2\"),\n\t\tma.StringCast(\"/ip4/1.2.3.4/udp/2345\"),\n\t}\n\n\tbhfNames := []string{\"udp\", \"ipv6\", \"tcp\", \"icmp\"}\n\tbhfState := []BlackHoleState{blackHoleStateAllowed, blackHoleStateBlocked}\n\n\ttests := map[string]func(){\n\t\t\"OpenedConnection\": func() {\n\t\t\tmt.OpenedConnection(randItem(directions), randItem(keys), randItem(connections), randItem(addrs))\n\t\t},\n\t\t\"ClosedConnection\": func() {\n\t\t\tmt.ClosedConnection(randItem(directions), time.Duration(mrand.Intn(100))*time.Second, randItem(connections), randItem(addrs))\n\t\t},\n\t\t\"CompletedHandshake\": func() {\n\t\t\tmt.CompletedHandshake(time.Duration(mrand.Intn(100))*time.Second, randItem(connections), randItem(addrs))\n\t\t},\n\t\t\"FailedDialing\":    func() { mt.FailedDialing(randItem(addrs), randItem(errors), randItem(errors)) },\n\t\t\"DialCompleted\":    func() { mt.DialCompleted(mrand.Intn(2) == 1, mrand.Intn(10), time.Duration(mrand.Intn(1000_000_000))) },\n\t\t\"DialRankingDelay\": func() { mt.DialRankingDelay(time.Duration(mrand.Intn(1e10))) },\n\t\t\"UpdatedBlackHoleSuccessCounter\": func() {\n\t\t\tmt.UpdatedBlackHoleSuccessCounter(\n\t\t\t\trandItem(bhfNames),\n\t\t\t\trandItem(bhfState),\n\t\t\t\tmrand.Intn(100),\n\t\t\t\tmrand.Float64(),\n\t\t\t)\n\t\t},\n\t}\n\n\tfor method, f := range tests {\n\t\tallocs := testing.AllocsPerRun(1000, f)\n\t\tif allocs > 0 {\n\t\t\tt.Fatalf(\"Alloc Test: %s, got: %0.2f, expected: 0 allocs\", method, allocs)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "p2p/net/swarm/swarm_net_test.go",
    "content": "package swarm_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t. \"github.com/libp2p/go-libp2p/p2p/net/swarm/testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\n// TestConnectednessCorrect starts a few networks, connects a few\n// and tests Connectedness value is correct.\nfunc TestConnectednessCorrect(t *testing.T) {\n\tnets := make([]network.Network, 4)\n\tfor i := range 4 {\n\t\tnets[i] = GenSwarm(t)\n\t}\n\n\t// connect 0-1, 0-2, 0-3, 1-2, 2-3\n\n\tdial := func(a, b network.Network) {\n\t\tDivulgeAddresses(b, a)\n\t\tif _, err := a.DialPeer(context.Background(), b.LocalPeer()); err != nil {\n\t\t\tt.Fatalf(\"Failed to dial: %s\", err)\n\t\t}\n\t}\n\n\tdial(nets[0], nets[1])\n\tdial(nets[0], nets[3])\n\tdial(nets[1], nets[2])\n\tdial(nets[3], nets[2])\n\n\t// The notifications for new connections get sent out asynchronously.\n\t// There is the potential for a race condition here, so we sleep to ensure\n\t// that they have been received.\n\ttime.Sleep(time.Millisecond * 100)\n\n\t// test those connected show up correctly\n\n\t// test connected\n\texpectConnectedness(t, nets[0], nets[1], network.Connected)\n\texpectConnectedness(t, nets[0], nets[3], network.Connected)\n\texpectConnectedness(t, nets[1], nets[2], network.Connected)\n\texpectConnectedness(t, nets[3], nets[2], network.Connected)\n\n\t// test not connected\n\texpectConnectedness(t, nets[0], nets[2], network.NotConnected)\n\texpectConnectedness(t, nets[1], nets[3], network.NotConnected)\n\n\trequire.Len(t, nets[0].Peers(), 2, \"expected net 0 to have two peers\")\n\trequire.Len(t, nets[2].Peers(), 2, \"expected net 2 to have two peers\")\n\trequire.NotZerof(t, nets[1].ConnsToPeer(nets[3].LocalPeer()), \"net 1 should have no connections to net 3\")\n\trequire.NoError(t, nets[2].ClosePeer(nets[1].LocalPeer()))\n\n\ttime.Sleep(time.Millisecond * 50)\n\texpectConnectedness(t, nets[2], nets[1], network.NotConnected)\n\n\tfor _, n := range nets {\n\t\tn.Close()\n\t}\n}\n\nfunc expectConnectedness(t *testing.T, a, b network.Network, expected network.Connectedness) {\n\tes := \"%s is connected to %s, but Connectedness incorrect. %s %s %s\"\n\tatob := a.Connectedness(b.LocalPeer())\n\tbtoa := b.Connectedness(a.LocalPeer())\n\tif atob != expected {\n\t\tt.Errorf(es, a, b, printConns(a), printConns(b), atob)\n\t}\n\n\t// test symmetric case\n\tif btoa != expected {\n\t\tt.Errorf(es, b, a, printConns(b), printConns(a), btoa)\n\t}\n}\n\nfunc printConns(n network.Network) string {\n\ts := fmt.Sprintf(\"Connections in %s:\\n\", n)\n\tfor _, c := range n.Conns() {\n\t\ts = s + fmt.Sprintf(\"- %s\\n\", c)\n\t}\n\treturn s\n}\n\nfunc TestNetworkOpenStream(t *testing.T) {\n\tctx := t.Context()\n\n\ttestString := \"hello ipfs\"\n\n\tnets := make([]network.Network, 4)\n\tfor i := range 4 {\n\t\tnets[i] = GenSwarm(t)\n\t}\n\n\tdial := func(a, b network.Network) {\n\t\tDivulgeAddresses(b, a)\n\t\tif _, err := a.DialPeer(ctx, b.LocalPeer()); err != nil {\n\t\t\tt.Fatalf(\"Failed to dial: %s\", err)\n\t\t}\n\t}\n\n\tdial(nets[0], nets[1])\n\tdial(nets[0], nets[3])\n\tdial(nets[1], nets[2])\n\n\tdone := make(chan bool)\n\tnets[1].SetStreamHandler(func(s network.Stream) {\n\t\tdefer close(done)\n\t\tdefer s.Close()\n\n\t\tbuf, err := io.ReadAll(s)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\treturn\n\t\t}\n\t\tif string(buf) != testString {\n\t\t\tt.Error(\"got wrong message\")\n\t\t}\n\t})\n\n\ts, err := nets[0].NewStream(ctx, nets[1].LocalPeer())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tvar numStreams int\n\tfor _, conn := range nets[0].ConnsToPeer(nets[1].LocalPeer()) {\n\t\tnumStreams += conn.Stat().NumStreams\n\t}\n\n\tif numStreams != 1 {\n\t\tt.Fatal(\"should only have one stream there\")\n\t}\n\n\tn, err := s.Write([]byte(testString))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t} else if n != len(testString) {\n\t\tt.Errorf(\"expected to write %d bytes, wrote %d\", len(testString), n)\n\t}\n\n\terr = s.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tselect {\n\tcase <-done:\n\tcase <-time.After(time.Millisecond * 100):\n\t\tt.Fatal(\"timed out waiting on stream\")\n\t}\n\n\t_, err = nets[1].NewStream(ctx, nets[3].LocalPeer())\n\tif err == nil {\n\t\tt.Fatal(\"expected stream open 1->3 to fail\")\n\t}\n}\n"
  },
  {
    "path": "p2p/net/swarm/swarm_notif_test.go",
    "content": "package swarm_test\n\nimport (\n\t\"context\"\n\t\"slices\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t. \"github.com/libp2p/go-libp2p/p2p/net/swarm\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestNotifications(t *testing.T) {\n\tconst swarmSize = 5\n\n\tnotifiees := make([]*netNotifiee, swarmSize)\n\n\tswarms := makeSwarms(t, swarmSize)\n\tdefer func() {\n\t\tfor i, s := range swarms {\n\t\t\tselect {\n\t\t\tcase <-notifiees[i].listenClose:\n\t\t\t\tt.Error(\"should not have been closed\")\n\t\t\tdefault:\n\t\t\t}\n\t\t\trequire.NoError(t, s.Close())\n\t\t\tselect {\n\t\t\tcase <-notifiees[i].listenClose:\n\t\t\tdefault:\n\t\t\t\tt.Error(\"expected a listen close notification\")\n\t\t\t}\n\t\t}\n\t}()\n\n\tconst timeout = 5 * time.Second\n\n\t// signup notifs\n\tfor i, swarm := range swarms {\n\t\tn := newNetNotifiee(swarmSize)\n\t\tswarm.Notify(n)\n\t\tnotifiees[i] = n\n\t}\n\n\tconnectSwarms(t, context.Background(), swarms)\n\n\ttime.Sleep(50 * time.Millisecond)\n\t// should've gotten 5 by now.\n\n\t// test everyone got the correct connection opened calls\n\tfor i, s := range swarms {\n\t\tn := notifiees[i]\n\t\tnotifs := make(map[peer.ID][]network.Conn)\n\t\tfor j, s2 := range swarms {\n\t\t\tif i == j {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// this feels a little sketchy, but its probably okay\n\t\t\tfor len(s.ConnsToPeer(s2.LocalPeer())) != len(notifs[s2.LocalPeer()]) {\n\t\t\t\tselect {\n\t\t\t\tcase c := <-n.connected:\n\t\t\t\t\tnfp := notifs[c.RemotePeer()]\n\t\t\t\t\tnotifs[c.RemotePeer()] = append(nfp, c)\n\t\t\t\tcase <-time.After(timeout):\n\t\t\t\t\tt.Fatal(\"timeout\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor p, cons := range notifs {\n\t\t\texpect := s.ConnsToPeer(p)\n\t\t\tif len(expect) != len(cons) {\n\t\t\t\tt.Fatal(\"got different number of connections\")\n\t\t\t}\n\n\t\t\tfor _, c := range cons {\n\t\t\t\tvar found bool\n\t\t\t\tif slices.Contains(expect, c) {\n\t\t\t\t\tfound = true\n\t\t\t\t}\n\n\t\t\t\tif !found {\n\t\t\t\t\tt.Fatal(\"connection not found!\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tnormalizeAddrs := func(a ma.Multiaddr, isLocal bool) ma.Multiaddr {\n\t\t// remove certhashes\n\t\tx, _ := ma.SplitFunc(a, func(c ma.Component) bool {\n\t\t\treturn c.Protocol().Code == ma.P_CERTHASH\n\t\t})\n\t\t// on local addrs, replace 0.0.0.0 with 127.0.0.1\n\t\tif isLocal {\n\t\t\tif manet.IsIPUnspecified(x) {\n\t\t\t\tip, rest := ma.SplitFirst(x)\n\t\t\t\tif ip.Protocol().Code == ma.P_IP4 {\n\t\t\t\t\treturn ma.StringCast(\"/ip4/127.0.0.1\").Encapsulate(rest)\n\t\t\t\t} else {\n\t\t\t\t\treturn ma.StringCast(\"/ip6/::1\").Encapsulate(rest)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn x\n\t}\n\tcomplement := func(c network.Conn) (*Swarm, *netNotifiee, *Conn) {\n\t\tfor i, s := range swarms {\n\t\t\tfor _, c2 := range s.Conns() {\n\t\t\t\tif normalizeAddrs(c.LocalMultiaddr(), true).Equal(normalizeAddrs(c2.RemoteMultiaddr(), false)) &&\n\t\t\t\t\tnormalizeAddrs(c2.LocalMultiaddr(), true).Equal(normalizeAddrs(c.RemoteMultiaddr(), false)) {\n\t\t\t\t\treturn s, notifiees[i], c2.(*Conn)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tt.Fatal(\"complementary conn not found\", c)\n\t\treturn nil, nil, nil\n\t}\n\n\t// close conns\n\tfor i, s := range swarms {\n\t\tn := notifiees[i]\n\t\tfor _, c := range s.Conns() {\n\t\t\t_, n2, c2 := complement(c)\n\t\t\tc.Close()\n\t\t\tc2.Close()\n\n\t\t\tvar c3, c4 network.Conn\n\t\t\tselect {\n\t\t\tcase c3 = <-n.disconnected:\n\t\t\tcase <-time.After(timeout):\n\t\t\t\tt.Fatal(\"timeout\")\n\t\t\t}\n\t\t\tif c != c3 {\n\t\t\t\tt.Fatal(\"got incorrect conn\", c, c3)\n\t\t\t}\n\n\t\t\tselect {\n\t\t\tcase c4 = <-n2.disconnected:\n\t\t\tcase <-time.After(timeout):\n\t\t\t\tt.Fatal(\"timeout\")\n\t\t\t}\n\t\t\tif c2 != c4 {\n\t\t\t\tt.Fatal(\"got incorrect conn\", c, c2)\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype netNotifiee struct {\n\tlisten       chan ma.Multiaddr\n\tlistenClose  chan ma.Multiaddr\n\tconnected    chan network.Conn\n\tdisconnected chan network.Conn\n}\n\nfunc newNetNotifiee(buffer int) *netNotifiee {\n\treturn &netNotifiee{\n\t\tlisten:       make(chan ma.Multiaddr, buffer),\n\t\tlistenClose:  make(chan ma.Multiaddr, buffer),\n\t\tconnected:    make(chan network.Conn, buffer),\n\t\tdisconnected: make(chan network.Conn, buffer),\n\t}\n}\n\nfunc (nn *netNotifiee) Listen(_ network.Network, a ma.Multiaddr) {\n\tnn.listen <- a\n}\nfunc (nn *netNotifiee) ListenClose(_ network.Network, a ma.Multiaddr) {\n\tnn.listenClose <- a\n}\nfunc (nn *netNotifiee) Connected(_ network.Network, v network.Conn) {\n\tnn.connected <- v\n}\nfunc (nn *netNotifiee) Disconnected(_ network.Network, v network.Conn) {\n\tnn.disconnected <- v\n}\n"
  },
  {
    "path": "p2p/net/swarm/swarm_stream.go",
    "content": "package swarm\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n)\n\n// Validate Stream conforms to the go-libp2p-net Stream interface\nvar _ network.Stream = &Stream{}\n\n// Stream is the stream type used by swarm. In general, you won't use this type\n// directly.\ntype Stream struct {\n\tid uint64\n\n\tstream network.MuxedStream\n\tconn   *Conn\n\tscope  network.StreamManagementScope\n\n\tcloseMx  sync.Mutex\n\tisClosed bool\n\t// acceptStreamGoroutineCompleted indicates whether the goroutine handling the incoming stream has exited\n\tacceptStreamGoroutineCompleted bool\n\n\tprotocol atomic.Pointer[protocol.ID]\n\n\tstat network.Stats\n}\n\nfunc (s *Stream) ID() string {\n\t// format: <first 10 chars of peer id>-<global conn ordinal>-<global stream ordinal>\n\treturn fmt.Sprintf(\"%s-%d\", s.conn.ID(), s.id)\n}\n\nfunc (s *Stream) String() string {\n\treturn fmt.Sprintf(\n\t\t\"<swarm.Stream[%s] %s (%s) <-> %s (%s)>\",\n\t\ts.conn.conn.Transport(),\n\t\ts.conn.LocalMultiaddr(),\n\t\ts.conn.LocalPeer(),\n\t\ts.conn.RemoteMultiaddr(),\n\t\ts.conn.RemotePeer(),\n\t)\n}\n\n// Conn returns the Conn associated with this stream, as an network.Conn\nfunc (s *Stream) Conn() network.Conn {\n\treturn s.conn\n}\n\n// Read reads bytes from a stream.\nfunc (s *Stream) Read(p []byte) (int, error) {\n\tn, err := s.stream.Read(p)\n\t// TODO: push this down to a lower level for better accuracy.\n\tif s.conn.swarm.bwc != nil {\n\t\ts.conn.swarm.bwc.LogRecvMessage(int64(n))\n\t\ts.conn.swarm.bwc.LogRecvMessageStream(int64(n), s.Protocol(), s.Conn().RemotePeer())\n\t}\n\treturn n, err\n}\n\n// Write writes bytes to a stream, flushing for each call.\nfunc (s *Stream) Write(p []byte) (int, error) {\n\tn, err := s.stream.Write(p)\n\t// TODO: push this down to a lower level for better accuracy.\n\tif s.conn.swarm.bwc != nil {\n\t\ts.conn.swarm.bwc.LogSentMessage(int64(n))\n\t\ts.conn.swarm.bwc.LogSentMessageStream(int64(n), s.Protocol(), s.Conn().RemotePeer())\n\t}\n\treturn n, err\n}\n\n// Close closes the stream, closing both ends and freeing all associated\n// resources.\nfunc (s *Stream) Close() error {\n\terr := s.stream.Close()\n\ts.closeAndRemoveStream()\n\treturn err\n}\n\n// Reset resets the stream, signaling an error on both ends and freeing all\n// associated resources.\nfunc (s *Stream) Reset() error {\n\terr := s.stream.Reset()\n\ts.closeAndRemoveStream()\n\treturn err\n}\n\nfunc (s *Stream) ResetWithError(errCode network.StreamErrorCode) error {\n\terr := s.stream.ResetWithError(errCode)\n\ts.closeAndRemoveStream()\n\treturn err\n}\n\nfunc (s *Stream) closeAndRemoveStream() {\n\ts.closeMx.Lock()\n\tdefer s.closeMx.Unlock()\n\tif s.isClosed {\n\t\treturn\n\t}\n\ts.isClosed = true\n\t// We don't want to keep swarm from closing till the stream handler has exited\n\ts.conn.swarm.refs.Done()\n\t// Cleanup the stream from connection only after the stream handler has completed\n\tif s.acceptStreamGoroutineCompleted {\n\t\ts.conn.removeStream(s)\n\t}\n}\n\n// CloseWrite closes the stream for writing, flushing all data and sending an EOF.\n// This function does not free resources, call Close or Reset when done with the\n// stream.\nfunc (s *Stream) CloseWrite() error {\n\treturn s.stream.CloseWrite()\n}\n\n// CloseRead closes the stream for reading. This function does not free resources,\n// call Close or Reset when done with the stream.\nfunc (s *Stream) CloseRead() error {\n\treturn s.stream.CloseRead()\n}\n\nfunc (s *Stream) completeAcceptStreamGoroutine() {\n\ts.closeMx.Lock()\n\tdefer s.closeMx.Unlock()\n\tif s.acceptStreamGoroutineCompleted {\n\t\treturn\n\t}\n\ts.acceptStreamGoroutineCompleted = true\n\tif s.isClosed {\n\t\ts.conn.removeStream(s)\n\t}\n}\n\n// Protocol returns the protocol negotiated on this stream (if set).\nfunc (s *Stream) Protocol() protocol.ID {\n\tp := s.protocol.Load()\n\tif p == nil {\n\t\treturn \"\"\n\t}\n\treturn *p\n}\n\n// SetProtocol sets the protocol for this stream.\n//\n// This doesn't actually *do* anything other than record the fact that we're\n// speaking the given protocol over this stream. It's still up to the user to\n// negotiate the protocol. This is usually done by the Host.\nfunc (s *Stream) SetProtocol(p protocol.ID) error {\n\tif err := s.scope.SetProtocol(p); err != nil {\n\t\treturn err\n\t}\n\n\ts.protocol.Store(&p)\n\treturn nil\n}\n\n// SetDeadline sets the read and write deadlines for this stream.\nfunc (s *Stream) SetDeadline(t time.Time) error {\n\treturn s.stream.SetDeadline(t)\n}\n\n// SetReadDeadline sets the read deadline for this stream.\nfunc (s *Stream) SetReadDeadline(t time.Time) error {\n\treturn s.stream.SetReadDeadline(t)\n}\n\n// SetWriteDeadline sets the write deadline for this stream.\nfunc (s *Stream) SetWriteDeadline(t time.Time) error {\n\treturn s.stream.SetWriteDeadline(t)\n}\n\n// Stat returns metadata information for this stream.\nfunc (s *Stream) Stat() network.Stats {\n\treturn s.stat\n}\n\nfunc (s *Stream) Scope() network.StreamScope {\n\treturn s.scope\n}\n"
  },
  {
    "path": "p2p/net/swarm/swarm_test.go",
    "content": "package swarm_test\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"slices\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/control\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\tmocknetwork \"github.com/libp2p/go-libp2p/core/network/mocks\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\t\"github.com/libp2p/go-libp2p/core/test\"\n\t\"github.com/libp2p/go-libp2p/p2p/net/swarm\"\n\t. \"github.com/libp2p/go-libp2p/p2p/net/swarm/testing\"\n\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/mock/gomock\"\n)\n\nvar log = logging.Logger(\"swarm_test\")\n\nfunc EchoStreamHandler(stream network.Stream) {\n\tgo func() {\n\t\tdefer stream.Close()\n\n\t\t// pull out the ipfs conn\n\t\tc := stream.Conn()\n\t\tlog.Info(\"ponging to peer\", \"local\", c.LocalPeer(), \"remote\", c.RemotePeer())\n\n\t\tbuf := make([]byte, 4)\n\n\t\tfor {\n\t\t\tif _, err := stream.Read(buf); err != nil {\n\t\t\t\tif err != io.EOF {\n\t\t\t\t\tlog.Error(\"ping receive error\", \"err\", err)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif !bytes.Equal(buf, []byte(\"ping\")) {\n\t\t\t\tlog.Error(\"ping receive error\", \"err\", fmt.Errorf(\"ping mismatch: %s\", string(buf)))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif _, err := stream.Write([]byte(\"pong\")); err != nil {\n\t\t\t\tlog.Error(\"pong send error\", \"err\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n}\n\nfunc makeDialOnlySwarm(t *testing.T) *swarm.Swarm {\n\tswarm := GenSwarm(t, OptDialOnly)\n\tswarm.SetStreamHandler(EchoStreamHandler)\n\treturn swarm\n}\n\nfunc makeSwarms(t *testing.T, num int, opts ...Option) []*swarm.Swarm {\n\tswarms := make([]*swarm.Swarm, 0, num)\n\tfor range num {\n\t\tswarm := GenSwarm(t, opts...)\n\t\tswarm.SetStreamHandler(EchoStreamHandler)\n\t\tswarms = append(swarms, swarm)\n\t}\n\treturn swarms\n}\n\nfunc connectSwarms(t *testing.T, ctx context.Context, swarms []*swarm.Swarm) {\n\tvar wg sync.WaitGroup\n\tconnect := func(s *swarm.Swarm, dst peer.ID, addrs []ma.Multiaddr) {\n\t\ts.Peerstore().AddAddrs(dst, addrs, peerstore.TempAddrTTL)\n\t\tif _, err := s.DialPeer(ctx, dst); err != nil {\n\t\t\tt.Fatal(\"error swarm dialing to peer\", err)\n\t\t}\n\t\twg.Done()\n\t}\n\n\tlog.Info(\"Connecting swarms simultaneously.\")\n\tfor i, s1 := range swarms {\n\t\tfor _, s2 := range swarms[i+1:] {\n\t\t\twg.Add(1)\n\t\t\tconnect(s1, s2.LocalPeer(), s2.ListenAddresses())\n\t\t}\n\t}\n\twg.Wait()\n\n\tfor _, s := range swarms {\n\t\tlog.Info(\"swarm routing table\", \"peer\", s.LocalPeer(), \"peers\", s.Peers())\n\t}\n}\n\nfunc subtestSwarm(t *testing.T, SwarmNum int, MsgNum int) {\n\tswarms := makeSwarms(t, SwarmNum, OptDisableReuseport)\n\n\t// connect everyone\n\tconnectSwarms(t, context.Background(), swarms)\n\n\t// ping/pong\n\tfor _, s1 := range swarms {\n\t\tlog.Debug(\"-------------------------------------------------------\")\n\t\tlog.Debug(\"ping pong round\", \"peer\", s1.LocalPeer())\n\t\tlog.Debug(\"-------------------------------------------------------\")\n\n\t\t_, cancel := context.WithCancel(context.Background())\n\t\tgot := map[peer.ID]int{}\n\t\terrChan := make(chan error, MsgNum*len(swarms))\n\t\tstreamChan := make(chan network.Stream, MsgNum)\n\n\t\t// send out \"ping\" x MsgNum to every peer\n\t\tgo func() {\n\t\t\tdefer close(streamChan)\n\n\t\t\tvar wg sync.WaitGroup\n\t\t\tsend := func(p peer.ID) {\n\t\t\t\tdefer wg.Done()\n\n\t\t\t\t// first, one stream per peer (nice)\n\t\t\t\tstream, err := s1.NewStream(context.Background(), p)\n\t\t\t\tif err != nil {\n\t\t\t\t\terrChan <- err\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\t// send out ping!\n\t\t\t\tfor k := range MsgNum { // with k messages\n\t\t\t\t\tmsg := \"ping\"\n\t\t\t\t\tlog.Debug(\"sending message\", \"local\", s1.LocalPeer(), \"msg\", msg, \"peer\", p, \"count\", k)\n\t\t\t\t\tif _, err := stream.Write([]byte(msg)); err != nil {\n\t\t\t\t\t\terrChan <- err\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// read it later\n\t\t\t\tstreamChan <- stream\n\t\t\t}\n\n\t\t\tfor _, s2 := range swarms {\n\t\t\t\tif s2.LocalPeer() == s1.LocalPeer() {\n\t\t\t\t\tcontinue // dont send to self...\n\t\t\t\t}\n\n\t\t\t\twg.Add(1)\n\t\t\t\tgo send(s2.LocalPeer())\n\t\t\t}\n\t\t\twg.Wait()\n\t\t}()\n\n\t\t// receive \"pong\" x MsgNum from every peer\n\t\tgo func() {\n\t\t\tdefer close(errChan)\n\t\t\tcount := 0\n\t\t\tcountShouldBe := MsgNum * (len(swarms) - 1)\n\t\t\tfor stream := range streamChan { // one per peer\n\t\t\t\t// get peer on the other side\n\t\t\t\tp := stream.Conn().RemotePeer()\n\n\t\t\t\t// receive pings\n\t\t\t\tmsgCount := 0\n\t\t\t\tmsg := make([]byte, 4)\n\t\t\t\tfor k := range MsgNum { // with k messages\n\n\t\t\t\t\t// read from the stream\n\t\t\t\t\tif _, err := stream.Read(msg); err != nil {\n\t\t\t\t\t\terrChan <- err\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tif string(msg) != \"pong\" {\n\t\t\t\t\t\terrChan <- fmt.Errorf(\"unexpected message: %s\", msg)\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tlog.Debug(\"sending message\", \"local\", s1.LocalPeer(), \"msg\", msg, \"peer\", p, \"count\", k)\n\t\t\t\t\tmsgCount++\n\t\t\t\t}\n\n\t\t\t\tgot[p] = msgCount\n\t\t\t\tcount += msgCount\n\t\t\t\tstream.Close()\n\t\t\t}\n\n\t\t\tif count != countShouldBe {\n\t\t\t\terrChan <- fmt.Errorf(\"count mismatch: %d != %d\", count, countShouldBe)\n\t\t\t}\n\t\t}()\n\n\t\t// check any errors (blocks till consumer is done)\n\t\tfor err := range errChan {\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err.Error())\n\t\t\t}\n\t\t}\n\n\t\tlog.Debug(\"got pongs\", \"peer\", s1.LocalPeer())\n\t\tif (len(swarms) - 1) != len(got) {\n\t\t\tt.Errorf(\"got (%d) less messages than sent (%d).\", len(got), len(swarms))\n\t\t}\n\n\t\tfor p, n := range got {\n\t\t\tif n != MsgNum {\n\t\t\t\tt.Error(\"peer did not get all msgs\", p, n, \"/\", MsgNum)\n\t\t\t}\n\t\t}\n\n\t\tcancel()\n\t\t<-time.After(10 * time.Millisecond)\n\t}\n}\n\nfunc TestSwarm(t *testing.T) {\n\tt.Parallel()\n\tsubtestSwarm(t, 5, 100)\n}\n\nfunc TestBasicSwarm(t *testing.T) {\n\t// t.Skip(\"skipping for another test\")\n\tt.Parallel()\n\tsubtestSwarm(t, 2, 1)\n}\n\nfunc TestConnectionGating(t *testing.T) {\n\tctx := context.Background()\n\ttcs := map[string]struct {\n\t\tp1Gater func(gater *MockConnectionGater) *MockConnectionGater\n\t\tp2Gater func(gater *MockConnectionGater) *MockConnectionGater\n\n\t\tp1ConnectednessToP2 network.Connectedness\n\t\tp2ConnectednessToP1 network.Connectedness\n\t\tisP1OutboundErr     bool\n\t\tdisableOnQUIC       bool\n\t}{\n\t\t\"no gating\": {\n\t\t\tp1ConnectednessToP2: network.Connected,\n\t\t\tp2ConnectednessToP1: network.Connected,\n\t\t\tisP1OutboundErr:     false,\n\t\t},\n\t\t\"p1 gates outbound peer dial\": {\n\t\t\tp1Gater: func(c *MockConnectionGater) *MockConnectionGater {\n\t\t\t\tc.PeerDial = func(_ peer.ID) bool { return false }\n\t\t\t\treturn c\n\t\t\t},\n\t\t\tp1ConnectednessToP2: network.NotConnected,\n\t\t\tp2ConnectednessToP1: network.NotConnected,\n\t\t\tisP1OutboundErr:     true,\n\t\t},\n\t\t\"p1 gates outbound addr dialing\": {\n\t\t\tp1Gater: func(c *MockConnectionGater) *MockConnectionGater {\n\t\t\t\tc.Dial = func(_ peer.ID, _ ma.Multiaddr) bool { return false }\n\t\t\t\treturn c\n\t\t\t},\n\t\t\tp1ConnectednessToP2: network.NotConnected,\n\t\t\tp2ConnectednessToP1: network.NotConnected,\n\t\t\tisP1OutboundErr:     true,\n\t\t},\n\t\t\"p2 accepts inbound peer dial if outgoing dial is gated\": {\n\t\t\tp2Gater: func(c *MockConnectionGater) *MockConnectionGater {\n\t\t\t\tc.Dial = func(peer.ID, ma.Multiaddr) bool { return false }\n\t\t\t\treturn c\n\t\t\t},\n\t\t\tp1ConnectednessToP2: network.Connected,\n\t\t\tp2ConnectednessToP1: network.Connected,\n\t\t\tisP1OutboundErr:     false,\n\t\t},\n\t\t\"p2 gates inbound peer dial before securing\": {\n\t\t\tp2Gater: func(c *MockConnectionGater) *MockConnectionGater {\n\t\t\t\tc.Accept = func(_ network.ConnMultiaddrs) bool { return false }\n\t\t\t\treturn c\n\t\t\t},\n\t\t\tp1ConnectednessToP2: network.NotConnected,\n\t\t\tp2ConnectednessToP1: network.NotConnected,\n\t\t\tisP1OutboundErr:     true,\n\t\t\t// QUIC gates the connection after completion of the handshake\n\t\t\tdisableOnQUIC: true,\n\t\t},\n\t\t\"p2 gates inbound peer dial before multiplexing\": {\n\t\t\tp1Gater: func(c *MockConnectionGater) *MockConnectionGater {\n\t\t\t\tc.Secured = func(network.Direction, peer.ID, network.ConnMultiaddrs) bool { return false }\n\t\t\t\treturn c\n\t\t\t},\n\t\t\tp1ConnectednessToP2: network.NotConnected,\n\t\t\tp2ConnectednessToP1: network.NotConnected,\n\t\t\tisP1OutboundErr:     true,\n\t\t},\n\t\t\"p2 gates inbound peer dial after upgrading\": {\n\t\t\tp1Gater: func(c *MockConnectionGater) *MockConnectionGater {\n\t\t\t\tc.Upgraded = func(_ network.Conn) (bool, control.DisconnectReason) { return false, 0 }\n\t\t\t\treturn c\n\t\t\t},\n\t\t\tp1ConnectednessToP2: network.NotConnected,\n\t\t\tp2ConnectednessToP1: network.NotConnected,\n\t\t\tisP1OutboundErr:     true,\n\t\t},\n\t\t\"p2 gates outbound dials\": {\n\t\t\tp2Gater: func(c *MockConnectionGater) *MockConnectionGater {\n\t\t\t\tc.PeerDial = func(_ peer.ID) bool { return false }\n\t\t\t\treturn c\n\t\t\t},\n\t\t\tp1ConnectednessToP2: network.Connected,\n\t\t\tp2ConnectednessToP1: network.Connected,\n\t\t\tisP1OutboundErr:     false,\n\t\t},\n\t}\n\n\tfor n, tc := range tcs {\n\t\tfor _, useQuic := range []bool{false, true} {\n\t\t\ttrString := \"TCP\"\n\t\t\toptTransport := OptDisableQUIC\n\t\t\tif useQuic {\n\t\t\t\tif tc.disableOnQUIC {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\ttrString = \"QUIC\"\n\t\t\t\toptTransport = OptDisableTCP\n\t\t\t}\n\t\t\tt.Run(fmt.Sprintf(\"%s %s\", n, trString), func(t *testing.T) {\n\t\t\t\tp1Gater := DefaultMockConnectionGater()\n\t\t\t\tp2Gater := DefaultMockConnectionGater()\n\t\t\t\tif tc.p1Gater != nil {\n\t\t\t\t\tp1Gater = tc.p1Gater(p1Gater)\n\t\t\t\t}\n\t\t\t\tif tc.p2Gater != nil {\n\t\t\t\t\tp2Gater = tc.p2Gater(p2Gater)\n\t\t\t\t}\n\n\t\t\t\tsw1 := GenSwarm(t, OptConnGater(p1Gater), optTransport)\n\t\t\t\tsw2 := GenSwarm(t, OptConnGater(p2Gater), optTransport)\n\n\t\t\t\tp1 := sw1.LocalPeer()\n\t\t\t\tp2 := sw2.LocalPeer()\n\t\t\t\tsw1.Peerstore().AddAddr(p2, sw2.ListenAddresses()[0], peerstore.PermanentAddrTTL)\n\t\t\t\t// 1 -> 2\n\t\t\t\t_, err := sw1.DialPeer(ctx, p2)\n\n\t\t\t\trequire.Equal(t, tc.isP1OutboundErr, err != nil, n)\n\t\t\t\trequire.Equal(t, tc.p1ConnectednessToP2, sw1.Connectedness(p2), n)\n\n\t\t\t\trequire.Eventually(t, func() bool {\n\t\t\t\t\treturn tc.p2ConnectednessToP1 == sw2.Connectedness(p1)\n\t\t\t\t}, 2*time.Second, 100*time.Millisecond, n)\n\t\t\t})\n\t\t}\n\t}\n}\n\nfunc TestNoDial(t *testing.T) {\n\tswarms := makeSwarms(t, 2)\n\n\t_, err := swarms[0].NewStream(network.WithNoDial(context.Background(), \"swarm test\"), swarms[1].LocalPeer())\n\tif err != network.ErrNoConn {\n\t\tt.Fatal(\"should have failed with ErrNoConn\")\n\t}\n}\n\nfunc TestCloseWithOpenStreams(t *testing.T) {\n\tctx := context.Background()\n\tswarms := makeSwarms(t, 2)\n\tconnectSwarms(t, ctx, swarms)\n\n\ts, err := swarms[0].NewStream(ctx, swarms[1].LocalPeer())\n\trequire.NoError(t, err)\n\tdefer s.Close()\n\t// close swarm before stream.\n\trequire.NoError(t, swarms[0].Close())\n}\n\nfunc TestTypedNilConn(t *testing.T) {\n\ts := GenSwarm(t)\n\tdefer s.Close()\n\n\t// We can't dial ourselves.\n\tc, err := s.DialPeer(context.Background(), s.LocalPeer())\n\trequire.Error(t, err)\n\t// If we fail to dial, the connection should be nil.\n\trequire.Nil(t, c)\n}\n\nfunc TestPreventDialListenAddr(t *testing.T) {\n\ts := GenSwarm(t, OptDialOnly)\n\tif err := s.Listen(ma.StringCast(\"/ip4/0.0.0.0/udp/0/quic-v1\")); err != nil {\n\t\tt.Fatal(err)\n\t}\n\taddrs, err := s.InterfaceListenAddresses()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tvar addr ma.Multiaddr\n\tfor _, a := range addrs {\n\t\t_, s, err := manet.DialArgs(a)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif strings.Split(s, \":\")[0] == \"127.0.0.1\" {\n\t\t\taddr = a\n\t\t\tbreak\n\t\t}\n\t}\n\tremote := test.RandPeerIDFatal(t)\n\ts.Peerstore().AddAddr(remote, addr, time.Hour)\n\t_, err = s.DialPeer(context.Background(), remote)\n\tif !errors.Is(err, swarm.ErrNoGoodAddresses) {\n\t\tt.Fatal(\"expected dial to fail: %w\", err)\n\t}\n}\n\nfunc TestStreamCount(t *testing.T) {\n\ts1 := GenSwarm(t)\n\ts2 := GenSwarm(t)\n\tconnectSwarms(t, context.Background(), []*swarm.Swarm{s2, s1})\n\n\tcountStreams := func() (n int) {\n\t\tvar num int\n\t\tfor _, c := range s1.ConnsToPeer(s2.LocalPeer()) {\n\t\t\tn += c.Stat().NumStreams\n\t\t\tnum += len(c.GetStreams())\n\t\t}\n\t\trequire.Equal(t, n, num, \"inconsistent stream count\")\n\t\treturn\n\t}\n\n\tstreams := make(chan network.Stream, 20)\n\tstreamAccepted := make(chan struct{}, 1)\n\ts1.SetStreamHandler(func(str network.Stream) {\n\t\tstreams <- str\n\t\tstreamAccepted <- struct{}{}\n\t})\n\n\tfor range 10 {\n\t\tstr, err := s2.NewStream(context.Background(), s1.LocalPeer())\n\t\trequire.NoError(t, err)\n\t\tstr.Write([]byte(\"foobar\"))\n\t\t<-streamAccepted\n\t}\n\trequire.Eventually(t, func() bool { return len(streams) == 10 }, 5*time.Second, 10*time.Millisecond)\n\trequire.Equal(t, 10, countStreams())\n\t(<-streams).Reset()\n\t(<-streams).Close()\n\trequire.Equal(t, 8, countStreams())\n\n\tstr, err := s1.NewStream(context.Background(), s2.LocalPeer())\n\trequire.NoError(t, err)\n\trequire.Equal(t, 9, countStreams())\n\tstr.Close()\n\trequire.Equal(t, 8, countStreams())\n}\n\nfunc TestResourceManager(t *testing.T) {\n\tctrl := gomock.NewController(t)\n\tdefer ctrl.Finish()\n\n\trcmgr1 := mocknetwork.NewMockResourceManager(ctrl)\n\ts1 := GenSwarm(t, WithSwarmOpts(swarm.WithResourceManager(rcmgr1)))\n\tdefer s1.Close()\n\n\trcmgr2 := mocknetwork.NewMockResourceManager(ctrl)\n\ts2 := GenSwarm(t, WithSwarmOpts(swarm.WithResourceManager(rcmgr2)))\n\tdefer s2.Close()\n\tconnectSwarms(t, context.Background(), []*swarm.Swarm{s1, s2})\n\n\tstrChan := make(chan network.Stream)\n\ts2.SetStreamHandler(func(str network.Stream) { strChan <- str })\n\n\tstreamScope1 := mocknetwork.NewMockStreamManagementScope(ctrl)\n\trcmgr1.EXPECT().OpenStream(s2.LocalPeer(), network.DirOutbound).Return(streamScope1, nil)\n\tstreamScope2 := mocknetwork.NewMockStreamManagementScope(ctrl)\n\trcmgr2.EXPECT().OpenStream(s1.LocalPeer(), network.DirInbound).Return(streamScope2, nil)\n\tstr, err := s1.NewStream(context.Background(), s2.LocalPeer())\n\trequire.NoError(t, err)\n\tdefer str.Close()\n\tstr.Write([]byte(\"foobar\"))\n\n\tp := protocol.ID(\"proto\")\n\tstreamScope1.EXPECT().SetProtocol(p)\n\trequire.NoError(t, str.SetProtocol(p))\n\n\tsstr := <-strChan\n\tstreamScope2.EXPECT().Done()\n\trequire.NoError(t, sstr.Close())\n\tstreamScope1.EXPECT().Done()\n}\n\nfunc TestResourceManagerNewStream(t *testing.T) {\n\tctrl := gomock.NewController(t)\n\tdefer ctrl.Finish()\n\n\trcmgr1 := mocknetwork.NewMockResourceManager(ctrl)\n\ts1 := GenSwarm(t, WithSwarmOpts(swarm.WithResourceManager(rcmgr1)))\n\tdefer s1.Close()\n\n\ts2 := GenSwarm(t)\n\tdefer s2.Close()\n\n\tconnectSwarms(t, context.Background(), []*swarm.Swarm{s1, s2})\n\n\trerr := errors.New(\"denied\")\n\trcmgr1.EXPECT().OpenStream(s2.LocalPeer(), network.DirOutbound).Return(nil, rerr)\n\t_, err := s1.NewStream(context.Background(), s2.LocalPeer())\n\trequire.ErrorIs(t, err, rerr)\n}\n\nfunc TestResourceManagerAcceptStream(t *testing.T) {\n\tctrl := gomock.NewController(t)\n\tdefer ctrl.Finish()\n\n\trcmgr1 := mocknetwork.NewMockResourceManager(ctrl)\n\ts1 := GenSwarm(t, WithSwarmOpts(swarm.WithResourceManager(rcmgr1)))\n\tdefer s1.Close()\n\n\trcmgr2 := mocknetwork.NewMockResourceManager(ctrl)\n\ts2 := GenSwarm(t, WithSwarmOpts(swarm.WithResourceManager(rcmgr2)))\n\tdefer s2.Close()\n\ts2.SetStreamHandler(func(_ network.Stream) { t.Fatal(\"didn't expect to accept a stream\") })\n\n\tconnectSwarms(t, context.Background(), []*swarm.Swarm{s1, s2})\n\n\tstreamScope := mocknetwork.NewMockStreamManagementScope(ctrl)\n\trcmgr1.EXPECT().OpenStream(s2.LocalPeer(), network.DirOutbound).Return(streamScope, nil)\n\tstreamScope.EXPECT().Done()\n\trcmgr2.EXPECT().OpenStream(s1.LocalPeer(), network.DirInbound).Return(nil, errors.New(\"nope\"))\n\tstr, err := s1.NewStream(context.Background(), s2.LocalPeer())\n\trequire.NoError(t, err)\n\t// The peer's resource manager is blocking any new stream.\n\t// Depending on how quickly we receive the stream reset, it surfaces either during the write or the read call.\n\t_, err = str.Write([]byte(\"foobar\"))\n\tif err == nil {\n\t\t_, err = str.Read([]byte{0})\n\t}\n\trequire.ErrorContains(t, err, \"stream reset\")\n}\n\nfunc TestListenCloseCount(t *testing.T) {\n\ts := GenSwarm(t, OptDialOnly)\n\taddrsToListen := []ma.Multiaddr{\n\t\tma.StringCast(\"/ip4/0.0.0.0/tcp/0\"),\n\t\tma.StringCast(\"/ip4/0.0.0.0/udp/0/quic-v1\"),\n\t}\n\n\tif err := s.Listen(addrsToListen...); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tlistenedAddrs := s.ListenAddresses()\n\trequire.Len(t, listenedAddrs, 2)\n\tvar addrToClose ma.Multiaddr\n\tfor _, addr := range listenedAddrs {\n\t\tif _, err := addr.ValueForProtocol(ma.P_QUIC_V1); err == nil {\n\t\t\t// make a copy of the address to make sure the multiaddr comparison actually works\n\t\t\taddrToClose = ma.StringCast(addr.String())\n\t\t}\n\t}\n\n\ts.ListenClose(addrToClose)\n\n\tremainingAddrs := s.ListenAddresses()\n\trequire.Len(t, remainingAddrs, 1)\n\t_, err := remainingAddrs[0].ValueForProtocol(ma.P_TCP)\n\trequire.NoError(t, err, \"expected the TCP address to still be present\")\n}\n\nfunc TestAddCertHashes(t *testing.T) {\n\ts := GenSwarm(t)\n\n\tlistenAddrs := s.ListenAddresses()\n\tsplitCertHashes := func(a ma.Multiaddr) (prefix, certhashes ma.Multiaddr, ok bool) {\n\t\tfor i, c := range a {\n\t\t\tif c.Protocol().Code == ma.P_CERTHASH {\n\t\t\t\treturn prefix, a[i:], true\n\t\t\t}\n\t\t\tprefix = append(prefix, c)\n\t\t}\n\t\treturn prefix, certhashes, false\n\t}\n\taddrWithNewIPPort := func(addr ma.Multiaddr, newIPPort ma.Multiaddr) ma.Multiaddr {\n\t\ta := slices.Clone(addr)\n\t\ta[0] = newIPPort[0]\n\t\ta[1] = newIPPort[1]\n\t\treturn a\n\t}\n\tpublicIPPort := []ma.Multiaddr{\n\t\tma.StringCast(\"/ip4/1.1.1.1/udp/1\"),\n\t\tma.StringCast(\"/ip4/1.2.3.4/udp/1\"),\n\t\tma.StringCast(\"/ip6/2005::/udp/1\"),\n\t}\n\n\tcertHashComponent := ma.StringCast(\"/certhash/uEgNmb28\")\n\tfor _, a := range listenAddrs {\n\t\tprefix, certhashes, ok := splitCertHashes(a)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tvar publicAddrs []ma.Multiaddr\n\t\tfor _, tc := range publicIPPort {\n\t\t\tpublicAddrs = append(publicAddrs, addrWithNewIPPort(prefix, tc))\n\t\t}\n\t\tfinalAddrs := s.AddCertHashes(publicAddrs)\n\t\tfor _, a := range finalAddrs {\n\t\t\t_, certhash2, ok := splitCertHashes(a)\n\t\t\trequire.True(t, ok)\n\t\t\trequire.Equal(t, certhashes, certhash2)\n\t\t}\n\n\t\t// if the addr has a certhash already, check it isn't modified\n\t\tpublicAddrs = nil\n\t\tfor _, tc := range publicIPPort {\n\t\t\ta := addrWithNewIPPort(prefix, tc)\n\t\t\ta = append(a, certHashComponent...)\n\t\t\tpublicAddrs = append(publicAddrs, a)\n\t\t}\n\t\tfinalAddrs = s.AddCertHashes(publicAddrs)\n\t\tfor _, a := range finalAddrs {\n\t\t\t_, certhash2, ok := splitCertHashes(a)\n\t\t\trequire.True(t, ok)\n\t\t\trequire.Equal(t, certHashComponent, certhash2)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "p2p/net/swarm/swarm_transport.go",
    "content": "package swarm\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/transport\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\n// TransportForDialing retrieves the appropriate transport for dialing the given\n// multiaddr.\nfunc (s *Swarm) TransportForDialing(a ma.Multiaddr) transport.Transport {\n\tif a == nil {\n\t\treturn nil\n\t}\n\tprotocols := a.Protocols()\n\tif len(protocols) == 0 {\n\t\treturn nil\n\t}\n\n\ts.transports.RLock()\n\tdefer s.transports.RUnlock()\n\n\tif len(s.transports.m) == 0 {\n\t\t// make sure we're not just shutting down.\n\t\tif s.transports.m != nil {\n\t\t\tlog.Error(\"you have no transports configured\")\n\t\t}\n\t\treturn nil\n\t}\n\tif isRelayAddr(a) {\n\t\treturn s.transports.m[ma.P_CIRCUIT]\n\t}\n\tif id, _ := peer.IDFromP2PAddr(a); id != \"\" {\n\t\t// This addr has a p2p component. Drop it so we can check transport.\n\t\ta, _ = ma.SplitLast(a)\n\t\tif a == nil {\n\t\t\treturn nil\n\t\t}\n\t}\n\tfor _, t := range s.transports.m {\n\t\tif t.CanDial(a) {\n\t\t\treturn t\n\t\t}\n\t}\n\treturn nil\n}\n\n// TransportForListening retrieves the appropriate transport for listening on\n// the given multiaddr.\nfunc (s *Swarm) TransportForListening(a ma.Multiaddr) transport.Transport {\n\tprotocols := a.Protocols()\n\tif len(protocols) == 0 {\n\t\treturn nil\n\t}\n\n\ts.transports.RLock()\n\tdefer s.transports.RUnlock()\n\tif len(s.transports.m) == 0 {\n\t\treturn nil\n\t}\n\n\tselected := s.transports.m[protocols[len(protocols)-1].Code]\n\tfor _, p := range protocols {\n\t\ttransport, ok := s.transports.m[p.Code]\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif transport.Proxy() {\n\t\t\tselected = transport\n\t\t}\n\t}\n\treturn selected\n}\n\n// AddTransport adds a transport to this swarm.\n//\n// Satisfies the Network interface from go-libp2p-transport.\nfunc (s *Swarm) AddTransport(t transport.Transport) error {\n\tprotocols := t.Protocols()\n\n\tif len(protocols) == 0 {\n\t\treturn fmt.Errorf(\"useless transport handles no protocols: %T\", t)\n\t}\n\n\ts.transports.Lock()\n\tdefer s.transports.Unlock()\n\tif s.transports.m == nil {\n\t\treturn ErrSwarmClosed\n\t}\n\tvar registered []string\n\tfor _, p := range protocols {\n\t\tif _, ok := s.transports.m[p]; ok {\n\t\t\tproto := ma.ProtocolWithCode(p)\n\t\t\tname := proto.Name\n\t\t\tif name == \"\" {\n\t\t\t\tname = fmt.Sprintf(\"unknown (%d)\", p)\n\t\t\t}\n\t\t\tregistered = append(registered, name)\n\t\t}\n\t}\n\tif len(registered) > 0 {\n\t\treturn fmt.Errorf(\n\t\t\t\"transports already registered for protocol(s): %s\",\n\t\t\tstrings.Join(registered, \", \"),\n\t\t)\n\t}\n\n\tfor _, p := range protocols {\n\t\ts.transports.m[p] = t\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "p2p/net/swarm/testing/testing.go",
    "content": "package testing\n\nimport (\n\t\"crypto/rand\"\n\t\"net\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/connmgr\"\n\t\"github.com/libp2p/go-libp2p/core/control\"\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/event\"\n\t\"github.com/libp2p/go-libp2p/core/metrics\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\t\"github.com/libp2p/go-libp2p/core/sec\"\n\t\"github.com/libp2p/go-libp2p/core/transport\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/eventbus\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoremem\"\n\t\"github.com/libp2p/go-libp2p/p2p/muxer/yamux\"\n\t\"github.com/libp2p/go-libp2p/p2p/net/swarm\"\n\ttptu \"github.com/libp2p/go-libp2p/p2p/net/upgrader\"\n\t\"github.com/libp2p/go-libp2p/p2p/security/insecure\"\n\tlibp2pquic \"github.com/libp2p/go-libp2p/p2p/transport/quic\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/quicreuse\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/tcp\"\n\tlibp2pwebrtc \"github.com/libp2p/go-libp2p/p2p/transport/webrtc\"\n\tlibp2pwebtransport \"github.com/libp2p/go-libp2p/p2p/transport/webtransport\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/quic-go/quic-go\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype config struct {\n\tdisableReuseport    bool\n\tdialOnly            bool\n\tdisableTCP          bool\n\tdisableQUIC         bool\n\tdisableWebTransport bool\n\tdisableWebRTC       bool\n\tconnectionGater     connmgr.ConnectionGater\n\tsk                  crypto.PrivKey\n\tswarmOpts           []swarm.Option\n\teventBus            event.Bus\n\tclock\n}\n\ntype clock interface {\n\tNow() time.Time\n}\n\ntype realclock struct{}\n\nfunc (rc realclock) Now() time.Time {\n\treturn time.Now()\n}\n\n// Option is an option that can be passed when constructing a test swarm.\ntype Option func(testing.TB, *config)\n\n// WithClock sets the clock to use for this swarm\nfunc WithClock(clock clock) Option {\n\treturn func(_ testing.TB, c *config) {\n\t\tc.clock = clock\n\t}\n}\n\nfunc WithSwarmOpts(swarmOpts ...swarm.Option) Option {\n\treturn func(_ testing.TB, c *config) {\n\t\tc.swarmOpts = swarmOpts\n\t}\n}\n\n// OptDisableReuseport disables reuseport in this test swarm.\nvar OptDisableReuseport Option = func(_ testing.TB, c *config) {\n\tc.disableReuseport = true\n}\n\n// OptDialOnly prevents the test swarm from listening.\nvar OptDialOnly Option = func(_ testing.TB, c *config) {\n\tc.dialOnly = true\n}\n\n// OptDisableTCP disables TCP.\nvar OptDisableTCP Option = func(_ testing.TB, c *config) {\n\tc.disableTCP = true\n}\n\n// OptDisableQUIC disables QUIC.\nvar OptDisableQUIC Option = func(_ testing.TB, c *config) {\n\tc.disableQUIC = true\n}\n\n// OptDisableWebTransport disables WebTransport.\nvar OptDisableWebTransport Option = func(_ testing.TB, c *config) {\n\tc.disableWebTransport = true\n}\n\n// OptDisableWebRTC disables WebRTC.\nvar OptDisableWebRTC Option = func(_ testing.TB, c *config) {\n\tc.disableWebRTC = true\n}\n\n// OptConnGater configures the given connection gater on the test\nfunc OptConnGater(cg connmgr.ConnectionGater) Option {\n\treturn func(_ testing.TB, c *config) {\n\t\tc.connectionGater = cg\n\t}\n}\n\n// OptPeerPrivateKey configures the peer private key which is then used to derive the public key and peer ID.\nfunc OptPeerPrivateKey(sk crypto.PrivKey) Option {\n\treturn func(_ testing.TB, c *config) {\n\t\tc.sk = sk\n\t}\n}\n\nfunc EventBus(b event.Bus) Option {\n\treturn func(_ testing.TB, c *config) {\n\t\tc.eventBus = b\n\t}\n}\n\n// GenUpgrader creates a new connection upgrader for use with this swarm.\nfunc GenUpgrader(t testing.TB, n *swarm.Swarm, connGater connmgr.ConnectionGater, opts ...tptu.Option) transport.Upgrader {\n\tid := n.LocalPeer()\n\tpk := n.Peerstore().PrivKey(id)\n\tst := insecure.NewWithIdentity(insecure.ID, id, pk)\n\n\tu, err := tptu.New([]sec.SecureTransport{st}, []tptu.StreamMuxer{{ID: yamux.ID, Muxer: yamux.DefaultTransport}}, nil, nil, connGater, opts...)\n\trequire.NoError(t, err)\n\treturn u\n}\n\n// GenSwarm generates a new test swarm.\nfunc GenSwarm(t testing.TB, opts ...Option) *swarm.Swarm {\n\tvar cfg config\n\tcfg.clock = realclock{}\n\tfor _, o := range opts {\n\t\to(t, &cfg)\n\t}\n\n\tvar priv crypto.PrivKey\n\tif cfg.sk == nil {\n\t\tvar err error\n\t\tpriv, _, err = crypto.GenerateEd25519Key(rand.Reader)\n\t\trequire.NoError(t, err)\n\t} else {\n\t\tpriv = cfg.sk\n\t}\n\tid, err := peer.IDFromPrivateKey(priv)\n\trequire.NoError(t, err)\n\n\tps, err := pstoremem.NewPeerstore(pstoremem.WithClock(cfg.clock))\n\trequire.NoError(t, err)\n\tps.AddPubKey(id, priv.GetPublic())\n\tps.AddPrivKey(id, priv)\n\tt.Cleanup(func() { ps.Close() })\n\n\tswarmOpts := cfg.swarmOpts\n\tswarmOpts = append(swarmOpts, swarm.WithMetrics(metrics.NewBandwidthCounter()))\n\tif cfg.connectionGater != nil {\n\t\tswarmOpts = append(swarmOpts, swarm.WithConnectionGater(cfg.connectionGater))\n\t}\n\n\teventBus := cfg.eventBus\n\tif eventBus == nil {\n\t\teventBus = eventbus.NewBus()\n\t}\n\ts, err := swarm.NewSwarm(id, ps, eventBus, swarmOpts...)\n\trequire.NoError(t, err)\n\n\tupgrader := GenUpgrader(t, s, cfg.connectionGater)\n\n\tif !cfg.disableTCP {\n\t\tvar tcpOpts []tcp.Option\n\t\tif cfg.disableReuseport {\n\t\t\ttcpOpts = append(tcpOpts, tcp.DisableReuseport())\n\t\t}\n\t\ttcpTransport, err := tcp.NewTCPTransport(upgrader, nil, nil, tcpOpts...)\n\t\trequire.NoError(t, err)\n\t\tif err := s.AddTransport(tcpTransport); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif !cfg.dialOnly {\n\t\t\tif err := s.Listen(ma.StringCast(\"/ip4/127.0.0.1/tcp/0\")); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t}\n\t}\n\tvar reuse *quicreuse.ConnManager\n\tif !cfg.disableQUIC {\n\t\treuse, err = quicreuse.NewConnManager(quic.StatelessResetKey{}, quic.TokenGeneratorKey{})\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tquicTransport, err := libp2pquic.NewTransport(priv, reuse, nil, cfg.connectionGater, nil)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif err := s.AddTransport(quicTransport); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif !cfg.dialOnly {\n\t\t\tif err := s.Listen(ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1\")); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t}\n\t}\n\tif !cfg.disableWebTransport {\n\t\tif reuse == nil {\n\t\t\treuse, err = quicreuse.NewConnManager(quic.StatelessResetKey{}, quic.TokenGeneratorKey{})\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t}\n\t\twtTransport, err := libp2pwebtransport.New(priv, nil, reuse, cfg.connectionGater, nil)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif err := s.AddTransport(wtTransport); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif !cfg.dialOnly {\n\t\t\tif err := s.Listen(ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1/webtransport\")); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t}\n\t}\n\tif !cfg.disableWebRTC {\n\t\tlistenUDPFn := func(network string, laddr *net.UDPAddr) (net.PacketConn, error) {\n\t\t\treturn net.ListenUDP(network, laddr)\n\t\t}\n\t\twrtcTransport, err := libp2pwebrtc.New(priv, nil, cfg.connectionGater, nil, listenUDPFn)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif err := s.AddTransport(wrtcTransport); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif !cfg.dialOnly {\n\t\t\tif err := s.Listen(ma.StringCast(\"/ip4/127.0.0.1/udp/0/webrtc-direct\")); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t}\n\t}\n\tif !cfg.dialOnly {\n\t\ts.Peerstore().AddAddrs(id, s.ListenAddresses(), peerstore.PermanentAddrTTL)\n\t}\n\treturn s\n}\n\n// DivulgeAddresses adds swarm a's addresses to swarm b's peerstore.\nfunc DivulgeAddresses(a, b network.Network) {\n\tid := a.LocalPeer()\n\taddrs := a.Peerstore().Addrs(id)\n\tb.Peerstore().AddAddrs(id, addrs, peerstore.PermanentAddrTTL)\n}\n\n// MockConnectionGater is a mock connection gater to be used by the tests.\ntype MockConnectionGater struct {\n\tDial     func(p peer.ID, addr ma.Multiaddr) bool\n\tPeerDial func(p peer.ID) bool\n\tAccept   func(c network.ConnMultiaddrs) bool\n\tSecured  func(network.Direction, peer.ID, network.ConnMultiaddrs) bool\n\tUpgraded func(c network.Conn) (bool, control.DisconnectReason)\n}\n\nfunc DefaultMockConnectionGater() *MockConnectionGater {\n\tm := &MockConnectionGater{}\n\tm.Dial = func(_ peer.ID, _ ma.Multiaddr) bool {\n\t\treturn true\n\t}\n\n\tm.PeerDial = func(_ peer.ID) bool {\n\t\treturn true\n\t}\n\n\tm.Accept = func(_ network.ConnMultiaddrs) bool {\n\t\treturn true\n\t}\n\n\tm.Secured = func(network.Direction, peer.ID, network.ConnMultiaddrs) bool {\n\t\treturn true\n\t}\n\n\tm.Upgraded = func(_ network.Conn) (bool, control.DisconnectReason) {\n\t\treturn true, 0\n\t}\n\n\treturn m\n}\n\nfunc (m *MockConnectionGater) InterceptAddrDial(p peer.ID, addr ma.Multiaddr) (allow bool) {\n\treturn m.Dial(p, addr)\n}\n\nfunc (m *MockConnectionGater) InterceptPeerDial(p peer.ID) (allow bool) {\n\treturn m.PeerDial(p)\n}\n\nfunc (m *MockConnectionGater) InterceptAccept(c network.ConnMultiaddrs) (allow bool) {\n\treturn m.Accept(c)\n}\n\nfunc (m *MockConnectionGater) InterceptSecured(d network.Direction, p peer.ID, c network.ConnMultiaddrs) (allow bool) {\n\treturn m.Secured(d, p, c)\n}\n\nfunc (m *MockConnectionGater) InterceptUpgraded(tc network.Conn) (allow bool, reason control.DisconnectReason) {\n\treturn m.Upgraded(tc)\n}\n"
  },
  {
    "path": "p2p/net/swarm/testing/testing_test.go",
    "content": "package testing\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestGenSwarm(t *testing.T) {\n\tswarm := GenSwarm(t)\n\trequire.NoError(t, swarm.Close())\n\tGenUpgrader(t, swarm, nil)\n}\n"
  },
  {
    "path": "p2p/net/swarm/transport_test.go",
    "content": "package swarm_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/transport\"\n\t\"github.com/libp2p/go-libp2p/p2p/net/swarm\"\n\tswarmt \"github.com/libp2p/go-libp2p/p2p/net/swarm/testing\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype dummyTransport struct {\n\tprotocols []int\n\tproxy     bool\n\tclosed    bool\n}\n\nfunc (dt *dummyTransport) Dial(_ context.Context, _ ma.Multiaddr, _ peer.ID) (transport.CapableConn, error) {\n\tpanic(\"unimplemented\")\n}\n\nfunc (dt *dummyTransport) CanDial(_ ma.Multiaddr) bool {\n\tpanic(\"unimplemented\")\n}\n\nfunc (dt *dummyTransport) Listen(_ ma.Multiaddr) (transport.Listener, error) {\n\tpanic(\"unimplemented\")\n}\n\nfunc (dt *dummyTransport) Proxy() bool {\n\treturn dt.proxy\n}\n\nfunc (dt *dummyTransport) Protocols() []int {\n\treturn dt.protocols\n}\nfunc (dt *dummyTransport) Close() error {\n\tdt.closed = true\n\treturn nil\n}\n\nfunc TestUselessTransport(t *testing.T) {\n\ts := swarmt.GenSwarm(t)\n\trequire.Error(t, s.AddTransport(new(dummyTransport)), \"adding a transport that supports no protocols should have failed\")\n}\n\nfunc TestTransportClose(t *testing.T) {\n\ts := swarmt.GenSwarm(t)\n\ttpt := &dummyTransport{protocols: []int{1}}\n\trequire.NoError(t, s.AddTransport(tpt))\n\t_ = s.Close()\n\tif !tpt.closed {\n\t\tt.Fatal(\"expected transport to be closed\")\n\t}\n}\n\nfunc TestTransportAfterClose(t *testing.T) {\n\ts := swarmt.GenSwarm(t)\n\ts.Close()\n\n\ttpt := &dummyTransport{protocols: []int{1}}\n\tif err := s.AddTransport(tpt); err != swarm.ErrSwarmClosed {\n\t\tt.Fatal(\"expected swarm closed error, got: \", err)\n\t}\n}\n"
  },
  {
    "path": "p2p/net/swarm/util_test.go",
    "content": "package swarm\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p/core/test\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestIsFdConsuming(t *testing.T) {\n\ttcs := map[string]struct {\n\t\taddr          string\n\t\tisFdConsuming bool\n\t}{\n\t\t\"tcp\": {\n\t\t\taddr:          \"/ip4/127.0.0.1/tcp/20\",\n\t\t\tisFdConsuming: true,\n\t\t},\n\t\t\"quic\": {\n\t\t\taddr:          \"/ip4/127.0.0.1/udp/0/quic-v1\",\n\t\t\tisFdConsuming: false,\n\t\t},\n\t\t\"addr-without-registered-transport\": {\n\t\t\taddr:          \"/ip4/127.0.0.1/tcp/20/ws\",\n\t\t\tisFdConsuming: true,\n\t\t},\n\t\t\"relay-tcp\": {\n\t\t\taddr:          fmt.Sprintf(\"/ip4/127.0.0.1/tcp/20/p2p-circuit/p2p/%s\", test.RandPeerIDFatal(t)),\n\t\t\tisFdConsuming: true,\n\t\t},\n\t\t\"relay-quic\": {\n\t\t\taddr:          fmt.Sprintf(\"/ip4/127.0.0.1/udp/20/quic/p2p-circuit/p2p/%s\", test.RandPeerIDFatal(t)),\n\t\t\tisFdConsuming: false,\n\t\t},\n\t\t\"relay-without-serveraddr\": {\n\t\t\taddr:          fmt.Sprintf(\"/p2p-circuit/p2p/%s\", test.RandPeerIDFatal(t)),\n\t\t\tisFdConsuming: true,\n\t\t},\n\t\t\"relay-without-registered-transport-server\": {\n\t\t\taddr:          fmt.Sprintf(\"/ip4/127.0.0.1/tcp/20/ws/p2p-circuit/p2p/%s\", test.RandPeerIDFatal(t)),\n\t\t\tisFdConsuming: true,\n\t\t},\n\t}\n\n\tfor name := range tcs {\n\t\tmaddr, err := ma.NewMultiaddr(tcs[name].addr)\n\t\trequire.NoError(t, err, name)\n\t\trequire.Equal(t, tcs[name].isFdConsuming, isFdConsumingAddr(maddr), name)\n\t}\n}\n"
  },
  {
    "path": "p2p/net/upgrader/conn.go",
    "content": "package upgrader\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\t\"github.com/libp2p/go-libp2p/core/transport\"\n)\n\ntype transportConn struct {\n\tnetwork.MuxedConn\n\tnetwork.ConnMultiaddrs\n\tnetwork.ConnSecurity\n\ttransport transport.Transport\n\tscope     network.ConnManagementScope\n\tstat      network.ConnStats\n\n\tmuxer                     protocol.ID\n\tsecurity                  protocol.ID\n\tusedEarlyMuxerNegotiation bool\n}\n\nvar _ transport.CapableConn = &transportConn{}\n\nfunc (c *transportConn) As(target any) bool {\n\treturn c.MuxedConn.As(target)\n}\n\nfunc (t *transportConn) Transport() transport.Transport {\n\treturn t.transport\n}\n\nfunc (t *transportConn) String() string {\n\tts := \"\"\n\tif s, ok := t.transport.(fmt.Stringer); ok {\n\t\tts = \"[\" + s.String() + \"]\"\n\t}\n\treturn fmt.Sprintf(\n\t\t\"<stream.Conn%s %s (%s) <-> %s (%s)>\",\n\t\tts,\n\t\tt.LocalMultiaddr(),\n\t\tt.LocalPeer(),\n\t\tt.RemoteMultiaddr(),\n\t\tt.RemotePeer(),\n\t)\n}\n\nfunc (t *transportConn) Stat() network.ConnStats {\n\treturn t.stat\n}\n\nfunc (t *transportConn) Scope() network.ConnScope {\n\treturn t.scope\n}\n\nfunc (t *transportConn) Close() error {\n\tdefer t.scope.Done()\n\treturn t.MuxedConn.Close()\n}\n\nfunc (t *transportConn) ConnState() network.ConnectionState {\n\treturn network.ConnectionState{\n\t\tStreamMultiplexer:         t.muxer,\n\t\tSecurity:                  t.security,\n\t\tTransport:                 \"tcp\",\n\t\tUsedEarlyMuxerNegotiation: t.usedEarlyMuxerNegotiation,\n\t}\n}\n\nfunc (t *transportConn) CloseWithError(errCode network.ConnErrorCode) error {\n\tdefer t.scope.Done()\n\treturn t.MuxedConn.CloseWithError(errCode)\n}\n"
  },
  {
    "path": "p2p/net/upgrader/gater_test.go",
    "content": "package upgrader_test\n\nimport (\n\t\"sync\"\n\n\t\"github.com/libp2p/go-libp2p/core/connmgr\"\n\t\"github.com/libp2p/go-libp2p/core/control\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\ntype testGater struct {\n\tsync.Mutex\n\n\tblockAccept, blockSecured bool\n}\n\nvar _ connmgr.ConnectionGater = (*testGater)(nil)\n\nfunc (t *testGater) BlockAccept(block bool) {\n\tt.Lock()\n\tdefer t.Unlock()\n\n\tt.blockAccept = block\n}\n\nfunc (t *testGater) BlockSecured(block bool) {\n\tt.Lock()\n\tdefer t.Unlock()\n\n\tt.blockSecured = block\n}\n\nfunc (t *testGater) InterceptPeerDial(_ peer.ID) (allow bool) {\n\tpanic(\"not implemented\")\n}\n\nfunc (t *testGater) InterceptAddrDial(_ peer.ID, _ ma.Multiaddr) (allow bool) {\n\tpanic(\"not implemented\")\n}\n\nfunc (t *testGater) InterceptAccept(_ network.ConnMultiaddrs) (allow bool) {\n\tt.Lock()\n\tdefer t.Unlock()\n\n\treturn !t.blockAccept\n}\n\nfunc (t *testGater) InterceptSecured(_ network.Direction, _ peer.ID, _ network.ConnMultiaddrs) (allow bool) {\n\tt.Lock()\n\tdefer t.Unlock()\n\n\treturn !t.blockSecured\n}\n\nfunc (t *testGater) InterceptUpgraded(_ network.Conn) (allow bool, reason control.DisconnectReason) {\n\tpanic(\"not implemented\")\n}\n"
  },
  {
    "path": "p2p/net/upgrader/listener.go",
    "content": "package upgrader\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/libp2p/go-libp2p/core/connmgr\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/transport\"\n\n\ttec \"github.com/jbenet/go-temp-err-catcher\"\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\nvar log = logging.Logger(\"upgrader\")\n\ntype listener struct {\n\ttransport.GatedMaListener\n\n\ttransport transport.Transport\n\tupgrader  *upgrader\n\trcmgr     network.ResourceManager\n\n\tincoming chan transport.CapableConn\n\terr      error\n\n\t// Used for backpressure\n\tthreshold *threshold\n\n\t// Canceling this context isn't sufficient to tear down the listener.\n\t// Call close.\n\tctx    context.Context\n\tcancel func()\n}\n\nvar _ transport.Listener = (*listener)(nil)\n\n// Close closes the listener.\nfunc (l *listener) Close() error {\n\t// Do this first to try to get any relevant errors.\n\terr := l.GatedMaListener.Close()\n\n\tl.cancel()\n\t// Drain and wait.\n\tfor c := range l.incoming {\n\t\tc.Close()\n\t}\n\treturn err\n}\n\n// handles inbound connections.\n//\n// This function does a few interesting things that should be noted:\n//\n//  1. It logs and discards temporary/transient errors (errors with a Temporary()\n//     function that returns true).\n//  2. It stops accepting new connections once AcceptQueueLength connections have\n//     been fully negotiated but not accepted. This gives us a basic backpressure\n//     mechanism while still allowing us to negotiate connections in parallel.\nfunc (l *listener) handleIncoming() {\n\tvar wg sync.WaitGroup\n\tdefer func() {\n\t\t// make sure we're closed\n\t\tl.GatedMaListener.Close()\n\t\tif l.err == nil {\n\t\t\tl.err = fmt.Errorf(\"listener closed\")\n\t\t}\n\n\t\twg.Wait()\n\t\tclose(l.incoming)\n\t}()\n\n\tvar catcher tec.TempErrCatcher\n\tfor l.ctx.Err() == nil {\n\t\tmaconn, connScope, err := l.GatedMaListener.Accept()\n\t\tif err != nil {\n\t\t\t// Note: function may pause the accept loop.\n\t\t\tif catcher.IsTemporary(err) {\n\t\t\t\tlog.Info(\"temporary accept error\", \"err\", err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tl.err = err\n\t\t\treturn\n\t\t}\n\t\tcatcher.Reset()\n\n\t\tif connScope == nil {\n\t\t\tlog.Error(\"BUG: got nil connScope for incoming connection\", \"remote_multiaddr\", maconn.RemoteMultiaddr())\n\t\t\tmaconn.Close()\n\t\t\tcontinue\n\t\t}\n\n\t\t// The go routine below calls Release when the context is\n\t\t// canceled so there's no need to wait on it here.\n\t\tl.threshold.Wait()\n\n\t\tlog.Debug(\"listener got connection\",\n\t\t\t\"listener\", l,\n\t\t\t\"local_multiaddr\", maconn.LocalMultiaddr(),\n\t\t\t\"remote_multiaddr\", maconn.RemoteMultiaddr())\n\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\n\t\t\tctx, cancel := context.WithTimeout(l.ctx, l.upgrader.acceptTimeout)\n\t\t\tdefer cancel()\n\n\t\t\tconn, err := l.upgrader.Upgrade(ctx, l.transport, maconn, network.DirInbound, \"\", connScope)\n\t\t\tif err != nil {\n\t\t\t\t// Don't bother bubbling this up. We just failed\n\t\t\t\t// to completely negotiate the connection.\n\t\t\t\tlog.Debug(\"accept upgrade error\",\n\t\t\t\t\t\"err\", err,\n\t\t\t\t\t\"local_multiaddr\", maconn.LocalMultiaddr(),\n\t\t\t\t\t\"remote_multiaddr\", maconn.RemoteMultiaddr())\n\t\t\t\tconnScope.Done()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tlog.Debug(\"listener accepted connection\",\n\t\t\t\t\"listener\", l,\n\t\t\t\t\"connection\", conn)\n\n\t\t\t// This records the fact that the connection has been\n\t\t\t// setup and is waiting to be accepted. This call\n\t\t\t// *never* blocks, even if we go over the threshold. It\n\t\t\t// simply ensures that calls to Wait block while we're\n\t\t\t// over the threshold.\n\t\t\tl.threshold.Acquire()\n\t\t\tdefer l.threshold.Release()\n\n\t\t\tselect {\n\t\t\tcase l.incoming <- conn:\n\t\t\tcase <-ctx.Done():\n\t\t\t\t// Listener not closed but the accept timeout expired.\n\t\t\t\tif l.ctx.Err() == nil {\n\t\t\t\t\tlog.Warn(\"listener dropped connection due to slow accept\", \"remote_multiaddr\", maconn.RemoteMultiaddr(), \"peer\", conn.RemotePeer())\n\t\t\t\t}\n\t\t\t\tconn.CloseWithError(network.ConnRateLimited)\n\t\t\t}\n\t\t}()\n\t}\n}\n\n// Accept accepts a connection.\nfunc (l *listener) Accept() (transport.CapableConn, error) {\n\tfor c := range l.incoming {\n\t\t// Could have been sitting there for a while.\n\t\tif !c.IsClosed() {\n\t\t\treturn c, nil\n\t\t}\n\t}\n\tif strings.Contains(l.err.Error(), \"use of closed network connection\") {\n\t\treturn nil, transport.ErrListenerClosed\n\t}\n\treturn nil, l.err\n}\n\nfunc (l *listener) String() string {\n\tif s, ok := l.transport.(fmt.Stringer); ok {\n\t\treturn fmt.Sprintf(\"<stream.Listener[%s] %s>\", s, l.Multiaddr())\n\t}\n\treturn fmt.Sprintf(\"<stream.Listener %s>\", l.Multiaddr())\n}\n\ntype gatedMaListener struct {\n\tmanet.Listener\n\trcmgr     network.ResourceManager\n\tconnGater connmgr.ConnectionGater\n}\n\nvar _ transport.GatedMaListener = &gatedMaListener{}\n\nfunc (l *gatedMaListener) Accept() (manet.Conn, network.ConnManagementScope, error) {\n\tfor {\n\t\tconn, err := l.Listener.Accept()\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\t\t// gate the connection if applicable\n\t\tif l.connGater != nil && !l.connGater.InterceptAccept(conn) {\n\t\t\tlog.Debug(\"gater blocked incoming connection\",\n\t\t\t\t\"local_multiaddr\", conn.LocalMultiaddr(),\n\t\t\t\t\"remote_multiaddr\", conn.RemoteMultiaddr())\n\t\t\tif err := conn.Close(); err != nil {\n\t\t\t\tlog.Warn(\"failed to close incoming connection rejected by gater\", \"err\", err)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tconnScope, err := l.rcmgr.OpenConnection(network.DirInbound, true, conn.RemoteMultiaddr())\n\t\tif err != nil {\n\t\t\tlog.Debug(\"resource manager blocked accept of new connection\", \"err\", err)\n\t\t\tif err := conn.Close(); err != nil {\n\t\t\t\tlog.Warn(\"failed to open incoming connection. Rejected by resource manager\", \"err\", err)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\treturn conn, connScope, nil\n\t}\n}\n"
  },
  {
    "path": "p2p/net/upgrader/listener_test.go",
    "content": "package upgrader_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\t\"os\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\tmocknetwork \"github.com/libp2p/go-libp2p/core/network/mocks\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/sec\"\n\t\"github.com/libp2p/go-libp2p/core/transport\"\n\t\"github.com/libp2p/go-libp2p/p2p/net/upgrader\"\n\t\"github.com/libp2p/go-libp2p/p2p/security/insecure\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/mock/gomock\"\n)\n\nfunc createListener(t *testing.T, u transport.Upgrader) transport.Listener {\n\tt.Helper()\n\taddr, err := ma.NewMultiaddr(\"/ip4/127.0.0.1/tcp/0\")\n\trequire.NoError(t, err)\n\tln, err := manet.Listen(addr)\n\trequire.NoError(t, err)\n\treturn u.UpgradeGatedMaListener(nil, u.GateMaListener(ln))\n}\n\nfunc TestAcceptSingleConn(t *testing.T) {\n\trequire := require.New(t)\n\n\tid, u := createUpgrader(t)\n\tln := createListener(t, u)\n\tdefer ln.Close()\n\n\tcconn, err := dial(t, u, ln.Multiaddr(), id, &network.NullScope{})\n\trequire.NoError(err)\n\n\tsconn, err := ln.Accept()\n\trequire.NoError(err)\n\n\ttestConn(t, cconn, sconn)\n}\n\nfunc TestAcceptMultipleConns(t *testing.T) {\n\trequire := require.New(t)\n\n\tid, u := createUpgrader(t)\n\tln := createListener(t, u)\n\tdefer ln.Close()\n\n\ttoClose := make([]io.Closer, 0, 20)\n\tdefer func() {\n\t\tfor _, c := range toClose {\n\t\t\t_ = c.Close()\n\t\t}\n\t}()\n\n\tfor range 10 {\n\t\tcconn, err := dial(t, u, ln.Multiaddr(), id, &network.NullScope{})\n\t\trequire.NoError(err)\n\t\ttoClose = append(toClose, cconn)\n\n\t\tsconn, err := ln.Accept()\n\t\trequire.NoError(err)\n\t\ttoClose = append(toClose, sconn)\n\n\t\ttestConn(t, cconn, sconn)\n\t}\n}\n\nfunc TestConnectionsClosedIfNotAccepted(t *testing.T) {\n\trequire := require.New(t)\n\n\tvar timeout = 100 * time.Millisecond\n\tif os.Getenv(\"CI\") != \"\" {\n\t\ttimeout = 500 * time.Millisecond\n\t}\n\n\tid, u := createUpgraderWithOpts(t, upgrader.WithAcceptTimeout(timeout))\n\tln := createListener(t, u)\n\tdefer ln.Close()\n\n\tconn, err := dial(t, u, ln.Multiaddr(), id, &network.NullScope{})\n\trequire.NoError(err)\n\n\terrCh := make(chan error)\n\tgo func() {\n\t\tdefer conn.Close()\n\t\tstr, err := conn.OpenStream(context.Background())\n\t\tif err != nil {\n\t\t\terrCh <- err\n\t\t\treturn\n\t\t}\n\t\t// start a Read. It will block until the connection is closed\n\t\t_, _ = str.Read([]byte{0})\n\t\terrCh <- nil\n\t}()\n\n\ttime.Sleep(timeout / 2)\n\tselect {\n\tcase err := <-errCh:\n\t\tt.Fatalf(\"connection closed earlier than expected. expected nothing on channel, got: %v\", err)\n\tdefault:\n\t}\n\n\ttime.Sleep(timeout)\n\trequire.NoError(<-errCh)\n}\n\nfunc TestFailedUpgradeOnListen(t *testing.T) {\n\trequire := require.New(t)\n\n\tid, u := createUpgraderWithMuxers(t, []upgrader.StreamMuxer{{ID: \"errorMuxer\", Muxer: &errorMuxer{}}}, nil, nil)\n\tln := createListener(t, u)\n\n\terrCh := make(chan error)\n\tgo func() {\n\t\t_, err := ln.Accept()\n\t\terrCh <- err\n\t}()\n\n\t_, err := dial(t, u, ln.Multiaddr(), id, &network.NullScope{})\n\trequire.Error(err)\n\n\t// close the listener.\n\tln.Close()\n\trequire.Error(<-errCh)\n}\n\nfunc TestListenerClose(t *testing.T) {\n\trequire := require.New(t)\n\n\t_, u := createUpgrader(t)\n\tln := createListener(t, u)\n\n\terrCh := make(chan error)\n\tgo func() {\n\t\t_, err := ln.Accept()\n\t\terrCh <- err\n\t}()\n\n\tselect {\n\tcase err := <-errCh:\n\t\tt.Fatalf(\"connection closed earlier than expected. expected nothing on channel, got: %v\", err)\n\tcase <-time.After(200 * time.Millisecond):\n\t\t// nothing in 200ms.\n\t}\n\n\t// unblocks Accept when it is closed.\n\trequire.NoError(ln.Close())\n\terr := <-errCh\n\trequire.Error(err)\n\trequire.Equal(err, transport.ErrListenerClosed)\n\n\t// doesn't accept new connections when it is closed\n\t_, err = dial(t, u, ln.Multiaddr(), peer.ID(\"1\"), &network.NullScope{})\n\trequire.Error(err)\n}\n\nfunc TestListenerCloseClosesQueued(t *testing.T) {\n\trequire := require.New(t)\n\n\tid, upgrader := createUpgrader(t)\n\tln := createListener(t, upgrader)\n\n\tconns := make([]transport.CapableConn, 0, 10)\n\tfor range 10 {\n\t\tconn, err := dial(t, upgrader, ln.Multiaddr(), id, &network.NullScope{})\n\t\trequire.NoError(err)\n\t\tconns = append(conns, conn)\n\t}\n\n\t// wait for all the dials to happen.\n\ttime.Sleep(500 * time.Millisecond)\n\n\t// all the connections are opened.\n\tfor _, c := range conns {\n\t\trequire.False(c.IsClosed())\n\t}\n\n\t// expect that all the connections will be closed.\n\terr := ln.Close()\n\trequire.NoError(err)\n\n\t// all the connections are closed.\n\trequire.Eventually(func() bool {\n\t\tfor _, c := range conns {\n\t\t\tif !c.IsClosed() {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\t}, 3*time.Second, 100*time.Millisecond)\n\n\tfor _, c := range conns {\n\t\t_ = c.Close()\n\t}\n}\n\nfunc TestConcurrentAccept(t *testing.T) {\n\tvar num = 3 * upgrader.AcceptQueueLength\n\n\tblockingMuxer := newBlockingMuxer()\n\tid, u := createUpgraderWithMuxers(t, []upgrader.StreamMuxer{{ID: \"blockingMuxer\", Muxer: blockingMuxer}}, nil, nil)\n\tln := createListener(t, u)\n\tdefer ln.Close()\n\n\taccepted := make(chan transport.CapableConn, num)\n\tgo func() {\n\t\tfor {\n\t\t\tconn, err := ln.Accept()\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t_ = conn.Close()\n\t\t\taccepted <- conn\n\t\t}\n\t}()\n\n\t// start num dials, which all block while setting up the muxer\n\terrCh := make(chan error, num)\n\tvar wg sync.WaitGroup\n\tfor range num {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\n\t\t\tconn, err := dial(t, u, ln.Multiaddr(), id, &network.NullScope{})\n\t\t\tif err != nil {\n\t\t\t\terrCh <- err\n\t\t\t\treturn\n\t\t\t}\n\t\t\tdefer conn.Close()\n\n\t\t\t_, err = conn.AcceptStream() // wait for conn to be accepted.\n\t\t\terrCh <- err\n\t\t}()\n\t}\n\n\ttime.Sleep(200 * time.Millisecond)\n\t// the dials are still blocked, so we shouldn't have any connection available yet\n\trequire.Empty(t, accepted)\n\tblockingMuxer.Unblock() // make all dials succeed\n\trequire.Eventually(t, func() bool { return len(accepted) == num }, 3*time.Second, 100*time.Millisecond)\n\twg.Wait()\n}\n\nfunc TestAcceptQueueBacklogged(t *testing.T) {\n\trequire := require.New(t)\n\n\tid, u := createUpgrader(t)\n\tln := createListener(t, u)\n\tdefer ln.Close()\n\n\t// setup AcceptQueueLength connections, but don't accept any of them\n\tvar counter atomic.Int32\n\tdoDial := func() {\n\t\tconn, err := dial(t, u, ln.Multiaddr(), id, &network.NullScope{})\n\t\trequire.NoError(err)\n\t\tcounter.Add(1)\n\t\tt.Cleanup(func() { conn.Close() })\n\t}\n\n\tfor i := 0; i < upgrader.AcceptQueueLength; i++ {\n\t\tgo doDial()\n\t}\n\n\trequire.Eventually(func() bool { return int(counter.Load()) == upgrader.AcceptQueueLength }, 2*time.Second, 50*time.Millisecond)\n\n\t// dial a new connection. This connection should not complete setup, since the queue is full\n\tgo doDial()\n\n\ttime.Sleep(100 * time.Millisecond)\n\trequire.Equal(int(counter.Load()), upgrader.AcceptQueueLength)\n\n\t// accept a single connection. Now the new connection should be set up, and fill the queue again\n\tconn, err := ln.Accept()\n\trequire.NoError(err)\n\trequire.NoError(conn.Close())\n\n\trequire.Eventually(func() bool { return int(counter.Load()) == upgrader.AcceptQueueLength+1 }, 2*time.Second, 50*time.Millisecond)\n}\n\nfunc TestListenerConnectionGater(t *testing.T) {\n\trequire := require.New(t)\n\n\ttestGater := &testGater{}\n\tid, u := createUpgraderWithConnGater(t, testGater)\n\n\tln := createListener(t, u)\n\tdefer ln.Close()\n\n\t// no gating.\n\tconn, err := dial(t, u, ln.Multiaddr(), id, &network.NullScope{})\n\trequire.NoError(err)\n\trequire.False(conn.IsClosed())\n\t_ = conn.Close()\n\n\t// rejecting after handshake.\n\ttestGater.BlockSecured(true)\n\ttestGater.BlockAccept(false)\n\tconn, err = dial(t, u, ln.Multiaddr(), \"invalid\", &network.NullScope{})\n\trequire.Error(err)\n\trequire.Nil(conn)\n\n\t// rejecting on accept will trigger firupgrader.\n\ttestGater.BlockSecured(true)\n\ttestGater.BlockAccept(true)\n\tconn, err = dial(t, u, ln.Multiaddr(), \"invalid\", &network.NullScope{})\n\trequire.Error(err)\n\trequire.Nil(conn)\n\n\t// rejecting only on acceptance.\n\ttestGater.BlockSecured(false)\n\ttestGater.BlockAccept(true)\n\tconn, err = dial(t, u, ln.Multiaddr(), \"invalid\", &network.NullScope{})\n\trequire.Error(err)\n\trequire.Nil(conn)\n\n\t// back to normal\n\ttestGater.BlockSecured(false)\n\ttestGater.BlockAccept(false)\n\tconn, err = dial(t, u, ln.Multiaddr(), id, &network.NullScope{})\n\trequire.NoError(err)\n\trequire.False(conn.IsClosed())\n\t_ = conn.Close()\n}\n\nfunc TestListenerResourceManagement(t *testing.T) {\n\tctrl := gomock.NewController(t)\n\tdefer ctrl.Finish()\n\trcmgr := mocknetwork.NewMockResourceManager(ctrl)\n\tid, upgrader := createUpgraderWithResourceManager(t, rcmgr)\n\tln := createListener(t, upgrader)\n\tdefer ln.Close()\n\n\tconnScope := mocknetwork.NewMockConnManagementScope(ctrl)\n\tgomock.InOrder(\n\t\trcmgr.EXPECT().OpenConnection(network.DirInbound, true, gomock.Not(ln.Multiaddr())).Return(connScope, nil),\n\t\tconnScope.EXPECT().PeerScope(),\n\t\tconnScope.EXPECT().SetPeer(id),\n\t\tconnScope.EXPECT().PeerScope(),\n\t)\n\n\tcconn, err := dial(t, upgrader, ln.Multiaddr(), id, &network.NullScope{})\n\trequire.NoError(t, err)\n\tdefer cconn.Close()\n\n\tsconn, err := ln.Accept()\n\trequire.NoError(t, err)\n\tconnScope.EXPECT().Done()\n\tdefer sconn.Close()\n}\n\nfunc TestListenerResourceManagementDenied(t *testing.T) {\n\tctrl := gomock.NewController(t)\n\tdefer ctrl.Finish()\n\trcmgr := mocknetwork.NewMockResourceManager(ctrl)\n\tid, upgrader := createUpgraderWithResourceManager(t, rcmgr)\n\tln := createListener(t, upgrader)\n\n\trcmgr.EXPECT().OpenConnection(network.DirInbound, true, gomock.Not(ln.Multiaddr())).Return(nil, errors.New(\"nope\"))\n\t_, err := dial(t, upgrader, ln.Multiaddr(), id, &network.NullScope{})\n\trequire.Error(t, err)\n\n\tdone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(done)\n\t\tln.Accept()\n\t}()\n\n\tselect {\n\tcase <-done:\n\t\tt.Fatal(\"accept shouldn't have accepted anything\")\n\tcase <-time.After(50 * time.Millisecond):\n\t}\n\trequire.NoError(t, ln.Close())\n\t<-done\n}\n\nfunc TestNoCommonSecurityProto(t *testing.T) {\n\tidA, privA := newPeer(t)\n\tidB, privB := newPeer(t)\n\tatInsecure := insecure.NewWithIdentity(\"/plaintext1\", idA, privA)\n\tbtInsecure := insecure.NewWithIdentity(\"/plaintext2\", idB, privB)\n\n\tua, err := upgrader.New([]sec.SecureTransport{atInsecure}, []upgrader.StreamMuxer{{ID: \"negotiate\", Muxer: &negotiatingMuxer{}}}, nil, nil, nil)\n\trequire.NoError(t, err)\n\tub, err := upgrader.New([]sec.SecureTransport{btInsecure}, []upgrader.StreamMuxer{{ID: \"negotiate\", Muxer: &negotiatingMuxer{}}}, nil, nil, nil)\n\trequire.NoError(t, err)\n\n\tln := createListener(t, ua)\n\tdone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(done)\n\t\tln.Accept()\n\t}()\n\n\t_, err = dial(t, ub, ln.Multiaddr(), idA, &network.NullScope{})\n\trequire.ErrorContains(t, err, \"failed to negotiate security protocol: protocols not supported\")\n\tselect {\n\tcase <-done:\n\t\tt.Fatal(\"didn't expect to accept a connection\")\n\tcase <-time.After(50 * time.Millisecond):\n\t}\n\n\tln.Close()\n\t<-done\n}\n"
  },
  {
    "path": "p2p/net/upgrader/threshold.go",
    "content": "package upgrader\n\nimport (\n\t\"sync\"\n)\n\nfunc newThreshold(cutoff int) *threshold {\n\tt := &threshold{\n\t\tthreshold: cutoff,\n\t}\n\tt.cond.L = &t.mu\n\treturn t\n}\n\ntype threshold struct {\n\tmu   sync.Mutex\n\tcond sync.Cond\n\n\tcount     int\n\tthreshold int\n}\n\n// Acquire increments the counter. It will not block.\nfunc (t *threshold) Acquire() {\n\tt.mu.Lock()\n\tt.count++\n\tt.mu.Unlock()\n}\n\n// Release decrements the counter.\nfunc (t *threshold) Release() {\n\tt.mu.Lock()\n\tif t.count == 0 {\n\t\tpanic(\"negative count\")\n\t}\n\tif t.threshold == t.count {\n\t\tt.cond.Broadcast()\n\t}\n\tt.count--\n\tt.mu.Unlock()\n}\n\n// Wait waits for the counter to drop below the threshold\nfunc (t *threshold) Wait() {\n\tt.mu.Lock()\n\tfor t.count >= t.threshold {\n\t\tt.cond.Wait()\n\t}\n\tt.mu.Unlock()\n}\n"
  },
  {
    "path": "p2p/net/upgrader/upgrader.go",
    "content": "package upgrader\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/connmgr\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\tipnet \"github.com/libp2p/go-libp2p/core/pnet\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\t\"github.com/libp2p/go-libp2p/core/sec\"\n\t\"github.com/libp2p/go-libp2p/core/transport\"\n\t\"github.com/libp2p/go-libp2p/p2p/net/pnet\"\n\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n\tmss \"github.com/multiformats/go-multistream\"\n)\n\n// ErrNilPeer is returned when attempting to upgrade an outbound connection\n// without specifying a peer ID.\nvar ErrNilPeer = errors.New(\"nil peer\")\n\n// AcceptQueueLength is the number of connections to fully setup before not accepting any new connections\nvar AcceptQueueLength = 16\n\nconst (\n\tdefaultAcceptTimeout    = 15 * time.Second\n\tdefaultNegotiateTimeout = 60 * time.Second\n)\n\ntype Option func(*upgrader) error\n\nfunc WithAcceptTimeout(t time.Duration) Option {\n\treturn func(u *upgrader) error {\n\t\tu.acceptTimeout = t\n\t\treturn nil\n\t}\n}\n\ntype StreamMuxer struct {\n\tID    protocol.ID\n\tMuxer network.Multiplexer\n}\n\n// Upgrader is a multistream upgrader that can upgrade an underlying connection\n// to a full transport connection (secure and multiplexed).\ntype upgrader struct {\n\tpsk       ipnet.PSK\n\tconnGater connmgr.ConnectionGater\n\trcmgr     network.ResourceManager\n\n\tmuxerMuxer *mss.MultistreamMuxer[protocol.ID]\n\tmuxers     []StreamMuxer\n\tmuxerIDs   []protocol.ID\n\n\tsecurity      []sec.SecureTransport\n\tsecurityMuxer *mss.MultistreamMuxer[protocol.ID]\n\tsecurityIDs   []protocol.ID\n\n\t// AcceptTimeout is the maximum duration an Accept is allowed to take.\n\t// This includes the time between accepting the raw network connection,\n\t// protocol selection as well as the handshake, if applicable.\n\t//\n\t// If unset, the default value (15s) is used.\n\tacceptTimeout time.Duration\n}\n\nvar _ transport.Upgrader = &upgrader{}\n\nfunc New(security []sec.SecureTransport, muxers []StreamMuxer, psk ipnet.PSK, rcmgr network.ResourceManager, connGater connmgr.ConnectionGater, opts ...Option) (transport.Upgrader, error) {\n\tu := &upgrader{\n\t\tacceptTimeout: defaultAcceptTimeout,\n\t\trcmgr:         rcmgr,\n\t\tconnGater:     connGater,\n\t\tpsk:           psk,\n\t\tmuxerMuxer:    mss.NewMultistreamMuxer[protocol.ID](),\n\t\tmuxers:        muxers,\n\t\tsecurity:      security,\n\t\tsecurityMuxer: mss.NewMultistreamMuxer[protocol.ID](),\n\t}\n\tfor _, opt := range opts {\n\t\tif err := opt(u); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tif u.rcmgr == nil {\n\t\tu.rcmgr = &network.NullResourceManager{}\n\t}\n\tu.muxerIDs = make([]protocol.ID, 0, len(muxers))\n\tfor _, m := range muxers {\n\t\tu.muxerMuxer.AddHandler(m.ID, nil)\n\t\tu.muxerIDs = append(u.muxerIDs, m.ID)\n\t}\n\tu.securityIDs = make([]protocol.ID, 0, len(security))\n\tfor _, s := range security {\n\t\tu.securityMuxer.AddHandler(s.ID(), nil)\n\t\tu.securityIDs = append(u.securityIDs, s.ID())\n\t}\n\treturn u, nil\n}\n\n// UpgradeListener upgrades the passed multiaddr-net listener into a full libp2p-transport listener.\nfunc (u *upgrader) UpgradeListener(t transport.Transport, list manet.Listener) transport.Listener {\n\treturn u.UpgradeGatedMaListener(t, u.GateMaListener(list))\n}\n\nfunc (u *upgrader) GateMaListener(l manet.Listener) transport.GatedMaListener {\n\treturn &gatedMaListener{\n\t\tListener:  l,\n\t\trcmgr:     u.rcmgr,\n\t\tconnGater: u.connGater,\n\t}\n}\n\n// UpgradeGatedMaListener upgrades the passed multiaddr-net listener into a full libp2p-transport listener.\nfunc (u *upgrader) UpgradeGatedMaListener(t transport.Transport, l transport.GatedMaListener) transport.Listener {\n\tctx, cancel := context.WithCancel(context.Background())\n\tlist := &listener{\n\t\tGatedMaListener: l,\n\t\tupgrader:        u,\n\t\ttransport:       t,\n\t\trcmgr:           u.rcmgr,\n\t\tthreshold:       newThreshold(AcceptQueueLength),\n\t\tincoming:        make(chan transport.CapableConn),\n\t\tcancel:          cancel,\n\t\tctx:             ctx,\n\t}\n\tgo list.handleIncoming()\n\treturn list\n}\n\n// Upgrade upgrades the multiaddr/net connection into a full libp2p-transport connection.\nfunc (u *upgrader) Upgrade(ctx context.Context, t transport.Transport, maconn manet.Conn, dir network.Direction, p peer.ID, connScope network.ConnManagementScope) (transport.CapableConn, error) {\n\tc, err := u.upgrade(ctx, t, maconn, dir, p, connScope)\n\tif err != nil {\n\t\tconnScope.Done()\n\t\treturn nil, err\n\t}\n\treturn c, nil\n}\n\nfunc (u *upgrader) upgrade(ctx context.Context, t transport.Transport, maconn manet.Conn, dir network.Direction, p peer.ID, connScope network.ConnManagementScope) (transport.CapableConn, error) {\n\tif dir == network.DirOutbound && p == \"\" {\n\t\treturn nil, ErrNilPeer\n\t}\n\tvar stat network.ConnStats\n\tif cs, ok := maconn.(network.ConnStat); ok {\n\t\tstat = cs.Stat()\n\t}\n\n\tvar conn net.Conn = maconn\n\tif u.psk != nil {\n\t\tpconn, err := pnet.NewProtectedConn(u.psk, conn)\n\t\tif err != nil {\n\t\t\tconn.Close()\n\t\t\treturn nil, fmt.Errorf(\"failed to setup private network protector: %w\", err)\n\t\t}\n\t\tconn = pconn\n\t} else if ipnet.ForcePrivateNetwork {\n\t\tlog.Error(\"tried to dial with no Private Network Protector but usage of Private Networks is forced by the environment\")\n\t\treturn nil, ipnet.ErrNotInPrivateNetwork\n\t}\n\n\tisServer := dir == network.DirInbound\n\tsconn, security, err := u.setupSecurity(ctx, conn, p, isServer)\n\tif err != nil {\n\t\tconn.Close()\n\t\treturn nil, fmt.Errorf(\"failed to negotiate security protocol: %w\", err)\n\t}\n\n\t// call the connection gater, if one is registered.\n\tif u.connGater != nil && !u.connGater.InterceptSecured(dir, sconn.RemotePeer(), maconn) {\n\t\tif err := maconn.Close(); err != nil {\n\t\t\tlog.Error(\"failed to close connection\", \"peer\", p, \"remote_multiaddr\", maconn.RemoteMultiaddr(), \"err\", err)\n\t\t}\n\t\treturn nil, fmt.Errorf(\"gater rejected connection with peer %s and addr %s with direction %d\",\n\t\t\tsconn.RemotePeer(), maconn.RemoteMultiaddr(), dir)\n\t}\n\t// Only call SetPeer if it hasn't already been set -- this can happen when we don't know\n\t// the peer in advance and in some bug scenarios.\n\tif connScope.PeerScope() == nil {\n\t\tif err := connScope.SetPeer(sconn.RemotePeer()); err != nil {\n\t\t\tlog.Debug(\"resource manager blocked connection for peer\", \"peer\", sconn.RemotePeer(), \"remote_addr\", conn.RemoteAddr(), \"err\", err)\n\t\t\tif err := maconn.Close(); err != nil {\n\t\t\t\tlog.Error(\"failed to close connection\", \"peer\", p, \"remote_multiaddr\", maconn.RemoteMultiaddr(), \"err\", err)\n\t\t\t}\n\t\t\treturn nil, fmt.Errorf(\"resource manager connection with peer %s and addr %s with direction %d\",\n\t\t\t\tsconn.RemotePeer(), maconn.RemoteMultiaddr(), dir)\n\t\t}\n\t}\n\n\tmuxer, smconn, err := u.setupMuxer(ctx, sconn, isServer, connScope.PeerScope())\n\tif err != nil {\n\t\tsconn.Close()\n\t\treturn nil, fmt.Errorf(\"failed to negotiate stream multiplexer: %w\", err)\n\t}\n\n\ttc := &transportConn{\n\t\tMuxedConn:                 smconn,\n\t\tConnMultiaddrs:            maconn,\n\t\tConnSecurity:              sconn,\n\t\ttransport:                 t,\n\t\tstat:                      stat,\n\t\tscope:                     connScope,\n\t\tmuxer:                     muxer,\n\t\tsecurity:                  security,\n\t\tusedEarlyMuxerNegotiation: sconn.ConnState().UsedEarlyMuxerNegotiation,\n\t}\n\treturn tc, nil\n}\n\nfunc (u *upgrader) setupSecurity(ctx context.Context, conn net.Conn, p peer.ID, isServer bool) (sec.SecureConn, protocol.ID, error) {\n\tst, err := u.negotiateSecurity(ctx, conn, isServer)\n\tif err != nil {\n\t\treturn nil, \"\", err\n\t}\n\tif isServer {\n\t\tsconn, err := st.SecureInbound(ctx, conn, p)\n\t\treturn sconn, st.ID(), err\n\t}\n\tsconn, err := st.SecureOutbound(ctx, conn, p)\n\treturn sconn, st.ID(), err\n}\n\nfunc (u *upgrader) negotiateMuxer(nc net.Conn, isServer bool) (*StreamMuxer, error) {\n\tif err := nc.SetDeadline(time.Now().Add(defaultNegotiateTimeout)); err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar proto protocol.ID\n\tif isServer {\n\t\tselected, _, err := u.muxerMuxer.Negotiate(nc)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tproto = selected\n\t} else {\n\t\tselected, err := mss.SelectOneOf(u.muxerIDs, nc)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tproto = selected\n\t}\n\n\tif err := nc.SetDeadline(time.Time{}); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif m := u.getMuxerByID(proto); m != nil {\n\t\treturn m, nil\n\t}\n\treturn nil, fmt.Errorf(\"selected protocol we don't have a transport for\")\n}\n\nfunc (u *upgrader) getMuxerByID(id protocol.ID) *StreamMuxer {\n\tfor _, m := range u.muxers {\n\t\tif m.ID == id {\n\t\t\treturn &m\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (u *upgrader) setupMuxer(ctx context.Context, conn sec.SecureConn, server bool, scope network.PeerScope) (protocol.ID, network.MuxedConn, error) {\n\tmuxerSelected := conn.ConnState().StreamMultiplexer\n\t// Use muxer selected from security handshake if available. Otherwise fall back to multistream-selection.\n\tif len(muxerSelected) > 0 {\n\t\tm := u.getMuxerByID(muxerSelected)\n\t\tif m == nil {\n\t\t\treturn \"\", nil, fmt.Errorf(\"selected a muxer we don't know: %s\", muxerSelected)\n\t\t}\n\t\tc, err := m.Muxer.NewConn(conn, server, scope)\n\t\tif err != nil {\n\t\t\treturn \"\", nil, err\n\t\t}\n\t\treturn muxerSelected, c, nil\n\t}\n\n\ttype result struct {\n\t\tsmconn  network.MuxedConn\n\t\tmuxerID protocol.ID\n\t\terr     error\n\t}\n\n\tdone := make(chan result, 1)\n\t// TODO: The muxer should take a context.\n\tgo func() {\n\t\tm, err := u.negotiateMuxer(conn, server)\n\t\tif err != nil {\n\t\t\tdone <- result{err: err}\n\t\t\treturn\n\t\t}\n\t\tsmconn, err := m.Muxer.NewConn(conn, server, scope)\n\t\tdone <- result{smconn: smconn, muxerID: m.ID, err: err}\n\t}()\n\n\tselect {\n\tcase r := <-done:\n\t\treturn r.muxerID, r.smconn, r.err\n\tcase <-ctx.Done():\n\t\t// interrupt this process\n\t\tconn.Close()\n\t\t// wait to finish\n\t\t<-done\n\t\treturn \"\", nil, ctx.Err()\n\t}\n}\n\nfunc (u *upgrader) getSecurityByID(id protocol.ID) sec.SecureTransport {\n\tfor _, s := range u.security {\n\t\tif s.ID() == id {\n\t\t\treturn s\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (u *upgrader) negotiateSecurity(ctx context.Context, insecure net.Conn, server bool) (sec.SecureTransport, error) {\n\ttype result struct {\n\t\tproto protocol.ID\n\t\terr   error\n\t}\n\n\tdone := make(chan result, 1)\n\tgo func() {\n\t\tif server {\n\t\t\tvar r result\n\t\t\tr.proto, _, r.err = u.securityMuxer.Negotiate(insecure)\n\t\t\tdone <- r\n\t\t\treturn\n\t\t}\n\t\tvar r result\n\t\tr.proto, r.err = mss.SelectOneOf(u.securityIDs, insecure)\n\t\tdone <- r\n\t}()\n\n\tselect {\n\tcase r := <-done:\n\t\tif r.err != nil {\n\t\t\treturn nil, r.err\n\t\t}\n\t\tif s := u.getSecurityByID(r.proto); s != nil {\n\t\t\treturn s, nil\n\t\t}\n\t\treturn nil, fmt.Errorf(\"selected unknown security transport: %s\", r.proto)\n\tcase <-ctx.Done():\n\t\t// We *must* do this. We have outstanding work on the connection, and it's no longer safe to use.\n\t\tinsecure.Close()\n\t\t<-done // wait to stop using the connection.\n\t\treturn nil, ctx.Err()\n\t}\n}\n"
  },
  {
    "path": "p2p/net/upgrader/upgrader_test.go",
    "content": "package upgrader_test\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"errors\"\n\t\"net\"\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p/core/connmgr\"\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\tmocknetwork \"github.com/libp2p/go-libp2p/core/network/mocks\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/sec\"\n\t\"github.com/libp2p/go-libp2p/core/transport\"\n\t\"github.com/libp2p/go-libp2p/p2p/muxer/yamux\"\n\t\"github.com/libp2p/go-libp2p/p2p/net/upgrader\"\n\t\"github.com/libp2p/go-libp2p/p2p/security/insecure\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/mock/gomock\"\n)\n\nfunc createUpgrader(t *testing.T) (peer.ID, transport.Upgrader) {\n\treturn createUpgraderWithMuxers(t, []upgrader.StreamMuxer{{ID: \"negotiate\", Muxer: &negotiatingMuxer{}}}, nil, nil)\n}\n\nfunc createUpgraderWithConnGater(t *testing.T, connGater connmgr.ConnectionGater) (peer.ID, transport.Upgrader) {\n\treturn createUpgraderWithMuxers(t, []upgrader.StreamMuxer{{ID: \"negotiate\", Muxer: &negotiatingMuxer{}}}, nil, connGater)\n}\n\nfunc createUpgraderWithResourceManager(t *testing.T, rcmgr network.ResourceManager) (peer.ID, transport.Upgrader) {\n\treturn createUpgraderWithMuxers(t, []upgrader.StreamMuxer{{ID: \"negotiate\", Muxer: &negotiatingMuxer{}}}, rcmgr, nil)\n}\n\nfunc createUpgraderWithOpts(t *testing.T, opts ...upgrader.Option) (peer.ID, transport.Upgrader) {\n\treturn createUpgraderWithMuxers(t, []upgrader.StreamMuxer{{ID: \"negotiate\", Muxer: &negotiatingMuxer{}}}, nil, nil, opts...)\n}\n\nfunc newPeer(t *testing.T) (peer.ID, crypto.PrivKey) {\n\tt.Helper()\n\tpriv, _, err := crypto.GenerateEd25519Key(rand.Reader)\n\trequire.NoError(t, err)\n\tid, err := peer.IDFromPrivateKey(priv)\n\trequire.NoError(t, err)\n\treturn id, priv\n}\n\nfunc createUpgraderWithMuxers(t *testing.T, muxers []upgrader.StreamMuxer, rcmgr network.ResourceManager, connGater connmgr.ConnectionGater, opts ...upgrader.Option) (peer.ID, transport.Upgrader) {\n\tid, priv := newPeer(t)\n\tu, err := upgrader.New([]sec.SecureTransport{insecure.NewWithIdentity(insecure.ID, id, priv)}, muxers, nil, rcmgr, connGater, opts...)\n\trequire.NoError(t, err)\n\treturn id, u\n}\n\n// negotiatingMuxer sets up a new yamux connection\n// It makes sure that this happens at the same time for client and server.\ntype negotiatingMuxer struct{}\n\nfunc (m *negotiatingMuxer) NewConn(c net.Conn, isServer bool, scope network.PeerScope) (network.MuxedConn, error) {\n\tvar err error\n\t// run a fake muxer negotiation\n\tif isServer {\n\t\t_, err = c.Write([]byte(\"setup\"))\n\t} else {\n\t\t_, err = c.Read(make([]byte, 5))\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn yamux.DefaultTransport.NewConn(c, isServer, scope)\n}\n\n// blockingMuxer blocks the muxer negotiation until the contained chan is closed\ntype blockingMuxer struct {\n\tunblock chan struct{}\n}\n\nvar _ network.Multiplexer = &blockingMuxer{}\n\nfunc newBlockingMuxer() *blockingMuxer {\n\treturn &blockingMuxer{unblock: make(chan struct{})}\n}\n\nfunc (m *blockingMuxer) NewConn(c net.Conn, isServer bool, scope network.PeerScope) (network.MuxedConn, error) {\n\t<-m.unblock\n\treturn (&negotiatingMuxer{}).NewConn(c, isServer, scope)\n}\n\nfunc (m *blockingMuxer) Unblock() {\n\tclose(m.unblock)\n}\n\n// errorMuxer is a muxer that errors while setting up\ntype errorMuxer struct{}\n\nvar _ network.Multiplexer = &errorMuxer{}\n\nfunc (m *errorMuxer) NewConn(_ net.Conn, _ bool, _ network.PeerScope) (network.MuxedConn, error) {\n\treturn nil, errors.New(\"mux error\")\n}\n\nfunc testConn(t *testing.T, clientConn, serverConn transport.CapableConn) {\n\tt.Helper()\n\trequire := require.New(t)\n\n\tcstr, err := clientConn.OpenStream(context.Background())\n\trequire.NoError(err)\n\n\t_, err = cstr.Write([]byte(\"foobar\"))\n\trequire.NoError(err)\n\n\tsstr, err := serverConn.AcceptStream()\n\trequire.NoError(err)\n\n\tb := make([]byte, 6)\n\t_, err = sstr.Read(b)\n\trequire.NoError(err)\n\trequire.Equal([]byte(\"foobar\"), b)\n}\n\nfunc dial(t *testing.T, upgrader transport.Upgrader, raddr ma.Multiaddr, p peer.ID, scope network.ConnManagementScope) (transport.CapableConn, error) {\n\tt.Helper()\n\n\tmacon, err := manet.Dial(raddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn upgrader.Upgrade(context.Background(), nil, macon, network.DirOutbound, p, scope)\n}\n\nfunc TestOutboundConnectionGating(t *testing.T) {\n\trequire := require.New(t)\n\n\tid, u := createUpgrader(t)\n\tln := createListener(t, u)\n\tdefer ln.Close()\n\n\ttestGater := &testGater{}\n\t_, dialUpgrader := createUpgraderWithConnGater(t, testGater)\n\tconn, err := dial(t, dialUpgrader, ln.Multiaddr(), id, &network.NullScope{})\n\trequire.NoError(err)\n\trequire.NotNil(conn)\n\t_ = conn.Close()\n\n\t// blocking accepts doesn't affect the dialling side, only the listener.\n\ttestGater.BlockAccept(true)\n\tconn, err = dial(t, dialUpgrader, ln.Multiaddr(), id, &network.NullScope{})\n\trequire.NoError(err)\n\trequire.NotNil(conn)\n\t_ = conn.Close()\n\n\t// now let's block all connections after being secured.\n\ttestGater.BlockSecured(true)\n\tconn, err = dial(t, dialUpgrader, ln.Multiaddr(), id, &network.NullScope{})\n\trequire.Error(err)\n\trequire.Contains(err.Error(), \"gater rejected connection\")\n\trequire.Nil(conn)\n}\n\nfunc TestOutboundResourceManagement(t *testing.T) {\n\tt.Run(\"successful handshake\", func(t *testing.T) {\n\t\tid, upgrader := createUpgrader(t)\n\t\tln := createListener(t, upgrader)\n\t\tdefer ln.Close()\n\n\t\tctrl := gomock.NewController(t)\n\t\tdefer ctrl.Finish()\n\t\tconnScope := mocknetwork.NewMockConnManagementScope(ctrl)\n\t\tgomock.InOrder(\n\t\t\tconnScope.EXPECT().PeerScope(),\n\t\t\tconnScope.EXPECT().SetPeer(id),\n\t\t\tconnScope.EXPECT().PeerScope().Return(&network.NullScope{}),\n\t\t)\n\t\t_, dialUpgrader := createUpgrader(t)\n\t\tconn, err := dial(t, dialUpgrader, ln.Multiaddr(), id, connScope)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, conn)\n\t\tconnScope.EXPECT().Done()\n\t\trequire.NoError(t, conn.Close())\n\t})\n\n\tt.Run(\"failed negotiation\", func(t *testing.T) {\n\t\tid, upgrader := createUpgraderWithMuxers(t, []upgrader.StreamMuxer{{ID: \"errorMuxer\", Muxer: &errorMuxer{}}}, nil, nil)\n\t\tln := createListener(t, upgrader)\n\t\tdefer ln.Close()\n\n\t\tctrl := gomock.NewController(t)\n\t\tdefer ctrl.Finish()\n\t\tconnScope := mocknetwork.NewMockConnManagementScope(ctrl)\n\t\tgomock.InOrder(\n\t\t\tconnScope.EXPECT().PeerScope(),\n\t\t\tconnScope.EXPECT().SetPeer(id),\n\t\t\tconnScope.EXPECT().PeerScope().Return(&network.NullScope{}),\n\t\t\tconnScope.EXPECT().Done(),\n\t\t)\n\t\t_, dialUpgrader := createUpgrader(t)\n\t\t_, err := dial(t, dialUpgrader, ln.Multiaddr(), id, connScope)\n\t\trequire.Error(t, err)\n\t})\n}\n"
  },
  {
    "path": "p2p/protocol/autonatv2/autonat.go",
    "content": "package autonatv2\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"iter\"\n\t\"math/rand/v2\"\n\t\"slices\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/event\"\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\nconst (\n\tServiceName      = \"libp2p.autonatv2\"\n\tDialBackProtocol = \"/libp2p/autonat/2/dial-back\"\n\tDialProtocol     = \"/libp2p/autonat/2/dial-request\"\n\n\tmaxMsgSize            = 8192\n\tstreamTimeout         = 15 * time.Second\n\tdialBackStreamTimeout = 5 * time.Second\n\tdialBackDialTimeout   = 10 * time.Second\n\tdialBackMaxMsgSize    = 1024\n\tminHandshakeSizeBytes = 30_000 // for amplification attack prevention\n\tmaxHandshakeSizeBytes = 100_000\n\t// maxPeerAddresses is the number of addresses in a dial request the server\n\t// will inspect, rest are ignored.\n\tmaxPeerAddresses = 50\n\n\tdefaultThrottlePeerDuration = 2 * time.Minute\n)\n\nvar (\n\t// ErrNoPeers is returned when the client knows no autonatv2 servers.\n\tErrNoPeers = errors.New(\"no peers for autonat v2\")\n\t// ErrPrivateAddrs is returned when the request has private IP addresses.\n\tErrPrivateAddrs = errors.New(\"private addresses cannot be verified with autonatv2\")\n\n\tlog = logging.Logger(\"autonatv2\")\n)\n\n// Request is the request to verify reachability of a single address\ntype Request struct {\n\t// Addr is the multiaddr to verify\n\tAddr ma.Multiaddr\n\t// SendDialData indicates whether to send dial data if the server requests it for Addr\n\tSendDialData bool\n}\n\n// Result is the result of the CheckReachability call\ntype Result struct {\n\t// Addr is the dialed address\n\tAddr ma.Multiaddr\n\t// Idx is the index of the address that was dialed\n\tIdx int\n\t// Reachability is the reachability for `Addr`\n\tReachability network.Reachability\n\t// AllAddrsRefused is true when the server refused to dial all the addresses in the request.\n\tAllAddrsRefused bool\n}\n\n// AutoNAT implements the AutoNAT v2 client and server.\n// Users can check reachability for their addresses using the CheckReachability method.\n// The server provides amplification attack prevention and rate limiting.\ntype AutoNAT struct {\n\thost host.Host\n\n\t// for cleanly closing\n\tctx    context.Context\n\tcancel context.CancelFunc\n\twg     sync.WaitGroup\n\n\tsrv *server\n\tcli *client\n\n\tmx           sync.Mutex\n\tpeers        *peersMap\n\tthrottlePeer map[peer.ID]time.Time\n\t// throttlePeerDuration is the duration to wait before making another dial request to the\n\t// same server.\n\tthrottlePeerDuration time.Duration\n\t// allowPrivateAddrs enables using private and localhost addresses for reachability checks.\n\t// This is only useful for testing.\n\tallowPrivateAddrs bool\n}\n\n// New returns a new AutoNAT instance.\n// host and dialerHost should have the same dialing capabilities. In case the host doesn't support\n// a transport, dial back requests for address for that transport will be ignored.\nfunc New(dialerHost host.Host, opts ...AutoNATOption) (*AutoNAT, error) {\n\ts := defaultSettings()\n\tfor _, o := range opts {\n\t\tif err := o(s); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to apply option: %w\", err)\n\t\t}\n\t}\n\n\tctx, cancel := context.WithCancel(context.Background())\n\tan := &AutoNAT{\n\t\tctx:                  ctx,\n\t\tcancel:               cancel,\n\t\tsrv:                  newServer(dialerHost, s),\n\t\tcli:                  newClient(s),\n\t\tallowPrivateAddrs:    s.allowPrivateAddrs,\n\t\tpeers:                newPeersMap(),\n\t\tthrottlePeer:         make(map[peer.ID]time.Time),\n\t\tthrottlePeerDuration: s.throttlePeerDuration,\n\t}\n\treturn an, nil\n}\n\nfunc (an *AutoNAT) background(sub event.Subscription) {\n\tticker := time.NewTicker(10 * time.Minute)\n\tfor {\n\t\tselect {\n\t\tcase <-an.ctx.Done():\n\t\t\tsub.Close()\n\t\t\tan.wg.Done()\n\t\t\treturn\n\t\tcase e := <-sub.Out():\n\t\t\tswitch evt := e.(type) {\n\t\t\tcase event.EvtPeerProtocolsUpdated:\n\t\t\t\tan.updatePeer(evt.Peer)\n\t\t\tcase event.EvtPeerConnectednessChanged:\n\t\t\t\tan.updatePeer(evt.Peer)\n\t\t\tcase event.EvtPeerIdentificationCompleted:\n\t\t\t\tan.updatePeer(evt.Peer)\n\t\t\tdefault:\n\t\t\t\tlog.Error(\"unexpected event\", \"event_type\", fmt.Sprintf(\"%T\", e))\n\t\t\t}\n\t\tcase <-ticker.C:\n\t\t\tnow := time.Now()\n\t\t\tan.mx.Lock()\n\t\t\tfor p, t := range an.throttlePeer {\n\t\t\t\tif t.Before(now) {\n\t\t\t\t\tdelete(an.throttlePeer, p)\n\t\t\t\t}\n\t\t\t}\n\t\t\tan.mx.Unlock()\n\t\t}\n\t}\n}\n\nfunc (an *AutoNAT) Start(h host.Host) error {\n\tan.host = h\n\t// Listen on event.EvtPeerProtocolsUpdated, event.EvtPeerConnectednessChanged\n\t// event.EvtPeerIdentificationCompleted to maintain our set of autonat supporting peers.\n\tsub, err := an.host.EventBus().Subscribe([]any{\n\t\tnew(event.EvtPeerProtocolsUpdated),\n\t\tnew(event.EvtPeerConnectednessChanged),\n\t\tnew(event.EvtPeerIdentificationCompleted),\n\t})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"event subscription failed: %w\", err)\n\t}\n\tan.cli.Start(h)\n\tan.srv.Start(h)\n\n\tan.wg.Add(1)\n\tgo an.background(sub)\n\treturn nil\n}\n\nfunc (an *AutoNAT) Close() {\n\tan.cancel()\n\tan.wg.Wait()\n\tan.srv.Close()\n\tan.cli.Close()\n\tan.peers = nil\n}\n\n// GetReachability makes a single dial request for checking reachability for requested addresses\nfunc (an *AutoNAT) GetReachability(ctx context.Context, reqs []Request) (Result, error) {\n\tvar filteredReqs []Request\n\tif !an.allowPrivateAddrs {\n\t\tfilteredReqs = make([]Request, 0, len(reqs))\n\t\tfor _, r := range reqs {\n\t\t\tif manet.IsPublicAddr(r.Addr) {\n\t\t\t\tfilteredReqs = append(filteredReqs, r)\n\t\t\t} else {\n\t\t\t\tlog.Error(\"private address in reachability check\", \"address\", r.Addr)\n\t\t\t}\n\t\t}\n\t\tif len(filteredReqs) == 0 {\n\t\t\treturn Result{}, ErrPrivateAddrs\n\t\t}\n\t} else {\n\t\tfilteredReqs = reqs\n\t}\n\tan.mx.Lock()\n\tnow := time.Now()\n\tvar p peer.ID\n\tfor pr := range an.peers.Shuffled() {\n\t\tif t := an.throttlePeer[pr]; t.After(now) {\n\t\t\tcontinue\n\t\t}\n\t\tp = pr\n\t\tan.throttlePeer[p] = time.Now().Add(an.throttlePeerDuration)\n\t\tbreak\n\t}\n\tan.mx.Unlock()\n\tif p == \"\" {\n\t\treturn Result{}, ErrNoPeers\n\t}\n\tres, err := an.cli.GetReachability(ctx, p, filteredReqs)\n\tif err != nil {\n\t\tlog.Debug(\"reachability check failed\", \"peer\", p, \"err\", err)\n\t\treturn res, fmt.Errorf(\"reachability check with %s failed: %w\", p, err)\n\t}\n\t// restore the correct index in case we'd filtered private addresses\n\tfor i, r := range reqs {\n\t\tif r.Addr.Equal(res.Addr) {\n\t\t\tres.Idx = i\n\t\t\tbreak\n\t\t}\n\t}\n\tlog.Debug(\"reachability check successful\", \"peer\", p)\n\treturn res, nil\n}\n\nfunc (an *AutoNAT) updatePeer(p peer.ID) {\n\tan.mx.Lock()\n\tdefer an.mx.Unlock()\n\n\t// There are no ordering gurantees between identify and swarm events. Check peerstore\n\t// and swarm for the current state\n\tprotos, err := an.host.Peerstore().SupportsProtocols(p, DialProtocol)\n\tconnectedness := an.host.Network().Connectedness(p)\n\tif err == nil && connectedness == network.Connected && slices.Contains(protos, DialProtocol) {\n\t\tan.peers.Put(p)\n\t} else {\n\t\tan.peers.Delete(p)\n\t}\n}\n\n// peersMap provides random access to a set of peers. This is useful when the map iteration order is\n// not sufficiently random.\ntype peersMap struct {\n\tpeerIdx map[peer.ID]int\n\tpeers   []peer.ID\n}\n\nfunc newPeersMap() *peersMap {\n\treturn &peersMap{\n\t\tpeerIdx: make(map[peer.ID]int),\n\t\tpeers:   make([]peer.ID, 0),\n\t}\n}\n\n// Shuffled iterates over the map in random order\nfunc (p *peersMap) Shuffled() iter.Seq[peer.ID] {\n\tn := len(p.peers)\n\tstart := 0\n\tif n > 0 {\n\t\tstart = rand.IntN(n)\n\t}\n\treturn func(yield func(peer.ID) bool) {\n\t\tfor i := range n {\n\t\t\tif !yield(p.peers[(i+start)%n]) {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (p *peersMap) Put(id peer.ID) {\n\tif _, ok := p.peerIdx[id]; ok {\n\t\treturn\n\t}\n\tp.peers = append(p.peers, id)\n\tp.peerIdx[id] = len(p.peers) - 1\n}\n\nfunc (p *peersMap) Delete(id peer.ID) {\n\tidx, ok := p.peerIdx[id]\n\tif !ok {\n\t\treturn\n\t}\n\tn := len(p.peers)\n\tlastPeer := p.peers[n-1]\n\tp.peers[idx] = lastPeer\n\tp.peerIdx[lastPeer] = idx\n\tp.peers[n-1] = \"\"\n\tp.peers = p.peers[:n-1]\n\tdelete(p.peerIdx, id)\n}\n"
  },
  {
    "path": "p2p/protocol/autonatv2/autonat_test.go",
    "content": "package autonatv2\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"net\"\n\t\"net/netip\"\n\t\"strings\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\tbhost \"github.com/libp2p/go-libp2p/p2p/host/blank\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/eventbus\"\n\t\"github.com/libp2p/go-libp2p/p2p/net/swarm\"\n\tswarmt \"github.com/libp2p/go-libp2p/p2p/net/swarm/testing\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/autonatv2/pb\"\n\n\t\"github.com/libp2p/go-msgio/pbio\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc newAutoNAT(t testing.TB, dialer host.Host, opts ...AutoNATOption) *AutoNAT {\n\tt.Helper()\n\tb := eventbus.NewBus()\n\th := bhost.NewBlankHost(\n\t\tswarmt.GenSwarm(t, swarmt.EventBus(b), swarmt.OptDisableWebTransport, swarmt.OptDisableWebRTC), bhost.WithEventBus(b))\n\tif dialer == nil {\n\t\tdialer = bhost.NewBlankHost(\n\t\t\tswarmt.GenSwarm(t,\n\t\t\t\tswarmt.WithSwarmOpts(\n\t\t\t\t\tswarm.WithUDPBlackHoleSuccessCounter(nil),\n\t\t\t\t\tswarm.WithIPv6BlackHoleSuccessCounter(nil))))\n\t}\n\topts = append([]AutoNATOption{withThrottlePeerDuration(0)}, opts...)\n\tan, err := New(dialer, opts...)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\trequire.NoError(t, an.Start(h))\n\tt.Cleanup(an.Close)\n\treturn an\n}\n\nfunc parseAddrs(t *testing.T, msg *pb.Message) []ma.Multiaddr {\n\tt.Helper()\n\treq := msg.GetDialRequest()\n\taddrs := make([]ma.Multiaddr, 0)\n\tfor _, ab := range req.Addrs {\n\t\ta, err := ma.NewMultiaddrBytes(ab)\n\t\tif err != nil {\n\t\t\tt.Error(\"invalid addr bytes\", ab)\n\t\t}\n\t\taddrs = append(addrs, a)\n\t}\n\treturn addrs\n}\n\n// idAndConnect identifies b to a and connects them\nfunc idAndConnect(t testing.TB, a, b host.Host) {\n\ta.Peerstore().AddAddrs(b.ID(), b.Addrs(), peerstore.PermanentAddrTTL)\n\ta.Peerstore().AddProtocols(b.ID(), DialProtocol)\n\n\terr := a.Connect(context.Background(), peer.AddrInfo{ID: b.ID()})\n\trequire.NoError(t, err)\n}\n\n// waitForPeer waits for a to have 1 peer in the peerMap\nfunc waitForPeer(t testing.TB, a *AutoNAT) {\n\tt.Helper()\n\trequire.Eventually(t, func() bool {\n\t\ta.mx.Lock()\n\t\tdefer a.mx.Unlock()\n\t\treturn len(a.peers.peers) != 0\n\t}, 5*time.Second, 100*time.Millisecond)\n}\n\n// idAndWait provides server address and protocol to client\nfunc idAndWait(t testing.TB, cli *AutoNAT, srv *AutoNAT) {\n\tidAndConnect(t, cli.host, srv.host)\n\twaitForPeer(t, cli)\n}\n\nfunc TestAutoNATPrivateAddr(t *testing.T) {\n\tan := newAutoNAT(t, nil)\n\tres, err := an.GetReachability(context.Background(), []Request{{Addr: ma.StringCast(\"/ip4/192.168.0.1/udp/10/quic-v1\")}})\n\trequire.Equal(t, res, Result{})\n\trequire.ErrorIs(t, err, ErrPrivateAddrs)\n}\n\nfunc TestClientRequest(t *testing.T) {\n\tan := newAutoNAT(t, nil, allowPrivateAddrs)\n\tdefer an.Close()\n\tdefer an.host.Close()\n\n\tb := bhost.NewBlankHost(swarmt.GenSwarm(t))\n\tdefer b.Close()\n\tidAndConnect(t, an.host, b)\n\twaitForPeer(t, an)\n\n\taddrs := an.host.Addrs()\n\taddrbs := make([][]byte, len(addrs))\n\tfor i := range addrs {\n\t\taddrbs[i] = addrs[i].Bytes()\n\t}\n\n\tvar receivedRequest atomic.Bool\n\tb.SetStreamHandler(DialProtocol, func(s network.Stream) {\n\t\treceivedRequest.Store(true)\n\t\tr := pbio.NewDelimitedReader(s, maxMsgSize)\n\t\tvar msg pb.Message\n\t\tassert.NoError(t, r.ReadMsg(&msg))\n\t\tassert.NotNil(t, msg.GetDialRequest())\n\t\tassert.Equal(t, addrbs, msg.GetDialRequest().Addrs)\n\t\ts.Reset()\n\t})\n\n\tres, err := an.GetReachability(context.Background(), []Request{\n\t\t{Addr: addrs[0], SendDialData: true}, {Addr: addrs[1]},\n\t})\n\trequire.Equal(t, res, Result{})\n\trequire.NotNil(t, err)\n\trequire.True(t, receivedRequest.Load())\n}\n\nfunc TestClientServerError(t *testing.T) {\n\tan := newAutoNAT(t, nil, allowPrivateAddrs)\n\tdefer an.Close()\n\tdefer an.host.Close()\n\n\tb := bhost.NewBlankHost(swarmt.GenSwarm(t))\n\tdefer b.Close()\n\tidAndConnect(t, an.host, b)\n\twaitForPeer(t, an)\n\n\ttests := []struct {\n\t\thandler  func(network.Stream)\n\t\terrorStr string\n\t}{\n\t\t{\n\t\t\thandler: func(s network.Stream) {\n\t\t\t\ts.Reset()\n\t\t\t},\n\t\t\terrorStr: \"stream reset\",\n\t\t},\n\t\t{\n\t\t\thandler: func(s network.Stream) {\n\t\t\t\tw := pbio.NewDelimitedWriter(s)\n\t\t\t\tassert.NoError(t, w.WriteMsg(\n\t\t\t\t\t&pb.Message{Msg: &pb.Message_DialRequest{DialRequest: &pb.DialRequest{}}}))\n\t\t\t},\n\t\t\terrorStr: \"invalid msg type\",\n\t\t},\n\t}\n\n\tfor i, tc := range tests {\n\t\tt.Run(fmt.Sprintf(\"test-%d\", i), func(t *testing.T) {\n\t\t\tb.SetStreamHandler(DialProtocol, tc.handler)\n\t\t\taddrs := an.host.Addrs()\n\t\t\tres, err := an.GetReachability(\n\t\t\t\tcontext.Background(),\n\t\t\t\tnewTestRequests(addrs, false))\n\t\t\trequire.Equal(t, res, Result{})\n\t\t\trequire.NotNil(t, err)\n\t\t\trequire.Contains(t, err.Error(), tc.errorStr)\n\t\t})\n\t}\n}\n\nfunc TestClientDataRequest(t *testing.T) {\n\tan := newAutoNAT(t, nil, allowPrivateAddrs)\n\tdefer an.Close()\n\tdefer an.host.Close()\n\n\tb := bhost.NewBlankHost(swarmt.GenSwarm(t))\n\tdefer b.Close()\n\tidAndConnect(t, an.host, b)\n\twaitForPeer(t, an)\n\n\ttests := []struct {\n\t\thandler func(network.Stream)\n\t\tname    string\n\t}{\n\t\t{\n\t\t\tname: \"provides dial data\",\n\t\t\thandler: func(s network.Stream) {\n\t\t\t\tr := pbio.NewDelimitedReader(s, maxMsgSize)\n\t\t\t\tvar msg pb.Message\n\t\t\t\tassert.NoError(t, r.ReadMsg(&msg))\n\t\t\t\tw := pbio.NewDelimitedWriter(s)\n\t\t\t\tif err := w.WriteMsg(&pb.Message{\n\t\t\t\t\tMsg: &pb.Message_DialDataRequest{\n\t\t\t\t\t\tDialDataRequest: &pb.DialDataRequest{\n\t\t\t\t\t\t\tAddrIdx:  0,\n\t\t\t\t\t\t\tNumBytes: 10000,\n\t\t\t\t\t\t},\n\t\t\t\t\t}},\n\t\t\t\t); err != nil {\n\t\t\t\t\tt.Error(err)\n\t\t\t\t\ts.Reset()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tvar dialData []byte\n\t\t\t\tfor len(dialData) < 10000 {\n\t\t\t\t\tif err := r.ReadMsg(&msg); err != nil {\n\t\t\t\t\t\tt.Error(err)\n\t\t\t\t\t\ts.Reset()\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tif msg.GetDialDataResponse() == nil {\n\t\t\t\t\t\tt.Errorf(\"expected to receive msg of type DialDataResponse\")\n\t\t\t\t\t\ts.Reset()\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tdialData = append(dialData, msg.GetDialDataResponse().Data...)\n\t\t\t\t}\n\t\t\t\ts.Reset()\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"low priority addr\",\n\t\t\thandler: func(s network.Stream) {\n\t\t\t\tr := pbio.NewDelimitedReader(s, maxMsgSize)\n\t\t\t\tvar msg pb.Message\n\t\t\t\tassert.NoError(t, r.ReadMsg(&msg))\n\t\t\t\tw := pbio.NewDelimitedWriter(s)\n\t\t\t\tif err := w.WriteMsg(&pb.Message{\n\t\t\t\t\tMsg: &pb.Message_DialDataRequest{\n\t\t\t\t\t\tDialDataRequest: &pb.DialDataRequest{\n\t\t\t\t\t\t\tAddrIdx:  1,\n\t\t\t\t\t\t\tNumBytes: 10000,\n\t\t\t\t\t\t},\n\t\t\t\t\t}},\n\t\t\t\t); err != nil {\n\t\t\t\t\tt.Error(err)\n\t\t\t\t\ts.Reset()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tassert.Error(t, r.ReadMsg(&msg))\n\t\t\t\ts.Reset()\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"too high dial data request\",\n\t\t\thandler: func(s network.Stream) {\n\t\t\t\tr := pbio.NewDelimitedReader(s, maxMsgSize)\n\t\t\t\tvar msg pb.Message\n\t\t\t\tassert.NoError(t, r.ReadMsg(&msg))\n\t\t\t\tw := pbio.NewDelimitedWriter(s)\n\t\t\t\tif err := w.WriteMsg(&pb.Message{\n\t\t\t\t\tMsg: &pb.Message_DialDataRequest{\n\t\t\t\t\t\tDialDataRequest: &pb.DialDataRequest{\n\t\t\t\t\t\t\tAddrIdx:  0,\n\t\t\t\t\t\t\tNumBytes: 1 << 32,\n\t\t\t\t\t\t},\n\t\t\t\t\t}},\n\t\t\t\t); err != nil {\n\t\t\t\t\tt.Error(err)\n\t\t\t\t\ts.Reset()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tassert.Error(t, r.ReadMsg(&msg))\n\t\t\t\ts.Reset()\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tb.SetStreamHandler(DialProtocol, tc.handler)\n\t\t\taddrs := an.host.Addrs()\n\n\t\t\tres, err := an.GetReachability(\n\t\t\t\tcontext.Background(),\n\t\t\t\t[]Request{\n\t\t\t\t\t{Addr: addrs[0], SendDialData: true},\n\t\t\t\t\t{Addr: addrs[1]},\n\t\t\t\t})\n\t\t\trequire.Equal(t, res, Result{})\n\t\t\trequire.NotNil(t, err)\n\t\t})\n\t}\n}\n\nfunc TestAutoNATPrivateAndPublicAddrs(t *testing.T) {\n\tan := newAutoNAT(t, nil)\n\tdefer an.Close()\n\tdefer an.host.Close()\n\n\tb := bhost.NewBlankHost(swarmt.GenSwarm(t))\n\tdefer b.Close()\n\tidAndConnect(t, an.host, b)\n\twaitForPeer(t, an)\n\n\tdialerHost := bhost.NewBlankHost(swarmt.GenSwarm(t))\n\tdefer dialerHost.Close()\n\thandler := func(s network.Stream) {\n\t\tw := pbio.NewDelimitedWriter(s)\n\t\tr := pbio.NewDelimitedReader(s, maxMsgSize)\n\t\tvar msg pb.Message\n\t\tassert.NoError(t, r.ReadMsg(&msg))\n\t\tw.WriteMsg(&pb.Message{\n\t\t\tMsg: &pb.Message_DialResponse{\n\t\t\t\tDialResponse: &pb.DialResponse{\n\t\t\t\t\tStatus:     pb.DialResponse_OK,\n\t\t\t\t\tDialStatus: pb.DialStatus_E_DIAL_ERROR,\n\t\t\t\t\tAddrIdx:    0,\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\ts.Close()\n\t}\n\n\tb.SetStreamHandler(DialProtocol, handler)\n\tprivateAddr := ma.StringCast(\"/ip4/192.168.0.1/udp/10/quic-v1\")\n\tpublicAddr := ma.StringCast(\"/ip4/1.2.3.4/udp/10/quic-v1\")\n\tres, err := an.GetReachability(context.Background(),\n\t\t[]Request{\n\t\t\t{Addr: privateAddr},\n\t\t\t{Addr: publicAddr},\n\t\t})\n\trequire.NoError(t, err)\n\trequire.Equal(t, res.Addr, publicAddr, \"%s\\n%s\", res.Addr, publicAddr)\n\trequire.Equal(t, res.Idx, 1)\n\trequire.Equal(t, res.Reachability, network.ReachabilityPrivate)\n}\n\nfunc TestClientDialBacks(t *testing.T) {\n\tan := newAutoNAT(t, nil, allowPrivateAddrs)\n\tdefer an.Close()\n\tdefer an.host.Close()\n\n\tb := bhost.NewBlankHost(swarmt.GenSwarm(t))\n\tdefer b.Close()\n\tidAndConnect(t, an.host, b)\n\twaitForPeer(t, an)\n\n\tdialerHost := bhost.NewBlankHost(swarmt.GenSwarm(t))\n\tdefer dialerHost.Close()\n\n\treadReq := func(r pbio.Reader) ([]ma.Multiaddr, uint64, error) {\n\t\tvar msg pb.Message\n\t\tif err := r.ReadMsg(&msg); err != nil {\n\t\t\treturn nil, 0, err\n\t\t}\n\t\tif msg.GetDialRequest() == nil {\n\t\t\treturn nil, 0, errors.New(\"no dial request in msg\")\n\t\t}\n\t\taddrs := parseAddrs(t, &msg)\n\t\treturn addrs, msg.GetDialRequest().GetNonce(), nil\n\t}\n\n\twriteNonce := func(addr ma.Multiaddr, nonce uint64) error {\n\t\tpid := an.host.ID()\n\t\tdialerHost.Peerstore().AddAddr(pid, addr, peerstore.PermanentAddrTTL)\n\t\tdefer func() {\n\t\t\tdialerHost.Network().ClosePeer(pid)\n\t\t\tdialerHost.Peerstore().RemovePeer(pid)\n\t\t\tdialerHost.Peerstore().ClearAddrs(pid)\n\t\t}()\n\t\tas, err := dialerHost.NewStream(context.Background(), pid, DialBackProtocol)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tw := pbio.NewDelimitedWriter(as)\n\t\tif err := w.WriteMsg(&pb.DialBack{Nonce: nonce}); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tas.CloseWrite()\n\t\tdata := make([]byte, 1)\n\t\tas.Read(data)\n\t\tas.Close()\n\t\treturn nil\n\t}\n\n\ttests := []struct {\n\t\tname    string\n\t\thandler func(network.Stream)\n\t\tsuccess bool\n\t}{\n\t\t{\n\t\t\tname: \"correct dial attempt\",\n\t\t\thandler: func(s network.Stream) {\n\t\t\t\tr := pbio.NewDelimitedReader(s, maxMsgSize)\n\t\t\t\tw := pbio.NewDelimitedWriter(s)\n\n\t\t\t\taddrs, nonce, err := readReq(r)\n\t\t\t\tif err != nil {\n\t\t\t\t\ts.Reset()\n\t\t\t\t\tt.Error(err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif err := writeNonce(addrs[1], nonce); err != nil {\n\t\t\t\t\ts.Reset()\n\t\t\t\t\tt.Error(err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tw.WriteMsg(&pb.Message{\n\t\t\t\t\tMsg: &pb.Message_DialResponse{\n\t\t\t\t\t\tDialResponse: &pb.DialResponse{\n\t\t\t\t\t\t\tStatus:     pb.DialResponse_OK,\n\t\t\t\t\t\t\tDialStatus: pb.DialStatus_OK,\n\t\t\t\t\t\t\tAddrIdx:    1,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\ts.Close()\n\t\t\t},\n\t\t\tsuccess: true,\n\t\t},\n\t\t{\n\t\t\tname: \"no dial attempt\",\n\t\t\thandler: func(s network.Stream) {\n\t\t\t\tr := pbio.NewDelimitedReader(s, maxMsgSize)\n\t\t\t\tif _, _, err := readReq(r); err != nil {\n\t\t\t\t\ts.Reset()\n\t\t\t\t\tt.Error(err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tresp := &pb.DialResponse{\n\t\t\t\t\tStatus:     pb.DialResponse_OK,\n\t\t\t\t\tDialStatus: pb.DialStatus_OK,\n\t\t\t\t\tAddrIdx:    0,\n\t\t\t\t}\n\t\t\t\tw := pbio.NewDelimitedWriter(s)\n\t\t\t\tw.WriteMsg(&pb.Message{\n\t\t\t\t\tMsg: &pb.Message_DialResponse{\n\t\t\t\t\t\tDialResponse: resp,\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\ts.Close()\n\t\t\t},\n\t\t\tsuccess: false,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid reported address\",\n\t\t\thandler: func(s network.Stream) {\n\t\t\t\tr := pbio.NewDelimitedReader(s, maxMsgSize)\n\t\t\t\taddrs, nonce, err := readReq(r)\n\t\t\t\tif err != nil {\n\t\t\t\t\ts.Reset()\n\t\t\t\t\tt.Error(err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif err := writeNonce(addrs[1], nonce); err != nil {\n\t\t\t\t\ts.Reset()\n\t\t\t\t\tt.Error(err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tw := pbio.NewDelimitedWriter(s)\n\t\t\t\tw.WriteMsg(&pb.Message{\n\t\t\t\t\tMsg: &pb.Message_DialResponse{\n\t\t\t\t\t\tDialResponse: &pb.DialResponse{\n\t\t\t\t\t\t\tStatus:     pb.DialResponse_OK,\n\t\t\t\t\t\t\tDialStatus: pb.DialStatus_OK,\n\t\t\t\t\t\t\tAddrIdx:    0,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\ts.Close()\n\t\t\t},\n\t\t\tsuccess: false,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid nonce\",\n\t\t\thandler: func(s network.Stream) {\n\t\t\t\tr := pbio.NewDelimitedReader(s, maxMsgSize)\n\t\t\t\taddrs, nonce, err := readReq(r)\n\t\t\t\tif err != nil {\n\t\t\t\t\ts.Reset()\n\t\t\t\t\tt.Error(err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif err := writeNonce(addrs[0], nonce-1); err != nil {\n\t\t\t\t\ts.Reset()\n\t\t\t\t\tt.Error(err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tw := pbio.NewDelimitedWriter(s)\n\t\t\t\tw.WriteMsg(&pb.Message{\n\t\t\t\t\tMsg: &pb.Message_DialResponse{\n\t\t\t\t\t\tDialResponse: &pb.DialResponse{\n\t\t\t\t\t\t\tStatus:     pb.DialResponse_OK,\n\t\t\t\t\t\t\tDialStatus: pb.DialStatus_OK,\n\t\t\t\t\t\t\tAddrIdx:    0,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\ts.Close()\n\t\t\t},\n\t\t\tsuccess: false,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid addr index\",\n\t\t\thandler: func(s network.Stream) {\n\t\t\t\tr := pbio.NewDelimitedReader(s, maxMsgSize)\n\t\t\t\t_, _, err := readReq(r)\n\t\t\t\tif err != nil {\n\t\t\t\t\ts.Reset()\n\t\t\t\t\tt.Error(err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tw := pbio.NewDelimitedWriter(s)\n\t\t\t\tw.WriteMsg(&pb.Message{\n\t\t\t\t\tMsg: &pb.Message_DialResponse{\n\t\t\t\t\t\tDialResponse: &pb.DialResponse{\n\t\t\t\t\t\t\tStatus:     pb.DialResponse_OK,\n\t\t\t\t\t\t\tDialStatus: pb.DialStatus_OK,\n\t\t\t\t\t\t\tAddrIdx:    10,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\ts.Close()\n\t\t\t},\n\t\t\tsuccess: false,\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\taddrs := an.host.Addrs()\n\t\t\tb.SetStreamHandler(DialProtocol, tc.handler)\n\t\t\tres, err := an.GetReachability(\n\t\t\t\tcontext.Background(),\n\t\t\t\t[]Request{\n\t\t\t\t\t{Addr: addrs[0], SendDialData: true},\n\t\t\t\t\t{Addr: addrs[1]},\n\t\t\t\t})\n\t\t\tif !tc.success {\n\t\t\t\trequire.Error(t, err)\n\t\t\t\trequire.Equal(t, Result{}, res)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, res.Reachability, network.ReachabilityPublic)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestEventSubscription(t *testing.T) {\n\tan := newAutoNAT(t, nil)\n\tdefer an.host.Close()\n\n\tb := bhost.NewBlankHost(swarmt.GenSwarm(t))\n\tdefer b.Close()\n\tc := bhost.NewBlankHost(swarmt.GenSwarm(t))\n\tdefer c.Close()\n\n\tidAndConnect(t, an.host, b)\n\trequire.Eventually(t, func() bool {\n\t\tan.mx.Lock()\n\t\tdefer an.mx.Unlock()\n\t\treturn len(an.peers.peers) == 1\n\t}, 5*time.Second, 100*time.Millisecond)\n\n\tidAndConnect(t, an.host, c)\n\trequire.Eventually(t, func() bool {\n\t\tan.mx.Lock()\n\t\tdefer an.mx.Unlock()\n\t\treturn len(an.peers.peers) == 2\n\t}, 5*time.Second, 100*time.Millisecond)\n\n\tan.host.Network().ClosePeer(b.ID())\n\trequire.Eventually(t, func() bool {\n\t\tan.mx.Lock()\n\t\tdefer an.mx.Unlock()\n\t\treturn len(an.peers.peers) == 1\n\t}, 5*time.Second, 100*time.Millisecond)\n\n\tan.host.Network().ClosePeer(c.ID())\n\trequire.Eventually(t, func() bool {\n\t\tan.mx.Lock()\n\t\tdefer an.mx.Unlock()\n\t\treturn len(an.peers.peers) == 0\n\t}, 5*time.Second, 100*time.Millisecond)\n}\n\nfunc TestAreAddrsConsistency(t *testing.T) {\n\tc := &client{}\n\ttests := []struct {\n\t\tname      string\n\t\tlocalAddr ma.Multiaddr\n\t\tdialAddr  ma.Multiaddr\n\t\tsuccess   bool\n\t}{\n\t\t{\n\t\t\tname:      \"simple match\",\n\t\t\tlocalAddr: ma.StringCast(\"/ip4/192.168.0.1/tcp/12345\"),\n\t\t\tdialAddr:  ma.StringCast(\"/ip4/1.2.3.4/tcp/23232\"),\n\t\t\tsuccess:   true,\n\t\t},\n\t\t{\n\t\t\tname:      \"nat64\",\n\t\t\tlocalAddr: ma.StringCast(\"/ip6/1::1/tcp/12345\"),\n\t\t\tdialAddr:  ma.StringCast(\"/ip4/1.2.3.4/tcp/23232\"),\n\t\t\tsuccess:   false,\n\t\t},\n\t\t{\n\t\t\tname:      \"simple mismatch\",\n\t\t\tlocalAddr: ma.StringCast(\"/ip4/192.168.0.1/tcp/12345\"),\n\t\t\tdialAddr:  ma.StringCast(\"/ip4/1.2.3.4/udp/23232/quic-v1\"),\n\t\t\tsuccess:   false,\n\t\t},\n\t\t{\n\t\t\tname:      \"quic-vs-webtransport\",\n\t\t\tlocalAddr: ma.StringCast(\"/ip4/192.168.0.1/udp/12345/quic-v1\"),\n\t\t\tdialAddr:  ma.StringCast(\"/ip4/1.2.3.4/udp/123/quic-v1/webtransport\"),\n\t\t\tsuccess:   false,\n\t\t},\n\t\t{\n\t\t\tname:      \"webtransport-certhash\",\n\t\t\tlocalAddr: ma.StringCast(\"/ip4/192.168.0.1/udp/12345/quic-v1/webtransport\"),\n\t\t\tdialAddr:  ma.StringCast(\"/ip4/1.2.3.4/udp/123/quic-v1/webtransport/certhash/uEgNmb28\"),\n\t\t\tsuccess:   true,\n\t\t},\n\t\t{\n\t\t\tname:      \"dns\",\n\t\t\tlocalAddr: ma.StringCast(\"/dns/lib.p2p/udp/12345/quic-v1\"),\n\t\t\tdialAddr:  ma.StringCast(\"/ip6/1::1/udp/123/quic-v1/\"),\n\t\t\tsuccess:   false,\n\t\t},\n\t\t{\n\t\t\tname:      \"dns6\",\n\t\t\tlocalAddr: ma.StringCast(\"/dns6/lib.p2p/udp/12345/quic-v1\"),\n\t\t\tdialAddr:  ma.StringCast(\"/ip4/1.2.3.4/udp/123/quic-v1/\"),\n\t\t\tsuccess:   false,\n\t\t},\n\t\t{\n\t\t\tname:      \"wss\",\n\t\t\tdialAddr:  ma.StringCast(\"/dns/lib.p2p/tcp/1/wss\"),\n\t\t\tlocalAddr: ma.StringCast(\"/ip4/1.2.3.4/tcp/1/tls/ws\"),\n\t\t\tsuccess:   true,\n\t\t},\n\t\t{\n\t\t\tname:      \"tls-sni\",\n\t\t\tlocalAddr: ma.StringCast(\"/ip4/1.2.3.4/tcp/1/wss\"),\n\t\t\tdialAddr:  ma.StringCast(\"/ip4/1.2.3.4/tcp/1/tls/sni/abc.xyz/ws\"),\n\t\t\tsuccess:   true,\n\t\t},\n\t\t{\n\t\t\tname:      \"only p2p\",\n\t\t\tlocalAddr: ma.StringCast(\"/p2p/QmYo41GybvrXk8y8Xnm1P7pfA4YEXCpfnLyzgRPnNbG35e\"),\n\t\t\tdialAddr:  ma.StringCast(\"/p2p/QmYo41GybvrXk8y8Xnm1P7pfA4YEXCpfnLyzgRPnNbG35e\"),\n\t\t\tsuccess:   true,\n\t\t},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif c.areAddrsConsistent(tc.localAddr, tc.dialAddr) != tc.success {\n\t\t\t\twantStr := \"match\"\n\t\t\t\tif !tc.success {\n\t\t\t\t\twantStr = \"mismatch\"\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"expected %s between\\nlocal addr: %s\\ndial addr:  %s\", wantStr, tc.localAddr, tc.dialAddr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestPeerMap(t *testing.T) {\n\tpm := newPeersMap()\n\t// Add 1, 2, 3\n\tpm.Put(peer.ID(\"1\"))\n\tpm.Put(peer.ID(\"2\"))\n\tpm.Put(peer.ID(\"3\"))\n\n\t// Remove 3, 2\n\tpm.Delete(peer.ID(\"3\"))\n\tpm.Delete(peer.ID(\"2\"))\n\n\t// Add 4\n\tpm.Put(peer.ID(\"4\"))\n\n\t// Remove 3, 2 again. Should be no op\n\tpm.Delete(peer.ID(\"3\"))\n\tpm.Delete(peer.ID(\"2\"))\n\n\tcontains := []peer.ID{\"1\", \"4\"}\n\telems := make([]peer.ID, 0)\n\tfor p := range pm.Shuffled() {\n\t\telems = append(elems, p)\n\t}\n\trequire.ElementsMatch(t, contains, elems)\n}\n\nfunc FuzzClient(f *testing.F) {\n\ta := newAutoNAT(f, nil, allowPrivateAddrs, WithServerRateLimit(math.MaxInt32, math.MaxInt32, math.MaxInt32, 2))\n\tc := newAutoNAT(f, nil)\n\tidAndWait(f, c, a)\n\n\t// TODO: Move this to go-multiaddrs\n\tgetProto := func(protos []byte) ma.Multiaddr {\n\t\tprotoType := 0\n\t\tif len(protos) > 0 {\n\t\t\tprotoType = int(protos[0])\n\t\t}\n\n\t\tport1, port2 := 0, 0\n\t\tif len(protos) > 1 {\n\t\t\tport1 = int(protos[1])\n\t\t}\n\t\tif len(protos) > 2 {\n\t\t\tport2 = int(protos[2])\n\t\t}\n\t\tprotoTemplates := []string{\n\t\t\t\"/tcp/%d/\",\n\t\t\t\"/udp/%d/\",\n\t\t\t\"/udp/%d/quic-v1/\",\n\t\t\t\"/udp/%d/quic-v1/tcp/%d\",\n\t\t\t\"/udp/%d/quic-v1/webtransport/\",\n\t\t\t\"/udp/%d/webrtc/\",\n\t\t\t\"/udp/%d/webrtc-direct/\",\n\t\t\t\"/unix/hello/\",\n\t\t}\n\t\ts := protoTemplates[protoType%len(protoTemplates)]\n\t\tport1 %= (1 << 16)\n\t\tif strings.Count(s, \"%d\") == 1 {\n\t\t\treturn ma.StringCast(fmt.Sprintf(s, port1))\n\t\t}\n\t\tport2 %= (1 << 16)\n\t\treturn ma.StringCast(fmt.Sprintf(s, port1, port2))\n\t}\n\n\tgetIP := func(ips []byte) ma.Multiaddr {\n\t\tipType := 0\n\t\tif len(ips) > 0 {\n\t\t\tipType = int(ips[0])\n\t\t}\n\t\tips = ips[1:]\n\t\tvar x, y int64\n\t\tsplit := min(len(ips), 128/8)\n\t\tvar b [8]byte\n\t\tcopy(b[:], ips[:split])\n\t\tx = int64(binary.LittleEndian.Uint64(b[:]))\n\t\tclear(b[:])\n\t\tcopy(b[:], ips[split:])\n\t\ty = int64(binary.LittleEndian.Uint64(b[:]))\n\n\t\tvar ip netip.Addr\n\t\tswitch ipType % 3 {\n\t\tcase 0:\n\t\t\tip = netip.AddrFrom4([4]byte{byte(x), byte(x >> 8), byte(x >> 16), byte(x >> 24)})\n\t\t\treturn ma.StringCast(fmt.Sprintf(\"/ip4/%s/\", ip))\n\t\tcase 1:\n\t\t\tpubIP := net.ParseIP(\"2005::\") // Public IP address\n\t\t\tx := int64(binary.LittleEndian.Uint64(pubIP[0:8]))\n\t\t\tip = netip.AddrFrom16([16]byte{\n\t\t\t\tbyte(x), byte(x >> 8), byte(x >> 16), byte(x >> 24),\n\t\t\t\tbyte(x >> 32), byte(x >> 40), byte(x >> 48), byte(x >> 56),\n\t\t\t\tbyte(y), byte(y >> 8), byte(y >> 16), byte(y >> 24),\n\t\t\t\tbyte(y >> 32), byte(y >> 40), byte(y >> 48), byte(y >> 56),\n\t\t\t})\n\t\t\treturn ma.StringCast(fmt.Sprintf(\"/ip6/%s/\", ip))\n\t\tdefault:\n\t\t\tip := netip.AddrFrom16([16]byte{\n\t\t\t\tbyte(x), byte(x >> 8), byte(x >> 16), byte(x >> 24),\n\t\t\t\tbyte(x >> 32), byte(x >> 40), byte(x >> 48), byte(x >> 56),\n\t\t\t\tbyte(y), byte(y >> 8), byte(y >> 16), byte(y >> 24),\n\t\t\t\tbyte(y >> 32), byte(y >> 40), byte(y >> 48), byte(y >> 56),\n\t\t\t})\n\t\t\treturn ma.StringCast(fmt.Sprintf(\"/ip6/%s/\", ip))\n\t\t}\n\t}\n\n\tgetAddr := func(addrType int, ips, protos []byte) ma.Multiaddr {\n\t\tswitch addrType % 4 {\n\t\tcase 0:\n\t\t\treturn getIP(ips).Encapsulate(getProto(protos))\n\t\tcase 1:\n\t\t\treturn getProto(protos)\n\t\tcase 2:\n\t\t\treturn nil\n\t\tdefault:\n\t\t\treturn getIP(ips).Encapsulate(getProto(protos))\n\t\t}\n\t}\n\n\tgetDNSAddr := func(hostNameBytes, protos []byte) ma.Multiaddr {\n\t\thostName := strings.ReplaceAll(string(hostNameBytes), \"\\\\\", \"\")\n\t\thostName = strings.ReplaceAll(hostName, \"/\", \"\")\n\t\tif hostName == \"\" {\n\t\t\thostName = \"localhost\"\n\t\t}\n\t\tdnsType := 0\n\t\tif len(hostNameBytes) > 0 {\n\t\t\tdnsType = int(hostNameBytes[0])\n\t\t}\n\t\tdnsProtos := []string{\"dns\", \"dns4\", \"dns6\", \"dnsaddr\"}\n\t\tda := ma.StringCast(fmt.Sprintf(\"/%s/%s/\", dnsProtos[dnsType%len(dnsProtos)], hostName))\n\t\treturn da.Encapsulate(getProto(protos))\n\t}\n\n\tconst maxAddrs = 100\n\tgetAddrs := func(numAddrs int, ips, protos, hostNames []byte) []ma.Multiaddr {\n\t\tif len(ips) == 0 || len(protos) == 0 || len(hostNames) == 0 {\n\t\t\treturn nil\n\t\t}\n\t\tnumAddrs = ((numAddrs % maxAddrs) + maxAddrs) % maxAddrs\n\t\taddrs := make([]ma.Multiaddr, numAddrs)\n\t\tipIdx := 0\n\t\tprotoIdx := 0\n\t\tfor i := range numAddrs {\n\t\t\taddrs[i] = getAddr(i, ips[ipIdx:], protos[protoIdx:])\n\t\t\tipIdx = (ipIdx + 1) % len(ips)\n\t\t\tprotoIdx = (protoIdx + 1) % len(protos)\n\t\t}\n\t\tmaxDNSAddrs := 10\n\t\tprotoIdx = 0\n\t\tfor i := 0; i < len(hostNames) && i < maxDNSAddrs; i += 2 {\n\t\t\ted := min(i+2, len(hostNames))\n\t\t\taddrs = append(addrs, getDNSAddr(hostNames[i:ed], protos[protoIdx:]))\n\t\t\tprotoIdx = (protoIdx + 1) % len(protos)\n\t\t}\n\t\treturn addrs\n\t}\n\t// reduce the streamTimeout before running this. TODO: fix this\n\tf.Fuzz(func(_ *testing.T, numAddrs int, ips, protos, hostNames []byte) {\n\t\taddrs := getAddrs(numAddrs, ips, protos, hostNames)\n\t\treqs := make([]Request, len(addrs))\n\t\tfor i, addr := range addrs {\n\t\t\treqs[i] = Request{Addr: addr, SendDialData: true}\n\t\t}\n\t\tc.GetReachability(context.Background(), reqs)\n\t})\n}\n\nfunc TestNormalizeMultiaddr(t *testing.T) {\n\trequire.Equal(t,\n\t\t\"/ip4/1.2.3.4/udp/9999/quic-v1/webtransport\",\n\t\tnormalizeMultiaddr(ma.StringCast(\"/ip4/1.2.3.4/udp/9999/quic-v1/webtransport/certhash/uEgNmb28\")).String(),\n\t)\n}\n"
  },
  {
    "path": "p2p/protocol/autonatv2/client.go",
    "content": "package autonatv2\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"runtime/debug\"\n\t\"sync\"\n\t\"time\"\n\n\t\"math/rand/v2\"\n\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/autonatv2/pb\"\n\t\"github.com/libp2p/go-msgio/pbio\"\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\n// client implements the client for making dial requests for AutoNAT v2. It verifies successful\n// dials and provides an option to send data for dial requests.\ntype client struct {\n\thost          host.Host\n\tdialData      []byte\n\tmetricsTracer MetricsTracer\n\n\tmu sync.Mutex\n\t// dialBackQueues maps nonce to the channel for providing the local multiaddr of the connection\n\t// the nonce was received on\n\tdialBackQueues map[uint64]chan ma.Multiaddr\n}\n\nfunc newClient(s *autoNATSettings) *client {\n\treturn &client{\n\t\tdialData:       make([]byte, 4000),\n\t\tdialBackQueues: make(map[uint64]chan ma.Multiaddr),\n\t\tmetricsTracer:  s.metricsTracer,\n\t}\n}\n\nfunc (ac *client) Start(h host.Host) {\n\tac.host = h\n\tac.host.SetStreamHandler(DialBackProtocol, ac.handleDialBack)\n}\n\nfunc (ac *client) Close() {\n\tac.host.RemoveStreamHandler(DialBackProtocol)\n}\n\n// GetReachability verifies address reachability with a AutoNAT v2 server p.\nfunc (ac *client) GetReachability(ctx context.Context, p peer.ID, reqs []Request) (Result, error) {\n\tresult, err := ac.getReachability(ctx, p, reqs)\n\n\t// Track metrics\n\tif ac.metricsTracer != nil {\n\t\tac.metricsTracer.ClientCompletedRequest(reqs, result, err)\n\t}\n\n\treturn result, err\n}\n\nfunc (ac *client) getReachability(ctx context.Context, p peer.ID, reqs []Request) (Result, error) {\n\tctx, cancel := context.WithTimeout(ctx, streamTimeout)\n\tdefer cancel()\n\n\ts, err := ac.host.NewStream(ctx, p, DialProtocol)\n\tif err != nil {\n\t\treturn Result{}, fmt.Errorf(\"open %s stream failed: %w\", DialProtocol, err)\n\t}\n\n\tif err := s.Scope().SetService(ServiceName); err != nil {\n\t\ts.Reset()\n\t\treturn Result{}, fmt.Errorf(\"attach stream %s to service %s failed: %w\", DialProtocol, ServiceName, err)\n\t}\n\n\tif err := s.Scope().ReserveMemory(maxMsgSize, network.ReservationPriorityAlways); err != nil {\n\t\ts.Reset()\n\t\treturn Result{}, fmt.Errorf(\"failed to reserve memory for stream %s: %w\", DialProtocol, err)\n\t}\n\tdefer s.Scope().ReleaseMemory(maxMsgSize)\n\n\ts.SetDeadline(time.Now().Add(streamTimeout))\n\tdefer s.Close()\n\n\tnonce := rand.Uint64()\n\tch := make(chan ma.Multiaddr, 1)\n\tac.mu.Lock()\n\tac.dialBackQueues[nonce] = ch\n\tac.mu.Unlock()\n\tdefer func() {\n\t\tac.mu.Lock()\n\t\tdelete(ac.dialBackQueues, nonce)\n\t\tac.mu.Unlock()\n\t}()\n\n\tmsg := newDialRequest(reqs, nonce)\n\tw := pbio.NewDelimitedWriter(s)\n\tif err := w.WriteMsg(&msg); err != nil {\n\t\ts.Reset()\n\t\treturn Result{}, fmt.Errorf(\"dial request write failed: %w\", err)\n\t}\n\n\tr := pbio.NewDelimitedReader(s, maxMsgSize)\n\tif err := r.ReadMsg(&msg); err != nil {\n\t\ts.Reset()\n\t\treturn Result{}, fmt.Errorf(\"dial msg read failed: %w\", err)\n\t}\n\n\tswitch {\n\tcase msg.GetDialResponse() != nil:\n\t\tbreak\n\t// provide dial data if appropriate\n\tcase msg.GetDialDataRequest() != nil:\n\t\tif err := validateDialDataRequest(reqs, &msg); err != nil {\n\t\t\ts.Reset()\n\t\t\treturn Result{}, fmt.Errorf(\"invalid dial data request: %s %w\", s.Conn().RemoteMultiaddr(), err)\n\t\t}\n\t\t// dial data request is valid and we want to send data\n\t\tif err := sendDialData(ac.dialData, int(msg.GetDialDataRequest().GetNumBytes()), w, &msg); err != nil {\n\t\t\ts.Reset()\n\t\t\treturn Result{}, fmt.Errorf(\"dial data send failed: %w\", err)\n\t\t}\n\t\tif err := r.ReadMsg(&msg); err != nil {\n\t\t\ts.Reset()\n\t\t\treturn Result{}, fmt.Errorf(\"dial response read failed: %w\", err)\n\t\t}\n\t\tif msg.GetDialResponse() == nil {\n\t\t\ts.Reset()\n\t\t\treturn Result{}, fmt.Errorf(\"invalid response type: %T\", msg.Msg)\n\t\t}\n\tdefault:\n\t\ts.Reset()\n\t\treturn Result{}, fmt.Errorf(\"invalid msg type: %T\", msg.Msg)\n\t}\n\n\tresp := msg.GetDialResponse()\n\tif resp.GetStatus() != pb.DialResponse_OK {\n\t\t// E_DIAL_REFUSED has implication for deciding future address verificiation priorities\n\t\t// wrap a distinct error for convenient errors.Is usage\n\t\tif resp.GetStatus() == pb.DialResponse_E_DIAL_REFUSED {\n\t\t\treturn Result{AllAddrsRefused: true}, nil\n\t\t}\n\t\treturn Result{}, fmt.Errorf(\"dial request failed: response status %d %s\", resp.GetStatus(),\n\t\t\tpb.DialResponse_ResponseStatus_name[int32(resp.GetStatus())])\n\t}\n\tif resp.GetDialStatus() == pb.DialStatus_UNUSED {\n\t\treturn Result{}, fmt.Errorf(\"invalid response: invalid dial status UNUSED\")\n\t}\n\tif int(resp.AddrIdx) >= len(reqs) {\n\t\treturn Result{}, fmt.Errorf(\"invalid response: addr index out of range: %d [0-%d)\", resp.AddrIdx, len(reqs))\n\t}\n\t// wait for nonce from the server\n\tvar dialBackAddr ma.Multiaddr\n\tif resp.GetDialStatus() == pb.DialStatus_OK {\n\t\ttimer := time.NewTimer(dialBackStreamTimeout)\n\t\tselect {\n\t\tcase at := <-ch:\n\t\t\tdialBackAddr = at\n\t\tcase <-ctx.Done():\n\t\tcase <-timer.C:\n\t\t}\n\t\ttimer.Stop()\n\t}\n\treturn ac.newResult(resp, reqs, dialBackAddr)\n}\n\nfunc validateDialDataRequest(reqs []Request, msg *pb.Message) error {\n\tidx := int(msg.GetDialDataRequest().AddrIdx)\n\tif idx >= len(reqs) { // invalid address index\n\t\treturn fmt.Errorf(\"addr index out of range: %d [0-%d)\", idx, len(reqs))\n\t}\n\tif msg.GetDialDataRequest().NumBytes > maxHandshakeSizeBytes { // data request is too high\n\t\treturn fmt.Errorf(\"requested data too high: %d\", msg.GetDialDataRequest().NumBytes)\n\t}\n\tif !reqs[idx].SendDialData { // low priority addr\n\t\treturn fmt.Errorf(\"low priority addr: %s index %d\", reqs[idx].Addr, idx)\n\t}\n\treturn nil\n}\n\nfunc (ac *client) newResult(resp *pb.DialResponse, reqs []Request, dialBackAddr ma.Multiaddr) (Result, error) {\n\tidx := int(resp.AddrIdx)\n\tif idx >= len(reqs) {\n\t\t// This should have been validated by this point, but checking this is cheap.\n\t\treturn Result{}, fmt.Errorf(\"addrs index(%d) greater than len(reqs)(%d)\", idx, len(reqs))\n\t}\n\taddr := reqs[idx].Addr\n\n\trch := network.ReachabilityUnknown //nolint:ineffassign\n\tswitch resp.DialStatus {\n\tcase pb.DialStatus_OK:\n\t\tif !ac.areAddrsConsistent(dialBackAddr, addr) {\n\t\t\t// the server is misinforming us about the address it successfully dialed\n\t\t\t// either we received no dialback or the address on the dialback is inconsistent with\n\t\t\t// what the server is telling us\n\t\t\treturn Result{}, fmt.Errorf(\"invalid response: dialBackAddr: %s, respAddr: %s\", dialBackAddr, addr)\n\t\t}\n\t\trch = network.ReachabilityPublic\n\tcase pb.DialStatus_E_DIAL_BACK_ERROR:\n\t\tif !ac.areAddrsConsistent(dialBackAddr, addr) {\n\t\t\treturn Result{}, fmt.Errorf(\"dial-back stream error: dialBackAddr: %s, respAddr: %s\", dialBackAddr, addr)\n\t\t}\n\t\t// We received the dial back but the server claims the dial back errored.\n\t\t// As long as we received the correct nonce in dial back it is safe to assume\n\t\t// that we are public.\n\t\trch = network.ReachabilityPublic\n\tcase pb.DialStatus_E_DIAL_ERROR:\n\t\trch = network.ReachabilityPrivate\n\tdefault:\n\t\t// Unexpected response code. Discard the response and fail.\n\t\tlog.Warn(\"invalid status code received in response\",\n\t\t\t\"address\", addr,\n\t\t\t\"dial_status\", resp.DialStatus)\n\t\treturn Result{}, fmt.Errorf(\"invalid response: invalid status code for addr %s: %d\", addr, resp.DialStatus)\n\t}\n\n\treturn Result{\n\t\tAddr:         addr,\n\t\tIdx:          idx,\n\t\tReachability: rch,\n\t}, nil\n}\n\nfunc sendDialData(dialData []byte, numBytes int, w pbio.Writer, msg *pb.Message) (err error) {\n\tddResp := &pb.DialDataResponse{Data: dialData}\n\t*msg = pb.Message{\n\t\tMsg: &pb.Message_DialDataResponse{\n\t\t\tDialDataResponse: ddResp,\n\t\t},\n\t}\n\tfor remain := numBytes; remain > 0; {\n\t\tif remain < len(ddResp.Data) {\n\t\t\tddResp.Data = ddResp.Data[:remain]\n\t\t}\n\t\tif err := w.WriteMsg(msg); err != nil {\n\t\t\treturn fmt.Errorf(\"write failed: %w\", err)\n\t\t}\n\t\tremain -= len(dialData)\n\t}\n\treturn nil\n}\n\nfunc newDialRequest(reqs []Request, nonce uint64) pb.Message {\n\taddrbs := make([][]byte, len(reqs))\n\tfor i, r := range reqs {\n\t\taddrbs[i] = r.Addr.Bytes()\n\t}\n\treturn pb.Message{\n\t\tMsg: &pb.Message_DialRequest{\n\t\t\tDialRequest: &pb.DialRequest{\n\t\t\t\tAddrs: addrbs,\n\t\t\t\tNonce: nonce,\n\t\t\t},\n\t\t},\n\t}\n}\n\n// handleDialBack receives the nonce on the dial-back stream\nfunc (ac *client) handleDialBack(s network.Stream) {\n\tdefer func() {\n\t\tif rerr := recover(); rerr != nil {\n\t\t\tfmt.Fprintf(os.Stderr, \"caught panic: %s\\n%s\\n\", rerr, debug.Stack())\n\t\t}\n\t\ts.Reset()\n\t}()\n\n\tif err := s.Scope().SetService(ServiceName); err != nil {\n\t\tlog.Debug(\"failed to attach stream to service\",\n\t\t\t\"service_name\", ServiceName,\n\t\t\t\"error\", err)\n\t\ts.Reset()\n\t\treturn\n\t}\n\n\tif err := s.Scope().ReserveMemory(dialBackMaxMsgSize, network.ReservationPriorityAlways); err != nil {\n\t\tlog.Debug(\"failed to reserve memory for stream\",\n\t\t\t\"protocol\", DialBackProtocol,\n\t\t\t\"error\", err)\n\t\ts.Reset()\n\t\treturn\n\t}\n\tdefer s.Scope().ReleaseMemory(dialBackMaxMsgSize)\n\n\ts.SetDeadline(time.Now().Add(dialBackStreamTimeout))\n\tdefer s.Close()\n\n\tr := pbio.NewDelimitedReader(s, dialBackMaxMsgSize)\n\tvar msg pb.DialBack\n\tif err := r.ReadMsg(&msg); err != nil {\n\t\tlog.Debug(\"failed to read dialback message\",\n\t\t\t\"remote_peer\", s.Conn().RemotePeer(),\n\t\t\t\"error\", err)\n\t\ts.Reset()\n\t\treturn\n\t}\n\tnonce := msg.GetNonce()\n\n\tac.mu.Lock()\n\tch := ac.dialBackQueues[nonce]\n\tac.mu.Unlock()\n\tif ch == nil {\n\t\tlog.Debug(\"dialback received with invalid nonce\",\n\t\t\t\"local_multiaddr\", s.Conn().LocalMultiaddr(),\n\t\t\t\"remote_peer\", s.Conn().RemotePeer(),\n\t\t\t\"nonce\", nonce)\n\t\ts.Reset()\n\t\treturn\n\t}\n\tselect {\n\tcase ch <- s.Conn().LocalMultiaddr():\n\tdefault:\n\t\tlog.Debug(\"multiple dialbacks received\",\n\t\t\t\"local_multiaddr\", s.Conn().LocalMultiaddr(),\n\t\t\t\"remote_peer\", s.Conn().RemotePeer())\n\t\ts.Reset()\n\t\treturn\n\t}\n\tw := pbio.NewDelimitedWriter(s)\n\tres := pb.DialBackResponse{}\n\tif err := w.WriteMsg(&res); err != nil {\n\t\tlog.Debug(\"failed to write dialback response\",\n\t\t\t\"error\", err)\n\t\ts.Reset()\n\t}\n}\n\nvar tlsWSAddr = ma.StringCast(\"/tls/ws\")\n\n// normalizeMultiaddr returns a multiaddr suitable for equality checks.\n// it removes trailing certhashes and p2p components, removes sni components,\n// and translates /wss to /tls/ws.\n// Remove sni components because there's no way for us to verify whether the\n// correct sni was dialled by the remote host as the LocalAddr on the websocket conn\n// doesn't have sni information.\n// Note: This is used for comparing two addresses where both the addresses are\n// controlled by the host not by a remote node.\nfunc normalizeMultiaddr(addr ma.Multiaddr) ma.Multiaddr {\n\taddr = removeTrailing(addr, ma.P_P2P)\n\taddr = removeTrailing(addr, ma.P_CERTHASH)\n\n\t// /wss => /tls/ws\n\tfor i, c := range addr {\n\t\tif c.Code() == ma.P_WSS {\n\t\t\tna := make(ma.Multiaddr, 0, len(addr)+1)\n\t\t\tna = append(na, addr[:i]...)\n\t\t\tna = append(na, tlsWSAddr...)\n\t\t\tna = append(na, addr[i+1:]...)\n\t\t\taddr = na\n\t\t\tbreak // only do this once; there shouldn't be two /wss components anyway\n\t\t}\n\t}\n\n\t// remove the sni component\n\tfor i, c := range addr {\n\t\tif c.Code() == ma.P_SNI {\n\t\t\tna := make(ma.Multiaddr, 0, len(addr)-1)\n\t\t\tna = append(na, addr[:i]...)\n\t\t\tna = append(na, addr[i+1:]...)\n\t\t\taddr = na\n\t\t\tbreak // only do this once; there shouldn't be two /sni components anyway\n\t\t}\n\t}\n\treturn addr\n}\n\nfunc removeTrailing(addr ma.Multiaddr, protocolCode int) ma.Multiaddr {\n\tfor i := len(addr) - 1; i >= 0; i-- {\n\t\tif addr[i].Code() != protocolCode {\n\t\t\treturn addr[:i+1]\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (ac *client) areAddrsConsistent(connLocalAddr, dialedAddr ma.Multiaddr) bool {\n\tif len(connLocalAddr) == 0 || len(dialedAddr) == 0 {\n\t\treturn false\n\t}\n\tconnLocalAddr = normalizeMultiaddr(connLocalAddr)\n\tdialedAddr = normalizeMultiaddr(dialedAddr)\n\n\tlocalProtos := connLocalAddr.Protocols()\n\texternalProtos := dialedAddr.Protocols()\n\tif len(localProtos) != len(externalProtos) {\n\t\treturn false\n\t}\n\tfor i, lp := range localProtos {\n\t\tep := externalProtos[i]\n\t\tif i == 0 {\n\t\t\tswitch ep.Code {\n\t\t\tcase ma.P_DNS, ma.P_DNSADDR:\n\t\t\t\tif lp.Code == ma.P_IP4 || lp.Code == ma.P_IP6 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treturn false\n\t\t\tcase ma.P_DNS4:\n\t\t\t\tif lp.Code == ma.P_IP4 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treturn false\n\t\t\tcase ma.P_DNS6:\n\t\t\t\tif lp.Code == ma.P_IP6 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tif lp.Code != ep.Code {\n\t\t\t\treturn false\n\t\t\t}\n\t\t} else if lp.Code != ep.Code {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "p2p/protocol/autonatv2/metrics.go",
    "content": "package autonatv2\n\nimport (\n\t\"strconv\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/p2p/metricshelper\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/autonatv2/pb\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n)\n\ntype MetricsTracer interface {\n\tCompletedRequest(EventDialRequestCompleted)\n\tClientCompletedRequest([]Request, Result, error)\n}\n\nconst metricNamespace = \"libp2p_autonatv2\"\n\nvar (\n\trequestsCompleted = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"requests_completed_total\",\n\t\t\tHelp:      \"Requests Completed\",\n\t\t},\n\t\t[]string{\"server_error\", \"response_status\", \"dial_status\", \"dial_data_required\", \"ip_or_dns_version\", \"transport\"},\n\t)\n\tclientRequestsCompleted = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"client_requests_completed_total\",\n\t\t\tHelp:      \"Client Requests Completed\",\n\t\t},\n\t\t[]string{\"ip_or_dns_version\", \"transport\", \"addr_count\", \"dial_refused\", \"reachability\"},\n\t)\n\tclientRequestsTotal = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"client_requests_total\",\n\t\t\tHelp:      \"Client Requests Total\",\n\t\t},\n\t\t[]string{\"outcome\"},\n\t)\n)\n\ntype metricsTracer struct {\n}\n\nfunc NewMetricsTracer(reg prometheus.Registerer) MetricsTracer {\n\tmetricshelper.RegisterCollectors(reg, requestsCompleted, clientRequestsCompleted, clientRequestsTotal)\n\treturn &metricsTracer{}\n}\n\nfunc (m *metricsTracer) CompletedRequest(e EventDialRequestCompleted) {\n\tlabels := metricshelper.GetStringSlice()\n\tdefer metricshelper.PutStringSlice(labels)\n\n\terrStr := getErrString(e.Error)\n\n\tdialData := \"false\"\n\tif e.DialDataRequired {\n\t\tdialData = \"true\"\n\t}\n\n\tvar ip, transport string\n\tif e.DialedAddr != nil {\n\t\tip = getIPOrDNSVersion(e.DialedAddr)\n\t\ttransport = metricshelper.GetTransport(e.DialedAddr)\n\t}\n\n\t*labels = append(*labels,\n\t\terrStr,\n\t\tpb.DialResponse_ResponseStatus_name[int32(e.ResponseStatus)],\n\t\tpb.DialStatus_name[int32(e.DialStatus)],\n\t\tdialData,\n\t\tip,\n\t\ttransport,\n\t)\n\trequestsCompleted.WithLabelValues(*labels...).Inc()\n}\n\nfunc (m *metricsTracer) ClientCompletedRequest(reqs []Request, result Result, err error) {\n\tlabels := metricshelper.GetStringSlice()\n\tdefer metricshelper.PutStringSlice(labels)\n\n\tif err != nil {\n\t\tclientRequestsTotal.WithLabelValues(\"failure\").Inc()\n\t\treturn\n\t}\n\tclientRequestsTotal.WithLabelValues(\"success\").Inc()\n\n\taddrCount := len(reqs)\n\tdialRefused := \"false\"\n\tif result.AllAddrsRefused {\n\t\tdialRefused = \"true\"\n\t}\n\treachability := \"unknown\"\n\tswitch result.Reachability {\n\tcase network.ReachabilityPublic:\n\t\treachability = \"public\"\n\tcase network.ReachabilityPrivate:\n\t\treachability = \"private\"\n\t}\n\n\tipOrDNSVersion := \"unknown\"\n\ttransport := \"unknown\"\n\tif result.Addr != nil {\n\t\tipOrDNSVersion = getIPOrDNSVersion(result.Addr)\n\t\ttransport = metricshelper.GetTransport(result.Addr)\n\t}\n\n\t*labels = append(*labels,\n\t\tipOrDNSVersion,\n\t\ttransport,\n\t\tstrconv.Itoa(addrCount),\n\t\tdialRefused,\n\t\treachability,\n\t)\n\tclientRequestsCompleted.WithLabelValues(*labels...).Inc()\n}\n\nfunc getIPOrDNSVersion(a ma.Multiaddr) string {\n\tif len(a) == 0 {\n\t\treturn \"\"\n\t}\n\tres := \"unknown\"\n\tswitch a[0].Protocol().Code {\n\tcase ma.P_DNS, ma.P_DNSADDR:\n\t\tres = \"dns\"\n\tcase ma.P_DNS4:\n\t\tres = \"dns4\"\n\tcase ma.P_DNS6:\n\t\tres = \"dns6\"\n\tcase ma.P_IP4:\n\t\tres = \"ip4\"\n\tcase ma.P_IP6:\n\t\tres = \"ip6\"\n\t}\n\treturn res\n}\n\nfunc getErrString(e error) string {\n\tvar errStr string\n\tswitch e {\n\tcase nil:\n\t\terrStr = \"nil\"\n\tcase errBadRequest, errDialDataRefused, errResourceLimitExceeded:\n\t\terrStr = e.Error()\n\tdefault:\n\t\terrStr = \"other\"\n\t}\n\treturn errStr\n}\n"
  },
  {
    "path": "p2p/protocol/autonatv2/metrics_test.go",
    "content": "package autonatv2\n\nimport (\n\t\"errors\"\n\t\"math/rand\"\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/autonatv2/pb\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n)\n\nfunc TestMetricsNoAllocNoCover(t *testing.T) {\n\tmt := NewMetricsTracer(prometheus.DefaultRegisterer)\n\trespStatuses := []pb.DialResponse_ResponseStatus{\n\t\tpb.DialResponse_E_DIAL_REFUSED,\n\t\tpb.DialResponse_OK,\n\t}\n\tdialStatuses := []pb.DialStatus{\n\t\tpb.DialStatus_OK,\n\t\tpb.DialStatus_E_DIAL_BACK_ERROR,\n\t}\n\terrs := []error{\n\t\tnil,\n\t\terrBadRequest,\n\t\terrDialDataRefused,\n\t\terrors.New(\"write failed\"),\n\t}\n\taddrs := []ma.Multiaddr{\n\t\tnil,\n\t\tma.StringCast(\"/ip4/1.2.3.4/udp/1/quic-v1\"),\n\t\tma.StringCast(\"/ip4/1.1.1.1/tcp/1/\"),\n\t}\n\treqs := [][]Request{\n\t\t{{Addr: addrs[0]}, {Addr: addrs[1], SendDialData: true}},\n\t\t{{Addr: addrs[1]}, {Addr: addrs[2]}},\n\t}\n\n\ttests := map[string]func(){\n\t\t\"CompletedRequest\": func() {\n\t\t\tmt.CompletedRequest(EventDialRequestCompleted{\n\t\t\t\tError:            errs[rand.Intn(len(errs))],\n\t\t\t\tResponseStatus:   respStatuses[rand.Intn(len(respStatuses))],\n\t\t\t\tDialStatus:       dialStatuses[rand.Intn(len(dialStatuses))],\n\t\t\t\tDialDataRequired: rand.Intn(2) == 1,\n\t\t\t\tDialedAddr:       addrs[rand.Intn(len(addrs))],\n\t\t\t})\n\t\t},\n\t\t\"CompletedClientRequest\": func() {\n\t\t\tmt.ClientCompletedRequest(reqs[rand.Intn(len(reqs))], Result{AllAddrsRefused: rand.Intn(2) == 1, Reachability: network.Reachability(rand.Intn(2)), Addr: addrs[rand.Intn(len(addrs))]}, errs[rand.Intn(len(errs))])\n\t\t},\n\t}\n\tfor method, f := range tests {\n\t\tallocs := testing.AllocsPerRun(10000, f)\n\t\tif allocs > 0 {\n\t\t\tt.Fatalf(\"%s alloc test failed expected 0 received %0.2f\", method, allocs)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "p2p/protocol/autonatv2/msg_reader.go",
    "content": "package autonatv2\n\nimport (\n\t\"io\"\n\n\t\"github.com/multiformats/go-varint\"\n)\n\n// msgReader reads a varint prefixed message from R without any buffering\ntype msgReader struct {\n\tR   io.Reader\n\tBuf []byte\n}\n\nfunc (m *msgReader) ReadByte() (byte, error) {\n\tbuf := m.Buf[:1]\n\t_, err := m.R.Read(buf)\n\treturn buf[0], err\n}\n\nfunc (m *msgReader) ReadMsg() ([]byte, error) {\n\tsz, err := varint.ReadUvarint(m)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif sz > uint64(len(m.Buf)) {\n\t\treturn nil, io.ErrShortBuffer\n\t}\n\tn := 0\n\tfor n < int(sz) {\n\t\tnr, err := m.R.Read(m.Buf[n:sz])\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tn += nr\n\t}\n\treturn m.Buf[:sz], nil\n}\n"
  },
  {
    "path": "p2p/protocol/autonatv2/options.go",
    "content": "package autonatv2\n\nimport \"time\"\n\n// autoNATSettings is used to configure AutoNAT\ntype autoNATSettings struct {\n\tallowPrivateAddrs                    bool\n\tserverRPM                            int\n\tserverPerPeerRPM                     int\n\tserverDialDataRPM                    int\n\tmaxConcurrentRequestsPerPeer         int\n\tdataRequestPolicy                    dataRequestPolicyFunc\n\tnow                                  func() time.Time\n\tamplificatonAttackPreventionDialWait time.Duration\n\tmetricsTracer                        MetricsTracer\n\tthrottlePeerDuration                 time.Duration\n}\n\nfunc defaultSettings() *autoNATSettings {\n\treturn &autoNATSettings{\n\t\tallowPrivateAddrs:                    false,\n\t\tserverRPM:                            60, // 1 every second\n\t\tserverPerPeerRPM:                     12, // 1 every 5 seconds\n\t\tserverDialDataRPM:                    12, // 1 every 5 seconds\n\t\tmaxConcurrentRequestsPerPeer:         2,\n\t\tdataRequestPolicy:                    amplificationAttackPrevention,\n\t\tamplificatonAttackPreventionDialWait: 3 * time.Second,\n\t\tnow:                                  time.Now,\n\t\tthrottlePeerDuration:                 defaultThrottlePeerDuration,\n\t}\n}\n\ntype AutoNATOption func(s *autoNATSettings) error\n\nfunc WithServerRateLimit(rpm, perPeerRPM, dialDataRPM int, maxConcurrentRequestsPerPeer int) AutoNATOption {\n\treturn func(s *autoNATSettings) error {\n\t\ts.serverRPM = rpm\n\t\ts.serverPerPeerRPM = perPeerRPM\n\t\ts.serverDialDataRPM = dialDataRPM\n\t\ts.maxConcurrentRequestsPerPeer = maxConcurrentRequestsPerPeer\n\t\treturn nil\n\t}\n}\n\nfunc WithMetricsTracer(m MetricsTracer) AutoNATOption {\n\treturn func(s *autoNATSettings) error {\n\t\ts.metricsTracer = m\n\t\treturn nil\n\t}\n}\n\nfunc withDataRequestPolicy(drp dataRequestPolicyFunc) AutoNATOption {\n\treturn func(s *autoNATSettings) error {\n\t\ts.dataRequestPolicy = drp\n\t\treturn nil\n\t}\n}\n\nfunc allowPrivateAddrs(s *autoNATSettings) error {\n\ts.allowPrivateAddrs = true\n\treturn nil\n}\n\nfunc withAmplificationAttackPreventionDialWait(d time.Duration) AutoNATOption {\n\treturn func(s *autoNATSettings) error {\n\t\ts.amplificatonAttackPreventionDialWait = d\n\t\treturn nil\n\t}\n}\n\nfunc withThrottlePeerDuration(d time.Duration) AutoNATOption {\n\treturn func(s *autoNATSettings) error {\n\t\ts.throttlePeerDuration = d\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "p2p/protocol/autonatv2/pb/autonatv2.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.6\n// \tprotoc        v5.29.2\n// source: p2p/protocol/autonatv2/pb/autonatv2.proto\n\npackage pb\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype DialStatus int32\n\nconst (\n\tDialStatus_UNUSED            DialStatus = 0\n\tDialStatus_E_DIAL_ERROR      DialStatus = 100\n\tDialStatus_E_DIAL_BACK_ERROR DialStatus = 101\n\tDialStatus_OK                DialStatus = 200\n)\n\n// Enum value maps for DialStatus.\nvar (\n\tDialStatus_name = map[int32]string{\n\t\t0:   \"UNUSED\",\n\t\t100: \"E_DIAL_ERROR\",\n\t\t101: \"E_DIAL_BACK_ERROR\",\n\t\t200: \"OK\",\n\t}\n\tDialStatus_value = map[string]int32{\n\t\t\"UNUSED\":            0,\n\t\t\"E_DIAL_ERROR\":      100,\n\t\t\"E_DIAL_BACK_ERROR\": 101,\n\t\t\"OK\":                200,\n\t}\n)\n\nfunc (x DialStatus) Enum() *DialStatus {\n\tp := new(DialStatus)\n\t*p = x\n\treturn p\n}\n\nfunc (x DialStatus) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (DialStatus) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_p2p_protocol_autonatv2_pb_autonatv2_proto_enumTypes[0].Descriptor()\n}\n\nfunc (DialStatus) Type() protoreflect.EnumType {\n\treturn &file_p2p_protocol_autonatv2_pb_autonatv2_proto_enumTypes[0]\n}\n\nfunc (x DialStatus) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use DialStatus.Descriptor instead.\nfunc (DialStatus) EnumDescriptor() ([]byte, []int) {\n\treturn file_p2p_protocol_autonatv2_pb_autonatv2_proto_rawDescGZIP(), []int{0}\n}\n\ntype DialResponse_ResponseStatus int32\n\nconst (\n\tDialResponse_E_INTERNAL_ERROR   DialResponse_ResponseStatus = 0\n\tDialResponse_E_REQUEST_REJECTED DialResponse_ResponseStatus = 100\n\tDialResponse_E_DIAL_REFUSED     DialResponse_ResponseStatus = 101\n\tDialResponse_OK                 DialResponse_ResponseStatus = 200\n)\n\n// Enum value maps for DialResponse_ResponseStatus.\nvar (\n\tDialResponse_ResponseStatus_name = map[int32]string{\n\t\t0:   \"E_INTERNAL_ERROR\",\n\t\t100: \"E_REQUEST_REJECTED\",\n\t\t101: \"E_DIAL_REFUSED\",\n\t\t200: \"OK\",\n\t}\n\tDialResponse_ResponseStatus_value = map[string]int32{\n\t\t\"E_INTERNAL_ERROR\":   0,\n\t\t\"E_REQUEST_REJECTED\": 100,\n\t\t\"E_DIAL_REFUSED\":     101,\n\t\t\"OK\":                 200,\n\t}\n)\n\nfunc (x DialResponse_ResponseStatus) Enum() *DialResponse_ResponseStatus {\n\tp := new(DialResponse_ResponseStatus)\n\t*p = x\n\treturn p\n}\n\nfunc (x DialResponse_ResponseStatus) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (DialResponse_ResponseStatus) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_p2p_protocol_autonatv2_pb_autonatv2_proto_enumTypes[1].Descriptor()\n}\n\nfunc (DialResponse_ResponseStatus) Type() protoreflect.EnumType {\n\treturn &file_p2p_protocol_autonatv2_pb_autonatv2_proto_enumTypes[1]\n}\n\nfunc (x DialResponse_ResponseStatus) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use DialResponse_ResponseStatus.Descriptor instead.\nfunc (DialResponse_ResponseStatus) EnumDescriptor() ([]byte, []int) {\n\treturn file_p2p_protocol_autonatv2_pb_autonatv2_proto_rawDescGZIP(), []int{3, 0}\n}\n\ntype DialBackResponse_DialBackStatus int32\n\nconst (\n\tDialBackResponse_OK DialBackResponse_DialBackStatus = 0\n)\n\n// Enum value maps for DialBackResponse_DialBackStatus.\nvar (\n\tDialBackResponse_DialBackStatus_name = map[int32]string{\n\t\t0: \"OK\",\n\t}\n\tDialBackResponse_DialBackStatus_value = map[string]int32{\n\t\t\"OK\": 0,\n\t}\n)\n\nfunc (x DialBackResponse_DialBackStatus) Enum() *DialBackResponse_DialBackStatus {\n\tp := new(DialBackResponse_DialBackStatus)\n\t*p = x\n\treturn p\n}\n\nfunc (x DialBackResponse_DialBackStatus) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (DialBackResponse_DialBackStatus) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_p2p_protocol_autonatv2_pb_autonatv2_proto_enumTypes[2].Descriptor()\n}\n\nfunc (DialBackResponse_DialBackStatus) Type() protoreflect.EnumType {\n\treturn &file_p2p_protocol_autonatv2_pb_autonatv2_proto_enumTypes[2]\n}\n\nfunc (x DialBackResponse_DialBackStatus) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use DialBackResponse_DialBackStatus.Descriptor instead.\nfunc (DialBackResponse_DialBackStatus) EnumDescriptor() ([]byte, []int) {\n\treturn file_p2p_protocol_autonatv2_pb_autonatv2_proto_rawDescGZIP(), []int{6, 0}\n}\n\ntype Message struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Types that are valid to be assigned to Msg:\n\t//\n\t//\t*Message_DialRequest\n\t//\t*Message_DialResponse\n\t//\t*Message_DialDataRequest\n\t//\t*Message_DialDataResponse\n\tMsg           isMessage_Msg `protobuf_oneof:\"msg\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Message) Reset() {\n\t*x = Message{}\n\tmi := &file_p2p_protocol_autonatv2_pb_autonatv2_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Message) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Message) ProtoMessage() {}\n\nfunc (x *Message) ProtoReflect() protoreflect.Message {\n\tmi := &file_p2p_protocol_autonatv2_pb_autonatv2_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Message.ProtoReflect.Descriptor instead.\nfunc (*Message) Descriptor() ([]byte, []int) {\n\treturn file_p2p_protocol_autonatv2_pb_autonatv2_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Message) GetMsg() isMessage_Msg {\n\tif x != nil {\n\t\treturn x.Msg\n\t}\n\treturn nil\n}\n\nfunc (x *Message) GetDialRequest() *DialRequest {\n\tif x != nil {\n\t\tif x, ok := x.Msg.(*Message_DialRequest); ok {\n\t\t\treturn x.DialRequest\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *Message) GetDialResponse() *DialResponse {\n\tif x != nil {\n\t\tif x, ok := x.Msg.(*Message_DialResponse); ok {\n\t\t\treturn x.DialResponse\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *Message) GetDialDataRequest() *DialDataRequest {\n\tif x != nil {\n\t\tif x, ok := x.Msg.(*Message_DialDataRequest); ok {\n\t\t\treturn x.DialDataRequest\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *Message) GetDialDataResponse() *DialDataResponse {\n\tif x != nil {\n\t\tif x, ok := x.Msg.(*Message_DialDataResponse); ok {\n\t\t\treturn x.DialDataResponse\n\t\t}\n\t}\n\treturn nil\n}\n\ntype isMessage_Msg interface {\n\tisMessage_Msg()\n}\n\ntype Message_DialRequest struct {\n\tDialRequest *DialRequest `protobuf:\"bytes,1,opt,name=dialRequest,proto3,oneof\"`\n}\n\ntype Message_DialResponse struct {\n\tDialResponse *DialResponse `protobuf:\"bytes,2,opt,name=dialResponse,proto3,oneof\"`\n}\n\ntype Message_DialDataRequest struct {\n\tDialDataRequest *DialDataRequest `protobuf:\"bytes,3,opt,name=dialDataRequest,proto3,oneof\"`\n}\n\ntype Message_DialDataResponse struct {\n\tDialDataResponse *DialDataResponse `protobuf:\"bytes,4,opt,name=dialDataResponse,proto3,oneof\"`\n}\n\nfunc (*Message_DialRequest) isMessage_Msg() {}\n\nfunc (*Message_DialResponse) isMessage_Msg() {}\n\nfunc (*Message_DialDataRequest) isMessage_Msg() {}\n\nfunc (*Message_DialDataResponse) isMessage_Msg() {}\n\ntype DialRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tAddrs         [][]byte               `protobuf:\"bytes,1,rep,name=addrs,proto3\" json:\"addrs,omitempty\"`\n\tNonce         uint64                 `protobuf:\"fixed64,2,opt,name=nonce,proto3\" json:\"nonce,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *DialRequest) Reset() {\n\t*x = DialRequest{}\n\tmi := &file_p2p_protocol_autonatv2_pb_autonatv2_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *DialRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DialRequest) ProtoMessage() {}\n\nfunc (x *DialRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_p2p_protocol_autonatv2_pb_autonatv2_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DialRequest.ProtoReflect.Descriptor instead.\nfunc (*DialRequest) Descriptor() ([]byte, []int) {\n\treturn file_p2p_protocol_autonatv2_pb_autonatv2_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *DialRequest) GetAddrs() [][]byte {\n\tif x != nil {\n\t\treturn x.Addrs\n\t}\n\treturn nil\n}\n\nfunc (x *DialRequest) GetNonce() uint64 {\n\tif x != nil {\n\t\treturn x.Nonce\n\t}\n\treturn 0\n}\n\ntype DialDataRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tAddrIdx       uint32                 `protobuf:\"varint,1,opt,name=addrIdx,proto3\" json:\"addrIdx,omitempty\"`\n\tNumBytes      uint64                 `protobuf:\"varint,2,opt,name=numBytes,proto3\" json:\"numBytes,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *DialDataRequest) Reset() {\n\t*x = DialDataRequest{}\n\tmi := &file_p2p_protocol_autonatv2_pb_autonatv2_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *DialDataRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DialDataRequest) ProtoMessage() {}\n\nfunc (x *DialDataRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_p2p_protocol_autonatv2_pb_autonatv2_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DialDataRequest.ProtoReflect.Descriptor instead.\nfunc (*DialDataRequest) Descriptor() ([]byte, []int) {\n\treturn file_p2p_protocol_autonatv2_pb_autonatv2_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *DialDataRequest) GetAddrIdx() uint32 {\n\tif x != nil {\n\t\treturn x.AddrIdx\n\t}\n\treturn 0\n}\n\nfunc (x *DialDataRequest) GetNumBytes() uint64 {\n\tif x != nil {\n\t\treturn x.NumBytes\n\t}\n\treturn 0\n}\n\ntype DialResponse struct {\n\tstate         protoimpl.MessageState      `protogen:\"open.v1\"`\n\tStatus        DialResponse_ResponseStatus `protobuf:\"varint,1,opt,name=status,proto3,enum=autonatv2.pb.DialResponse_ResponseStatus\" json:\"status,omitempty\"`\n\tAddrIdx       uint32                      `protobuf:\"varint,2,opt,name=addrIdx,proto3\" json:\"addrIdx,omitempty\"`\n\tDialStatus    DialStatus                  `protobuf:\"varint,3,opt,name=dialStatus,proto3,enum=autonatv2.pb.DialStatus\" json:\"dialStatus,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *DialResponse) Reset() {\n\t*x = DialResponse{}\n\tmi := &file_p2p_protocol_autonatv2_pb_autonatv2_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *DialResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DialResponse) ProtoMessage() {}\n\nfunc (x *DialResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_p2p_protocol_autonatv2_pb_autonatv2_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DialResponse.ProtoReflect.Descriptor instead.\nfunc (*DialResponse) Descriptor() ([]byte, []int) {\n\treturn file_p2p_protocol_autonatv2_pb_autonatv2_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *DialResponse) GetStatus() DialResponse_ResponseStatus {\n\tif x != nil {\n\t\treturn x.Status\n\t}\n\treturn DialResponse_E_INTERNAL_ERROR\n}\n\nfunc (x *DialResponse) GetAddrIdx() uint32 {\n\tif x != nil {\n\t\treturn x.AddrIdx\n\t}\n\treturn 0\n}\n\nfunc (x *DialResponse) GetDialStatus() DialStatus {\n\tif x != nil {\n\t\treturn x.DialStatus\n\t}\n\treturn DialStatus_UNUSED\n}\n\ntype DialDataResponse struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tData          []byte                 `protobuf:\"bytes,1,opt,name=data,proto3\" json:\"data,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *DialDataResponse) Reset() {\n\t*x = DialDataResponse{}\n\tmi := &file_p2p_protocol_autonatv2_pb_autonatv2_proto_msgTypes[4]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *DialDataResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DialDataResponse) ProtoMessage() {}\n\nfunc (x *DialDataResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_p2p_protocol_autonatv2_pb_autonatv2_proto_msgTypes[4]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DialDataResponse.ProtoReflect.Descriptor instead.\nfunc (*DialDataResponse) Descriptor() ([]byte, []int) {\n\treturn file_p2p_protocol_autonatv2_pb_autonatv2_proto_rawDescGZIP(), []int{4}\n}\n\nfunc (x *DialDataResponse) GetData() []byte {\n\tif x != nil {\n\t\treturn x.Data\n\t}\n\treturn nil\n}\n\ntype DialBack struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tNonce         uint64                 `protobuf:\"fixed64,1,opt,name=nonce,proto3\" json:\"nonce,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *DialBack) Reset() {\n\t*x = DialBack{}\n\tmi := &file_p2p_protocol_autonatv2_pb_autonatv2_proto_msgTypes[5]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *DialBack) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DialBack) ProtoMessage() {}\n\nfunc (x *DialBack) ProtoReflect() protoreflect.Message {\n\tmi := &file_p2p_protocol_autonatv2_pb_autonatv2_proto_msgTypes[5]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DialBack.ProtoReflect.Descriptor instead.\nfunc (*DialBack) Descriptor() ([]byte, []int) {\n\treturn file_p2p_protocol_autonatv2_pb_autonatv2_proto_rawDescGZIP(), []int{5}\n}\n\nfunc (x *DialBack) GetNonce() uint64 {\n\tif x != nil {\n\t\treturn x.Nonce\n\t}\n\treturn 0\n}\n\ntype DialBackResponse struct {\n\tstate         protoimpl.MessageState          `protogen:\"open.v1\"`\n\tStatus        DialBackResponse_DialBackStatus `protobuf:\"varint,1,opt,name=status,proto3,enum=autonatv2.pb.DialBackResponse_DialBackStatus\" json:\"status,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *DialBackResponse) Reset() {\n\t*x = DialBackResponse{}\n\tmi := &file_p2p_protocol_autonatv2_pb_autonatv2_proto_msgTypes[6]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *DialBackResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DialBackResponse) ProtoMessage() {}\n\nfunc (x *DialBackResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_p2p_protocol_autonatv2_pb_autonatv2_proto_msgTypes[6]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DialBackResponse.ProtoReflect.Descriptor instead.\nfunc (*DialBackResponse) Descriptor() ([]byte, []int) {\n\treturn file_p2p_protocol_autonatv2_pb_autonatv2_proto_rawDescGZIP(), []int{6}\n}\n\nfunc (x *DialBackResponse) GetStatus() DialBackResponse_DialBackStatus {\n\tif x != nil {\n\t\treturn x.Status\n\t}\n\treturn DialBackResponse_OK\n}\n\nvar File_p2p_protocol_autonatv2_pb_autonatv2_proto protoreflect.FileDescriptor\n\nconst file_p2p_protocol_autonatv2_pb_autonatv2_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\")p2p/protocol/autonatv2/pb/autonatv2.proto\\x12\\fautonatv2.pb\\\"\\xaa\\x02\\n\" +\n\t\"\\aMessage\\x12=\\n\" +\n\t\"\\vdialRequest\\x18\\x01 \\x01(\\v2\\x19.autonatv2.pb.DialRequestH\\x00R\\vdialRequest\\x12@\\n\" +\n\t\"\\fdialResponse\\x18\\x02 \\x01(\\v2\\x1a.autonatv2.pb.DialResponseH\\x00R\\fdialResponse\\x12I\\n\" +\n\t\"\\x0fdialDataRequest\\x18\\x03 \\x01(\\v2\\x1d.autonatv2.pb.DialDataRequestH\\x00R\\x0fdialDataRequest\\x12L\\n\" +\n\t\"\\x10dialDataResponse\\x18\\x04 \\x01(\\v2\\x1e.autonatv2.pb.DialDataResponseH\\x00R\\x10dialDataResponseB\\x05\\n\" +\n\t\"\\x03msg\\\"9\\n\" +\n\t\"\\vDialRequest\\x12\\x14\\n\" +\n\t\"\\x05addrs\\x18\\x01 \\x03(\\fR\\x05addrs\\x12\\x14\\n\" +\n\t\"\\x05nonce\\x18\\x02 \\x01(\\x06R\\x05nonce\\\"G\\n\" +\n\t\"\\x0fDialDataRequest\\x12\\x18\\n\" +\n\t\"\\aaddrIdx\\x18\\x01 \\x01(\\rR\\aaddrIdx\\x12\\x1a\\n\" +\n\t\"\\bnumBytes\\x18\\x02 \\x01(\\x04R\\bnumBytes\\\"\\x82\\x02\\n\" +\n\t\"\\fDialResponse\\x12A\\n\" +\n\t\"\\x06status\\x18\\x01 \\x01(\\x0e2).autonatv2.pb.DialResponse.ResponseStatusR\\x06status\\x12\\x18\\n\" +\n\t\"\\aaddrIdx\\x18\\x02 \\x01(\\rR\\aaddrIdx\\x128\\n\" +\n\t\"\\n\" +\n\t\"dialStatus\\x18\\x03 \\x01(\\x0e2\\x18.autonatv2.pb.DialStatusR\\n\" +\n\t\"dialStatus\\\"[\\n\" +\n\t\"\\x0eResponseStatus\\x12\\x14\\n\" +\n\t\"\\x10E_INTERNAL_ERROR\\x10\\x00\\x12\\x16\\n\" +\n\t\"\\x12E_REQUEST_REJECTED\\x10d\\x12\\x12\\n\" +\n\t\"\\x0eE_DIAL_REFUSED\\x10e\\x12\\a\\n\" +\n\t\"\\x02OK\\x10\\xc8\\x01\\\"&\\n\" +\n\t\"\\x10DialDataResponse\\x12\\x12\\n\" +\n\t\"\\x04data\\x18\\x01 \\x01(\\fR\\x04data\\\" \\n\" +\n\t\"\\bDialBack\\x12\\x14\\n\" +\n\t\"\\x05nonce\\x18\\x01 \\x01(\\x06R\\x05nonce\\\"s\\n\" +\n\t\"\\x10DialBackResponse\\x12E\\n\" +\n\t\"\\x06status\\x18\\x01 \\x01(\\x0e2-.autonatv2.pb.DialBackResponse.DialBackStatusR\\x06status\\\"\\x18\\n\" +\n\t\"\\x0eDialBackStatus\\x12\\x06\\n\" +\n\t\"\\x02OK\\x10\\x00*J\\n\" +\n\t\"\\n\" +\n\t\"DialStatus\\x12\\n\" +\n\t\"\\n\" +\n\t\"\\x06UNUSED\\x10\\x00\\x12\\x10\\n\" +\n\t\"\\fE_DIAL_ERROR\\x10d\\x12\\x15\\n\" +\n\t\"\\x11E_DIAL_BACK_ERROR\\x10e\\x12\\a\\n\" +\n\t\"\\x02OK\\x10\\xc8\\x01B7Z5github.com/libp2p/go-libp2p/p2p/protocol/autonatv2/pbb\\x06proto3\"\n\nvar (\n\tfile_p2p_protocol_autonatv2_pb_autonatv2_proto_rawDescOnce sync.Once\n\tfile_p2p_protocol_autonatv2_pb_autonatv2_proto_rawDescData []byte\n)\n\nfunc file_p2p_protocol_autonatv2_pb_autonatv2_proto_rawDescGZIP() []byte {\n\tfile_p2p_protocol_autonatv2_pb_autonatv2_proto_rawDescOnce.Do(func() {\n\t\tfile_p2p_protocol_autonatv2_pb_autonatv2_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_p2p_protocol_autonatv2_pb_autonatv2_proto_rawDesc), len(file_p2p_protocol_autonatv2_pb_autonatv2_proto_rawDesc)))\n\t})\n\treturn file_p2p_protocol_autonatv2_pb_autonatv2_proto_rawDescData\n}\n\nvar file_p2p_protocol_autonatv2_pb_autonatv2_proto_enumTypes = make([]protoimpl.EnumInfo, 3)\nvar file_p2p_protocol_autonatv2_pb_autonatv2_proto_msgTypes = make([]protoimpl.MessageInfo, 7)\nvar file_p2p_protocol_autonatv2_pb_autonatv2_proto_goTypes = []any{\n\t(DialStatus)(0),                      // 0: autonatv2.pb.DialStatus\n\t(DialResponse_ResponseStatus)(0),     // 1: autonatv2.pb.DialResponse.ResponseStatus\n\t(DialBackResponse_DialBackStatus)(0), // 2: autonatv2.pb.DialBackResponse.DialBackStatus\n\t(*Message)(nil),                      // 3: autonatv2.pb.Message\n\t(*DialRequest)(nil),                  // 4: autonatv2.pb.DialRequest\n\t(*DialDataRequest)(nil),              // 5: autonatv2.pb.DialDataRequest\n\t(*DialResponse)(nil),                 // 6: autonatv2.pb.DialResponse\n\t(*DialDataResponse)(nil),             // 7: autonatv2.pb.DialDataResponse\n\t(*DialBack)(nil),                     // 8: autonatv2.pb.DialBack\n\t(*DialBackResponse)(nil),             // 9: autonatv2.pb.DialBackResponse\n}\nvar file_p2p_protocol_autonatv2_pb_autonatv2_proto_depIdxs = []int32{\n\t4, // 0: autonatv2.pb.Message.dialRequest:type_name -> autonatv2.pb.DialRequest\n\t6, // 1: autonatv2.pb.Message.dialResponse:type_name -> autonatv2.pb.DialResponse\n\t5, // 2: autonatv2.pb.Message.dialDataRequest:type_name -> autonatv2.pb.DialDataRequest\n\t7, // 3: autonatv2.pb.Message.dialDataResponse:type_name -> autonatv2.pb.DialDataResponse\n\t1, // 4: autonatv2.pb.DialResponse.status:type_name -> autonatv2.pb.DialResponse.ResponseStatus\n\t0, // 5: autonatv2.pb.DialResponse.dialStatus:type_name -> autonatv2.pb.DialStatus\n\t2, // 6: autonatv2.pb.DialBackResponse.status:type_name -> autonatv2.pb.DialBackResponse.DialBackStatus\n\t7, // [7:7] is the sub-list for method output_type\n\t7, // [7:7] is the sub-list for method input_type\n\t7, // [7:7] is the sub-list for extension type_name\n\t7, // [7:7] is the sub-list for extension extendee\n\t0, // [0:7] is the sub-list for field type_name\n}\n\nfunc init() { file_p2p_protocol_autonatv2_pb_autonatv2_proto_init() }\nfunc file_p2p_protocol_autonatv2_pb_autonatv2_proto_init() {\n\tif File_p2p_protocol_autonatv2_pb_autonatv2_proto != nil {\n\t\treturn\n\t}\n\tfile_p2p_protocol_autonatv2_pb_autonatv2_proto_msgTypes[0].OneofWrappers = []any{\n\t\t(*Message_DialRequest)(nil),\n\t\t(*Message_DialResponse)(nil),\n\t\t(*Message_DialDataRequest)(nil),\n\t\t(*Message_DialDataResponse)(nil),\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_p2p_protocol_autonatv2_pb_autonatv2_proto_rawDesc), len(file_p2p_protocol_autonatv2_pb_autonatv2_proto_rawDesc)),\n\t\t\tNumEnums:      3,\n\t\t\tNumMessages:   7,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_p2p_protocol_autonatv2_pb_autonatv2_proto_goTypes,\n\t\tDependencyIndexes: file_p2p_protocol_autonatv2_pb_autonatv2_proto_depIdxs,\n\t\tEnumInfos:         file_p2p_protocol_autonatv2_pb_autonatv2_proto_enumTypes,\n\t\tMessageInfos:      file_p2p_protocol_autonatv2_pb_autonatv2_proto_msgTypes,\n\t}.Build()\n\tFile_p2p_protocol_autonatv2_pb_autonatv2_proto = out.File\n\tfile_p2p_protocol_autonatv2_pb_autonatv2_proto_goTypes = nil\n\tfile_p2p_protocol_autonatv2_pb_autonatv2_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "p2p/protocol/autonatv2/pb/autonatv2.proto",
    "content": "syntax = \"proto3\";\n\npackage autonatv2.pb;\n\noption go_package = \"github.com/libp2p/go-libp2p/p2p/protocol/autonatv2/pb\";\n\nmessage Message {\n    oneof msg {\n        DialRequest dialRequest   = 1;\n        DialResponse dialResponse = 2;\n        DialDataRequest dialDataRequest = 3;\n        DialDataResponse dialDataResponse = 4;\n    }\n}\n\nmessage DialRequest {\n    repeated bytes addrs = 1;\n    fixed64 nonce = 2;\n}\n\n\nmessage DialDataRequest {\n    uint32 addrIdx = 1;\n    uint64 numBytes = 2;\n}\n\n\nenum DialStatus {\n    UNUSED            = 0;\n    E_DIAL_ERROR      = 100;\n    E_DIAL_BACK_ERROR = 101;\n    OK                = 200;\n}\n\n\nmessage DialResponse {\n    enum ResponseStatus {\n        E_INTERNAL_ERROR   = 0;\n        E_REQUEST_REJECTED = 100; \n        E_DIAL_REFUSED     = 101;\n        OK  = 200; \n    }\n\n    ResponseStatus status = 1;\n    uint32 addrIdx        = 2; \n    DialStatus dialStatus = 3;\n}\n\n\nmessage DialDataResponse {\n    bytes data = 1;\n}\n\n\nmessage DialBack {\n    fixed64 nonce = 1;\n}\n\n\nmessage DialBackResponse {\n    enum DialBackStatus {\n        OK = 0;\n    }\n\n    DialBackStatus status = 1;\n}\n"
  },
  {
    "path": "p2p/protocol/autonatv2/server.go",
    "content": "package autonatv2\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"runtime/debug\"\n\t\"sync\"\n\t\"time\"\n\n\tpool \"github.com/libp2p/go-buffer-pool\"\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/autonatv2/pb\"\n\t\"github.com/libp2p/go-msgio/pbio\"\n\n\t\"math/rand\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\nvar (\n\terrResourceLimitExceeded = errors.New(\"resource limit exceeded\")\n\terrBadRequest            = errors.New(\"bad request\")\n\terrDialDataRefused       = errors.New(\"dial data refused\")\n)\n\ntype dataRequestPolicyFunc = func(observedAddr, dialAddr ma.Multiaddr) bool\n\ntype EventDialRequestCompleted struct {\n\tError            error\n\tResponseStatus   pb.DialResponse_ResponseStatus\n\tDialStatus       pb.DialStatus\n\tDialDataRequired bool\n\tDialedAddr       ma.Multiaddr\n}\n\n// server implements the AutoNATv2 server.\n// It can ask client to provide dial data before attempting the requested dial.\n// It rate limits requests on a global level, per peer level and on whether the request requires dial data.\ntype server struct {\n\thost       host.Host\n\tdialerHost host.Host\n\tlimiter    *rateLimiter\n\n\t// dialDataRequestPolicy is used to determine whether dialing the address requires receiving\n\t// dial data. It is set to amplification attack prevention by default.\n\tdialDataRequestPolicy                dataRequestPolicyFunc\n\tamplificatonAttackPreventionDialWait time.Duration\n\tmetricsTracer                        MetricsTracer\n\n\t// for tests\n\tnow               func() time.Time\n\tallowPrivateAddrs bool\n}\n\nfunc newServer(dialer host.Host, s *autoNATSettings) *server {\n\treturn &server{\n\t\tdialerHost:                           dialer,\n\t\tdialDataRequestPolicy:                s.dataRequestPolicy,\n\t\tamplificatonAttackPreventionDialWait: s.amplificatonAttackPreventionDialWait,\n\t\tallowPrivateAddrs:                    s.allowPrivateAddrs,\n\t\tlimiter: &rateLimiter{\n\t\t\tRPM:                          s.serverRPM,\n\t\t\tPerPeerRPM:                   s.serverPerPeerRPM,\n\t\t\tDialDataRPM:                  s.serverDialDataRPM,\n\t\t\tMaxConcurrentRequestsPerPeer: s.maxConcurrentRequestsPerPeer,\n\t\t\tnow:                          s.now,\n\t\t},\n\t\tnow:           s.now,\n\t\tmetricsTracer: s.metricsTracer,\n\t}\n}\n\n// Enable attaches the stream handler to the host.\nfunc (as *server) Start(h host.Host) {\n\tas.host = h\n\tas.host.SetStreamHandler(DialProtocol, as.handleDialRequest)\n}\n\nfunc (as *server) Close() {\n\tas.host.RemoveStreamHandler(DialProtocol)\n\tas.dialerHost.Close()\n\tas.limiter.Close()\n}\n\n// handleDialRequest is the dial-request protocol stream handler\nfunc (as *server) handleDialRequest(s network.Stream) {\n\tdefer func() {\n\t\tif rerr := recover(); rerr != nil {\n\t\t\tfmt.Fprintf(os.Stderr, \"caught panic: %s\\n%s\\n\", rerr, debug.Stack())\n\t\t\ts.Reset()\n\t\t}\n\t}()\n\n\tlog.Debug(\"received dial-request\",\n\t\t\"remote_peer\", s.Conn().RemotePeer(),\n\t\t\"remote_multiaddr\", s.Conn().RemoteMultiaddr())\n\tevt := as.serveDialRequest(s)\n\tlog.Debug(\"completed dial-request\",\n\t\t\"remote_peer\", s.Conn().RemotePeer(),\n\t\t\"response_status\", evt.ResponseStatus,\n\t\t\"dial_status\", evt.DialStatus,\n\t\t\"error\", evt.Error)\n\tif as.metricsTracer != nil {\n\t\tas.metricsTracer.CompletedRequest(evt)\n\t}\n}\n\nfunc (as *server) serveDialRequest(s network.Stream) EventDialRequestCompleted {\n\tif err := s.Scope().SetService(ServiceName); err != nil {\n\t\ts.Reset()\n\t\tlog.Debug(\"failed to attach stream to service\",\n\t\t\t\"service_name\", ServiceName,\n\t\t\t\"error\", err)\n\t\treturn EventDialRequestCompleted{\n\t\t\tError: errors.New(\"failed to attach stream to autonat-v2\"),\n\t\t}\n\t}\n\n\tif err := s.Scope().ReserveMemory(maxMsgSize, network.ReservationPriorityAlways); err != nil {\n\t\ts.Reset()\n\t\tlog.Debug(\"failed to reserve memory for stream\",\n\t\t\t\"protocol\", DialProtocol,\n\t\t\t\"error\", err)\n\t\treturn EventDialRequestCompleted{Error: errResourceLimitExceeded}\n\t}\n\tdefer s.Scope().ReleaseMemory(maxMsgSize)\n\n\tdeadline := as.now().Add(streamTimeout)\n\tctx, cancel := context.WithDeadline(context.Background(), deadline)\n\tdefer cancel()\n\ts.SetDeadline(as.now().Add(streamTimeout))\n\tdefer s.Close()\n\n\tp := s.Conn().RemotePeer()\n\n\tvar msg pb.Message\n\tw := pbio.NewDelimitedWriter(s)\n\t// Check for rate limit before parsing the request\n\tif !as.limiter.Accept(p) {\n\t\tmsg = pb.Message{\n\t\t\tMsg: &pb.Message_DialResponse{\n\t\t\t\tDialResponse: &pb.DialResponse{\n\t\t\t\t\tStatus: pb.DialResponse_E_REQUEST_REJECTED,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\tif err := w.WriteMsg(&msg); err != nil {\n\t\t\ts.Reset()\n\t\t\tlog.Debug(\"failed to write request rejected response\",\n\t\t\t\t\"remote_peer\", p,\n\t\t\t\t\"error\", err)\n\t\t\treturn EventDialRequestCompleted{\n\t\t\t\tResponseStatus: pb.DialResponse_E_REQUEST_REJECTED,\n\t\t\t\tError:          fmt.Errorf(\"write failed: %w\", err),\n\t\t\t}\n\t\t}\n\t\tlog.Debug(\"rejected request\",\n\t\t\t\"remote_peer\", p,\n\t\t\t\"reason\", \"rate limit exceeded\")\n\t\treturn EventDialRequestCompleted{ResponseStatus: pb.DialResponse_E_REQUEST_REJECTED}\n\t}\n\tdefer as.limiter.CompleteRequest(p)\n\n\tr := pbio.NewDelimitedReader(s, maxMsgSize)\n\tif err := r.ReadMsg(&msg); err != nil {\n\t\ts.Reset()\n\t\tlog.Debug(\"failed to read request\",\n\t\t\t\"remote_peer\", p,\n\t\t\t\"error\", err)\n\t\treturn EventDialRequestCompleted{Error: fmt.Errorf(\"read failed: %w\", err)}\n\t}\n\tif msg.GetDialRequest() == nil {\n\t\ts.Reset()\n\t\tlog.Debug(\"invalid message type\",\n\t\t\t\"remote_peer\", p,\n\t\t\t\"actual_type\", fmt.Sprintf(\"%T\", msg.Msg),\n\t\t\t\"expected_type\", \"DialRequest\")\n\t\treturn EventDialRequestCompleted{Error: errBadRequest}\n\t}\n\n\t// parse peer's addresses\n\tvar dialAddr ma.Multiaddr\n\tvar addrIdx int\n\tfor i, ab := range msg.GetDialRequest().GetAddrs() {\n\t\tif i >= maxPeerAddresses {\n\t\t\tbreak\n\t\t}\n\t\ta, err := ma.NewMultiaddrBytes(ab)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tif !as.allowPrivateAddrs && !manet.IsPublicAddr(a) {\n\t\t\tcontinue\n\t\t}\n\t\tif !as.dialerHost.Network().CanDial(p, a) {\n\t\t\tcontinue\n\t\t}\n\t\tdialAddr = a\n\t\taddrIdx = i\n\t\tbreak\n\t}\n\t// No dialable address\n\tif dialAddr == nil {\n\t\tmsg = pb.Message{\n\t\t\tMsg: &pb.Message_DialResponse{\n\t\t\t\tDialResponse: &pb.DialResponse{\n\t\t\t\t\tStatus: pb.DialResponse_E_DIAL_REFUSED,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\tif err := w.WriteMsg(&msg); err != nil {\n\t\t\ts.Reset()\n\t\t\tlog.Debug(\"failed to write dial refused response\",\n\t\t\t\t\"remote_peer\", p,\n\t\t\t\t\"error\", err)\n\t\t\treturn EventDialRequestCompleted{\n\t\t\t\tResponseStatus: pb.DialResponse_E_DIAL_REFUSED,\n\t\t\t\tError:          fmt.Errorf(\"write failed: %w\", err),\n\t\t\t}\n\t\t}\n\t\treturn EventDialRequestCompleted{\n\t\t\tResponseStatus: pb.DialResponse_E_DIAL_REFUSED,\n\t\t}\n\t}\n\n\tnonce := msg.GetDialRequest().Nonce\n\n\tisDialDataRequired := as.dialDataRequestPolicy(s.Conn().RemoteMultiaddr(), dialAddr)\n\tif isDialDataRequired && !as.limiter.AcceptDialDataRequest() {\n\t\tmsg = pb.Message{\n\t\t\tMsg: &pb.Message_DialResponse{\n\t\t\t\tDialResponse: &pb.DialResponse{\n\t\t\t\t\tStatus: pb.DialResponse_E_REQUEST_REJECTED,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\tif err := w.WriteMsg(&msg); err != nil {\n\t\t\ts.Reset()\n\t\t\tlog.Debug(\"failed to write request rejected response\",\n\t\t\t\t\"remote_peer\", p,\n\t\t\t\t\"error\", err)\n\t\t\treturn EventDialRequestCompleted{\n\t\t\t\tResponseStatus:   pb.DialResponse_E_REQUEST_REJECTED,\n\t\t\t\tError:            fmt.Errorf(\"write failed: %w\", err),\n\t\t\t\tDialDataRequired: true,\n\t\t\t}\n\t\t}\n\t\tlog.Debug(\"rejected request\",\n\t\t\t\"remote_peer\", p,\n\t\t\t\"reason\", \"rate limit exceeded\")\n\t\treturn EventDialRequestCompleted{\n\t\t\tResponseStatus:   pb.DialResponse_E_REQUEST_REJECTED,\n\t\t\tDialDataRequired: true,\n\t\t}\n\t}\n\n\tif isDialDataRequired {\n\t\tif err := getDialData(w, s, &msg, addrIdx); err != nil {\n\t\t\ts.Reset()\n\t\t\tlog.Debug(\"dial data request refused\",\n\t\t\t\t\"remote_peer\", p,\n\t\t\t\t\"error\", err)\n\t\t\treturn EventDialRequestCompleted{\n\t\t\t\tError:            errDialDataRefused,\n\t\t\t\tDialDataRequired: true,\n\t\t\t\tDialedAddr:       dialAddr,\n\t\t\t}\n\t\t}\n\t\t// wait for a bit to prevent thundering herd style attacks on a victim\n\t\twaitTime := time.Duration(rand.Intn(int(as.amplificatonAttackPreventionDialWait) + 1)) // the range is [0, n)\n\t\tt := time.NewTimer(waitTime)\n\t\tdefer t.Stop()\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\ts.Reset()\n\t\t\tlog.Debug(\"rejecting request without dialing\",\n\t\t\t\t\"remote_peer\", p,\n\t\t\t\t\"error\", ctx.Err())\n\t\t\treturn EventDialRequestCompleted{Error: ctx.Err(), DialDataRequired: true, DialedAddr: dialAddr}\n\t\tcase <-t.C:\n\t\t}\n\t}\n\n\tdialStatus := as.dialBack(ctx, s.Conn().RemotePeer(), dialAddr, nonce)\n\tmsg = pb.Message{\n\t\tMsg: &pb.Message_DialResponse{\n\t\t\tDialResponse: &pb.DialResponse{\n\t\t\t\tStatus:     pb.DialResponse_OK,\n\t\t\t\tDialStatus: dialStatus,\n\t\t\t\tAddrIdx:    uint32(addrIdx),\n\t\t\t},\n\t\t},\n\t}\n\tif err := w.WriteMsg(&msg); err != nil {\n\t\ts.Reset()\n\t\tlog.Debug(\"failed to write response\",\n\t\t\t\"remote_peer\", p,\n\t\t\t\"error\", err)\n\t\treturn EventDialRequestCompleted{\n\t\t\tResponseStatus:   pb.DialResponse_OK,\n\t\t\tDialStatus:       dialStatus,\n\t\t\tError:            fmt.Errorf(\"write failed: %w\", err),\n\t\t\tDialDataRequired: isDialDataRequired,\n\t\t\tDialedAddr:       dialAddr,\n\t\t}\n\t}\n\treturn EventDialRequestCompleted{\n\t\tResponseStatus:   pb.DialResponse_OK,\n\t\tDialStatus:       dialStatus,\n\t\tError:            nil,\n\t\tDialDataRequired: isDialDataRequired,\n\t\tDialedAddr:       dialAddr,\n\t}\n}\n\n// getDialData gets data from the client for dialing the address\nfunc getDialData(w pbio.Writer, s network.Stream, msg *pb.Message, addrIdx int) error {\n\tnumBytes := minHandshakeSizeBytes + rand.Intn(maxHandshakeSizeBytes-minHandshakeSizeBytes)\n\t*msg = pb.Message{\n\t\tMsg: &pb.Message_DialDataRequest{\n\t\t\tDialDataRequest: &pb.DialDataRequest{\n\t\t\t\tAddrIdx:  uint32(addrIdx),\n\t\t\t\tNumBytes: uint64(numBytes),\n\t\t\t},\n\t\t},\n\t}\n\tif err := w.WriteMsg(msg); err != nil {\n\t\treturn fmt.Errorf(\"dial data write: %w\", err)\n\t}\n\t// pbio.Reader that we used so far on this stream is buffered. But at this point\n\t// there is nothing unread on the stream. So it is safe to use the raw stream to\n\t// read, reducing allocations.\n\treturn readDialData(numBytes, s)\n}\n\nfunc readDialData(numBytes int, r io.Reader) error {\n\tmr := &msgReader{R: r, Buf: pool.Get(maxMsgSize)}\n\tdefer pool.Put(mr.Buf)\n\tfor remain := numBytes; remain > 0; {\n\t\tmsg, err := mr.ReadMsg()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"dial data read: %w\", err)\n\t\t}\n\t\t// protobuf format is:\n\t\t// (oneof dialDataResponse:<fieldTag><len varint>)(dial data:<fieldTag><len varint><bytes>)\n\t\tbytesLen := len(msg)\n\t\tbytesLen -= 2 // fieldTag + varint first byte\n\t\tif bytesLen > 127 {\n\t\t\tbytesLen -= 1 // varint second byte\n\t\t}\n\t\tbytesLen -= 2 // second fieldTag + varint first byte\n\t\tif bytesLen > 127 {\n\t\t\tbytesLen -= 1 // varint second byte\n\t\t}\n\t\tif bytesLen > 0 {\n\t\t\tremain -= bytesLen\n\t\t}\n\t\t// Check if the peer is not sending too little data forcing us to just do a lot of compute\n\t\tif bytesLen < 100 && remain > 0 {\n\t\t\treturn fmt.Errorf(\"dial data msg too small: %d\", bytesLen)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (as *server) dialBack(ctx context.Context, p peer.ID, addr ma.Multiaddr, nonce uint64) pb.DialStatus {\n\tctx, cancel := context.WithTimeout(ctx, dialBackDialTimeout)\n\tctx = network.WithForceDirectDial(ctx, \"autonatv2\")\n\tas.dialerHost.Peerstore().AddAddr(p, addr, peerstore.TempAddrTTL)\n\tdefer func() {\n\t\tcancel()\n\t\tas.dialerHost.Network().ClosePeer(p)\n\t\tas.dialerHost.Peerstore().ClearAddrs(p)\n\t\tas.dialerHost.Peerstore().RemovePeer(p)\n\t}()\n\n\terr := as.dialerHost.Connect(ctx, peer.AddrInfo{ID: p})\n\tif err != nil {\n\t\treturn pb.DialStatus_E_DIAL_ERROR\n\t}\n\n\ts, err := as.dialerHost.NewStream(ctx, p, DialBackProtocol)\n\tif err != nil {\n\t\treturn pb.DialStatus_E_DIAL_BACK_ERROR\n\t}\n\n\tdefer s.Close()\n\ts.SetDeadline(as.now().Add(dialBackStreamTimeout))\n\n\tw := pbio.NewDelimitedWriter(s)\n\tif err := w.WriteMsg(&pb.DialBack{Nonce: nonce}); err != nil {\n\t\ts.Reset()\n\t\treturn pb.DialStatus_E_DIAL_BACK_ERROR\n\t}\n\n\t// Since the underlying connection is on a separate dialer, it'll be closed after this\n\t// function returns. Connection close will drop all the queued writes. To ensure message\n\t// delivery, do a CloseWrite and read a byte from the stream. The peer actually sends a\n\t// response of type DialBackResponse but we only care about the fact that the DialBack\n\t// message has reached the peer. So we ignore that message on the read side.\n\ts.CloseWrite()\n\ts.SetDeadline(as.now().Add(5 * time.Second)) // 5 is a magic number\n\tb := make([]byte, 1)                         // Read 1 byte here because 0 len reads are free to return (0, nil) immediately\n\ts.Read(b)\n\n\treturn pb.DialStatus_OK\n}\n\n// rateLimiter implements a sliding window rate limit of requests per minute. It allows 1 concurrent request\n// per peer. It rate limits requests globally, at a peer level and depending on whether it requires dial data.\ntype rateLimiter struct {\n\t// PerPeerRPM is the rate limit per peer\n\tPerPeerRPM int\n\t// RPM is the global rate limit\n\tRPM int\n\t// DialDataRPM is the rate limit for requests that require dial data\n\tDialDataRPM int\n\t// MaxConcurrentRequestsPerPeer is the maximum number of concurrent requests per peer\n\tMaxConcurrentRequestsPerPeer int\n\n\tmu           sync.Mutex\n\tclosed       bool\n\treqs         []entry\n\tpeerReqs     map[peer.ID][]time.Time\n\tdialDataReqs []time.Time\n\t// inProgressReqs tracks in progress requests. This is used to limit multiple\n\t// concurrent requests by the same peer.\n\tinProgressReqs map[peer.ID]int\n\n\tnow func() time.Time // for tests\n}\n\ntype entry struct {\n\tPeerID peer.ID\n\tTime   time.Time\n}\n\nfunc (r *rateLimiter) init() {\n\tif r.peerReqs == nil {\n\t\tr.peerReqs = make(map[peer.ID][]time.Time)\n\t\tr.inProgressReqs = make(map[peer.ID]int)\n\t}\n}\n\nfunc (r *rateLimiter) Accept(p peer.ID) bool {\n\tr.mu.Lock()\n\tdefer r.mu.Unlock()\n\tif r.closed {\n\t\treturn false\n\t}\n\tr.init()\n\tnw := r.now()\n\tr.cleanup(nw)\n\n\tif r.inProgressReqs[p] >= r.MaxConcurrentRequestsPerPeer {\n\t\treturn false\n\t}\n\tif len(r.reqs) >= r.RPM || len(r.peerReqs[p]) >= r.PerPeerRPM {\n\t\treturn false\n\t}\n\n\tr.inProgressReqs[p]++\n\tr.reqs = append(r.reqs, entry{PeerID: p, Time: nw})\n\tr.peerReqs[p] = append(r.peerReqs[p], nw)\n\treturn true\n}\n\nfunc (r *rateLimiter) AcceptDialDataRequest() bool {\n\tr.mu.Lock()\n\tdefer r.mu.Unlock()\n\tif r.closed {\n\t\treturn false\n\t}\n\tr.init()\n\tnw := r.now()\n\tr.cleanup(nw)\n\tif len(r.dialDataReqs) >= r.DialDataRPM {\n\t\treturn false\n\t}\n\tr.dialDataReqs = append(r.dialDataReqs, nw)\n\treturn true\n}\n\n// cleanup removes stale requests.\n//\n// This is fast enough in rate limited cases and the state is small enough to\n// clean up quickly when blocking requests.\nfunc (r *rateLimiter) cleanup(now time.Time) {\n\tidx := len(r.reqs)\n\tfor i, e := range r.reqs {\n\t\tif now.Sub(e.Time) >= time.Minute {\n\t\t\tpi := len(r.peerReqs[e.PeerID])\n\t\t\tfor j, t := range r.peerReqs[e.PeerID] {\n\t\t\t\tif now.Sub(t) < time.Minute {\n\t\t\t\t\tpi = j\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tr.peerReqs[e.PeerID] = r.peerReqs[e.PeerID][pi:]\n\t\t\tif len(r.peerReqs[e.PeerID]) == 0 {\n\t\t\t\tdelete(r.peerReqs, e.PeerID)\n\t\t\t}\n\t\t} else {\n\t\t\tidx = i\n\t\t\tbreak\n\t\t}\n\t}\n\tr.reqs = r.reqs[idx:]\n\n\tidx = len(r.dialDataReqs)\n\tfor i, t := range r.dialDataReqs {\n\t\tif now.Sub(t) < time.Minute {\n\t\t\tidx = i\n\t\t\tbreak\n\t\t}\n\t}\n\tr.dialDataReqs = r.dialDataReqs[idx:]\n}\n\nfunc (r *rateLimiter) CompleteRequest(p peer.ID) {\n\tr.mu.Lock()\n\tdefer r.mu.Unlock()\n\tif r.closed {\n\t\treturn\n\t}\n\tr.inProgressReqs[p]--\n\tif r.inProgressReqs[p] <= 0 {\n\t\tdelete(r.inProgressReqs, p)\n\t\tif r.inProgressReqs[p] < 0 {\n\t\t\tlog.Error(\"BUG: negative in progress requests\",\n\t\t\t\t\"remote_peer\", p)\n\t\t}\n\t}\n}\n\nfunc (r *rateLimiter) Close() {\n\tr.mu.Lock()\n\tdefer r.mu.Unlock()\n\tr.closed = true\n\tr.peerReqs = nil\n\tr.inProgressReqs = nil\n\tr.dialDataReqs = nil\n}\n\n// amplificationAttackPrevention is a dialDataRequestPolicy which requests data when the peer's observed\n// IP address is different from the dial back IP address\nfunc amplificationAttackPrevention(observedAddr, dialAddr ma.Multiaddr) bool {\n\tobservedIP, err := manet.ToIP(observedAddr)\n\tif err != nil {\n\t\treturn true\n\t}\n\tdialIP, err := manet.ToIP(dialAddr) // can be dns addr\n\tif err != nil {\n\t\treturn true\n\t}\n\treturn !observedIP.Equal(dialIP)\n}\n"
  },
  {
    "path": "p2p/protocol/autonatv2/server_test.go",
    "content": "package autonatv2\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"math\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/test\"\n\tbhost \"github.com/libp2p/go-libp2p/p2p/host/blank\"\n\t\"github.com/libp2p/go-libp2p/p2p/net/swarm\"\n\tswarmt \"github.com/libp2p/go-libp2p/p2p/net/swarm/testing\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/autonatv2/pb\"\n\t\"github.com/libp2p/go-msgio/pbio\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/multiformats/go-varint\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc newTestRequests(addrs []ma.Multiaddr, sendDialData bool) (reqs []Request) {\n\treqs = make([]Request, len(addrs))\n\tfor i := range addrs {\n\t\treqs[i] = Request{Addr: addrs[i], SendDialData: sendDialData}\n\t}\n\treturn\n}\n\nfunc TestServerInvalidAddrsRejected(t *testing.T) {\n\tc := newAutoNAT(t, nil, allowPrivateAddrs, withAmplificationAttackPreventionDialWait(0))\n\tdefer c.Close()\n\tdefer c.host.Close()\n\n\tt.Run(\"no transport\", func(t *testing.T) {\n\t\tdialer := bhost.NewBlankHost(swarmt.GenSwarm(t, swarmt.OptDisableQUIC, swarmt.OptDisableTCP))\n\t\tan := newAutoNAT(t, dialer, allowPrivateAddrs)\n\t\tdefer an.Close()\n\t\tdefer an.host.Close()\n\n\t\tidAndWait(t, c, an)\n\n\t\tres, err := c.GetReachability(context.Background(), newTestRequests(c.host.Addrs(), true))\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, Result{AllAddrsRefused: true}, res)\n\t})\n\n\tt.Run(\"black holed addr\", func(t *testing.T) {\n\t\tdialer := bhost.NewBlankHost(swarmt.GenSwarm(\n\t\t\tt, swarmt.WithSwarmOpts(swarm.WithReadOnlyBlackHoleDetector())))\n\t\tan := newAutoNAT(t, dialer)\n\t\tdefer an.Close()\n\t\tdefer an.host.Close()\n\n\t\tidAndWait(t, c, an)\n\n\t\tres, err := c.GetReachability(context.Background(),\n\t\t\t[]Request{{\n\t\t\t\tAddr:         ma.StringCast(\"/ip4/1.2.3.4/udp/1234/quic-v1\"),\n\t\t\t\tSendDialData: true,\n\t\t\t}})\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, Result{AllAddrsRefused: true}, res)\n\t})\n\n\tt.Run(\"private addrs\", func(t *testing.T) {\n\t\tan := newAutoNAT(t, nil)\n\t\tdefer an.Close()\n\t\tdefer an.host.Close()\n\n\t\tidAndWait(t, c, an)\n\n\t\tres, err := c.GetReachability(context.Background(), newTestRequests(c.host.Addrs(), true))\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, Result{AllAddrsRefused: true}, res)\n\t})\n\n\tt.Run(\"relay addrs\", func(t *testing.T) {\n\t\tan := newAutoNAT(t, nil)\n\t\tdefer an.Close()\n\t\tdefer an.host.Close()\n\n\t\tidAndWait(t, c, an)\n\n\t\tres, err := c.GetReachability(context.Background(), newTestRequests(\n\t\t\t[]ma.Multiaddr{ma.StringCast(fmt.Sprintf(\"/ip4/1.2.3.4/tcp/1/p2p/%s/p2p-circuit/p2p/%s\", c.host.ID(), c.srv.dialerHost.ID()))}, true))\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, Result{AllAddrsRefused: true}, res)\n\t})\n\n\tt.Run(\"no addr\", func(t *testing.T) {\n\t\t_, err := c.GetReachability(context.Background(), nil)\n\t\trequire.Error(t, err)\n\t})\n\n\tt.Run(\"too many address\", func(t *testing.T) {\n\t\tdialer := bhost.NewBlankHost(swarmt.GenSwarm(t, swarmt.OptDisableTCP))\n\t\tan := newAutoNAT(t, dialer, allowPrivateAddrs)\n\t\tdefer an.Close()\n\t\tdefer an.host.Close()\n\n\t\tvar addrs []ma.Multiaddr\n\t\tfor i := range 100 {\n\t\t\taddrs = append(addrs, ma.StringCast(fmt.Sprintf(\"/ip4/127.0.0.1/tcp/%d\", 2000+i)))\n\t\t}\n\t\taddrs = append(addrs, c.host.Addrs()...)\n\t\t// The dial should still fail because we have too many addresses that the server cannot dial\n\t\tidAndWait(t, c, an)\n\n\t\tres, err := c.GetReachability(context.Background(), newTestRequests(addrs, true))\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, Result{AllAddrsRefused: true}, res)\n\t})\n\n\tt.Run(\"msg too large\", func(t *testing.T) {\n\t\tdialer := bhost.NewBlankHost(swarmt.GenSwarm(t, swarmt.OptDisableTCP))\n\t\tan := newAutoNAT(t, dialer, allowPrivateAddrs)\n\t\tdefer an.Close()\n\t\tdefer an.host.Close()\n\n\t\tvar addrs []ma.Multiaddr\n\t\tfor i := range 10000 {\n\t\t\taddrs = append(addrs, ma.StringCast(fmt.Sprintf(\"/ip4/127.0.0.1/tcp/%d\", 2000+i)))\n\t\t}\n\t\taddrs = append(addrs, c.host.Addrs()...)\n\t\t// The dial should still fail because we have too many addresses that the server cannot dial\n\t\tidAndWait(t, c, an)\n\n\t\tres, err := c.GetReachability(context.Background(), newTestRequests(addrs, true))\n\t\trequire.ErrorIs(t, err, network.ErrReset)\n\t\trequire.Equal(t, Result{}, res)\n\t})\n}\n\nfunc TestServerDataRequest(t *testing.T) {\n\t// server will skip all tcp addresses\n\tdialer := bhost.NewBlankHost(swarmt.GenSwarm(t, swarmt.OptDisableTCP))\n\t// ask for dial data for quic address\n\tan := newAutoNAT(t, dialer, allowPrivateAddrs, withDataRequestPolicy(\n\t\tfunc(_, dialAddr ma.Multiaddr) bool {\n\t\t\tif _, err := dialAddr.ValueForProtocol(ma.P_QUIC_V1); err == nil {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn false\n\t\t}),\n\t\tWithServerRateLimit(10, 10, 10, 2),\n\t\twithAmplificationAttackPreventionDialWait(0),\n\t)\n\tdefer an.Close()\n\tdefer an.host.Close()\n\n\tc := newAutoNAT(t, nil, allowPrivateAddrs)\n\tdefer c.Close()\n\tdefer c.host.Close()\n\n\tidAndWait(t, c, an)\n\n\tvar quicAddr, tcpAddr ma.Multiaddr\n\tfor _, a := range c.host.Addrs() {\n\t\tif _, err := a.ValueForProtocol(ma.P_QUIC_V1); err == nil {\n\t\t\tquicAddr = a\n\t\t} else if _, err := a.ValueForProtocol(ma.P_TCP); err == nil {\n\t\t\ttcpAddr = a\n\t\t}\n\t}\n\n\t_, err := c.GetReachability(context.Background(), []Request{{Addr: tcpAddr, SendDialData: true}, {Addr: quicAddr}})\n\trequire.Error(t, err)\n\n\tres, err := c.GetReachability(context.Background(), []Request{{Addr: quicAddr, SendDialData: true}, {Addr: tcpAddr}})\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, Result{\n\t\tAddr:         quicAddr,\n\t\tIdx:          0,\n\t\tReachability: network.ReachabilityPublic,\n\t}, res)\n\n\t// Small messages should be rejected for dial data\n\tc.cli.dialData = c.cli.dialData[:10]\n\t_, err = c.GetReachability(context.Background(), []Request{{Addr: quicAddr, SendDialData: true}, {Addr: tcpAddr}})\n\trequire.Error(t, err)\n}\n\nfunc TestServerMaxConcurrentRequestsPerPeer(t *testing.T) {\n\tconst concurrentRequests = 5\n\n\tstallChan := make(chan struct{})\n\tan := newAutoNAT(t, nil, allowPrivateAddrs, withDataRequestPolicy(\n\t\t// stall all allowed requests\n\t\tfunc(_, _ ma.Multiaddr) bool {\n\t\t\t<-stallChan\n\t\t\treturn true\n\t\t}),\n\t\tWithServerRateLimit(10, 10, 10, concurrentRequests),\n\t\twithAmplificationAttackPreventionDialWait(0),\n\t)\n\tdefer an.Close()\n\tdefer an.host.Close()\n\n\t// server will skip all tcp addresses\n\tdialer := bhost.NewBlankHost(swarmt.GenSwarm(t, swarmt.OptDisableTCP))\n\tc := newAutoNAT(t, dialer, allowPrivateAddrs)\n\tdefer c.Close()\n\tdefer c.host.Close()\n\n\tidAndWait(t, c, an)\n\n\terrChan := make(chan error)\n\tconst n = 10\n\t// num concurrentRequests will stall and n will fail\n\tfor range concurrentRequests + n {\n\t\tgo func() {\n\t\t\t_, err := c.GetReachability(context.Background(), []Request{{Addr: c.host.Addrs()[0], SendDialData: false}})\n\t\t\terrChan <- err\n\t\t}()\n\t}\n\n\t// check N failures\n\tfor i := range n {\n\t\tselect {\n\t\tcase err := <-errChan:\n\t\t\trequire.Error(t, err)\n\t\t\tif !strings.Contains(err.Error(), \"stream reset\") && !strings.Contains(err.Error(), \"E_REQUEST_REJECTED\") {\n\t\t\t\tt.Fatalf(\"invalid error: %s expected: stream reset or E_REQUEST_REJECTED\", err)\n\t\t\t}\n\t\tcase <-time.After(10 * time.Second):\n\t\t\tt.Fatalf(\"expected %d errors: got: %d\", n, i)\n\t\t}\n\t}\n\n\tclose(stallChan) // complete stalled requests\n\t// check concurrentRequests failures, as we won't send dial data\n\tfor i := range concurrentRequests {\n\t\tselect {\n\t\tcase err := <-errChan:\n\t\t\trequire.Error(t, err)\n\t\tcase <-time.After(5 * time.Second):\n\t\t\tt.Fatalf(\"expected %d errors: got: %d\", concurrentRequests, i)\n\t\t}\n\t}\n\tselect {\n\tcase err := <-errChan:\n\t\tt.Fatalf(\"expected no more errors: got: %v\", err)\n\tdefault:\n\t}\n}\n\nfunc TestServerDataRequestJitter(t *testing.T) {\n\t// server will skip all tcp addresses\n\tdialer := bhost.NewBlankHost(swarmt.GenSwarm(t, swarmt.OptDisableTCP))\n\t// ask for dial data for quic address\n\tan := newAutoNAT(t, dialer, allowPrivateAddrs, withDataRequestPolicy(\n\t\tfunc(_, dialAddr ma.Multiaddr) bool {\n\t\t\tif _, err := dialAddr.ValueForProtocol(ma.P_QUIC_V1); err == nil {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn false\n\t\t}),\n\t\tWithServerRateLimit(10, 10, 10, 2),\n\t\twithAmplificationAttackPreventionDialWait(5*time.Second),\n\t)\n\tdefer an.Close()\n\tdefer an.host.Close()\n\n\tc := newAutoNAT(t, nil, allowPrivateAddrs)\n\tdefer c.Close()\n\tdefer c.host.Close()\n\n\tidAndWait(t, c, an)\n\n\tvar quicAddr, tcpAddr ma.Multiaddr\n\tfor _, a := range c.host.Addrs() {\n\t\tif _, err := a.ValueForProtocol(ma.P_QUIC_V1); err == nil {\n\t\t\tquicAddr = a\n\t\t} else if _, err := a.ValueForProtocol(ma.P_TCP); err == nil {\n\t\t\ttcpAddr = a\n\t\t}\n\t}\n\n\tfor range 10 {\n\t\tst := time.Now()\n\t\tres, err := c.GetReachability(context.Background(), []Request{{Addr: quicAddr, SendDialData: true}, {Addr: tcpAddr}})\n\t\ttook := time.Since(st)\n\t\trequire.NoError(t, err)\n\n\t\trequire.Equal(t, Result{\n\t\t\tAddr:         quicAddr,\n\t\t\tIdx:          0,\n\t\t\tReachability: network.ReachabilityPublic,\n\t\t}, res)\n\t\tif took > 500*time.Millisecond {\n\t\t\treturn\n\t\t}\n\t}\n\tt.Fatalf(\"expected server to delay at least 1 dial\")\n}\n\nfunc TestServerDial(t *testing.T) {\n\tan := newAutoNAT(t, nil, WithServerRateLimit(10, 10, 10, 2), allowPrivateAddrs)\n\tdefer an.Close()\n\tdefer an.host.Close()\n\n\tc := newAutoNAT(t, nil, allowPrivateAddrs)\n\tdefer c.Close()\n\tdefer c.host.Close()\n\n\tidAndWait(t, c, an)\n\n\tunreachableAddr := ma.StringCast(\"/ip4/1.2.3.4/tcp/2\")\n\thostAddrs := c.host.Addrs()\n\n\tt.Run(\"unreachable addr\", func(t *testing.T) {\n\t\tres, err := c.GetReachability(context.Background(),\n\t\t\tappend([]Request{{Addr: unreachableAddr, SendDialData: true}}, newTestRequests(hostAddrs, false)...))\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, Result{\n\t\t\tAddr:         unreachableAddr,\n\t\t\tIdx:          0,\n\t\t\tReachability: network.ReachabilityPrivate,\n\t\t}, res)\n\t})\n\n\tt.Run(\"reachable addr\", func(t *testing.T) {\n\t\tres, err := c.GetReachability(context.Background(), newTestRequests(c.host.Addrs(), false))\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, Result{\n\t\t\tAddr:         hostAddrs[0],\n\t\t\tIdx:          0,\n\t\t\tReachability: network.ReachabilityPublic,\n\t\t}, res)\n\t\tfor _, addr := range c.host.Addrs() {\n\t\t\tres, err := c.GetReachability(context.Background(), newTestRequests([]ma.Multiaddr{addr}, false))\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, Result{\n\t\t\t\tAddr:         addr,\n\t\t\t\tIdx:          0,\n\t\t\t\tReachability: network.ReachabilityPublic,\n\t\t\t}, res)\n\t\t}\n\t})\n\n\tt.Run(\"dialback error\", func(t *testing.T) {\n\t\tc.host.RemoveStreamHandler(DialBackProtocol)\n\t\tres, err := c.GetReachability(context.Background(), newTestRequests(c.host.Addrs(), false))\n\t\trequire.ErrorContains(t, err, \"dial-back stream error\")\n\t\trequire.Equal(t, Result{}, res)\n\t})\n}\n\nfunc TestRateLimiter(t *testing.T) {\n\tcl := test.NewMockClock()\n\tr := rateLimiter{RPM: 3, PerPeerRPM: 2, DialDataRPM: 1, now: cl.Now, MaxConcurrentRequestsPerPeer: 1}\n\n\trequire.True(t, r.Accept(\"peer1\"))\n\n\tcl.AdvanceBy(10 * time.Second)\n\trequire.False(t, r.Accept(\"peer1\")) // first request is still active\n\tr.CompleteRequest(\"peer1\")\n\n\trequire.True(t, r.Accept(\"peer1\"))\n\tr.CompleteRequest(\"peer1\")\n\n\tcl.AdvanceBy(10 * time.Second)\n\trequire.False(t, r.Accept(\"peer1\"))\n\n\tcl.AdvanceBy(10 * time.Second)\n\trequire.True(t, r.Accept(\"peer2\"))\n\tr.CompleteRequest(\"peer2\")\n\n\tcl.AdvanceBy(10 * time.Second)\n\trequire.False(t, r.Accept(\"peer3\"))\n\n\tcl.AdvanceBy(21 * time.Second) // first request expired\n\trequire.True(t, r.Accept(\"peer1\"))\n\tr.CompleteRequest(\"peer1\")\n\n\tcl.AdvanceBy(10 * time.Second)\n\trequire.True(t, r.Accept(\"peer3\"))\n\tr.CompleteRequest(\"peer3\")\n\n\tcl.AdvanceBy(50 * time.Second)\n\trequire.True(t, r.Accept(\"peer3\"))\n\tr.CompleteRequest(\"peer3\")\n\n\tcl.AdvanceBy(1 * time.Second)\n\trequire.False(t, r.Accept(\"peer3\"))\n\n\tcl.AdvanceBy(10 * time.Second)\n\trequire.True(t, r.Accept(\"peer3\"))\n}\n\nfunc TestRateLimiterConcurrentRequests(t *testing.T) {\n\tconst N = 5\n\tconst Peers = 5\n\tfor concurrentRequests := 1; concurrentRequests <= N; concurrentRequests++ {\n\t\tcl := test.NewMockClock()\n\t\tr := rateLimiter{RPM: 10 * Peers * N, PerPeerRPM: 10 * Peers * N, DialDataRPM: 10 * Peers * N, now: cl.Now, MaxConcurrentRequestsPerPeer: concurrentRequests}\n\t\tfor p := range Peers {\n\t\t\tfor i := 0; i < concurrentRequests; i++ {\n\t\t\t\trequire.True(t, r.Accept(peer.ID(fmt.Sprintf(\"peer-%d\", p))))\n\t\t\t}\n\t\t\trequire.False(t, r.Accept(peer.ID(fmt.Sprintf(\"peer-%d\", p))))\n\t\t\t// Now complete the requests\n\t\t\tfor i := 0; i < concurrentRequests; i++ {\n\t\t\t\tr.CompleteRequest(peer.ID(fmt.Sprintf(\"peer-%d\", p)))\n\t\t\t}\n\t\t\t// Now we should be able to accept new requests\n\t\t\tfor i := 0; i < concurrentRequests; i++ {\n\t\t\t\trequire.True(t, r.Accept(peer.ID(fmt.Sprintf(\"peer-%d\", p))))\n\t\t\t}\n\t\t\trequire.False(t, r.Accept(peer.ID(fmt.Sprintf(\"peer-%d\", p))))\n\t\t}\n\t}\n}\n\nfunc TestRateLimiterStress(t *testing.T) {\n\tcl := test.NewMockClock()\n\tfor i := range 10 {\n\t\tr := rateLimiter{RPM: 20 + i, PerPeerRPM: 10 + i, DialDataRPM: i, MaxConcurrentRequestsPerPeer: 1, now: cl.Now}\n\n\t\tpeers := make([]peer.ID, 10+i)\n\t\tfor i := range peers {\n\t\t\tpeers[i] = peer.ID(fmt.Sprintf(\"peer-%d\", i))\n\t\t}\n\t\tpeerSuccesses := make([]atomic.Int64, len(peers))\n\t\tvar success, dialDataSuccesses atomic.Int64\n\t\tvar wg sync.WaitGroup\n\t\tfor range 5 {\n\t\t\twg.Add(1)\n\t\t\tgo func() {\n\t\t\t\tdefer wg.Done()\n\t\t\t\tfor range 2 * 60 {\n\t\t\t\t\tfor j, p := range peers {\n\t\t\t\t\t\tif r.Accept(p) {\n\t\t\t\t\t\t\tsuccess.Add(1)\n\t\t\t\t\t\t\tpeerSuccesses[j].Add(1)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif r.AcceptDialDataRequest() {\n\t\t\t\t\t\t\tdialDataSuccesses.Add(1)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tr.CompleteRequest(p)\n\t\t\t\t\t}\n\t\t\t\t\tcl.AdvanceBy(time.Second)\n\t\t\t\t}\n\t\t\t}()\n\t\t}\n\t\twg.Wait()\n\t\tif int(success.Load()) > 10*r.RPM || int(success.Load()) < 9*r.RPM {\n\t\t\tt.Fatalf(\"invalid successes, %d, expected %d-%d\", success.Load(), 9*r.RPM, 10*r.RPM)\n\t\t}\n\t\tif int(dialDataSuccesses.Load()) > 10*r.DialDataRPM || int(dialDataSuccesses.Load()) < 9*r.DialDataRPM {\n\t\t\tt.Fatalf(\"invalid dial data successes, %d expected %d-%d\", dialDataSuccesses.Load(), 9*r.DialDataRPM, 10*r.DialDataRPM)\n\t\t}\n\t\tfor i := range peerSuccesses {\n\t\t\t// We cannot check the lower bound because some peers would be hitting the global rpm limit\n\t\t\tif int(peerSuccesses[i].Load()) > 10*r.PerPeerRPM {\n\t\t\t\tt.Fatalf(\"too many per peer successes, PerPeerRPM=%d\", r.PerPeerRPM)\n\t\t\t}\n\t\t}\n\t\tcl.AdvanceBy(1 * time.Minute)\n\t\trequire.True(t, r.Accept(peers[0]))\n\t\t// Assert lengths to check that we are cleaning up correctly\n\t\trequire.Equal(t, len(r.reqs), 1)\n\t\trequire.Equal(t, len(r.peerReqs), 1)\n\t\trequire.Equal(t, len(r.peerReqs[peers[0]]), 1)\n\t\trequire.Equal(t, len(r.dialDataReqs), 0)\n\t\trequire.Equal(t, len(r.inProgressReqs), 1)\n\t}\n}\n\nfunc TestReadDialData(t *testing.T) {\n\tfor N := 30_000; N < 30_010; N++ {\n\t\tfor msgSize := 100; msgSize < 256; msgSize++ {\n\t\t\tr, w := io.Pipe()\n\t\t\tmsg := &pb.Message{}\n\t\t\tvar wg sync.WaitGroup\n\t\t\twg.Add(1)\n\t\t\tgo func() {\n\t\t\t\tdefer wg.Done()\n\t\t\t\tmw := pbio.NewDelimitedWriter(w)\n\t\t\t\terr := sendDialData(make([]byte, msgSize), N, mw, msg)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Error(err)\n\t\t\t\t}\n\t\t\t\tmw.Close()\n\t\t\t}()\n\t\t\terr := readDialData(N, r)\n\t\t\trequire.NoError(t, err)\n\t\t\twg.Wait()\n\t\t}\n\n\t\tfor msgSize := 1000; msgSize < 1256; msgSize++ {\n\t\t\tr, w := io.Pipe()\n\t\t\tmsg := &pb.Message{}\n\t\t\tvar wg sync.WaitGroup\n\t\t\twg.Add(1)\n\t\t\tgo func() {\n\t\t\t\tdefer wg.Done()\n\t\t\t\tmw := pbio.NewDelimitedWriter(w)\n\t\t\t\terr := sendDialData(make([]byte, msgSize), N, mw, msg)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Error(err)\n\t\t\t\t}\n\t\t\t\tmw.Close()\n\t\t\t}()\n\t\t\terr := readDialData(N, r)\n\t\t\trequire.NoError(t, err)\n\t\t\twg.Wait()\n\t\t}\n\t}\n}\n\nfunc TestServerDataRequestWithAmplificationAttackPrevention(t *testing.T) {\n\t// server will skip all tcp addresses\n\tdialer := bhost.NewBlankHost(swarmt.GenSwarm(t, swarmt.OptDisableTCP))\n\t// ask for dial data for quic address\n\tan := newAutoNAT(t, dialer, allowPrivateAddrs,\n\t\tWithServerRateLimit(10, 10, 10, 2),\n\t\twithAmplificationAttackPreventionDialWait(0),\n\t)\n\tdefer an.Close()\n\tdefer an.host.Close()\n\n\tc := newAutoNAT(t, nil, allowPrivateAddrs)\n\tdefer c.Close()\n\tdefer c.host.Close()\n\n\tidAndWait(t, c, an)\n\n\terr := c.host.Network().Listen(ma.StringCast(\"/ip6/::1/udp/0/quic-v1\"))\n\tif err != nil {\n\t\t// machine doesn't have ipv6\n\t\tt.Skip(\"skipping test because machine doesn't have ipv6\")\n\t}\n\n\tvar quicv4Addr ma.Multiaddr\n\tvar quicv6Addr ma.Multiaddr\n\tfor _, a := range c.host.Addrs() {\n\t\tif _, err := a.ValueForProtocol(ma.P_QUIC_V1); err == nil {\n\t\t\tif _, err := a.ValueForProtocol(ma.P_IP4); err == nil {\n\t\t\t\tquicv4Addr = a\n\t\t\t} else {\n\t\t\t\tquicv6Addr = a\n\t\t\t}\n\t\t}\n\t}\n\tres, err := c.GetReachability(context.Background(), []Request{{Addr: quicv4Addr, SendDialData: false}})\n\trequire.NoError(t, err)\n\trequire.Equal(t, Result{\n\t\tAddr:         quicv4Addr,\n\t\tIdx:          0,\n\t\tReachability: network.ReachabilityPublic,\n\t}, res)\n\n\t// ipv6 address should require dial data\n\t_, err = c.GetReachability(context.Background(), []Request{{Addr: quicv6Addr, SendDialData: false}})\n\trequire.Error(t, err)\n\trequire.ErrorContains(t, err, \"invalid dial data request\")\n\trequire.ErrorContains(t, err, \"low priority addr\")\n\n\t// ipv6 address should work fine with dial data\n\tres, err = c.GetReachability(context.Background(), []Request{{Addr: quicv6Addr, SendDialData: true}})\n\trequire.NoError(t, err)\n\trequire.Equal(t, Result{\n\t\tAddr:         quicv6Addr,\n\t\tIdx:          0,\n\t\tReachability: network.ReachabilityPublic,\n\t}, res)\n}\n\nfunc TestDefaultAmplificationAttackPrevention(t *testing.T) {\n\tq1 := ma.StringCast(\"/ip4/1.2.3.4/udp/1234/quic-v1\")\n\tq2 := ma.StringCast(\"/ip4/1.2.3.4/udp/1235/quic-v1\")\n\tt1 := ma.StringCast(\"/ip4/1.2.3.4/tcp/1234\")\n\n\trequire.False(t, amplificationAttackPrevention(q1, q1))\n\trequire.False(t, amplificationAttackPrevention(q1, q2))\n\trequire.False(t, amplificationAttackPrevention(q1, t1))\n\n\tt2 := ma.StringCast(\"/ip4/1.1.1.1/tcp/1235\") // different IP\n\trequire.True(t, amplificationAttackPrevention(q2, t2))\n\n\t// always ask dial data for dns addrs\n\td1 := ma.StringCast(\"/dns/localhost/udp/1/quic-v1\")\n\td2 := ma.StringCast(\"/dnsaddr/libp2p.io/tcp/1\")\n\trequire.True(t, amplificationAttackPrevention(d1, t1))\n\trequire.True(t, amplificationAttackPrevention(d2, t1))\n\n}\n\nfunc FuzzServerDialRequest(f *testing.F) {\n\ta := newAutoNAT(f, nil, allowPrivateAddrs, WithServerRateLimit(math.MaxInt32, math.MaxInt32, math.MaxInt32, 2))\n\tc := newAutoNAT(f, nil)\n\tidAndWait(f, c, a)\n\t// reduce the streamTimeout before running this. TODO: fix this\n\tf.Fuzz(func(t *testing.T, data []byte) {\n\t\ts, err := c.host.NewStream(context.Background(), a.host.ID(), DialProtocol)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\ts.SetDeadline(time.Now().Add(10 * time.Second))\n\t\ts.Write(data)\n\t\tbuf := make([]byte, 64)\n\t\ts.Read(buf) // We only care that server didn't panic\n\t\ts, err = c.host.NewStream(context.Background(), a.host.ID(), DialProtocol)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tn := varint.PutUvarint(buf, uint64(len(data)))\n\t\ts.SetDeadline(time.Now().Add(10 * time.Second))\n\t\ts.Write(buf[:n])\n\t\ts.Write(data)\n\t\ts.Read(buf) // We only care that server didn't panic\n\t\ts.Reset()\n\t})\n}\n\nfunc FuzzReadDialData(f *testing.F) {\n\tf.Fuzz(func(_ *testing.T, numBytes int, data []byte) {\n\t\treadDialData(numBytes, bytes.NewReader(data))\n\t})\n}\n\nfunc BenchmarkDialData(b *testing.B) {\n\tb.ReportAllocs()\n\tconst N = 100_000\n\tstreamBuffer := make([]byte, 2*N)\n\tbuf := bytes.NewBuffer(streamBuffer[:0])\n\tdialData := make([]byte, 4000)\n\tmsg := &pb.Message{}\n\tw := pbio.NewDelimitedWriter(buf)\n\terr := sendDialData(dialData, N, w, msg)\n\trequire.NoError(b, err)\n\tdialDataBuf := buf.Bytes()\n\tfor i := 0; i < b.N; i++ {\n\t\terr = readDialData(N, bytes.NewReader(dialDataBuf))\n\t\trequire.NoError(b, err)\n\t}\n}\n"
  },
  {
    "path": "p2p/protocol/circuitv2/client/client.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"sync\"\n\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/transport\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/proto\"\n\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n)\n\nvar log = logging.Logger(\"p2p-circuit\")\n\n// Client implements the client-side of the p2p-circuit/v2 protocol:\n// - it implements dialing through v2 relays\n// - it listens for incoming connections through v2 relays.\n//\n// For backwards compatibility with v1 relays and older nodes, the client will\n// also accept relay connections through v1 relays and fallback dial peers using p2p-circuit/v1.\n// This allows us to use the v2 code as drop in replacement for v1 in a host without breaking\n// existing code and interoperability with older nodes.\ntype Client struct {\n\tctx       context.Context\n\tctxCancel context.CancelFunc\n\thost      host.Host\n\tupgrader  transport.Upgrader\n\n\tincoming chan accept\n\n\tmx          sync.Mutex\n\tactiveDials map[peer.ID]*completion\n\thopCount    map[peer.ID]int\n}\n\nvar _ io.Closer = &Client{}\nvar _ transport.Transport = &Client{}\n\ntype accept struct {\n\tconn          *Conn\n\twriteResponse func() error\n}\n\ntype completion struct {\n\tch    chan struct{}\n\trelay peer.ID\n\terr   error\n}\n\n// New constructs a new p2p-circuit/v2 client, attached to the given host and using the given\n// upgrader to perform connection upgrades.\nfunc New(h host.Host, upgrader transport.Upgrader) (*Client, error) {\n\tcl := &Client{\n\t\thost:        h,\n\t\tupgrader:    upgrader,\n\t\tincoming:    make(chan accept),\n\t\tactiveDials: make(map[peer.ID]*completion),\n\t\thopCount:    make(map[peer.ID]int),\n\t}\n\tcl.ctx, cl.ctxCancel = context.WithCancel(context.Background())\n\treturn cl, nil\n}\n\n// Start registers the circuit (client) protocol stream handlers\nfunc (c *Client) Start() {\n\tc.host.SetStreamHandler(proto.ProtoIDv2Stop, c.handleStreamV2)\n}\n\nfunc (c *Client) Close() error {\n\tc.ctxCancel()\n\tc.host.RemoveStreamHandler(proto.ProtoIDv2Stop)\n\treturn nil\n}\n"
  },
  {
    "path": "p2p/protocol/circuitv2/client/conn.go",
    "content": "package client\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\ttpt \"github.com/libp2p/go-libp2p/core/transport\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\n// HopTagWeight is the connection manager weight for connections carrying relay hop streams\nvar HopTagWeight = 5\n\ntype statLimitDuration struct{}\ntype statLimitData struct{}\n\nvar (\n\tStatLimitDuration = statLimitDuration{}\n\tStatLimitData     = statLimitData{}\n)\n\ntype Conn struct {\n\tstream network.Stream\n\tremote peer.AddrInfo\n\tstat   network.ConnStats\n\n\tclient *Client\n}\n\ntype NetAddr struct {\n\tRelay  string\n\tRemote string\n}\n\nvar _ net.Addr = (*NetAddr)(nil)\n\nfunc (n *NetAddr) Network() string {\n\treturn \"libp2p-circuit-relay\"\n}\n\nfunc (n *NetAddr) String() string {\n\treturn fmt.Sprintf(\"relay[%s-%s]\", n.Remote, n.Relay)\n}\n\n// Conn interface\nvar _ manet.Conn = (*Conn)(nil)\n\nfunc (c *Conn) Close() error {\n\tc.untagHop()\n\treturn c.stream.Reset()\n}\n\nfunc (c *Conn) Read(buf []byte) (int, error) {\n\treturn c.stream.Read(buf)\n}\n\nfunc (c *Conn) Write(buf []byte) (int, error) {\n\treturn c.stream.Write(buf)\n}\n\nfunc (c *Conn) SetDeadline(t time.Time) error {\n\treturn c.stream.SetDeadline(t)\n}\n\nfunc (c *Conn) SetReadDeadline(t time.Time) error {\n\treturn c.stream.SetReadDeadline(t)\n}\n\nfunc (c *Conn) SetWriteDeadline(t time.Time) error {\n\treturn c.stream.SetWriteDeadline(t)\n}\n\n// TODO: is it okay to cast c.Conn().RemotePeer() into a multiaddr? might be \"user input\"\nfunc (c *Conn) RemoteMultiaddr() ma.Multiaddr {\n\t// TODO: We should be able to do this directly without converting to/from a string.\n\trelayAddr, err := ma.NewComponent(\n\t\tma.ProtocolWithCode(ma.P_P2P).Name,\n\t\tc.stream.Conn().RemotePeer().String(),\n\t)\n\tif err != nil {\n\t\tlog.Error(\"failed to create relay address:\", \"err\", err)\n\t\treturn ma.Join(c.stream.Conn().RemoteMultiaddr(), circuitAddr)\n\t}\n\treturn ma.Join(c.stream.Conn().RemoteMultiaddr(), relayAddr.Multiaddr(), circuitAddr)\n}\n\nfunc (c *Conn) LocalMultiaddr() ma.Multiaddr {\n\treturn c.stream.Conn().LocalMultiaddr()\n}\n\nfunc (c *Conn) LocalAddr() net.Addr {\n\tna, err := manet.ToNetAddr(c.stream.Conn().LocalMultiaddr())\n\tif err != nil {\n\t\tlog.Error(\"failed to convert local multiaddr to net addr:\", \"err\", err)\n\t\treturn nil\n\t}\n\treturn na\n}\n\nfunc (c *Conn) RemoteAddr() net.Addr {\n\treturn &NetAddr{\n\t\tRelay:  c.stream.Conn().RemotePeer().String(),\n\t\tRemote: c.remote.ID.String(),\n\t}\n}\n\n// ConnStat interface\nvar _ network.ConnStat = (*Conn)(nil)\n\nfunc (c *Conn) Stat() network.ConnStats {\n\treturn c.stat\n}\n\n// tagHop tags the underlying relay connection so that it can be (somewhat) protected from the\n// connection manager as it is an important connection that proxies other connections.\n// This is handled here so that the user code doesnt need to bother with this and avoid\n// clown shoes situations where a high value peer connection is behind a relayed connection and it is\n// implicitly because the connection manager closed the underlying relay connection.\nfunc (c *Conn) tagHop() {\n\tc.client.mx.Lock()\n\tdefer c.client.mx.Unlock()\n\n\tp := c.stream.Conn().RemotePeer()\n\tc.client.hopCount[p]++\n\tif c.client.hopCount[p] == 1 {\n\t\tc.client.host.ConnManager().TagPeer(p, \"relay-hop-stream\", HopTagWeight)\n\t}\n}\n\n// untagHop removes the relay-hop-stream tag if necessary; it is invoked when a relayed connection\n// is closed.\nfunc (c *Conn) untagHop() {\n\tc.client.mx.Lock()\n\tdefer c.client.mx.Unlock()\n\n\tp := c.stream.Conn().RemotePeer()\n\tc.client.hopCount[p]--\n\tif c.client.hopCount[p] == 0 {\n\t\tc.client.host.ConnManager().UntagPeer(p, \"relay-hop-stream\")\n\t\tdelete(c.client.hopCount, p)\n\t}\n}\n\ntype capableConnWithStat interface {\n\ttpt.CapableConn\n\tnetwork.ConnStat\n}\n\ntype capableConn struct {\n\tcapableConnWithStat\n}\n\nvar transportName = ma.ProtocolWithCode(ma.P_CIRCUIT).Name\n\nfunc (c capableConn) ConnState() network.ConnectionState {\n\treturn network.ConnectionState{\n\t\tTransport: transportName,\n\t}\n}\n"
  },
  {
    "path": "p2p/protocol/circuitv2/client/dial.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\tpbv2 \"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/pb\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/proto\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/util\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\nconst maxMessageSize = 4096\n\nvar DialTimeout = time.Minute\nvar DialRelayTimeout = 5 * time.Second\n\n// relay protocol errors; used for signalling deduplication\ntype relayError struct {\n\terr string\n}\n\nfunc (e relayError) Error() string {\n\treturn e.err\n}\n\nfunc newRelayError(t string, args ...any) error {\n\treturn relayError{err: fmt.Sprintf(t, args...)}\n}\n\nfunc isRelayError(err error) bool {\n\t_, ok := err.(relayError)\n\treturn ok\n}\n\n// dialer\nfunc (c *Client) dial(ctx context.Context, a ma.Multiaddr, p peer.ID) (*Conn, error) {\n\t// split /a/p2p-circuit/b into (/a, /p2p-circuit/b)\n\trelayaddr, destaddr := ma.SplitFunc(a, func(c ma.Component) bool {\n\t\treturn c.Protocol().Code == ma.P_CIRCUIT\n\t})\n\n\t// If the address contained no /p2p-circuit part, the second part is nil.\n\tif destaddr == nil {\n\t\treturn nil, fmt.Errorf(\"%s is not a relay address\", a)\n\t}\n\n\tif relayaddr == nil {\n\t\treturn nil, fmt.Errorf(\"can't dial a p2p-circuit without specifying a relay: %s\", a)\n\t}\n\n\tdinfo := peer.AddrInfo{ID: p}\n\n\t// Strip the /p2p-circuit prefix from the destaddr so that we can pass the destination address\n\t// (if present) for active relays\n\t_, destaddr = ma.SplitFirst(destaddr)\n\tif destaddr != nil {\n\t\tdinfo.Addrs = append(dinfo.Addrs, destaddr)\n\t}\n\n\trinfo, err := peer.AddrInfoFromP2pAddr(relayaddr)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error parsing relay multiaddr '%s': %w\", relayaddr, err)\n\t}\n\n\t// deduplicate active relay dials to the same peer\nretry:\n\tc.mx.Lock()\n\tdedup, active := c.activeDials[p]\n\tif !active {\n\t\tdedup = &completion{ch: make(chan struct{}), relay: rinfo.ID}\n\t\tc.activeDials[p] = dedup\n\t}\n\tc.mx.Unlock()\n\n\tif active {\n\t\tselect {\n\t\tcase <-dedup.ch:\n\t\t\tif dedup.err != nil {\n\t\t\t\tif dedup.relay != rinfo.ID {\n\t\t\t\t\t// different relay, retry\n\t\t\t\t\tgoto retry\n\t\t\t\t}\n\n\t\t\t\tif !isRelayError(dedup.err) {\n\t\t\t\t\t// not a relay protocol error, retry\n\t\t\t\t\tgoto retry\n\t\t\t\t}\n\n\t\t\t\t// don't try the same relay if it failed to connect with a protocol error\n\t\t\t\treturn nil, fmt.Errorf(\"concurrent active dial through the same relay failed with a protocol error\")\n\t\t\t}\n\n\t\t\treturn nil, fmt.Errorf(\"concurrent active dial succeeded\")\n\n\t\tcase <-ctx.Done():\n\t\t\treturn nil, ctx.Err()\n\t\t}\n\t}\n\n\tconn, err := c.dialPeer(ctx, *rinfo, dinfo)\n\n\tc.mx.Lock()\n\tdedup.err = err\n\tclose(dedup.ch)\n\tdelete(c.activeDials, p)\n\tc.mx.Unlock()\n\n\treturn conn, err\n}\n\nfunc (c *Client) dialPeer(ctx context.Context, relay, dest peer.AddrInfo) (*Conn, error) {\n\tlog.Debug(\"dialing peer through relay\",\n\t\t\"destination_peer\", dest.ID,\n\t\t\"relay_peer\", relay.ID)\n\n\tif len(relay.Addrs) > 0 {\n\t\tc.host.Peerstore().AddAddrs(relay.ID, relay.Addrs, peerstore.TempAddrTTL)\n\t}\n\n\tdialCtx, cancel := context.WithTimeout(ctx, DialRelayTimeout)\n\tdefer cancel()\n\ts, err := c.host.NewStream(dialCtx, relay.ID, proto.ProtoIDv2Hop)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error opening hop stream to relay: %w\", err)\n\t}\n\treturn c.connect(s, dest)\n}\n\nfunc (c *Client) connect(s network.Stream, dest peer.AddrInfo) (*Conn, error) {\n\tif err := s.Scope().ReserveMemory(maxMessageSize, network.ReservationPriorityAlways); err != nil {\n\t\ts.Reset()\n\t\treturn nil, err\n\t}\n\tdefer s.Scope().ReleaseMemory(maxMessageSize)\n\n\trd := util.NewDelimitedReader(s, maxMessageSize)\n\twr := util.NewDelimitedWriter(s)\n\tdefer rd.Close()\n\n\tvar msg pbv2.HopMessage\n\n\tmsg.Type = pbv2.HopMessage_CONNECT.Enum()\n\tmsg.Peer = util.PeerInfoToPeerV2(dest)\n\n\ts.SetDeadline(time.Now().Add(DialTimeout))\n\n\terr := wr.WriteMsg(&msg)\n\tif err != nil {\n\t\ts.Reset()\n\t\treturn nil, err\n\t}\n\n\tmsg.Reset()\n\n\terr = rd.ReadMsg(&msg)\n\tif err != nil {\n\t\ts.Reset()\n\t\treturn nil, err\n\t}\n\n\ts.SetDeadline(time.Time{})\n\n\tif msg.GetType() != pbv2.HopMessage_STATUS {\n\t\ts.Reset()\n\t\treturn nil, newRelayError(\"unexpected relay response; not a status message (%d)\", msg.GetType())\n\t}\n\n\tstatus := msg.GetStatus()\n\tif status != pbv2.Status_OK {\n\t\ts.Reset()\n\t\treturn nil, newRelayError(\"error opening relay circuit: %s (%d)\", pbv2.Status_name[int32(status)], status)\n\t}\n\n\t// check for a limit provided by the relay; if the limit is not nil, then this is a limited\n\t// relay connection and we mark the connection as transient.\n\tvar stat network.ConnStats\n\tif limit := msg.GetLimit(); limit != nil {\n\t\tstat.Limited = true\n\t\tstat.Extra = make(map[any]any)\n\t\tstat.Extra[StatLimitDuration] = time.Duration(limit.GetDuration()) * time.Second\n\t\tstat.Extra[StatLimitData] = limit.GetData()\n\t}\n\n\treturn &Conn{stream: s, remote: dest, stat: stat, client: c}, nil\n}\n"
  },
  {
    "path": "p2p/protocol/circuitv2/client/handlers.go",
    "content": "package client\n\nimport (\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\tpbv2 \"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/pb\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/util\"\n)\n\nvar (\n\tStreamTimeout = 1 * time.Minute\n\tAcceptTimeout = 10 * time.Second\n)\n\nfunc (c *Client) handleStreamV2(s network.Stream) {\n\tlog.Debug(\"new relay/v2 stream\", \"remote_peer\", s.Conn().RemotePeer())\n\n\ts.SetReadDeadline(time.Now().Add(StreamTimeout))\n\n\trd := util.NewDelimitedReader(s, maxMessageSize)\n\tdefer rd.Close()\n\n\twriteResponse := func(status pbv2.Status) error {\n\t\ts.SetWriteDeadline(time.Now().Add(StreamTimeout))\n\t\tdefer s.SetWriteDeadline(time.Time{})\n\t\twr := util.NewDelimitedWriter(s)\n\n\t\tvar msg pbv2.StopMessage\n\t\tmsg.Type = pbv2.StopMessage_STATUS.Enum()\n\t\tmsg.Status = status.Enum()\n\n\t\treturn wr.WriteMsg(&msg)\n\t}\n\n\thandleError := func(status pbv2.Status) {\n\t\tlog.Debug(\"protocol error\", \"status_name\", pbv2.Status_name[int32(status)], \"status_code\", status)\n\t\terr := writeResponse(status)\n\t\tif err != nil {\n\t\t\ts.Reset()\n\t\t\tlog.Debug(\"error writing circuit response\", \"err\", err)\n\t\t} else {\n\t\t\ts.Close()\n\t\t}\n\t}\n\n\tvar msg pbv2.StopMessage\n\n\terr := rd.ReadMsg(&msg)\n\tif err != nil {\n\t\thandleError(pbv2.Status_MALFORMED_MESSAGE)\n\t\treturn\n\t}\n\t// reset stream deadline as message has been read\n\ts.SetReadDeadline(time.Time{})\n\n\tif msg.GetType() != pbv2.StopMessage_CONNECT {\n\t\thandleError(pbv2.Status_UNEXPECTED_MESSAGE)\n\t\treturn\n\t}\n\n\tsrc, err := util.PeerToPeerInfoV2(msg.GetPeer())\n\tif err != nil {\n\t\thandleError(pbv2.Status_MALFORMED_MESSAGE)\n\t\treturn\n\t}\n\n\t// check for a limit provided by the relay; if the limit is not nil, then this is a limited\n\t// relay connection and we mark the connection as transient.\n\tvar stat network.ConnStats\n\tif limit := msg.GetLimit(); limit != nil {\n\t\tstat.Limited = true\n\t\tstat.Extra = make(map[any]any)\n\t\tstat.Extra[StatLimitDuration] = time.Duration(limit.GetDuration()) * time.Second\n\t\tstat.Extra[StatLimitData] = limit.GetData()\n\t}\n\n\tlog.Debug(\"incoming relay connection\", \"source_peer\", src.ID)\n\n\tselect {\n\tcase c.incoming <- accept{\n\t\tconn: &Conn{stream: s, remote: src, stat: stat, client: c},\n\t\twriteResponse: func() error {\n\t\t\treturn writeResponse(pbv2.Status_OK)\n\t\t},\n\t}:\n\tcase <-time.After(AcceptTimeout):\n\t\thandleError(pbv2.Status_CONNECTION_FAILED)\n\t}\n}\n"
  },
  {
    "path": "p2p/protocol/circuitv2/client/listen.go",
    "content": "package client\n\nimport (\n\t\"net\"\n\n\t\"github.com/libp2p/go-libp2p/core/transport\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\nvar _ manet.Listener = (*Listener)(nil)\n\ntype Listener Client\n\nfunc (c *Client) Listener() *Listener {\n\treturn (*Listener)(c)\n}\n\nfunc (l *Listener) Accept() (manet.Conn, error) {\n\tfor {\n\t\tselect {\n\t\tcase evt := <-l.incoming:\n\t\t\terr := evt.writeResponse()\n\t\t\tif err != nil {\n\t\t\t\tlog.Debug(\"error writing relay response\", \"err\", err)\n\t\t\t\tevt.conn.stream.Reset()\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tlog.Debug(\"accepted relay connection\",\n\t\t\t\t\"remote_peer\", evt.conn.remote.ID,\n\t\t\t\t\"remote_multiaddr\", evt.conn.RemoteMultiaddr())\n\n\t\t\tevt.conn.tagHop()\n\t\t\treturn evt.conn, nil\n\n\t\tcase <-l.ctx.Done():\n\t\t\treturn nil, transport.ErrListenerClosed\n\t\t}\n\t}\n}\n\nfunc (l *Listener) Addr() net.Addr {\n\treturn &NetAddr{\n\t\tRelay:  \"any\",\n\t\tRemote: \"any\",\n\t}\n}\n\nfunc (l *Listener) Multiaddr() ma.Multiaddr {\n\treturn circuitAddr\n}\n\nfunc (l *Listener) Close() error {\n\treturn (*Client)(l).Close()\n}\n"
  },
  {
    "path": "p2p/protocol/circuitv2/client/reservation.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\t\"github.com/libp2p/go-libp2p/core/record\"\n\tpbv2 \"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/pb\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/proto\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/util\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\nvar ReserveTimeout = time.Minute\n\n// Reservation is a struct carrying information about a relay/v2 slot reservation.\ntype Reservation struct {\n\t// Expiration is the expiration time of the reservation\n\tExpiration time.Time\n\t// Addrs contains the vouched public addresses of the reserving peer, which can be\n\t// announced to the network\n\tAddrs []ma.Multiaddr\n\n\t// LimitDuration is the time limit for which the relay will keep a relayed connection\n\t// open. If 0, there is no limit.\n\tLimitDuration time.Duration\n\t// LimitData is the number of bytes that the relay will relay in each direction before\n\t// resetting a relayed connection.\n\tLimitData uint64\n\n\t// Voucher is a signed reservation voucher provided by the relay\n\tVoucher *proto.ReservationVoucher\n}\n\n// ReservationError is the error returned on failure to reserve a slot in the relay\ntype ReservationError struct {\n\n\t// Status is the status returned by the relay for rejecting the reservation\n\t// request. It is set to pbv2.Status_CONNECTION_FAILED on other failures\n\tStatus pbv2.Status\n\n\t// Reason is the reason for reservation failure\n\tReason string\n\n\terr error\n}\n\nfunc (re ReservationError) Error() string {\n\treturn fmt.Sprintf(\"reservation error: status: %s reason: %s err: %s\", pbv2.Status_name[int32(re.Status)], re.Reason, re.err)\n}\n\nfunc (re ReservationError) Unwrap() error {\n\treturn re.err\n}\n\n// Reserve reserves a slot in a relay and returns the reservation information.\n// Clients must reserve slots in order for the relay to relay connections to them.\nfunc Reserve(ctx context.Context, h host.Host, ai peer.AddrInfo) (*Reservation, error) {\n\tif len(ai.Addrs) > 0 {\n\t\th.Peerstore().AddAddrs(ai.ID, ai.Addrs, peerstore.TempAddrTTL)\n\t}\n\n\ts, err := h.NewStream(ctx, ai.ID, proto.ProtoIDv2Hop)\n\tif err != nil {\n\t\treturn nil, ReservationError{Status: pbv2.Status_CONNECTION_FAILED, Reason: \"failed to open stream\", err: err}\n\t}\n\tdefer s.Close()\n\n\trd := util.NewDelimitedReader(s, maxMessageSize)\n\twr := util.NewDelimitedWriter(s)\n\tdefer rd.Close()\n\n\tvar msg pbv2.HopMessage\n\tmsg.Type = pbv2.HopMessage_RESERVE.Enum()\n\n\ts.SetDeadline(time.Now().Add(ReserveTimeout))\n\n\tif err := wr.WriteMsg(&msg); err != nil {\n\t\ts.Reset()\n\t\treturn nil, ReservationError{Status: pbv2.Status_CONNECTION_FAILED, Reason: \"error writing reservation message\", err: err}\n\t}\n\n\tmsg.Reset()\n\n\tif err := rd.ReadMsg(&msg); err != nil {\n\t\ts.Reset()\n\t\treturn nil, ReservationError{Status: pbv2.Status_CONNECTION_FAILED, Reason: \"error reading reservation response message: %w\", err: err}\n\t}\n\n\tif msg.GetType() != pbv2.HopMessage_STATUS {\n\t\treturn nil, ReservationError{Status: pbv2.Status_MALFORMED_MESSAGE, Reason: fmt.Sprintf(\"unexpected relay response: not a status message (%d)\", msg.GetType())}\n\t}\n\n\tif status := msg.GetStatus(); status != pbv2.Status_OK {\n\t\treturn nil, ReservationError{Status: msg.GetStatus(), Reason: \"reservation failed\"}\n\t}\n\n\trsvp := msg.GetReservation()\n\tif rsvp == nil {\n\t\treturn nil, ReservationError{Status: pbv2.Status_MALFORMED_MESSAGE, Reason: \"missing reservation info\"}\n\t}\n\n\tresult := &Reservation{}\n\tresult.Expiration = time.Unix(int64(rsvp.GetExpire()), 0)\n\tif result.Expiration.Before(time.Now()) {\n\t\treturn nil, ReservationError{\n\t\t\tStatus: pbv2.Status_MALFORMED_MESSAGE,\n\t\t\tReason: fmt.Sprintf(\"received reservation with expiration date in the past: %s\", result.Expiration),\n\t\t}\n\t}\n\n\taddrs := rsvp.GetAddrs()\n\tresult.Addrs = make([]ma.Multiaddr, 0, len(addrs))\n\tfor _, ab := range addrs {\n\t\ta, err := ma.NewMultiaddrBytes(ab)\n\t\tif err != nil {\n\t\t\tlog.Warn(\"ignoring unparsable relay address\", \"err\", err)\n\t\t\tcontinue\n\t\t}\n\t\tresult.Addrs = append(result.Addrs, a)\n\t}\n\n\tvoucherBytes := rsvp.GetVoucher()\n\tif voucherBytes != nil {\n\t\tenv, rec, err := record.ConsumeEnvelope(voucherBytes, proto.RecordDomain)\n\t\tif err != nil {\n\t\t\treturn nil, ReservationError{\n\t\t\t\tStatus: pbv2.Status_MALFORMED_MESSAGE,\n\t\t\t\tReason: fmt.Sprintf(\"error consuming voucher envelope: %s\", err),\n\t\t\t\terr:    err,\n\t\t\t}\n\t\t}\n\n\t\tvoucher, ok := rec.(*proto.ReservationVoucher)\n\t\tif !ok {\n\t\t\treturn nil, ReservationError{\n\t\t\t\tStatus: pbv2.Status_MALFORMED_MESSAGE,\n\t\t\t\tReason: fmt.Sprintf(\"unexpected voucher record type: %+T\", rec),\n\t\t\t}\n\t\t}\n\t\tsignerPeerID, err := peer.IDFromPublicKey(env.PublicKey)\n\t\tif err != nil {\n\t\t\treturn nil, ReservationError{\n\t\t\t\tStatus: pbv2.Status_MALFORMED_MESSAGE,\n\t\t\t\tReason: fmt.Sprintf(\"invalid voucher signing public key: %s\", err),\n\t\t\t\terr:    err,\n\t\t\t}\n\t\t}\n\t\tif signerPeerID != voucher.Relay {\n\t\t\treturn nil, ReservationError{\n\t\t\t\tStatus: pbv2.Status_MALFORMED_MESSAGE,\n\t\t\t\tReason: fmt.Sprintf(\"invalid voucher relay id: expected %s, got %s\", signerPeerID, voucher.Relay),\n\t\t\t}\n\t\t}\n\t\tif h.ID() != voucher.Peer {\n\t\t\treturn nil, ReservationError{\n\t\t\t\tStatus: pbv2.Status_MALFORMED_MESSAGE,\n\t\t\t\tReason: fmt.Sprintf(\"invalid voucher peer id: expected %s, got %s\", h.ID(), voucher.Peer),\n\t\t\t}\n\n\t\t}\n\t\tresult.Voucher = voucher\n\t}\n\n\tlimit := msg.GetLimit()\n\tif limit != nil {\n\t\tresult.LimitDuration = time.Duration(limit.GetDuration()) * time.Second\n\t\tresult.LimitData = limit.GetData()\n\t}\n\n\treturn result, nil\n}\n"
  },
  {
    "path": "p2p/protocol/circuitv2/client/reservation_test.go",
    "content": "package client_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"math\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p\"\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/record\"\n\t\"github.com/libp2p/go-libp2p/core/test\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/client\"\n\tpbv2 \"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/pb\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/proto\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/util\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestReservationFailures(t *testing.T) {\n\ttype testcase struct {\n\t\tname          string\n\t\tstreamHandler network.StreamHandler\n\t\terr           string\n\t\tstatus        pbv2.Status\n\t}\n\ttestcases := []testcase{\n\t\t{\n\t\t\tname:          \"unsupported protocol\",\n\t\t\tstreamHandler: nil,\n\t\t\terr:           \"protocols not supported\",\n\t\t},\n\t\t{\n\t\t\tname: \"wrong message type\",\n\t\t\tstreamHandler: func(s network.Stream) {\n\t\t\t\tutil.NewDelimitedWriter(s).WriteMsg(&pbv2.HopMessage{\n\t\t\t\t\tType: pbv2.HopMessage_RESERVE.Enum(),\n\t\t\t\t})\n\t\t\t},\n\t\t\terr:    \"unexpected relay response: not a status message\",\n\t\t\tstatus: pbv2.Status_MALFORMED_MESSAGE,\n\t\t},\n\t\t{\n\t\t\tname: \"unknown status\",\n\t\t\tstreamHandler: func(s network.Stream) {\n\t\t\t\tstatus := pbv2.Status(1337)\n\t\t\t\tutil.NewDelimitedWriter(s).WriteMsg(&pbv2.HopMessage{\n\t\t\t\t\tType:   pbv2.HopMessage_STATUS.Enum(),\n\t\t\t\t\tStatus: &status,\n\t\t\t\t})\n\t\t\t},\n\t\t\terr:    \"reservation failed\",\n\t\t\tstatus: pbv2.Status(1337),\n\t\t},\n\t\t{\n\t\t\tname: \"invalid time\",\n\t\t\tstreamHandler: func(s network.Stream) {\n\t\t\t\tstatus := pbv2.Status_OK\n\t\t\t\texpire := uint64(math.MaxUint64)\n\t\t\t\tutil.NewDelimitedWriter(s).WriteMsg(&pbv2.HopMessage{\n\t\t\t\t\tType:        pbv2.HopMessage_STATUS.Enum(),\n\t\t\t\t\tStatus:      &status,\n\t\t\t\t\tReservation: &pbv2.Reservation{Expire: &expire},\n\t\t\t\t})\n\t\t\t},\n\t\t\terr:    \"received reservation with expiration date in the past\",\n\t\t\tstatus: pbv2.Status_MALFORMED_MESSAGE,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid voucher\",\n\t\t\tstreamHandler: func(s network.Stream) {\n\t\t\t\tstatus := pbv2.Status_OK\n\t\t\t\texpire := uint64(time.Now().Add(time.Hour).UnixNano())\n\t\t\t\tutil.NewDelimitedWriter(s).WriteMsg(&pbv2.HopMessage{\n\t\t\t\t\tType:   pbv2.HopMessage_STATUS.Enum(),\n\t\t\t\t\tStatus: &status,\n\t\t\t\t\tReservation: &pbv2.Reservation{\n\t\t\t\t\t\tExpire:  &expire,\n\t\t\t\t\t\tVoucher: []byte(\"foobar\"),\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t},\n\t\t\terr:    \"error consuming voucher envelope: failed when unmarshalling the envelope\",\n\t\t\tstatus: pbv2.Status_MALFORMED_MESSAGE,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid voucher 2\",\n\t\t\tstreamHandler: func(s network.Stream) {\n\t\t\t\tstatus := pbv2.Status_OK\n\t\t\t\texpire := uint64(time.Now().Add(time.Hour).UnixNano())\n\t\t\t\tpriv, _, err := test.RandTestKeyPair(crypto.Ed25519, 256)\n\t\t\t\tif err != nil {\n\t\t\t\t\ts.Reset()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\trelay, _ := test.RandPeerID()\n\t\t\t\tpeer, _ := test.RandPeerID()\n\t\t\t\tvoucher := &proto.ReservationVoucher{\n\t\t\t\t\tRelay:      relay,\n\t\t\t\t\tPeer:       peer,\n\t\t\t\t\tExpiration: time.Now().Add(time.Hour),\n\t\t\t\t}\n\t\t\t\tsignedVoucher, err := record.Seal(voucher, priv)\n\t\t\t\tif err != nil {\n\t\t\t\t\ts.Reset()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tenv, err := signedVoucher.Marshal()\n\t\t\t\tif err != nil {\n\t\t\t\t\ts.Reset()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tutil.NewDelimitedWriter(s).WriteMsg(&pbv2.HopMessage{\n\t\t\t\t\tType:   pbv2.HopMessage_STATUS.Enum(),\n\t\t\t\t\tStatus: &status,\n\t\t\t\t\tReservation: &pbv2.Reservation{\n\t\t\t\t\t\tExpire:  &expire,\n\t\t\t\t\t\tVoucher: env,\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t},\n\t\t\terr:    \"invalid voucher relay id\",\n\t\t\tstatus: pbv2.Status_MALFORMED_MESSAGE,\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\thost, err := libp2p.New(libp2p.ResourceManager(&network.NullResourceManager{}))\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer host.Close()\n\t\t\tif tc.streamHandler != nil {\n\t\t\t\thost.SetStreamHandler(proto.ProtoIDv2Hop, tc.streamHandler)\n\t\t\t}\n\n\t\t\tcl, err := libp2p.New(libp2p.ResourceManager(&network.NullResourceManager{}))\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer cl.Close()\n\t\t\t_, err = client.Reserve(context.Background(), cl, peer.AddrInfo{ID: host.ID(), Addrs: host.Addrs()})\n\t\t\tif tc.err == \"\" {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t} else {\n\t\t\t\trequire.Error(t, err)\n\t\t\t\trequire.Contains(t, err.Error(), tc.err)\n\t\t\t\tif tc.status != 0 {\n\t\t\t\t\tvar re client.ReservationError\n\t\t\t\t\tif !errors.As(err, &re) {\n\t\t\t\t\t\tt.Errorf(\"expected error to be of type %T\", re)\n\t\t\t\t\t}\n\t\t\t\t\tif re.Status != tc.status {\n\t\t\t\t\t\tt.Errorf(\"expected status %d got %d\", tc.status, re.Status)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "p2p/protocol/circuitv2/client/transport.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/transport\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\nvar circuitProtocol = ma.ProtocolWithCode(ma.P_CIRCUIT)\nvar circuitAddr = ma.Cast(circuitProtocol.VCode)\n\n// AddTransport constructs a new p2p-circuit/v2 client and adds it as a transport to the\n// host network\nfunc AddTransport(h host.Host, upgrader transport.Upgrader) error {\n\tn, ok := h.Network().(transport.TransportNetwork)\n\tif !ok {\n\t\treturn fmt.Errorf(\"%v is not a transport network\", h.Network())\n\t}\n\n\tc, err := New(h, upgrader)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error constructing circuit client: %w\", err)\n\t}\n\n\terr = n.AddTransport(c)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error adding circuit transport: %w\", err)\n\t}\n\n\terr = n.Listen(circuitAddr)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error listening to circuit addr: %w\", err)\n\t}\n\n\tc.Start()\n\n\treturn nil\n}\n\n// Transport interface\nvar _ transport.Transport = (*Client)(nil)\n\n// p2p-circuit implements the SkipResolver interface so that the underlying\n// transport can do the address resolution later. If you wrap this transport,\n// make sure you also implement SkipResolver as well.\nvar _ transport.SkipResolver = (*Client)(nil)\nvar _ io.Closer = (*Client)(nil)\n\n// SkipResolve returns true since we always defer to the inner transport for\n// the actual connection. By skipping resolution here, we let the inner\n// transport decide how to resolve the multiaddr\nfunc (c *Client) SkipResolve(_ context.Context, _ ma.Multiaddr) bool {\n\treturn true\n}\n\nfunc (c *Client) Dial(ctx context.Context, a ma.Multiaddr, p peer.ID) (transport.CapableConn, error) {\n\tconnScope, err := c.host.Network().ResourceManager().OpenConnection(network.DirOutbound, false, a)\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tconn, err := c.dialAndUpgrade(ctx, a, p, connScope)\n\tif err != nil {\n\t\tconnScope.Done()\n\t\treturn nil, err\n\t}\n\treturn conn, nil\n}\n\nfunc (c *Client) dialAndUpgrade(ctx context.Context, a ma.Multiaddr, p peer.ID, connScope network.ConnManagementScope) (transport.CapableConn, error) {\n\tif err := connScope.SetPeer(p); err != nil {\n\t\treturn nil, err\n\t}\n\tconn, err := c.dial(ctx, a, p)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tconn.tagHop()\n\tcc, err := c.upgrader.Upgrade(ctx, c, conn, network.DirOutbound, p, connScope)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn capableConn{cc.(capableConnWithStat)}, nil\n}\n\nfunc (c *Client) CanDial(addr ma.Multiaddr) bool {\n\t_, err := addr.ValueForProtocol(ma.P_CIRCUIT)\n\treturn err == nil\n}\n\nfunc (c *Client) Listen(addr ma.Multiaddr) (transport.Listener, error) {\n\t// TODO connect to the relay and reserve slot if specified\n\tif _, err := addr.ValueForProtocol(ma.P_CIRCUIT); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn c.upgrader.UpgradeGatedMaListener(c, c.upgrader.GateMaListener(c.Listener())), nil\n}\n\nfunc (c *Client) Protocols() []int {\n\treturn []int{ma.P_CIRCUIT}\n}\n\nfunc (c *Client) Proxy() bool {\n\treturn true\n}\n"
  },
  {
    "path": "p2p/protocol/circuitv2/pb/circuit.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.6\n// \tprotoc        v5.29.2\n// source: p2p/protocol/circuitv2/pb/circuit.proto\n\npackage pb\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Status int32\n\nconst (\n\t// zero value field required for proto3 compatibility\n\tStatus_UNUSED                  Status = 0\n\tStatus_OK                      Status = 100\n\tStatus_RESERVATION_REFUSED     Status = 200\n\tStatus_RESOURCE_LIMIT_EXCEEDED Status = 201\n\tStatus_PERMISSION_DENIED       Status = 202\n\tStatus_CONNECTION_FAILED       Status = 203\n\tStatus_NO_RESERVATION          Status = 204\n\tStatus_MALFORMED_MESSAGE       Status = 400\n\tStatus_UNEXPECTED_MESSAGE      Status = 401\n)\n\n// Enum value maps for Status.\nvar (\n\tStatus_name = map[int32]string{\n\t\t0:   \"UNUSED\",\n\t\t100: \"OK\",\n\t\t200: \"RESERVATION_REFUSED\",\n\t\t201: \"RESOURCE_LIMIT_EXCEEDED\",\n\t\t202: \"PERMISSION_DENIED\",\n\t\t203: \"CONNECTION_FAILED\",\n\t\t204: \"NO_RESERVATION\",\n\t\t400: \"MALFORMED_MESSAGE\",\n\t\t401: \"UNEXPECTED_MESSAGE\",\n\t}\n\tStatus_value = map[string]int32{\n\t\t\"UNUSED\":                  0,\n\t\t\"OK\":                      100,\n\t\t\"RESERVATION_REFUSED\":     200,\n\t\t\"RESOURCE_LIMIT_EXCEEDED\": 201,\n\t\t\"PERMISSION_DENIED\":       202,\n\t\t\"CONNECTION_FAILED\":       203,\n\t\t\"NO_RESERVATION\":          204,\n\t\t\"MALFORMED_MESSAGE\":       400,\n\t\t\"UNEXPECTED_MESSAGE\":      401,\n\t}\n)\n\nfunc (x Status) Enum() *Status {\n\tp := new(Status)\n\t*p = x\n\treturn p\n}\n\nfunc (x Status) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (Status) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_p2p_protocol_circuitv2_pb_circuit_proto_enumTypes[0].Descriptor()\n}\n\nfunc (Status) Type() protoreflect.EnumType {\n\treturn &file_p2p_protocol_circuitv2_pb_circuit_proto_enumTypes[0]\n}\n\nfunc (x Status) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use Status.Descriptor instead.\nfunc (Status) EnumDescriptor() ([]byte, []int) {\n\treturn file_p2p_protocol_circuitv2_pb_circuit_proto_rawDescGZIP(), []int{0}\n}\n\ntype HopMessage_Type int32\n\nconst (\n\tHopMessage_RESERVE HopMessage_Type = 0\n\tHopMessage_CONNECT HopMessage_Type = 1\n\tHopMessage_STATUS  HopMessage_Type = 2\n)\n\n// Enum value maps for HopMessage_Type.\nvar (\n\tHopMessage_Type_name = map[int32]string{\n\t\t0: \"RESERVE\",\n\t\t1: \"CONNECT\",\n\t\t2: \"STATUS\",\n\t}\n\tHopMessage_Type_value = map[string]int32{\n\t\t\"RESERVE\": 0,\n\t\t\"CONNECT\": 1,\n\t\t\"STATUS\":  2,\n\t}\n)\n\nfunc (x HopMessage_Type) Enum() *HopMessage_Type {\n\tp := new(HopMessage_Type)\n\t*p = x\n\treturn p\n}\n\nfunc (x HopMessage_Type) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (HopMessage_Type) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_p2p_protocol_circuitv2_pb_circuit_proto_enumTypes[1].Descriptor()\n}\n\nfunc (HopMessage_Type) Type() protoreflect.EnumType {\n\treturn &file_p2p_protocol_circuitv2_pb_circuit_proto_enumTypes[1]\n}\n\nfunc (x HopMessage_Type) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use HopMessage_Type.Descriptor instead.\nfunc (HopMessage_Type) EnumDescriptor() ([]byte, []int) {\n\treturn file_p2p_protocol_circuitv2_pb_circuit_proto_rawDescGZIP(), []int{0, 0}\n}\n\ntype StopMessage_Type int32\n\nconst (\n\tStopMessage_CONNECT StopMessage_Type = 0\n\tStopMessage_STATUS  StopMessage_Type = 1\n)\n\n// Enum value maps for StopMessage_Type.\nvar (\n\tStopMessage_Type_name = map[int32]string{\n\t\t0: \"CONNECT\",\n\t\t1: \"STATUS\",\n\t}\n\tStopMessage_Type_value = map[string]int32{\n\t\t\"CONNECT\": 0,\n\t\t\"STATUS\":  1,\n\t}\n)\n\nfunc (x StopMessage_Type) Enum() *StopMessage_Type {\n\tp := new(StopMessage_Type)\n\t*p = x\n\treturn p\n}\n\nfunc (x StopMessage_Type) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (StopMessage_Type) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_p2p_protocol_circuitv2_pb_circuit_proto_enumTypes[2].Descriptor()\n}\n\nfunc (StopMessage_Type) Type() protoreflect.EnumType {\n\treturn &file_p2p_protocol_circuitv2_pb_circuit_proto_enumTypes[2]\n}\n\nfunc (x StopMessage_Type) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use StopMessage_Type.Descriptor instead.\nfunc (StopMessage_Type) EnumDescriptor() ([]byte, []int) {\n\treturn file_p2p_protocol_circuitv2_pb_circuit_proto_rawDescGZIP(), []int{1, 0}\n}\n\ntype HopMessage struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// This field is marked optional for backwards compatibility with proto2.\n\t// Users should make sure to always set this.\n\tType          *HopMessage_Type `protobuf:\"varint,1,opt,name=type,proto3,enum=circuit.pb.HopMessage_Type,oneof\" json:\"type,omitempty\"`\n\tPeer          *Peer            `protobuf:\"bytes,2,opt,name=peer,proto3,oneof\" json:\"peer,omitempty\"`\n\tReservation   *Reservation     `protobuf:\"bytes,3,opt,name=reservation,proto3,oneof\" json:\"reservation,omitempty\"`\n\tLimit         *Limit           `protobuf:\"bytes,4,opt,name=limit,proto3,oneof\" json:\"limit,omitempty\"`\n\tStatus        *Status          `protobuf:\"varint,5,opt,name=status,proto3,enum=circuit.pb.Status,oneof\" json:\"status,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *HopMessage) Reset() {\n\t*x = HopMessage{}\n\tmi := &file_p2p_protocol_circuitv2_pb_circuit_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *HopMessage) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*HopMessage) ProtoMessage() {}\n\nfunc (x *HopMessage) ProtoReflect() protoreflect.Message {\n\tmi := &file_p2p_protocol_circuitv2_pb_circuit_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use HopMessage.ProtoReflect.Descriptor instead.\nfunc (*HopMessage) Descriptor() ([]byte, []int) {\n\treturn file_p2p_protocol_circuitv2_pb_circuit_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *HopMessage) GetType() HopMessage_Type {\n\tif x != nil && x.Type != nil {\n\t\treturn *x.Type\n\t}\n\treturn HopMessage_RESERVE\n}\n\nfunc (x *HopMessage) GetPeer() *Peer {\n\tif x != nil {\n\t\treturn x.Peer\n\t}\n\treturn nil\n}\n\nfunc (x *HopMessage) GetReservation() *Reservation {\n\tif x != nil {\n\t\treturn x.Reservation\n\t}\n\treturn nil\n}\n\nfunc (x *HopMessage) GetLimit() *Limit {\n\tif x != nil {\n\t\treturn x.Limit\n\t}\n\treturn nil\n}\n\nfunc (x *HopMessage) GetStatus() Status {\n\tif x != nil && x.Status != nil {\n\t\treturn *x.Status\n\t}\n\treturn Status_UNUSED\n}\n\ntype StopMessage struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// This field is marked optional for backwards compatibility with proto2.\n\t// Users should make sure to always set this.\n\tType          *StopMessage_Type `protobuf:\"varint,1,opt,name=type,proto3,enum=circuit.pb.StopMessage_Type,oneof\" json:\"type,omitempty\"`\n\tPeer          *Peer             `protobuf:\"bytes,2,opt,name=peer,proto3,oneof\" json:\"peer,omitempty\"`\n\tLimit         *Limit            `protobuf:\"bytes,3,opt,name=limit,proto3,oneof\" json:\"limit,omitempty\"`\n\tStatus        *Status           `protobuf:\"varint,4,opt,name=status,proto3,enum=circuit.pb.Status,oneof\" json:\"status,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *StopMessage) Reset() {\n\t*x = StopMessage{}\n\tmi := &file_p2p_protocol_circuitv2_pb_circuit_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *StopMessage) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*StopMessage) ProtoMessage() {}\n\nfunc (x *StopMessage) ProtoReflect() protoreflect.Message {\n\tmi := &file_p2p_protocol_circuitv2_pb_circuit_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use StopMessage.ProtoReflect.Descriptor instead.\nfunc (*StopMessage) Descriptor() ([]byte, []int) {\n\treturn file_p2p_protocol_circuitv2_pb_circuit_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *StopMessage) GetType() StopMessage_Type {\n\tif x != nil && x.Type != nil {\n\t\treturn *x.Type\n\t}\n\treturn StopMessage_CONNECT\n}\n\nfunc (x *StopMessage) GetPeer() *Peer {\n\tif x != nil {\n\t\treturn x.Peer\n\t}\n\treturn nil\n}\n\nfunc (x *StopMessage) GetLimit() *Limit {\n\tif x != nil {\n\t\treturn x.Limit\n\t}\n\treturn nil\n}\n\nfunc (x *StopMessage) GetStatus() Status {\n\tif x != nil && x.Status != nil {\n\t\treturn *x.Status\n\t}\n\treturn Status_UNUSED\n}\n\ntype Peer struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// This field is marked optional for backwards compatibility with proto2.\n\t// Users should make sure to always set this.\n\tId            []byte   `protobuf:\"bytes,1,opt,name=id,proto3,oneof\" json:\"id,omitempty\"`\n\tAddrs         [][]byte `protobuf:\"bytes,2,rep,name=addrs,proto3\" json:\"addrs,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Peer) Reset() {\n\t*x = Peer{}\n\tmi := &file_p2p_protocol_circuitv2_pb_circuit_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Peer) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Peer) ProtoMessage() {}\n\nfunc (x *Peer) ProtoReflect() protoreflect.Message {\n\tmi := &file_p2p_protocol_circuitv2_pb_circuit_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Peer.ProtoReflect.Descriptor instead.\nfunc (*Peer) Descriptor() ([]byte, []int) {\n\treturn file_p2p_protocol_circuitv2_pb_circuit_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *Peer) GetId() []byte {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn nil\n}\n\nfunc (x *Peer) GetAddrs() [][]byte {\n\tif x != nil {\n\t\treturn x.Addrs\n\t}\n\treturn nil\n}\n\ntype Reservation struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// This field is marked optional for backwards compatibility with proto2.\n\t// Users should make sure to always set this.\n\tExpire        *uint64  `protobuf:\"varint,1,opt,name=expire,proto3,oneof\" json:\"expire,omitempty\"`  // Unix expiration time (UTC)\n\tAddrs         [][]byte `protobuf:\"bytes,2,rep,name=addrs,proto3\" json:\"addrs,omitempty\"`           // relay addrs for reserving peer\n\tVoucher       []byte   `protobuf:\"bytes,3,opt,name=voucher,proto3,oneof\" json:\"voucher,omitempty\"` // reservation voucher\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Reservation) Reset() {\n\t*x = Reservation{}\n\tmi := &file_p2p_protocol_circuitv2_pb_circuit_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Reservation) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Reservation) ProtoMessage() {}\n\nfunc (x *Reservation) ProtoReflect() protoreflect.Message {\n\tmi := &file_p2p_protocol_circuitv2_pb_circuit_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Reservation.ProtoReflect.Descriptor instead.\nfunc (*Reservation) Descriptor() ([]byte, []int) {\n\treturn file_p2p_protocol_circuitv2_pb_circuit_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *Reservation) GetExpire() uint64 {\n\tif x != nil && x.Expire != nil {\n\t\treturn *x.Expire\n\t}\n\treturn 0\n}\n\nfunc (x *Reservation) GetAddrs() [][]byte {\n\tif x != nil {\n\t\treturn x.Addrs\n\t}\n\treturn nil\n}\n\nfunc (x *Reservation) GetVoucher() []byte {\n\tif x != nil {\n\t\treturn x.Voucher\n\t}\n\treturn nil\n}\n\ntype Limit struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tDuration      *uint32                `protobuf:\"varint,1,opt,name=duration,proto3,oneof\" json:\"duration,omitempty\"` // seconds\n\tData          *uint64                `protobuf:\"varint,2,opt,name=data,proto3,oneof\" json:\"data,omitempty\"`         // bytes\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Limit) Reset() {\n\t*x = Limit{}\n\tmi := &file_p2p_protocol_circuitv2_pb_circuit_proto_msgTypes[4]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Limit) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Limit) ProtoMessage() {}\n\nfunc (x *Limit) ProtoReflect() protoreflect.Message {\n\tmi := &file_p2p_protocol_circuitv2_pb_circuit_proto_msgTypes[4]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Limit.ProtoReflect.Descriptor instead.\nfunc (*Limit) Descriptor() ([]byte, []int) {\n\treturn file_p2p_protocol_circuitv2_pb_circuit_proto_rawDescGZIP(), []int{4}\n}\n\nfunc (x *Limit) GetDuration() uint32 {\n\tif x != nil && x.Duration != nil {\n\t\treturn *x.Duration\n\t}\n\treturn 0\n}\n\nfunc (x *Limit) GetData() uint64 {\n\tif x != nil && x.Data != nil {\n\t\treturn *x.Data\n\t}\n\treturn 0\n}\n\nvar File_p2p_protocol_circuitv2_pb_circuit_proto protoreflect.FileDescriptor\n\nconst file_p2p_protocol_circuitv2_pb_circuit_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"'p2p/protocol/circuitv2/pb/circuit.proto\\x12\\n\" +\n\t\"circuit.pb\\\"\\xf1\\x02\\n\" +\n\t\"\\n\" +\n\t\"HopMessage\\x124\\n\" +\n\t\"\\x04type\\x18\\x01 \\x01(\\x0e2\\x1b.circuit.pb.HopMessage.TypeH\\x00R\\x04type\\x88\\x01\\x01\\x12)\\n\" +\n\t\"\\x04peer\\x18\\x02 \\x01(\\v2\\x10.circuit.pb.PeerH\\x01R\\x04peer\\x88\\x01\\x01\\x12>\\n\" +\n\t\"\\vreservation\\x18\\x03 \\x01(\\v2\\x17.circuit.pb.ReservationH\\x02R\\vreservation\\x88\\x01\\x01\\x12,\\n\" +\n\t\"\\x05limit\\x18\\x04 \\x01(\\v2\\x11.circuit.pb.LimitH\\x03R\\x05limit\\x88\\x01\\x01\\x12/\\n\" +\n\t\"\\x06status\\x18\\x05 \\x01(\\x0e2\\x12.circuit.pb.StatusH\\x04R\\x06status\\x88\\x01\\x01\\\",\\n\" +\n\t\"\\x04Type\\x12\\v\\n\" +\n\t\"\\aRESERVE\\x10\\x00\\x12\\v\\n\" +\n\t\"\\aCONNECT\\x10\\x01\\x12\\n\" +\n\t\"\\n\" +\n\t\"\\x06STATUS\\x10\\x02B\\a\\n\" +\n\t\"\\x05_typeB\\a\\n\" +\n\t\"\\x05_peerB\\x0e\\n\" +\n\t\"\\f_reservationB\\b\\n\" +\n\t\"\\x06_limitB\\t\\n\" +\n\t\"\\a_status\\\"\\x96\\x02\\n\" +\n\t\"\\vStopMessage\\x125\\n\" +\n\t\"\\x04type\\x18\\x01 \\x01(\\x0e2\\x1c.circuit.pb.StopMessage.TypeH\\x00R\\x04type\\x88\\x01\\x01\\x12)\\n\" +\n\t\"\\x04peer\\x18\\x02 \\x01(\\v2\\x10.circuit.pb.PeerH\\x01R\\x04peer\\x88\\x01\\x01\\x12,\\n\" +\n\t\"\\x05limit\\x18\\x03 \\x01(\\v2\\x11.circuit.pb.LimitH\\x02R\\x05limit\\x88\\x01\\x01\\x12/\\n\" +\n\t\"\\x06status\\x18\\x04 \\x01(\\x0e2\\x12.circuit.pb.StatusH\\x03R\\x06status\\x88\\x01\\x01\\\"\\x1f\\n\" +\n\t\"\\x04Type\\x12\\v\\n\" +\n\t\"\\aCONNECT\\x10\\x00\\x12\\n\" +\n\t\"\\n\" +\n\t\"\\x06STATUS\\x10\\x01B\\a\\n\" +\n\t\"\\x05_typeB\\a\\n\" +\n\t\"\\x05_peerB\\b\\n\" +\n\t\"\\x06_limitB\\t\\n\" +\n\t\"\\a_status\\\"8\\n\" +\n\t\"\\x04Peer\\x12\\x13\\n\" +\n\t\"\\x02id\\x18\\x01 \\x01(\\fH\\x00R\\x02id\\x88\\x01\\x01\\x12\\x14\\n\" +\n\t\"\\x05addrs\\x18\\x02 \\x03(\\fR\\x05addrsB\\x05\\n\" +\n\t\"\\x03_id\\\"v\\n\" +\n\t\"\\vReservation\\x12\\x1b\\n\" +\n\t\"\\x06expire\\x18\\x01 \\x01(\\x04H\\x00R\\x06expire\\x88\\x01\\x01\\x12\\x14\\n\" +\n\t\"\\x05addrs\\x18\\x02 \\x03(\\fR\\x05addrs\\x12\\x1d\\n\" +\n\t\"\\avoucher\\x18\\x03 \\x01(\\fH\\x01R\\avoucher\\x88\\x01\\x01B\\t\\n\" +\n\t\"\\a_expireB\\n\" +\n\t\"\\n\" +\n\t\"\\b_voucher\\\"W\\n\" +\n\t\"\\x05Limit\\x12\\x1f\\n\" +\n\t\"\\bduration\\x18\\x01 \\x01(\\rH\\x00R\\bduration\\x88\\x01\\x01\\x12\\x17\\n\" +\n\t\"\\x04data\\x18\\x02 \\x01(\\x04H\\x01R\\x04data\\x88\\x01\\x01B\\v\\n\" +\n\t\"\\t_durationB\\a\\n\" +\n\t\"\\x05_data*\\xca\\x01\\n\" +\n\t\"\\x06Status\\x12\\n\" +\n\t\"\\n\" +\n\t\"\\x06UNUSED\\x10\\x00\\x12\\x06\\n\" +\n\t\"\\x02OK\\x10d\\x12\\x18\\n\" +\n\t\"\\x13RESERVATION_REFUSED\\x10\\xc8\\x01\\x12\\x1c\\n\" +\n\t\"\\x17RESOURCE_LIMIT_EXCEEDED\\x10\\xc9\\x01\\x12\\x16\\n\" +\n\t\"\\x11PERMISSION_DENIED\\x10\\xca\\x01\\x12\\x16\\n\" +\n\t\"\\x11CONNECTION_FAILED\\x10\\xcb\\x01\\x12\\x13\\n\" +\n\t\"\\x0eNO_RESERVATION\\x10\\xcc\\x01\\x12\\x16\\n\" +\n\t\"\\x11MALFORMED_MESSAGE\\x10\\x90\\x03\\x12\\x17\\n\" +\n\t\"\\x12UNEXPECTED_MESSAGE\\x10\\x91\\x03B7Z5github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/pbb\\x06proto3\"\n\nvar (\n\tfile_p2p_protocol_circuitv2_pb_circuit_proto_rawDescOnce sync.Once\n\tfile_p2p_protocol_circuitv2_pb_circuit_proto_rawDescData []byte\n)\n\nfunc file_p2p_protocol_circuitv2_pb_circuit_proto_rawDescGZIP() []byte {\n\tfile_p2p_protocol_circuitv2_pb_circuit_proto_rawDescOnce.Do(func() {\n\t\tfile_p2p_protocol_circuitv2_pb_circuit_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_p2p_protocol_circuitv2_pb_circuit_proto_rawDesc), len(file_p2p_protocol_circuitv2_pb_circuit_proto_rawDesc)))\n\t})\n\treturn file_p2p_protocol_circuitv2_pb_circuit_proto_rawDescData\n}\n\nvar file_p2p_protocol_circuitv2_pb_circuit_proto_enumTypes = make([]protoimpl.EnumInfo, 3)\nvar file_p2p_protocol_circuitv2_pb_circuit_proto_msgTypes = make([]protoimpl.MessageInfo, 5)\nvar file_p2p_protocol_circuitv2_pb_circuit_proto_goTypes = []any{\n\t(Status)(0),           // 0: circuit.pb.Status\n\t(HopMessage_Type)(0),  // 1: circuit.pb.HopMessage.Type\n\t(StopMessage_Type)(0), // 2: circuit.pb.StopMessage.Type\n\t(*HopMessage)(nil),    // 3: circuit.pb.HopMessage\n\t(*StopMessage)(nil),   // 4: circuit.pb.StopMessage\n\t(*Peer)(nil),          // 5: circuit.pb.Peer\n\t(*Reservation)(nil),   // 6: circuit.pb.Reservation\n\t(*Limit)(nil),         // 7: circuit.pb.Limit\n}\nvar file_p2p_protocol_circuitv2_pb_circuit_proto_depIdxs = []int32{\n\t1, // 0: circuit.pb.HopMessage.type:type_name -> circuit.pb.HopMessage.Type\n\t5, // 1: circuit.pb.HopMessage.peer:type_name -> circuit.pb.Peer\n\t6, // 2: circuit.pb.HopMessage.reservation:type_name -> circuit.pb.Reservation\n\t7, // 3: circuit.pb.HopMessage.limit:type_name -> circuit.pb.Limit\n\t0, // 4: circuit.pb.HopMessage.status:type_name -> circuit.pb.Status\n\t2, // 5: circuit.pb.StopMessage.type:type_name -> circuit.pb.StopMessage.Type\n\t5, // 6: circuit.pb.StopMessage.peer:type_name -> circuit.pb.Peer\n\t7, // 7: circuit.pb.StopMessage.limit:type_name -> circuit.pb.Limit\n\t0, // 8: circuit.pb.StopMessage.status:type_name -> circuit.pb.Status\n\t9, // [9:9] is the sub-list for method output_type\n\t9, // [9:9] is the sub-list for method input_type\n\t9, // [9:9] is the sub-list for extension type_name\n\t9, // [9:9] is the sub-list for extension extendee\n\t0, // [0:9] is the sub-list for field type_name\n}\n\nfunc init() { file_p2p_protocol_circuitv2_pb_circuit_proto_init() }\nfunc file_p2p_protocol_circuitv2_pb_circuit_proto_init() {\n\tif File_p2p_protocol_circuitv2_pb_circuit_proto != nil {\n\t\treturn\n\t}\n\tfile_p2p_protocol_circuitv2_pb_circuit_proto_msgTypes[0].OneofWrappers = []any{}\n\tfile_p2p_protocol_circuitv2_pb_circuit_proto_msgTypes[1].OneofWrappers = []any{}\n\tfile_p2p_protocol_circuitv2_pb_circuit_proto_msgTypes[2].OneofWrappers = []any{}\n\tfile_p2p_protocol_circuitv2_pb_circuit_proto_msgTypes[3].OneofWrappers = []any{}\n\tfile_p2p_protocol_circuitv2_pb_circuit_proto_msgTypes[4].OneofWrappers = []any{}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_p2p_protocol_circuitv2_pb_circuit_proto_rawDesc), len(file_p2p_protocol_circuitv2_pb_circuit_proto_rawDesc)),\n\t\t\tNumEnums:      3,\n\t\t\tNumMessages:   5,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_p2p_protocol_circuitv2_pb_circuit_proto_goTypes,\n\t\tDependencyIndexes: file_p2p_protocol_circuitv2_pb_circuit_proto_depIdxs,\n\t\tEnumInfos:         file_p2p_protocol_circuitv2_pb_circuit_proto_enumTypes,\n\t\tMessageInfos:      file_p2p_protocol_circuitv2_pb_circuit_proto_msgTypes,\n\t}.Build()\n\tFile_p2p_protocol_circuitv2_pb_circuit_proto = out.File\n\tfile_p2p_protocol_circuitv2_pb_circuit_proto_goTypes = nil\n\tfile_p2p_protocol_circuitv2_pb_circuit_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "p2p/protocol/circuitv2/pb/circuit.proto",
    "content": "syntax = \"proto3\";\n\npackage circuit.pb;\n\noption go_package = \"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/pb\";\n\nmessage HopMessage {\n  enum Type {\n    RESERVE = 0;\n    CONNECT = 1;\n    STATUS = 2;\n  }\n\n  // This field is marked optional for backwards compatibility with proto2.\n  // Users should make sure to always set this.\n  optional Type type = 1;\n\n  optional Peer peer = 2;\n  optional Reservation reservation = 3;\n  optional Limit limit = 4;\n\n  optional Status status = 5;\n}\n\nmessage StopMessage {\n  enum Type {\n    CONNECT = 0;\n    STATUS = 1;\n  }\n\n  // This field is marked optional for backwards compatibility with proto2.\n  // Users should make sure to always set this.\n  optional Type type = 1;\n\n  optional Peer peer = 2;\n  optional Limit limit = 3;\n\n  optional Status status = 4;\n}\n\nmessage Peer {\n  // This field is marked optional for backwards compatibility with proto2.\n  // Users should make sure to always set this.\n  optional bytes id = 1;\n  repeated bytes addrs = 2;\n}\n\nmessage Reservation {\n  // This field is marked optional for backwards compatibility with proto2.\n  // Users should make sure to always set this.\n  optional uint64 expire = 1; // Unix expiration time (UTC)\n  repeated bytes addrs = 2;   // relay addrs for reserving peer\n  optional bytes voucher = 3; // reservation voucher\n}\n\nmessage Limit {\n  optional uint32 duration = 1; // seconds\n  optional uint64 data = 2;     // bytes\n}\n\nenum Status {\n  // zero value field required for proto3 compatibility\n  UNUSED                  = 0;\n  OK                      = 100;\n  RESERVATION_REFUSED     = 200;\n  RESOURCE_LIMIT_EXCEEDED = 201;\n  PERMISSION_DENIED       = 202;\n  CONNECTION_FAILED       = 203;\n  NO_RESERVATION          = 204;\n  MALFORMED_MESSAGE       = 400;\n  UNEXPECTED_MESSAGE      = 401;\n}\n"
  },
  {
    "path": "p2p/protocol/circuitv2/pb/voucher.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.6\n// \tprotoc        v5.29.2\n// source: p2p/protocol/circuitv2/pb/voucher.proto\n\npackage pb\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype ReservationVoucher struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// These fields are marked optional for backwards compatibility with proto2.\n\t// Users should make sure to always set these.\n\tRelay         []byte  `protobuf:\"bytes,1,opt,name=relay,proto3,oneof\" json:\"relay,omitempty\"`\n\tPeer          []byte  `protobuf:\"bytes,2,opt,name=peer,proto3,oneof\" json:\"peer,omitempty\"`\n\tExpiration    *uint64 `protobuf:\"varint,3,opt,name=expiration,proto3,oneof\" json:\"expiration,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ReservationVoucher) Reset() {\n\t*x = ReservationVoucher{}\n\tmi := &file_p2p_protocol_circuitv2_pb_voucher_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ReservationVoucher) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ReservationVoucher) ProtoMessage() {}\n\nfunc (x *ReservationVoucher) ProtoReflect() protoreflect.Message {\n\tmi := &file_p2p_protocol_circuitv2_pb_voucher_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ReservationVoucher.ProtoReflect.Descriptor instead.\nfunc (*ReservationVoucher) Descriptor() ([]byte, []int) {\n\treturn file_p2p_protocol_circuitv2_pb_voucher_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *ReservationVoucher) GetRelay() []byte {\n\tif x != nil {\n\t\treturn x.Relay\n\t}\n\treturn nil\n}\n\nfunc (x *ReservationVoucher) GetPeer() []byte {\n\tif x != nil {\n\t\treturn x.Peer\n\t}\n\treturn nil\n}\n\nfunc (x *ReservationVoucher) GetExpiration() uint64 {\n\tif x != nil && x.Expiration != nil {\n\t\treturn *x.Expiration\n\t}\n\treturn 0\n}\n\nvar File_p2p_protocol_circuitv2_pb_voucher_proto protoreflect.FileDescriptor\n\nconst file_p2p_protocol_circuitv2_pb_voucher_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"'p2p/protocol/circuitv2/pb/voucher.proto\\x12\\n\" +\n\t\"circuit.pb\\\"\\x8f\\x01\\n\" +\n\t\"\\x12ReservationVoucher\\x12\\x19\\n\" +\n\t\"\\x05relay\\x18\\x01 \\x01(\\fH\\x00R\\x05relay\\x88\\x01\\x01\\x12\\x17\\n\" +\n\t\"\\x04peer\\x18\\x02 \\x01(\\fH\\x01R\\x04peer\\x88\\x01\\x01\\x12#\\n\" +\n\t\"\\n\" +\n\t\"expiration\\x18\\x03 \\x01(\\x04H\\x02R\\n\" +\n\t\"expiration\\x88\\x01\\x01B\\b\\n\" +\n\t\"\\x06_relayB\\a\\n\" +\n\t\"\\x05_peerB\\r\\n\" +\n\t\"\\v_expirationB7Z5github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/pbb\\x06proto3\"\n\nvar (\n\tfile_p2p_protocol_circuitv2_pb_voucher_proto_rawDescOnce sync.Once\n\tfile_p2p_protocol_circuitv2_pb_voucher_proto_rawDescData []byte\n)\n\nfunc file_p2p_protocol_circuitv2_pb_voucher_proto_rawDescGZIP() []byte {\n\tfile_p2p_protocol_circuitv2_pb_voucher_proto_rawDescOnce.Do(func() {\n\t\tfile_p2p_protocol_circuitv2_pb_voucher_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_p2p_protocol_circuitv2_pb_voucher_proto_rawDesc), len(file_p2p_protocol_circuitv2_pb_voucher_proto_rawDesc)))\n\t})\n\treturn file_p2p_protocol_circuitv2_pb_voucher_proto_rawDescData\n}\n\nvar file_p2p_protocol_circuitv2_pb_voucher_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_p2p_protocol_circuitv2_pb_voucher_proto_goTypes = []any{\n\t(*ReservationVoucher)(nil), // 0: circuit.pb.ReservationVoucher\n}\nvar file_p2p_protocol_circuitv2_pb_voucher_proto_depIdxs = []int32{\n\t0, // [0:0] is the sub-list for method output_type\n\t0, // [0:0] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_p2p_protocol_circuitv2_pb_voucher_proto_init() }\nfunc file_p2p_protocol_circuitv2_pb_voucher_proto_init() {\n\tif File_p2p_protocol_circuitv2_pb_voucher_proto != nil {\n\t\treturn\n\t}\n\tfile_p2p_protocol_circuitv2_pb_voucher_proto_msgTypes[0].OneofWrappers = []any{}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_p2p_protocol_circuitv2_pb_voucher_proto_rawDesc), len(file_p2p_protocol_circuitv2_pb_voucher_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_p2p_protocol_circuitv2_pb_voucher_proto_goTypes,\n\t\tDependencyIndexes: file_p2p_protocol_circuitv2_pb_voucher_proto_depIdxs,\n\t\tMessageInfos:      file_p2p_protocol_circuitv2_pb_voucher_proto_msgTypes,\n\t}.Build()\n\tFile_p2p_protocol_circuitv2_pb_voucher_proto = out.File\n\tfile_p2p_protocol_circuitv2_pb_voucher_proto_goTypes = nil\n\tfile_p2p_protocol_circuitv2_pb_voucher_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "p2p/protocol/circuitv2/pb/voucher.proto",
    "content": "syntax = \"proto3\";\n\npackage circuit.pb;\n\noption go_package = \"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/pb\";\n\nmessage ReservationVoucher {\n  // These fields are marked optional for backwards compatibility with proto2.\n  // Users should make sure to always set these.\n  optional bytes relay = 1;\n  optional bytes peer = 2;\n  optional uint64 expiration = 3;\n}\n"
  },
  {
    "path": "p2p/protocol/circuitv2/proto/protocol.go",
    "content": "package proto\n\nconst (\n\tProtoIDv2Hop  = \"/libp2p/circuit/relay/0.2.0/hop\"\n\tProtoIDv2Stop = \"/libp2p/circuit/relay/0.2.0/stop\"\n)\n"
  },
  {
    "path": "p2p/protocol/circuitv2/proto/voucher.go",
    "content": "package proto\n\nimport (\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/record\"\n\tpbv2 \"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/pb\"\n\n\t\"google.golang.org/protobuf/proto\"\n)\n\nconst RecordDomain = \"libp2p-relay-rsvp\"\n\n// TODO: register in multicodec table in https://github.com/multiformats/multicodec\nvar RecordCodec = []byte{0x03, 0x02}\n\nfunc init() {\n\trecord.RegisterType(&ReservationVoucher{})\n}\n\ntype ReservationVoucher struct {\n\t// Relay is the ID of the peer providing relay service\n\tRelay peer.ID\n\t// Peer is the ID of the peer receiving relay service through Relay\n\tPeer peer.ID\n\t// Expiration is the expiration time of the reservation\n\tExpiration time.Time\n}\n\nvar _ record.Record = (*ReservationVoucher)(nil)\n\nfunc (rv *ReservationVoucher) Domain() string {\n\treturn RecordDomain\n}\n\nfunc (rv *ReservationVoucher) Codec() []byte {\n\treturn RecordCodec\n}\n\nfunc (rv *ReservationVoucher) MarshalRecord() ([]byte, error) {\n\texpiration := uint64(rv.Expiration.Unix())\n\treturn proto.Marshal(&pbv2.ReservationVoucher{\n\t\tRelay:      []byte(rv.Relay),\n\t\tPeer:       []byte(rv.Peer),\n\t\tExpiration: &expiration,\n\t})\n}\n\nfunc (rv *ReservationVoucher) UnmarshalRecord(blob []byte) error {\n\tpbrv := pbv2.ReservationVoucher{}\n\terr := proto.Unmarshal(blob, &pbrv)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\trv.Relay, err = peer.IDFromBytes(pbrv.GetRelay())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\trv.Peer, err = peer.IDFromBytes(pbrv.GetPeer())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\trv.Expiration = time.Unix(int64(pbrv.GetExpiration()), 0)\n\treturn nil\n}\n"
  },
  {
    "path": "p2p/protocol/circuitv2/proto/voucher_test.go",
    "content": "package proto\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/record\"\n)\n\nfunc TestReservationVoucher(t *testing.T) {\n\trelayPrivk, relayPubk, err := crypto.GenerateKeyPair(crypto.Ed25519, 0)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t_, peerPubk, err := crypto.GenerateKeyPair(crypto.Ed25519, 0)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\trelayID, err := peer.IDFromPublicKey(relayPubk)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tpeerID, err := peer.IDFromPublicKey(peerPubk)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\trsvp := &ReservationVoucher{\n\t\tRelay:      relayID,\n\t\tPeer:       peerID,\n\t\tExpiration: time.Now().Add(time.Hour),\n\t}\n\n\tenvelope, err := record.Seal(rsvp, relayPrivk)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tblob, err := envelope.Marshal()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t_, rec, err := record.ConsumeEnvelope(blob, RecordDomain)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\trsvp2, ok := rec.(*ReservationVoucher)\n\tif !ok {\n\t\tt.Fatalf(\"invalid record type %+T\", rec)\n\t}\n\n\tif rsvp.Relay != rsvp2.Relay {\n\t\tt.Fatal(\"relay IDs don't match\")\n\t}\n\tif rsvp.Peer != rsvp2.Peer {\n\t\tt.Fatal(\"peer IDs don't match\")\n\t}\n\tif rsvp.Expiration.Unix() != rsvp2.Expiration.Unix() {\n\t\tt.Fatal(\"expirations don't match\")\n\t}\n}\n"
  },
  {
    "path": "p2p/protocol/circuitv2/relay/acl.go",
    "content": "package relay\n\nimport (\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\n// ACLFilter is an Access Control mechanism for relayed connect.\ntype ACLFilter interface {\n\t// AllowReserve returns true if a reservation from a peer with the given peer ID and multiaddr\n\t// is allowed.\n\tAllowReserve(p peer.ID, a ma.Multiaddr) bool\n\t// AllowConnect returns true if a source peer, with a given multiaddr is allowed to connect\n\t// to a destination peer.\n\tAllowConnect(src peer.ID, srcAddr ma.Multiaddr, dest peer.ID) bool\n}\n"
  },
  {
    "path": "p2p/protocol/circuitv2/relay/constraints.go",
    "content": "package relay\n\nimport (\n\t\"errors\"\n\t\"slices\"\n\t\"sync\"\n\t\"time\"\n\n\tasnutil \"github.com/libp2p/go-libp2p-asn-util\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\nvar (\n\terrTooManyReservations       = errors.New(\"too many reservations\")\n\terrTooManyReservationsForIP  = errors.New(\"too many peers for IP address\")\n\terrTooManyReservationsForASN = errors.New(\"too many peers for ASN\")\n)\n\ntype peerWithExpiry struct {\n\tExpiry time.Time\n\tPeer   peer.ID\n}\n\n// constraints implements various reservation constraints\ntype constraints struct {\n\trc *Resources\n\n\tmutex sync.Mutex\n\ttotal []peerWithExpiry\n\tips   map[string][]peerWithExpiry\n\tasns  map[uint32][]peerWithExpiry\n}\n\n// newConstraints creates a new constraints object.\n// The methods are *not* thread-safe; an external lock must be held if synchronization\n// is required.\nfunc newConstraints(rc *Resources) *constraints {\n\treturn &constraints{\n\t\trc:   rc,\n\t\tips:  make(map[string][]peerWithExpiry),\n\t\tasns: make(map[uint32][]peerWithExpiry),\n\t}\n}\n\n// Reserve adds a reservation for a given peer with a given multiaddr.\n// If adding this reservation violates IP, ASN, or total reservation constraints, an error is returned.\nfunc (c *constraints) Reserve(p peer.ID, a ma.Multiaddr, expiry time.Time) error {\n\tc.mutex.Lock()\n\tdefer c.mutex.Unlock()\n\n\tnow := time.Now()\n\tc.cleanup(now)\n\t// To handle refreshes correctly, remove the existing reservation for the peer.\n\tc.cleanupPeer(p)\n\n\tif len(c.total) >= c.rc.MaxReservations {\n\t\treturn errTooManyReservations\n\t}\n\n\tip, err := manet.ToIP(a)\n\tif err != nil {\n\t\treturn errors.New(\"no IP address associated with peer\")\n\t}\n\n\tipReservations := c.ips[ip.String()]\n\tif len(ipReservations) >= c.rc.MaxReservationsPerIP {\n\t\treturn errTooManyReservationsForIP\n\t}\n\n\tvar asnReservations []peerWithExpiry\n\tvar asn uint32\n\tif ip.To4() == nil {\n\t\tasn = asnutil.AsnForIPv6(ip)\n\t\tif asn != 0 {\n\t\t\tasnReservations = c.asns[asn]\n\t\t\tif len(asnReservations) >= c.rc.MaxReservationsPerASN {\n\t\t\t\treturn errTooManyReservationsForASN\n\t\t\t}\n\t\t}\n\t}\n\n\tc.total = append(c.total, peerWithExpiry{Expiry: expiry, Peer: p})\n\n\tipReservations = append(ipReservations, peerWithExpiry{Expiry: expiry, Peer: p})\n\tc.ips[ip.String()] = ipReservations\n\n\tif asn != 0 {\n\t\tasnReservations = append(asnReservations, peerWithExpiry{Expiry: expiry, Peer: p})\n\t\tc.asns[asn] = asnReservations\n\t}\n\treturn nil\n}\n\nfunc (c *constraints) cleanup(now time.Time) {\n\texpireFunc := func(pe peerWithExpiry) bool {\n\t\treturn pe.Expiry.Before(now)\n\t}\n\tc.total = slices.DeleteFunc(c.total, expireFunc)\n\tfor k, ipReservations := range c.ips {\n\t\tc.ips[k] = slices.DeleteFunc(ipReservations, expireFunc)\n\t\tif len(c.ips[k]) == 0 {\n\t\t\tdelete(c.ips, k)\n\t\t}\n\t}\n\tfor k, asnReservations := range c.asns {\n\t\tc.asns[k] = slices.DeleteFunc(asnReservations, expireFunc)\n\t\tif len(c.asns[k]) == 0 {\n\t\t\tdelete(c.asns, k)\n\t\t}\n\t}\n}\n\nfunc (c *constraints) cleanupPeer(p peer.ID) {\n\tremoveFunc := func(pe peerWithExpiry) bool {\n\t\treturn pe.Peer == p\n\t}\n\tc.total = slices.DeleteFunc(c.total, removeFunc)\n\tfor k, ipReservations := range c.ips {\n\t\tc.ips[k] = slices.DeleteFunc(ipReservations, removeFunc)\n\t\tif len(c.ips[k]) == 0 {\n\t\t\tdelete(c.ips, k)\n\t\t}\n\t}\n\tfor k, asnReservations := range c.asns {\n\t\tc.asns[k] = slices.DeleteFunc(asnReservations, removeFunc)\n\t\tif len(c.asns[k]) == 0 {\n\t\t\tdelete(c.asns, k)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "p2p/protocol/circuitv2/relay/constraints_test.go",
    "content": "package relay\n\nimport (\n\t\"crypto/rand\"\n\t\"fmt\"\n\t\"math\"\n\t\"net\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/test\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\nfunc randomIPv4Addr(t *testing.T) ma.Multiaddr {\n\tt.Helper()\n\tb := make([]byte, 4)\n\trand.Read(b)\n\taddr, err := ma.NewMultiaddr(fmt.Sprintf(\"/ip4/%s/tcp/1234\", net.IP(b)))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\treturn addr\n}\n\nfunc TestConstraints(t *testing.T) {\n\tinfResources := func() *Resources {\n\t\treturn &Resources{\n\t\t\tMaxReservations:        math.MaxInt32,\n\t\t\tMaxReservationsPerPeer: math.MaxInt32,\n\t\t\tMaxReservationsPerIP:   math.MaxInt32,\n\t\t\tMaxReservationsPerASN:  math.MaxInt32,\n\t\t}\n\t}\n\tconst limit = 7\n\texpiry := time.Now().Add(30 * time.Minute)\n\n\tt.Run(\"total reservations\", func(t *testing.T) {\n\t\tres := infResources()\n\t\tres.MaxReservations = limit\n\t\tc := newConstraints(res)\n\t\tfor range limit {\n\t\t\tif err := c.Reserve(test.RandPeerIDFatal(t), randomIPv4Addr(t), expiry); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t}\n\t\tif err := c.Reserve(test.RandPeerIDFatal(t), randomIPv4Addr(t), expiry); err != errTooManyReservations {\n\t\t\tt.Fatalf(\"expected to run into total reservation limit, got %v\", err)\n\t\t}\n\t})\n\n\tt.Run(\"updates reservations on the same peer\", func(t *testing.T) {\n\t\tp := test.RandPeerIDFatal(t)\n\t\tp2 := test.RandPeerIDFatal(t)\n\t\tres := infResources()\n\t\tres.MaxReservationsPerIP = 1\n\t\tc := newConstraints(res)\n\n\t\tipAddr := randomIPv4Addr(t)\n\t\tif err := c.Reserve(p, ipAddr, expiry); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif err := c.Reserve(p2, ipAddr, expiry); err != errTooManyReservationsForIP {\n\t\t\tt.Fatalf(\"expected to run into IP reservation limit as this IP has already been reserved by a different peer, got %v\", err)\n\t\t}\n\t\tif err := c.Reserve(p, randomIPv4Addr(t), expiry); err != nil {\n\t\t\tt.Fatalf(\"expected to update existing reservation for peer, got %v\", err)\n\t\t}\n\t\tif err := c.Reserve(p2, ipAddr, expiry); err != nil {\n\t\t\tt.Fatalf(\"expected reservation for different peer to be possible, got %v\", err)\n\t\t}\n\t})\n\n\tt.Run(\"reservations per IP\", func(t *testing.T) {\n\t\tip := randomIPv4Addr(t)\n\t\tres := infResources()\n\t\tres.MaxReservationsPerIP = limit\n\t\tc := newConstraints(res)\n\t\tfor range limit {\n\t\t\tif err := c.Reserve(test.RandPeerIDFatal(t), ip, expiry); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t}\n\t\tif err := c.Reserve(test.RandPeerIDFatal(t), ip, expiry); err != errTooManyReservationsForIP {\n\t\t\tt.Fatalf(\"expected to run into total reservation limit, got %v\", err)\n\t\t}\n\t\tif err := c.Reserve(test.RandPeerIDFatal(t), randomIPv4Addr(t), expiry); err != nil {\n\t\t\tt.Fatalf(\"expected reservation for different IP to be possible, got %v\", err)\n\t\t}\n\t})\n\n\tt.Run(\"reservations per ASN\", func(t *testing.T) {\n\t\tgetAddr := func(t *testing.T, ip net.IP) ma.Multiaddr {\n\t\t\tt.Helper()\n\t\t\taddr, err := ma.NewMultiaddr(fmt.Sprintf(\"/ip6/%s/tcp/1234\", ip))\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\treturn addr\n\t\t}\n\n\t\tres := infResources()\n\t\tres.MaxReservationsPerASN = limit\n\t\tc := newConstraints(res)\n\t\tconst ipv6Prefix = \"2a03:2880:f003:c07:face:b00c::\"\n\t\tfor i := range limit {\n\t\t\taddr := getAddr(t, net.ParseIP(fmt.Sprintf(\"%s%d\", ipv6Prefix, i+1)))\n\t\t\tif err := c.Reserve(test.RandPeerIDFatal(t), addr, expiry); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t}\n\t\tif err := c.Reserve(test.RandPeerIDFatal(t), getAddr(t, net.ParseIP(fmt.Sprintf(\"%s%d\", ipv6Prefix, 42))), expiry); err != errTooManyReservationsForASN {\n\t\t\tt.Fatalf(\"expected to run into total reservation limit, got %v\", err)\n\t\t}\n\t\tif err := c.Reserve(test.RandPeerIDFatal(t), randomIPv4Addr(t), expiry); err != nil {\n\t\t\tt.Fatalf(\"expected reservation for different IP to be possible, got %v\", err)\n\t\t}\n\t})\n}\n\nfunc TestConstraintsCleanup(t *testing.T) {\n\tconst limit = 7\n\tvalidity := 500 * time.Millisecond\n\texpiry := time.Now().Add(validity)\n\tres := &Resources{\n\t\tMaxReservations:        limit,\n\t\tMaxReservationsPerPeer: math.MaxInt32,\n\t\tMaxReservationsPerIP:   math.MaxInt32,\n\t\tMaxReservationsPerASN:  math.MaxInt32,\n\t}\n\tc := newConstraints(res)\n\tfor range limit {\n\t\tif err := c.Reserve(test.RandPeerIDFatal(t), randomIPv4Addr(t), expiry); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n\tif err := c.Reserve(test.RandPeerIDFatal(t), randomIPv4Addr(t), expiry); err != errTooManyReservations {\n\t\tt.Fatalf(\"expected to run into total reservation limit, got %v\", err)\n\t}\n\n\ttime.Sleep(validity + time.Millisecond)\n\tif err := c.Reserve(test.RandPeerIDFatal(t), randomIPv4Addr(t), expiry); err != nil {\n\t\tt.Fatalf(\"expected old reservations to have been garbage collected, %v\", err)\n\t}\n}\n"
  },
  {
    "path": "p2p/protocol/circuitv2/relay/metrics.go",
    "content": "package relay\n\nimport (\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/p2p/metricshelper\"\n\tpbv2 \"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/pb\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n)\n\nconst metricNamespace = \"libp2p_relaysvc\"\n\nvar (\n\tstatus = prometheus.NewGauge(\n\t\tprometheus.GaugeOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"status\",\n\t\t\tHelp:      \"Relay Status\",\n\t\t},\n\t)\n\n\treservationsTotal = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"reservations_total\",\n\t\t\tHelp:      \"Relay Reservation Request\",\n\t\t},\n\t\t[]string{\"type\"},\n\t)\n\treservationRequestResponseStatusTotal = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"reservation_request_response_status_total\",\n\t\t\tHelp:      \"Relay Reservation Request Response Status\",\n\t\t},\n\t\t[]string{\"status\"},\n\t)\n\treservationRejectionsTotal = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"reservation_rejections_total\",\n\t\t\tHelp:      \"Relay Reservation Rejected Reason\",\n\t\t},\n\t\t[]string{\"reason\"},\n\t)\n\n\tconnectionsTotal = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"connections_total\",\n\t\t\tHelp:      \"Relay Connection Total\",\n\t\t},\n\t\t[]string{\"type\"},\n\t)\n\tconnectionRequestResponseStatusTotal = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"connection_request_response_status_total\",\n\t\t\tHelp:      \"Relay Connection Request Status\",\n\t\t},\n\t\t[]string{\"status\"},\n\t)\n\tconnectionRejectionsTotal = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"connection_rejections_total\",\n\t\t\tHelp:      \"Relay Connection Rejected Reason\",\n\t\t},\n\t\t[]string{\"reason\"},\n\t)\n\tconnectionDurationSeconds = prometheus.NewHistogram(\n\t\tprometheus.HistogramOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"connection_duration_seconds\",\n\t\t\tHelp:      \"Relay Connection Duration\",\n\t\t},\n\t)\n\n\tdataTransferredBytesTotal = prometheus.NewCounter(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"data_transferred_bytes_total\",\n\t\t\tHelp:      \"Bytes Transferred Total\",\n\t\t},\n\t)\n\n\tcollectors = []prometheus.Collector{\n\t\tstatus,\n\t\treservationsTotal,\n\t\treservationRequestResponseStatusTotal,\n\t\treservationRejectionsTotal,\n\t\tconnectionsTotal,\n\t\tconnectionRequestResponseStatusTotal,\n\t\tconnectionRejectionsTotal,\n\t\tconnectionDurationSeconds,\n\t\tdataTransferredBytesTotal,\n\t}\n)\n\nconst (\n\trequestStatusOK       = \"ok\"\n\trequestStatusRejected = \"rejected\"\n\trequestStatusError    = \"error\"\n)\n\n// MetricsTracer is the interface for tracking metrics for relay service\ntype MetricsTracer interface {\n\t// RelayStatus tracks whether the service is currently active\n\tRelayStatus(enabled bool)\n\n\t// ConnectionOpened tracks metrics on opening a relay connection\n\tConnectionOpened()\n\t// ConnectionClosed tracks metrics on closing a relay connection\n\tConnectionClosed(d time.Duration)\n\t// ConnectionRequestHandled tracks metrics on handling a relay connection request\n\tConnectionRequestHandled(status pbv2.Status)\n\n\t// ReservationAllowed tracks metrics on opening or renewing a relay reservation\n\tReservationAllowed(isRenewal bool)\n\t// ReservationRequestClosed tracks metrics on closing a relay reservation\n\tReservationClosed(cnt int)\n\t// ReservationRequestHandled tracks metrics on handling a relay reservation request\n\tReservationRequestHandled(status pbv2.Status)\n\n\t// BytesTransferred tracks the total bytes transferred by the relay service\n\tBytesTransferred(cnt int)\n}\n\ntype metricsTracer struct{}\n\nvar _ MetricsTracer = &metricsTracer{}\n\ntype metricsTracerSetting struct {\n\treg prometheus.Registerer\n}\n\ntype MetricsTracerOption func(*metricsTracerSetting)\n\nfunc WithRegisterer(reg prometheus.Registerer) MetricsTracerOption {\n\treturn func(s *metricsTracerSetting) {\n\t\tif reg != nil {\n\t\t\ts.reg = reg\n\t\t}\n\t}\n}\n\nfunc NewMetricsTracer(opts ...MetricsTracerOption) MetricsTracer {\n\tsetting := &metricsTracerSetting{reg: prometheus.DefaultRegisterer}\n\tfor _, opt := range opts {\n\t\topt(setting)\n\t}\n\tmetricshelper.RegisterCollectors(setting.reg, collectors...)\n\treturn &metricsTracer{}\n}\n\nfunc (mt *metricsTracer) RelayStatus(enabled bool) {\n\tif enabled {\n\t\tstatus.Set(1)\n\t} else {\n\t\tstatus.Set(0)\n\t}\n}\n\nfunc (mt *metricsTracer) ConnectionOpened() {\n\ttags := metricshelper.GetStringSlice()\n\tdefer metricshelper.PutStringSlice(tags)\n\t*tags = append(*tags, \"opened\")\n\n\tconnectionsTotal.WithLabelValues(*tags...).Add(1)\n}\n\nfunc (mt *metricsTracer) ConnectionClosed(d time.Duration) {\n\ttags := metricshelper.GetStringSlice()\n\tdefer metricshelper.PutStringSlice(tags)\n\t*tags = append(*tags, \"closed\")\n\n\tconnectionsTotal.WithLabelValues(*tags...).Add(1)\n\tconnectionDurationSeconds.Observe(d.Seconds())\n}\n\nfunc (mt *metricsTracer) ConnectionRequestHandled(status pbv2.Status) {\n\ttags := metricshelper.GetStringSlice()\n\tdefer metricshelper.PutStringSlice(tags)\n\n\trespStatus := getResponseStatus(status)\n\n\t*tags = append(*tags, respStatus)\n\tconnectionRequestResponseStatusTotal.WithLabelValues(*tags...).Add(1)\n\tif respStatus == requestStatusRejected {\n\t\t*tags = (*tags)[:0]\n\t\t*tags = append(*tags, getRejectionReason(status))\n\t\tconnectionRejectionsTotal.WithLabelValues(*tags...).Add(1)\n\t}\n}\n\nfunc (mt *metricsTracer) ReservationAllowed(isRenewal bool) {\n\ttags := metricshelper.GetStringSlice()\n\tdefer metricshelper.PutStringSlice(tags)\n\tif isRenewal {\n\t\t*tags = append(*tags, \"renewed\")\n\t} else {\n\t\t*tags = append(*tags, \"opened\")\n\t}\n\n\treservationsTotal.WithLabelValues(*tags...).Add(1)\n}\n\nfunc (mt *metricsTracer) ReservationClosed(cnt int) {\n\ttags := metricshelper.GetStringSlice()\n\tdefer metricshelper.PutStringSlice(tags)\n\t*tags = append(*tags, \"closed\")\n\n\treservationsTotal.WithLabelValues(*tags...).Add(float64(cnt))\n}\n\nfunc (mt *metricsTracer) ReservationRequestHandled(status pbv2.Status) {\n\ttags := metricshelper.GetStringSlice()\n\tdefer metricshelper.PutStringSlice(tags)\n\n\trespStatus := getResponseStatus(status)\n\n\t*tags = append(*tags, respStatus)\n\treservationRequestResponseStatusTotal.WithLabelValues(*tags...).Add(1)\n\tif respStatus == requestStatusRejected {\n\t\t*tags = (*tags)[:0]\n\t\t*tags = append(*tags, getRejectionReason(status))\n\t\treservationRejectionsTotal.WithLabelValues(*tags...).Add(1)\n\t}\n}\n\nfunc (mt *metricsTracer) BytesTransferred(cnt int) {\n\tdataTransferredBytesTotal.Add(float64(cnt))\n}\n\nfunc getResponseStatus(status pbv2.Status) string {\n\tresponseStatus := \"unknown\"\n\tswitch status {\n\tcase pbv2.Status_RESERVATION_REFUSED,\n\t\tpbv2.Status_RESOURCE_LIMIT_EXCEEDED,\n\t\tpbv2.Status_PERMISSION_DENIED,\n\t\tpbv2.Status_NO_RESERVATION,\n\t\tpbv2.Status_MALFORMED_MESSAGE:\n\n\t\tresponseStatus = requestStatusRejected\n\tcase pbv2.Status_UNEXPECTED_MESSAGE, pbv2.Status_CONNECTION_FAILED:\n\t\tresponseStatus = requestStatusError\n\tcase pbv2.Status_OK:\n\t\tresponseStatus = requestStatusOK\n\t}\n\treturn responseStatus\n}\n\nfunc getRejectionReason(status pbv2.Status) string {\n\treason := \"unknown\"\n\tswitch status {\n\tcase pbv2.Status_RESERVATION_REFUSED:\n\t\treason = \"ip constraint violation\"\n\tcase pbv2.Status_RESOURCE_LIMIT_EXCEEDED:\n\t\treason = \"resource limit exceeded\"\n\tcase pbv2.Status_PERMISSION_DENIED:\n\t\treason = \"permission denied\"\n\tcase pbv2.Status_NO_RESERVATION:\n\t\treason = \"no reservation\"\n\tcase pbv2.Status_MALFORMED_MESSAGE:\n\t\treason = \"malformed message\"\n\t}\n\treturn reason\n}\n"
  },
  {
    "path": "p2p/protocol/circuitv2/relay/metrics_test.go",
    "content": "//go:build nocover\n\npackage relay\n\nimport (\n\t\"math/rand\"\n\t\"testing\"\n\t\"time\"\n\n\tpbv2 \"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/pb\"\n)\n\nfunc TestNoCoverNoAlloc(t *testing.T) {\n\tstatuses := []pbv2.Status{\n\t\tpbv2.Status_OK,\n\t\tpbv2.Status_NO_RESERVATION,\n\t\tpbv2.Status_RESOURCE_LIMIT_EXCEEDED,\n\t\tpbv2.Status_PERMISSION_DENIED,\n\t}\n\tmt := NewMetricsTracer()\n\ttests := map[string]func(){\n\t\t\"RelayStatus\":               func() { mt.RelayStatus(rand.Intn(2) == 1) },\n\t\t\"ConnectionOpened\":          func() { mt.ConnectionOpened() },\n\t\t\"ConnectionClosed\":          func() { mt.ConnectionClosed(time.Duration(rand.Intn(10)) * time.Second) },\n\t\t\"ConnectionRequestHandled\":  func() { mt.ConnectionRequestHandled(statuses[rand.Intn(len(statuses))]) },\n\t\t\"ReservationAllowed\":        func() { mt.ReservationAllowed(rand.Intn(2) == 1) },\n\t\t\"ReservationClosed\":         func() { mt.ReservationClosed(rand.Intn(10)) },\n\t\t\"ReservationRequestHandled\": func() { mt.ReservationRequestHandled(statuses[rand.Intn(len(statuses))]) },\n\t\t\"BytesTransferred\":          func() { mt.BytesTransferred(rand.Intn(1000)) },\n\t}\n\tfor method, f := range tests {\n\t\tallocs := testing.AllocsPerRun(1000, f)\n\t\tif allocs > 0 {\n\t\t\tt.Fatalf(\"Alloc Test: %s, got: %0.2f, expected: 0 allocs\", method, allocs)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "p2p/protocol/circuitv2/relay/options.go",
    "content": "package relay\n\nimport (\n\t\"github.com/multiformats/go-multiaddr\"\n)\n\ntype Option func(*Relay) error\n\n// WithResources is a Relay option that sets specific relay resources for the relay.\nfunc WithResources(rc Resources) Option {\n\treturn func(r *Relay) error {\n\t\tr.rc = rc\n\t\treturn nil\n\t}\n}\n\n// WithLimit is a Relay option that sets only the relayed connection limits for the relay.\nfunc WithLimit(limit *RelayLimit) Option {\n\treturn func(r *Relay) error {\n\t\tr.rc.Limit = limit\n\t\treturn nil\n\t}\n}\n\n// Reservation address function used to promote addresses to connected nodes\ntype ReservationAddressFilterFunc func(addr multiaddr.Multiaddr) (include bool)\n\n// Overrides the default reservation address filter.\n// This will permit the relay let the client know it have access to non public addresses too.\nfunc WithReservationAddressFilter(filter ReservationAddressFilterFunc) (option Option) {\n\treturn func(r *Relay) (err error) {\n\t\tr.reservationAddrFilter = filter\n\t\treturn nil\n\t}\n}\n\n// WithInfiniteLimits is a Relay option that disables limits.\nfunc WithInfiniteLimits() Option {\n\treturn func(r *Relay) error {\n\t\tr.rc.Limit = nil\n\t\treturn nil\n\t}\n}\n\n// WithACL is a Relay option that supplies an ACLFilter for access control.\nfunc WithACL(acl ACLFilter) Option {\n\treturn func(r *Relay) error {\n\t\tr.acl = acl\n\t\treturn nil\n\t}\n}\n\n// WithMetricsTracer is a Relay option that supplies a MetricsTracer for metrics\nfunc WithMetricsTracer(mt MetricsTracer) Option {\n\treturn func(r *Relay) error {\n\t\tr.metricsTracer = mt\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "p2p/protocol/circuitv2/relay/relay.go",
    "content": "package relay\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/record\"\n\tpbv2 \"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/pb\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/proto\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/util\"\n\n\tpool \"github.com/libp2p/go-buffer-pool\"\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\nconst (\n\tServiceName = \"libp2p.relay/v2\"\n\n\tReservationTagWeight = 10\n\n\tStreamTimeout    = time.Minute\n\tConnectTimeout   = 30 * time.Second\n\tHandshakeTimeout = time.Minute\n\n\trelayHopTag      = \"relay-v2-hop\"\n\trelayHopTagValue = 2\n\n\tmaxMessageSize = 4096\n)\n\nvar log = logging.Logger(\"relay\")\n\n// Relay is the (limited) relay service object.\ntype Relay struct {\n\tctx    context.Context\n\tcancel func()\n\n\treservationAddrFilter ReservationAddressFilterFunc\n\n\thost        host.Host\n\trc          Resources\n\tacl         ACLFilter\n\tconstraints *constraints\n\tscope       network.ResourceScopeSpan\n\tnotifiee    network.Notifiee\n\n\tmx     sync.Mutex\n\trsvp   map[peer.ID]time.Time\n\tconns  map[peer.ID]int\n\tclosed bool\n\n\tselfAddr ma.Multiaddr\n\n\tmetricsTracer MetricsTracer\n}\n\n// New constructs a new limited relay that can provide relay services in the given host.\nfunc New(h host.Host, opts ...Option) (*Relay, error) {\n\tctx, cancel := context.WithCancel(context.Background())\n\n\tr := &Relay{\n\t\tctx:    ctx,\n\t\tcancel: cancel,\n\t\thost:   h,\n\t\trc:     DefaultResources(),\n\t\tacl:    nil,\n\t\trsvp:   make(map[peer.ID]time.Time),\n\t\tconns:  make(map[peer.ID]int),\n\n\t\treservationAddrFilter: manet.IsPublicAddr,\n\t}\n\n\tfor _, opt := range opts {\n\t\terr := opt(r)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error applying relay option: %w\", err)\n\t\t}\n\t}\n\n\t// get a scope for memory reservations at service level\n\terr := h.Network().ResourceManager().ViewService(ServiceName,\n\t\tfunc(s network.ServiceScope) error {\n\t\t\tvar err error\n\t\t\tr.scope, err = s.BeginSpan()\n\t\t\treturn err\n\t\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tr.constraints = newConstraints(&r.rc)\n\tr.selfAddr = ma.StringCast(fmt.Sprintf(\"/p2p/%s\", h.ID()))\n\n\th.SetStreamHandler(proto.ProtoIDv2Hop, r.handleStream)\n\tr.notifiee = &network.NotifyBundle{DisconnectedF: r.disconnected}\n\th.Network().Notify(r.notifiee)\n\n\tif r.metricsTracer != nil {\n\t\tr.metricsTracer.RelayStatus(true)\n\t}\n\tgo r.background()\n\n\treturn r, nil\n}\n\nfunc (r *Relay) Close() error {\n\tr.mx.Lock()\n\tif !r.closed {\n\t\tr.closed = true\n\t\tr.mx.Unlock()\n\n\t\tr.host.RemoveStreamHandler(proto.ProtoIDv2Hop)\n\t\tr.host.Network().StopNotify(r.notifiee)\n\t\tdefer r.scope.Done()\n\t\tr.cancel()\n\t\tr.gc()\n\t\tif r.metricsTracer != nil {\n\t\t\tr.metricsTracer.RelayStatus(false)\n\t\t}\n\t\treturn nil\n\t}\n\tr.mx.Unlock()\n\treturn nil\n}\n\nfunc (r *Relay) handleStream(s network.Stream) {\n\tlog.Info(\"new relay stream\", \"remote_peer\", s.Conn().RemotePeer())\n\n\tif err := s.Scope().SetService(ServiceName); err != nil {\n\t\tlog.Debug(\"error attaching stream to relay service\", \"err\", err)\n\t\ts.Reset()\n\t\treturn\n\t}\n\n\tif err := s.Scope().ReserveMemory(maxMessageSize, network.ReservationPriorityAlways); err != nil {\n\t\tlog.Debug(\"error reserving memory for stream\", \"err\", err)\n\t\ts.Reset()\n\t\treturn\n\t}\n\tdefer s.Scope().ReleaseMemory(maxMessageSize)\n\n\trd := util.NewDelimitedReader(s, maxMessageSize)\n\tdefer rd.Close()\n\n\ts.SetReadDeadline(time.Now().Add(StreamTimeout))\n\n\tvar msg pbv2.HopMessage\n\n\terr := rd.ReadMsg(&msg)\n\tif err != nil {\n\t\tr.handleError(s, pbv2.Status_MALFORMED_MESSAGE)\n\t\treturn\n\t}\n\t// reset stream deadline as message has been read\n\ts.SetReadDeadline(time.Time{})\n\tswitch msg.GetType() {\n\tcase pbv2.HopMessage_RESERVE:\n\t\tstatus := r.handleReserve(s)\n\t\tif r.metricsTracer != nil {\n\t\t\tr.metricsTracer.ReservationRequestHandled(status)\n\t\t}\n\tcase pbv2.HopMessage_CONNECT:\n\t\tstatus := r.handleConnect(s, &msg)\n\t\tif r.metricsTracer != nil {\n\t\t\tr.metricsTracer.ConnectionRequestHandled(status)\n\t\t}\n\tdefault:\n\t\tr.handleError(s, pbv2.Status_MALFORMED_MESSAGE)\n\t}\n}\n\nfunc (r *Relay) handleReserve(s network.Stream) pbv2.Status {\n\tdefer s.Close()\n\tp := s.Conn().RemotePeer()\n\ta := s.Conn().RemoteMultiaddr()\n\n\tif isRelayAddr(a) {\n\t\tlog.Debug(\"refusing relay reservation\",\n\t\t\t\"remote_peer\", p,\n\t\t\t\"reason\", \"reservation attempt over relay connection\")\n\t\tr.handleError(s, pbv2.Status_PERMISSION_DENIED)\n\t\treturn pbv2.Status_PERMISSION_DENIED\n\t}\n\n\tif r.acl != nil && !r.acl.AllowReserve(p, a) {\n\t\tlog.Debug(\"refusing relay reservation\",\n\t\t\t\"remote_peer\", p,\n\t\t\t\"reason\", \"permission denied\")\n\t\tr.handleError(s, pbv2.Status_PERMISSION_DENIED)\n\t\treturn pbv2.Status_PERMISSION_DENIED\n\t}\n\n\tr.mx.Lock()\n\t// Check if relay is still active. Otherwise ConnManager.UnTagPeer will not be called if this block runs after\n\t// Close() call\n\tif r.closed {\n\t\tr.mx.Unlock()\n\t\tlog.Debug(\"refusing relay reservation\",\n\t\t\t\"remote_peer\", p,\n\t\t\t\"reason\", \"relay closed\")\n\t\tr.handleError(s, pbv2.Status_PERMISSION_DENIED)\n\t\treturn pbv2.Status_PERMISSION_DENIED\n\t}\n\tnow := time.Now()\n\texpire := now.Add(r.rc.ReservationTTL)\n\n\t_, exists := r.rsvp[p]\n\tif err := r.constraints.Reserve(p, a, expire); err != nil {\n\t\tr.mx.Unlock()\n\t\tlog.Debug(\"refusing relay reservation\",\n\t\t\t\"remote_peer\", p,\n\t\t\t\"reason\", \"IP constraint violation\",\n\t\t\t\"error\", err)\n\t\tr.handleError(s, pbv2.Status_RESERVATION_REFUSED)\n\t\treturn pbv2.Status_RESERVATION_REFUSED\n\t}\n\n\tr.rsvp[p] = expire\n\tr.host.ConnManager().TagPeer(p, \"relay-reservation\", ReservationTagWeight)\n\tr.mx.Unlock()\n\tif r.metricsTracer != nil {\n\t\tr.metricsTracer.ReservationAllowed(exists)\n\t}\n\n\tlog.Debug(\"reserving relay slot\", \"remote_peer\", p)\n\n\t// Delivery of the reservation might fail for a number of reasons.\n\t// For example, the stream might be reset or the connection might be closed before the reservation is received.\n\t// In that case, the reservation will just be garbage collected later.\n\trsvp := makeReservationMsg(\n\t\tr.reservationAddrFilter,\n\t\tr.host.Peerstore().PrivKey(r.host.ID()),\n\t\tr.host.ID(),\n\t\tr.host.Addrs(),\n\t\tp,\n\t\texpire)\n\tif err := r.writeResponse(s, pbv2.Status_OK, rsvp, r.makeLimitMsg(p)); err != nil {\n\t\tlog.Debug(\"error writing reservation response\",\n\t\t\t\"remote_peer\", p,\n\t\t\t\"reason\", \"retracting reservation\")\n\t\ts.Reset()\n\t\treturn pbv2.Status_CONNECTION_FAILED\n\t}\n\treturn pbv2.Status_OK\n}\n\nfunc (r *Relay) handleConnect(s network.Stream, msg *pbv2.HopMessage) pbv2.Status {\n\tsrc := s.Conn().RemotePeer()\n\ta := s.Conn().RemoteMultiaddr()\n\n\tspan, err := r.scope.BeginSpan()\n\tif err != nil {\n\t\tlog.Debug(\"failed to begin relay transaction\",\n\t\t\t\"error\", err)\n\t\tr.handleError(s, pbv2.Status_RESOURCE_LIMIT_EXCEEDED)\n\t\treturn pbv2.Status_RESOURCE_LIMIT_EXCEEDED\n\t}\n\n\tfail := func(status pbv2.Status) {\n\t\tspan.Done()\n\t\tr.handleError(s, status)\n\t}\n\n\t// reserve buffers for the relay\n\tif err := span.ReserveMemory(2*r.rc.BufferSize, network.ReservationPriorityHigh); err != nil {\n\t\tlog.Debug(\"error reserving memory for relay\",\n\t\t\t\"error\", err)\n\t\tfail(pbv2.Status_RESOURCE_LIMIT_EXCEEDED)\n\t\treturn pbv2.Status_RESOURCE_LIMIT_EXCEEDED\n\t}\n\n\tif isRelayAddr(a) {\n\t\tlog.Debug(\"refusing connection\",\n\t\t\t\"reason\", \"connection attempt over relay connection\")\n\t\tfail(pbv2.Status_PERMISSION_DENIED)\n\t\treturn pbv2.Status_PERMISSION_DENIED\n\t}\n\n\tdest, err := util.PeerToPeerInfoV2(msg.GetPeer())\n\tif err != nil {\n\t\tfail(pbv2.Status_MALFORMED_MESSAGE)\n\t\treturn pbv2.Status_MALFORMED_MESSAGE\n\t}\n\n\tif r.acl != nil && !r.acl.AllowConnect(src, s.Conn().RemoteMultiaddr(), dest.ID) {\n\t\tlog.Debug(\"refusing connection\",\n\t\t\t\"source_peer\", src,\n\t\t\t\"destination_peer\", dest.ID,\n\t\t\t\"reason\", \"permission denied\")\n\t\tfail(pbv2.Status_PERMISSION_DENIED)\n\t\treturn pbv2.Status_PERMISSION_DENIED\n\t}\n\n\tr.mx.Lock()\n\t_, rsvp := r.rsvp[dest.ID]\n\tif !rsvp {\n\t\tr.mx.Unlock()\n\t\tlog.Debug(\"refusing connection\",\n\t\t\t\"source_peer\", src,\n\t\t\t\"destination_peer\", dest.ID,\n\t\t\t\"reason\", \"no reservation\")\n\t\tfail(pbv2.Status_NO_RESERVATION)\n\t\treturn pbv2.Status_NO_RESERVATION\n\t}\n\n\tsrcConns := r.conns[src]\n\tif srcConns >= r.rc.MaxCircuits {\n\t\tr.mx.Unlock()\n\t\tlog.Debug(\"refusing connection\",\n\t\t\t\"source_peer\", src,\n\t\t\t\"destination_peer\", dest.ID,\n\t\t\t\"reason\", \"too many connections from source\")\n\t\tfail(pbv2.Status_RESOURCE_LIMIT_EXCEEDED)\n\t\treturn pbv2.Status_RESOURCE_LIMIT_EXCEEDED\n\t}\n\n\tdestConns := r.conns[dest.ID]\n\tif destConns >= r.rc.MaxCircuits {\n\t\tr.mx.Unlock()\n\t\tlog.Debug(\"refusing connection\",\n\t\t\t\"source_peer\", src,\n\t\t\t\"destination_peer\", dest.ID,\n\t\t\t\"reason\", \"too many connections to destination\")\n\t\tfail(pbv2.Status_RESOURCE_LIMIT_EXCEEDED)\n\t\treturn pbv2.Status_RESOURCE_LIMIT_EXCEEDED\n\t}\n\n\tr.addConn(src)\n\tr.addConn(dest.ID)\n\tr.mx.Unlock()\n\n\tif r.metricsTracer != nil {\n\t\tr.metricsTracer.ConnectionOpened()\n\t}\n\tconnStTime := time.Now()\n\n\tcleanup := func() {\n\t\tdefer span.Done()\n\t\tr.mx.Lock()\n\t\tr.rmConn(src)\n\t\tr.rmConn(dest.ID)\n\t\tr.mx.Unlock()\n\t\tif r.metricsTracer != nil {\n\t\t\tr.metricsTracer.ConnectionClosed(time.Since(connStTime))\n\t\t}\n\t}\n\n\tctx, cancel := context.WithTimeout(r.ctx, ConnectTimeout)\n\tdefer cancel()\n\n\tctx = network.WithNoDial(ctx, \"relay connect\")\n\n\tbs, err := r.host.NewStream(ctx, dest.ID, proto.ProtoIDv2Stop)\n\tif err != nil {\n\t\tlog.Debug(\"error opening relay stream\",\n\t\t\t\"destination_peer\", dest.ID,\n\t\t\t\"err\", err)\n\t\tcleanup()\n\t\tr.handleError(s, pbv2.Status_CONNECTION_FAILED)\n\t\treturn pbv2.Status_CONNECTION_FAILED\n\t}\n\n\tfail = func(status pbv2.Status) {\n\t\tbs.Reset()\n\t\tcleanup()\n\t\tr.handleError(s, status)\n\t}\n\n\tif err := bs.Scope().SetService(ServiceName); err != nil {\n\t\tlog.Debug(\"error attaching stream to relay service\",\n\t\t\t\"error\", err)\n\t\tfail(pbv2.Status_RESOURCE_LIMIT_EXCEEDED)\n\t\treturn pbv2.Status_RESOURCE_LIMIT_EXCEEDED\n\t}\n\n\t// handshake\n\tif err := bs.Scope().ReserveMemory(maxMessageSize, network.ReservationPriorityAlways); err != nil {\n\t\tlog.Debug(\"error reserving memory for stream\",\n\t\t\t\"error\", err)\n\t\tfail(pbv2.Status_RESOURCE_LIMIT_EXCEEDED)\n\t\treturn pbv2.Status_RESOURCE_LIMIT_EXCEEDED\n\t}\n\tdefer bs.Scope().ReleaseMemory(maxMessageSize)\n\n\trd := util.NewDelimitedReader(bs, maxMessageSize)\n\twr := util.NewDelimitedWriter(bs)\n\tdefer rd.Close()\n\n\tvar stopmsg pbv2.StopMessage\n\tstopmsg.Type = pbv2.StopMessage_CONNECT.Enum()\n\tstopmsg.Peer = util.PeerInfoToPeerV2(peer.AddrInfo{ID: src})\n\tstopmsg.Limit = r.makeLimitMsg(dest.ID)\n\n\tbs.SetDeadline(time.Now().Add(HandshakeTimeout))\n\n\terr = wr.WriteMsg(&stopmsg)\n\tif err != nil {\n\t\tlog.Debug(\"error writing stop handshake\")\n\t\tfail(pbv2.Status_CONNECTION_FAILED)\n\t\treturn pbv2.Status_CONNECTION_FAILED\n\t}\n\n\tstopmsg.Reset()\n\n\terr = rd.ReadMsg(&stopmsg)\n\tif err != nil {\n\t\tlog.Debug(\"error reading stop response\",\n\t\t\t\"err\", err)\n\t\tfail(pbv2.Status_CONNECTION_FAILED)\n\t\treturn pbv2.Status_CONNECTION_FAILED\n\t}\n\n\tif t := stopmsg.GetType(); t != pbv2.StopMessage_STATUS {\n\t\tlog.Debug(\"unexpected stop response\",\n\t\t\t\"message_type\", t,\n\t\t\t\"expected\", \"status message\")\n\t\tfail(pbv2.Status_CONNECTION_FAILED)\n\t\treturn pbv2.Status_CONNECTION_FAILED\n\t}\n\n\tif status := stopmsg.GetStatus(); status != pbv2.Status_OK {\n\t\tlog.Debug(\"relay stop failure\",\n\t\t\t\"status\", status)\n\t\tfail(pbv2.Status_CONNECTION_FAILED)\n\t\treturn pbv2.Status_CONNECTION_FAILED\n\t}\n\n\tvar response pbv2.HopMessage\n\tresponse.Type = pbv2.HopMessage_STATUS.Enum()\n\tresponse.Status = pbv2.Status_OK.Enum()\n\tresponse.Limit = r.makeLimitMsg(dest.ID)\n\n\twr = util.NewDelimitedWriter(s)\n\terr = wr.WriteMsg(&response)\n\tif err != nil {\n\t\tlog.Debug(\"error writing relay response\",\n\t\t\t\"err\", err)\n\t\tbs.Reset()\n\t\ts.Reset()\n\t\tcleanup()\n\t\treturn pbv2.Status_CONNECTION_FAILED\n\t}\n\n\t// reset deadline\n\tbs.SetDeadline(time.Time{})\n\n\tlog.Info(\"relaying connection\",\n\t\t\"source_peer\", src,\n\t\t\"destination_peer\", dest.ID)\n\n\tvar goroutines atomic.Int32\n\tgoroutines.Store(2)\n\n\tdone := func() {\n\t\tif goroutines.Add(-1) == 0 {\n\t\t\ts.Close()\n\t\t\tbs.Close()\n\t\t\tcleanup()\n\t\t}\n\t}\n\n\tif r.rc.Limit != nil {\n\t\tdeadline := time.Now().Add(r.rc.Limit.Duration)\n\t\ts.SetDeadline(deadline)\n\t\tbs.SetDeadline(deadline)\n\t\tgo r.relayLimited(s, bs, src, dest.ID, r.rc.Limit.Data, done)\n\t\tgo r.relayLimited(bs, s, dest.ID, src, r.rc.Limit.Data, done)\n\t} else {\n\t\tgo r.relayUnlimited(s, bs, src, dest.ID, done)\n\t\tgo r.relayUnlimited(bs, s, dest.ID, src, done)\n\t}\n\n\treturn pbv2.Status_OK\n}\n\nfunc (r *Relay) addConn(p peer.ID) {\n\tconns := r.conns[p]\n\tconns++\n\tr.conns[p] = conns\n\tif conns == 1 {\n\t\tr.host.ConnManager().TagPeer(p, relayHopTag, relayHopTagValue)\n\t}\n}\n\nfunc (r *Relay) rmConn(p peer.ID) {\n\tconns := r.conns[p]\n\tconns--\n\tif conns > 0 {\n\t\tr.conns[p] = conns\n\t} else {\n\t\tdelete(r.conns, p)\n\t\tr.host.ConnManager().UntagPeer(p, relayHopTag)\n\t}\n}\n\nfunc (r *Relay) relayLimited(src, dest network.Stream, srcID, destID peer.ID, limit int64, done func()) {\n\tdefer done()\n\n\tbuf := pool.Get(r.rc.BufferSize)\n\tdefer pool.Put(buf)\n\n\tlimitedSrc := io.LimitReader(src, limit)\n\n\tcount, err := r.copyWithBuffer(dest, limitedSrc, buf)\n\tif err != nil {\n\t\tlog.Debug(\"relay copy error\", \"err\", err)\n\t\t// Reset both.\n\t\tsrc.Reset()\n\t\tdest.Reset()\n\t} else {\n\t\t// propagate the close\n\t\tdest.CloseWrite()\n\t\tif count == limit {\n\t\t\t// we've reached the limit, discard further input\n\t\t\tsrc.CloseRead()\n\t\t}\n\t}\n\n\tlog.Debug(\"relayed bytes\", \"count\", count, \"srcID\", srcID, \"destID\", destID)\n}\n\nfunc (r *Relay) relayUnlimited(src, dest network.Stream, srcID, destID peer.ID, done func()) {\n\tdefer done()\n\n\tbuf := pool.Get(r.rc.BufferSize)\n\tdefer pool.Put(buf)\n\n\tcount, err := r.copyWithBuffer(dest, src, buf)\n\tif err != nil {\n\t\tlog.Debug(\"relay copy error\", \"err\", err)\n\t\t// Reset both.\n\t\tsrc.Reset()\n\t\tdest.Reset()\n\t} else {\n\t\t// propagate the close\n\t\tdest.CloseWrite()\n\t}\n\n\tlog.Debug(\"relayed bytes\", \"count\", count, \"srcID\", srcID, \"destID\", destID)\n}\n\n// errInvalidWrite means that a write returned an impossible count.\n// copied from io.errInvalidWrite\nvar errInvalidWrite = errors.New(\"invalid write result\")\n\n// copyWithBuffer copies from src to dst using the provided buf until either EOF is reached\n// on src or an error occurs. It reports the number of bytes transferred to metricsTracer.\n// The implementation is a modified form of io.CopyBuffer to support metrics tracking.\nfunc (r *Relay) copyWithBuffer(dst io.Writer, src io.Reader, buf []byte) (written int64, err error) {\n\tfor {\n\t\tnr, er := src.Read(buf)\n\t\tif nr > 0 {\n\t\t\tnw, ew := dst.Write(buf[0:nr])\n\t\t\tif nw < 0 || nr < nw {\n\t\t\t\tnw = 0\n\t\t\t\tif ew == nil {\n\t\t\t\t\tew = errInvalidWrite\n\t\t\t\t}\n\t\t\t}\n\t\t\twritten += int64(nw)\n\t\t\tif ew != nil {\n\t\t\t\terr = ew\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif nr != nw {\n\t\t\t\terr = io.ErrShortWrite\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif r.metricsTracer != nil {\n\t\t\t\tr.metricsTracer.BytesTransferred(nw)\n\t\t\t}\n\t\t}\n\t\tif er != nil {\n\t\t\tif er != io.EOF {\n\t\t\t\terr = er\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\treturn written, err\n}\n\nfunc (r *Relay) handleError(s network.Stream, status pbv2.Status) {\n\tlog.Debug(\"relay error\", \"status_name\", pbv2.Status_name[int32(status)], \"status\", status)\n\terr := r.writeResponse(s, status, nil, nil)\n\tif err != nil {\n\t\ts.Reset()\n\t\tlog.Debug(\"error writing relay response\", \"err\", err)\n\t} else {\n\t\ts.Close()\n\t}\n}\n\nfunc (r *Relay) writeResponse(s network.Stream, status pbv2.Status, rsvp *pbv2.Reservation, limit *pbv2.Limit) error {\n\ts.SetWriteDeadline(time.Now().Add(StreamTimeout))\n\tdefer s.SetWriteDeadline(time.Time{})\n\twr := util.NewDelimitedWriter(s)\n\n\tvar msg pbv2.HopMessage\n\tmsg.Type = pbv2.HopMessage_STATUS.Enum()\n\tmsg.Status = status.Enum()\n\tmsg.Reservation = rsvp\n\tmsg.Limit = limit\n\n\treturn wr.WriteMsg(&msg)\n}\n\nfunc makeReservationMsg(\n\treservationAddrFilter ReservationAddressFilterFunc,\n\tsigningKey crypto.PrivKey,\n\tselfID peer.ID,\n\tselfAddrs []ma.Multiaddr,\n\tp peer.ID,\n\texpire time.Time,\n) *pbv2.Reservation {\n\texpireUnix := uint64(expire.Unix())\n\n\trsvp := &pbv2.Reservation{Expire: &expireUnix}\n\n\tselfP2PAddr, err := ma.NewComponent(\"p2p\", selfID.String())\n\tif err != nil {\n\t\tlog.Error(\"error creating p2p component\", \"err\", err)\n\t\treturn rsvp\n\t}\n\n\taddrBytes := make([][]byte, 0, len(selfAddrs))\n\tfor _, addr := range selfAddrs {\n\t\tif !reservationAddrFilter(addr) {\n\t\t\tcontinue\n\t\t}\n\n\t\tid, _ := peer.IDFromP2PAddr(addr)\n\t\tswitch {\n\t\tcase id == \"\":\n\t\t\t// No ID, we'll add one to the address\n\t\t\taddr = addr.Encapsulate(selfP2PAddr)\n\t\tcase id == selfID:\n\t\t// This address already has our ID in it.\n\t\t// Do nothing\n\t\tcase id != selfID:\n\t\t\t// This address has a different ID in it. Skip it.\n\t\t\tlog.Warn(\"skipping address\", \"addr\", addr, \"reason\", \"contains an unexpected ID\")\n\t\t\tcontinue\n\t\t}\n\t\taddrBytes = append(addrBytes, addr.Bytes())\n\t}\n\n\trsvp.Addrs = addrBytes\n\n\tvoucher := &proto.ReservationVoucher{\n\t\tRelay:      selfID,\n\t\tPeer:       p,\n\t\tExpiration: expire,\n\t}\n\n\tenvelope, err := record.Seal(voucher, signingKey)\n\tif err != nil {\n\t\tlog.Error(\"error sealing voucher\", \"peer\", p, \"err\", err)\n\t\treturn rsvp\n\t}\n\n\tblob, err := envelope.Marshal()\n\tif err != nil {\n\t\tlog.Error(\"error marshalling voucher\", \"peer\", p, \"err\", err)\n\t\treturn rsvp\n\t}\n\n\trsvp.Voucher = blob\n\n\treturn rsvp\n}\n\nfunc (r *Relay) makeLimitMsg(_ peer.ID) *pbv2.Limit {\n\tif r.rc.Limit == nil {\n\t\treturn nil\n\t}\n\n\tduration := uint32(r.rc.Limit.Duration / time.Second)\n\tdata := uint64(r.rc.Limit.Data)\n\n\treturn &pbv2.Limit{\n\t\tDuration: &duration,\n\t\tData:     &data,\n\t}\n}\n\nfunc (r *Relay) background() {\n\tticker := time.NewTicker(time.Minute)\n\tdefer ticker.Stop()\n\n\tfor {\n\t\tselect {\n\t\tcase <-ticker.C:\n\t\t\tr.gc()\n\t\tcase <-r.ctx.Done():\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (r *Relay) gc() {\n\tr.mx.Lock()\n\tdefer r.mx.Unlock()\n\n\tnow := time.Now()\n\tcnt := 0\n\tfor p, expire := range r.rsvp {\n\t\tif r.closed || expire.Before(now) {\n\t\t\tdelete(r.rsvp, p)\n\t\t\tr.host.ConnManager().UntagPeer(p, \"relay-reservation\")\n\t\t\tcnt++\n\t\t}\n\t}\n\tif r.metricsTracer != nil {\n\t\tr.metricsTracer.ReservationClosed(cnt)\n\t}\n\n\tfor p, count := range r.conns {\n\t\tif count == 0 {\n\t\t\tdelete(r.conns, p)\n\t\t}\n\t}\n}\n\nfunc (r *Relay) disconnected(n network.Network, c network.Conn) {\n\tp := c.RemotePeer()\n\tif n.Connectedness(p) == network.Connected {\n\t\treturn\n\t}\n\n\tr.mx.Lock()\n\t_, ok := r.rsvp[p]\n\tif ok {\n\t\tdelete(r.rsvp, p)\n\t}\n\tr.constraints.cleanupPeer(p)\n\tr.mx.Unlock()\n\n\tif ok && r.metricsTracer != nil {\n\t\tr.metricsTracer.ReservationClosed(1)\n\t}\n}\n\nfunc isRelayAddr(a ma.Multiaddr) bool {\n\t_, err := a.ValueForProtocol(ma.P_CIRCUIT)\n\treturn err == nil\n}\n"
  },
  {
    "path": "p2p/protocol/circuitv2/relay/relay_priv_test.go",
    "content": "package relay\n\nimport (\n\t\"crypto/rand\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/stretchr/testify/require\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/multiformats/go-multiaddr/matest\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\nfunc genKeyAndID(t *testing.T) (crypto.PrivKey, peer.ID) {\n\tt.Helper()\n\tkey, _, err := crypto.GenerateEd25519Key(rand.Reader)\n\trequire.NoError(t, err)\n\tid, err := peer.IDFromPrivateKey(key)\n\trequire.NoError(t, err)\n\treturn key, id\n}\n\n// TestMakeReservationWithP2PAddrs ensures that our reservation message builder\n// sanitizes the input addresses\nfunc TestMakeReservationWithP2PAddrs(t *testing.T) {\n\tselfKey, selfID := genKeyAndID(t)\n\t_, otherID := genKeyAndID(t)\n\t_, reserverID := genKeyAndID(t)\n\n\ttcs := []struct {\n\t\tname     string\n\t\tfilter   func(ma.Multiaddr) bool\n\t\tinput    []ma.Multiaddr\n\t\texpected []ma.Multiaddr\n\t}{{\n\t\tname:   \"only public\",\n\t\tfilter: manet.IsPublicAddr,\n\t\tinput: []ma.Multiaddr{\n\t\t\tma.StringCast(\"/ip4/1.2.3.4/tcp/1234\"),                            // No p2p part\n\t\t\tma.StringCast(\"/ip4/1.2.3.4/tcp/1235/p2p/\" + selfID.String()),     // Already has p2p part\n\t\t\tma.StringCast(\"/ip4/192.168.1.9/tcp/1235/p2p/\" + selfID.String()), // Already has p2p part\n\t\t\tma.StringCast(\"/ip4/1.2.3.4/tcp/1236/p2p/\" + otherID.String()),    // Some other peer (?? Not expected, but we could get anything in this func)\n\t\t},\n\t\texpected: []ma.Multiaddr{\n\t\t\tma.StringCast(\"/ip4/1.2.3.4/tcp/1234/p2p/\" + selfID.String()),\n\t\t\tma.StringCast(\"/ip4/1.2.3.4/tcp/1235/p2p/\" + selfID.String()),\n\t\t},\n\t}, {\n\t\tname:   \"only not public\",\n\t\tfilter: func(m ma.Multiaddr) bool { return !manet.IsPublicAddr(m) },\n\t\tinput: []ma.Multiaddr{\n\t\t\tma.StringCast(\"/ip4/1.2.3.4/tcp/1234\"),                            // No p2p part\n\t\t\tma.StringCast(\"/ip4/1.2.3.4/tcp/1235/p2p/\" + selfID.String()),     // Already has p2p part\n\t\t\tma.StringCast(\"/ip4/192.168.1.9/tcp/1235/p2p/\" + selfID.String()), // Already has p2p part\n\t\t\tma.StringCast(\"/ip4/1.2.3.4/tcp/1236/p2p/\" + otherID.String()),    // Some other peer (?? Not expected, but we could get anything in this func)\n\t\t},\n\t\texpected: []ma.Multiaddr{\n\t\t\tma.StringCast(\"/ip4/192.168.1.9/tcp/1235/p2p/\" + selfID.String()),\n\t\t},\n\t}}\n\tfor _, tc := range tcs {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trsvp := makeReservationMsg(tc.filter, selfKey, selfID, tc.input, reserverID, time.Now().Add(time.Minute))\n\t\t\trequire.NotNil(t, rsvp)\n\n\t\t\taddrsFromRsvp := make([]ma.Multiaddr, 0, len(rsvp.GetAddrs()))\n\t\t\tfor _, addr := range rsvp.GetAddrs() {\n\t\t\t\ta, err := ma.NewMultiaddrBytes(addr)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\taddrsFromRsvp = append(addrsFromRsvp, a)\n\t\t\t}\n\t\t\tmatest.AssertEqualMultiaddrs(t, tc.expected, addrsFromRsvp)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "p2p/protocol/circuitv2/relay/relay_test.go",
    "content": "package relay_test\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/rand\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/event\"\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/metrics\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/transport\"\n\tbhost \"github.com/libp2p/go-libp2p/p2p/host/blank\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/eventbus\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoremem\"\n\t\"github.com/libp2p/go-libp2p/p2p/net/swarm\"\n\tswarmt \"github.com/libp2p/go-libp2p/p2p/net/swarm/testing\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/client\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/relay\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/tcp\"\n\t\"github.com/stretchr/testify/require\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\nfunc getNetHosts(t *testing.T, _ context.Context, n int) (hosts []host.Host, upgraders []transport.Upgrader) {\n\tfor range n {\n\t\tprivk, pubk, err := crypto.GenerateKeyPair(crypto.Ed25519, 0)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tp, err := peer.IDFromPublicKey(pubk)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tps, err := pstoremem.NewPeerstore()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\terr = ps.AddPrivKey(p, privk)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tbwr := metrics.NewBandwidthCounter()\n\t\tbus := eventbus.NewBus()\n\t\tnetw, err := swarm.NewSwarm(p, ps, bus, swarm.WithMetrics(bwr))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tupgrader := swarmt.GenUpgrader(t, netw, nil)\n\t\tupgraders = append(upgraders, upgrader)\n\n\t\ttpt, err := tcp.NewTCPTransport(upgrader, nil, nil)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif err := netw.AddTransport(tpt); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\terr = netw.Listen(ma.StringCast(\"/ip4/127.0.0.1/tcp/0\"))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\th := bhost.NewBlankHost(netw, bhost.WithEventBus(bus))\n\n\t\thosts = append(hosts, h)\n\t}\n\n\treturn hosts, upgraders\n}\n\nfunc connect(t *testing.T, a, b host.Host) {\n\tpi := peer.AddrInfo{ID: a.ID(), Addrs: a.Addrs()}\n\terr := b.Connect(context.Background(), pi)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc addTransport(t *testing.T, h host.Host, upgrader transport.Upgrader) {\n\tif err := client.AddTransport(h, upgrader); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestBasicRelay(t *testing.T) {\n\tctx := t.Context()\n\n\thosts, upgraders := getNetHosts(t, ctx, 3)\n\taddTransport(t, hosts[0], upgraders[0])\n\taddTransport(t, hosts[2], upgraders[2])\n\n\trch := make(chan []byte, 1)\n\thosts[0].SetStreamHandler(\"test\", func(s network.Stream) {\n\t\tdefer s.Close()\n\t\tdefer close(rch)\n\n\t\tbuf := make([]byte, 1024)\n\t\tnread := 0\n\t\tfor nread < len(buf) {\n\t\t\tn, err := s.Read(buf[nread:])\n\t\t\tnread += n\n\t\t\tif err != nil {\n\t\t\t\tif err == io.EOF {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t}\n\n\t\trch <- buf[:nread]\n\t})\n\n\tr, err := relay.New(hosts[1])\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer r.Close()\n\n\tconnect(t, hosts[0], hosts[1])\n\tconnect(t, hosts[1], hosts[2])\n\n\trinfo := hosts[1].Peerstore().PeerInfo(hosts[1].ID())\n\trsvp, err := client.Reserve(ctx, hosts[0], rinfo)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif rsvp.Voucher == nil {\n\t\tt.Fatal(\"no reservation voucher\")\n\t}\n\n\traddr, err := ma.NewMultiaddr(fmt.Sprintf(\"/p2p/%s/p2p-circuit/p2p/%s\", hosts[1].ID(), hosts[0].ID()))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tsub, err := hosts[2].EventBus().Subscribe(new(event.EvtPeerConnectednessChanged))\n\trequire.NoError(t, err)\n\n\terr = hosts[2].Connect(ctx, peer.AddrInfo{ID: hosts[0].ID(), Addrs: []ma.Multiaddr{raddr}})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tfor {\n\t\tvar e any\n\t\tselect {\n\t\tcase e = <-sub.Out():\n\t\tcase <-time.After(2 * time.Second):\n\t\t\tt.Fatal(\"expected limited connectivity event\")\n\t\t}\n\t\tevt, ok := e.(event.EvtPeerConnectednessChanged)\n\t\tif !ok {\n\t\t\tt.Fatalf(\"invalid event: %s\", e)\n\t\t}\n\t\tif evt.Peer == hosts[0].ID() {\n\t\t\tif evt.Connectedness != network.Limited {\n\t\t\t\tt.Fatalf(\"expected limited connectivity %s\", evt.Connectedness)\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\n\tconns := hosts[2].Network().ConnsToPeer(hosts[0].ID())\n\tif len(conns) != 1 {\n\t\tt.Fatalf(\"expected 1 connection, but got %d\", len(conns))\n\t}\n\tif !conns[0].Stat().Limited {\n\t\tt.Fatal(\"expected transient connection\")\n\t}\n\n\ts, err := hosts[2].NewStream(network.WithAllowLimitedConn(ctx, \"test\"), hosts[0].ID(), \"test\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tmsg := []byte(\"relay works!\")\n\tnwritten, err := s.Write(msg)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif nwritten != len(msg) {\n\t\tt.Fatalf(\"expected to write %d bytes, but wrote %d instead\", len(msg), nwritten)\n\t}\n\ts.CloseWrite()\n\n\tgot := <-rch\n\tif !bytes.Equal(msg, got) {\n\t\tt.Fatalf(\"Wrong echo; expected %s but got %s\", string(msg), string(got))\n\t}\n}\n\nfunc TestRelayLimitTime(t *testing.T) {\n\tctx := t.Context()\n\n\thosts, upgraders := getNetHosts(t, ctx, 3)\n\taddTransport(t, hosts[0], upgraders[0])\n\taddTransport(t, hosts[2], upgraders[2])\n\n\trch := make(chan error, 1)\n\thosts[0].SetStreamHandler(\"test\", func(s network.Stream) {\n\t\tdefer s.Close()\n\t\tdefer close(rch)\n\n\t\tbuf := make([]byte, 1024)\n\t\t_, err := s.Read(buf)\n\t\trch <- err\n\t})\n\n\trc := relay.DefaultResources()\n\trc.Limit.Duration = time.Second\n\n\tr, err := relay.New(hosts[1], relay.WithResources(rc))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer r.Close()\n\n\tconnect(t, hosts[0], hosts[1])\n\tconnect(t, hosts[1], hosts[2])\n\n\trinfo := hosts[1].Peerstore().PeerInfo(hosts[1].ID())\n\t_, err = client.Reserve(ctx, hosts[0], rinfo)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\traddr, err := ma.NewMultiaddr(fmt.Sprintf(\"/p2p/%s/p2p-circuit/p2p/%s\", hosts[1].ID(), hosts[0].ID()))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = hosts[2].Connect(ctx, peer.AddrInfo{ID: hosts[0].ID(), Addrs: []ma.Multiaddr{raddr}})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tconns := hosts[2].Network().ConnsToPeer(hosts[0].ID())\n\tif len(conns) != 1 {\n\t\tt.Fatalf(\"expected 1 connection, but got %d\", len(conns))\n\t}\n\tif !conns[0].Stat().Limited {\n\t\tt.Fatal(\"expected transient connection\")\n\t}\n\n\ts, err := hosts[2].NewStream(network.WithAllowLimitedConn(ctx, \"test\"), hosts[0].ID(), \"test\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttime.Sleep(2 * time.Second)\n\tn, err := s.Write([]byte(\"should be closed\"))\n\tif n > 0 {\n\t\tt.Fatalf(\"expected to write 0 bytes, wrote %d\", n)\n\t}\n\tif !errors.Is(err, network.ErrReset) {\n\t\tt.Fatalf(\"expected reset, but got %s\", err)\n\t}\n\n\terr = <-rch\n\tif !errors.Is(err, network.ErrReset) {\n\t\tt.Fatalf(\"expected reset, but got %s\", err)\n\t}\n}\n\nfunc TestRelayLimitData(t *testing.T) {\n\tctx := t.Context()\n\n\thosts, upgraders := getNetHosts(t, ctx, 3)\n\taddTransport(t, hosts[0], upgraders[0])\n\taddTransport(t, hosts[2], upgraders[2])\n\n\trch := make(chan int, 1)\n\thosts[0].SetStreamHandler(\"test\", func(s network.Stream) {\n\t\tdefer s.Close()\n\t\tdefer close(rch)\n\n\t\tbuf := make([]byte, 1024)\n\t\tfor range 3 {\n\t\t\tn, err := s.Read(buf)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\trch <- n\n\t\t}\n\n\t\tn, err := s.Read(buf)\n\t\tif !errors.Is(err, network.ErrReset) {\n\t\t\tt.Fatalf(\"expected reset but got %s\", err)\n\t\t}\n\t\trch <- n\n\t})\n\n\trc := relay.DefaultResources()\n\trc.Limit.Duration = time.Second\n\trc.Limit.Data = 4096\n\n\tr, err := relay.New(hosts[1], relay.WithResources(rc))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer r.Close()\n\n\tconnect(t, hosts[0], hosts[1])\n\tconnect(t, hosts[1], hosts[2])\n\n\trinfo := hosts[1].Peerstore().PeerInfo(hosts[1].ID())\n\t_, err = client.Reserve(ctx, hosts[0], rinfo)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\traddr, err := ma.NewMultiaddr(fmt.Sprintf(\"/p2p/%s/p2p-circuit/p2p/%s\", hosts[1].ID(), hosts[0].ID()))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = hosts[2].Connect(ctx, peer.AddrInfo{ID: hosts[0].ID(), Addrs: []ma.Multiaddr{raddr}})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tconns := hosts[2].Network().ConnsToPeer(hosts[0].ID())\n\tif len(conns) != 1 {\n\t\tt.Fatalf(\"expected 1 connection, but got %d\", len(conns))\n\t}\n\tif !conns[0].Stat().Limited {\n\t\tt.Fatal(\"expected transient connection\")\n\t}\n\n\ts, err := hosts[2].NewStream(network.WithAllowLimitedConn(ctx, \"test\"), hosts[0].ID(), \"test\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tbuf := make([]byte, 1024)\n\tfor range 3 {\n\t\tif _, err := rand.Read(buf); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tn, err := s.Write(buf)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif n != len(buf) {\n\t\t\tt.Fatalf(\"expected to write %d bytes but wrote %d\", len(buf), n)\n\t\t}\n\n\t\tn = <-rch\n\t\tif n != len(buf) {\n\t\t\tt.Fatalf(\"expected to read %d bytes but read %d\", len(buf), n)\n\t\t}\n\t}\n\n\tbuf = make([]byte, 4096)\n\tif _, err := rand.Read(buf); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ts.Write(buf)\n\n\tn := <-rch\n\tif n != 0 {\n\t\tt.Fatalf(\"expected to read 0 bytes but read %d\", n)\n\t}\n\n}\n"
  },
  {
    "path": "p2p/protocol/circuitv2/relay/resources.go",
    "content": "package relay\n\nimport (\n\t\"time\"\n)\n\n// Resources are the resource limits associated with the relay service.\ntype Resources struct {\n\t// Limit is the (optional) relayed connection limits.\n\tLimit *RelayLimit\n\n\t// ReservationTTL is the duration of a new (or refreshed reservation).\n\t// Defaults to 1hr.\n\tReservationTTL time.Duration\n\n\t// MaxReservations is the maximum number of active relay slots; defaults to 128.\n\tMaxReservations int\n\t// MaxCircuits is the maximum number of open relay connections for each peer; defaults to 16.\n\tMaxCircuits int\n\t// BufferSize is the size of the relayed connection buffers; defaults to 2048.\n\tBufferSize int\n\n\t// MaxReservationsPerPeer is the maximum number of reservations originating from the same\n\t// peer; default is 4.\n\t//\n\t// Deprecated: We only need 1 reservation per peer.\n\tMaxReservationsPerPeer int\n\t// MaxReservationsPerIP is the maximum number of reservations originating from the same\n\t// IP address; default is 8.\n\tMaxReservationsPerIP int\n\t// MaxReservationsPerASN is the maximum number of reservations origination from the same\n\t// ASN; default is 32\n\tMaxReservationsPerASN int\n}\n\n// RelayLimit are the per relayed connection resource limits.\ntype RelayLimit struct {\n\t// Duration is the time limit before resetting a relayed connection; defaults to 2min.\n\tDuration time.Duration\n\t// Data is the limit of data relayed (on each direction) before resetting the connection.\n\t// Defaults to 128KB\n\tData int64\n}\n\n// DefaultResources returns a Resources object with the default filled in.\nfunc DefaultResources() Resources {\n\treturn Resources{\n\t\tLimit: DefaultLimit(),\n\n\t\tReservationTTL: time.Hour,\n\n\t\tMaxReservations: 128,\n\t\tMaxCircuits:     16,\n\t\tBufferSize:      2048,\n\n\t\tMaxReservationsPerPeer: 1,\n\t\tMaxReservationsPerIP:   8,\n\t\tMaxReservationsPerASN:  32,\n\t}\n}\n\n// DefaultLimit returns a RelayLimit object with the defaults filled in.\nfunc DefaultLimit() *RelayLimit {\n\treturn &RelayLimit{\n\t\tDuration: 2 * time.Minute,\n\t\tData:     1 << 17, // 128K\n\t}\n}\n"
  },
  {
    "path": "p2p/protocol/circuitv2/util/io.go",
    "content": "package util\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\tpool \"github.com/libp2p/go-buffer-pool\"\n\t\"github.com/libp2p/go-msgio/pbio\"\n\t\"github.com/multiformats/go-varint\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\ntype DelimitedReader struct {\n\tr   io.Reader\n\tbuf []byte\n}\n\n// The gogo protobuf NewDelimitedReader is buffered, which may eat up stream data.\n// So we need to implement a compatible delimited reader that reads unbuffered.\n// There is a slowdown from unbuffered reading: when reading the message\n// it can take multiple single byte Reads to read the length and another Read\n// to read the message payload.\n// However, this is not critical performance degradation as\n//   - the reader is utilized to read one (dialer, stop) or two messages (hop) during\n//     the handshake, so it's a drop in the water for the connection lifetime.\n//   - messages are small (max 4k) and the length fits in a couple of bytes,\n//     so overall we have at most three reads per message.\nfunc NewDelimitedReader(r io.Reader, maxSize int) *DelimitedReader {\n\treturn &DelimitedReader{r: r, buf: pool.Get(maxSize)}\n}\n\nfunc (d *DelimitedReader) Close() {\n\tif d.buf != nil {\n\t\tpool.Put(d.buf)\n\t\td.buf = nil\n\t}\n}\n\nfunc (d *DelimitedReader) ReadByte() (byte, error) {\n\tbuf := d.buf[:1]\n\t_, err := d.r.Read(buf)\n\treturn buf[0], err\n}\n\nfunc (d *DelimitedReader) ReadMsg(msg proto.Message) error {\n\tmlen, err := varint.ReadUvarint(d)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif uint64(len(d.buf)) < mlen {\n\t\treturn errors.New(\"message too large\")\n\t}\n\n\tbuf := d.buf[:mlen]\n\t_, err = io.ReadFull(d.r, buf)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn proto.Unmarshal(buf, msg)\n}\n\nfunc NewDelimitedWriter(w io.Writer) pbio.WriteCloser {\n\treturn pbio.NewDelimitedWriter(w)\n}\n"
  },
  {
    "path": "p2p/protocol/circuitv2/util/pbconv.go",
    "content": "package util\n\nimport (\n\t\"errors\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\tpbv2 \"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/pb\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\nfunc PeerToPeerInfoV2(p *pbv2.Peer) (peer.AddrInfo, error) {\n\tif p == nil {\n\t\treturn peer.AddrInfo{}, errors.New(\"nil peer\")\n\t}\n\n\tid, err := peer.IDFromBytes(p.Id)\n\tif err != nil {\n\t\treturn peer.AddrInfo{}, err\n\t}\n\n\taddrs := make([]ma.Multiaddr, 0, len(p.Addrs))\n\n\tfor _, addrBytes := range p.Addrs {\n\t\ta, err := ma.NewMultiaddrBytes(addrBytes)\n\t\tif err == nil {\n\t\t\taddrs = append(addrs, a)\n\t\t}\n\t}\n\n\treturn peer.AddrInfo{ID: id, Addrs: addrs}, nil\n}\n\nfunc PeerInfoToPeerV2(pi peer.AddrInfo) *pbv2.Peer {\n\taddrs := make([][]byte, 0, len(pi.Addrs))\n\tfor _, addr := range pi.Addrs {\n\t\taddrs = append(addrs, addr.Bytes())\n\t}\n\n\treturn &pbv2.Peer{\n\t\tId:    []byte(pi.ID),\n\t\tAddrs: addrs,\n\t}\n}\n"
  },
  {
    "path": "p2p/protocol/holepunch/filter.go",
    "content": "package holepunch\n\nimport (\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\n// WithAddrFilter is a Service option that enables multiaddress filtering.\n// It allows to only send a subset of observed addresses to the remote\n// peer. E.g., only announce TCP or QUIC multi addresses instead of both.\n// It also allows to only consider a subset of received multi addresses\n// that remote peers announced to us.\n// Theoretically, this API also allows to add multi addresses in both cases.\nfunc WithAddrFilter(f AddrFilter) Option {\n\treturn func(hps *Service) error {\n\t\thps.filter = f\n\t\treturn nil\n\t}\n}\n\n// AddrFilter defines the interface for the multi address filtering.\ntype AddrFilter interface {\n\t// FilterLocal filters the multi addresses that are sent to the remote peer.\n\tFilterLocal(remoteID peer.ID, maddrs []ma.Multiaddr) []ma.Multiaddr\n\t// FilterRemote filters the multi addresses received from the remote peer.\n\tFilterRemote(remoteID peer.ID, maddrs []ma.Multiaddr) []ma.Multiaddr\n}\n"
  },
  {
    "path": "p2p/protocol/holepunch/holepunch_test.go",
    "content": "package holepunch_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"slices\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p\"\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\trelayv2 \"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/relay\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/holepunch\"\n\tholepunch_pb \"github.com/libp2p/go-libp2p/p2p/protocol/holepunch/pb\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/identify\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/ping\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/quicreuse\"\n\t\"github.com/marcopolo/simnet\"\n\t\"go.uber.org/fx\"\n\n\t\"github.com/libp2p/go-msgio/pbio\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype mockEventTracer struct {\n\tmutex  sync.Mutex\n\tevents []*holepunch.Event\n}\n\nfunc (m *mockEventTracer) Trace(evt *holepunch.Event) {\n\tm.mutex.Lock()\n\tm.events = append(m.events, evt)\n\tm.mutex.Unlock()\n}\n\nfunc (m *mockEventTracer) getEvents() []*holepunch.Event {\n\tm.mutex.Lock()\n\tdefer m.mutex.Unlock()\n\t// copy the slice\n\treturn append([]*holepunch.Event{}, m.events...)\n}\n\nvar _ holepunch.EventTracer = &mockEventTracer{}\n\ntype mockMaddrFilter struct {\n\tfilterLocal  func(remoteID peer.ID, maddrs []ma.Multiaddr) []ma.Multiaddr\n\tfilterRemote func(remoteID peer.ID, maddrs []ma.Multiaddr) []ma.Multiaddr\n}\n\nfunc (m mockMaddrFilter) FilterLocal(remoteID peer.ID, maddrs []ma.Multiaddr) []ma.Multiaddr {\n\treturn m.filterLocal(remoteID, maddrs)\n}\n\nfunc (m mockMaddrFilter) FilterRemote(remoteID peer.ID, maddrs []ma.Multiaddr) []ma.Multiaddr {\n\treturn m.filterRemote(remoteID, maddrs)\n}\n\nvar _ holepunch.AddrFilter = &mockMaddrFilter{}\n\nfunc newIDService(t *testing.T, h host.Host) identify.IDService {\n\tids, err := identify.NewIDService(h)\n\trequire.NoError(t, err)\n\tids.Start()\n\tt.Cleanup(func() { ids.Close() })\n\treturn ids\n}\n\nfunc TestNoHolePunchIfDirectConnExists(t *testing.T) {\n\trouter := &simnet.SimpleFirewallRouter{}\n\trelay := MustNewHost(t,\n\t\tquicSimnet(true, router),\n\t\tlibp2p.ListenAddrs(ma.StringCast(\"/ip4/1.2.0.1/udp/8000/quic-v1\")),\n\t\tlibp2p.DisableRelay(),\n\t\tlibp2p.ResourceManager(&network.NullResourceManager{}),\n\t\tlibp2p.WithFxOption(fx.Invoke(func(h host.Host) {\n\t\t\t// Setup relay service\n\t\t\t_, err := relayv2.New(h)\n\t\t\trequire.NoError(t, err)\n\t\t})),\n\t)\n\n\ttr := &mockEventTracer{}\n\th1 := MustNewHost(t,\n\t\tquicSimnet(false, router),\n\t\tlibp2p.EnableHolePunching(holepunch.DirectDialTimeout(100*time.Millisecond)),\n\t\tlibp2p.ListenAddrs(ma.StringCast(\"/ip4/2.2.0.1/udp/8000/quic-v1\")),\n\t\tlibp2p.ResourceManager(&network.NullResourceManager{}),\n\t)\n\n\th2 := MustNewHost(t,\n\t\tquicSimnet(true, router),\n\t\tlibp2p.ListenAddrs(ma.StringCast(\"/ip4/2.2.0.2/udp/8001/quic-v1\")),\n\t\tlibp2p.ResourceManager(&network.NullResourceManager{}),\n\t\tlibp2p.ForceReachabilityPublic(),\n\t\tconnectToRelay(&relay),\n\t\tlibp2p.EnableHolePunching(holepunch.WithTracer(tr), holepunch.DirectDialTimeout(100*time.Millisecond)),\n\t)\n\n\tdefer h1.Close()\n\tdefer h2.Close()\n\tdefer relay.Close()\n\n\twaitForHolePunchingSvcActive(t, h1)\n\twaitForHolePunchingSvcActive(t, h2)\n\n\th1.Peerstore().AddAddrs(h2.ID(), h2.Addrs(), peerstore.ConnectedAddrTTL)\n\t// try to hole punch without any connection and streams, if it works -> it's a direct connection\n\trequire.Empty(t, h1.Network().ConnsToPeer(h2.ID()))\n\tpingAtoB(t, h1, h2)\n\n\tnc1 := len(h1.Network().ConnsToPeer(h2.ID()))\n\trequire.Equal(t, nc1, 1)\n\tnc2 := len(h2.Network().ConnsToPeer(h1.ID()))\n\trequire.Equal(t, nc2, 1)\n\tassert.Never(t, func() bool {\n\t\treturn (len(h1.Network().ConnsToPeer(h2.ID())) != nc1 ||\n\t\t\tlen(h2.Network().ConnsToPeer(h1.ID())) != nc2 ||\n\t\t\tlen(tr.getEvents()) != 0)\n\t}, time.Second, 100*time.Millisecond)\n}\n\nfunc TestDirectDialWorks(t *testing.T) {\n\trouter := &simnet.SimpleFirewallRouter{}\n\trelay := MustNewHost(t,\n\t\tquicSimnet(true, router),\n\t\tlibp2p.ListenAddrs(ma.StringCast(\"/ip4/1.2.0.1/udp/8000/quic-v1\")),\n\t\tlibp2p.DisableRelay(),\n\t\tlibp2p.ResourceManager(&network.NullResourceManager{}),\n\t\tlibp2p.WithFxOption(fx.Invoke(func(h host.Host) {\n\t\t\t// Setup relay service\n\t\t\t_, err := relayv2.New(h)\n\t\t\trequire.NoError(t, err)\n\t\t})),\n\t)\n\n\ttr := &mockEventTracer{}\n\t// h1 is public\n\th1 := MustNewHost(t,\n\t\tquicSimnet(true, router),\n\t\tlibp2p.ForceReachabilityPublic(),\n\t\tlibp2p.EnableHolePunching(holepunch.DirectDialTimeout(100*time.Millisecond)),\n\t\tlibp2p.ListenAddrs(ma.StringCast(\"/ip4/2.2.0.1/udp/8000/quic-v1\")),\n\t\tlibp2p.ResourceManager(&network.NullResourceManager{}),\n\t)\n\n\th2 := MustNewHost(t,\n\t\tquicSimnet(false, router),\n\t\tlibp2p.ListenAddrs(ma.StringCast(\"/ip4/2.2.0.2/udp/8001/quic-v1\")),\n\t\tlibp2p.ResourceManager(&network.NullResourceManager{}),\n\t\tconnectToRelay(&relay),\n\t\tlibp2p.EnableHolePunching(holepunch.WithTracer(tr), holepunch.DirectDialTimeout(100*time.Millisecond)),\n\t\tlibp2p.ForceReachabilityPrivate(),\n\t)\n\n\tdefer h1.Close()\n\tdefer h2.Close()\n\tdefer relay.Close()\n\n\t// wait for dcutr to be available\n\twaitForHolePunchingSvcActive(t, h2)\n\n\th1.Peerstore().AddAddrs(h2.ID(), h2.Addrs(), peerstore.ConnectedAddrTTL)\n\t// try to hole punch without any connection and streams, if it works -> it's a direct connection\n\trequire.Empty(t, h1.Network().ConnsToPeer(h2.ID()))\n\tpingAtoB(t, h1, h2)\n\n\t// require.NoError(t, h1ps.DirectConnect(h2.ID()))\n\trequire.GreaterOrEqual(t, len(h1.Network().ConnsToPeer(h2.ID())), 1)\n\trequire.GreaterOrEqual(t, len(h2.Network().ConnsToPeer(h1.ID())), 1)\n\trequire.EventuallyWithT(t, func(collect *assert.CollectT) {\n\t\tevents := tr.getEvents()\n\t\tfmt.Println(\"events:\", events)\n\t\tif !assert.Len(collect, events, 1) {\n\t\t\treturn\n\t\t}\n\t\tassert.Equal(t, holepunch.DirectDialEvtT, events[0].Type)\n\t}, 2*time.Second, 100*time.Millisecond)\n}\n\nfunc connectToRelay(relayPtr *host.Host) libp2p.Option {\n\treturn func(cfg *libp2p.Config) error {\n\t\tif relayPtr == nil {\n\t\t\treturn nil\n\t\t}\n\t\trelay := *relayPtr\n\t\tpi := peer.AddrInfo{\n\t\t\tID:    relay.ID(),\n\t\t\tAddrs: relay.Addrs(),\n\t\t}\n\n\t\treturn cfg.Apply(\n\t\t\tlibp2p.EnableRelay(),\n\t\t\tlibp2p.EnableAutoRelayWithStaticRelays([]peer.AddrInfo{pi}),\n\t\t)\n\t}\n}\n\nfunc learnAddrs(h1, h2 host.Host) {\n\th1.Peerstore().AddAddrs(h2.ID(), h2.Addrs(), peerstore.ConnectedAddrTTL)\n\th2.Peerstore().AddAddrs(h1.ID(), h1.Addrs(), peerstore.ConnectedAddrTTL)\n}\n\nfunc pingAtoB(t *testing.T, a, b host.Host) {\n\tt.Helper()\n\tp1 := ping.NewPingService(a)\n\trequire.NoError(t, a.Connect(context.Background(), peer.AddrInfo{\n\t\tID:    b.ID(),\n\t\tAddrs: b.Addrs(),\n\t}))\n\tctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\tres := p1.Ping(ctx, b.ID())\n\tresult := <-res\n\trequire.NoError(t, result.Error)\n}\n\nfunc MustNewHost(t *testing.T, opts ...libp2p.Option) host.Host {\n\tt.Helper()\n\th, err := libp2p.New(opts...)\n\trequire.NoError(t, err)\n\treturn h\n}\n\nfunc TestEndToEndSimConnect(t *testing.T) {\n\tfor _, useLegacyHolePunchingBehavior := range []bool{true, false} {\n\t\tt.Run(fmt.Sprintf(\"legacy=%t\", useLegacyHolePunchingBehavior), func(t *testing.T) {\n\t\t\th1tr := &mockEventTracer{}\n\t\t\th2tr := &mockEventTracer{}\n\n\t\t\trouter := &simnet.SimpleFirewallRouter{}\n\t\t\trelay := MustNewHost(t,\n\t\t\t\tquicSimnet(true, router),\n\t\t\t\tlibp2p.ListenAddrs(ma.StringCast(\"/ip4/1.2.0.1/udp/8000/quic-v1\")),\n\t\t\t\tlibp2p.DisableRelay(),\n\t\t\t\tlibp2p.ResourceManager(&network.NullResourceManager{}),\n\t\t\t\tlibp2p.WithFxOption(fx.Invoke(func(h host.Host) {\n\t\t\t\t\t// Setup relay service\n\t\t\t\t\t_, err := relayv2.New(h)\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t})),\n\t\t\t)\n\n\t\t\th1 := MustNewHost(t,\n\t\t\t\tquicSimnet(false, router),\n\t\t\t\tlibp2p.EnableHolePunching(holepunch.WithTracer(h1tr), holepunch.DirectDialTimeout(100*time.Millisecond)),\n\t\t\t\tlibp2p.ListenAddrs(ma.StringCast(\"/ip4/2.2.0.1/udp/8000/quic-v1\")),\n\t\t\t\tlibp2p.ResourceManager(&network.NullResourceManager{}),\n\t\t\t\tlibp2p.ForceReachabilityPrivate(),\n\t\t\t)\n\n\t\t\th2 := MustNewHost(t,\n\t\t\t\tquicSimnet(false, router),\n\t\t\t\tlibp2p.ListenAddrs(ma.StringCast(\"/ip4/2.2.0.2/udp/8001/quic-v1\")),\n\t\t\t\tlibp2p.ResourceManager(&network.NullResourceManager{}),\n\t\t\t\tconnectToRelay(&relay),\n\t\t\t\tlibp2p.EnableHolePunching(holepunch.WithTracer(h2tr), holepunch.DirectDialTimeout(100*time.Millisecond)),\n\t\t\t\tlibp2p.ForceReachabilityPrivate(),\n\t\t\t)\n\n\t\t\tdefer h1.Close()\n\t\t\tdefer h2.Close()\n\t\t\tdefer relay.Close()\n\n\t\t\t// Wait for holepunch service to start\n\t\t\twaitForHolePunchingSvcActive(t, h1)\n\t\t\twaitForHolePunchingSvcActive(t, h2)\n\n\t\t\tlearnAddrs(h1, h2)\n\t\t\tpingAtoB(t, h1, h2)\n\n\t\t\t// wait till a direct connection is complete\n\t\t\tensureDirectConn(t, h1, h2)\n\t\t\t// ensure no hole-punching streams are open on either side\n\t\t\tensureNoHolePunchingStream(t, h1, h2)\n\t\t\tvar h2Events []*holepunch.Event\n\t\t\trequire.Eventually(t,\n\t\t\t\tfunc() bool {\n\t\t\t\t\th2Events = h2tr.getEvents()\n\t\t\t\t\treturn len(h2Events) == 4\n\t\t\t\t},\n\t\t\t\ttime.Second,\n\t\t\t\t100*time.Millisecond,\n\t\t\t)\n\t\t\trequire.Equal(t, holepunch.DirectDialEvtT, h2Events[0].Type)\n\t\t\trequire.Equal(t, holepunch.StartHolePunchEvtT, h2Events[1].Type)\n\t\t\trequire.Equal(t, holepunch.HolePunchAttemptEvtT, h2Events[2].Type)\n\t\t\trequire.Equal(t, holepunch.EndHolePunchEvtT, h2Events[3].Type)\n\n\t\t\th1Events := h1tr.getEvents()\n\t\t\t// We don't really expect a hole-punched connection to be established in this test,\n\t\t\t// as we probably don't get the timing right for the TCP simultaneous open.\n\t\t\t// From time to time, it still happens occasionally, and then we get a EndHolePunchEvtT here.\n\t\t\tif len(h1Events) != 2 && len(h1Events) != 3 {\n\t\t\t\tt.Fatal(\"expected either 2 or 3 events\")\n\t\t\t}\n\t\t\trequire.Equal(t, holepunch.StartHolePunchEvtT, h1Events[0].Type)\n\t\t\trequire.Equal(t, holepunch.HolePunchAttemptEvtT, h1Events[1].Type)\n\t\t\tif len(h1Events) == 3 {\n\t\t\t\trequire.Equal(t, holepunch.EndHolePunchEvtT, h1Events[2].Type)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestFailuresOnInitiator(t *testing.T) {\n\ttcs := map[string]struct {\n\t\trhandler         func(s network.Stream)\n\t\terrMsg           string\n\t\tholePunchTimeout time.Duration\n\t\tfilter           func(remoteID peer.ID, maddrs []ma.Multiaddr) []ma.Multiaddr\n\t}{\n\t\t\"responder does NOT send a CONNECT message\": {\n\t\t\trhandler: func(s network.Stream) {\n\t\t\t\twr := pbio.NewDelimitedWriter(s)\n\t\t\t\twr.WriteMsg(&holepunch_pb.HolePunch{Type: holepunch_pb.HolePunch_SYNC.Enum()})\n\t\t\t},\n\t\t\terrMsg: \"expect CONNECT message, got SYNC\",\n\t\t},\n\t\t\"responder does NOT support protocol\": {\n\t\t\trhandler: nil,\n\t\t},\n\t\t\"unable to READ CONNECT message from responder\": {\n\t\t\trhandler: func(s network.Stream) {\n\t\t\t\ts.Reset()\n\t\t\t},\n\t\t\terrMsg: \"failed to read CONNECT message\",\n\t\t},\n\t\t\"responder does NOT reply within hole punch deadline\": {\n\t\t\tholePunchTimeout: 200 * time.Millisecond,\n\t\t\trhandler:         func(_ network.Stream) { time.Sleep(5 * time.Second) },\n\t\t\terrMsg:           \"i/o deadline reached\",\n\t\t},\n\t\t\"no addrs after filtering\": {\n\t\t\terrMsg:   \"aborting hole punch initiation as we have no public address\",\n\t\t\trhandler: func(_ network.Stream) { time.Sleep(5 * time.Second) },\n\t\t\tfilter: func(_ peer.ID, _ []ma.Multiaddr) []ma.Multiaddr {\n\t\t\t\treturn []ma.Multiaddr{}\n\t\t\t},\n\t\t},\n\t}\n\n\tfor name, tc := range tcs {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tif tc.holePunchTimeout != 0 {\n\t\t\t\tcpy := holepunch.StreamTimeout\n\t\t\t\tholepunch.StreamTimeout = tc.holePunchTimeout\n\t\t\t\tdefer func() { holepunch.StreamTimeout = cpy }()\n\t\t\t}\n\n\t\t\trouter := &simnet.SimpleFirewallRouter{}\n\t\t\trelay := MustNewHost(t,\n\t\t\t\tquicSimnet(true, router),\n\t\t\t\tlibp2p.ListenAddrs(ma.StringCast(\"/ip4/1.2.0.1/udp/8000/quic-v1\")),\n\t\t\t\tlibp2p.DisableRelay(),\n\t\t\t\tlibp2p.ResourceManager(&network.NullResourceManager{}),\n\t\t\t\tlibp2p.WithFxOption(fx.Invoke(func(h host.Host) {\n\t\t\t\t\t// Setup relay service\n\t\t\t\t\t_, err := relayv2.New(h)\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t})),\n\t\t\t)\n\n\t\t\t// h1 does not have a holepunching service because we'll mock the holepunching stream handler below.\n\t\t\th1 := MustNewHost(t,\n\t\t\t\tquicSimnet(false, router),\n\t\t\t\tlibp2p.ForceReachabilityPrivate(),\n\t\t\t\tlibp2p.ListenAddrs(ma.StringCast(\"/ip4/2.2.0.1/udp/8000/quic-v1\")),\n\t\t\t\tlibp2p.ResourceManager(&network.NullResourceManager{}),\n\t\t\t\tconnectToRelay(&relay),\n\t\t\t)\n\n\t\t\th2 := MustNewHost(t,\n\t\t\t\tquicSimnet(false, router),\n\t\t\t\tlibp2p.ListenAddrs(ma.StringCast(\"/ip4/2.2.0.2/udp/8001/quic-v1\")),\n\t\t\t\tlibp2p.ResourceManager(&network.NullResourceManager{}),\n\t\t\t\tconnectToRelay(&relay),\n\t\t\t)\n\n\t\t\tdefer h1.Close()\n\t\t\tdefer h2.Close()\n\t\t\tdefer relay.Close()\n\n\t\t\ttime.Sleep(100 * time.Millisecond)\n\n\t\t\ttr := &mockEventTracer{}\n\t\t\topts := []holepunch.Option{holepunch.WithTracer(tr), holepunch.DirectDialTimeout(100 * time.Millisecond)}\n\t\t\tif tc.filter != nil {\n\t\t\t\tf := mockMaddrFilter{\n\t\t\t\t\tfilterLocal:  tc.filter,\n\t\t\t\t\tfilterRemote: tc.filter,\n\t\t\t\t}\n\t\t\t\topts = append(opts, holepunch.WithAddrFilter(f))\n\t\t\t}\n\n\t\t\thps := addHolePunchService(t, h2, []ma.Multiaddr{ma.StringCast(\"/ip4/2.2.0.2/udp/8001/quic-v1\")}, opts...)\n\t\t\t// We are only holepunching from h2 to h1. Remove h2's holepunching stream handler to avoid confusion.\n\t\t\th2.RemoveStreamHandler(holepunch.Protocol)\n\t\t\tif tc.rhandler != nil {\n\t\t\t\th1.SetStreamHandler(holepunch.Protocol, tc.rhandler)\n\t\t\t}\n\n\t\t\trequire.NoError(t, h2.Connect(context.Background(), peer.AddrInfo{\n\t\t\t\tID:    h1.ID(),\n\t\t\t\tAddrs: h1.Addrs(),\n\t\t\t}))\n\n\t\t\terr := hps.DirectConnect(h1.ID())\n\t\t\trequire.Error(t, err)\n\t\t\tif tc.errMsg != \"\" {\n\t\t\t\trequire.Contains(t, err.Error(), tc.errMsg)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc addrsToBytes(as []ma.Multiaddr) [][]byte {\n\tbzs := make([][]byte, 0, len(as))\n\tfor _, a := range as {\n\t\tbzs = append(bzs, a.Bytes())\n\t}\n\treturn bzs\n}\n\nfunc TestFailuresOnResponder(t *testing.T) {\n\ttcs := map[string]struct {\n\t\tinitiator        func(s network.Stream)\n\t\terrMsg           string\n\t\tholePunchTimeout time.Duration\n\t\tfilter           func(remoteID peer.ID, maddrs []ma.Multiaddr) []ma.Multiaddr\n\t}{\n\t\t\"initiator does NOT send a CONNECT message\": {\n\t\t\tinitiator: func(s network.Stream) {\n\t\t\t\tpbio.NewDelimitedWriter(s).WriteMsg(&holepunch_pb.HolePunch{Type: holepunch_pb.HolePunch_SYNC.Enum()})\n\t\t\t},\n\t\t\terrMsg: \"expected CONNECT message\",\n\t\t},\n\t\t\"initiator does NOT send a SYNC message after a CONNECT message\": {\n\t\t\tinitiator: func(s network.Stream) {\n\t\t\t\tw := pbio.NewDelimitedWriter(s)\n\t\t\t\tw.WriteMsg(&holepunch_pb.HolePunch{\n\t\t\t\t\tType:     holepunch_pb.HolePunch_CONNECT.Enum(),\n\t\t\t\t\tObsAddrs: addrsToBytes([]ma.Multiaddr{ma.StringCast(\"/ip4/127.0.0.1/tcp/1234\")}),\n\t\t\t\t})\n\t\t\t\tw.WriteMsg(&holepunch_pb.HolePunch{Type: holepunch_pb.HolePunch_CONNECT.Enum()})\n\t\t\t},\n\t\t\terrMsg: \"expected SYNC message\",\n\t\t},\n\t\t\"initiator does NOT reply within hole punch deadline\": {\n\t\t\tholePunchTimeout: 10 * time.Millisecond,\n\t\t\tinitiator: func(s network.Stream) {\n\t\t\t\tpbio.NewDelimitedWriter(s).WriteMsg(&holepunch_pb.HolePunch{\n\t\t\t\t\tType:     holepunch_pb.HolePunch_CONNECT.Enum(),\n\t\t\t\t\tObsAddrs: addrsToBytes([]ma.Multiaddr{ma.StringCast(\"/ip4/127.0.0.1/tcp/1234\")}),\n\t\t\t\t})\n\t\t\t\ttime.Sleep(10 * time.Second)\n\t\t\t},\n\t\t\terrMsg: \"i/o deadline reached\",\n\t\t},\n\t\t\"initiator does NOT send any addresses in CONNECT\": {\n\t\t\tholePunchTimeout: 10 * time.Millisecond,\n\t\t\tinitiator: func(s network.Stream) {\n\t\t\t\tpbio.NewDelimitedWriter(s).WriteMsg(&holepunch_pb.HolePunch{Type: holepunch_pb.HolePunch_CONNECT.Enum()})\n\t\t\t\ttime.Sleep(10 * time.Second)\n\t\t\t},\n\t\t\terrMsg: \"expected CONNECT message to contain at least one address\",\n\t\t},\n\t\t\"no addrs after filtering\": {\n\t\t\terrMsg: \"rejecting hole punch request, as we don't have any public addresses\",\n\t\t\tinitiator: func(s network.Stream) {\n\t\t\t\tpbio.NewDelimitedWriter(s).WriteMsg(&holepunch_pb.HolePunch{\n\t\t\t\t\tType:     holepunch_pb.HolePunch_CONNECT.Enum(),\n\t\t\t\t\tObsAddrs: addrsToBytes([]ma.Multiaddr{ma.StringCast(\"/ip4/127.0.0.1/tcp/1234\")}),\n\t\t\t\t})\n\t\t\t\ttime.Sleep(10 * time.Second)\n\t\t\t},\n\t\t\tfilter: func(_ peer.ID, _ []ma.Multiaddr) []ma.Multiaddr {\n\t\t\t\treturn []ma.Multiaddr{}\n\t\t\t},\n\t\t},\n\t}\n\n\tfor name, tc := range tcs {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tif tc.holePunchTimeout != 0 {\n\t\t\t\tcpy := holepunch.StreamTimeout\n\t\t\t\tholepunch.StreamTimeout = tc.holePunchTimeout\n\t\t\t\tdefer func() { holepunch.StreamTimeout = cpy }()\n\t\t\t}\n\t\t\ttr := &mockEventTracer{}\n\n\t\t\topts := []holepunch.Option{holepunch.WithTracer(tr), holepunch.DirectDialTimeout(100 * time.Millisecond)}\n\t\t\tif tc.filter != nil {\n\t\t\t\tf := mockMaddrFilter{\n\t\t\t\t\tfilterLocal:  tc.filter,\n\t\t\t\t\tfilterRemote: tc.filter,\n\t\t\t\t}\n\t\t\t\topts = append(opts, holepunch.WithAddrFilter(f))\n\t\t\t}\n\n\t\t\trouter := &simnet.SimpleFirewallRouter{}\n\t\t\trelay := MustNewHost(t,\n\t\t\t\tquicSimnet(true, router),\n\t\t\t\tlibp2p.ListenAddrs(ma.StringCast(\"/ip4/1.2.0.1/udp/8000/quic-v1\")),\n\t\t\t\tlibp2p.DisableRelay(),\n\t\t\t\tlibp2p.ResourceManager(&network.NullResourceManager{}),\n\t\t\t\tlibp2p.WithFxOption(fx.Invoke(func(h host.Host) {\n\t\t\t\t\t// Setup relay service\n\t\t\t\t\t_, err := relayv2.New(h)\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t})),\n\t\t\t)\n\t\t\th1 := MustNewHost(t,\n\t\t\t\tquicSimnet(false, router),\n\t\t\t\tlibp2p.EnableHolePunching(opts...),\n\t\t\t\tlibp2p.ListenAddrs(ma.StringCast(\"/ip4/2.2.0.1/udp/8000/quic-v1\")),\n\t\t\t\tlibp2p.ResourceManager(&network.NullResourceManager{}),\n\t\t\t\tconnectToRelay(&relay),\n\t\t\t\tlibp2p.ForceReachabilityPrivate(),\n\t\t\t)\n\n\t\t\th2 := MustNewHost(t,\n\t\t\t\tquicSimnet(false, router),\n\t\t\t\tlibp2p.ListenAddrs(ma.StringCast(\"/ip4/2.2.0.2/udp/8001/quic-v1\")),\n\t\t\t\tlibp2p.ResourceManager(&network.NullResourceManager{}),\n\t\t\t\tconnectToRelay(&relay),\n\t\t\t\tlibp2p.ForceReachabilityPrivate(),\n\t\t\t)\n\n\t\t\tdefer h1.Close()\n\t\t\tdefer h2.Close()\n\t\t\tdefer relay.Close()\n\n\t\t\ttime.Sleep(100 * time.Millisecond)\n\t\t\t// Apparently changing the order of the following two flakiness.\n\t\t\t// https://github.com/libp2p/go-libp2p/issues/3440\n\t\t\trequire.EventuallyWithT(t, func(c *assert.CollectT) {\n\t\t\t\tassert.Contains(c, h1.Mux().Protocols(), holepunch.Protocol)\n\t\t\t}, time.Second, 100*time.Millisecond)\n\t\t\trequire.NoError(t, h1.Connect(context.Background(), peer.AddrInfo{\n\t\t\t\tID:    h2.ID(),\n\t\t\t\tAddrs: h2.Addrs(),\n\t\t\t}))\n\n\t\t\ts, err := h2.NewStream(network.WithAllowLimitedConn(context.Background(), \"holepunch\"), h1.ID(), holepunch.Protocol)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tgo tc.initiator(s)\n\n\t\t\tgetTracerError := func(tr *mockEventTracer) []string {\n\t\t\t\tvar errs []string\n\t\t\t\tevents := tr.getEvents()\n\t\t\t\tfor _, ev := range events {\n\t\t\t\t\tif errEv, ok := ev.Evt.(*holepunch.ProtocolErrorEvt); ok {\n\t\t\t\t\t\terrs = append(errs, errEv.Error)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn errs\n\t\t\t}\n\n\t\t\trequire.Eventually(t, func() bool { return len(getTracerError(tr)) > 0 }, 5*time.Second, 100*time.Millisecond)\n\t\t\terrs := getTracerError(tr)\n\t\t\trequire.Len(t, errs, 1)\n\t\t\trequire.Contains(t, errs[0], tc.errMsg)\n\t\t})\n\t}\n}\n\nfunc ensureNoHolePunchingStream(t *testing.T, h1, h2 host.Host) {\n\trequire.Eventually(t, func() bool {\n\t\tfor _, c := range h1.Network().ConnsToPeer(h2.ID()) {\n\t\t\tfor _, s := range c.GetStreams() {\n\t\t\t\tif s.ID() == string(holepunch.Protocol) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true\n\t}, 5*time.Second, 50*time.Millisecond)\n\n\trequire.Eventually(t, func() bool {\n\t\tfor _, c := range h2.Network().ConnsToPeer(h1.ID()) {\n\t\t\tfor _, s := range c.GetStreams() {\n\t\t\t\tif s.ID() == string(holepunch.Protocol) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true\n\t}, 5*time.Second, 50*time.Millisecond)\n}\n\nfunc ensureDirectConn(t *testing.T, h1, h2 host.Host) {\n\trequire.Eventually(t, func() bool {\n\t\tfor _, c := range h1.Network().ConnsToPeer(h2.ID()) {\n\t\t\tif _, err := c.RemoteMultiaddr().ValueForProtocol(ma.P_CIRCUIT); err != nil {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\treturn false\n\t}, 5*time.Second, 50*time.Millisecond)\n\n\trequire.Eventually(t, func() bool {\n\t\tfor _, c := range h2.Network().ConnsToPeer(h1.ID()) {\n\t\t\tif _, err := c.RemoteMultiaddr().ValueForProtocol(ma.P_CIRCUIT); err != nil {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\treturn false\n\t}, 5*time.Second, 50*time.Millisecond)\n}\n\ntype MockSourceIPSelector struct {\n\tip atomic.Pointer[net.IP]\n}\n\nfunc (m *MockSourceIPSelector) PreferredSourceIPForDestination(_ *net.UDPAddr) (net.IP, error) {\n\treturn *m.ip.Load(), nil\n}\n\nfunc quicSimnet(isPubliclyReachably bool, router *simnet.SimpleFirewallRouter) libp2p.Option {\n\tm := &MockSourceIPSelector{}\n\treturn libp2p.QUICReuse(\n\t\tquicreuse.NewConnManager,\n\t\tquicreuse.OverrideSourceIPSelector(func() (quicreuse.SourceIPSelector, error) {\n\t\t\treturn m, nil\n\t\t}),\n\t\tquicreuse.OverrideListenUDP(func(_ string, address *net.UDPAddr) (net.PacketConn, error) {\n\t\t\tm.ip.Store(&address.IP)\n\t\t\tif isPubliclyReachably {\n\t\t\t\trouter.SetAddrPubliclyReachable(address)\n\t\t\t}\n\t\t\tc := simnet.NewSimConn(address)\n\t\t\trouter.AddNode(address, c)\n\t\t\treturn c, nil\n\t\t}))\n}\n\nfunc addHolePunchService(t *testing.T, h host.Host, extraAddrs []ma.Multiaddr, opts ...holepunch.Option) *holepunch.Service {\n\tt.Helper()\n\thps, err := holepunch.NewService(h, newIDService(t, h), func() []ma.Multiaddr {\n\t\taddrs := h.Addrs()\n\t\taddrs = append(addrs, extraAddrs...)\n\t\treturn addrs\n\t}, opts...)\n\trequire.NoError(t, err)\n\treturn hps\n}\n\nfunc waitForHolePunchingSvcActive(t *testing.T, h host.Host) {\n\trequire.EventuallyWithT(t, func(c *assert.CollectT) {\n\t\tassert.Contains(c, h.Mux().Protocols(), holepunch.Protocol)\n\t}, time.Second, 100*time.Millisecond)\n}\n\n// TestEndToEndSimConnectQUICReuse tests that hole punching works if we are\n// reusing the same port for QUIC and WebTransport, and when we have multiple\n// QUIC listeners on different ports.\n//\n// If this tests fails or is flaky it may be because:\n// - The quicreuse logic (and association logic) is not returning the appropriate transport for holepunching.\n// - The ordering of listeners is unexpected (remember the swarm will sort the listeners with `.ListenOrder()`).\nfunc TestEndToEndSimConnectQUICReuse(t *testing.T) {\n\th1tr := &mockEventTracer{}\n\th2tr := &mockEventTracer{}\n\n\trouter := &simnet.SimpleFirewallRouter{}\n\trelay := MustNewHost(t,\n\t\tquicSimnet(true, router),\n\t\tlibp2p.ListenAddrs(ma.StringCast(\"/ip4/1.2.0.1/udp/8000/quic-v1\")),\n\t\tlibp2p.DisableRelay(),\n\t\tlibp2p.ResourceManager(&network.NullResourceManager{}),\n\t\tlibp2p.WithFxOption(fx.Invoke(func(h host.Host) {\n\t\t\t// Setup relay service\n\t\t\t_, err := relayv2.New(h)\n\t\t\trequire.NoError(t, err)\n\t\t})),\n\t)\n\n\t// We return addrs of quic on port 8001 and circuit.\n\t// This lets us listen on other ports for QUIC in order to confuse the quicreuse logic during hole punching.\n\tonlyQuicOnPort8001AndCircuit := func(addrs []ma.Multiaddr) []ma.Multiaddr {\n\t\treturn slices.DeleteFunc(addrs, func(a ma.Multiaddr) bool {\n\t\t\t_, err := a.ValueForProtocol(ma.P_CIRCUIT)\n\t\t\tisCircuit := err == nil\n\t\t\tif isCircuit {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\t_, err = a.ValueForProtocol(ma.P_QUIC_V1)\n\t\t\tisQuic := err == nil\n\t\t\tif !isQuic {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tport, err := a.ValueForProtocol(ma.P_UDP)\n\t\t\tif err != nil {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tisPort8001 := port == \"8001\"\n\t\t\treturn !isPort8001\n\t\t})\n\t}\n\n\th1 := MustNewHost(t,\n\t\tquicSimnet(false, router),\n\t\tlibp2p.EnableHolePunching(holepunch.WithTracer(h1tr), holepunch.DirectDialTimeout(100*time.Millisecond)),\n\t\tlibp2p.ListenAddrs(ma.StringCast(\"/ip4/2.2.0.1/udp/8001/quic-v1/webtransport\")),\n\t\tlibp2p.ResourceManager(&network.NullResourceManager{}),\n\t\tlibp2p.AddrsFactory(onlyQuicOnPort8001AndCircuit),\n\t\tlibp2p.ForceReachabilityPrivate(),\n\t)\n\t// Listen on quic *after* listening on webtransport.\n\t// This is to test that the quicreuse logic is not returning the wrong transport.\n\t// See: https://github.com/libp2p/go-libp2p/issues/3165#issuecomment-2700126706 for details.\n\th1.Network().Listen(\n\t\tma.StringCast(\"/ip4/2.2.0.1/udp/8001/quic-v1\"),\n\t\tma.StringCast(\"/ip4/2.2.0.1/udp/9001/quic-v1\"),\n\t)\n\n\th2 := MustNewHost(t,\n\t\tquicSimnet(false, router),\n\t\tlibp2p.ListenAddrs(\n\t\t\tma.StringCast(\"/ip4/2.2.0.2/udp/8001/quic-v1/webtransport\"),\n\t\t),\n\t\tlibp2p.ResourceManager(&network.NullResourceManager{}),\n\t\tconnectToRelay(&relay),\n\t\tlibp2p.EnableHolePunching(holepunch.WithTracer(h2tr), holepunch.DirectDialTimeout(100*time.Millisecond)),\n\t\tlibp2p.AddrsFactory(onlyQuicOnPort8001AndCircuit),\n\t\tlibp2p.ForceReachabilityPrivate(),\n\t)\n\t// Listen on quic after listening on webtransport.\n\th2.Network().Listen(\n\t\tma.StringCast(\"/ip4/2.2.0.2/udp/8001/quic-v1\"),\n\t\tma.StringCast(\"/ip4/2.2.0.2/udp/9001/quic-v1\"),\n\t)\n\n\tdefer h1.Close()\n\tdefer h2.Close()\n\tdefer relay.Close()\n\n\t// Wait for holepunch service to start\n\twaitForHolePunchingSvcActive(t, h1)\n\twaitForHolePunchingSvcActive(t, h2)\n\n\tlearnAddrs(h1, h2)\n\tpingAtoB(t, h1, h2)\n\n\t// wait till a direct connection is complete\n\tensureDirectConn(t, h1, h2)\n}\n"
  },
  {
    "path": "p2p/protocol/holepunch/holepuncher.go",
    "content": "package holepunch\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/holepunch/pb\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/identify\"\n\t\"github.com/libp2p/go-msgio/pbio\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\n// ErrHolePunchActive is returned from DirectConnect when another hole punching attempt is currently running\nvar ErrHolePunchActive = errors.New(\"another hole punching attempt to this peer is active\")\n\nconst maxRetries = 3\n\n// The holePuncher is run on the peer that's behind a NAT / Firewall.\n// It observes new incoming connections via a relay that it has a reservation with,\n// and initiates the DCUtR protocol with them.\n// It then first tries to establish a direct connection, and if that fails, it\n// initiates a hole punch.\ntype holePuncher struct {\n\tctx       context.Context\n\tctxCancel context.CancelFunc\n\n\thost     host.Host\n\trefCount sync.WaitGroup\n\n\tids         identify.IDService\n\tlistenAddrs func() []ma.Multiaddr\n\n\tdirectDialTimeout time.Duration\n\n\t// active hole punches for deduplicating\n\tactiveMx sync.Mutex\n\tactive   map[peer.ID]struct{}\n\n\tcloseMx sync.RWMutex\n\tclosed  bool\n\n\ttracer *tracer\n\tfilter AddrFilter\n}\n\nfunc newHolePuncher(h host.Host, ids identify.IDService, listenAddrs func() []ma.Multiaddr, tracer *tracer, filter AddrFilter) *holePuncher {\n\thp := &holePuncher{\n\t\thost:        h,\n\t\tids:         ids,\n\t\tactive:      make(map[peer.ID]struct{}),\n\t\ttracer:      tracer,\n\t\tfilter:      filter,\n\t\tlistenAddrs: listenAddrs,\n\t}\n\thp.ctx, hp.ctxCancel = context.WithCancel(context.Background())\n\th.Network().Notify((*netNotifiee)(hp))\n\treturn hp\n}\n\nfunc (hp *holePuncher) beginDirectConnect(p peer.ID) error {\n\thp.closeMx.RLock()\n\tdefer hp.closeMx.RUnlock()\n\tif hp.closed {\n\t\treturn ErrClosed\n\t}\n\n\thp.activeMx.Lock()\n\tdefer hp.activeMx.Unlock()\n\tif _, ok := hp.active[p]; ok {\n\t\treturn ErrHolePunchActive\n\t}\n\n\thp.active[p] = struct{}{}\n\treturn nil\n}\n\n// DirectConnect attempts to make a direct connection with a remote peer.\n// It first attempts a direct dial (if we have a public address of that peer), and then\n// coordinates a hole punch over the given relay connection.\nfunc (hp *holePuncher) DirectConnect(p peer.ID) error {\n\tlog.Debug(\"beginDirectConnect\", \"source_peer\", hp.host.ID(), \"destination_peer\", p)\n\tif err := hp.beginDirectConnect(p); err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\thp.activeMx.Lock()\n\t\tdelete(hp.active, p)\n\t\thp.activeMx.Unlock()\n\t}()\n\n\treturn hp.directConnect(p)\n}\n\nfunc (hp *holePuncher) directConnect(rp peer.ID) error {\n\t// short-circuit check to see if we already have a direct connection\n\tif getDirectConnection(hp.host, rp) != nil {\n\t\tlog.Debug(\"already connected\", \"source_peer\", hp.host.ID(), \"destination_peer\", rp)\n\t\treturn nil\n\t}\n\n\tlog.Debug(\"attempting direct dial\", \"source_peer\", hp.host.ID(), \"destination_peer\", rp, \"addrs\", hp.host.Peerstore().Addrs(rp))\n\t// short-circuit hole punching if a direct dial works.\n\t// attempt a direct connection ONLY if we have a public address for the remote peer\n\tfor _, a := range hp.host.Peerstore().Addrs(rp) {\n\t\tif !isRelayAddress(a) && manet.IsPublicAddr(a) {\n\t\t\tforceDirectConnCtx := network.WithForceDirectDial(hp.ctx, \"hole-punching\")\n\t\t\tdialCtx, cancel := context.WithTimeout(forceDirectConnCtx, hp.directDialTimeout)\n\n\t\t\ttstart := time.Now()\n\t\t\t// This dials *all* addresses, public and private, from the peerstore.\n\t\t\terr := hp.host.Connect(dialCtx, peer.AddrInfo{ID: rp})\n\t\t\tdt := time.Since(tstart)\n\t\t\tcancel()\n\n\t\t\tif err != nil {\n\t\t\t\thp.tracer.DirectDialFailed(rp, dt, err)\n\t\t\t\tbreak\n\t\t\t}\n\t\t\thp.tracer.DirectDialSuccessful(rp, dt)\n\t\t\tlog.Debug(\"direct connection to peer successful, no need for a hole punch\", \"destination_peer\", rp)\n\t\t\treturn nil\n\t\t}\n\t}\n\n\tlog.Debug(\"got inbound proxy conn\", \"destination_peer\", rp)\n\n\t// hole punch\n\tfor i := 1; i <= maxRetries; i++ {\n\t\tisClient := false\n\t\t// On the last attempt we switch roles in case the connection is\n\t\t// being made with a client with switched roles. Common for peers\n\t\t// running go-libp2p prior to v0.41.\n\t\tif i == maxRetries {\n\t\t\tisClient = true\n\t\t}\n\t\taddrs, obsAddrs, rtt, err := hp.initiateHolePunch(rp)\n\t\tif err != nil {\n\t\t\thp.tracer.ProtocolError(rp, err)\n\t\t\treturn err\n\t\t}\n\t\tsynTime := rtt / 2\n\t\tlog.Debug(\"peer RTT and starting hole punch\", \"rtt\", rtt, \"syn_time\", synTime)\n\n\t\t// wait for sync to reach the other peer and then punch a hole for it in our NAT\n\t\t// by attempting a connect to it.\n\t\ttimer := time.NewTimer(synTime)\n\t\tselect {\n\t\tcase start := <-timer.C:\n\t\t\tpi := peer.AddrInfo{\n\t\t\t\tID:    rp,\n\t\t\t\tAddrs: addrs,\n\t\t\t}\n\t\t\thp.tracer.StartHolePunch(rp, addrs, rtt)\n\t\t\thp.tracer.HolePunchAttempt(pi.ID)\n\t\t\tctx, cancel := context.WithTimeout(hp.ctx, hp.directDialTimeout)\n\t\t\terr := holePunchConnect(ctx, hp.host, pi, isClient)\n\t\t\tcancel()\n\t\t\tdt := time.Since(start)\n\t\t\thp.tracer.EndHolePunch(rp, dt, err)\n\t\t\tif err == nil {\n\t\t\t\tlog.Debug(\"hole punching with successful\", \"destination_peer\", rp, \"duration\", dt)\n\t\t\t\thp.tracer.HolePunchFinished(\"initiator\", i, addrs, obsAddrs, getDirectConnection(hp.host, rp))\n\t\t\t\treturn nil\n\t\t\t}\n\t\tcase <-hp.ctx.Done():\n\t\t\ttimer.Stop()\n\t\t\treturn hp.ctx.Err()\n\t\t}\n\t\tif i == maxRetries {\n\t\t\thp.tracer.HolePunchFinished(\"initiator\", maxRetries, addrs, obsAddrs, nil)\n\t\t}\n\t}\n\treturn fmt.Errorf(\"all retries for hole punch with peer %s failed\", rp)\n}\n\n// initiateHolePunch opens a new hole punching coordination stream,\n// exchanges the addresses and measures the RTT.\nfunc (hp *holePuncher) initiateHolePunch(rp peer.ID) ([]ma.Multiaddr, []ma.Multiaddr, time.Duration, error) {\n\thpCtx := network.WithAllowLimitedConn(hp.ctx, \"hole-punch\")\n\tsCtx := network.WithNoDial(hpCtx, \"hole-punch\")\n\n\tstr, err := hp.host.NewStream(sCtx, rp, Protocol)\n\tif err != nil {\n\t\treturn nil, nil, 0, fmt.Errorf(\"failed to open hole-punching stream: %w\", err)\n\t}\n\tdefer str.Close()\n\tlog.Debug(\"initiateHolePunch\", \"remote_peer\", str.Conn().RemotePeer(), \"remote_multiaddr\", str.Conn().RemoteMultiaddr())\n\n\taddr, obsAddr, rtt, err := hp.initiateHolePunchImpl(str)\n\tif err != nil {\n\t\tstr.Reset()\n\t\treturn addr, obsAddr, rtt, fmt.Errorf(\"failed to initiateHolePunch: %w\", err)\n\t}\n\treturn addr, obsAddr, rtt, err\n}\n\nfunc (hp *holePuncher) initiateHolePunchImpl(str network.Stream) ([]ma.Multiaddr, []ma.Multiaddr, time.Duration, error) {\n\tif err := str.Scope().SetService(ServiceName); err != nil {\n\t\treturn nil, nil, 0, fmt.Errorf(\"error attaching stream to holepunch service: %s\", err)\n\t}\n\n\tif err := str.Scope().ReserveMemory(maxMsgSize, network.ReservationPriorityAlways); err != nil {\n\t\treturn nil, nil, 0, fmt.Errorf(\"error reserving memory for stream: %s\", err)\n\t}\n\tdefer str.Scope().ReleaseMemory(maxMsgSize)\n\n\tw := pbio.NewDelimitedWriter(str)\n\trd := pbio.NewDelimitedReader(str, maxMsgSize)\n\n\tstr.SetDeadline(time.Now().Add(StreamTimeout))\n\n\t// send a CONNECT and start RTT measurement.\n\tobsAddrs := removeRelayAddrs(hp.listenAddrs())\n\tif hp.filter != nil {\n\t\tobsAddrs = hp.filter.FilterLocal(str.Conn().RemotePeer(), obsAddrs)\n\t}\n\tif len(obsAddrs) == 0 {\n\t\treturn nil, nil, 0, errors.New(\"aborting hole punch initiation as we have no public address\")\n\t}\n\tlog.Debug(\"initiating hole punch\", \"observed_addrs\", obsAddrs)\n\n\tstart := time.Now()\n\tif err := w.WriteMsg(&pb.HolePunch{\n\t\tType:     pb.HolePunch_CONNECT.Enum(),\n\t\tObsAddrs: addrsToBytes(obsAddrs),\n\t}); err != nil {\n\t\tstr.Reset()\n\t\treturn nil, nil, 0, err\n\t}\n\n\t// wait for a CONNECT message from the remote peer\n\tvar msg pb.HolePunch\n\tif err := rd.ReadMsg(&msg); err != nil {\n\t\treturn nil, nil, 0, fmt.Errorf(\"failed to read CONNECT message from remote peer: %w\", err)\n\t}\n\trtt := time.Since(start)\n\tif t := msg.GetType(); t != pb.HolePunch_CONNECT {\n\t\treturn nil, nil, 0, fmt.Errorf(\"expect CONNECT message, got %s\", t)\n\t}\n\n\taddrs := removeRelayAddrs(addrsFromBytes(msg.ObsAddrs))\n\tif hp.filter != nil {\n\t\taddrs = hp.filter.FilterRemote(str.Conn().RemotePeer(), addrs)\n\t}\n\n\tif len(addrs) == 0 {\n\t\treturn nil, nil, 0, errors.New(\"didn't receive any public addresses in CONNECT\")\n\t}\n\n\tif err := w.WriteMsg(&pb.HolePunch{Type: pb.HolePunch_SYNC.Enum()}); err != nil {\n\t\treturn nil, nil, 0, fmt.Errorf(\"failed to send SYNC message for hole punching: %w\", err)\n\t}\n\treturn addrs, obsAddrs, rtt, nil\n}\n\nfunc (hp *holePuncher) Close() error {\n\thp.closeMx.Lock()\n\thp.closed = true\n\thp.closeMx.Unlock()\n\thp.ctxCancel()\n\thp.refCount.Wait()\n\treturn nil\n}\n\ntype netNotifiee holePuncher\n\nfunc (nn *netNotifiee) Connected(_ network.Network, conn network.Conn) {\n\ths := (*holePuncher)(nn)\n\n\t// Hole punch if it's an inbound proxy connection.\n\t// If we already have a direct connection with the remote peer, this will be a no-op.\n\tif conn.Stat().Direction == network.DirInbound && isRelayAddress(conn.RemoteMultiaddr()) {\n\t\ths.refCount.Add(1)\n\t\tgo func() {\n\t\t\tdefer hs.refCount.Done()\n\n\t\t\tselect {\n\t\t\t// waiting for Identify here will allow us to access the peer's public and observed addresses\n\t\t\t// that we can dial to for a hole punch.\n\t\t\tcase <-hs.ids.IdentifyWait(conn):\n\t\t\tcase <-hs.ctx.Done():\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\terr := hs.DirectConnect(conn.RemotePeer())\n\t\t\tif err != nil {\n\t\t\t\tlog.Debug(\"attempt to perform DirectConnect failed\", \"remote_peer\", conn.RemotePeer(), \"err\", err)\n\t\t\t}\n\t\t}()\n\t}\n}\n\nfunc (nn *netNotifiee) Disconnected(_ network.Network, _ network.Conn) {}\nfunc (nn *netNotifiee) Listen(_ network.Network, _ ma.Multiaddr)       {}\nfunc (nn *netNotifiee) ListenClose(_ network.Network, _ ma.Multiaddr)  {}\n"
  },
  {
    "path": "p2p/protocol/holepunch/metrics.go",
    "content": "package holepunch\n\nimport (\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/p2p/metricshelper\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n)\n\nconst metricNamespace = \"libp2p_holepunch\"\n\nvar (\n\tdirectDialsTotal = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"direct_dials_total\",\n\t\t\tHelp:      \"Direct Dials Total\",\n\t\t},\n\t\t[]string{\"outcome\"},\n\t)\n\thpAddressOutcomesTotal = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"address_outcomes_total\",\n\t\t\tHelp:      \"Hole Punch outcomes by Transport\",\n\t\t},\n\t\t[]string{\"side\", \"num_attempts\", \"ipv\", \"transport\", \"outcome\"},\n\t)\n\thpOutcomesTotal = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"outcomes_total\",\n\t\t\tHelp:      \"Hole Punch outcomes overall\",\n\t\t},\n\t\t[]string{\"side\", \"num_attempts\", \"outcome\"},\n\t)\n\n\tcollectors = []prometheus.Collector{\n\t\tdirectDialsTotal,\n\t\thpAddressOutcomesTotal,\n\t\thpOutcomesTotal,\n\t}\n)\n\ntype MetricsTracer interface {\n\tHolePunchFinished(side string, attemptNum int, theirAddrs []ma.Multiaddr, ourAddr []ma.Multiaddr, directConn network.ConnMultiaddrs)\n\tDirectDialFinished(success bool)\n}\n\ntype metricsTracer struct{}\n\nvar _ MetricsTracer = &metricsTracer{}\n\ntype metricsTracerSetting struct {\n\treg prometheus.Registerer\n}\n\ntype MetricsTracerOption func(*metricsTracerSetting)\n\nfunc WithRegisterer(reg prometheus.Registerer) MetricsTracerOption {\n\treturn func(s *metricsTracerSetting) {\n\t\tif reg != nil {\n\t\t\ts.reg = reg\n\t\t}\n\t}\n}\n\nfunc NewMetricsTracer(opts ...MetricsTracerOption) MetricsTracer {\n\tsetting := &metricsTracerSetting{reg: prometheus.DefaultRegisterer}\n\tfor _, opt := range opts {\n\t\topt(setting)\n\t}\n\tmetricshelper.RegisterCollectors(setting.reg, collectors...)\n\t// initialise metrics's labels so that the first data point is handled correctly\n\tfor _, side := range []string{\"initiator\", \"receiver\"} {\n\t\tfor _, numAttempts := range []string{\"1\", \"2\", \"3\", \"4\"} {\n\t\t\tfor _, outcome := range []string{\"success\", \"failed\", \"cancelled\", \"no_suitable_address\"} {\n\t\t\t\tfor _, ipv := range []string{\"ip4\", \"ip6\"} {\n\t\t\t\t\tfor _, transport := range []string{\"quic\", \"quic-v1\", \"tcp\", \"webtransport\"} {\n\t\t\t\t\t\thpAddressOutcomesTotal.WithLabelValues(side, numAttempts, ipv, transport, outcome)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif outcome == \"cancelled\" {\n\t\t\t\t\t// not a valid outcome for the overall holepunch metric\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\thpOutcomesTotal.WithLabelValues(side, numAttempts, outcome)\n\t\t\t}\n\t\t}\n\t}\n\treturn &metricsTracer{}\n}\n\n// HolePunchFinished tracks metrics completion of a holepunch. Metrics are tracked on\n// a holepunch attempt level and on individual addresses involved in a holepunch.\n//\n// outcome for an address is computed as:\n//\n//   - success:\n//     A direct connection was established with the peer using this address\n//   - cancelled:\n//     A direct connection was established with the peer but not using this address\n//   - failed:\n//     No direct connection was made to the peer and the peer reported an address\n//     with the same transport as this address\n//   - no_suitable_address:\n//     The peer reported no address with the same transport as this address\nfunc (mt *metricsTracer) HolePunchFinished(side string, numAttempts int,\n\tremoteAddrs []ma.Multiaddr, localAddrs []ma.Multiaddr, directConn network.ConnMultiaddrs) {\n\ttags := metricshelper.GetStringSlice()\n\tdefer metricshelper.PutStringSlice(tags)\n\n\t*tags = append(*tags, side, getNumAttemptString(numAttempts))\n\tvar dipv, dtransport string\n\tif directConn != nil {\n\t\tdipv = metricshelper.GetIPVersion(directConn.LocalMultiaddr())\n\t\tdtransport = metricshelper.GetTransport(directConn.LocalMultiaddr())\n\t}\n\n\tmatchingAddressCount := 0\n\t// calculate holepunch outcome for all the addresses involved\n\tfor _, la := range localAddrs {\n\t\tlipv := metricshelper.GetIPVersion(la)\n\t\tltransport := metricshelper.GetTransport(la)\n\n\t\tmatchingAddress := false\n\t\tfor _, ra := range remoteAddrs {\n\t\t\tripv := metricshelper.GetIPVersion(ra)\n\t\t\trtransport := metricshelper.GetTransport(ra)\n\t\t\tif ripv == lipv && rtransport == ltransport {\n\t\t\t\t// the peer reported an address with the same transport\n\t\t\t\tmatchingAddress = true\n\t\t\t\tmatchingAddressCount++\n\n\t\t\t\t*tags = append(*tags, ripv, rtransport)\n\t\t\t\tif directConn != nil && dipv == ripv && dtransport == rtransport {\n\t\t\t\t\t// the connection was made using this address\n\t\t\t\t\t*tags = append(*tags, \"success\")\n\t\t\t\t} else if directConn != nil {\n\t\t\t\t\t// connection was made but not using this address\n\t\t\t\t\t*tags = append(*tags, \"cancelled\")\n\t\t\t\t} else {\n\t\t\t\t\t// no connection was made\n\t\t\t\t\t*tags = append(*tags, \"failed\")\n\t\t\t\t}\n\t\t\t\thpAddressOutcomesTotal.WithLabelValues(*tags...).Inc()\n\t\t\t\t*tags = (*tags)[:2] // 2 because we want to keep (side, numAttempts)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !matchingAddress {\n\t\t\t*tags = append(*tags, lipv, ltransport, \"no_suitable_address\")\n\t\t\thpAddressOutcomesTotal.WithLabelValues(*tags...).Inc()\n\t\t\t*tags = (*tags)[:2] // 2 because we want to keep (side, numAttempts)\n\t\t}\n\t}\n\n\toutcome := \"failed\"\n\tif directConn != nil {\n\t\toutcome = \"success\"\n\t} else if matchingAddressCount == 0 {\n\t\t// there were no matching addresses, this attempt was going to fail\n\t\toutcome = \"no_suitable_address\"\n\t}\n\n\t*tags = append(*tags, outcome)\n\thpOutcomesTotal.WithLabelValues(*tags...).Inc()\n}\n\nfunc getNumAttemptString(numAttempt int) string {\n\tvar attemptStr = [...]string{\"0\", \"1\", \"2\", \"3\", \"4\", \"5\"}\n\tif numAttempt > 5 {\n\t\treturn \"> 5\"\n\t}\n\treturn attemptStr[numAttempt]\n}\n\nfunc (mt *metricsTracer) DirectDialFinished(success bool) {\n\ttags := metricshelper.GetStringSlice()\n\tdefer metricshelper.PutStringSlice(tags)\n\tif success {\n\t\t*tags = append(*tags, \"success\")\n\t} else {\n\t\t*tags = append(*tags, \"failed\")\n\t}\n\tdirectDialsTotal.WithLabelValues(*tags...).Inc()\n}\n"
  },
  {
    "path": "p2p/protocol/holepunch/metrics_noalloc_test.go",
    "content": "//go:build nocover\n\npackage holepunch\n\nimport (\n\t\"math/rand\"\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\nfunc TestNoCoverNoAllocMetrics(t *testing.T) {\n\taddrs1 := [][]ma.Multiaddr{\n\t\t{\n\t\t\tma.StringCast(\"/ip4/0.0.0.0/tcp/1\"),\n\t\t\tma.StringCast(\"/ip4/1.2.3.4/udp/2/quic-v1\"),\n\t\t},\n\t\tnil,\n\t}\n\taddrs2 := [][]ma.Multiaddr{\n\t\t{\n\t\t\tma.StringCast(\"/ip4/1.2.3.4/tcp/3\"),\n\t\t\tma.StringCast(\"/ip4/1.2.3.4/udp/4/quic-v1\"),\n\t\t},\n\t\tnil,\n\t}\n\tconns := []network.ConnMultiaddrs{\n\t\t&mockConnMultiaddrs{local: addrs1[0][0], remote: addrs2[0][0]},\n\t\tnil,\n\t}\n\tsides := []string{\"initiator\", \"receiver\"}\n\tmt := NewMetricsTracer()\n\ttestcases := map[string]func(){\n\t\t\"DirectDialFinished\": func() { mt.DirectDialFinished(rand.Intn(2) == 1) },\n\t\t\"HolePunchFinished\": func() {\n\t\t\tmt.HolePunchFinished(sides[rand.Intn(len(sides))], rand.Intn(maxRetries), addrs1[rand.Intn(len(addrs1))],\n\t\t\t\taddrs2[rand.Intn(len(addrs2))], conns[rand.Intn(len(conns))])\n\t\t},\n\t}\n\tfor method, f := range testcases {\n\t\tt.Run(method, func(t *testing.T) {\n\t\t\tcnt := testing.AllocsPerRun(1000, f)\n\t\t\tif cnt > 0 {\n\t\t\t\tt.Errorf(\"%s Failed: expected 0 allocs got %0.2f\", method, cnt)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "p2p/protocol/holepunch/metrics_test.go",
    "content": "package holepunch\n\nimport (\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\tdto \"github.com/prometheus/client_model/go\"\n)\n\nfunc getCounterValue(t *testing.T, counter *prometheus.CounterVec, labels ...string) int {\n\tt.Helper()\n\tm := &dto.Metric{}\n\tif err := counter.WithLabelValues(labels...).Write(m); err != nil {\n\t\tt.Errorf(\"failed to extract counter value %s\", err)\n\t\treturn 0\n\t}\n\treturn int(*m.Counter.Value)\n\n}\n\nfunc TestHolePunchOutcomeCounter(t *testing.T) {\n\tt1 := ma.StringCast(\"/ip4/1.2.3.4/tcp/1\")\n\tt2 := ma.StringCast(\"/ip4/1.2.3.4/tcp/2\")\n\n\tq1v1 := ma.StringCast(\"/ip4/1.2.3.4/udp/1/quic-v1\")\n\tq2v1 := ma.StringCast(\"/ip4/1.2.3.4/udp/2/quic-v1\")\n\n\ttype testcase struct {\n\t\tname       string\n\t\ttheirAddrs []ma.Multiaddr\n\t\tourAddrs   []ma.Multiaddr\n\t\tconn       network.ConnMultiaddrs\n\t\tresult     map[[3]string]int\n\t}\n\ttestcases := []testcase{\n\t\t{\n\t\t\tname:       \"connection success\",\n\t\t\ttheirAddrs: []ma.Multiaddr{t1, q1v1},\n\t\t\tourAddrs:   []ma.Multiaddr{t2, q2v1},\n\t\t\tconn:       &mockConnMultiaddrs{local: t1, remote: t2},\n\t\t\tresult: map[[3]string]int{\n\t\t\t\t[...]string{\"ip4\", \"tcp\", \"success\"}:       1,\n\t\t\t\t[...]string{\"ip4\", \"quic-v1\", \"cancelled\"}: 1,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:       \"connection failed\",\n\t\t\ttheirAddrs: []ma.Multiaddr{t1},\n\t\t\tourAddrs:   []ma.Multiaddr{t2, q2v1},\n\t\t\tconn:       nil,\n\t\t\tresult: map[[3]string]int{\n\t\t\t\t[...]string{\"ip4\", \"tcp\", \"failed\"}:                  1,\n\t\t\t\t[...]string{\"ip4\", \"quic-v1\", \"no_suitable_address\"}: 1,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:       \"no_suitable_address\",\n\t\t\ttheirAddrs: []ma.Multiaddr{t1, q1v1},\n\t\t\tourAddrs:   []ma.Multiaddr{t2, q2v1},\n\t\t\tconn:       &mockConnMultiaddrs{local: q1v1, remote: q2v1},\n\t\t\tresult: map[[3]string]int{\n\t\t\t\t[...]string{\"ip4\", \"tcp\", \"cancelled\"}:   1,\n\t\t\t\t[...]string{\"ip4\", \"quic-v1\", \"failed\"}:  0,\n\t\t\t\t[...]string{\"ip4\", \"quic-v1\", \"success\"}: 1,\n\t\t\t\t[...]string{\"ip4\", \"tcp\", \"success\"}:     0,\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\treg := prometheus.NewRegistry()\n\t\t\thpAddressOutcomesTotal.Reset()\n\t\t\tmt := NewMetricsTracer(WithRegisterer(reg))\n\t\t\tfor _, side := range []string{\"receiver\", \"initiator\"} {\n\t\t\t\tmt.HolePunchFinished(side, 1, tc.theirAddrs, tc.ourAddrs, tc.conn)\n\t\t\t\tfor labels, value := range tc.result {\n\t\t\t\t\tv := getCounterValue(t, hpAddressOutcomesTotal, side, \"1\", labels[0], labels[1], labels[2])\n\t\t\t\t\tif v != value {\n\t\t\t\t\t\tt.Errorf(\"Invalid metric value %s: expected: %d got: %d\", labels, value, v)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype mockConnMultiaddrs struct {\n\tlocal, remote ma.Multiaddr\n}\n\nfunc (cma *mockConnMultiaddrs) LocalMultiaddr() ma.Multiaddr {\n\treturn cma.local\n}\n\nfunc (cma *mockConnMultiaddrs) RemoteMultiaddr() ma.Multiaddr {\n\treturn cma.remote\n}\n"
  },
  {
    "path": "p2p/protocol/holepunch/pb/holepunch.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.6\n// \tprotoc        v5.29.2\n// source: p2p/protocol/holepunch/pb/holepunch.proto\n\npackage pb\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype HolePunch_Type int32\n\nconst (\n\tHolePunch_CONNECT HolePunch_Type = 100\n\tHolePunch_SYNC    HolePunch_Type = 300\n)\n\n// Enum value maps for HolePunch_Type.\nvar (\n\tHolePunch_Type_name = map[int32]string{\n\t\t100: \"CONNECT\",\n\t\t300: \"SYNC\",\n\t}\n\tHolePunch_Type_value = map[string]int32{\n\t\t\"CONNECT\": 100,\n\t\t\"SYNC\":    300,\n\t}\n)\n\nfunc (x HolePunch_Type) Enum() *HolePunch_Type {\n\tp := new(HolePunch_Type)\n\t*p = x\n\treturn p\n}\n\nfunc (x HolePunch_Type) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (HolePunch_Type) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_p2p_protocol_holepunch_pb_holepunch_proto_enumTypes[0].Descriptor()\n}\n\nfunc (HolePunch_Type) Type() protoreflect.EnumType {\n\treturn &file_p2p_protocol_holepunch_pb_holepunch_proto_enumTypes[0]\n}\n\nfunc (x HolePunch_Type) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Do not use.\nfunc (x *HolePunch_Type) UnmarshalJSON(b []byte) error {\n\tnum, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)\n\tif err != nil {\n\t\treturn err\n\t}\n\t*x = HolePunch_Type(num)\n\treturn nil\n}\n\n// Deprecated: Use HolePunch_Type.Descriptor instead.\nfunc (HolePunch_Type) EnumDescriptor() ([]byte, []int) {\n\treturn file_p2p_protocol_holepunch_pb_holepunch_proto_rawDescGZIP(), []int{0, 0}\n}\n\n// spec: https://github.com/libp2p/specs/blob/master/relay/DCUtR.md\ntype HolePunch struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tType          *HolePunch_Type        `protobuf:\"varint,1,req,name=type,enum=holepunch.pb.HolePunch_Type\" json:\"type,omitempty\"`\n\tObsAddrs      [][]byte               `protobuf:\"bytes,2,rep,name=ObsAddrs\" json:\"ObsAddrs,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *HolePunch) Reset() {\n\t*x = HolePunch{}\n\tmi := &file_p2p_protocol_holepunch_pb_holepunch_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *HolePunch) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*HolePunch) ProtoMessage() {}\n\nfunc (x *HolePunch) ProtoReflect() protoreflect.Message {\n\tmi := &file_p2p_protocol_holepunch_pb_holepunch_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use HolePunch.ProtoReflect.Descriptor instead.\nfunc (*HolePunch) Descriptor() ([]byte, []int) {\n\treturn file_p2p_protocol_holepunch_pb_holepunch_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *HolePunch) GetType() HolePunch_Type {\n\tif x != nil && x.Type != nil {\n\t\treturn *x.Type\n\t}\n\treturn HolePunch_CONNECT\n}\n\nfunc (x *HolePunch) GetObsAddrs() [][]byte {\n\tif x != nil {\n\t\treturn x.ObsAddrs\n\t}\n\treturn nil\n}\n\nvar File_p2p_protocol_holepunch_pb_holepunch_proto protoreflect.FileDescriptor\n\nconst file_p2p_protocol_holepunch_pb_holepunch_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\")p2p/protocol/holepunch/pb/holepunch.proto\\x12\\fholepunch.pb\\\"y\\n\" +\n\t\"\\tHolePunch\\x120\\n\" +\n\t\"\\x04type\\x18\\x01 \\x02(\\x0e2\\x1c.holepunch.pb.HolePunch.TypeR\\x04type\\x12\\x1a\\n\" +\n\t\"\\bObsAddrs\\x18\\x02 \\x03(\\fR\\bObsAddrs\\\"\\x1e\\n\" +\n\t\"\\x04Type\\x12\\v\\n\" +\n\t\"\\aCONNECT\\x10d\\x12\\t\\n\" +\n\t\"\\x04SYNC\\x10\\xac\\x02B7Z5github.com/libp2p/go-libp2p/p2p/protocol/holepunch/pb\"\n\nvar (\n\tfile_p2p_protocol_holepunch_pb_holepunch_proto_rawDescOnce sync.Once\n\tfile_p2p_protocol_holepunch_pb_holepunch_proto_rawDescData []byte\n)\n\nfunc file_p2p_protocol_holepunch_pb_holepunch_proto_rawDescGZIP() []byte {\n\tfile_p2p_protocol_holepunch_pb_holepunch_proto_rawDescOnce.Do(func() {\n\t\tfile_p2p_protocol_holepunch_pb_holepunch_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_p2p_protocol_holepunch_pb_holepunch_proto_rawDesc), len(file_p2p_protocol_holepunch_pb_holepunch_proto_rawDesc)))\n\t})\n\treturn file_p2p_protocol_holepunch_pb_holepunch_proto_rawDescData\n}\n\nvar file_p2p_protocol_holepunch_pb_holepunch_proto_enumTypes = make([]protoimpl.EnumInfo, 1)\nvar file_p2p_protocol_holepunch_pb_holepunch_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_p2p_protocol_holepunch_pb_holepunch_proto_goTypes = []any{\n\t(HolePunch_Type)(0), // 0: holepunch.pb.HolePunch.Type\n\t(*HolePunch)(nil),   // 1: holepunch.pb.HolePunch\n}\nvar file_p2p_protocol_holepunch_pb_holepunch_proto_depIdxs = []int32{\n\t0, // 0: holepunch.pb.HolePunch.type:type_name -> holepunch.pb.HolePunch.Type\n\t1, // [1:1] is the sub-list for method output_type\n\t1, // [1:1] is the sub-list for method input_type\n\t1, // [1:1] is the sub-list for extension type_name\n\t1, // [1:1] is the sub-list for extension extendee\n\t0, // [0:1] is the sub-list for field type_name\n}\n\nfunc init() { file_p2p_protocol_holepunch_pb_holepunch_proto_init() }\nfunc file_p2p_protocol_holepunch_pb_holepunch_proto_init() {\n\tif File_p2p_protocol_holepunch_pb_holepunch_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_p2p_protocol_holepunch_pb_holepunch_proto_rawDesc), len(file_p2p_protocol_holepunch_pb_holepunch_proto_rawDesc)),\n\t\t\tNumEnums:      1,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_p2p_protocol_holepunch_pb_holepunch_proto_goTypes,\n\t\tDependencyIndexes: file_p2p_protocol_holepunch_pb_holepunch_proto_depIdxs,\n\t\tEnumInfos:         file_p2p_protocol_holepunch_pb_holepunch_proto_enumTypes,\n\t\tMessageInfos:      file_p2p_protocol_holepunch_pb_holepunch_proto_msgTypes,\n\t}.Build()\n\tFile_p2p_protocol_holepunch_pb_holepunch_proto = out.File\n\tfile_p2p_protocol_holepunch_pb_holepunch_proto_goTypes = nil\n\tfile_p2p_protocol_holepunch_pb_holepunch_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "p2p/protocol/holepunch/pb/holepunch.proto",
    "content": "syntax = \"proto2\";\n\npackage holepunch.pb;\n\noption go_package = \"github.com/libp2p/go-libp2p/p2p/protocol/holepunch/pb\";\n\n// spec: https://github.com/libp2p/specs/blob/master/relay/DCUtR.md\nmessage HolePunch {\n  enum Type {\n    CONNECT = 100;\n    SYNC = 300;\n  }\n\n  required Type type=1;\n  repeated bytes ObsAddrs = 2;\n}\n"
  },
  {
    "path": "p2p/protocol/holepunch/svc.go",
    "content": "package holepunch\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/holepunch/pb\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/identify\"\n\t\"github.com/libp2p/go-msgio/pbio\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\nconst defaultDirectDialTimeout = 10 * time.Second\n\n// Protocol is the libp2p protocol for Hole Punching.\nconst Protocol protocol.ID = \"/libp2p/dcutr\"\n\nvar log = logging.Logger(\"p2p-holepunch\")\n\n// StreamTimeout is the timeout for the hole punch protocol stream.\nvar StreamTimeout = 1 * time.Minute\n\nconst (\n\tServiceName = \"libp2p.holepunch\"\n\n\tmaxMsgSize = 4 * 1024 // 4K\n)\n\n// ErrClosed is returned when the hole punching is closed\nvar ErrClosed = errors.New(\"hole punching service closing\")\n\ntype Option func(*Service) error\n\nfunc DirectDialTimeout(timeout time.Duration) Option {\n\treturn func(s *Service) error {\n\t\ts.directDialTimeout = timeout\n\t\treturn nil\n\t}\n}\n\n// The Service runs on every node that supports the DCUtR protocol.\ntype Service struct {\n\tctx       context.Context\n\tctxCancel context.CancelFunc\n\n\thost host.Host\n\t// ids helps with connection reversal. We wait for identify to complete and attempt\n\t// a direct connection to the peer if it's publicly reachable.\n\tids identify.IDService\n\t// listenAddrs provides the addresses for the host to be used for hole punching. We use this\n\t// and not host.Addrs because host.Addrs might remove public unreachable address and only advertise\n\t// publicly reachable relay addresses.\n\tlistenAddrs func() []ma.Multiaddr\n\n\tdirectDialTimeout time.Duration\n\tholePuncherMx     sync.Mutex\n\tholePuncher       *holePuncher\n\n\thasPublicAddrsChan chan struct{}\n\n\ttracer *tracer\n\tfilter AddrFilter\n\n\trefCount sync.WaitGroup\n}\n\n// NewService creates a new service that can be used for hole punching\n// The Service runs on all hosts that support the DCUtR protocol,\n// no matter if they are behind a NAT / firewall or not.\n// The Service handles DCUtR streams (which are initiated from the node behind\n// a NAT / Firewall once we establish a connection to them through a relay.\n//\n// listenAddrs MUST only return public addresses.\nfunc NewService(h host.Host, ids identify.IDService, listenAddrs func() []ma.Multiaddr, opts ...Option) (*Service, error) {\n\tif ids == nil {\n\t\treturn nil, errors.New(\"identify service can't be nil\")\n\t}\n\n\tctx, cancel := context.WithCancel(context.Background())\n\ts := &Service{\n\t\tctx:                ctx,\n\t\tctxCancel:          cancel,\n\t\thost:               h,\n\t\tids:                ids,\n\t\tlistenAddrs:        listenAddrs,\n\t\thasPublicAddrsChan: make(chan struct{}),\n\t\tdirectDialTimeout:  defaultDirectDialTimeout,\n\t}\n\n\tfor _, opt := range opts {\n\t\tif err := opt(s); err != nil {\n\t\t\tcancel()\n\t\t\treturn nil, err\n\t\t}\n\t}\n\ts.tracer.Start()\n\n\ts.refCount.Add(1)\n\tgo s.waitForPublicAddr()\n\n\treturn s, nil\n}\n\nfunc (s *Service) waitForPublicAddr() {\n\tdefer s.refCount.Done()\n\n\tlog.Debug(\"waiting until we have at least one public address\", \"peer\", s.host.ID())\n\n\t// TODO: We should have an event here that fires when identify discovers a new\n\t// address.\n\t// As we currently don't have an event like this, just check our observed addresses\n\t// regularly (exponential backoff starting at 250 ms, capped at 5s).\n\tduration := 250 * time.Millisecond\n\tconst maxDuration = 5 * time.Second\n\tt := time.NewTimer(duration)\n\tdefer t.Stop()\n\tfor {\n\t\tif len(s.listenAddrs()) > 0 {\n\t\t\tlog.Debug(\"Host now has a public address\", \"hostID\", s.host.ID(), \"addresses\", s.host.Addrs())\n\t\t\ts.host.SetStreamHandler(Protocol, s.handleNewStream)\n\t\t\tbreak\n\t\t}\n\n\t\tselect {\n\t\tcase <-s.ctx.Done():\n\t\t\treturn\n\t\tcase <-t.C:\n\t\t\tduration *= 2\n\t\t\tif duration > maxDuration {\n\t\t\t\tduration = maxDuration\n\t\t\t}\n\t\t\tt.Reset(duration)\n\t\t}\n\t}\n\n\ts.holePuncherMx.Lock()\n\tif s.ctx.Err() != nil {\n\t\t// service is closed\n\t\treturn\n\t}\n\ts.holePuncher = newHolePuncher(s.host, s.ids, s.listenAddrs, s.tracer, s.filter)\n\ts.holePuncher.directDialTimeout = s.directDialTimeout\n\ts.holePuncherMx.Unlock()\n\tclose(s.hasPublicAddrsChan)\n}\n\n// Close closes the Hole Punch Service.\nfunc (s *Service) Close() error {\n\tvar err error\n\ts.ctxCancel()\n\ts.holePuncherMx.Lock()\n\tif s.holePuncher != nil {\n\t\terr = s.holePuncher.Close()\n\t}\n\ts.holePuncherMx.Unlock()\n\ts.tracer.Close()\n\ts.host.RemoveStreamHandler(Protocol)\n\ts.refCount.Wait()\n\treturn err\n}\n\nfunc (s *Service) incomingHolePunch(str network.Stream) (rtt time.Duration, remoteAddrs []ma.Multiaddr, ownAddrs []ma.Multiaddr, err error) {\n\t// sanity check: a hole punch request should only come from peers behind a relay\n\tif !isRelayAddress(str.Conn().RemoteMultiaddr()) {\n\t\treturn 0, nil, nil, fmt.Errorf(\"received hole punch stream: %s\", str.Conn().RemoteMultiaddr())\n\t}\n\townAddrs = s.listenAddrs()\n\tif s.filter != nil {\n\t\townAddrs = s.filter.FilterLocal(str.Conn().RemotePeer(), ownAddrs)\n\t}\n\n\t// If we can't tell the peer where to dial us, there's no point in starting the hole punching.\n\tif len(ownAddrs) == 0 {\n\t\treturn 0, nil, nil, errors.New(\"rejecting hole punch request, as we don't have any public addresses\")\n\t}\n\n\tif err := str.Scope().ReserveMemory(maxMsgSize, network.ReservationPriorityAlways); err != nil {\n\t\tlog.Debug(\"error reserving memory for stream\", \"err\", err)\n\t\treturn 0, nil, nil, err\n\t}\n\tdefer str.Scope().ReleaseMemory(maxMsgSize)\n\n\twr := pbio.NewDelimitedWriter(str)\n\trd := pbio.NewDelimitedReader(str, maxMsgSize)\n\n\t// Read Connect message\n\tmsg := new(pb.HolePunch)\n\n\tstr.SetDeadline(time.Now().Add(StreamTimeout))\n\n\tif err := rd.ReadMsg(msg); err != nil {\n\t\treturn 0, nil, nil, fmt.Errorf(\"failed to read message from initiator: %w\", err)\n\t}\n\tif t := msg.GetType(); t != pb.HolePunch_CONNECT {\n\t\treturn 0, nil, nil, fmt.Errorf(\"expected CONNECT message from initiator but got %d\", t)\n\t}\n\n\tobsDial := removeRelayAddrs(addrsFromBytes(msg.ObsAddrs))\n\tif s.filter != nil {\n\t\tobsDial = s.filter.FilterRemote(str.Conn().RemotePeer(), obsDial)\n\t}\n\n\tlog.Debug(\"received hole punch request\", \"peer\", str.Conn().RemotePeer(), \"addrs\", obsDial)\n\tif len(obsDial) == 0 {\n\t\treturn 0, nil, nil, errors.New(\"expected CONNECT message to contain at least one address\")\n\t}\n\n\t// Write CONNECT message\n\tmsg.Reset()\n\tmsg.Type = pb.HolePunch_CONNECT.Enum()\n\tmsg.ObsAddrs = addrsToBytes(ownAddrs)\n\ttstart := time.Now()\n\tif err := wr.WriteMsg(msg); err != nil {\n\t\treturn 0, nil, nil, fmt.Errorf(\"failed to write CONNECT message to initiator: %w\", err)\n\t}\n\n\t// Read SYNC message\n\tmsg.Reset()\n\tif err := rd.ReadMsg(msg); err != nil {\n\t\treturn 0, nil, nil, fmt.Errorf(\"failed to read message from initiator: %w\", err)\n\t}\n\tif t := msg.GetType(); t != pb.HolePunch_SYNC {\n\t\treturn 0, nil, nil, fmt.Errorf(\"expected SYNC message from initiator but got %d\", t)\n\t}\n\treturn time.Since(tstart), obsDial, ownAddrs, nil\n}\n\nfunc (s *Service) handleNewStream(str network.Stream) {\n\t// Check directionality of the underlying connection.\n\t// Peer A receives an inbound connection from peer B.\n\t// Peer A opens a new hole punch stream to peer B.\n\t// Peer B receives this stream, calling this function.\n\t// Peer B sees the underlying connection as an outbound connection.\n\tif str.Conn().Stat().Direction == network.DirInbound {\n\t\tstr.Reset()\n\t\treturn\n\t}\n\n\tif err := str.Scope().SetService(ServiceName); err != nil {\n\t\tlog.Debug(\"error attaching stream to holepunch service\", \"err\", err)\n\t\tstr.Reset()\n\t\treturn\n\t}\n\n\trp := str.Conn().RemotePeer()\n\trtt, addrs, ownAddrs, err := s.incomingHolePunch(str)\n\tif err != nil {\n\t\ts.tracer.ProtocolError(rp, err)\n\t\tlog.Debug(\"error handling holepunching stream\", \"peer\", rp, \"err\", err)\n\t\tstr.Reset()\n\t\treturn\n\t}\n\tstr.Close()\n\n\t// Hole punch now by forcing a connect\n\tpi := peer.AddrInfo{\n\t\tID:    rp,\n\t\tAddrs: addrs,\n\t}\n\ts.tracer.StartHolePunch(rp, addrs, rtt)\n\tlog.Debug(\"starting hole punch\", \"peer\", rp)\n\tstart := time.Now()\n\ts.tracer.HolePunchAttempt(pi.ID)\n\tctx, cancel := context.WithTimeout(s.ctx, s.directDialTimeout)\n\terr = holePunchConnect(ctx, s.host, pi, true) // true (Client)\n\tcancel()\n\tdt := time.Since(start)\n\ts.tracer.EndHolePunch(rp, dt, err)\n\ts.tracer.HolePunchFinished(\"receiver\", 1, addrs, ownAddrs, getDirectConnection(s.host, rp))\n}\n\n// DirectConnect is only exposed for testing purposes.\n// TODO: find a solution for this.\nfunc (s *Service) DirectConnect(p peer.ID) error {\n\t<-s.hasPublicAddrsChan\n\ts.holePuncherMx.Lock()\n\tholePuncher := s.holePuncher\n\ts.holePuncherMx.Unlock()\n\treturn holePuncher.DirectConnect(p)\n}\n"
  },
  {
    "path": "p2p/protocol/holepunch/tracer.go",
    "content": "package holepunch\n\nimport (\n\t\"context\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\nconst (\n\ttracerGCInterval    = 2 * time.Minute\n\ttracerCacheDuration = 5 * time.Minute\n)\n\n// WithTracer enables holepunch tracing with EventTracer et\nfunc WithTracer(et EventTracer) Option {\n\treturn func(hps *Service) error {\n\t\thps.tracer = &tracer{\n\t\t\tet:    et,\n\t\t\tmt:    nil,\n\t\t\tself:  hps.host.ID(),\n\t\t\tpeers: make(map[peer.ID]peerInfo),\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// WithMetricsTracer enables holepunch Tracing with MetricsTracer mt\nfunc WithMetricsTracer(mt MetricsTracer) Option {\n\treturn func(hps *Service) error {\n\t\thps.tracer = &tracer{\n\t\t\tet:    nil,\n\t\t\tmt:    mt,\n\t\t\tself:  hps.host.ID(),\n\t\t\tpeers: make(map[peer.ID]peerInfo),\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// WithMetricsAndEventTracer enables holepunch tracking with MetricsTracer and EventTracer\nfunc WithMetricsAndEventTracer(mt MetricsTracer, et EventTracer) Option {\n\treturn func(hps *Service) error {\n\t\thps.tracer = &tracer{\n\t\t\tet:    et,\n\t\t\tmt:    mt,\n\t\t\tself:  hps.host.ID(),\n\t\t\tpeers: make(map[peer.ID]peerInfo),\n\t\t}\n\t\treturn nil\n\t}\n}\n\ntype tracer struct {\n\tet   EventTracer\n\tmt   MetricsTracer\n\tself peer.ID\n\n\trefCount  sync.WaitGroup\n\tctx       context.Context\n\tctxCancel context.CancelFunc\n\n\tmutex sync.Mutex\n\tpeers map[peer.ID]peerInfo\n}\n\ntype peerInfo struct {\n\tcounter int\n\tlast    time.Time\n}\n\ntype EventTracer interface {\n\tTrace(evt *Event)\n}\n\ntype Event struct {\n\tTimestamp int64   // UNIX nanos\n\tPeer      peer.ID // local peer ID\n\tRemote    peer.ID // remote peer ID\n\tType      string  // event type\n\tEvt       any     // the actual event\n}\n\n// Event Types\nconst (\n\tDirectDialEvtT       = \"DirectDial\"\n\tProtocolErrorEvtT    = \"ProtocolError\"\n\tStartHolePunchEvtT   = \"StartHolePunch\"\n\tEndHolePunchEvtT     = \"EndHolePunch\"\n\tHolePunchAttemptEvtT = \"HolePunchAttempt\"\n)\n\n// Event Objects\ntype DirectDialEvt struct {\n\tSuccess      bool\n\tEllapsedTime time.Duration\n\tError        string `json:\",omitempty\"`\n}\n\ntype ProtocolErrorEvt struct {\n\tError string\n}\n\ntype StartHolePunchEvt struct {\n\tRemoteAddrs []string\n\tRTT         time.Duration\n}\n\ntype EndHolePunchEvt struct {\n\tSuccess      bool\n\tEllapsedTime time.Duration\n\tError        string `json:\",omitempty\"`\n}\n\ntype HolePunchAttemptEvt struct {\n\tAttempt int\n}\n\n// tracer interface\nfunc (t *tracer) DirectDialSuccessful(p peer.ID, dt time.Duration) {\n\tif t == nil {\n\t\treturn\n\t}\n\n\tif t.et != nil {\n\t\tt.et.Trace(&Event{\n\t\t\tTimestamp: time.Now().UnixNano(),\n\t\t\tPeer:      t.self,\n\t\t\tRemote:    p,\n\t\t\tType:      DirectDialEvtT,\n\t\t\tEvt: &DirectDialEvt{\n\t\t\t\tSuccess:      true,\n\t\t\t\tEllapsedTime: dt,\n\t\t\t},\n\t\t})\n\t}\n\n\tif t.mt != nil {\n\t\tt.mt.DirectDialFinished(true)\n\t}\n}\n\nfunc (t *tracer) DirectDialFailed(p peer.ID, dt time.Duration, err error) {\n\tif t == nil {\n\t\treturn\n\t}\n\n\tif t.et != nil {\n\t\tt.et.Trace(&Event{\n\t\t\tTimestamp: time.Now().UnixNano(),\n\t\t\tPeer:      t.self,\n\t\t\tRemote:    p,\n\t\t\tType:      DirectDialEvtT,\n\t\t\tEvt: &DirectDialEvt{\n\t\t\t\tSuccess:      false,\n\t\t\t\tEllapsedTime: dt,\n\t\t\t\tError:        err.Error(),\n\t\t\t},\n\t\t})\n\t}\n\n\tif t.mt != nil {\n\t\tt.mt.DirectDialFinished(false)\n\t}\n}\n\nfunc (t *tracer) ProtocolError(p peer.ID, err error) {\n\tif t != nil && t.et != nil {\n\t\tt.et.Trace(&Event{\n\t\t\tTimestamp: time.Now().UnixNano(),\n\t\t\tPeer:      t.self,\n\t\t\tRemote:    p,\n\t\t\tType:      ProtocolErrorEvtT,\n\t\t\tEvt: &ProtocolErrorEvt{\n\t\t\t\tError: err.Error(),\n\t\t\t},\n\t\t})\n\t}\n}\n\nfunc (t *tracer) StartHolePunch(p peer.ID, obsAddrs []ma.Multiaddr, rtt time.Duration) {\n\tif t != nil && t.et != nil {\n\t\taddrs := make([]string, 0, len(obsAddrs))\n\t\tfor _, a := range obsAddrs {\n\t\t\taddrs = append(addrs, a.String())\n\t\t}\n\n\t\tt.et.Trace(&Event{\n\t\t\tTimestamp: time.Now().UnixNano(),\n\t\t\tPeer:      t.self,\n\t\t\tRemote:    p,\n\t\t\tType:      StartHolePunchEvtT,\n\t\t\tEvt: &StartHolePunchEvt{\n\t\t\t\tRemoteAddrs: addrs,\n\t\t\t\tRTT:         rtt,\n\t\t\t},\n\t\t})\n\t}\n}\n\nfunc (t *tracer) EndHolePunch(p peer.ID, dt time.Duration, err error) {\n\tif t != nil && t.et != nil {\n\t\tevt := &EndHolePunchEvt{\n\t\t\tSuccess:      err == nil,\n\t\t\tEllapsedTime: dt,\n\t\t}\n\t\tif err != nil {\n\t\t\tevt.Error = err.Error()\n\t\t}\n\n\t\tt.et.Trace(&Event{\n\t\t\tTimestamp: time.Now().UnixNano(),\n\t\t\tPeer:      t.self,\n\t\t\tRemote:    p,\n\t\t\tType:      EndHolePunchEvtT,\n\t\t\tEvt:       evt,\n\t\t})\n\t}\n}\n\nfunc (t *tracer) HolePunchFinished(side string, numAttempts int, theirAddrs []ma.Multiaddr, ourAddrs []ma.Multiaddr, directConn network.Conn) {\n\tif t != nil && t.mt != nil {\n\t\tt.mt.HolePunchFinished(side, numAttempts, theirAddrs, ourAddrs, directConn)\n\t}\n}\n\nfunc (t *tracer) HolePunchAttempt(p peer.ID) {\n\tif t != nil && t.et != nil {\n\t\tnow := time.Now()\n\t\tt.mutex.Lock()\n\t\tattempt := t.peers[p]\n\t\tattempt.counter++\n\t\tcounter := attempt.counter\n\t\tattempt.last = now\n\t\tt.peers[p] = attempt\n\t\tt.mutex.Unlock()\n\n\t\tt.et.Trace(&Event{\n\t\t\tTimestamp: now.UnixNano(),\n\t\t\tPeer:      t.self,\n\t\t\tRemote:    p,\n\t\t\tType:      HolePunchAttemptEvtT,\n\t\t\tEvt:       &HolePunchAttemptEvt{Attempt: counter},\n\t\t})\n\t}\n}\n\n// gc cleans up the peers map. This is only run when tracer is initialised with a non nil\n// EventTracer\nfunc (t *tracer) gc() {\n\tdefer t.refCount.Done()\n\ttimer := time.NewTicker(tracerGCInterval)\n\tdefer timer.Stop()\n\n\tfor {\n\t\tselect {\n\t\tcase <-timer.C:\n\t\t\tnow := time.Now()\n\t\t\tt.mutex.Lock()\n\t\t\tfor id, entry := range t.peers {\n\t\t\t\tif entry.last.Before(now.Add(-tracerCacheDuration)) {\n\t\t\t\t\tdelete(t.peers, id)\n\t\t\t\t}\n\t\t\t}\n\t\t\tt.mutex.Unlock()\n\t\tcase <-t.ctx.Done():\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (t *tracer) Start() {\n\tif t != nil && t.et != nil {\n\t\tt.ctx, t.ctxCancel = context.WithCancel(context.Background())\n\t\tt.refCount.Add(1)\n\t\tgo t.gc()\n\t}\n}\n\nfunc (t *tracer) Close() error {\n\tif t != nil && t.et != nil {\n\t\tt.ctxCancel()\n\t\tt.refCount.Wait()\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "p2p/protocol/holepunch/util.go",
    "content": "package holepunch\n\nimport (\n\t\"context\"\n\t\"slices\"\n\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\nfunc removeRelayAddrs(addrs []ma.Multiaddr) []ma.Multiaddr {\n\treturn slices.DeleteFunc(addrs, isRelayAddress)\n}\n\nfunc isRelayAddress(a ma.Multiaddr) bool {\n\t_, err := a.ValueForProtocol(ma.P_CIRCUIT)\n\treturn err == nil\n}\n\nfunc addrsToBytes(as []ma.Multiaddr) [][]byte {\n\tbzs := make([][]byte, 0, len(as))\n\tfor _, a := range as {\n\t\tbzs = append(bzs, a.Bytes())\n\t}\n\treturn bzs\n}\n\nfunc addrsFromBytes(bzs [][]byte) []ma.Multiaddr {\n\taddrs := make([]ma.Multiaddr, 0, len(bzs))\n\tfor _, bz := range bzs {\n\t\ta, err := ma.NewMultiaddrBytes(bz)\n\t\tif err == nil {\n\t\t\taddrs = append(addrs, a)\n\t\t}\n\t}\n\treturn addrs\n}\n\nfunc getDirectConnection(h host.Host, p peer.ID) network.Conn {\n\tfor _, c := range h.Network().ConnsToPeer(p) {\n\t\tif !isRelayAddress(c.RemoteMultiaddr()) {\n\t\t\treturn c\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc holePunchConnect(ctx context.Context, host host.Host, pi peer.AddrInfo, isClient bool) error {\n\tholePunchCtx := network.WithSimultaneousConnect(ctx, isClient, \"hole-punching\")\n\tforceDirectConnCtx := network.WithForceDirectDial(holePunchCtx, \"hole-punching\")\n\n\tlog.Debug(\"holepunchConnect\", \"source_peer\", host.ID(), \"destination_peer\", pi.ID, \"addrs\", pi.Addrs)\n\tif err := host.Connect(forceDirectConnCtx, pi); err != nil {\n\t\tlog.Debug(\"hole punch attempt with peer failed\", \"destination_peer\", pi.ID, \"err\", err)\n\t\treturn err\n\t}\n\tlog.Debug(\"hole punch successful\", \"destination_peer\", pi.ID)\n\treturn nil\n}\n"
  },
  {
    "path": "p2p/protocol/identify/id.go",
    "content": "package identify\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/netip\"\n\t\"slices\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/event\"\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\t\"github.com/libp2p/go-libp2p/core/record\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/eventbus\"\n\tuseragent \"github.com/libp2p/go-libp2p/p2p/protocol/identify/internal/user-agent\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/identify/pb\"\n\t\"github.com/libp2p/go-libp2p/x/rate\"\n\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n\t\"github.com/libp2p/go-msgio/pbio\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n\tmsmux \"github.com/multiformats/go-multistream\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\nvar log = logging.Logger(\"net/identify\")\n\nconst (\n\t// ID is the protocol.ID of version 1.0.0 of the identify service.\n\tID = \"/ipfs/id/1.0.0\"\n\t// IDPush is the protocol.ID of the Identify push protocol.\n\t// It sends full identify messages containing the current state of the peer.\n\tIDPush = \"/ipfs/id/push/1.0.0\"\n\t// DefaultTimeout for all id interactions, incoming / outgoing, id / id-push.\n\tDefaultTimeout = 5 * time.Second\n\t// ServiceName is the default identify service name\n\tServiceName = \"libp2p.identify\"\n\n\tlegacyIDSize          = 2 * 1024\n\tsignedIDSize          = 8 * 1024\n\tmaxOwnIdentifyMsgSize = 4 * 1024 // smaller than what we accept. This is 4k to be compatible with rust-libp2p\n\tmaxMessages           = 10\n\tmaxPushConcurrency    = 32\n\t// number of addresses to keep for peers we have disconnected from for peerstore.RecentlyConnectedTTL time\n\t// This number can be small as we already filter peer addresses based on whether the peer is connected to us over\n\t// localhost, private IP or public IP address\n\trecentlyConnectedPeerMaxAddrs = 20\n\tconnectedPeerMaxAddrs         = 500\n)\n\nvar (\n\tdefaultNetworkPrefixRateLimits = []rate.PrefixLimit{\n\t\t{Prefix: netip.MustParsePrefix(\"127.0.0.0/8\"), Limit: rate.Limit{}}, // inf\n\t\t{Prefix: netip.MustParsePrefix(\"::1/128\"), Limit: rate.Limit{}},     // inf\n\t}\n\tdefaultGlobalRateLimit      = rate.Limit{RPS: 2000, Burst: 3000}\n\tdefaultIPv4SubnetRateLimits = []rate.SubnetLimit{\n\t\t{PrefixLength: 24, Limit: rate.Limit{RPS: 0.2, Burst: 10}}, // 1 every 5 seconds\n\t}\n\tdefaultIPv6SubnetRateLimits = []rate.SubnetLimit{\n\t\t{PrefixLength: 56, Limit: rate.Limit{RPS: 0.2, Burst: 10}}, // 1 every 5 seconds\n\t\t{PrefixLength: 48, Limit: rate.Limit{RPS: 0.5, Burst: 20}}, // 1 every 2 seconds\n\t}\n)\n\ntype identifySnapshot struct {\n\tseq       uint64\n\tprotocols []protocol.ID\n\taddrs     []ma.Multiaddr\n\trecord    *record.Envelope\n}\n\n// Equal says if two snapshots are identical.\n// It does NOT compare the sequence number.\nfunc (s identifySnapshot) Equal(other *identifySnapshot) bool {\n\thasRecord := s.record != nil\n\totherHasRecord := other.record != nil\n\tif hasRecord != otherHasRecord {\n\t\treturn false\n\t}\n\tif hasRecord && !s.record.Equal(other.record) {\n\t\treturn false\n\t}\n\tif !slices.Equal(s.protocols, other.protocols) {\n\t\treturn false\n\t}\n\tif len(s.addrs) != len(other.addrs) {\n\t\treturn false\n\t}\n\tfor i, a := range s.addrs {\n\t\tif !a.Equal(other.addrs[i]) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\ntype IDService interface {\n\t// IdentifyConn synchronously triggers an identify request on the connection and\n\t// waits for it to complete. If the connection is being identified by another\n\t// caller, this call will wait. If the connection has already been identified,\n\t// it will return immediately.\n\tIdentifyConn(network.Conn)\n\t// IdentifyWait triggers an identify (if the connection has not already been\n\t// identified) and returns a channel that is closed when the identify protocol\n\t// completes.\n\tIdentifyWait(network.Conn) <-chan struct{}\n\tStart()\n\tio.Closer\n}\n\ntype identifyPushSupport uint8\n\nconst (\n\tidentifyPushSupportUnknown identifyPushSupport = iota\n\tidentifyPushSupported\n\tidentifyPushUnsupported\n)\n\ntype entry struct {\n\t// The IdentifyWaitChan is created when IdentifyWait is called for the first time.\n\t// IdentifyWait closes this channel when the Identify request completes, or when it fails.\n\tIdentifyWaitChan chan struct{}\n\n\t// PushSupport saves our knowledge about the peer's support of the Identify Push protocol.\n\t// Before the identify request returns, we don't know yet if the peer supports Identify Push.\n\tPushSupport identifyPushSupport\n\t// Sequence is the sequence number of the last snapshot we sent to this peer.\n\tSequence uint64\n}\n\n// idService is a structure that implements ProtocolIdentify.\n// It is a trivial service that gives the other peer some\n// useful information about the local peer. A sort of hello.\n//\n// The idService sends:\n//   - Our libp2p Protocol Version\n//   - Our libp2p Agent Version\n//   - Our public Listen Addresses\ntype idService struct {\n\tHost            host.Host\n\tUserAgent       string\n\tProtocolVersion string\n\n\tmetricsTracer MetricsTracer\n\n\tsetupCompleted chan struct{} // is closed when Start has finished setting up\n\tctx            context.Context\n\tctxCancel      context.CancelFunc\n\t// track resources that need to be shut down before we shut down\n\trefCount sync.WaitGroup\n\n\tdisableSignedPeerRecord bool\n\ttimeout                 time.Duration\n\n\tconnsMu sync.RWMutex\n\t// The conns map contains all connections we're currently handling.\n\t// Connections are inserted as soon as they're available in the swarm\n\t// Connections are removed from the map when the connection disconnects.\n\tconns map[network.Conn]entry\n\n\taddrMu sync.Mutex\n\n\temitters struct {\n\t\tevtPeerProtocolsUpdated        event.Emitter\n\t\tevtPeerIdentificationCompleted event.Emitter\n\t\tevtPeerIdentificationFailed    event.Emitter\n\t}\n\n\tcurrentSnapshot struct {\n\t\tsync.Mutex\n\t\tsnapshot identifySnapshot\n\t}\n\n\trateLimiter *rate.Limiter\n}\n\n// NewIDService constructs a new *idService and activates it by\n// attaching its stream handler to the given host.Host.\nfunc NewIDService(h host.Host, opts ...Option) (*idService, error) {\n\tcfg := config{\n\t\ttimeout: DefaultTimeout,\n\t}\n\tfor _, opt := range opts {\n\t\topt(&cfg)\n\t}\n\n\tuserAgent := useragent.DefaultUserAgent()\n\tif cfg.userAgent != \"\" {\n\t\tuserAgent = cfg.userAgent\n\t}\n\n\tctx, cancel := context.WithCancel(context.Background())\n\ts := &idService{\n\t\tHost:                    h,\n\t\tUserAgent:               userAgent,\n\t\tProtocolVersion:         cfg.protocolVersion,\n\t\tctx:                     ctx,\n\t\tctxCancel:               cancel,\n\t\tconns:                   make(map[network.Conn]entry),\n\t\tdisableSignedPeerRecord: cfg.disableSignedPeerRecord,\n\t\tsetupCompleted:          make(chan struct{}),\n\t\tmetricsTracer:           cfg.metricsTracer,\n\t\ttimeout:                 cfg.timeout,\n\t\trateLimiter: &rate.Limiter{\n\t\t\tGlobalLimit:         defaultGlobalRateLimit,\n\t\t\tNetworkPrefixLimits: defaultNetworkPrefixRateLimits,\n\t\t\tSubnetRateLimiter: rate.SubnetLimiter{\n\t\t\t\tIPv4SubnetLimits: defaultIPv4SubnetRateLimits,\n\t\t\t\tIPv6SubnetLimits: defaultIPv6SubnetRateLimits,\n\t\t\t\tGracePeriod:      1 * time.Minute,\n\t\t\t},\n\t\t},\n\t}\n\n\tvar err error\n\ts.emitters.evtPeerProtocolsUpdated, err = h.EventBus().Emitter(&event.EvtPeerProtocolsUpdated{})\n\tif err != nil {\n\t\tlog.Warn(\"identify service not emitting peer protocol updates\", \"err\", err)\n\t}\n\ts.emitters.evtPeerIdentificationCompleted, err = h.EventBus().Emitter(&event.EvtPeerIdentificationCompleted{})\n\tif err != nil {\n\t\tlog.Warn(\"identify service not emitting identification completed events\", \"err\", err)\n\t}\n\ts.emitters.evtPeerIdentificationFailed, err = h.EventBus().Emitter(&event.EvtPeerIdentificationFailed{})\n\tif err != nil {\n\t\tlog.Warn(\"identify service not emitting identification failed events\", \"err\", err)\n\t}\n\treturn s, nil\n}\n\nfunc (ids *idService) Start() {\n\tids.Host.Network().Notify((*netNotifiee)(ids))\n\tids.Host.SetStreamHandler(ID, ids.handleIdentifyRequest)\n\tids.Host.SetStreamHandler(IDPush, ids.rateLimiter.Limit(ids.handlePush))\n\tids.updateSnapshot()\n\tclose(ids.setupCompleted)\n\n\tids.refCount.Add(1)\n\tgo ids.loop(ids.ctx)\n}\n\nfunc (ids *idService) loop(ctx context.Context) {\n\tdefer ids.refCount.Done()\n\n\tsub, err := ids.Host.EventBus().Subscribe(\n\t\t[]any{&event.EvtLocalProtocolsUpdated{}, &event.EvtLocalAddressesUpdated{}},\n\t\teventbus.BufSize(256),\n\t\teventbus.Name(\"identify (loop)\"),\n\t)\n\tif err != nil {\n\t\tlog.Error(\"failed to subscribe to events on the bus\", \"err\", err)\n\t\treturn\n\t}\n\tdefer sub.Close()\n\n\t// Send pushes from a separate Go routine.\n\t// That way, we can end up with\n\t// * this Go routine busy looping over all peers in sendPushes\n\t// * another push being queued in the triggerPush channel\n\ttriggerPush := make(chan struct{}, 1)\n\tids.refCount.Add(1)\n\tgo func() {\n\t\tdefer ids.refCount.Done()\n\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn\n\t\t\tcase <-triggerPush:\n\t\t\t\tids.sendPushes(ctx)\n\t\t\t}\n\t\t}\n\t}()\n\n\tfor {\n\t\tselect {\n\t\tcase e, ok := <-sub.Out():\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif updated := ids.updateSnapshot(); !updated {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif ids.metricsTracer != nil {\n\t\t\t\tids.metricsTracer.TriggeredPushes(e)\n\t\t\t}\n\t\t\tselect {\n\t\t\tcase triggerPush <- struct{}{}:\n\t\t\tdefault: // we already have one more push queued, no need to queue another one\n\t\t\t}\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (ids *idService) sendPushes(ctx context.Context) {\n\tids.connsMu.RLock()\n\tconns := make([]network.Conn, 0, len(ids.conns))\n\tfor c, e := range ids.conns {\n\t\t// Push even if we don't know if push is supported.\n\t\t// This will be only the case while the IdentifyWaitChan call is in flight.\n\t\tif e.PushSupport == identifyPushSupported || e.PushSupport == identifyPushSupportUnknown {\n\t\t\tconns = append(conns, c)\n\t\t}\n\t}\n\tids.connsMu.RUnlock()\n\n\tsem := make(chan struct{}, maxPushConcurrency)\n\tvar wg sync.WaitGroup\n\tfor _, c := range conns {\n\t\t// check if the connection is still alive\n\t\tids.connsMu.RLock()\n\t\te, ok := ids.conns[c]\n\t\tids.connsMu.RUnlock()\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\t// check if we already sent the current snapshot to this peer\n\t\tids.currentSnapshot.Lock()\n\t\tsnapshot := ids.currentSnapshot.snapshot\n\t\tids.currentSnapshot.Unlock()\n\t\tif e.Sequence >= snapshot.seq {\n\t\t\tlog.Debug(\"already sent this snapshot to peer\", \"peer\", c.RemotePeer(), \"seq\", snapshot.seq)\n\t\t\tcontinue\n\t\t}\n\t\t// we haven't, send it now\n\t\tsem <- struct{}{}\n\t\twg.Add(1)\n\t\tgo func(c network.Conn) {\n\t\t\tdefer wg.Done()\n\t\t\tdefer func() { <-sem }()\n\t\t\tctx, cancel := context.WithTimeout(ctx, ids.timeout)\n\t\t\tdefer cancel()\n\n\t\t\tstr, err := newStreamAndNegotiate(ctx, c, IDPush, ids.timeout)\n\t\t\tif err != nil { // connection might have been closed recently\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// TODO: find out if the peer supports push if we didn't have any information about push support\n\t\t\tif err := ids.sendIdentifyResp(str, true); err != nil {\n\t\t\t\tlog.Debug(\"failed to send identify push\", \"peer\", c.RemotePeer(), \"error\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t}(c)\n\t}\n\twg.Wait()\n}\n\n// Close shuts down the idService\nfunc (ids *idService) Close() error {\n\tids.ctxCancel()\n\tids.refCount.Wait()\n\treturn nil\n}\n\n// IdentifyConn runs the Identify protocol on a connection.\n// It returns when we've received the peer's Identify message (or the request fails).\n// If successful, the peer store will contain the peer's addresses and supported protocols.\nfunc (ids *idService) IdentifyConn(c network.Conn) {\n\t<-ids.IdentifyWait(c)\n}\n\n// IdentifyWait runs the Identify protocol on a connection.\n// It doesn't block and returns a channel that is closed when we receive\n// the peer's Identify message (or the request fails).\n// If successful, the peer store will contain the peer's addresses and supported protocols.\nfunc (ids *idService) IdentifyWait(c network.Conn) <-chan struct{} {\n\tids.connsMu.Lock()\n\tdefer ids.connsMu.Unlock()\n\n\te, found := ids.conns[c]\n\tif !found {\n\t\t// No entry found. We may have gotten an out of order notification. Check it we should have this conn (because we're still connected)\n\t\t// We hold the ids.connsMu lock so this is safe since a disconnect event will be processed later if we are connected.\n\t\tif c.IsClosed() {\n\t\t\tlog.Debug(\"connection not found in identify service\", \"peer\", c.RemotePeer())\n\t\t\tch := make(chan struct{})\n\t\t\tclose(ch)\n\t\t\treturn ch\n\t\t} else {\n\t\t\tids.addConnWithLock(c)\n\t\t}\n\t}\n\n\tif e.IdentifyWaitChan != nil {\n\t\treturn e.IdentifyWaitChan\n\t}\n\t// First call to IdentifyWait for this connection. Create the channel.\n\te.IdentifyWaitChan = make(chan struct{})\n\tids.conns[c] = e\n\n\t// Spawn an identify. The connection may actually be closed\n\t// already, but that doesn't really matter. We'll fail to open a\n\t// stream then forget the connection.\n\tgo func() {\n\t\tdefer close(e.IdentifyWaitChan)\n\t\tif err := ids.identifyConn(c); err != nil {\n\t\t\tlog.Warn(\"failed to identify peer\", \"peer\", c.RemotePeer(), \"error\", err)\n\t\t\tids.emitters.evtPeerIdentificationFailed.Emit(event.EvtPeerIdentificationFailed{Peer: c.RemotePeer(), Reason: err})\n\t\t\treturn\n\t\t}\n\t}()\n\n\treturn e.IdentifyWaitChan\n}\n\n// newStreamAndNegotiate opens a new stream on the given connection and negotiates the given protocol.\nfunc newStreamAndNegotiate(ctx context.Context, c network.Conn, proto protocol.ID, timeout time.Duration) (network.Stream, error) {\n\ts, err := c.NewStream(network.WithAllowLimitedConn(ctx, \"identify\"))\n\tif err != nil {\n\t\tlog.Debug(\"error opening identify stream\", \"peer\", c.RemotePeer(), \"error\", err)\n\t\treturn nil, fmt.Errorf(\"failed to open new stream: %w\", err)\n\t}\n\n\t// Ignore the error. Consistent with our previous behavior. (See https://github.com/libp2p/go-libp2p/issues/3109)\n\t_ = s.SetDeadline(time.Now().Add(timeout))\n\n\tif err := s.SetProtocol(proto); err != nil {\n\t\tlog.Warn(\"error setting identify protocol for stream\", \"err\", err)\n\t\t_ = s.Reset()\n\t\treturn nil, fmt.Errorf(\"failed to set protocol: %w\", err)\n\t}\n\n\t// ok give the response to our handler.\n\tif err := msmux.SelectProtoOrFail(proto, s); err != nil {\n\t\tlog.Info(\"failed negotiate identify protocol with peer\", \"peer\", c.RemotePeer(), \"error\", err)\n\t\t_ = s.Reset()\n\t\treturn nil, fmt.Errorf(\"multistream mux select protocol failed: %w\", err)\n\t}\n\treturn s, nil\n}\n\nfunc (ids *idService) identifyConn(c network.Conn) error {\n\tctx, cancel := context.WithTimeout(context.Background(), ids.timeout)\n\tdefer cancel()\n\ts, err := newStreamAndNegotiate(network.WithAllowLimitedConn(ctx, \"identify\"), c, ID, ids.timeout)\n\tif err != nil {\n\t\tlog.Debug(\"error opening identify stream\", \"peer\", c.RemotePeer(), \"error\", err)\n\t\treturn err\n\t}\n\n\treturn ids.handleIdentifyResponse(s, false)\n}\n\n// handlePush handles incoming identify push streams\nfunc (ids *idService) handlePush(s network.Stream) {\n\ts.SetDeadline(time.Now().Add(ids.timeout))\n\tif err := ids.handleIdentifyResponse(s, true); err != nil {\n\t\tlog.Debug(\"failed to handle identify push\", \"err\", err)\n\t}\n}\n\nfunc (ids *idService) handleIdentifyRequest(s network.Stream) {\n\t_ = ids.sendIdentifyResp(s, false)\n}\n\nfunc (ids *idService) sendIdentifyResp(s network.Stream, isPush bool) error {\n\tif err := s.Scope().SetService(ServiceName); err != nil {\n\t\ts.Reset()\n\t\treturn fmt.Errorf(\"failed to attaching stream to identify service: %w\", err)\n\t}\n\tdefer s.Close()\n\n\tids.currentSnapshot.Lock()\n\tsnapshot := ids.currentSnapshot.snapshot\n\tids.currentSnapshot.Unlock()\n\n\tlog.Debug(\"sending snapshot\", \"seq\", snapshot.seq, \"protocols\", snapshot.protocols, \"addrs\", snapshot.addrs)\n\n\tmes := ids.createBaseIdentifyResponse(s.Conn(), &snapshot)\n\tmes.SignedPeerRecord = ids.getSignedRecord(&snapshot)\n\n\tlog.Debug(\"sending identify message\", \"id\", ID, \"remote_peer\", s.Conn().RemotePeer(), \"remote_multiaddr\", s.Conn().RemoteMultiaddr())\n\tif err := ids.writeChunkedIdentifyMsg(s, mes); err != nil {\n\t\treturn err\n\t}\n\n\tif ids.metricsTracer != nil {\n\t\tids.metricsTracer.IdentifySent(isPush, len(mes.Protocols), len(mes.ListenAddrs))\n\t}\n\n\tids.connsMu.Lock()\n\tdefer ids.connsMu.Unlock()\n\te, ok := ids.conns[s.Conn()]\n\t// The connection might already have been closed.\n\t// We *should* receive the Connected notification from the swarm before we're able to accept the peer's\n\t// Identify stream, but if that for some reason doesn't work, we also wouldn't have a map entry here.\n\t// The only consequence would be that we send a spurious Push to that peer later.\n\tif !ok {\n\t\treturn nil\n\t}\n\te.Sequence = snapshot.seq\n\tids.conns[s.Conn()] = e\n\treturn nil\n}\n\nfunc (ids *idService) handleIdentifyResponse(s network.Stream, isPush bool) error {\n\tif err := s.Scope().SetService(ServiceName); err != nil {\n\t\tlog.Warn(\"error attaching stream to identify service\", \"err\", err)\n\t\ts.Reset()\n\t\treturn err\n\t}\n\n\tif err := s.Scope().ReserveMemory(signedIDSize, network.ReservationPriorityAlways); err != nil {\n\t\tlog.Warn(\"error reserving memory for identify stream\", \"err\", err)\n\t\ts.Reset()\n\t\treturn err\n\t}\n\tdefer s.Scope().ReleaseMemory(signedIDSize)\n\n\tc := s.Conn()\n\n\tr := pbio.NewDelimitedReader(s, signedIDSize)\n\tmes := &pb.Identify{}\n\n\tif err := readAllIDMessages(r, mes); err != nil {\n\t\tlog.Warn(\"error reading identify message\", \"err\", err)\n\t\ts.Reset()\n\t\treturn err\n\t}\n\n\tdefer s.Close()\n\n\tlog.Debug(\"received identify message\", \"protocol\", s.Protocol(), \"remote_peer\", c.RemotePeer(), \"remote_multiaddr\", c.RemoteMultiaddr())\n\n\tids.consumeMessage(mes, c, isPush)\n\n\tif ids.metricsTracer != nil {\n\t\tids.metricsTracer.IdentifyReceived(isPush, len(mes.Protocols), len(mes.ListenAddrs))\n\t}\n\n\tids.connsMu.Lock()\n\tdefer ids.connsMu.Unlock()\n\te, ok := ids.conns[c]\n\tif !ok { // might already have disconnected\n\t\treturn nil\n\t}\n\tsup, err := ids.Host.Peerstore().SupportsProtocols(c.RemotePeer(), IDPush)\n\tif supportsIdentifyPush := err == nil && len(sup) > 0; supportsIdentifyPush {\n\t\te.PushSupport = identifyPushSupported\n\t} else {\n\t\te.PushSupport = identifyPushUnsupported\n\t}\n\n\tif ids.metricsTracer != nil {\n\t\tids.metricsTracer.ConnPushSupport(e.PushSupport)\n\t}\n\n\tids.conns[c] = e\n\treturn nil\n}\n\nfunc readAllIDMessages(r pbio.Reader, finalMsg proto.Message) error {\n\tmes := &pb.Identify{}\n\tfor range maxMessages {\n\t\tswitch err := r.ReadMsg(mes); err {\n\t\tcase io.EOF:\n\t\t\treturn nil\n\t\tcase nil:\n\t\t\tproto.Merge(finalMsg, mes)\n\t\tdefault:\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn fmt.Errorf(\"too many parts\")\n}\n\nfunc (ids *idService) updateSnapshot() (updated bool) {\n\tprotos := ids.Host.Mux().Protocols()\n\tslices.Sort(protos)\n\n\taddrs := ids.Host.Addrs()\n\tslices.SortFunc(addrs, func(a, b ma.Multiaddr) int { return bytes.Compare(a.Bytes(), b.Bytes()) })\n\n\tusedSpace := len(ids.ProtocolVersion) + len(ids.UserAgent)\n\tfor i := range protos {\n\t\tusedSpace += len(protos[i])\n\t}\n\taddrs = trimHostAddrList(addrs, maxOwnIdentifyMsgSize-usedSpace-256) // 256 bytes of buffer\n\n\tsnapshot := identifySnapshot{\n\t\taddrs:     addrs,\n\t\tprotocols: protos,\n\t}\n\n\tif !ids.disableSignedPeerRecord {\n\t\tif cab, ok := peerstore.GetCertifiedAddrBook(ids.Host.Peerstore()); ok {\n\t\t\tsnapshot.record = cab.GetPeerRecord(ids.Host.ID())\n\t\t}\n\t}\n\n\tids.currentSnapshot.Lock()\n\tdefer ids.currentSnapshot.Unlock()\n\n\tif ids.currentSnapshot.snapshot.Equal(&snapshot) {\n\t\treturn false\n\t}\n\n\tsnapshot.seq = ids.currentSnapshot.snapshot.seq + 1\n\tids.currentSnapshot.snapshot = snapshot\n\n\tlog.Debug(\"updating snapshot\", \"seq\", snapshot.seq, \"addrs\", snapshot.addrs)\n\treturn true\n}\n\nfunc (ids *idService) writeChunkedIdentifyMsg(s network.Stream, mes *pb.Identify) error {\n\twriter := pbio.NewDelimitedWriter(s)\n\n\tif mes.SignedPeerRecord == nil || proto.Size(mes) <= legacyIDSize {\n\t\treturn writer.WriteMsg(mes)\n\t}\n\n\tsr := mes.SignedPeerRecord\n\tmes.SignedPeerRecord = nil\n\tif err := writer.WriteMsg(mes); err != nil {\n\t\treturn err\n\t}\n\t// then write just the signed record\n\treturn writer.WriteMsg(&pb.Identify{SignedPeerRecord: sr})\n}\n\nfunc (ids *idService) createBaseIdentifyResponse(conn network.Conn, snapshot *identifySnapshot) *pb.Identify {\n\tmes := &pb.Identify{}\n\n\tremoteAddr := conn.RemoteMultiaddr()\n\tlocalAddr := conn.LocalMultiaddr()\n\n\t// set protocols this node is currently handling\n\tmes.Protocols = protocol.ConvertToStrings(snapshot.protocols)\n\n\t// observed address so other side is informed of their\n\t// \"public\" address, at least in relation to us.\n\tmes.ObservedAddr = remoteAddr.Bytes()\n\n\t// populate unsigned addresses.\n\t// peers that do not yet support signed addresses will need this.\n\t// Note: LocalMultiaddr is sometimes 0.0.0.0\n\tviaLoopback := manet.IsIPLoopback(localAddr) || manet.IsIPLoopback(remoteAddr)\n\tmes.ListenAddrs = make([][]byte, 0, len(snapshot.addrs))\n\tfor _, addr := range snapshot.addrs {\n\t\tif !viaLoopback && manet.IsIPLoopback(addr) {\n\t\t\tcontinue\n\t\t}\n\t\tmes.ListenAddrs = append(mes.ListenAddrs, addr.Bytes())\n\t}\n\t// set our public key\n\townKey := ids.Host.Peerstore().PubKey(ids.Host.ID())\n\n\t// check if we even have a public key.\n\tif ownKey == nil {\n\t\t// public key is nil. We are either using insecure transport or something erratic happened.\n\t\t// check if we're even operating in \"secure mode\"\n\t\tif ids.Host.Peerstore().PrivKey(ids.Host.ID()) != nil {\n\t\t\t// private key is present. But NO public key. Something bad happened.\n\t\t\tlog.Error(\"did not have own public key in Peerstore\")\n\t\t}\n\t\t// if neither of the key is present it is safe to assume that we are using an insecure transport.\n\t} else {\n\t\t// public key is present. Safe to proceed.\n\t\tif kb, err := crypto.MarshalPublicKey(ownKey); err != nil {\n\t\t\tlog.Error(\"failed to convert key to bytes\")\n\t\t} else {\n\t\t\tmes.PublicKey = kb\n\t\t}\n\t}\n\n\t// set protocol versions\n\tmes.ProtocolVersion = &ids.ProtocolVersion\n\tmes.AgentVersion = &ids.UserAgent\n\n\treturn mes\n}\n\nfunc (ids *idService) getSignedRecord(snapshot *identifySnapshot) []byte {\n\tif ids.disableSignedPeerRecord || snapshot.record == nil {\n\t\treturn nil\n\t}\n\n\trecBytes, err := snapshot.record.Marshal()\n\tif err != nil {\n\t\tlog.Error(\"failed to marshal signed record\", \"err\", err)\n\t\treturn nil\n\t}\n\n\treturn recBytes\n}\n\n// diff takes two slices of strings (a and b) and computes which elements were added and removed in b\nfunc diff(a, b []protocol.ID) (added, removed []protocol.ID) {\n\t// This is O(n^2), but it's fine because the slices are small.\n\tfor _, x := range b {\n\t\tvar found bool\n\t\tif slices.Contains(a, x) {\n\t\t\tfound = true\n\t\t}\n\t\tif !found {\n\t\t\tadded = append(added, x)\n\t\t}\n\t}\n\tfor _, x := range a {\n\t\tvar found bool\n\t\tif slices.Contains(b, x) {\n\t\t\tfound = true\n\t\t}\n\t\tif !found {\n\t\t\tremoved = append(removed, x)\n\t\t}\n\t}\n\treturn\n}\n\nfunc (ids *idService) consumeMessage(mes *pb.Identify, c network.Conn, isPush bool) {\n\tp := c.RemotePeer()\n\n\tsupported, _ := ids.Host.Peerstore().GetProtocols(p)\n\tmesProtocols := protocol.ConvertFromStrings(mes.Protocols)\n\tadded, removed := diff(supported, mesProtocols)\n\tids.Host.Peerstore().SetProtocols(p, mesProtocols...)\n\tif isPush {\n\t\tids.emitters.evtPeerProtocolsUpdated.Emit(event.EvtPeerProtocolsUpdated{\n\t\t\tPeer:    p,\n\t\t\tAdded:   added,\n\t\t\tRemoved: removed,\n\t\t})\n\t}\n\n\tobsAddr, err := ma.NewMultiaddrBytes(mes.GetObservedAddr())\n\tif err != nil {\n\t\tlog.Debug(\"error parsing received observed addr\", \"connection\", c, \"err\", err)\n\t\tobsAddr = nil\n\t}\n\n\t// mes.ListenAddrs\n\tladdrs := mes.GetListenAddrs()\n\tlmaddrs := make([]ma.Multiaddr, 0, len(laddrs))\n\tfor _, addr := range laddrs {\n\t\tmaddr, err := ma.NewMultiaddrBytes(addr)\n\t\tif err != nil {\n\t\t\tlog.Debug(\"failed to parse multiaddr\", \"id\", ID, \"peer\", p, \"remote_multiaddr\", c.RemoteMultiaddr())\n\t\t\tcontinue\n\t\t}\n\t\tlmaddrs = append(lmaddrs, maddr)\n\t}\n\n\t// NOTE: Do not add `c.RemoteMultiaddr()` to the peerstore if the remote\n\t// peer doesn't tell us to do so. Otherwise, we'll advertise it.\n\t//\n\t// This can cause an \"addr-splosion\" issue where the network will slowly\n\t// gossip and collect observed but unadvertised addresses. Given a NAT\n\t// that picks random source ports, this can cause DHT nodes to collect\n\t// many undialable addresses for other peers.\n\n\t// add certified addresses for the peer, if they sent us a signed peer record\n\t// otherwise use the unsigned addresses.\n\tsignedPeerRecord, err := signedPeerRecordFromMessage(mes)\n\tif err != nil {\n\t\tlog.Debug(\"error getting peer record from Identify message\", \"err\", err)\n\t}\n\n\t// Extend the TTLs on the known (probably) good addresses.\n\t// Taking the lock ensures that we don't concurrently process a disconnect.\n\tids.addrMu.Lock()\n\tttl := peerstore.RecentlyConnectedAddrTTL\n\tswitch ids.Host.Network().Connectedness(p) {\n\tcase network.Limited, network.Connected:\n\t\tttl = peerstore.ConnectedAddrTTL\n\t}\n\n\t// Downgrade connected and recently connected addrs to a temporary TTL.\n\tfor _, ttl := range []time.Duration{\n\t\tpeerstore.RecentlyConnectedAddrTTL,\n\t\tpeerstore.ConnectedAddrTTL,\n\t} {\n\t\tids.Host.Peerstore().UpdateAddrs(p, ttl, peerstore.TempAddrTTL)\n\t}\n\n\tvar addrs []ma.Multiaddr\n\tif signedPeerRecord != nil {\n\t\tsignedAddrs, err := ids.consumeSignedPeerRecord(c.RemotePeer(), signedPeerRecord)\n\t\tif err != nil {\n\t\t\tlog.Debug(\"failed to consume signed peer record\", \"err\", err)\n\t\t\tsignedPeerRecord = nil\n\t\t} else {\n\t\t\taddrs = signedAddrs\n\t\t}\n\t} else {\n\t\taddrs = lmaddrs\n\t}\n\taddrs = filterAddrs(addrs, c.RemoteMultiaddr())\n\tif len(addrs) > connectedPeerMaxAddrs {\n\t\taddrs = addrs[:connectedPeerMaxAddrs]\n\t}\n\n\tids.Host.Peerstore().AddAddrs(p, addrs, ttl)\n\n\t// Finally, expire all temporary addrs.\n\tids.Host.Peerstore().UpdateAddrs(p, peerstore.TempAddrTTL, 0)\n\tids.addrMu.Unlock()\n\n\tlog.Debug(\"received listen addresses\",\n\t\t\"local_peer\", c.LocalPeer(),\n\t\t\"remote_peer\", c.RemotePeer(),\n\t\t\"addresses\", addrs)\n\n\t// get protocol versions\n\tpv := mes.GetProtocolVersion()\n\tav := mes.GetAgentVersion()\n\n\tids.Host.Peerstore().Put(p, \"ProtocolVersion\", pv)\n\tids.Host.Peerstore().Put(p, \"AgentVersion\", av)\n\n\t// get the key from the other side. we may not have it (no-auth transport)\n\tids.consumeReceivedPubKey(c, mes.PublicKey)\n\n\tids.emitters.evtPeerIdentificationCompleted.Emit(event.EvtPeerIdentificationCompleted{\n\t\tPeer:             c.RemotePeer(),\n\t\tConn:             c,\n\t\tListenAddrs:      lmaddrs,\n\t\tProtocols:        mesProtocols,\n\t\tSignedPeerRecord: signedPeerRecord,\n\t\tObservedAddr:     obsAddr,\n\t\tProtocolVersion:  pv,\n\t\tAgentVersion:     av,\n\t})\n}\n\nfunc (ids *idService) consumeSignedPeerRecord(p peer.ID, signedPeerRecord *record.Envelope) ([]ma.Multiaddr, error) {\n\tif signedPeerRecord.PublicKey == nil {\n\t\treturn nil, errors.New(\"missing pubkey\")\n\t}\n\tid, err := peer.IDFromPublicKey(signedPeerRecord.PublicKey)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to derive peer ID: %s\", err)\n\t}\n\tif id != p {\n\t\treturn nil, fmt.Errorf(\"received signed peer record envelope for unexpected peer ID. expected %s, got %s\", p, id)\n\t}\n\tr, err := signedPeerRecord.Record()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to obtain record: %w\", err)\n\t}\n\trec, ok := r.(*peer.PeerRecord)\n\tif !ok {\n\t\treturn nil, errors.New(\"not a peer record\")\n\t}\n\tif rec.PeerID != p {\n\t\treturn nil, fmt.Errorf(\"received signed peer record for unexpected peer ID. expected %s, got %s\", p, rec.PeerID)\n\t}\n\t// Don't put the signed peer record into the peer store.\n\t// They're not used anywhere.\n\t// All we care about are the addresses.\n\treturn rec.Addrs, nil\n}\n\nfunc (ids *idService) consumeReceivedPubKey(c network.Conn, kb []byte) {\n\tlp := c.LocalPeer()\n\trp := c.RemotePeer()\n\n\tif kb == nil {\n\t\tlog.Debug(\"did not receive public key for remote peer\",\n\t\t\t\"local_peer\", lp,\n\t\t\t\"remote_peer\", rp)\n\t\treturn\n\t}\n\n\tnewKey, err := crypto.UnmarshalPublicKey(kb)\n\tif err != nil {\n\t\tlog.Warn(\"cannot unmarshal key from remote peer\",\n\t\t\t\"local_peer\", lp,\n\t\t\t\"remote_peer\", rp,\n\t\t\t\"err\", err)\n\t\treturn\n\t}\n\n\t// verify key matches peer.ID\n\tnp, err := peer.IDFromPublicKey(newKey)\n\tif err != nil {\n\t\tlog.Debug(\"cannot get peer.ID from key of remote peer\",\n\t\t\t\"local_peer\", lp,\n\t\t\t\"remote_peer\", rp,\n\t\t\t\"err\", err)\n\t\treturn\n\t}\n\n\tif np != rp {\n\t\t// if the newKey's peer.ID does not match known peer.ID...\n\n\t\tif rp == \"\" && np != \"\" {\n\t\t\t// if local peerid is empty, then use the new, sent key.\n\t\t\terr := ids.Host.Peerstore().AddPubKey(rp, newKey)\n\t\t\tif err != nil {\n\t\t\t\tlog.Debug(\"could not add key for peer to peerstore\",\n\t\t\t\t\t\"local_peer\", lp,\n\t\t\t\t\t\"remote_peer\", rp,\n\t\t\t\t\t\"err\", err)\n\t\t\t}\n\n\t\t} else {\n\t\t\t// we have a local peer.ID and it does not match the sent key... error.\n\t\t\tlog.Error(\"received key for remote peer mismatch\",\n\t\t\t\t\"local_peer\", lp,\n\t\t\t\t\"remote_peer\", rp,\n\t\t\t\t\"peer_id\", np)\n\t\t}\n\t\treturn\n\t}\n\n\tcurrKey := ids.Host.Peerstore().PubKey(rp)\n\tif currKey == nil {\n\t\t// no key? no auth transport. set this one.\n\t\terr := ids.Host.Peerstore().AddPubKey(rp, newKey)\n\t\tif err != nil {\n\t\t\tlog.Debug(\"could not add key for peer to peerstore\",\n\t\t\t\t\"local_peer\", lp,\n\t\t\t\t\"remote_peer\", rp,\n\t\t\t\t\"err\", err)\n\t\t}\n\t\treturn\n\t}\n\n\t// ok, we have a local key, we should verify they match.\n\tif currKey.Equals(newKey) {\n\t\treturn // ok great. we're done.\n\t}\n\n\t// weird, got a different key... but the different key MATCHES the peer.ID.\n\t// this odd. let's log error and investigate. this should basically never happen\n\t// and it means we have something funky going on and possibly a bug.\n\tlog.Error(\"identify got a different key\",\n\t\t\"local_peer\", lp,\n\t\t\"remote_peer\", rp)\n\n\t// okay... does ours NOT match the remote peer.ID?\n\tcp, err := peer.IDFromPublicKey(currKey)\n\tif err != nil {\n\t\tlog.Error(\"cannot get peer.ID from local key of remote peer\",\n\t\t\t\"local_peer\", lp,\n\t\t\t\"remote_peer\", rp,\n\t\t\t\"err\", err)\n\t\treturn\n\t}\n\tif cp != rp {\n\t\tlog.Error(\"local key for remote peer yields different peer.ID\",\n\t\t\t\"local_peer\", lp,\n\t\t\t\"remote_peer\", rp,\n\t\t\t\"calculated_peer_id\", cp)\n\t\treturn\n\t}\n\n\t// okay... curr key DOES NOT match new key. both match peer.ID. wat?\n\tlog.Error(\"local key and received key do not match, but match peer.ID\",\n\t\t\"local_peer\", lp,\n\t\t\"remote_peer\", rp)\n}\n\n// HasConsistentTransport returns true if the address 'a' shares a\n// protocol set with any address in the green set. This is used\n// to check if a given address might be one of the addresses a peer is\n// listening on.\nfunc HasConsistentTransport(a ma.Multiaddr, green []ma.Multiaddr) bool {\n\tprotosMatch := func(a, b []ma.Protocol) bool {\n\t\tif len(a) != len(b) {\n\t\t\treturn false\n\t\t}\n\n\t\tfor i, p := range a {\n\t\t\tif b[i].Code != p.Code {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\t}\n\n\tprotos := a.Protocols()\n\n\tfor _, ga := range green {\n\t\tif protosMatch(protos, ga.Protocols()) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n// addConnWithLock assuems caller holds the connsMu lock\nfunc (ids *idService) addConnWithLock(c network.Conn) {\n\t_, found := ids.conns[c]\n\tif !found {\n\t\t<-ids.setupCompleted\n\t\tids.conns[c] = entry{}\n\t}\n}\n\nfunc signedPeerRecordFromMessage(msg *pb.Identify) (*record.Envelope, error) {\n\tif len(msg.SignedPeerRecord) == 0 {\n\t\treturn nil, nil\n\t}\n\tenv, _, err := record.ConsumeEnvelope(msg.SignedPeerRecord, peer.PeerRecordEnvelopeDomain)\n\treturn env, err\n}\n\n// netNotifiee defines methods to be used with the swarm\ntype netNotifiee idService\n\nfunc (nn *netNotifiee) IDService() *idService {\n\treturn (*idService)(nn)\n}\n\nfunc (nn *netNotifiee) Connected(_ network.Network, c network.Conn) {\n\tids := nn.IDService()\n\n\tids.connsMu.Lock()\n\tids.addConnWithLock(c)\n\tids.connsMu.Unlock()\n\n\tnn.IDService().IdentifyWait(c)\n}\n\nfunc (nn *netNotifiee) Disconnected(_ network.Network, c network.Conn) {\n\tids := nn.IDService()\n\n\t// Stop tracking the connection.\n\tids.connsMu.Lock()\n\tdelete(ids.conns, c)\n\tids.connsMu.Unlock()\n\n\t// Last disconnect.\n\t// Undo the setting of addresses to peer.ConnectedAddrTTL we did\n\tids.addrMu.Lock()\n\tdefer ids.addrMu.Unlock()\n\n\t// This check MUST happen after acquiring the Lock as identify on a different connection\n\t// might be trying to add addresses.\n\tswitch ids.Host.Network().Connectedness(c.RemotePeer()) {\n\tcase network.Connected, network.Limited:\n\t\treturn\n\t}\n\t// peerstore returns the elements in a random order as it uses a map to store the addresses\n\taddrs := ids.Host.Peerstore().Addrs(c.RemotePeer())\n\tn := len(addrs)\n\tif n > recentlyConnectedPeerMaxAddrs {\n\t\t// We want to always save the address we are connected to\n\t\tfor i, a := range addrs {\n\t\t\tif a.Equal(c.RemoteMultiaddr()) {\n\t\t\t\taddrs[i], addrs[0] = addrs[0], addrs[i]\n\t\t\t}\n\t\t}\n\t\tn = recentlyConnectedPeerMaxAddrs\n\t}\n\tids.Host.Peerstore().UpdateAddrs(c.RemotePeer(), peerstore.ConnectedAddrTTL, peerstore.TempAddrTTL)\n\tids.Host.Peerstore().AddAddrs(c.RemotePeer(), addrs[:n], peerstore.RecentlyConnectedAddrTTL)\n\tids.Host.Peerstore().UpdateAddrs(c.RemotePeer(), peerstore.TempAddrTTL, 0)\n}\n\nfunc (nn *netNotifiee) Listen(_ network.Network, _ ma.Multiaddr)      {}\nfunc (nn *netNotifiee) ListenClose(_ network.Network, _ ma.Multiaddr) {}\n\n// filterAddrs filters the address slice based on the remote multiaddr:\n//   - if it's a localhost address, no filtering is applied\n//   - if it's a private network address, all localhost addresses are filtered out\n//   - if it's a public address, all non-public addresses are filtered out\n//   - if none of the above, (e.g. discard prefix), no filtering is applied.\n//     We can't do anything meaningful here so we do nothing.\nfunc filterAddrs(addrs []ma.Multiaddr, remote ma.Multiaddr) []ma.Multiaddr {\n\tswitch {\n\tcase manet.IsIPLoopback(remote):\n\t\treturn addrs\n\tcase manet.IsPrivateAddr(remote):\n\t\treturn ma.FilterAddrs(addrs, func(a ma.Multiaddr) bool { return !manet.IsIPLoopback(a) })\n\tcase manet.IsPublicAddr(remote):\n\t\treturn ma.FilterAddrs(addrs, manet.IsPublicAddr)\n\tdefault:\n\t\treturn addrs\n\t}\n}\n\nfunc trimHostAddrList(addrs []ma.Multiaddr, maxSize int) []ma.Multiaddr {\n\ttotalSize := 0\n\tfor _, a := range addrs {\n\t\ttotalSize += len(a.Bytes())\n\t}\n\tif totalSize <= maxSize {\n\t\treturn addrs\n\t}\n\n\tscore := func(addr ma.Multiaddr) int {\n\t\tvar res int\n\t\tif manet.IsPublicAddr(addr) {\n\t\t\tres |= 1 << 12\n\t\t} else if !manet.IsIPLoopback(addr) {\n\t\t\tres |= 1 << 11\n\t\t}\n\t\tvar protocolWeight int\n\t\tma.ForEach(addr, func(c ma.Component) bool {\n\t\t\tswitch c.Protocol().Code {\n\t\t\tcase ma.P_QUIC_V1:\n\t\t\t\tprotocolWeight = 5\n\t\t\tcase ma.P_TCP:\n\t\t\t\tprotocolWeight = 4\n\t\t\tcase ma.P_WSS:\n\t\t\t\tprotocolWeight = 3\n\t\t\tcase ma.P_WEBTRANSPORT:\n\t\t\t\tprotocolWeight = 2\n\t\t\tcase ma.P_WEBRTC_DIRECT:\n\t\t\t\tprotocolWeight = 1\n\t\t\tcase ma.P_P2P:\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn true\n\t\t})\n\t\tres |= 1 << protocolWeight\n\t\treturn res\n\t}\n\n\tslices.SortStableFunc(addrs, func(a, b ma.Multiaddr) int {\n\t\treturn score(b) - score(a) // b-a for reverse order\n\t})\n\ttotalSize = 0\n\tfor i, a := range addrs {\n\t\ttotalSize += len(a.Bytes())\n\t\tif totalSize > maxSize {\n\t\t\taddrs = addrs[:i]\n\t\t\tbreak\n\t\t}\n\t}\n\treturn addrs\n}\n"
  },
  {
    "path": "p2p/protocol/identify/id_glass_test.go",
    "content": "package identify\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\trecordPb \"github.com/libp2p/go-libp2p/core/record/pb\"\n\tblhost \"github.com/libp2p/go-libp2p/p2p/host/blank\"\n\tswarmt \"github.com/libp2p/go-libp2p/p2p/net/swarm/testing\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"google.golang.org/protobuf/proto\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestFastDisconnect(t *testing.T) {\n\t// This test checks to see if we correctly abort sending an identify\n\t// response if the peer disconnects before we handle the request.\n\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\tdefer cancel()\n\n\ttarget := blhost.NewBlankHost(swarmt.GenSwarm(t))\n\tdefer target.Close()\n\tids, err := NewIDService(target)\n\trequire.NoError(t, err)\n\tdefer ids.Close()\n\tids.Start()\n\n\tsync := make(chan struct{})\n\ttarget.SetStreamHandler(ID, func(s network.Stream) {\n\t\t// Wait till the stream is set up on both sides.\n\t\tselect {\n\t\tcase <-sync:\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\t}\n\n\t\t// Kill the connection, and make sure we're completely disconnected.\n\t\tassert.Eventually(t,\n\t\t\tfunc() bool {\n\t\t\t\tfor _, conn := range target.Network().ConnsToPeer(s.Conn().RemotePeer()) {\n\t\t\t\t\tconn.Close()\n\t\t\t\t}\n\t\t\t\treturn target.Network().Connectedness(s.Conn().RemotePeer()) != network.Connected\n\t\t\t},\n\t\t\t2*time.Second,\n\t\t\ttime.Millisecond,\n\t\t)\n\t\t// Now try to handle the response.\n\t\t// This should not block indefinitely, or panic, or anything like that.\n\t\t//\n\t\t// However, if we have a bug, that _could_ happen.\n\t\tids.handleIdentifyRequest(s)\n\n\t\t// Ok, allow the outer test to continue.\n\t\tselect {\n\t\tcase <-sync:\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\t}\n\t})\n\n\tsource := blhost.NewBlankHost(swarmt.GenSwarm(t))\n\tdefer source.Close()\n\n\t// only connect to the first address, to make sure we only end up with one connection\n\trequire.NoError(t, source.Connect(ctx, peer.AddrInfo{ID: target.ID(), Addrs: target.Addrs()}))\n\ts, err := source.NewStream(ctx, target.ID(), ID)\n\trequire.NoError(t, err)\n\tselect {\n\tcase sync <- struct{}{}:\n\tcase <-ctx.Done():\n\t\tt.Fatal(ctx.Err())\n\t}\n\ts.Reset()\n\tselect {\n\tcase sync <- struct{}{}:\n\tcase <-ctx.Done():\n\t\tt.Fatal(ctx.Err())\n\t}\n\t// double-check to make sure we didn't actually timeout somewhere.\n\trequire.NoError(t, ctx.Err())\n}\n\nfunc TestWrongSignedPeerRecord(t *testing.T) {\n\th1 := blhost.NewBlankHost(swarmt.GenSwarm(t))\n\tdefer h1.Close()\n\tids, err := NewIDService(h1)\n\trequire.NoError(t, err)\n\tids.Start()\n\tdefer ids.Close()\n\n\th2 := blhost.NewBlankHost(swarmt.GenSwarm(t))\n\tdefer h2.Close()\n\tids2, err := NewIDService(h2)\n\trequire.NoError(t, err)\n\tids2.Start()\n\tdefer ids2.Close()\n\n\th3 := blhost.NewBlankHost(swarmt.GenSwarm(t))\n\tdefer h2.Close()\n\tids3, err := NewIDService(h3)\n\trequire.NoError(t, err)\n\tids3.Start()\n\tdefer ids3.Close()\n\n\th2.Connect(context.Background(), peer.AddrInfo{ID: h1.ID(), Addrs: h1.Addrs()})\n\ts, err := h2.NewStream(context.Background(), h1.ID(), IDPush)\n\trequire.NoError(t, err)\n\n\terr = ids3.sendIdentifyResp(s, true)\n\t// This should fail because the peer record is signed by h3, not h2\n\trequire.NoError(t, err)\n\ttime.Sleep(time.Second)\n\n\trequire.Empty(t, h1.Peerstore().Addrs(h3.ID()), \"h1 should not know about h3 since it was relayed over h2\")\n}\n\nfunc TestInvalidSignedPeerRecord(t *testing.T) {\n\th1 := blhost.NewBlankHost(swarmt.GenSwarm(t))\n\tdefer h1.Close()\n\tids, err := NewIDService(h1)\n\trequire.NoError(t, err)\n\tids.Start()\n\tdefer ids.Close()\n\n\th2 := blhost.NewBlankHost(swarmt.GenSwarm(t))\n\tdefer h2.Close()\n\tids2, err := NewIDService(h2)\n\trequire.NoError(t, err)\n\t// We don't want to start the identify service, we'll manage the messages h2\n\t// sends manually so we can tweak it\n\t// ids2.Start()\n\n\th2.Connect(context.Background(), peer.AddrInfo{ID: h1.ID(), Addrs: h1.Addrs()})\n\trequire.Empty(t, h1.Peerstore().Addrs(h2.ID()))\n\n\ts, err := h2.NewStream(context.Background(), h1.ID(), IDPush)\n\trequire.NoError(t, err)\n\n\tids2.updateSnapshot()\n\tids2.currentSnapshot.Lock()\n\tsnapshot := ids2.currentSnapshot.snapshot\n\tids2.currentSnapshot.Unlock()\n\tmes := ids2.createBaseIdentifyResponse(s.Conn(), &snapshot)\n\tfmt.Println(\"Signed record is\", snapshot.record)\n\tmarshalled, err := snapshot.record.Marshal()\n\trequire.NoError(t, err)\n\n\tvar envPb recordPb.Envelope\n\terr = proto.Unmarshal(marshalled, &envPb)\n\trequire.NoError(t, err)\n\n\tenvPb.Signature = []byte(\"invalid\")\n\n\tmes.SignedPeerRecord, err = proto.Marshal(&envPb)\n\trequire.NoError(t, err)\n\n\terr = ids2.writeChunkedIdentifyMsg(s, mes)\n\trequire.NoError(t, err)\n\tfmt.Println(\"Done sending msg\")\n\ts.Close()\n\n\t// Wait a bit for h1 to process the message\n\ttime.Sleep(1 * time.Second)\n\n\tcab, ok := h1.Peerstore().(peerstore.CertifiedAddrBook)\n\trequire.True(t, ok)\n\trequire.Nil(t, cab.GetPeerRecord(h2.ID()))\n}\n\nfunc TestIncomingAddrFilter(t *testing.T) {\n\tlhAddr := ma.StringCast(\"/ip4/127.0.0.1/udp/123/quic-v1\")\n\tprivAddr := ma.StringCast(\"/ip4/192.168.1.101/tcp/123\")\n\tpubAddr := ma.StringCast(\"/ip6/2001::1/udp/123/quic-v1\")\n\tpubDNSAddr := ma.StringCast(\"/dns/example.com/udp/123/quic-v1\")\n\tprivDNSAddr := ma.StringCast(\"/dns4/localhost/udp/123/quic-v1\")\n\ttests := []struct {\n\t\toutput []ma.Multiaddr\n\t\tremote ma.Multiaddr\n\t}{\n\t\t{\n\t\t\toutput: []ma.Multiaddr{lhAddr, privAddr, pubAddr, pubDNSAddr, privDNSAddr},\n\t\t\tremote: lhAddr,\n\t\t},\n\t\t{\n\t\t\toutput: []ma.Multiaddr{privAddr, pubAddr, pubDNSAddr, privDNSAddr},\n\t\t\tremote: privAddr,\n\t\t},\n\t\t{\n\t\t\toutput: []ma.Multiaddr{pubAddr, pubDNSAddr},\n\t\t\tremote: pubAddr,\n\t\t},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(fmt.Sprintf(\"remote:%s\", tc.remote), func(t *testing.T) {\n\t\t\tinput := []ma.Multiaddr{lhAddr, privAddr, pubAddr, pubDNSAddr, privDNSAddr}\n\t\t\tgot := filterAddrs(input, tc.remote)\n\t\t\trequire.ElementsMatch(t, tc.output, got, \"%s\\n%s\", tc.output, got)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "p2p/protocol/identify/id_test.go",
    "content": "package identify_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"slices\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p\"\n\tic \"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/event\"\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\t\"github.com/libp2p/go-libp2p/core/record\"\n\tcoretest \"github.com/libp2p/go-libp2p/core/test\"\n\tblhost \"github.com/libp2p/go-libp2p/p2p/host/blank\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/eventbus\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoremem\"\n\tmocknet \"github.com/libp2p/go-libp2p/p2p/net/mock\"\n\t\"github.com/libp2p/go-libp2p/p2p/net/swarm\"\n\tswarmt \"github.com/libp2p/go-libp2p/p2p/net/swarm/testing\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/identify\"\n\tuseragent \"github.com/libp2p/go-libp2p/p2p/protocol/identify/internal/user-agent\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/identify/pb\"\n\n\tmockClock \"github.com/benbjohnson/clock\"\n\t\"github.com/libp2p/go-libp2p-testing/race\"\n\t\"github.com/libp2p/go-msgio/pbio\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmatest \"github.com/multiformats/go-multiaddr/matest\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc testKnowsAddrs(t *testing.T, h host.Host, p peer.ID, expected []ma.Multiaddr) {\n\tt.Helper()\n\trequire.True(t, matest.AssertMultiaddrsMatch(t, expected, h.Peerstore().Addrs(p)), fmt.Sprintf(\"%s did not have addr for %s\", h.ID(), p))\n}\n\nfunc testHasAgentVersion(t *testing.T, h host.Host, p peer.ID) {\n\tv, err := h.Peerstore().Get(p, \"AgentVersion\")\n\trequire.NoError(t, err, \"fetching agent version\")\n\trequire.Equal(t, useragent.DefaultUserAgent(), v, \"agent version\")\n}\n\nfunc testHasPublicKey(t *testing.T, h host.Host, p peer.ID, shouldBe ic.PubKey) {\n\tk := h.Peerstore().PubKey(p)\n\tif k == nil {\n\t\tt.Error(\"no public key\")\n\t\treturn\n\t}\n\tif !k.Equals(shouldBe) {\n\t\tt.Error(\"key mismatch\")\n\t\treturn\n\t}\n\n\tp2, err := peer.IDFromPublicKey(k)\n\tif err != nil {\n\t\tt.Error(\"could not make key\")\n\t} else if p != p2 {\n\t\tt.Error(\"key does not match peerid\")\n\t}\n}\n\n// we're using BlankHost in our tests, which doesn't automatically generate peer records\n// and emit address change events on the bus like BasicHost.\n// This generates a record, puts it in the peerstore and emits an addr change event\n// which will cause the identify service to push it to all peers it's connected to.\nfunc emitAddrChangeEvt(t *testing.T, h host.Host) {\n\tt.Helper()\n\n\tkey := h.Peerstore().PrivKey(h.ID())\n\tif key == nil {\n\t\tt.Fatal(\"no private key for host\")\n\t}\n\n\trec := peer.NewPeerRecord()\n\trec.PeerID = h.ID()\n\trec.Addrs = h.Addrs()\n\tsigned, err := record.Seal(rec, key)\n\tif err != nil {\n\t\tt.Fatalf(\"error generating peer record: %s\", err)\n\t}\n\n\tcab, ok := peerstore.GetCertifiedAddrBook(h.Peerstore())\n\trequire.True(t, ok)\n\t_, err = cab.ConsumePeerRecord(signed, peerstore.PermanentAddrTTL)\n\trequire.NoError(t, err)\n\n\tevt := event.EvtLocalAddressesUpdated{}\n\temitter, err := h.EventBus().Emitter(new(event.EvtLocalAddressesUpdated), eventbus.Stateful)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\terr = emitter.Emit(evt)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\n// TestIDService gives the ID service 1s to finish after dialing\n// this is because it used to be concurrent. Now, Dial wait till the\n// id service is done.\nfunc TestIDService(t *testing.T) {\n\tif race.WithRace() {\n\t\tt.Skip(\"This test modifies peerstore.RecentlyConnectedAddrTTL, which is racy.\")\n\t}\n\t// This test is highly timing dependent, waiting on timeouts/expiration.\n\toldTTL := peerstore.RecentlyConnectedAddrTTL\n\toldTempTTL := peerstore.TempAddrTTL\n\tpeerstore.RecentlyConnectedAddrTTL = 500 * time.Millisecond\n\tpeerstore.TempAddrTTL = 50 * time.Millisecond\n\tt.Cleanup(func() {\n\t\tpeerstore.RecentlyConnectedAddrTTL = oldTTL\n\t\tpeerstore.TempAddrTTL = oldTempTTL\n\t})\n\n\tclk := mockClock.NewMock()\n\tswarm1 := swarmt.GenSwarm(t, swarmt.WithClock(clk))\n\tswarm2 := swarmt.GenSwarm(t, swarmt.WithClock(clk))\n\th1 := blhost.NewBlankHost(swarm1)\n\th2 := blhost.NewBlankHost(swarm2)\n\n\th1p := h1.ID()\n\th2p := h2.ID()\n\n\tids1, err := identify.NewIDService(h1)\n\trequire.NoError(t, err)\n\tdefer ids1.Close()\n\tids1.Start()\n\n\tids2, err := identify.NewIDService(h2)\n\trequire.NoError(t, err)\n\tdefer ids2.Close()\n\tids2.Start()\n\n\tsub, err := ids1.Host.EventBus().Subscribe(new(event.EvtPeerIdentificationCompleted))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttestKnowsAddrs(t, h1, h2p, []ma.Multiaddr{}) // nothing\n\ttestKnowsAddrs(t, h2, h1p, []ma.Multiaddr{}) // nothing\n\n\t// the forgetMe addr represents an address for h1 that h2 has learned out of band\n\t// (not via identify protocol). During the identify exchange, it will be\n\t// forgotten and replaced by the addrs h1 sends.\n\tforgetMe, _ := ma.NewMultiaddr(\"/ip4/1.2.3.4/tcp/1234\")\n\n\th2.Peerstore().AddAddr(h1p, forgetMe, peerstore.RecentlyConnectedAddrTTL)\n\th2pi := h2.Peerstore().PeerInfo(h2p)\n\trequire.NoError(t, h1.Connect(context.Background(), h2pi))\n\n\th1t2c := h1.Network().ConnsToPeer(h2p)\n\trequire.NotEmpty(t, h1t2c, \"should have a conn here\")\n\n\tids1.IdentifyConn(h1t2c[0])\n\n\t// the idService should be opened automatically, by the network.\n\t// what we should see now is that both peers know about each others listen addresses.\n\tt.Log(\"test peer1 has peer2 addrs correctly\")\n\ttestKnowsAddrs(t, h1, h2p, h2.Addrs()) // has them\n\ttestHasAgentVersion(t, h1, h2p)\n\ttestHasPublicKey(t, h1, h2p, h2.Peerstore().PubKey(h2p)) // h1 should have h2's public key\n\n\t// now, this wait we do have to do. it's the wait for the Listening side\n\t// to be done identifying the connection.\n\tc := h2.Network().ConnsToPeer(h1.ID())\n\trequire.NotEmpty(t, c, \"should have connection by now at least.\")\n\tids2.IdentifyConn(c[0])\n\n\t// and the protocol versions.\n\tt.Log(\"test peer2 has peer1 addrs correctly\")\n\ttestKnowsAddrs(t, h2, h1p, h1.Addrs()) // has them\n\ttestHasAgentVersion(t, h2, h1p)\n\ttestHasPublicKey(t, h2, h1p, h1.Peerstore().PubKey(h1p)) // h1 should have h2's public key\n\n\t// Need both sides to actually notice that the connection has been closed.\n\tsentDisconnect1 := waitForDisconnectNotification(swarm1)\n\tsentDisconnect2 := waitForDisconnectNotification(swarm2)\n\th1.Network().ClosePeer(h2p)\n\th2.Network().ClosePeer(h1p)\n\tif len(h2.Network().ConnsToPeer(h1.ID())) != 0 || len(h1.Network().ConnsToPeer(h2.ID())) != 0 {\n\t\tt.Fatal(\"should have no connections\")\n\t}\n\n\tt.Log(\"testing addrs just after disconnect\")\n\t// addresses don't immediately expire on disconnect, so we should still have them\n\ttestKnowsAddrs(t, h2, h1p, h1.Addrs())\n\ttestKnowsAddrs(t, h1, h2p, h2.Addrs())\n\n\t<-sentDisconnect1\n\t<-sentDisconnect2\n\n\t// the addrs had their TTLs reduced on disconnect, and\n\t// will be forgotten soon after\n\tt.Log(\"testing addrs after TTL expiration\")\n\tclk.Add(time.Second)\n\ttestKnowsAddrs(t, h1, h2p, []ma.Multiaddr{})\n\ttestKnowsAddrs(t, h2, h1p, []ma.Multiaddr{})\n\n\t// test that we received the \"identify completed\" event.\n\tselect {\n\tcase evtAny := <-sub.Out():\n\t\tassertCorrectEvtPeerIdentificationCompleted(t, evtAny, h2)\n\tcase <-time.After(3 * time.Second):\n\t\tt.Fatalf(\"expected EvtPeerIdentificationCompleted event within 10 seconds; none received\")\n\t}\n}\n\nfunc assertCorrectEvtPeerIdentificationCompleted(t *testing.T, evtAny any, other host.Host) {\n\tt.Helper()\n\tevt := evtAny.(event.EvtPeerIdentificationCompleted)\n\trequire.NotNil(t, evt.Conn)\n\trequire.Equal(t, other.ID(), evt.Peer)\n\n\trequire.Equal(t, len(other.Addrs()), len(evt.ListenAddrs))\n\tif len(other.Addrs()) == len(evt.ListenAddrs) {\n\t\totherAddrsStrings := make([]string, len(other.Addrs()))\n\t\tevtAddrStrings := make([]string, len(evt.ListenAddrs))\n\t\tfor i, a := range other.Addrs() {\n\t\t\totherAddrsStrings[i] = a.String()\n\t\t\tevtAddrStrings[i] = evt.ListenAddrs[i].String()\n\t\t}\n\t\tslices.Sort(otherAddrsStrings)\n\t\tslices.Sort(evtAddrStrings)\n\t\trequire.Equal(t, otherAddrsStrings, evtAddrStrings)\n\t}\n\n\totherProtos := other.Mux().Protocols()\n\tslices.Sort(otherProtos)\n\tevtProtos := evt.Protocols\n\tslices.Sort(evtProtos)\n\trequire.Equal(t, otherProtos, evtProtos)\n\tidFromSignedRecord, err := peer.IDFromPublicKey(evt.SignedPeerRecord.PublicKey)\n\trequire.NoError(t, err)\n\trequire.Equal(t, other.ID(), idFromSignedRecord)\n\trequire.Equal(t, peer.PeerRecordEnvelopePayloadType, evt.SignedPeerRecord.PayloadType)\n\tvar peerRecord peer.PeerRecord\n\tevt.SignedPeerRecord.TypedRecord(&peerRecord)\n\trequire.Equal(t, other.ID(), peerRecord.PeerID)\n\tmatest.AssertMultiaddrsMatch(t, other.Addrs(), peerRecord.Addrs)\n}\n\nfunc TestProtoMatching(t *testing.T) {\n\ttcp1, _ := ma.NewMultiaddr(\"/ip4/1.2.3.4/tcp/1234\")\n\ttcp2, _ := ma.NewMultiaddr(\"/ip4/1.2.3.4/tcp/2345\")\n\ttcp3, _ := ma.NewMultiaddr(\"/ip4/1.2.3.4/tcp/4567\")\n\tutp, _ := ma.NewMultiaddr(\"/ip4/1.2.3.4/udp/1234/utp\")\n\n\tif !identify.HasConsistentTransport(tcp1, []ma.Multiaddr{tcp2, tcp3, utp}) {\n\t\tt.Fatal(\"expected match\")\n\t}\n\n\tif identify.HasConsistentTransport(utp, []ma.Multiaddr{tcp2, tcp3}) {\n\t\tt.Fatal(\"expected mismatch\")\n\t}\n}\n\nfunc TestLocalhostAddrFiltering(t *testing.T) {\n\tt.Skip(\"need to fix this test\")\n\tmn := mocknet.New()\n\tdefer mn.Close()\n\tid1 := coretest.RandPeerIDFatal(t)\n\tps1, err := pstoremem.NewPeerstore()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tp1addr1, _ := ma.NewMultiaddr(\"/ip4/1.2.3.4/tcp/1234\")\n\tp1addr2, _ := ma.NewMultiaddr(\"/ip4/127.0.0.1/tcp/2345\")\n\tps1.AddAddrs(id1, []ma.Multiaddr{p1addr1, p1addr2}, peerstore.PermanentAddrTTL)\n\tp1, err := mn.AddPeerWithPeerstore(id1, ps1)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tid2 := coretest.RandPeerIDFatal(t)\n\tps2, err := pstoremem.NewPeerstore()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tp2addr1, _ := ma.NewMultiaddr(\"/ip4/1.2.3.5/tcp/1234\")\n\tp2addr2, _ := ma.NewMultiaddr(\"/ip4/127.0.0.1/tcp/3456\")\n\tp2addrs := []ma.Multiaddr{p2addr1, p2addr2}\n\tps2.AddAddrs(id2, p2addrs, peerstore.PermanentAddrTTL)\n\tp2, err := mn.AddPeerWithPeerstore(id2, ps2)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tid3 := coretest.RandPeerIDFatal(t)\n\tps3, err := pstoremem.NewPeerstore()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tp3addr1, _ := ma.NewMultiaddr(\"/ip4/127.0.0.1/tcp/4567\")\n\tps3.AddAddrs(id3, []ma.Multiaddr{p3addr1}, peerstore.PermanentAddrTTL)\n\tp3, err := mn.AddPeerWithPeerstore(id3, ps3)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = mn.LinkAll()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tp1.Connect(context.Background(), peer.AddrInfo{\n\t\tID:    id2,\n\t\tAddrs: p2addrs[0:1],\n\t})\n\tp3.Connect(context.Background(), peer.AddrInfo{\n\t\tID:    id2,\n\t\tAddrs: p2addrs[1:],\n\t})\n\n\tids1, err := identify.NewIDService(p1)\n\trequire.NoError(t, err)\n\tids1.Start()\n\n\tids2, err := identify.NewIDService(p2)\n\trequire.NoError(t, err)\n\tids2.Start()\n\n\tids3, err := identify.NewIDService(p3)\n\trequire.NoError(t, err)\n\tids3.Start()\n\n\tdefer func() {\n\t\tids1.Close()\n\t\tids2.Close()\n\t\tids3.Close()\n\t}()\n\n\tconns := p2.Network().ConnsToPeer(id1)\n\tif len(conns) == 0 {\n\t\tt.Fatal(\"no conns\")\n\t}\n\tconn := conns[0]\n\tids2.IdentifyConn(conn)\n\taddrs := p2.Peerstore().Addrs(id1)\n\tif len(addrs) != 1 {\n\t\tt.Fatalf(\"expected one addr, found %s\", addrs)\n\t}\n\n\tconns = p3.Network().ConnsToPeer(id2)\n\tif len(conns) == 0 {\n\t\tt.Fatal(\"no conns\")\n\t}\n\tconn = conns[0]\n\tids3.IdentifyConn(conn)\n\taddrs = p3.Peerstore().Addrs(id2)\n\tif len(addrs) != 2 {\n\t\tt.Fatalf(\"expected 2 addrs for %s, found %d: %s\", id2, len(addrs), addrs)\n\t}\n}\n\n// TestIdentifyPushWhileIdentifyingConn tests that the host waits to push updates if an identify is ongoing.\nfunc TestIdentifyPushWhileIdentifyingConn(t *testing.T) {\n\tt.Skip()\n\tctx := t.Context()\n\n\th1 := blhost.NewBlankHost(swarmt.GenSwarm(t))\n\th2 := blhost.NewBlankHost(swarmt.GenSwarm(t))\n\tdefer h2.Close()\n\tdefer h1.Close()\n\tt.Log(\"h1:\", h1.ID())\n\tt.Log(\"h2:\", h2.ID())\n\n\tids1, err := identify.NewIDService(h1)\n\trequire.NoError(t, err)\n\tids1.Start()\n\n\tids2, err := identify.NewIDService(h2)\n\trequire.NoError(t, err)\n\tids2.Start()\n\n\tdefer ids1.Close()\n\tdefer ids2.Close()\n\n\t// replace the original identify handler by one that blocks until we close the block channel.\n\t// this allows us to control how long identify runs.\n\tblock := make(chan struct{})\n\thandler := func(s network.Stream) {\n\t\t<-block\n\t\tw := pbio.NewDelimitedWriter(s)\n\t\tw.WriteMsg(&pb.Identify{Protocols: protocol.ConvertToStrings(h1.Mux().Protocols())})\n\t\ts.Close()\n\t}\n\th1.RemoveStreamHandler(identify.ID)\n\th1.SetStreamHandler(identify.ID, handler)\n\n\t// from h2 connect to h1.\n\tif err := h2.Connect(ctx, peer.AddrInfo{ID: h1.ID(), Addrs: h1.Addrs()}); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// from h2, identify h1.\n\tconn := h2.Network().ConnsToPeer(h1.ID())[0]\n\tgo ids2.IdentifyConn(conn)\n\n\t<-time.After(500 * time.Millisecond)\n\n\t// subscribe to events in h1; after identify h1 should receive the update from h2 and publish an event in the bus.\n\tsub, err := h1.EventBus().Subscribe(&event.EvtPeerProtocolsUpdated{})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer sub.Close()\n\n\t// add a handler in h2; the update to h1 will queue until we're done identifying h1.\n\th2.SetStreamHandler(protocol.TestingID, func(_ network.Stream) {})\n\t<-time.After(500 * time.Millisecond)\n\n\t// make sure we haven't received any events yet.\n\tif q := len(sub.Out()); q > 0 {\n\t\tt.Fatalf(\"expected no events yet; queued: %d\", q)\n\t}\n\n\tclose(block)\n\tselect {\n\tcase evt := <-sub.Out():\n\t\te := evt.(event.EvtPeerProtocolsUpdated)\n\t\tif e.Peer != h2.ID() || len(e.Added) != 1 || e.Added[0] != protocol.TestingID {\n\t\t\tt.Fatalf(\"expected an event for protocol changes in h2, with the testing protocol added; instead got: %v\", evt)\n\t\t}\n\tcase <-time.After(2 * time.Second):\n\t\tt.Fatalf(\"timed out while waiting for an event for the protocol changes in h2\")\n\t}\n}\n\nfunc TestIdentifyPushOnAddrChange(t *testing.T) {\n\tctx := t.Context()\n\n\th1 := blhost.NewBlankHost(swarmt.GenSwarm(t))\n\th2 := blhost.NewBlankHost(swarmt.GenSwarm(t))\n\n\th1p := h1.ID()\n\th2p := h2.ID()\n\n\tids1, err := identify.NewIDService(h1)\n\trequire.NoError(t, err)\n\tdefer ids1.Close()\n\tids1.Start()\n\n\tids2, err := identify.NewIDService(h2)\n\trequire.NoError(t, err)\n\tdefer ids2.Close()\n\tids2.Start()\n\n\ttestKnowsAddrs(t, h1, h2p, []ma.Multiaddr{}) // nothing\n\ttestKnowsAddrs(t, h2, h1p, []ma.Multiaddr{}) // nothing\n\n\trequire.NoError(t, h1.Connect(ctx, h2.Peerstore().PeerInfo(h2p)))\n\t// h1 should immediately see a connection from h2\n\trequire.NotEmpty(t, h1.Network().ConnsToPeer(h2p))\n\t// wait for h2 to Identify itself so we are sure h2 has seen the connection.\n\tids1.IdentifyConn(h1.Network().ConnsToPeer(h2p)[0])\n\n\t// h2 should now see the connection and we should wait for h1 to Identify itself to h2.\n\trequire.NotEmpty(t, h2.Network().ConnsToPeer(h1p))\n\tids2.IdentifyConn(h2.Network().ConnsToPeer(h1p)[0])\n\n\ttestKnowsAddrs(t, h1, h2p, h2.Peerstore().Addrs(h2p))\n\ttestKnowsAddrs(t, h2, h1p, h1.Peerstore().Addrs(h1p))\n\n\t// change addr on host 1 and ensure host2 gets a push\n\tlad := ma.StringCast(\"/ip4/127.0.0.1/tcp/1234\")\n\trequire.NoError(t, h1.Network().Listen(lad))\n\tmatest.AssertMultiaddrsContain(t, h1.Addrs(), lad)\n\n\th2AddrStream := h2.Peerstore().AddrStream(ctx, h1p)\n\n\temitAddrChangeEvt(t, h1)\n\n\t// Wait for h2 to process the new addr\n\twaitForAddrInStream(t, h2AddrStream, lad, 10*time.Second, \"h2 did not receive addr change\")\n\n\trequire.True(t, ma.Contains(h2.Peerstore().Addrs(h1p), lad))\n\n\t// change addr on host2 and ensure host 1 gets a pus\n\tlad = ma.StringCast(\"/ip4/127.0.0.1/tcp/1235\")\n\trequire.NoError(t, h2.Network().Listen(lad))\n\tmatest.AssertMultiaddrsContain(t, h2.Addrs(), lad)\n\th1AddrStream := h1.Peerstore().AddrStream(ctx, h2p)\n\temitAddrChangeEvt(t, h2)\n\n\t// Wait for h1 to process the new addr\n\twaitForAddrInStream(t, h1AddrStream, lad, 10*time.Second, \"h1 did not receive addr change\")\n\n\trequire.True(t, ma.Contains(h1.Peerstore().Addrs(h2p), lad))\n\n\t// change addr on host2 again\n\tlad2 := ma.StringCast(\"/ip4/127.0.0.1/tcp/1236\")\n\trequire.NoError(t, h2.Network().Listen(lad2))\n\tmatest.AssertMultiaddrsContain(t, h2.Addrs(), lad2)\n\temitAddrChangeEvt(t, h2)\n\n\t// Wait for h1 to process the new addr\n\twaitForAddrInStream(t, h1AddrStream, lad2, 10*time.Second, \"h1 did not receive addr change\")\n\n\trequire.True(t, ma.Contains(h1.Peerstore().Addrs(h2p), lad2))\n}\n\nfunc TestUserAgent(t *testing.T) {\n\tctx := t.Context()\n\n\th1, err := libp2p.New(libp2p.UserAgent(\"foo\"), libp2p.ListenAddrStrings(\"/ip4/127.0.0.1/tcp/0\"))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer h1.Close()\n\n\th2, err := libp2p.New(libp2p.UserAgent(\"bar\"), libp2p.ListenAddrStrings(\"/ip4/127.0.0.1/tcp/0\"))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer h2.Close()\n\n\terr = h1.Connect(ctx, peer.AddrInfo{ID: h2.ID(), Addrs: h2.Addrs()})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tav, err := h1.Peerstore().Get(h2.ID(), \"AgentVersion\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif ver, ok := av.(string); !ok || ver != \"bar\" {\n\t\tt.Errorf(\"expected agent version %q, got %q\", \"bar\", av)\n\t}\n}\n\nfunc TestNotListening(t *testing.T) {\n\t// Make sure we don't panic if we're not listening on any addresses.\n\t//\n\t// https://github.com/libp2p/go-libp2p/issues/939\n\tctx := t.Context()\n\n\th1, err := libp2p.New(libp2p.NoListenAddrs)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer h1.Close()\n\n\th2, err := libp2p.New(libp2p.ListenAddrStrings(\"/ip4/127.0.0.1/tcp/0\"))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer h2.Close()\n\n\terr = h1.Connect(ctx, peer.AddrInfo{ID: h2.ID(), Addrs: h2.Addrs()})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestSendPush(t *testing.T) {\n\tctx := t.Context()\n\n\th1 := blhost.NewBlankHost(swarmt.GenSwarm(t))\n\th2 := blhost.NewBlankHost(swarmt.GenSwarm(t))\n\tdefer h2.Close()\n\tdefer h1.Close()\n\n\tids1, err := identify.NewIDService(h1)\n\trequire.NoError(t, err)\n\tdefer ids1.Close()\n\tids1.Start()\n\n\tids2, err := identify.NewIDService(h2)\n\trequire.NoError(t, err)\n\tdefer ids2.Close()\n\tids2.Start()\n\n\terr = h1.Connect(ctx, peer.AddrInfo{ID: h2.ID(), Addrs: h2.Addrs()})\n\trequire.NoError(t, err)\n\n\t// wait for them to Identify each other\n\tids1.IdentifyConn(h1.Network().ConnsToPeer(h2.ID())[0])\n\tids2.IdentifyConn(h2.Network().ConnsToPeer(h1.ID())[0])\n\n\t// h1 starts listening on a new protocol and h2 finds out about that through a push\n\th1.SetStreamHandler(\"rand\", func(network.Stream) {})\n\trequire.Eventually(t, func() bool {\n\t\tsup, err := h2.Peerstore().SupportsProtocols(h1.ID(), []protocol.ID{\"rand\"}...)\n\t\treturn err == nil && len(sup) == 1 && sup[0] == \"rand\"\n\t}, time.Second, 10*time.Millisecond)\n\n\t// h1 stops listening on a protocol and h2 finds out about it via a push\n\th1.RemoveStreamHandler(\"rand\")\n\trequire.Eventually(t, func() bool {\n\t\tsup, err := h2.Peerstore().SupportsProtocols(h1.ID(), []protocol.ID{\"rand\"}...)\n\t\treturn err == nil && len(sup) == 0\n\t}, time.Second, 10*time.Millisecond)\n}\n\nfunc TestLargeIdentifyMessage(t *testing.T) {\n\tif race.WithRace() {\n\t\tt.Skip(\"setting peerstore.RecentlyConnectedAddrTTL is racy\")\n\t}\n\toldTTL := peerstore.RecentlyConnectedAddrTTL\n\toldTempTTL := peerstore.TempAddrTTL\n\tpeerstore.RecentlyConnectedAddrTTL = 500 * time.Millisecond\n\tpeerstore.TempAddrTTL = 50 * time.Millisecond\n\tt.Cleanup(func() {\n\t\tpeerstore.RecentlyConnectedAddrTTL = oldTTL\n\t\tpeerstore.TempAddrTTL = oldTempTTL\n\t})\n\n\tclk := mockClock.NewMock()\n\tswarm1 := swarmt.GenSwarm(t, swarmt.WithClock(clk))\n\tswarm2 := swarmt.GenSwarm(t, swarmt.WithClock(clk))\n\th1 := blhost.NewBlankHost(swarm1)\n\th2 := blhost.NewBlankHost(swarm2)\n\n\t// add protocol strings to make the message larger\n\t// about 2K of protocol strings\n\tfor i := range 500 {\n\t\tr := protocol.ID(fmt.Sprintf(\"rand%d\", i))\n\t\th1.SetStreamHandler(r, func(network.Stream) {})\n\t\th2.SetStreamHandler(r, func(network.Stream) {})\n\t}\n\n\th1p := h1.ID()\n\th2p := h2.ID()\n\n\tids1, err := identify.NewIDService(h1)\n\trequire.NoError(t, err)\n\tdefer ids1.Close()\n\tids1.Start()\n\n\tids2, err := identify.NewIDService(h2)\n\trequire.NoError(t, err)\n\tdefer ids2.Close()\n\tids2.Start()\n\n\tsub, err := ids1.Host.EventBus().Subscribe(new(event.EvtPeerIdentificationCompleted))\n\trequire.NoError(t, err)\n\n\ttestKnowsAddrs(t, h1, h2p, []ma.Multiaddr{}) // nothing\n\ttestKnowsAddrs(t, h2, h1p, []ma.Multiaddr{}) // nothing\n\n\t// the forgetMe addr represents an address for h1 that h2 has learned out of band\n\t// (not via identify protocol). During the identify exchange, it will be\n\t// forgotten and replaced by the addrs h1 sends.\n\tforgetMe, _ := ma.NewMultiaddr(\"/ip4/1.2.3.4/tcp/1234\")\n\th2.Peerstore().AddAddr(h1p, forgetMe, peerstore.RecentlyConnectedAddrTTL)\n\n\th2pi := h2.Peerstore().PeerInfo(h2p)\n\th2pi.Addrs = h2pi.Addrs[:1]\n\trequire.NoError(t, h1.Connect(context.Background(), h2pi))\n\n\th1t2c := h1.Network().ConnsToPeer(h2p)\n\trequire.Len(t, h1t2c, 1, \"should have a conn here\")\n\n\tids1.IdentifyConn(h1t2c[0])\n\n\t// the idService should be opened automatically, by the network.\n\t// what we should see now is that both peers know about each others listen addresses.\n\tt.Log(\"test peer1 has peer2 addrs correctly\")\n\ttestKnowsAddrs(t, h1, h2p, h2.Addrs()) // has them\n\ttestHasAgentVersion(t, h1, h2p)\n\ttestHasPublicKey(t, h1, h2p, h2.Peerstore().PubKey(h2p)) // h1 should have h2's public key\n\n\t// now, this wait we do have to do. it's the wait for the Listening side\n\t// to be done identifying the connection.\n\tc := h2.Network().ConnsToPeer(h1.ID())\n\tif len(c) != 1 {\n\t\tt.Fatal(\"should have connection by now at least.\")\n\t}\n\tids2.IdentifyConn(c[0])\n\n\t// and the protocol versions.\n\tt.Log(\"test peer2 has peer1 addrs correctly\")\n\ttestKnowsAddrs(t, h2, h1p, h1.Addrs()) // has them\n\ttestHasAgentVersion(t, h2, h1p)\n\ttestHasPublicKey(t, h2, h1p, h1.Peerstore().PubKey(h1p)) // h1 should have h2's public key\n\n\t// Need both sides to actually notice that the connection has been closed.\n\tsentDisconnect1 := waitForDisconnectNotification(swarm1)\n\tsentDisconnect2 := waitForDisconnectNotification(swarm2)\n\th1.Network().ClosePeer(h2p)\n\th2.Network().ClosePeer(h1p)\n\tif len(h2.Network().ConnsToPeer(h1.ID())) != 0 || len(h1.Network().ConnsToPeer(h2.ID())) != 0 {\n\t\tt.Fatal(\"should have no connections\")\n\t}\n\n\tt.Log(\"testing addrs just after disconnect\")\n\t// addresses don't immediately expire on disconnect, so we should still have them\n\ttestKnowsAddrs(t, h2, h1p, h1.Addrs())\n\ttestKnowsAddrs(t, h1, h2p, h2.Addrs())\n\n\t<-sentDisconnect1\n\t<-sentDisconnect2\n\n\t// the addrs had their TTLs reduced on disconnect, and\n\t// will be forgotten soon after\n\tt.Log(\"testing addrs after TTL expiration\")\n\tclk.Add(time.Second)\n\ttestKnowsAddrs(t, h1, h2p, []ma.Multiaddr{})\n\ttestKnowsAddrs(t, h2, h1p, []ma.Multiaddr{})\n\n\t// test that we received the \"identify completed\" event.\n\tselect {\n\tcase evtAny := <-sub.Out():\n\t\tassertCorrectEvtPeerIdentificationCompleted(t, evtAny, h2)\n\tcase <-time.After(3 * time.Second):\n\t\tt.Fatalf(\"expected EvtPeerIdentificationCompleted event within 3 seconds; none received\")\n\t}\n}\n\nfunc randString(n int) string {\n\tchars := \"abcdefghijklmnopqrstuvwxyz\"\n\tbuf := make([]byte, n)\n\tfor i := range n {\n\t\tbuf[i] = chars[rand.Intn(len(chars))]\n\t}\n\treturn string(buf)\n}\n\nfunc TestLargePushMessage(t *testing.T) {\n\tctx := t.Context()\n\n\th1 := blhost.NewBlankHost(swarmt.GenSwarm(t))\n\th2 := blhost.NewBlankHost(swarmt.GenSwarm(t))\n\n\t// add protocol strings to make the message larger\n\t// about 3K of protocol strings\n\tfor i := range 100 {\n\t\tr := protocol.ID(fmt.Sprintf(\"%s-%d\", randString(30), i))\n\t\th1.SetStreamHandler(r, func(network.Stream) {})\n\t\th2.SetStreamHandler(r, func(network.Stream) {})\n\t}\n\n\th1p := h1.ID()\n\th2p := h2.ID()\n\n\tids1, err := identify.NewIDService(h1)\n\trequire.NoError(t, err)\n\tdefer ids1.Close()\n\tids1.Start()\n\n\tids2, err := identify.NewIDService(h2)\n\trequire.NoError(t, err)\n\tdefer ids2.Close()\n\tids2.Start()\n\n\ttestKnowsAddrs(t, h1, h2p, []ma.Multiaddr{}) // nothing\n\ttestKnowsAddrs(t, h2, h1p, []ma.Multiaddr{}) // nothing\n\n\th2pi := h2.Peerstore().PeerInfo(h2p)\n\trequire.NoError(t, h1.Connect(ctx, h2pi))\n\t// h1 should immediately see a connection from h2\n\trequire.NotEmpty(t, h1.Network().ConnsToPeer(h2p))\n\t// wait for h2 to Identify itself so we are sure h2 has seen the connection.\n\tids1.IdentifyConn(h1.Network().ConnsToPeer(h2p)[0])\n\n\t// h2 should now see the connection and we should wait for h1 to Identify itself to h2.\n\trequire.NotEmpty(t, h2.Network().ConnsToPeer(h1p))\n\tids2.IdentifyConn(h2.Network().ConnsToPeer(h1p)[0])\n\n\ttestKnowsAddrs(t, h1, h2p, h2.Peerstore().Addrs(h2p))\n\ttestKnowsAddrs(t, h2, h1p, h1.Peerstore().Addrs(h1p))\n\n\t// change addr on host 1 and ensure host2 gets a push\n\tlad := ma.StringCast(\"/ip4/127.0.0.1/tcp/1234\")\n\trequire.NoError(t, h1.Network().Listen(lad))\n\tmatest.AssertMultiaddrsContain(t, h1.Addrs(), lad)\n\temitAddrChangeEvt(t, h1)\n\n\trequire.Eventually(t, func() bool {\n\t\treturn ma.Contains(h2.Peerstore().Addrs(h1p), lad)\n\t}, time.Second, 10*time.Millisecond)\n\n\t// change addr on host2 and ensure host 1 gets a pus\n\tlad = ma.StringCast(\"/ip4/127.0.0.1/tcp/1235\")\n\trequire.NoError(t, h2.Network().Listen(lad))\n\tmatest.AssertMultiaddrsContain(t, h2.Addrs(), lad)\n\temitAddrChangeEvt(t, h2)\n\n\trequire.Eventually(t, func() bool {\n\t\treturn ma.Contains(h1.Peerstore().Addrs(h2p), lad)\n\t}, time.Second, 10*time.Millisecond)\n\n\t// change addr on host2 again\n\tlad2 := ma.StringCast(\"/ip4/127.0.0.1/tcp/1236\")\n\trequire.NoError(t, h2.Network().Listen(lad2))\n\tmatest.AssertMultiaddrsContain(t, h2.Addrs(), lad2)\n\temitAddrChangeEvt(t, h2)\n\n\trequire.Eventually(t, func() bool {\n\t\treturn ma.Contains(h1.Peerstore().Addrs(h2p), lad2)\n\t}, time.Second, 10*time.Millisecond)\n}\n\nfunc TestIdentifyResponseReadTimeout(t *testing.T) {\n\tctx := t.Context()\n\n\th1 := blhost.NewBlankHost(swarmt.GenSwarm(t))\n\th2 := blhost.NewBlankHost(swarmt.GenSwarm(t))\n\tdefer h1.Close()\n\tdefer h2.Close()\n\n\th2p := h2.ID()\n\tids1, err := identify.NewIDService(h1, identify.WithTimeout(100*time.Millisecond))\n\trequire.NoError(t, err)\n\tdefer ids1.Close()\n\tids1.Start()\n\n\tids2, err := identify.NewIDService(h2, identify.WithTimeout(100*time.Millisecond))\n\trequire.NoError(t, err)\n\tdefer ids2.Close()\n\tids2.Start()\n\n\t// remote stream handler will just hang and not send back an identify response\n\th2.SetStreamHandler(identify.ID, func(_ network.Stream) {\n\t\ttime.Sleep(100 * time.Second)\n\t})\n\n\tsub, err := ids1.Host.EventBus().Subscribe(new(event.EvtPeerIdentificationFailed))\n\trequire.NoError(t, err)\n\n\th2pi := h2.Peerstore().PeerInfo(h2p)\n\trequire.NoError(t, h1.Connect(ctx, h2pi))\n\n\tselect {\n\tcase ev := <-sub.Out():\n\t\tfev := ev.(event.EvtPeerIdentificationFailed)\n\t\trequire.Contains(t, fev.Reason.Error(), \"deadline\")\n\tcase <-time.After(5 * time.Second):\n\t\tt.Fatal(\"did not receive identify failure event\")\n\t}\n}\n\nfunc TestIncomingIDStreamsTimeout(t *testing.T) {\n\tctx := t.Context()\n\n\tprotocols := []protocol.ID{identify.IDPush}\n\n\tfor _, p := range protocols {\n\t\th1 := blhost.NewBlankHost(swarmt.GenSwarm(t))\n\t\th2 := blhost.NewBlankHost(swarmt.GenSwarm(t))\n\t\tdefer h1.Close()\n\t\tdefer h2.Close()\n\n\t\tids1, err := identify.NewIDService(h1, identify.WithTimeout(100*time.Millisecond))\n\t\trequire.NoError(t, err)\n\t\tdefer ids1.Close()\n\t\tids1.Start()\n\n\t\tids2, err := identify.NewIDService(h2, identify.WithTimeout(100*time.Millisecond))\n\t\trequire.NoError(t, err)\n\t\tdefer ids2.Close()\n\t\tids2.Start()\n\n\t\th2p := h2.ID()\n\t\th2pi := h2.Peerstore().PeerInfo(h2p)\n\t\trequire.NoError(t, h1.Connect(ctx, h2pi))\n\n\t\t_, err = h1.NewStream(ctx, h2p, p)\n\t\trequire.NoError(t, err)\n\n\t\t// remote peer should eventually reset stream\n\t\trequire.Eventually(t, func() bool {\n\t\t\tfor _, c := range h2.Network().ConnsToPeer(h1.ID()) {\n\t\t\t\tif len(c.GetStreams()) > 0 {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true\n\t\t}, 5*time.Second, 200*time.Millisecond)\n\t}\n}\n\nfunc TestOutOfOrderConnectedNotifs(t *testing.T) {\n\th1, err := libp2p.New(libp2p.NoListenAddrs)\n\trequire.NoError(t, err)\n\tdefer h1.Close()\n\th2, err := libp2p.New(libp2p.ListenAddrs(ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1\")))\n\trequire.NoError(t, err)\n\tdefer h2.Close()\n\n\tdoneCh := make(chan struct{})\n\terrCh := make(chan error)\n\n\t// This callback may be called before identify's Connnected callback completes. If it does, the IdentifyWait should still finish successfully.\n\th1.Network().Notify(&network.NotifyBundle{\n\t\tConnectedF: func(_ network.Network, c network.Conn) {\n\t\t\tidChan := h1.(interface{ IDService() identify.IDService }).IDService().IdentifyWait(c)\n\t\t\tgo func() {\n\t\t\t\t<-idChan\n\t\t\t\tprotos, err := h1.Peerstore().GetProtocols(h2.ID())\n\t\t\t\tif err != nil {\n\t\t\t\t\terrCh <- err\n\t\t\t\t}\n\t\t\t\tif len(protos) == 0 {\n\t\t\t\t\terrCh <- errors.New(\"no protocols found. Identify did not complete\")\n\t\t\t\t}\n\n\t\t\t\tclose(doneCh)\n\t\t\t}()\n\t\t},\n\t})\n\n\th1.Connect(context.Background(), peer.AddrInfo{ID: h2.ID(), Addrs: h2.Addrs()})\n\n\tselect {\n\tcase <-doneCh:\n\tcase err := <-errCh:\n\t\tt.Fatalf(\"err: %v\", err)\n\tcase <-time.After(3 * time.Second):\n\t\tt.Fatalf(\"identify wait never completed\")\n\t}\n}\n\nfunc waitForAddrInStream(t *testing.T, s <-chan ma.Multiaddr, expected ma.Multiaddr, timeout time.Duration, failMsg string) {\n\tt.Helper()\n\tfor {\n\t\tselect {\n\t\tcase addr := <-s:\n\t\t\tif addr.Equal(expected) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tcontinue\n\t\tcase <-time.After(timeout):\n\t\t\tt.Fatal(failMsg)\n\t\t}\n\t}\n}\n\nfunc waitForDisconnectNotification(swarm *swarm.Swarm) <-chan struct{} {\n\tdone := make(chan struct{})\n\tvar once sync.Once\n\tvar nb *network.NotifyBundle\n\tnb = &network.NotifyBundle{\n\t\tDisconnectedF: func(_ network.Network, _ network.Conn) {\n\t\t\tonce.Do(func() {\n\t\t\t\tgo func() {\n\t\t\t\t\tswarm.StopNotify(nb)\n\t\t\t\t\tclose(done)\n\t\t\t\t}()\n\t\t\t})\n\t\t},\n\t}\n\tswarm.Notify(nb)\n\n\treturn done\n}\n"
  },
  {
    "path": "p2p/protocol/identify/internal/user-agent/user_agent.go",
    "content": "package useragent\n\nimport (\n\t\"fmt\"\n\t\"runtime/debug\"\n)\n\nfunc DefaultUserAgent() string {\n\treturn defaultUserAgent\n}\n\nvar defaultUserAgent = \"github.com/libp2p/go-libp2p\"\n\nfunc init() {\n\tbi, ok := debug.ReadBuildInfo()\n\tif !ok {\n\t\treturn\n\t}\n\tversion := bi.Main.Version\n\t// version will only be non-empty if built as a dependency of another module\n\tif version == \"\" {\n\t\treturn\n\t}\n\n\tif version != \"(devel)\" {\n\t\tdefaultUserAgent = fmt.Sprintf(\"%s@%s\", bi.Main.Path, bi.Main.Version)\n\t\treturn\n\t}\n\n\tvar revision string\n\tvar dirty bool\n\tfor _, bs := range bi.Settings {\n\t\tswitch bs.Key {\n\t\tcase \"vcs.revision\":\n\t\t\trevision = bs.Value\n\t\t\tif len(revision) > 9 {\n\t\t\t\trevision = revision[:9]\n\t\t\t}\n\t\tcase \"vcs.modified\":\n\t\t\tif bs.Value == \"true\" {\n\t\t\t\tdirty = true\n\t\t\t}\n\t\t}\n\t}\n\tdefaultUserAgent = fmt.Sprintf(\"%s@%s\", bi.Main.Path, revision)\n\tif dirty {\n\t\tdefaultUserAgent += \"-dirty\"\n\t}\n}\n"
  },
  {
    "path": "p2p/protocol/identify/metrics.go",
    "content": "package identify\n\nimport (\n\t\"github.com/libp2p/go-libp2p/core/event\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/p2p/metricshelper\"\n\n\t\"github.com/prometheus/client_golang/prometheus\"\n)\n\nconst metricNamespace = \"libp2p_identify\"\n\nvar (\n\tpushesTriggered = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"identify_pushes_triggered_total\",\n\t\t\tHelp:      \"Pushes Triggered\",\n\t\t},\n\t\t[]string{\"trigger\"},\n\t)\n\tidentify = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"identify_total\",\n\t\t\tHelp:      \"Identify\",\n\t\t},\n\t\t[]string{\"dir\"},\n\t)\n\tidentifyPush = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"identify_push_total\",\n\t\t\tHelp:      \"Identify Push\",\n\t\t},\n\t\t[]string{\"dir\"},\n\t)\n\tconnPushSupportTotal = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"conn_push_support_total\",\n\t\t\tHelp:      \"Identify Connection Push Support\",\n\t\t},\n\t\t[]string{\"support\"},\n\t)\n\tprotocolsCount = prometheus.NewGauge(\n\t\tprometheus.GaugeOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"protocols_count\",\n\t\t\tHelp:      \"Protocols Count\",\n\t\t},\n\t)\n\taddrsCount = prometheus.NewGauge(\n\t\tprometheus.GaugeOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"addrs_count\",\n\t\t\tHelp:      \"Address Count\",\n\t\t},\n\t)\n\tnumProtocolsReceived = prometheus.NewHistogram(\n\t\tprometheus.HistogramOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"protocols_received\",\n\t\t\tHelp:      \"Number of Protocols received\",\n\t\t\tBuckets:   buckets,\n\t\t},\n\t)\n\tnumAddrsReceived = prometheus.NewHistogram(\n\t\tprometheus.HistogramOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"addrs_received\",\n\t\t\tHelp:      \"Number of addrs received\",\n\t\t\tBuckets:   buckets,\n\t\t},\n\t)\n\tcollectors = []prometheus.Collector{\n\t\tpushesTriggered,\n\t\tidentify,\n\t\tidentifyPush,\n\t\tconnPushSupportTotal,\n\t\tprotocolsCount,\n\t\taddrsCount,\n\t\tnumProtocolsReceived,\n\t\tnumAddrsReceived,\n\t}\n\t// 1 to 20 and then up to 100 in steps of 5\n\tbuckets = append(\n\t\tprometheus.LinearBuckets(1, 1, 20),\n\t\tprometheus.LinearBuckets(25, 5, 16)...,\n\t)\n)\n\ntype MetricsTracer interface {\n\t// TriggeredPushes counts IdentifyPushes triggered by event\n\tTriggeredPushes(event any)\n\n\t// ConnPushSupport counts peers by Push Support\n\tConnPushSupport(identifyPushSupport)\n\n\t// IdentifyReceived tracks metrics on receiving an identify response\n\tIdentifyReceived(isPush bool, numProtocols int, numAddrs int)\n\n\t// IdentifySent tracks metrics on sending an identify response\n\tIdentifySent(isPush bool, numProtocols int, numAddrs int)\n}\n\ntype metricsTracer struct{}\n\nvar _ MetricsTracer = &metricsTracer{}\n\ntype metricsTracerSetting struct {\n\treg prometheus.Registerer\n}\n\ntype MetricsTracerOption func(*metricsTracerSetting)\n\nfunc WithRegisterer(reg prometheus.Registerer) MetricsTracerOption {\n\treturn func(s *metricsTracerSetting) {\n\t\tif reg != nil {\n\t\t\ts.reg = reg\n\t\t}\n\t}\n}\n\nfunc NewMetricsTracer(opts ...MetricsTracerOption) MetricsTracer {\n\tsetting := &metricsTracerSetting{reg: prometheus.DefaultRegisterer}\n\tfor _, opt := range opts {\n\t\topt(setting)\n\t}\n\tmetricshelper.RegisterCollectors(setting.reg, collectors...)\n\treturn &metricsTracer{}\n}\n\nfunc (t *metricsTracer) TriggeredPushes(ev any) {\n\ttags := metricshelper.GetStringSlice()\n\tdefer metricshelper.PutStringSlice(tags)\n\n\ttyp := \"unknown\"\n\tswitch ev.(type) {\n\tcase event.EvtLocalProtocolsUpdated:\n\t\ttyp = \"protocols_updated\"\n\tcase event.EvtLocalAddressesUpdated:\n\t\ttyp = \"addresses_updated\"\n\t}\n\t*tags = append(*tags, typ)\n\tpushesTriggered.WithLabelValues(*tags...).Inc()\n}\n\nfunc (t *metricsTracer) IncrementPushSupport(s identifyPushSupport) {\n\ttags := metricshelper.GetStringSlice()\n\tdefer metricshelper.PutStringSlice(tags)\n\n\t*tags = append(*tags, getPushSupport(s))\n\tconnPushSupportTotal.WithLabelValues(*tags...).Inc()\n}\n\nfunc (t *metricsTracer) IdentifySent(isPush bool, numProtocols int, numAddrs int) {\n\ttags := metricshelper.GetStringSlice()\n\tdefer metricshelper.PutStringSlice(tags)\n\n\tif isPush {\n\t\t*tags = append(*tags, metricshelper.GetDirection(network.DirOutbound))\n\t\tidentifyPush.WithLabelValues(*tags...).Inc()\n\t} else {\n\t\t*tags = append(*tags, metricshelper.GetDirection(network.DirInbound))\n\t\tidentify.WithLabelValues(*tags...).Inc()\n\t}\n\n\tprotocolsCount.Set(float64(numProtocols))\n\taddrsCount.Set(float64(numAddrs))\n}\n\nfunc (t *metricsTracer) IdentifyReceived(isPush bool, numProtocols int, numAddrs int) {\n\ttags := metricshelper.GetStringSlice()\n\tdefer metricshelper.PutStringSlice(tags)\n\n\tif isPush {\n\t\t*tags = append(*tags, metricshelper.GetDirection(network.DirInbound))\n\t\tidentifyPush.WithLabelValues(*tags...).Inc()\n\t} else {\n\t\t*tags = append(*tags, metricshelper.GetDirection(network.DirOutbound))\n\t\tidentify.WithLabelValues(*tags...).Inc()\n\t}\n\n\tnumProtocolsReceived.Observe(float64(numProtocols))\n\tnumAddrsReceived.Observe(float64(numAddrs))\n}\n\nfunc (t *metricsTracer) ConnPushSupport(support identifyPushSupport) {\n\ttags := metricshelper.GetStringSlice()\n\tdefer metricshelper.PutStringSlice(tags)\n\n\t*tags = append(*tags, getPushSupport(support))\n\tconnPushSupportTotal.WithLabelValues(*tags...).Inc()\n}\n\nfunc getPushSupport(s identifyPushSupport) string {\n\tswitch s {\n\tcase identifyPushSupported:\n\t\treturn \"supported\"\n\tcase identifyPushUnsupported:\n\t\treturn \"not supported\"\n\tdefault:\n\t\treturn \"unknown\"\n\t}\n}\n"
  },
  {
    "path": "p2p/protocol/identify/metrics_test.go",
    "content": "//go:build nocover\n\npackage identify\n\nimport (\n\t\"math/rand\"\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p/core/event\"\n)\n\nfunc TestMetricsNoAllocNoCover(t *testing.T) {\n\tevents := []any{\n\t\tevent.EvtLocalAddressesUpdated{},\n\t\tevent.EvtLocalProtocolsUpdated{},\n\t\tevent.EvtNATDeviceTypeChanged{},\n\t}\n\n\tpushSupport := []identifyPushSupport{\n\t\tidentifyPushSupportUnknown,\n\t\tidentifyPushSupported,\n\t\tidentifyPushUnsupported,\n\t}\n\n\ttr := NewMetricsTracer()\n\ttests := map[string]func(){\n\t\t\"TriggeredPushes\":  func() { tr.TriggeredPushes(events[rand.Intn(len(events))]) },\n\t\t\"ConnPushSupport\":  func() { tr.ConnPushSupport(pushSupport[rand.Intn(len(pushSupport))]) },\n\t\t\"IdentifyReceived\": func() { tr.IdentifyReceived(rand.Intn(2) == 0, rand.Intn(20), rand.Intn(20)) },\n\t\t\"IdentifySent\":     func() { tr.IdentifySent(rand.Intn(2) == 0, rand.Intn(20), rand.Intn(20)) },\n\t}\n\tfor method, f := range tests {\n\t\tallocs := testing.AllocsPerRun(1000, f)\n\t\tif allocs > 0 {\n\t\t\tt.Fatalf(\"Alloc Test: %s, got: %0.2f, expected: 0 allocs\", method, allocs)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "p2p/protocol/identify/opts.go",
    "content": "package identify\n\nimport \"time\"\n\ntype config struct {\n\tprotocolVersion         string\n\tuserAgent               string\n\tdisableSignedPeerRecord bool\n\tmetricsTracer           MetricsTracer\n\ttimeout                 time.Duration\n}\n\n// Option is an option function for identify.\ntype Option func(*config)\n\n// ProtocolVersion sets the protocol version string that will be used to\n// identify the family of protocols used by the peer.\nfunc ProtocolVersion(s string) Option {\n\treturn func(cfg *config) {\n\t\tcfg.protocolVersion = s\n\t}\n}\n\n// UserAgent sets the user agent this node will identify itself with to peers.\nfunc UserAgent(ua string) Option {\n\treturn func(cfg *config) {\n\t\tcfg.userAgent = ua\n\t}\n}\n\n// DisableSignedPeerRecord disables populating signed peer records on the outgoing Identify response\n// and ONLY sends the unsigned addresses.\nfunc DisableSignedPeerRecord() Option {\n\treturn func(cfg *config) {\n\t\tcfg.disableSignedPeerRecord = true\n\t}\n}\n\nfunc WithMetricsTracer(tr MetricsTracer) Option {\n\treturn func(cfg *config) {\n\t\tcfg.metricsTracer = tr\n\t}\n}\n\n// WithTimeout sets the timeout for identify interactions.\nfunc WithTimeout(timeout time.Duration) Option {\n\treturn func(cfg *config) {\n\t\tcfg.timeout = timeout\n\t}\n}\n"
  },
  {
    "path": "p2p/protocol/identify/pb/identify.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.6\n// \tprotoc        v5.29.2\n// source: p2p/protocol/identify/pb/identify.proto\n\npackage pb\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Identify struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// protocolVersion determines compatibility between peers\n\tProtocolVersion *string `protobuf:\"bytes,5,opt,name=protocolVersion\" json:\"protocolVersion,omitempty\"` // e.g. ipfs/1.0.0\n\t// agentVersion is like a UserAgent string in browsers, or client version in bittorrent\n\t// includes the client name and client.\n\tAgentVersion *string `protobuf:\"bytes,6,opt,name=agentVersion\" json:\"agentVersion,omitempty\"` // e.g. go-ipfs/0.1.0\n\t// publicKey is this node's public key (which also gives its node.ID)\n\t// - may not need to be sent, as secure channel implies it has been sent.\n\t// - then again, if we change / disable secure channel, may still want it.\n\tPublicKey []byte `protobuf:\"bytes,1,opt,name=publicKey\" json:\"publicKey,omitempty\"`\n\t// listenAddrs are the multiaddrs the sender node listens for open connections on\n\tListenAddrs [][]byte `protobuf:\"bytes,2,rep,name=listenAddrs\" json:\"listenAddrs,omitempty\"`\n\t// oservedAddr is the multiaddr of the remote endpoint that the sender node perceives\n\t// this is useful information to convey to the other side, as it helps the remote endpoint\n\t// determine whether its connection to the local peer goes through NAT.\n\tObservedAddr []byte `protobuf:\"bytes,4,opt,name=observedAddr\" json:\"observedAddr,omitempty\"`\n\t// protocols are the services this node is running\n\tProtocols []string `protobuf:\"bytes,3,rep,name=protocols\" json:\"protocols,omitempty\"`\n\t// signedPeerRecord contains a serialized SignedEnvelope containing a PeerRecord,\n\t// signed by the sending node. It contains the same addresses as the listenAddrs field, but\n\t// in a form that lets us share authenticated addrs with other peers.\n\t// see github.com/libp2p/go-libp2p/core/record/pb/envelope.proto and\n\t// github.com/libp2p/go-libp2p/core/peer/pb/peer_record.proto for message definitions.\n\tSignedPeerRecord []byte `protobuf:\"bytes,8,opt,name=signedPeerRecord\" json:\"signedPeerRecord,omitempty\"`\n\tunknownFields    protoimpl.UnknownFields\n\tsizeCache        protoimpl.SizeCache\n}\n\nfunc (x *Identify) Reset() {\n\t*x = Identify{}\n\tmi := &file_p2p_protocol_identify_pb_identify_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Identify) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Identify) ProtoMessage() {}\n\nfunc (x *Identify) ProtoReflect() protoreflect.Message {\n\tmi := &file_p2p_protocol_identify_pb_identify_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Identify.ProtoReflect.Descriptor instead.\nfunc (*Identify) Descriptor() ([]byte, []int) {\n\treturn file_p2p_protocol_identify_pb_identify_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Identify) GetProtocolVersion() string {\n\tif x != nil && x.ProtocolVersion != nil {\n\t\treturn *x.ProtocolVersion\n\t}\n\treturn \"\"\n}\n\nfunc (x *Identify) GetAgentVersion() string {\n\tif x != nil && x.AgentVersion != nil {\n\t\treturn *x.AgentVersion\n\t}\n\treturn \"\"\n}\n\nfunc (x *Identify) GetPublicKey() []byte {\n\tif x != nil {\n\t\treturn x.PublicKey\n\t}\n\treturn nil\n}\n\nfunc (x *Identify) GetListenAddrs() [][]byte {\n\tif x != nil {\n\t\treturn x.ListenAddrs\n\t}\n\treturn nil\n}\n\nfunc (x *Identify) GetObservedAddr() []byte {\n\tif x != nil {\n\t\treturn x.ObservedAddr\n\t}\n\treturn nil\n}\n\nfunc (x *Identify) GetProtocols() []string {\n\tif x != nil {\n\t\treturn x.Protocols\n\t}\n\treturn nil\n}\n\nfunc (x *Identify) GetSignedPeerRecord() []byte {\n\tif x != nil {\n\t\treturn x.SignedPeerRecord\n\t}\n\treturn nil\n}\n\nvar File_p2p_protocol_identify_pb_identify_proto protoreflect.FileDescriptor\n\nconst file_p2p_protocol_identify_pb_identify_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"'p2p/protocol/identify/pb/identify.proto\\x12\\videntify.pb\\\"\\x86\\x02\\n\" +\n\t\"\\bIdentify\\x12(\\n\" +\n\t\"\\x0fprotocolVersion\\x18\\x05 \\x01(\\tR\\x0fprotocolVersion\\x12\\\"\\n\" +\n\t\"\\fagentVersion\\x18\\x06 \\x01(\\tR\\fagentVersion\\x12\\x1c\\n\" +\n\t\"\\tpublicKey\\x18\\x01 \\x01(\\fR\\tpublicKey\\x12 \\n\" +\n\t\"\\vlistenAddrs\\x18\\x02 \\x03(\\fR\\vlistenAddrs\\x12\\\"\\n\" +\n\t\"\\fobservedAddr\\x18\\x04 \\x01(\\fR\\fobservedAddr\\x12\\x1c\\n\" +\n\t\"\\tprotocols\\x18\\x03 \\x03(\\tR\\tprotocols\\x12*\\n\" +\n\t\"\\x10signedPeerRecord\\x18\\b \\x01(\\fR\\x10signedPeerRecordB6Z4github.com/libp2p/go-libp2p/p2p/protocol/identify/pb\"\n\nvar (\n\tfile_p2p_protocol_identify_pb_identify_proto_rawDescOnce sync.Once\n\tfile_p2p_protocol_identify_pb_identify_proto_rawDescData []byte\n)\n\nfunc file_p2p_protocol_identify_pb_identify_proto_rawDescGZIP() []byte {\n\tfile_p2p_protocol_identify_pb_identify_proto_rawDescOnce.Do(func() {\n\t\tfile_p2p_protocol_identify_pb_identify_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_p2p_protocol_identify_pb_identify_proto_rawDesc), len(file_p2p_protocol_identify_pb_identify_proto_rawDesc)))\n\t})\n\treturn file_p2p_protocol_identify_pb_identify_proto_rawDescData\n}\n\nvar file_p2p_protocol_identify_pb_identify_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_p2p_protocol_identify_pb_identify_proto_goTypes = []any{\n\t(*Identify)(nil), // 0: identify.pb.Identify\n}\nvar file_p2p_protocol_identify_pb_identify_proto_depIdxs = []int32{\n\t0, // [0:0] is the sub-list for method output_type\n\t0, // [0:0] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_p2p_protocol_identify_pb_identify_proto_init() }\nfunc file_p2p_protocol_identify_pb_identify_proto_init() {\n\tif File_p2p_protocol_identify_pb_identify_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_p2p_protocol_identify_pb_identify_proto_rawDesc), len(file_p2p_protocol_identify_pb_identify_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_p2p_protocol_identify_pb_identify_proto_goTypes,\n\t\tDependencyIndexes: file_p2p_protocol_identify_pb_identify_proto_depIdxs,\n\t\tMessageInfos:      file_p2p_protocol_identify_pb_identify_proto_msgTypes,\n\t}.Build()\n\tFile_p2p_protocol_identify_pb_identify_proto = out.File\n\tfile_p2p_protocol_identify_pb_identify_proto_goTypes = nil\n\tfile_p2p_protocol_identify_pb_identify_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "p2p/protocol/identify/pb/identify.proto",
    "content": "syntax = \"proto2\";\n\npackage identify.pb;\n\noption go_package = \"github.com/libp2p/go-libp2p/p2p/protocol/identify/pb\";\n\nmessage Identify {\n\n  // protocolVersion determines compatibility between peers\n  optional string protocolVersion = 5; // e.g. ipfs/1.0.0\n\n  // agentVersion is like a UserAgent string in browsers, or client version in bittorrent\n  // includes the client name and client.\n  optional string agentVersion = 6; // e.g. go-ipfs/0.1.0\n\n  // publicKey is this node's public key (which also gives its node.ID)\n  // - may not need to be sent, as secure channel implies it has been sent.\n  // - then again, if we change / disable secure channel, may still want it.\n  optional bytes publicKey = 1;\n\n  // listenAddrs are the multiaddrs the sender node listens for open connections on\n  repeated bytes listenAddrs = 2;\n\n  // oservedAddr is the multiaddr of the remote endpoint that the sender node perceives\n  // this is useful information to convey to the other side, as it helps the remote endpoint\n  // determine whether its connection to the local peer goes through NAT.\n  optional bytes observedAddr = 4;\n\n  // protocols are the services this node is running\n  repeated string protocols = 3;\n\n  // signedPeerRecord contains a serialized SignedEnvelope containing a PeerRecord,\n  // signed by the sending node. It contains the same addresses as the listenAddrs field, but\n  // in a form that lets us share authenticated addrs with other peers.\n  // see github.com/libp2p/go-libp2p/core/record/pb/envelope.proto and\n  // github.com/libp2p/go-libp2p/core/peer/pb/peer_record.proto for message definitions.\n  optional bytes signedPeerRecord = 8;\n}\n"
  },
  {
    "path": "p2p/protocol/identify/snapshot_test.go",
    "content": "package identify\n\nimport (\n\t\"crypto/rand\"\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\t\"github.com/libp2p/go-libp2p/core/record\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSnapshotEquality(t *testing.T) {\n\taddr1 := ma.StringCast(\"/ip4/127.0.0.1/tcp/1234\")\n\taddr2 := ma.StringCast(\"/ip4/127.0.0.1/udp/1234/quic-v1\")\n\n\t_, pubKey1, err := crypto.GenerateEd25519Key(rand.Reader)\n\trequire.NoError(t, err)\n\t_, pubKey2, err := crypto.GenerateEd25519Key(rand.Reader)\n\trequire.NoError(t, err)\n\trecord1 := &record.Envelope{PublicKey: pubKey1}\n\trecord2 := &record.Envelope{PublicKey: pubKey2}\n\n\tfor _, tc := range []struct {\n\t\ts1, s2 *identifySnapshot\n\t\tresult bool\n\t}{\n\t\t{s1: &identifySnapshot{record: record1}, s2: &identifySnapshot{record: record1}, result: true},\n\t\t{s1: &identifySnapshot{record: record1}, s2: &identifySnapshot{record: record2}, result: false},\n\t\t{s1: &identifySnapshot{addrs: []ma.Multiaddr{addr1}}, s2: &identifySnapshot{addrs: []ma.Multiaddr{addr1}}, result: true},\n\t\t{s1: &identifySnapshot{addrs: []ma.Multiaddr{addr1}}, s2: &identifySnapshot{addrs: []ma.Multiaddr{addr2}}, result: false},\n\t\t{s1: &identifySnapshot{addrs: []ma.Multiaddr{addr1, addr2}}, s2: &identifySnapshot{addrs: []ma.Multiaddr{addr2}}, result: false},\n\t\t{s1: &identifySnapshot{addrs: []ma.Multiaddr{addr1}}, s2: &identifySnapshot{addrs: []ma.Multiaddr{addr1, addr2}}, result: false},\n\t\t{s1: &identifySnapshot{protocols: []protocol.ID{\"/foo\"}}, s2: &identifySnapshot{protocols: []protocol.ID{\"/foo\"}}, result: true},\n\t\t{s1: &identifySnapshot{protocols: []protocol.ID{\"/foo\"}}, s2: &identifySnapshot{protocols: []protocol.ID{\"/bar\"}}, result: false},\n\t\t{s1: &identifySnapshot{protocols: []protocol.ID{\"/foo\", \"/bar\"}}, s2: &identifySnapshot{protocols: []protocol.ID{\"/bar\"}}, result: false},\n\t\t{s1: &identifySnapshot{protocols: []protocol.ID{\"/foo\"}}, s2: &identifySnapshot{protocols: []protocol.ID{\"/foo\", \"/bar\"}}, result: false},\n\t} {\n\t\tif tc.result {\n\t\t\trequire.Truef(t, tc.s1.Equal(tc.s2), \"expected equal: %+v and %+v\", tc.s1, tc.s2)\n\t\t} else {\n\t\t\trequire.Falsef(t, tc.s1.Equal(tc.s2), \"expected unequal: %+v and %+v\", tc.s1, tc.s2)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "p2p/protocol/ping/ping.go",
    "content": "package ping\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/rand\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"io\"\n\tmrand \"math/rand\"\n\t\"time\"\n\n\tpool \"github.com/libp2p/go-buffer-pool\"\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n)\n\nvar log = logging.Logger(\"ping\")\n\nconst (\n\tPingSize     = 32\n\tpingTimeout  = 10 * time.Second\n\tpingDuration = 30 * time.Second\n\n\tID = \"/ipfs/ping/1.0.0\"\n\n\tServiceName = \"libp2p.ping\"\n)\n\ntype PingService struct {\n\tHost host.Host\n}\n\nfunc NewPingService(h host.Host) *PingService {\n\tps := &PingService{h}\n\th.SetStreamHandler(ID, ps.PingHandler)\n\treturn ps\n}\n\nfunc (p *PingService) PingHandler(s network.Stream) {\n\tif err := s.Scope().SetService(ServiceName); err != nil {\n\t\tlog.Debug(\"error attaching stream to ping service\", \"err\", err)\n\t\ts.Reset()\n\t\treturn\n\t}\n\n\tif err := s.Scope().ReserveMemory(PingSize, network.ReservationPriorityAlways); err != nil {\n\t\tlog.Debug(\"error reserving memory for ping stream\", \"err\", err)\n\t\ts.Reset()\n\t\treturn\n\t}\n\tdefer s.Scope().ReleaseMemory(PingSize)\n\n\ts.SetDeadline(time.Now().Add(pingDuration))\n\n\tbuf := pool.Get(PingSize)\n\tdefer pool.Put(buf)\n\n\terrCh := make(chan error, 1)\n\tdefer close(errCh)\n\ttimer := time.NewTimer(pingTimeout)\n\tdefer timer.Stop()\n\n\tgo func() {\n\t\tselect {\n\t\tcase <-timer.C:\n\t\t\tlog.Debug(\"ping timeout\")\n\t\tcase err, ok := <-errCh:\n\t\t\tif ok {\n\t\t\t\tlog.Debug(\"ping error\", \"err\", err)\n\t\t\t} else {\n\t\t\t\tlog.Error(\"ping loop failed without error\")\n\t\t\t}\n\t\t}\n\t\ts.Close()\n\t}()\n\n\tfor {\n\t\t_, err := io.ReadFull(s, buf)\n\t\tif err != nil {\n\t\t\terrCh <- err\n\t\t\treturn\n\t\t}\n\n\t\t_, err = s.Write(buf)\n\t\tif err != nil {\n\t\t\terrCh <- err\n\t\t\treturn\n\t\t}\n\n\t\ttimer.Reset(pingTimeout)\n\t}\n}\n\n// Result is a result of a ping attempt, either an RTT or an error.\ntype Result struct {\n\tRTT   time.Duration\n\tError error\n}\n\nfunc (ps *PingService) Ping(ctx context.Context, p peer.ID) <-chan Result {\n\treturn Ping(ctx, ps.Host, p)\n}\n\nfunc pingError(err error) chan Result {\n\tch := make(chan Result, 1)\n\tch <- Result{Error: err}\n\tclose(ch)\n\treturn ch\n}\n\n// Ping pings the remote peer until the context is canceled, returning a stream\n// of RTTs or errors.\nfunc Ping(ctx context.Context, h host.Host, p peer.ID) <-chan Result {\n\ts, err := h.NewStream(network.WithAllowLimitedConn(ctx, \"ping\"), p, ID)\n\tif err != nil {\n\t\treturn pingError(err)\n\t}\n\n\tif err := s.Scope().SetService(ServiceName); err != nil {\n\t\tlog.Debug(\"error attaching stream to ping service\", \"err\", err)\n\t\ts.Reset()\n\t\treturn pingError(err)\n\t}\n\n\tb := make([]byte, 8)\n\tif _, err := rand.Read(b); err != nil {\n\t\tlog.Error(\"failed to get cryptographic random\", \"err\", err)\n\t\ts.Reset()\n\t\treturn pingError(err)\n\t}\n\tra := mrand.New(mrand.NewSource(int64(binary.BigEndian.Uint64(b))))\n\n\tctx, cancel := context.WithCancel(ctx)\n\n\tout := make(chan Result)\n\tgo func() {\n\t\tdefer close(out)\n\t\tdefer cancel()\n\n\t\tfor ctx.Err() == nil {\n\t\t\tvar res Result\n\t\t\tres.RTT, res.Error = ping(s, ra)\n\n\t\t\t// canceled, ignore everything.\n\t\t\tif ctx.Err() != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// No error, record the RTT.\n\t\t\tif res.Error == nil {\n\t\t\t\th.Peerstore().RecordLatency(p, res.RTT)\n\t\t\t}\n\n\t\t\tselect {\n\t\t\tcase out <- res:\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\tcontext.AfterFunc(ctx, func() {\n\t\t// forces the ping to abort.\n\t\ts.Reset()\n\t})\n\n\treturn out\n}\n\nfunc ping(s network.Stream, randReader io.Reader) (time.Duration, error) {\n\tif err := s.Scope().ReserveMemory(2*PingSize, network.ReservationPriorityAlways); err != nil {\n\t\tlog.Debug(\"error reserving memory for ping stream\", \"err\", err)\n\t\ts.Reset()\n\t\treturn 0, err\n\t}\n\tdefer s.Scope().ReleaseMemory(2 * PingSize)\n\n\tbuf := pool.Get(PingSize)\n\tdefer pool.Put(buf)\n\n\tif _, err := io.ReadFull(randReader, buf); err != nil {\n\t\treturn 0, err\n\t}\n\n\tbefore := time.Now()\n\tif _, err := s.Write(buf); err != nil {\n\t\treturn 0, err\n\t}\n\n\trbuf := pool.Get(PingSize)\n\tdefer pool.Put(rbuf)\n\n\tif _, err := io.ReadFull(s, rbuf); err != nil {\n\t\treturn 0, err\n\t}\n\n\tif !bytes.Equal(buf, rbuf) {\n\t\treturn 0, errors.New(\"ping packet was incorrect\")\n\t}\n\n\treturn time.Since(before), nil\n}\n"
  },
  {
    "path": "p2p/protocol/ping/ping_test.go",
    "content": "package ping_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\tbhost \"github.com/libp2p/go-libp2p/p2p/host/basic\"\n\tswarmt \"github.com/libp2p/go-libp2p/p2p/net/swarm/testing\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/ping\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestPing(t *testing.T) {\n\tctx := t.Context()\n\th1, err := bhost.NewHost(swarmt.GenSwarm(t), nil)\n\trequire.NoError(t, err)\n\tdefer h1.Close()\n\th1.Start()\n\th2, err := bhost.NewHost(swarmt.GenSwarm(t), nil)\n\trequire.NoError(t, err)\n\tdefer h2.Close()\n\th2.Start()\n\n\terr = h1.Connect(ctx, peer.AddrInfo{\n\t\tID:    h2.ID(),\n\t\tAddrs: []ma.Multiaddr{h2.Addrs()[0]},\n\t})\n\trequire.NoError(t, err)\n\n\tps1 := ping.NewPingService(h1)\n\tps2 := ping.NewPingService(h2)\n\n\ttestPing(t, ps1, h2.ID())\n\ttestPing(t, ps2, h1.ID())\n}\n\nfunc testPing(t *testing.T, ps *ping.PingService, p peer.ID) {\n\tpctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\tts := ps.Ping(pctx, p)\n\n\tfor range 5 {\n\t\tselect {\n\t\tcase res := <-ts:\n\t\t\trequire.NoError(t, res.Error)\n\t\t\tt.Log(\"ping took: \", res.RTT)\n\t\tcase <-time.After(time.Second * 4):\n\t\t\tt.Fatal(\"failed to receive ping\")\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "p2p/security/insecure/insecure.go",
    "content": "// Package insecure provides an insecure, unencrypted implementation of the SecureConn and SecureTransport interfaces.\n//\n// Not Recommended\npackage insecure\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\n\tci \"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\t\"github.com/libp2p/go-libp2p/core/sec\"\n\t\"github.com/libp2p/go-libp2p/p2p/security/insecure/pb\"\n\n\t\"github.com/libp2p/go-msgio\"\n\n\t\"google.golang.org/protobuf/proto\"\n)\n\n// ID is the multistream-select protocol ID that should be used when identifying\n// this security transport.\nconst ID = \"/plaintext/2.0.0\"\n\n// Transport is a no-op stream security transport. It provides no\n// security and simply mocks the security methods. Identity methods\n// return the local peer's ID and private key, and whatever the remote\n// peer presents as their ID and public key.\n// No authentication of the remote identity is performed.\ntype Transport struct {\n\tid         peer.ID\n\tkey        ci.PrivKey\n\tprotocolID protocol.ID\n}\n\nvar _ sec.SecureTransport = &Transport{}\n\n// NewWithIdentity constructs a new insecure transport. The public key is sent to\n// remote peers. No security is provided.\nfunc NewWithIdentity(protocolID protocol.ID, id peer.ID, key ci.PrivKey) *Transport {\n\treturn &Transport{\n\t\tprotocolID: protocolID,\n\t\tid:         id,\n\t\tkey:        key,\n\t}\n}\n\n// LocalPeer returns the transport's local peer ID.\nfunc (t *Transport) LocalPeer() peer.ID {\n\treturn t.id\n}\n\n// SecureInbound *pretends to secure* an inbound connection to the given peer.\n// It sends the local peer's ID and public key, and receives the same from the remote peer.\n// No validation is performed as to the authenticity or ownership of the provided public key,\n// and the key exchange provides no security.\n//\n// SecureInbound may fail if the remote peer sends an ID and public key that are inconsistent\n// with each other, or if a network error occurs during the ID exchange.\nfunc (t *Transport) SecureInbound(_ context.Context, insecure net.Conn, p peer.ID) (sec.SecureConn, error) {\n\tconn := &Conn{\n\t\tConn:        insecure,\n\t\tlocal:       t.id,\n\t\tlocalPubKey: t.key.GetPublic(),\n\t}\n\n\tif err := conn.runHandshakeSync(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif p != \"\" && p != conn.remote {\n\t\treturn nil, fmt.Errorf(\"remote peer sent unexpected peer ID. expected=%s received=%s\", p, conn.remote)\n\t}\n\n\treturn conn, nil\n}\n\n// SecureOutbound *pretends to secure* an outbound connection to the given peer.\n// It sends the local peer's ID and public key, and receives the same from the remote peer.\n// No validation is performed as to the authenticity or ownership of the provided public key,\n// and the key exchange provides no security.\n//\n// SecureOutbound may fail if the remote peer sends an ID and public key that are inconsistent\n// with each other, or if the ID sent by the remote peer does not match the one dialed. It may\n// also fail if a network error occurs during the ID exchange.\nfunc (t *Transport) SecureOutbound(_ context.Context, insecure net.Conn, p peer.ID) (sec.SecureConn, error) {\n\tconn := &Conn{\n\t\tConn:        insecure,\n\t\tlocal:       t.id,\n\t\tlocalPubKey: t.key.GetPublic(),\n\t}\n\n\tif err := conn.runHandshakeSync(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif p != conn.remote {\n\t\treturn nil, fmt.Errorf(\"remote peer sent unexpected peer ID. expected=%s received=%s\",\n\t\t\tp, conn.remote)\n\t}\n\n\treturn conn, nil\n}\n\nfunc (t *Transport) ID() protocol.ID { return t.protocolID }\n\n// Conn is the connection type returned by the insecure transport.\ntype Conn struct {\n\tnet.Conn\n\n\tlocal, remote             peer.ID\n\tlocalPubKey, remotePubKey ci.PubKey\n}\n\nfunc makeExchangeMessage(pubkey ci.PubKey) (*pb.Exchange, error) {\n\tkeyMsg, err := ci.PublicKeyToProto(pubkey)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tid, err := peer.IDFromPublicKey(pubkey)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &pb.Exchange{\n\t\tId:     []byte(id),\n\t\tPubkey: keyMsg,\n\t}, nil\n}\n\nfunc (ic *Conn) runHandshakeSync() error {\n\t// If we were initialized without keys, behave as in plaintext/1.0.0 (do nothing)\n\tif ic.localPubKey == nil {\n\t\treturn nil\n\t}\n\n\t// Generate an Exchange message\n\tmsg, err := makeExchangeMessage(ic.localPubKey)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Send our Exchange and read theirs\n\tremoteMsg, err := readWriteMsg(ic.Conn, msg)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Pull remote ID and public key from message\n\tremotePubkey, err := ci.PublicKeyFromProto(remoteMsg.Pubkey)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tremoteID, err := peer.IDFromBytes(remoteMsg.Id)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Validate that ID matches public key\n\tif !remoteID.MatchesPublicKey(remotePubkey) {\n\t\tcalculatedID, _ := peer.IDFromPublicKey(remotePubkey)\n\t\treturn fmt.Errorf(\"remote peer id does not match public key. id=%s calculated_id=%s\",\n\t\t\tremoteID, calculatedID)\n\t}\n\n\t// Add remote ID and key to conn state\n\tic.remotePubKey = remotePubkey\n\tic.remote = remoteID\n\treturn nil\n}\n\n// read and write a message at the same time.\nfunc readWriteMsg(rw io.ReadWriter, out *pb.Exchange) (*pb.Exchange, error) {\n\tconst maxMessageSize = 1 << 16\n\n\toutBytes, err := proto.Marshal(out)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\twresult := make(chan error)\n\tgo func() {\n\t\tw := msgio.NewVarintWriter(rw)\n\t\twresult <- w.WriteMsg(outBytes)\n\t}()\n\n\tr := msgio.NewVarintReaderSize(rw, maxMessageSize)\n\tb, err1 := r.ReadMsg()\n\n\t// Always wait for the read to finish.\n\terr2 := <-wresult\n\n\tif err1 != nil {\n\t\treturn nil, err1\n\t}\n\tif err2 != nil {\n\t\tr.ReleaseMsg(b)\n\t\treturn nil, err2\n\t}\n\tinMsg := new(pb.Exchange)\n\terr = proto.Unmarshal(b, inMsg)\n\treturn inMsg, err\n}\n\n// LocalPeer returns the local peer ID.\nfunc (ic *Conn) LocalPeer() peer.ID {\n\treturn ic.local\n}\n\n// RemotePeer returns the remote peer ID if we initiated the dial. Otherwise, it\n// returns \"\" (because this connection isn't actually secure).\nfunc (ic *Conn) RemotePeer() peer.ID {\n\treturn ic.remote\n}\n\n// RemotePublicKey returns whatever public key was given by the remote peer.\n// Note that no verification of ownership is done, as this connection is not secure.\nfunc (ic *Conn) RemotePublicKey() ci.PubKey {\n\treturn ic.remotePubKey\n}\n\n// ConnState returns the security connection's state information.\nfunc (ic *Conn) ConnState() network.ConnectionState {\n\treturn network.ConnectionState{}\n}\n\nvar _ sec.SecureTransport = (*Transport)(nil)\nvar _ sec.SecureConn = (*Conn)(nil)\n"
  },
  {
    "path": "p2p/security/insecure/insecure_test.go",
    "content": "package insecure\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net\"\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/sec\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\n// Run a set of sessions through the session setup and verification.\nfunc TestConnections(t *testing.T) {\n\tclientTpt := newTestTransport(t, crypto.RSA, 2048)\n\tserverTpt := newTestTransport(t, crypto.Ed25519, 1024)\n\n\tclientConn, serverConn, clientErr, serverErr := connect(t, clientTpt, serverTpt, serverTpt.LocalPeer(), \"\")\n\trequire.NoError(t, clientErr)\n\trequire.NoError(t, serverErr)\n\ttestIDs(t, clientTpt, serverTpt, clientConn, serverConn)\n\ttestKeys(t, clientTpt, serverTpt, clientConn, serverConn)\n\ttestReadWrite(t, clientConn, serverConn)\n}\n\nfunc TestPeerIdMatchInbound(t *testing.T) {\n\tclientTpt := newTestTransport(t, crypto.RSA, 2048)\n\tserverTpt := newTestTransport(t, crypto.Ed25519, 1024)\n\n\tclientConn, serverConn, clientErr, serverErr := connect(t, clientTpt, serverTpt, serverTpt.LocalPeer(), clientTpt.LocalPeer())\n\trequire.NoError(t, clientErr)\n\trequire.NoError(t, serverErr)\n\ttestIDs(t, clientTpt, serverTpt, clientConn, serverConn)\n\ttestKeys(t, clientTpt, serverTpt, clientConn, serverConn)\n\ttestReadWrite(t, clientConn, serverConn)\n}\n\nfunc TestPeerIDMismatchInbound(t *testing.T) {\n\tclientTpt := newTestTransport(t, crypto.RSA, 2048)\n\tserverTpt := newTestTransport(t, crypto.Ed25519, 1024)\n\n\t_, _, _, serverErr := connect(t, clientTpt, serverTpt, serverTpt.LocalPeer(), \"a-random-peer\")\n\trequire.Error(t, serverErr)\n\trequire.Contains(t, serverErr.Error(), \"remote peer sent unexpected peer ID\")\n}\n\nfunc TestPeerIDMismatchOutbound(t *testing.T) {\n\tclientTpt := newTestTransport(t, crypto.RSA, 2048)\n\tserverTpt := newTestTransport(t, crypto.Ed25519, 1024)\n\n\t_, _, clientErr, _ := connect(t, clientTpt, serverTpt, \"a random peer\", \"\")\n\trequire.Error(t, clientErr)\n\trequire.Contains(t, clientErr.Error(), \"remote peer sent unexpected peer ID\")\n}\n\nfunc newTestTransport(t *testing.T, typ, bits int) *Transport {\n\tpriv, pub, err := crypto.GenerateKeyPair(typ, bits)\n\trequire.NoError(t, err)\n\tid, err := peer.IDFromPublicKey(pub)\n\trequire.NoError(t, err)\n\treturn NewWithIdentity(\"/test/1.0.0\", id, priv)\n}\n\n// Create a new pair of connected TCP sockets.\nfunc newConnPair(t *testing.T) (net.Conn, net.Conn) {\n\tlstnr, err := net.Listen(\"tcp\", \"localhost:0\")\n\trequire.NoError(t, err, \"failed to listen\")\n\n\tvar clientErr error\n\tvar client net.Conn\n\tdone := make(chan struct{})\n\n\tgo func() {\n\t\tdefer close(done)\n\t\taddr := lstnr.Addr()\n\t\tclient, clientErr = net.Dial(addr.Network(), addr.String())\n\t}()\n\n\tserver, err := lstnr.Accept()\n\trequire.NoError(t, err, \"failed to accept\")\n\n\t<-done\n\tlstnr.Close()\n\trequire.NoError(t, clientErr, \"failed to connect\")\n\treturn client, server\n}\n\nfunc connect(t *testing.T, clientTpt, serverTpt *Transport, clientExpectsID, serverExpectsID peer.ID) (clientConn sec.SecureConn, serverConn sec.SecureConn, clientErr, serverErr error) {\n\tclient, server := newConnPair(t)\n\n\tdone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(done)\n\t\tclientConn, clientErr = clientTpt.SecureOutbound(context.TODO(), client, clientExpectsID)\n\t}()\n\tserverConn, serverErr = serverTpt.SecureInbound(context.TODO(), server, serverExpectsID)\n\t<-done\n\treturn\n}\n\n// Check the peer IDs\nfunc testIDs(t *testing.T, clientTpt, serverTpt *Transport, clientConn, serverConn sec.SecureConn) {\n\tt.Helper()\n\trequire.Equal(t, clientConn.LocalPeer(), clientTpt.LocalPeer(), \"Client Local Peer ID mismatch.\")\n\trequire.Equal(t, clientConn.RemotePeer(), serverTpt.LocalPeer(), \"Client Remote Peer ID mismatch.\")\n\trequire.Equal(t, clientConn.LocalPeer(), serverConn.RemotePeer(), \"Server Local Peer ID mismatch.\")\n}\n\n// Check the keys\nfunc testKeys(t *testing.T, clientTpt, serverTpt *Transport, clientConn, serverConn sec.SecureConn) {\n\tt.Helper()\n\trequire.True(t, clientConn.RemotePublicKey().Equals(serverTpt.key.GetPublic()), \"client conn key mismatch\")\n\trequire.True(t, serverConn.RemotePublicKey().Equals(clientTpt.key.GetPublic()), \"server conn key mismatch\")\n}\n\n// Check sending and receiving messages\nfunc testReadWrite(t *testing.T, clientConn, serverConn sec.SecureConn) {\n\tbefore := []byte(\"hello world\")\n\t_, err := clientConn.Write(before)\n\trequire.NoError(t, err)\n\n\tafter := make([]byte, len(before))\n\t_, err = io.ReadFull(serverConn, after)\n\trequire.NoError(t, err)\n\trequire.Equal(t, before, after, \"message mismatch\")\n}\n"
  },
  {
    "path": "p2p/security/insecure/pb/plaintext.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.6\n// \tprotoc        v5.29.2\n// source: p2p/security/insecure/pb/plaintext.proto\n\npackage pb\n\nimport (\n\tpb \"github.com/libp2p/go-libp2p/core/crypto/pb\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Exchange struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tId            []byte                 `protobuf:\"bytes,1,opt,name=id\" json:\"id,omitempty\"`\n\tPubkey        *pb.PublicKey          `protobuf:\"bytes,2,opt,name=pubkey\" json:\"pubkey,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Exchange) Reset() {\n\t*x = Exchange{}\n\tmi := &file_p2p_security_insecure_pb_plaintext_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Exchange) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Exchange) ProtoMessage() {}\n\nfunc (x *Exchange) ProtoReflect() protoreflect.Message {\n\tmi := &file_p2p_security_insecure_pb_plaintext_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Exchange.ProtoReflect.Descriptor instead.\nfunc (*Exchange) Descriptor() ([]byte, []int) {\n\treturn file_p2p_security_insecure_pb_plaintext_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Exchange) GetId() []byte {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn nil\n}\n\nfunc (x *Exchange) GetPubkey() *pb.PublicKey {\n\tif x != nil {\n\t\treturn x.Pubkey\n\t}\n\treturn nil\n}\n\nvar File_p2p_security_insecure_pb_plaintext_proto protoreflect.FileDescriptor\n\nconst file_p2p_security_insecure_pb_plaintext_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"(p2p/security/insecure/pb/plaintext.proto\\x12\\fplaintext.pb\\x1a\\x1bcore/crypto/pb/crypto.proto\\\"H\\n\" +\n\t\"\\bExchange\\x12\\x0e\\n\" +\n\t\"\\x02id\\x18\\x01 \\x01(\\fR\\x02id\\x12,\\n\" +\n\t\"\\x06pubkey\\x18\\x02 \\x01(\\v2\\x14.crypto.pb.PublicKeyR\\x06pubkeyB6Z4github.com/libp2p/go-libp2p/p2p/security/insecure/pb\"\n\nvar (\n\tfile_p2p_security_insecure_pb_plaintext_proto_rawDescOnce sync.Once\n\tfile_p2p_security_insecure_pb_plaintext_proto_rawDescData []byte\n)\n\nfunc file_p2p_security_insecure_pb_plaintext_proto_rawDescGZIP() []byte {\n\tfile_p2p_security_insecure_pb_plaintext_proto_rawDescOnce.Do(func() {\n\t\tfile_p2p_security_insecure_pb_plaintext_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_p2p_security_insecure_pb_plaintext_proto_rawDesc), len(file_p2p_security_insecure_pb_plaintext_proto_rawDesc)))\n\t})\n\treturn file_p2p_security_insecure_pb_plaintext_proto_rawDescData\n}\n\nvar file_p2p_security_insecure_pb_plaintext_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_p2p_security_insecure_pb_plaintext_proto_goTypes = []any{\n\t(*Exchange)(nil),     // 0: plaintext.pb.Exchange\n\t(*pb.PublicKey)(nil), // 1: crypto.pb.PublicKey\n}\nvar file_p2p_security_insecure_pb_plaintext_proto_depIdxs = []int32{\n\t1, // 0: plaintext.pb.Exchange.pubkey:type_name -> crypto.pb.PublicKey\n\t1, // [1:1] is the sub-list for method output_type\n\t1, // [1:1] is the sub-list for method input_type\n\t1, // [1:1] is the sub-list for extension type_name\n\t1, // [1:1] is the sub-list for extension extendee\n\t0, // [0:1] is the sub-list for field type_name\n}\n\nfunc init() { file_p2p_security_insecure_pb_plaintext_proto_init() }\nfunc file_p2p_security_insecure_pb_plaintext_proto_init() {\n\tif File_p2p_security_insecure_pb_plaintext_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_p2p_security_insecure_pb_plaintext_proto_rawDesc), len(file_p2p_security_insecure_pb_plaintext_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_p2p_security_insecure_pb_plaintext_proto_goTypes,\n\t\tDependencyIndexes: file_p2p_security_insecure_pb_plaintext_proto_depIdxs,\n\t\tMessageInfos:      file_p2p_security_insecure_pb_plaintext_proto_msgTypes,\n\t}.Build()\n\tFile_p2p_security_insecure_pb_plaintext_proto = out.File\n\tfile_p2p_security_insecure_pb_plaintext_proto_goTypes = nil\n\tfile_p2p_security_insecure_pb_plaintext_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "p2p/security/insecure/pb/plaintext.proto",
    "content": "syntax = \"proto2\";\n\npackage plaintext.pb;\n\nimport \"core/crypto/pb/crypto.proto\";\n\noption go_package = \"github.com/libp2p/go-libp2p/p2p/security/insecure/pb\";\n\nmessage Exchange {\n  optional bytes id = 1;\n  optional crypto.pb.PublicKey pubkey = 2;\n}\n"
  },
  {
    "path": "p2p/security/noise/benchmark_test.go",
    "content": "package noise\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"math/rand\"\n\t\"net\"\n\t\"testing\"\n\t\"time\"\n\n\t\"golang.org/x/crypto/chacha20poly1305\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/sec\"\n)\n\ntype testMode int\n\nconst (\n\treadBufferGtEncMsg testMode = iota\n\treadBufferLtPlainText\n)\n\nvar bcs = map[string]struct {\n\tm testMode\n}{\n\t\"readBuffer > encrypted message\": {\n\t\treadBufferGtEncMsg,\n\t},\n\t\"readBuffer < decrypted plaintext\": {\n\t\treadBufferLtPlainText,\n\t},\n}\n\nfunc makeTransport(b *testing.B) *Transport {\n\tb.Helper()\n\n\tpriv, _, err := crypto.GenerateKeyPair(crypto.Ed25519, 256)\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\ttpt, err := New(ID, priv, nil)\n\tif err != nil {\n\t\tb.Fatalf(\"error constructing transport: %v\", err)\n\t}\n\treturn tpt\n}\n\ntype benchenv struct {\n\t*testing.B\n\n\tinitTpt *Transport\n\trespTpt *Transport\n\trndSrc  rand.Source\n}\n\nfunc setupEnv(b *testing.B) *benchenv {\n\tb.StopTimer()\n\tdefer b.StartTimer()\n\tinitTpt := makeTransport(b)\n\trespTpt := makeTransport(b)\n\n\treturn &benchenv{\n\t\tB:       b,\n\t\tinitTpt: initTpt,\n\t\trespTpt: respTpt,\n\t\trndSrc:  rand.NewSource(42),\n\t}\n}\n\nfunc (b benchenv) connect(stopTimer bool) (*secureSession, *secureSession) {\n\tinitConn, respConn := net.Pipe()\n\n\tif stopTimer {\n\t\tb.StopTimer()\n\t\tdefer b.StartTimer()\n\t}\n\n\tvar initSession sec.SecureConn\n\tvar initErr error\n\tdone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(done)\n\t\tinitSession, initErr = b.initTpt.SecureOutbound(context.TODO(), initConn, b.respTpt.localID)\n\t}()\n\n\trespSession, respErr := b.respTpt.SecureInbound(context.TODO(), respConn, \"\")\n\t<-done\n\n\tif initErr != nil {\n\t\tb.Fatal(initErr)\n\t}\n\n\tif respErr != nil {\n\t\tb.Fatal(respErr)\n\t}\n\n\treturn initSession.(*secureSession), respSession.(*secureSession)\n}\n\nfunc drain(r io.Reader, done chan<- error, writeTo io.Writer) {\n\t_, err := io.Copy(writeTo, r)\n\tdone <- err\n}\n\ntype discardWithBuffer struct {\n\tbuf []byte\n\tio.Writer\n}\n\nfunc (d *discardWithBuffer) ReadFrom(r io.Reader) (n int64, err error) {\n\treadSize := 0\n\tfor {\n\t\treadSize, err = r.Read(d.buf)\n\t\tn += int64(readSize)\n\t\tif err != nil {\n\t\t\tif err == io.EOF {\n\t\t\t\treturn n, nil\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc sink(dst io.WriteCloser, src io.Reader, done chan<- error, buf []byte) {\n\t_, err := io.CopyBuffer(dst, src, buf)\n\tif err != nil {\n\t\tdone <- err\n\t}\n\tdone <- dst.Close()\n}\n\nfunc pipeRandom(src rand.Source, w io.WriteCloser, r io.Reader, n int64, plainTextBuf []byte,\n\twriteTo io.Writer) error {\n\trnd := rand.New(src)\n\tlr := io.LimitReader(rnd, n)\n\n\twriteCh := make(chan error, 1)\n\treadCh := make(chan error, 1)\n\n\tgo sink(w, lr, writeCh, plainTextBuf)\n\tgo drain(r, readCh, writeTo)\n\n\twriteDone := false\n\treadDone := false\n\tfor !(readDone && writeDone) {\n\t\tselect {\n\t\tcase err := <-readCh:\n\t\t\tif err != nil && err != io.EOF {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treadDone = true\n\t\tcase err := <-writeCh:\n\t\t\tif err != nil && err != io.EOF {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\twriteDone = true\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc benchDataTransfer(b *benchenv, dataSize int64, m testMode) {\n\tvar totalBytes int64\n\tvar totalTime time.Duration\n\n\tplainTextBufs := make([][]byte, 61)\n\twriteTos := make(map[int]io.Writer)\n\tfor i := range plainTextBufs {\n\t\tvar rbuf []byte\n\t\t// plaintext will be 2 KB to 62 KB\n\t\tplainTextBufs[i] = make([]byte, (i+2)*1024)\n\t\tswitch m {\n\t\tcase readBufferGtEncMsg:\n\t\t\trbuf = make([]byte, len(plainTextBufs[i])+chacha20poly1305.Overhead+1)\n\t\tcase readBufferLtPlainText:\n\t\t\trbuf = make([]byte, len(plainTextBufs[i])-2)\n\t\t}\n\t\twriteTos[i] = &discardWithBuffer{rbuf, io.Discard}\n\t}\n\n\tb.ResetTimer()\n\tb.ReportAllocs()\n\n\tfor i := 0; i < b.N; i++ {\n\t\tinitSession, respSession := b.connect(true)\n\n\t\tstart := time.Now()\n\n\t\tbufi := i % len(plainTextBufs)\n\t\terr := pipeRandom(b.rndSrc, initSession, respSession, dataSize, plainTextBufs[bufi], writeTos[bufi])\n\t\tif err != nil {\n\t\t\tb.Fatalf(\"error sending random data: %s\", err)\n\t\t}\n\t\telapsed := time.Since(start)\n\t\ttotalTime += elapsed\n\t\ttotalBytes += dataSize\n\t}\n\tbytesPerSec := float64(totalBytes) / totalTime.Seconds()\n\tb.ReportMetric(bytesPerSec, \"bytes/sec\")\n}\n\nfunc BenchmarkTransfer1MB(b *testing.B) {\n\tfor n, bc := range bcs {\n\t\tb.Run(n, func(b *testing.B) {\n\t\t\tbenchDataTransfer(setupEnv(b), 1024*1024, bc.m)\n\t\t})\n\t}\n\n}\n\nfunc BenchmarkTransfer100MB(b *testing.B) {\n\tfor n, bc := range bcs {\n\t\tb.Run(n, func(b *testing.B) {\n\t\t\tbenchDataTransfer(setupEnv(b), 1024*1024*100, bc.m)\n\t\t})\n\t}\n}\n\nfunc BenchmarkTransfer500Mb(b *testing.B) {\n\tfor n, bc := range bcs {\n\t\tb.Run(n, func(b *testing.B) {\n\t\t\tbenchDataTransfer(setupEnv(b), 1024*1024*500, bc.m)\n\t\t})\n\t}\n}\n\nfunc (b benchenv) benchHandshake() {\n\tb.ResetTimer()\n\tb.ReportAllocs()\n\n\tfor i := 0; i < b.N; i++ {\n\t\ti, r := b.connect(false)\n\t\tb.StopTimer()\n\t\terr := i.Close()\n\t\tif err != nil {\n\t\t\tb.Errorf(\"error closing session: %s\", err)\n\t\t}\n\t\terr = r.Close()\n\t\tif err != nil {\n\t\t\tb.Errorf(\"error closing session: %s\", err)\n\t\t}\n\t\tb.StartTimer()\n\t}\n}\n\nfunc BenchmarkHandshakeXX(b *testing.B) {\n\tenv := setupEnv(b)\n\tenv.benchHandshake()\n}\n"
  },
  {
    "path": "p2p/security/noise/crypto.go",
    "content": "package noise\n\nimport (\n\t\"errors\"\n)\n\n// encrypt calls the cipher's encryption. It encrypts the provided plaintext,\n// slice-appending the ciphertext on out.\n//\n// Usually you want to pass a 0-len slice to this method, with enough capacity\n// to accommodate the ciphertext in order to spare allocs.\n//\n// encrypt returns a new slice header, whose len is the length of the resulting\n// ciphertext, including the authentication tag.\n//\n// This method will not allocate if the supplied slice is large enough to\n// accommodate the encrypted data + authentication tag. If so, the returned\n// slice header should be a view of the original slice.\n//\n// With the poly1305 MAC function that noise-libp2p uses, the authentication tag\n// adds an overhead of 16 bytes.\nfunc (s *secureSession) encrypt(out, plaintext []byte) ([]byte, error) {\n\tif s.enc == nil {\n\t\treturn nil, errors.New(\"cannot encrypt, handshake incomplete\")\n\t}\n\treturn s.enc.Encrypt(out, nil, plaintext)\n}\n\n// decrypt calls the cipher's decryption. It decrypts the provided ciphertext,\n// slice-appending the plaintext on out.\n//\n// Usually you want to pass a 0-len slice to this method, with enough capacity\n// to accommodate the plaintext in order to spare allocs.\n//\n// decrypt returns a new slice header, whose len is the length of the resulting\n// plaintext, without the authentication tag.\n//\n// This method will not allocate if the supplied slice is large enough to\n// accommodate the plaintext. If so, the returned slice header should be a view\n// of the original slice.\nfunc (s *secureSession) decrypt(out, ciphertext []byte) ([]byte, error) {\n\tif s.dec == nil {\n\t\treturn nil, errors.New(\"cannot decrypt, handshake incomplete\")\n\t}\n\treturn s.dec.Decrypt(out, nil, ciphertext)\n}\n"
  },
  {
    "path": "p2p/security/noise/crypto_test.go",
    "content": "package noise\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"net\"\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n)\n\nfunc TestEncryptAndDecrypt_InitToResp(t *testing.T) {\n\tinitTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\trespTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\n\tinitConn, respConn := connect(t, initTransport, respTransport)\n\tdefer initConn.Close()\n\tdefer respConn.Close()\n\n\tplaintext := []byte(\"helloworld\")\n\tciphertext, err := initConn.encrypt(nil, plaintext)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tresult, err := respConn.decrypt(nil, ciphertext)\n\tif !bytes.Equal(plaintext, result) {\n\t\tt.Fatalf(\"got %x expected %x\", result, plaintext)\n\t} else if err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tplaintext = []byte(\"goodbye\")\n\tciphertext, err = initConn.encrypt(nil, plaintext)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tresult, err = respConn.decrypt(nil, ciphertext)\n\tif !bytes.Equal(plaintext, result) {\n\t\tt.Fatalf(\"got %x expected %x\", result, plaintext)\n\t} else if err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestEncryptAndDecrypt_RespToInit(t *testing.T) {\n\tinitTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\trespTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\n\tinitConn, respConn := connect(t, initTransport, respTransport)\n\tdefer initConn.Close()\n\tdefer respConn.Close()\n\n\tplaintext := []byte(\"helloworld\")\n\tciphertext, err := respConn.encrypt(nil, plaintext)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tresult, err := initConn.decrypt(nil, ciphertext)\n\tif !bytes.Equal(plaintext, result) {\n\t\tt.Fatalf(\"got %x expected %x\", result, plaintext)\n\t} else if err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestCryptoFailsIfCiphertextIsAltered(t *testing.T) {\n\tinitTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\trespTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\n\tinitConn, respConn := connect(t, initTransport, respTransport)\n\tdefer initConn.Close()\n\tdefer respConn.Close()\n\n\tplaintext := []byte(\"helloworld\")\n\tciphertext, err := respConn.encrypt(nil, plaintext)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tciphertext[0] = ^ciphertext[0]\n\n\t_, err = initConn.decrypt(nil, ciphertext)\n\tif err == nil {\n\t\tt.Fatal(\"expected decryption to fail when ciphertext altered\")\n\t}\n}\n\nfunc TestCryptoFailsIfHandshakeIncomplete(t *testing.T) {\n\tinitTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\tinit, resp := net.Pipe()\n\t_ = resp.Close()\n\n\tsession, _ := newSecureSession(initTransport, context.TODO(), init, \"remote-peer\", nil, nil, nil, true, true)\n\t_, err := session.encrypt(nil, []byte(\"hi\"))\n\tif err == nil {\n\t\tt.Error(\"expected encryption error when handshake incomplete\")\n\t}\n\t_, err = session.decrypt(nil, []byte(\"it's a secret\"))\n\tif err == nil {\n\t\tt.Error(\"expected decryption error when handshake incomplete\")\n\t}\n}\n"
  },
  {
    "path": "p2p/security/noise/handshake.go",
    "content": "package noise\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"os\"\n\t\"runtime/debug\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/sec\"\n\t\"github.com/libp2p/go-libp2p/p2p/security/noise/pb\"\n\n\t\"github.com/flynn/noise\"\n\tpool \"github.com/libp2p/go-buffer-pool\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\n// payloadSigPrefix is prepended to our Noise static key before signing with\n// our libp2p identity key.\nconst payloadSigPrefix = \"noise-libp2p-static-key:\"\n\n// All noise session share a fixed cipher suite\nvar cipherSuite = noise.NewCipherSuite(noise.DH25519, noise.CipherChaChaPoly, noise.HashSHA256)\n\n// runHandshake exchanges handshake messages with the remote peer to establish\n// a noise-libp2p session. It blocks until the handshake completes or fails.\nfunc (s *secureSession) runHandshake(ctx context.Context) (err error) {\n\tdefer func() {\n\t\tif rerr := recover(); rerr != nil {\n\t\t\tfmt.Fprintf(os.Stderr, \"caught panic: %s\\n%s\\n\", rerr, debug.Stack())\n\t\t\terr = fmt.Errorf(\"panic in Noise handshake: %s\", rerr)\n\t\t}\n\t}()\n\n\tkp, err := noise.DH25519.GenerateKeypair(rand.Reader)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error generating static keypair: %w\", err)\n\t}\n\n\tcfg := noise.Config{\n\t\tCipherSuite:   cipherSuite,\n\t\tPattern:       noise.HandshakeXX,\n\t\tInitiator:     s.initiator,\n\t\tStaticKeypair: kp,\n\t\tPrologue:      s.prologue,\n\t}\n\n\ths, err := noise.NewHandshakeState(cfg)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error initializing handshake state: %w\", err)\n\t}\n\n\t// set a deadline to complete the handshake, if one has been supplied.\n\t// clear it after we're done.\n\tif deadline, ok := ctx.Deadline(); ok {\n\t\tif err := s.SetDeadline(deadline); err == nil {\n\t\t\t// schedule the deadline removal once we're done handshaking.\n\t\t\tdefer s.SetDeadline(time.Time{})\n\t\t}\n\t}\n\n\t// We can re-use this buffer for all handshake messages.\n\thbuf := pool.Get(2 << 10)\n\tdefer pool.Put(hbuf)\n\n\tif s.initiator {\n\t\t// stage 0 //\n\t\t// Handshake Msg Len = len(DH ephemeral key)\n\t\tif err := s.sendHandshakeMessage(hs, nil, hbuf); err != nil {\n\t\t\treturn fmt.Errorf(\"error sending handshake message: %w\", err)\n\t\t}\n\n\t\t// stage 1 //\n\t\tplaintext, err := s.readHandshakeMessage(hs)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error reading handshake message: %w\", err)\n\t\t}\n\t\trcvdEd, err := s.handleRemoteHandshakePayload(plaintext, hs.PeerStatic())\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif s.initiatorEarlyDataHandler != nil {\n\t\t\tif err := s.initiatorEarlyDataHandler.Received(ctx, s.insecureConn, rcvdEd); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\t// stage 2 //\n\t\t// Handshake Msg Len = len(DHT static key) +  MAC(static key is encrypted) + len(Payload) + MAC(payload is encrypted)\n\t\tvar ed *pb.NoiseExtensions\n\t\tif s.initiatorEarlyDataHandler != nil {\n\t\t\ted = s.initiatorEarlyDataHandler.Send(ctx, s.insecureConn, s.remoteID)\n\t\t}\n\t\tpayload, err := s.generateHandshakePayload(kp, ed)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := s.sendHandshakeMessage(hs, payload, hbuf); err != nil {\n\t\t\treturn fmt.Errorf(\"error sending handshake message: %w\", err)\n\t\t}\n\t\treturn nil\n\t} else {\n\t\t// stage 0 //\n\t\tif _, err := s.readHandshakeMessage(hs); err != nil {\n\t\t\treturn fmt.Errorf(\"error reading handshake message: %w\", err)\n\t\t}\n\n\t\t// stage 1 //\n\t\t// Handshake Msg Len = len(DH ephemeral key) + len(DHT static key) +  MAC(static key is encrypted) + len(Payload) +\n\t\t// MAC(payload is encrypted)\n\t\tvar ed *pb.NoiseExtensions\n\t\tif s.responderEarlyDataHandler != nil {\n\t\t\ted = s.responderEarlyDataHandler.Send(ctx, s.insecureConn, s.remoteID)\n\t\t}\n\t\tpayload, err := s.generateHandshakePayload(kp, ed)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := s.sendHandshakeMessage(hs, payload, hbuf); err != nil {\n\t\t\treturn fmt.Errorf(\"error sending handshake message: %w\", err)\n\t\t}\n\n\t\t// stage 2 //\n\t\tplaintext, err := s.readHandshakeMessage(hs)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error reading handshake message: %w\", err)\n\t\t}\n\t\trcvdEd, err := s.handleRemoteHandshakePayload(plaintext, hs.PeerStatic())\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif s.responderEarlyDataHandler != nil {\n\t\t\tif err := s.responderEarlyDataHandler.Received(ctx, s.insecureConn, rcvdEd); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// setCipherStates sets the initial cipher states that will be used to protect\n// traffic after the handshake.\n//\n// It is called when the final handshake message is processed by\n// either sendHandshakeMessage or readHandshakeMessage.\nfunc (s *secureSession) setCipherStates(cs1, cs2 *noise.CipherState) {\n\tif s.initiator {\n\t\ts.enc = cs1\n\t\ts.dec = cs2\n\t} else {\n\t\ts.enc = cs2\n\t\ts.dec = cs1\n\t}\n}\n\n// sendHandshakeMessage sends the next handshake message in the sequence.\n//\n// If payload is non-empty, it will be included in the handshake message.\n// If this is the final message in the sequence, calls setCipherStates\n// to initialize cipher states.\nfunc (s *secureSession) sendHandshakeMessage(hs *noise.HandshakeState, payload []byte, hbuf []byte) error {\n\t// the first two bytes will be the length of the noise handshake message.\n\tbz, cs1, cs2, err := hs.WriteMessage(hbuf[:LengthPrefixLength], payload)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// bz will also include the length prefix as we passed a slice of LengthPrefixLength length\n\t// to hs.Write().\n\tbinary.BigEndian.PutUint16(bz, uint16(len(bz)-LengthPrefixLength))\n\n\t_, err = s.writeMsgInsecure(bz)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif cs1 != nil && cs2 != nil {\n\t\ts.setCipherStates(cs1, cs2)\n\t}\n\treturn nil\n}\n\n// readHandshakeMessage reads a message from the insecure conn and tries to\n// process it as the expected next message in the handshake sequence.\n//\n// If the message contains a payload, it will be decrypted and returned.\n//\n// If this is the final message in the sequence, it calls setCipherStates\n// to initialize cipher states.\nfunc (s *secureSession) readHandshakeMessage(hs *noise.HandshakeState) ([]byte, error) {\n\tl, err := s.readNextInsecureMsgLen()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tbuf := pool.Get(l)\n\tdefer pool.Put(buf)\n\n\tif err := s.readNextMsgInsecure(buf); err != nil {\n\t\treturn nil, err\n\t}\n\n\tmsg, cs1, cs2, err := hs.ReadMessage(nil, buf)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif cs1 != nil && cs2 != nil {\n\t\ts.setCipherStates(cs1, cs2)\n\t}\n\treturn msg, nil\n}\n\n// generateHandshakePayload creates a libp2p handshake payload with a\n// signature of our static noise key.\nfunc (s *secureSession) generateHandshakePayload(localStatic noise.DHKey, ext *pb.NoiseExtensions) ([]byte, error) {\n\t// obtain the public key from the handshake session, so we can sign it with\n\t// our libp2p secret key.\n\tlocalKeyRaw, err := crypto.MarshalPublicKey(s.LocalPublicKey())\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error serializing libp2p identity key: %w\", err)\n\t}\n\n\t// prepare payload to sign; perform signature.\n\ttoSign := append([]byte(payloadSigPrefix), localStatic.Public...)\n\tsignedPayload, err := s.localKey.Sign(toSign)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error sigining handshake payload: %w\", err)\n\t}\n\n\t// create payload\n\tpayloadEnc, err := proto.Marshal(&pb.NoiseHandshakePayload{\n\t\tIdentityKey: localKeyRaw,\n\t\tIdentitySig: signedPayload,\n\t\tExtensions:  ext,\n\t})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error marshaling handshake payload: %w\", err)\n\t}\n\treturn payloadEnc, nil\n}\n\n// handleRemoteHandshakePayload unmarshals the handshake payload object sent\n// by the remote peer and validates the signature against the peer's static Noise key.\n// It returns the data attached to the payload.\nfunc (s *secureSession) handleRemoteHandshakePayload(payload []byte, remoteStatic []byte) (*pb.NoiseExtensions, error) {\n\t// unmarshal payload\n\tnhp := new(pb.NoiseHandshakePayload)\n\terr := proto.Unmarshal(payload, nhp)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error unmarshaling remote handshake payload: %w\", err)\n\t}\n\n\t// unpack remote peer's public libp2p key\n\tremotePubKey, err := crypto.UnmarshalPublicKey(nhp.GetIdentityKey())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tid, err := peer.IDFromPublicKey(remotePubKey)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// check the peer ID if enabled\n\tif s.checkPeerID && s.remoteID != id {\n\t\treturn nil, sec.ErrPeerIDMismatch{Expected: s.remoteID, Actual: id}\n\t}\n\n\t// verify payload is signed by asserted remote libp2p key.\n\tsig := nhp.GetIdentitySig()\n\tmsg := append([]byte(payloadSigPrefix), remoteStatic...)\n\tok, err := remotePubKey.Verify(msg, sig)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error verifying signature: %w\", err)\n\t} else if !ok {\n\t\treturn nil, fmt.Errorf(\"handshake signature invalid\")\n\t}\n\n\t// set remote peer key and id\n\ts.remoteID = id\n\ts.remoteKey = remotePubKey\n\treturn nhp.Extensions, nil\n}\n"
  },
  {
    "path": "p2p/security/noise/pb/payload.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.6\n// \tprotoc        v5.29.2\n// source: p2p/security/noise/pb/payload.proto\n\npackage pb\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype NoiseExtensions struct {\n\tstate                  protoimpl.MessageState `protogen:\"open.v1\"`\n\tWebtransportCerthashes [][]byte               `protobuf:\"bytes,1,rep,name=webtransport_certhashes,json=webtransportCerthashes\" json:\"webtransport_certhashes,omitempty\"`\n\tStreamMuxers           []string               `protobuf:\"bytes,2,rep,name=stream_muxers,json=streamMuxers\" json:\"stream_muxers,omitempty\"`\n\tunknownFields          protoimpl.UnknownFields\n\tsizeCache              protoimpl.SizeCache\n}\n\nfunc (x *NoiseExtensions) Reset() {\n\t*x = NoiseExtensions{}\n\tmi := &file_p2p_security_noise_pb_payload_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *NoiseExtensions) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*NoiseExtensions) ProtoMessage() {}\n\nfunc (x *NoiseExtensions) ProtoReflect() protoreflect.Message {\n\tmi := &file_p2p_security_noise_pb_payload_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use NoiseExtensions.ProtoReflect.Descriptor instead.\nfunc (*NoiseExtensions) Descriptor() ([]byte, []int) {\n\treturn file_p2p_security_noise_pb_payload_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *NoiseExtensions) GetWebtransportCerthashes() [][]byte {\n\tif x != nil {\n\t\treturn x.WebtransportCerthashes\n\t}\n\treturn nil\n}\n\nfunc (x *NoiseExtensions) GetStreamMuxers() []string {\n\tif x != nil {\n\t\treturn x.StreamMuxers\n\t}\n\treturn nil\n}\n\ntype NoiseHandshakePayload struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tIdentityKey   []byte                 `protobuf:\"bytes,1,opt,name=identity_key,json=identityKey\" json:\"identity_key,omitempty\"`\n\tIdentitySig   []byte                 `protobuf:\"bytes,2,opt,name=identity_sig,json=identitySig\" json:\"identity_sig,omitempty\"`\n\tExtensions    *NoiseExtensions       `protobuf:\"bytes,4,opt,name=extensions\" json:\"extensions,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *NoiseHandshakePayload) Reset() {\n\t*x = NoiseHandshakePayload{}\n\tmi := &file_p2p_security_noise_pb_payload_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *NoiseHandshakePayload) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*NoiseHandshakePayload) ProtoMessage() {}\n\nfunc (x *NoiseHandshakePayload) ProtoReflect() protoreflect.Message {\n\tmi := &file_p2p_security_noise_pb_payload_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use NoiseHandshakePayload.ProtoReflect.Descriptor instead.\nfunc (*NoiseHandshakePayload) Descriptor() ([]byte, []int) {\n\treturn file_p2p_security_noise_pb_payload_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *NoiseHandshakePayload) GetIdentityKey() []byte {\n\tif x != nil {\n\t\treturn x.IdentityKey\n\t}\n\treturn nil\n}\n\nfunc (x *NoiseHandshakePayload) GetIdentitySig() []byte {\n\tif x != nil {\n\t\treturn x.IdentitySig\n\t}\n\treturn nil\n}\n\nfunc (x *NoiseHandshakePayload) GetExtensions() *NoiseExtensions {\n\tif x != nil {\n\t\treturn x.Extensions\n\t}\n\treturn nil\n}\n\nvar File_p2p_security_noise_pb_payload_proto protoreflect.FileDescriptor\n\nconst file_p2p_security_noise_pb_payload_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"#p2p/security/noise/pb/payload.proto\\x12\\x02pb\\\"o\\n\" +\n\t\"\\x0fNoiseExtensions\\x127\\n\" +\n\t\"\\x17webtransport_certhashes\\x18\\x01 \\x03(\\fR\\x16webtransportCerthashes\\x12#\\n\" +\n\t\"\\rstream_muxers\\x18\\x02 \\x03(\\tR\\fstreamMuxers\\\"\\x92\\x01\\n\" +\n\t\"\\x15NoiseHandshakePayload\\x12!\\n\" +\n\t\"\\fidentity_key\\x18\\x01 \\x01(\\fR\\videntityKey\\x12!\\n\" +\n\t\"\\fidentity_sig\\x18\\x02 \\x01(\\fR\\videntitySig\\x123\\n\" +\n\t\"\\n\" +\n\t\"extensions\\x18\\x04 \\x01(\\v2\\x13.pb.NoiseExtensionsR\\n\" +\n\t\"extensionsB3Z1github.com/libp2p/go-libp2p/p2p/security/noise/pb\"\n\nvar (\n\tfile_p2p_security_noise_pb_payload_proto_rawDescOnce sync.Once\n\tfile_p2p_security_noise_pb_payload_proto_rawDescData []byte\n)\n\nfunc file_p2p_security_noise_pb_payload_proto_rawDescGZIP() []byte {\n\tfile_p2p_security_noise_pb_payload_proto_rawDescOnce.Do(func() {\n\t\tfile_p2p_security_noise_pb_payload_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_p2p_security_noise_pb_payload_proto_rawDesc), len(file_p2p_security_noise_pb_payload_proto_rawDesc)))\n\t})\n\treturn file_p2p_security_noise_pb_payload_proto_rawDescData\n}\n\nvar file_p2p_security_noise_pb_payload_proto_msgTypes = make([]protoimpl.MessageInfo, 2)\nvar file_p2p_security_noise_pb_payload_proto_goTypes = []any{\n\t(*NoiseExtensions)(nil),       // 0: pb.NoiseExtensions\n\t(*NoiseHandshakePayload)(nil), // 1: pb.NoiseHandshakePayload\n}\nvar file_p2p_security_noise_pb_payload_proto_depIdxs = []int32{\n\t0, // 0: pb.NoiseHandshakePayload.extensions:type_name -> pb.NoiseExtensions\n\t1, // [1:1] is the sub-list for method output_type\n\t1, // [1:1] is the sub-list for method input_type\n\t1, // [1:1] is the sub-list for extension type_name\n\t1, // [1:1] is the sub-list for extension extendee\n\t0, // [0:1] is the sub-list for field type_name\n}\n\nfunc init() { file_p2p_security_noise_pb_payload_proto_init() }\nfunc file_p2p_security_noise_pb_payload_proto_init() {\n\tif File_p2p_security_noise_pb_payload_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_p2p_security_noise_pb_payload_proto_rawDesc), len(file_p2p_security_noise_pb_payload_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   2,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_p2p_security_noise_pb_payload_proto_goTypes,\n\t\tDependencyIndexes: file_p2p_security_noise_pb_payload_proto_depIdxs,\n\t\tMessageInfos:      file_p2p_security_noise_pb_payload_proto_msgTypes,\n\t}.Build()\n\tFile_p2p_security_noise_pb_payload_proto = out.File\n\tfile_p2p_security_noise_pb_payload_proto_goTypes = nil\n\tfile_p2p_security_noise_pb_payload_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "p2p/security/noise/pb/payload.proto",
    "content": "syntax = \"proto2\";\npackage pb;\n\noption go_package = \"github.com/libp2p/go-libp2p/p2p/security/noise/pb\";\n\nmessage NoiseExtensions {\n\trepeated bytes webtransport_certhashes = 1;\n\trepeated string stream_muxers = 2;\n}\n\nmessage NoiseHandshakePayload {\n\toptional bytes identity_key = 1;\n\toptional bytes identity_sig = 2;\n\toptional NoiseExtensions extensions = 4;\n}\n"
  },
  {
    "path": "p2p/security/noise/rw.go",
    "content": "package noise\n\nimport (\n\t\"encoding/binary\"\n\t\"io\"\n\n\tpool \"github.com/libp2p/go-buffer-pool\"\n\t\"golang.org/x/crypto/chacha20poly1305\"\n)\n\n// MaxTransportMsgLength is the Noise-imposed maximum transport message length,\n// inclusive of the MAC size (16 bytes, Poly1305 for noise-libp2p).\nconst MaxTransportMsgLength = 0xffff\n\n// MaxPlaintextLength is the maximum payload size. It is MaxTransportMsgLength\n// minus the MAC size. Payloads over this size will be automatically chunked.\nconst MaxPlaintextLength = MaxTransportMsgLength - chacha20poly1305.Overhead\n\n// LengthPrefixLength is the length of the length prefix itself, which precedes\n// all transport messages in order to delimit them. In bytes.\nconst LengthPrefixLength = 2\n\n// Read reads from the secure connection, returning plaintext data in `buf`.\n//\n// Honours io.Reader in terms of behaviour.\nfunc (s *secureSession) Read(buf []byte) (int, error) {\n\ts.readLock.Lock()\n\tdefer s.readLock.Unlock()\n\n\t// 1. If we have queued received bytes:\n\t//   1a. If len(buf) < len(queued), saturate buf, update seek pointer, return.\n\t//   1b. If len(buf) >= len(queued), copy remaining to buf, release queued buffer back into pool, return.\n\t//\n\t// 2. Else, read the next message off the wire; next_len is length prefix.\n\t//   2a. If len(buf) >= next_len, copy the message to input buffer (zero-alloc path), and return.\n\t//   2b. If len(buf) >= (next_len - length of Authentication Tag), get buffer from pool, read encrypted message into it.\n\t//       decrypt message directly into the input buffer and return the buffer obtained from the pool.\n\t//   2c. If len(buf) < next_len, obtain buffer from pool, copy entire message into it, saturate buf, update seek pointer.\n\tif s.qbuf != nil {\n\t\t// we have queued bytes; copy as much as we can.\n\t\tcopied := copy(buf, s.qbuf[s.qseek:])\n\t\ts.qseek += copied\n\t\tif s.qseek == len(s.qbuf) {\n\t\t\t// queued buffer is now empty, reset and release.\n\t\t\tpool.Put(s.qbuf)\n\t\t\ts.qseek, s.qbuf = 0, nil\n\t\t}\n\t\treturn copied, nil\n\t}\n\n\t// length of the next encrypted message.\n\tnextMsgLen, err := s.readNextInsecureMsgLen()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\t// If the buffer is atleast as big as the encrypted message size,\n\t// we can read AND decrypt in place.\n\tif len(buf) >= nextMsgLen {\n\t\tif err := s.readNextMsgInsecure(buf[:nextMsgLen]); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\n\t\tdbuf, err := s.decrypt(buf[:0], buf[:nextMsgLen])\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\n\t\treturn len(dbuf), nil\n\t}\n\n\t// otherwise, we get a buffer from the pool so we can read the message into it\n\t// and then decrypt in place, since we're retaining the buffer (or a view thereof).\n\tcbuf := pool.Get(nextMsgLen)\n\tif err := s.readNextMsgInsecure(cbuf); err != nil {\n\t\treturn 0, err\n\t}\n\n\tif s.qbuf, err = s.decrypt(cbuf[:0], cbuf); err != nil {\n\t\treturn 0, err\n\t}\n\n\t// copy as many bytes as we can; update seek pointer.\n\ts.qseek = copy(buf, s.qbuf)\n\n\treturn s.qseek, nil\n}\n\n// Write encrypts the plaintext `in` data and sends it on the\n// secure connection.\nfunc (s *secureSession) Write(data []byte) (int, error) {\n\ts.writeLock.Lock()\n\tdefer s.writeLock.Unlock()\n\n\tvar (\n\t\twritten int\n\t\tcbuf    []byte\n\t\ttotal   = len(data)\n\t)\n\n\tif total < MaxPlaintextLength {\n\t\tcbuf = pool.Get(total + chacha20poly1305.Overhead + LengthPrefixLength)\n\t} else {\n\t\tcbuf = pool.Get(MaxTransportMsgLength + LengthPrefixLength)\n\t}\n\n\tdefer pool.Put(cbuf)\n\n\tfor written < total {\n\t\tend := min(written+MaxPlaintextLength, total)\n\n\t\tb, err := s.encrypt(cbuf[:LengthPrefixLength], data[written:end])\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\n\t\tbinary.BigEndian.PutUint16(b, uint16(len(b)-LengthPrefixLength))\n\n\t\t_, err = s.writeMsgInsecure(b)\n\t\tif err != nil {\n\t\t\treturn written, err\n\t\t}\n\t\twritten = end\n\t}\n\treturn written, nil\n}\n\n// readNextInsecureMsgLen reads the length of the next message on the insecureConn channel.\nfunc (s *secureSession) readNextInsecureMsgLen() (int, error) {\n\t_, err := io.ReadFull(s.insecureReader, s.rlen[:])\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn int(binary.BigEndian.Uint16(s.rlen[:])), err\n}\n\n// readNextMsgInsecure tries to read exactly len(buf) bytes into buf from\n// the insecureConn channel and returns the error, if any.\n// Ideally, for reading a message, you'd first want to call `readNextInsecureMsgLen`\n// to determine the size of the next message to be read from the insecureConn channel and then call\n// this function with a buffer of exactly that size.\nfunc (s *secureSession) readNextMsgInsecure(buf []byte) error {\n\t_, err := io.ReadFull(s.insecureReader, buf)\n\treturn err\n}\n\n// writeMsgInsecure writes to the insecureConn conn.\n// data will be prefixed with its length in bytes, written as a 16-bit uint in network order.\nfunc (s *secureSession) writeMsgInsecure(data []byte) (int, error) {\n\treturn s.insecureConn.Write(data)\n}\n"
  },
  {
    "path": "p2p/security/noise/session.go",
    "content": "package noise\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"net\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/flynn/noise\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n)\n\ntype secureSession struct {\n\tinitiator   bool\n\tcheckPeerID bool\n\n\tlocalID   peer.ID\n\tlocalKey  crypto.PrivKey\n\tremoteID  peer.ID\n\tremoteKey crypto.PubKey\n\n\treadLock  sync.Mutex\n\twriteLock sync.Mutex\n\n\tinsecureConn   net.Conn\n\tinsecureReader *bufio.Reader // to cushion io read syscalls\n\t// we don't buffer writes to avoid introducing latency; optimisation possible. // TODO revisit\n\n\tqseek int     // queued bytes seek value.\n\tqbuf  []byte  // queued bytes buffer.\n\trlen  [2]byte // work buffer to read in the incoming message length.\n\n\tenc *noise.CipherState\n\tdec *noise.CipherState\n\n\t// noise prologue\n\tprologue []byte\n\n\tinitiatorEarlyDataHandler, responderEarlyDataHandler EarlyDataHandler\n\n\t// ConnectionState holds state information releated to the secureSession entity.\n\tconnectionState network.ConnectionState\n}\n\n// newSecureSession creates a Noise session over the given insecureConn Conn, using\n// the libp2p identity keypair from the given Transport.\nfunc newSecureSession(tpt *Transport, ctx context.Context, insecure net.Conn, remote peer.ID, prologue []byte, initiatorEDH, responderEDH EarlyDataHandler, initiator, checkPeerID bool) (*secureSession, error) {\n\ts := &secureSession{\n\t\tinsecureConn:              insecure,\n\t\tinsecureReader:            bufio.NewReader(insecure),\n\t\tinitiator:                 initiator,\n\t\tlocalID:                   tpt.localID,\n\t\tlocalKey:                  tpt.privateKey,\n\t\tremoteID:                  remote,\n\t\tprologue:                  prologue,\n\t\tinitiatorEarlyDataHandler: initiatorEDH,\n\t\tresponderEarlyDataHandler: responderEDH,\n\t\tcheckPeerID:               checkPeerID,\n\t}\n\n\t// the go-routine we create to run the handshake will\n\t// write the result of the handshake to the respCh.\n\trespCh := make(chan error, 1)\n\tgo func() {\n\t\trespCh <- s.runHandshake(ctx)\n\t}()\n\n\tselect {\n\tcase err := <-respCh:\n\t\tif err != nil {\n\t\t\t_ = s.insecureConn.Close()\n\t\t}\n\t\treturn s, err\n\n\tcase <-ctx.Done():\n\t\t// If the context has been cancelled, we close the underlying connection.\n\t\t// We then wait for the handshake to return because of the first error it encounters\n\t\t// so we don't return without cleaning up the go-routine.\n\t\t_ = s.insecureConn.Close()\n\t\t<-respCh\n\t\treturn nil, ctx.Err()\n\t}\n}\n\nfunc (s *secureSession) LocalAddr() net.Addr {\n\treturn s.insecureConn.LocalAddr()\n}\n\nfunc (s *secureSession) LocalPeer() peer.ID {\n\treturn s.localID\n}\n\nfunc (s *secureSession) LocalPublicKey() crypto.PubKey {\n\treturn s.localKey.GetPublic()\n}\n\nfunc (s *secureSession) RemoteAddr() net.Addr {\n\treturn s.insecureConn.RemoteAddr()\n}\n\nfunc (s *secureSession) RemotePeer() peer.ID {\n\treturn s.remoteID\n}\n\nfunc (s *secureSession) RemotePublicKey() crypto.PubKey {\n\treturn s.remoteKey\n}\n\nfunc (s *secureSession) ConnState() network.ConnectionState {\n\treturn s.connectionState\n}\n\nfunc (s *secureSession) SetDeadline(t time.Time) error {\n\treturn s.insecureConn.SetDeadline(t)\n}\n\nfunc (s *secureSession) SetReadDeadline(t time.Time) error {\n\treturn s.insecureConn.SetReadDeadline(t)\n}\n\nfunc (s *secureSession) SetWriteDeadline(t time.Time) error {\n\treturn s.insecureConn.SetWriteDeadline(t)\n}\n\nfunc (s *secureSession) Close() error {\n\treturn s.insecureConn.Close()\n}\n\nfunc SessionWithConnState(s *secureSession, muxer protocol.ID) *secureSession {\n\tif s != nil {\n\t\ts.connectionState.StreamMultiplexer = muxer\n\t\ts.connectionState.UsedEarlyMuxerNegotiation = muxer != \"\"\n\t}\n\treturn s\n}\n"
  },
  {
    "path": "p2p/security/noise/session_test.go",
    "content": "package noise\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestContextCancellationRespected(t *testing.T) {\n\tinitTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\trespTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\n\tinit, resp := newConnPair(t)\n\tdefer init.Close()\n\tdefer resp.Close()\n\n\tctx, cancel := context.WithCancel(context.Background())\n\tcancel()\n\n\t_, err := initTransport.SecureOutbound(ctx, init, respTransport.localID)\n\trequire.Error(t, err)\n\trequire.Equal(t, ctx.Err(), err)\n}\n"
  },
  {
    "path": "p2p/security/noise/session_transport.go",
    "content": "package noise\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\t\"github.com/libp2p/go-libp2p/core/sec\"\n\t\"github.com/libp2p/go-libp2p/p2p/canonicallog\"\n\t\"github.com/libp2p/go-libp2p/p2p/security/noise/pb\"\n\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\ntype SessionOption = func(*SessionTransport) error\n\n// Prologue sets a prologue for the Noise session.\n// The handshake will only complete successfully if both parties set the same prologue.\n// See https://noiseprotocol.org/noise.html#prologue for details.\nfunc Prologue(prologue []byte) SessionOption {\n\treturn func(s *SessionTransport) error {\n\t\ts.prologue = prologue\n\t\treturn nil\n\t}\n}\n\n// EarlyDataHandler defines what the application payload is for either the second\n// (if responder) or third (if initiator) handshake message, and defines the\n// logic for handling the other side's early data. Note the early data in the\n// second handshake message is encrypted, but the peer is not authenticated at that point.\ntype EarlyDataHandler interface {\n\t// Send for the initiator is called for the client before sending the third\n\t// handshake message. Defines the application payload for the third message.\n\t// Send for the responder is called before sending the second handshake message.\n\tSend(context.Context, net.Conn, peer.ID) *pb.NoiseExtensions\n\t// Received for the initiator is called when the second handshake message\n\t// from the responder is received.\n\t// Received for the responder is called when the third handshake message\n\t// from the initiator is received.\n\tReceived(context.Context, net.Conn, *pb.NoiseExtensions) error\n}\n\n// EarlyData sets the `EarlyDataHandler` for the initiator and responder roles.\n// See `EarlyDataHandler` for more details.\nfunc EarlyData(initiator, responder EarlyDataHandler) SessionOption {\n\treturn func(s *SessionTransport) error {\n\t\ts.initiatorEarlyDataHandler = initiator\n\t\ts.responderEarlyDataHandler = responder\n\t\treturn nil\n\t}\n}\n\n// DisablePeerIDCheck disables checking the remote peer ID for a noise connection.\n// For outbound connections, this is the equivalent of calling `SecureInbound` with an empty\n// peer ID. This is susceptible to MITM attacks since we do not verify the identity of the remote\n// peer.\nfunc DisablePeerIDCheck() SessionOption {\n\treturn func(s *SessionTransport) error {\n\t\ts.disablePeerIDCheck = true\n\t\treturn nil\n\t}\n}\n\nvar _ sec.SecureTransport = &SessionTransport{}\n\n// SessionTransport can be used\n// to provide per-connection options\ntype SessionTransport struct {\n\tt *Transport\n\t// options\n\tprologue           []byte\n\tdisablePeerIDCheck bool\n\n\tprotocolID protocol.ID\n\n\tinitiatorEarlyDataHandler, responderEarlyDataHandler EarlyDataHandler\n}\n\n// SecureInbound runs the Noise handshake as the responder.\n// If p is empty, connections from any peer are accepted.\nfunc (i *SessionTransport) SecureInbound(ctx context.Context, insecure net.Conn, p peer.ID) (sec.SecureConn, error) {\n\tcheckPeerID := !i.disablePeerIDCheck && p != \"\"\n\tc, err := newSecureSession(i.t, ctx, insecure, p, i.prologue, i.initiatorEarlyDataHandler, i.responderEarlyDataHandler, false, checkPeerID)\n\tif err != nil {\n\t\taddr, maErr := manet.FromNetAddr(insecure.RemoteAddr())\n\t\tif maErr == nil {\n\t\t\tcanonicallog.LogPeerStatus(100, p, addr, \"handshake_failure\", \"noise\", \"err\", err.Error())\n\t\t}\n\t}\n\treturn c, err\n}\n\n// SecureOutbound runs the Noise handshake as the initiator.\nfunc (i *SessionTransport) SecureOutbound(ctx context.Context, insecure net.Conn, p peer.ID) (sec.SecureConn, error) {\n\treturn newSecureSession(i.t, ctx, insecure, p, i.prologue, i.initiatorEarlyDataHandler, i.responderEarlyDataHandler, true, !i.disablePeerIDCheck)\n}\n\nfunc (i *SessionTransport) ID() protocol.ID {\n\treturn i.protocolID\n}\n"
  },
  {
    "path": "p2p/security/noise/transport.go",
    "content": "package noise\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"slices\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\t\"github.com/libp2p/go-libp2p/core/sec\"\n\t\"github.com/libp2p/go-libp2p/p2p/canonicallog\"\n\ttptu \"github.com/libp2p/go-libp2p/p2p/net/upgrader\"\n\t\"github.com/libp2p/go-libp2p/p2p/security/noise/pb\"\n\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\n// ID is the protocol ID for noise\nconst ID = \"/noise\"\nconst maxProtoNum = 100\n\ntype Transport struct {\n\tprotocolID protocol.ID\n\tlocalID    peer.ID\n\tprivateKey crypto.PrivKey\n\tmuxers     []protocol.ID\n}\n\nvar _ sec.SecureTransport = &Transport{}\n\n// New creates a new Noise transport using the given private key as its\n// libp2p identity key.\nfunc New(id protocol.ID, privkey crypto.PrivKey, muxers []tptu.StreamMuxer) (*Transport, error) {\n\tlocalID, err := peer.IDFromPrivateKey(privkey)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tmuxerIDs := make([]protocol.ID, 0, len(muxers))\n\tfor _, m := range muxers {\n\t\tmuxerIDs = append(muxerIDs, m.ID)\n\t}\n\n\treturn &Transport{\n\t\tprotocolID: id,\n\t\tlocalID:    localID,\n\t\tprivateKey: privkey,\n\t\tmuxers:     muxerIDs,\n\t}, nil\n}\n\n// SecureInbound runs the Noise handshake as the responder.\n// If p is empty, connections from any peer are accepted.\nfunc (t *Transport) SecureInbound(ctx context.Context, insecure net.Conn, p peer.ID) (sec.SecureConn, error) {\n\tresponderEDH := newTransportEDH(t)\n\tc, err := newSecureSession(t, ctx, insecure, p, nil, nil, responderEDH, false, p != \"\")\n\tif err != nil {\n\t\taddr, maErr := manet.FromNetAddr(insecure.RemoteAddr())\n\t\tif maErr == nil {\n\t\t\tcanonicallog.LogPeerStatus(100, p, addr, \"handshake_failure\", \"noise\", \"err\", err.Error())\n\t\t}\n\t}\n\treturn SessionWithConnState(c, responderEDH.MatchMuxers(false)), err\n}\n\n// SecureOutbound runs the Noise handshake as the initiator.\nfunc (t *Transport) SecureOutbound(ctx context.Context, insecure net.Conn, p peer.ID) (sec.SecureConn, error) {\n\tinitiatorEDH := newTransportEDH(t)\n\tc, err := newSecureSession(t, ctx, insecure, p, nil, initiatorEDH, nil, true, true)\n\tif err != nil {\n\t\treturn c, err\n\t}\n\treturn SessionWithConnState(c, initiatorEDH.MatchMuxers(true)), err\n}\n\nfunc (t *Transport) WithSessionOptions(opts ...SessionOption) (*SessionTransport, error) {\n\tst := &SessionTransport{t: t, protocolID: t.protocolID}\n\tfor _, opt := range opts {\n\t\tif err := opt(st); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn st, nil\n}\n\nfunc (t *Transport) ID() protocol.ID {\n\treturn t.protocolID\n}\n\nfunc matchMuxers(initiatorMuxers, responderMuxers []protocol.ID) protocol.ID {\n\tfor _, initMuxer := range initiatorMuxers {\n\t\tif slices.Contains(responderMuxers, initMuxer) {\n\t\t\treturn initMuxer\n\t\t}\n\t}\n\treturn \"\"\n}\n\ntype transportEarlyDataHandler struct {\n\ttransport      *Transport\n\treceivedMuxers []protocol.ID\n}\n\nvar _ EarlyDataHandler = &transportEarlyDataHandler{}\n\nfunc newTransportEDH(t *Transport) *transportEarlyDataHandler {\n\treturn &transportEarlyDataHandler{transport: t}\n}\n\nfunc (i *transportEarlyDataHandler) Send(context.Context, net.Conn, peer.ID) *pb.NoiseExtensions {\n\treturn &pb.NoiseExtensions{\n\t\tStreamMuxers: protocol.ConvertToStrings(i.transport.muxers),\n\t}\n}\n\nfunc (i *transportEarlyDataHandler) Received(_ context.Context, _ net.Conn, extension *pb.NoiseExtensions) error {\n\t// Discard messages with size or the number of protocols exceeding extension limit for security.\n\tif extension != nil && len(extension.StreamMuxers) <= maxProtoNum {\n\t\ti.receivedMuxers = protocol.ConvertFromStrings(extension.GetStreamMuxers())\n\t}\n\treturn nil\n}\n\nfunc (i *transportEarlyDataHandler) MatchMuxers(isInitiator bool) protocol.ID {\n\tif isInitiator {\n\t\treturn matchMuxers(i.transport.muxers, i.receivedMuxers)\n\t}\n\treturn matchMuxers(i.receivedMuxers, i.transport.muxers)\n}\n"
  },
  {
    "path": "p2p/security/noise/transport_test.go",
    "content": "package noise\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"io\"\n\t\"math/rand\"\n\t\"net\"\n\t\"testing\"\n\t\"time\"\n\n\t\"golang.org/x/crypto/chacha20poly1305\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\t\"github.com/libp2p/go-libp2p/core/sec\"\n\t\"github.com/libp2p/go-libp2p/p2p/security/noise/pb\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc newTestTransport(t *testing.T, typ, bits int) *Transport {\n\tpriv, pub, err := crypto.GenerateKeyPair(typ, bits)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tid, err := peer.IDFromPublicKey(pub)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\treturn &Transport{\n\t\tlocalID:    id,\n\t\tprivateKey: priv,\n\t}\n}\n\nfunc newTestTransportWithMuxers(t *testing.T, typ, bits int, muxers []protocol.ID) *Transport {\n\ttransport := newTestTransport(t, typ, bits)\n\ttransport.muxers = muxers\n\treturn transport\n}\n\n// Create a new pair of connected TCP sockets.\nfunc newConnPair(t *testing.T) (net.Conn, net.Conn) {\n\tlstnr, err := net.Listen(\"tcp\", \"localhost:0\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to listen: %v\", err)\n\t\treturn nil, nil\n\t}\n\n\tvar clientErr error\n\tvar client net.Conn\n\taddr := lstnr.Addr()\n\tdone := make(chan struct{})\n\n\tgo func() {\n\t\tdefer close(done)\n\t\tclient, clientErr = net.Dial(addr.Network(), addr.String())\n\t}()\n\n\tserver, err := lstnr.Accept()\n\t<-done\n\n\tlstnr.Close()\n\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to accept: %v\", err)\n\t}\n\n\tif clientErr != nil {\n\t\tt.Fatalf(\"Failed to connect: %v\", clientErr)\n\t}\n\n\treturn client, server\n}\n\nfunc connect(t *testing.T, initTransport, respTransport *Transport) (*secureSession, *secureSession) {\n\tinit, resp := newConnPair(t)\n\n\tvar initConn sec.SecureConn\n\tvar initErr error\n\tdone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(done)\n\t\tinitConn, initErr = initTransport.SecureOutbound(context.Background(), init, respTransport.localID)\n\t}()\n\n\trespConn, respErr := respTransport.SecureInbound(context.Background(), resp, \"\")\n\t<-done\n\n\tif initErr != nil {\n\t\tt.Fatal(initErr)\n\t}\n\n\tif respErr != nil {\n\t\tt.Fatal(respErr)\n\t}\n\n\treturn initConn.(*secureSession), respConn.(*secureSession)\n}\n\nfunc TestDeadlines(t *testing.T) {\n\tinitTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\trespTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\n\tinit, resp := newConnPair(t)\n\tdefer init.Close()\n\tdefer resp.Close()\n\n\tctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)\n\tdefer cancel()\n\n\t_, err := initTransport.SecureOutbound(ctx, init, respTransport.localID)\n\tif err == nil {\n\t\tt.Fatalf(\"expected i/o timeout err; got: %s\", err)\n\t}\n\n\tvar neterr net.Error\n\tif ok := errors.As(err, &neterr); !ok || !neterr.Timeout() {\n\t\tt.Fatalf(\"expected i/o timeout err; got: %s\", err)\n\t}\n}\n\nfunc TestIDs(t *testing.T) {\n\tinitTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\trespTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\n\tinitConn, respConn := connect(t, initTransport, respTransport)\n\tdefer initConn.Close()\n\tdefer respConn.Close()\n\n\tif initConn.LocalPeer() != initTransport.localID {\n\t\tt.Fatal(\"Initiator Local Peer ID mismatch.\")\n\t}\n\n\tif respConn.RemotePeer() != initTransport.localID {\n\t\tt.Fatal(\"Responder Remote Peer ID mismatch.\")\n\t}\n\n\tif initConn.LocalPeer() != respConn.RemotePeer() {\n\t\tt.Fatal(\"Responder Local Peer ID mismatch.\")\n\t}\n\n\t// TODO: check after stage 0 of handshake if updated\n\tif initConn.RemotePeer() != respTransport.localID {\n\t\tt.Errorf(\"Initiator Remote Peer ID mismatch. expected %x got %x\", respTransport.localID, initConn.RemotePeer())\n\t}\n}\n\nfunc TestKeys(t *testing.T) {\n\tinitTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\trespTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\n\tinitConn, respConn := connect(t, initTransport, respTransport)\n\tdefer initConn.Close()\n\tdefer respConn.Close()\n\n\tpk1 := respConn.RemotePublicKey()\n\tpk2 := initTransport.privateKey.GetPublic()\n\tif !pk1.Equals(pk2) {\n\t\tt.Errorf(\"Public key mismatch. expected %x got %x\", pk1, pk2)\n\t}\n\n\tpk3 := initConn.RemotePublicKey()\n\tpk4 := respTransport.privateKey.GetPublic()\n\tif !pk3.Equals(pk4) {\n\t\tt.Errorf(\"Public key mismatch. expected %x got %x\", pk3, pk4)\n\t}\n}\n\nfunc TestPeerIDMatch(t *testing.T) {\n\tinitTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\trespTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\tinit, resp := newConnPair(t)\n\n\tdone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(done)\n\t\tconn, err := initTransport.SecureOutbound(context.Background(), init, respTransport.localID)\n\t\tassert.NoError(t, err)\n\t\tassert.Equal(t, respTransport.localID, conn.RemotePeer())\n\t\tb := make([]byte, 6)\n\t\t_, err = conn.Read(b)\n\t\tassert.NoError(t, err)\n\t\tassert.Equal(t, []byte(\"foobar\"), b)\n\t}()\n\n\tconn, err := respTransport.SecureInbound(context.Background(), resp, initTransport.localID)\n\trequire.NoError(t, err)\n\trequire.Equal(t, initTransport.localID, conn.RemotePeer())\n\t_, err = conn.Write([]byte(\"foobar\"))\n\trequire.NoError(t, err)\n}\n\nfunc TestPeerIDMismatchOutboundFailsHandshake(t *testing.T) {\n\tinitTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\trespTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\tinit, resp := newConnPair(t)\n\n\terrChan := make(chan error)\n\tgo func() {\n\t\t_, err := initTransport.SecureOutbound(context.Background(), init, \"a-random-peer-id\")\n\t\terrChan <- err\n\t}()\n\n\t_, err := respTransport.SecureInbound(context.Background(), resp, \"\")\n\trequire.Error(t, err)\n\n\tinitErr := <-errChan\n\trequire.Error(t, initErr, \"expected initiator to fail with peer ID mismatch error\")\n\tvar mismatchErr sec.ErrPeerIDMismatch\n\trequire.ErrorAs(t, initErr, &mismatchErr)\n\trequire.Equal(t, mismatchErr.Expected, peer.ID(\"a-random-peer-id\"))\n\trequire.Equal(t, mismatchErr.Actual, respTransport.localID)\n}\n\nfunc TestPeerIDMismatchInboundFailsHandshake(t *testing.T) {\n\tinitTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\trespTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\tinit, resp := newConnPair(t)\n\n\tdone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(done)\n\t\tconn, err := initTransport.SecureOutbound(context.Background(), init, respTransport.localID)\n\t\tassert.NoError(t, err)\n\t\t_, err = conn.Read([]byte{0})\n\t\tassert.Error(t, err)\n\t}()\n\n\t_, err := respTransport.SecureInbound(context.Background(), resp, \"a-random-peer-id\")\n\trequire.Error(t, err, \"expected responder to fail with peer ID mismatch error\")\n\tvar mismatchErr sec.ErrPeerIDMismatch\n\trequire.ErrorAs(t, err, &mismatchErr)\n\trequire.Equal(t, mismatchErr.Expected, peer.ID(\"a-random-peer-id\"))\n\trequire.Equal(t, mismatchErr.Actual, initTransport.localID)\n\t<-done\n}\n\nfunc TestPeerIDInboundCheckDisabled(t *testing.T) {\n\tinitTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\trespTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\tinit, resp := newConnPair(t)\n\n\tinitSessionTransport, err := initTransport.WithSessionOptions(DisablePeerIDCheck())\n\trequire.NoError(t, err)\n\terrChan := make(chan error)\n\tgo func() {\n\t\t_, err := initSessionTransport.SecureInbound(context.Background(), init, \"test\")\n\t\terrChan <- err\n\t}()\n\t_, err = respTransport.SecureOutbound(context.Background(), resp, initTransport.localID)\n\trequire.NoError(t, err)\n\tinitErr := <-errChan\n\trequire.NoError(t, initErr)\n}\n\nfunc TestPeerIDOutboundNoCheck(t *testing.T) {\n\tinitTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\trespTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\tinit, resp := newConnPair(t)\n\n\tinitSessionTransport, err := initTransport.WithSessionOptions(DisablePeerIDCheck())\n\trequire.NoError(t, err)\n\n\terrChan := make(chan error)\n\tgo func() {\n\t\t_, err := initSessionTransport.SecureOutbound(context.Background(), init, \"test\")\n\t\terrChan <- err\n\t}()\n\n\t_, err = respTransport.SecureInbound(context.Background(), resp, \"\")\n\trequire.NoError(t, err)\n\tinitErr := <-errChan\n\trequire.NoError(t, initErr)\n}\n\nfunc TestLargePayloads(t *testing.T) {\n\tinitTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\trespTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\n\tinitConn, respConn := connect(t, initTransport, respTransport)\n\tdefer initConn.Close()\n\tdefer respConn.Close()\n\n\t// enough to require a couple Noise messages, with a size that\n\t// isn't a neat multiple of Noise message size, just in case\n\trnd := rand.New(rand.NewSource(1234))\n\tconst size = 100000\n\tbefore := make([]byte, size)\n\trnd.Read(before)\n\n\tif _, err := initConn.Write(before); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tafter := make([]byte, len(before))\n\tafterLen, err := io.ReadFull(respConn, after)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif len(before) != afterLen {\n\t\tt.Errorf(\"expected to read same amount of data as written. written=%d read=%d\", len(before), afterLen)\n\t}\n\tif !bytes.Equal(before, after) {\n\t\tt.Error(\"Message mismatch.\")\n\t}\n}\n\n// Tests XX handshake\nfunc TestHandshakeXX(t *testing.T) {\n\tinitTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\trespTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\n\tinitConn, respConn := connect(t, initTransport, respTransport)\n\tdefer initConn.Close()\n\tdefer respConn.Close()\n\n\tbefore := []byte(\"hello world\")\n\t_, err := initConn.Write(before)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tafter := make([]byte, len(before))\n\t_, err = respConn.Read(after)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif !bytes.Equal(before, after) {\n\t\tt.Errorf(\"Message mismatch. %v != %v\", before, after)\n\t}\n}\n\nfunc TestBufferEqEncPayload(t *testing.T) {\n\tinitTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\trespTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\n\tinitConn, respConn := connect(t, initTransport, respTransport)\n\tdefer initConn.Close()\n\tdefer respConn.Close()\n\n\tbefore := []byte(\"hello world\")\n\t_, err := initConn.Write(before)\n\trequire.NoError(t, err)\n\n\tafter := make([]byte, len(before)+chacha20poly1305.Overhead)\n\tafterLen, err := respConn.Read(after)\n\trequire.NoError(t, err)\n\n\trequire.Len(t, before, afterLen)\n\trequire.Equal(t, before, after[:len(before)])\n}\n\nfunc TestBufferEqDecryptedPayload(t *testing.T) {\n\tinitTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\trespTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\n\tinitConn, respConn := connect(t, initTransport, respTransport)\n\tdefer initConn.Close()\n\tdefer respConn.Close()\n\n\tbefore := []byte(\"hello world\")\n\t_, err := initConn.Write(before)\n\trequire.NoError(t, err)\n\n\tafter := make([]byte, len(before)+1)\n\tafterLen, err := respConn.Read(after)\n\trequire.NoError(t, err)\n\n\trequire.Len(t, before, afterLen)\n\trequire.Equal(t, before, after[:len(before)])\n}\n\nfunc TestReadUnencryptedFails(t *testing.T) {\n\t// case1 buffer > len(msg)\n\tinitTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\trespTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\n\tinitConn, respConn := connect(t, initTransport, respTransport)\n\tdefer initConn.Close()\n\tdefer respConn.Close()\n\n\tbefore := []byte(\"hello world\")\n\tmsg := make([]byte, len(before)+LengthPrefixLength)\n\tbinary.BigEndian.PutUint16(msg, uint16(len(before)))\n\tcopy(msg[LengthPrefixLength:], before)\n\tn, err := initConn.insecureConn.Write(msg)\n\trequire.NoError(t, err)\n\trequire.Len(t, msg, n)\n\n\tafter := make([]byte, len(msg)+1)\n\tafterLen, err := respConn.Read(after)\n\trequire.Error(t, err)\n\trequire.Equal(t, 0, afterLen)\n\n\t// case2: buffer < len(msg)\n\tinitTransport = newTestTransport(t, crypto.Ed25519, 2048)\n\trespTransport = newTestTransport(t, crypto.Ed25519, 2048)\n\n\tinitConn, respConn = connect(t, initTransport, respTransport)\n\tdefer initConn.Close()\n\tdefer respConn.Close()\n\n\tbefore = []byte(\"hello world\")\n\tmsg = make([]byte, len(before)+LengthPrefixLength)\n\tbinary.BigEndian.PutUint16(msg, uint16(len(before)))\n\tcopy(msg[LengthPrefixLength:], before)\n\tn, err = initConn.insecureConn.Write(msg)\n\trequire.NoError(t, err)\n\trequire.Len(t, msg, n)\n\n\tafter = make([]byte, 1)\n\tafterLen, err = respConn.Read(after)\n\trequire.Error(t, err)\n\trequire.Equal(t, 0, afterLen)\n}\n\nfunc TestPrologueMatches(t *testing.T) {\n\tcommonPrologue := []byte(\"test\")\n\tinitTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\trespTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\n\tinitConn, respConn := newConnPair(t)\n\n\tdone := make(chan struct{})\n\n\tgo func() {\n\t\tdefer close(done)\n\t\ttpt, err := initTransport.\n\t\t\tWithSessionOptions(Prologue(commonPrologue))\n\t\trequire.NoError(t, err)\n\t\tconn, err := tpt.SecureOutbound(context.Background(), initConn, respTransport.localID)\n\t\trequire.NoError(t, err)\n\t\tdefer conn.Close()\n\t}()\n\n\ttpt, err := respTransport.\n\t\tWithSessionOptions(Prologue(commonPrologue))\n\trequire.NoError(t, err)\n\tconn, err := tpt.SecureInbound(context.Background(), respConn, \"\")\n\trequire.NoError(t, err)\n\tdefer conn.Close()\n\t<-done\n}\n\nfunc TestPrologueDoesNotMatchFailsHandshake(t *testing.T) {\n\tinitPrologue, respPrologue := []byte(\"initPrologue\"), []byte(\"respPrologue\")\n\tinitTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\trespTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\n\tinitConn, respConn := newConnPair(t)\n\n\tdone := make(chan struct{})\n\n\tgo func() {\n\t\tdefer close(done)\n\t\ttpt, err := initTransport.\n\t\t\tWithSessionOptions(Prologue(initPrologue))\n\t\trequire.NoError(t, err)\n\t\t_, err = tpt.SecureOutbound(context.Background(), initConn, respTransport.localID)\n\t\trequire.Error(t, err)\n\t}()\n\n\ttpt, err := respTransport.WithSessionOptions(Prologue(respPrologue))\n\trequire.NoError(t, err)\n\n\t_, err = tpt.SecureInbound(context.Background(), respConn, \"\")\n\trequire.Error(t, err)\n\t<-done\n}\n\ntype earlyDataHandler struct {\n\tsend     func(context.Context, net.Conn, peer.ID) *pb.NoiseExtensions\n\treceived func(context.Context, net.Conn, *pb.NoiseExtensions) error\n}\n\nfunc (e *earlyDataHandler) Send(ctx context.Context, conn net.Conn, id peer.ID) *pb.NoiseExtensions {\n\tif e.send == nil {\n\t\treturn nil\n\t}\n\treturn e.send(ctx, conn, id)\n}\n\nfunc (e *earlyDataHandler) Received(ctx context.Context, conn net.Conn, ext *pb.NoiseExtensions) error {\n\tif e.received == nil {\n\t\treturn nil\n\t}\n\treturn e.received(ctx, conn, ext)\n}\n\nfunc TestEarlyDataAccepted(t *testing.T) {\n\thandshake := func(t *testing.T, client, server EarlyDataHandler) {\n\t\tt.Helper()\n\t\tinitTransport, err := newTestTransport(t, crypto.Ed25519, 2048).WithSessionOptions(EarlyData(client, nil))\n\t\trequire.NoError(t, err)\n\t\ttpt := newTestTransport(t, crypto.Ed25519, 2048)\n\t\trespTransport, err := tpt.WithSessionOptions(EarlyData(nil, server))\n\t\trequire.NoError(t, err)\n\n\t\tinitConn, respConn := newConnPair(t)\n\n\t\terrChan := make(chan error)\n\t\tgo func() {\n\t\t\t_, err := respTransport.SecureInbound(context.Background(), initConn, \"\")\n\t\t\terrChan <- err\n\t\t}()\n\n\t\tconn, err := initTransport.SecureOutbound(context.Background(), respConn, tpt.localID)\n\t\trequire.NoError(t, err)\n\t\tselect {\n\t\tcase <-time.After(500 * time.Millisecond):\n\t\t\tt.Fatal(\"timeout\")\n\t\tcase err := <-errChan:\n\t\t\trequire.NoError(t, err)\n\t\t}\n\t\tdefer conn.Close()\n\t}\n\n\tvar receivedExtensions *pb.NoiseExtensions\n\treceivingEDH := &earlyDataHandler{\n\t\treceived: func(_ context.Context, _ net.Conn, ext *pb.NoiseExtensions) error {\n\t\t\treceivedExtensions = ext\n\t\t\treturn nil\n\t\t},\n\t}\n\tsendingEDH := &earlyDataHandler{\n\t\tsend: func(context.Context, net.Conn, peer.ID) *pb.NoiseExtensions {\n\t\t\treturn &pb.NoiseExtensions{WebtransportCerthashes: [][]byte{[]byte(\"foobar\")}}\n\t\t},\n\t}\n\n\tt.Run(\"client sending\", func(t *testing.T) {\n\t\thandshake(t, sendingEDH, receivingEDH)\n\t\trequire.Equal(t, [][]byte{[]byte(\"foobar\")}, receivedExtensions.WebtransportCerthashes)\n\t\treceivedExtensions = nil\n\t})\n\n\tt.Run(\"server sending\", func(t *testing.T) {\n\t\thandshake(t, receivingEDH, sendingEDH)\n\t\trequire.Equal(t, [][]byte{[]byte(\"foobar\")}, receivedExtensions.WebtransportCerthashes)\n\t\treceivedExtensions = nil\n\t})\n}\n\nfunc TestEarlyDataRejected(t *testing.T) {\n\thandshake := func(t *testing.T, client, server EarlyDataHandler) (clientErr, serverErr error) {\n\t\tinitTransport, err := newTestTransport(t, crypto.Ed25519, 2048).WithSessionOptions(EarlyData(client, nil))\n\t\trequire.NoError(t, err)\n\t\ttpt := newTestTransport(t, crypto.Ed25519, 2048)\n\t\trespTransport, err := tpt.WithSessionOptions(EarlyData(nil, server))\n\t\trequire.NoError(t, err)\n\n\t\tinitConn, respConn := newConnPair(t)\n\n\t\terrChan := make(chan error)\n\t\tgo func() {\n\t\t\t_, err := respTransport.SecureInbound(context.Background(), initConn, \"\")\n\t\t\terrChan <- err\n\t\t}()\n\n\t\t// As early data is sent with the last handshake message, the handshake will appear\n\t\t// to succeed for the client.\n\t\tvar conn sec.SecureConn\n\t\tconn, clientErr = initTransport.SecureOutbound(context.Background(), respConn, tpt.localID)\n\t\tif clientErr == nil {\n\t\t\t_, clientErr = conn.Read([]byte{0})\n\t\t}\n\n\t\tselect {\n\t\tcase <-time.After(500 * time.Millisecond):\n\t\t\tt.Fatal(\"timeout\")\n\t\tcase err := <-errChan:\n\t\t\tserverErr = err\n\t\t}\n\t\treturn\n\t}\n\n\treceivingEDH := &earlyDataHandler{\n\t\treceived: func(context.Context, net.Conn, *pb.NoiseExtensions) error { return errors.New(\"nope\") },\n\t}\n\tsendingEDH := &earlyDataHandler{\n\t\tsend: func(context.Context, net.Conn, peer.ID) *pb.NoiseExtensions {\n\t\t\treturn &pb.NoiseExtensions{WebtransportCerthashes: [][]byte{[]byte(\"foobar\")}}\n\t\t},\n\t}\n\n\tt.Run(\"client sending\", func(t *testing.T) {\n\t\tclientErr, serverErr := handshake(t, sendingEDH, receivingEDH)\n\t\trequire.Error(t, clientErr)\n\t\trequire.EqualError(t, serverErr, \"nope\")\n\t})\n\n\tt.Run(\"server sending\", func(t *testing.T) {\n\t\tclientErr, serverErr := handshake(t, receivingEDH, sendingEDH)\n\t\trequire.Error(t, serverErr)\n\t\trequire.EqualError(t, clientErr, \"nope\")\n\t})\n}\n\nfunc TestEarlyfffDataAcceptedWithNoHandler(t *testing.T) {\n\tclientEDH := &earlyDataHandler{\n\t\tsend: func(_ context.Context, _ net.Conn, _ peer.ID) *pb.NoiseExtensions {\n\t\t\treturn &pb.NoiseExtensions{WebtransportCerthashes: [][]byte{[]byte(\"foobar\")}}\n\t\t},\n\t}\n\tinitTransport, err := newTestTransport(t, crypto.Ed25519, 2048).WithSessionOptions(EarlyData(clientEDH, nil))\n\trequire.NoError(t, err)\n\trespTransport := newTestTransport(t, crypto.Ed25519, 2048)\n\n\tinitConn, respConn := newConnPair(t)\n\n\terrChan := make(chan error)\n\tgo func() {\n\t\t_, err := respTransport.SecureInbound(context.Background(), initConn, \"\")\n\t\terrChan <- err\n\t}()\n\n\tconn, err := initTransport.SecureOutbound(context.Background(), respConn, respTransport.localID)\n\trequire.NoError(t, err)\n\tdefer conn.Close()\n\n\tselect {\n\tcase <-time.After(500 * time.Millisecond):\n\t\tt.Fatal(\"timeout\")\n\tcase err := <-errChan:\n\t\trequire.NoError(t, err)\n\t}\n}\n\ntype noiseEarlyDataTestCase struct {\n\tclientProtos   []protocol.ID\n\tserverProtos   []protocol.ID\n\texpectedResult protocol.ID\n}\n\nfunc TestHandshakeWithTransportEarlyData(t *testing.T) {\n\ttests := []noiseEarlyDataTestCase{\n\t\t{\n\t\t\tclientProtos:   nil,\n\t\t\tserverProtos:   nil,\n\t\t\texpectedResult: \"\",\n\t\t},\n\t\t{\n\t\t\tclientProtos:   []protocol.ID{\"muxer1\"},\n\t\t\tserverProtos:   []protocol.ID{\"muxer1\"},\n\t\t\texpectedResult: \"muxer1\",\n\t\t},\n\t\t{\n\t\t\tclientProtos:   []protocol.ID{\"muxer1\"},\n\t\t\tserverProtos:   []protocol.ID{},\n\t\t\texpectedResult: \"\",\n\t\t},\n\t\t{\n\t\t\tclientProtos:   []protocol.ID{},\n\t\t\tserverProtos:   []protocol.ID{\"muxer2\"},\n\t\t\texpectedResult: \"\",\n\t\t},\n\t\t{\n\t\t\tclientProtos:   []protocol.ID{\"muxer2\"},\n\t\t\tserverProtos:   []protocol.ID{\"muxer1\"},\n\t\t\texpectedResult: \"\",\n\t\t},\n\t\t{\n\t\t\tclientProtos:   []protocol.ID{\"muxer1\", \"muxer2\"},\n\t\t\tserverProtos:   []protocol.ID{\"muxer2\", \"muxer1\"},\n\t\t\texpectedResult: \"muxer1\",\n\t\t},\n\t\t{\n\t\t\tclientProtos:   []protocol.ID{\"muxer3\", \"muxer2\", \"muxer1\"},\n\t\t\tserverProtos:   []protocol.ID{\"muxer2\", \"muxer1\"},\n\t\t\texpectedResult: \"muxer2\",\n\t\t},\n\t\t{\n\t\t\tclientProtos:   []protocol.ID{\"muxer1\", \"muxer2\"},\n\t\t\tserverProtos:   []protocol.ID{\"muxer3\"},\n\t\t\texpectedResult: \"\",\n\t\t},\n\t}\n\n\tnoiseHandshake := func(t *testing.T, initProtos, respProtos []protocol.ID, expectedProto protocol.ID) {\n\t\tinitTransport := newTestTransportWithMuxers(t, crypto.Ed25519, 2048, initProtos)\n\t\trespTransport := newTestTransportWithMuxers(t, crypto.Ed25519, 2048, respProtos)\n\n\t\tinitConn, respConn := connect(t, initTransport, respTransport)\n\t\tdefer initConn.Close()\n\t\tdefer respConn.Close()\n\n\t\trequire.Equal(t, expectedProto, initConn.connectionState.StreamMultiplexer)\n\t\trequire.Equal(t, expectedProto != \"\", initConn.connectionState.UsedEarlyMuxerNegotiation)\n\t\trequire.Equal(t, expectedProto, respConn.connectionState.StreamMultiplexer)\n\t\trequire.Equal(t, expectedProto != \"\", respConn.connectionState.UsedEarlyMuxerNegotiation)\n\n\t\tinitData := []byte(\"Test data for noise transport\")\n\t\t_, err := initConn.Write(initData)\n\t\trequire.NoError(t, err)\n\n\t\trespData := make([]byte, len(initData))\n\t\t_, err = respConn.Read(respData)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, initData, respData)\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(\"Transport EarlyData Test\", func(t *testing.T) {\n\t\t\tnoiseHandshake(t, test.clientProtos, test.serverProtos, test.expectedResult)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "p2p/security/tls/cmd/README.md",
    "content": "# TLS handshake example\n\nRun\n```bash\ngo run cmd/tlsdiag.go server\n```\n"
  },
  {
    "path": "p2p/security/tls/cmd/tlsdiag/client.go",
    "content": "package tlsdiag\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"time\"\n\n\tlibp2ptls \"github.com/libp2p/go-libp2p/p2p/security/tls\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n)\n\nfunc StartClient() error {\n\tport := flag.Int(\"p\", 5533, \"port\")\n\tpeerIDString := flag.String(\"id\", \"\", \"peer ID\")\n\tkeyType := flag.String(\"key\", \"ecdsa\", \"rsa, ecdsa, ed25519 or secp256k1\")\n\tflag.Parse()\n\n\tpriv, err := generateKey(*keyType)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tpeerID, err := peer.Decode(*peerIDString)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tid, err := peer.IDFromPrivateKey(priv)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfmt.Printf(\" Peer ID: %s\\n\", id)\n\ttp, err := libp2ptls.New(libp2ptls.ID, priv, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tremoteAddr := fmt.Sprintf(\"localhost:%d\", *port)\n\tfmt.Printf(\"Dialing %s\\n\", remoteAddr)\n\tconn, err := net.Dial(\"tcp\", remoteAddr)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfmt.Printf(\"Dialed raw connection to %s\\n\", conn.RemoteAddr())\n\n\tctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)\n\tdefer cancel()\n\tsconn, err := tp.SecureOutbound(ctx, conn, peerID)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfmt.Printf(\"Authenticated server: %s\\n\", sconn.RemotePeer())\n\tdata, err := io.ReadAll(sconn)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfmt.Printf(\"Received message from server: %s\\n\", string(data))\n\treturn nil\n}\n"
  },
  {
    "path": "p2p/security/tls/cmd/tlsdiag/key.go",
    "content": "package tlsdiag\n\nimport (\n\t\"crypto/rand\"\n\t\"fmt\"\n\n\tic \"github.com/libp2p/go-libp2p/core/crypto\"\n)\n\nfunc generateKey(keyType string) (priv ic.PrivKey, err error) {\n\tswitch keyType {\n\tcase \"rsa\":\n\t\tfmt.Printf(\"Generated new peer with an RSA key.\")\n\t\tpriv, _, err = ic.GenerateRSAKeyPair(2048, rand.Reader)\n\tcase \"ecdsa\":\n\t\tfmt.Printf(\"Generated new peer with an ECDSA key.\")\n\t\tpriv, _, err = ic.GenerateECDSAKeyPair(rand.Reader)\n\tcase \"ed25519\":\n\t\tfmt.Printf(\"Generated new peer with an Ed25519 key.\")\n\t\tpriv, _, err = ic.GenerateEd25519Key(rand.Reader)\n\tcase \"secp256k1\":\n\t\tfmt.Printf(\"Generated new peer with an Secp256k1 key.\")\n\t\tpriv, _, err = ic.GenerateSecp256k1Key(rand.Reader)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unknown key type: %s\", keyType)\n\t}\n\treturn\n}\n"
  },
  {
    "path": "p2p/security/tls/cmd/tlsdiag/server.go",
    "content": "package tlsdiag\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\t\"net\"\n\t\"time\"\n\n\tlibp2ptls \"github.com/libp2p/go-libp2p/p2p/security/tls\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n)\n\nfunc StartServer() error {\n\tport := flag.Int(\"p\", 5533, \"port\")\n\tkeyType := flag.String(\"key\", \"ecdsa\", \"rsa, ecdsa, ed25519 or secp256k1\")\n\tflag.Parse()\n\n\tpriv, err := generateKey(*keyType)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tid, err := peer.IDFromPrivateKey(priv)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfmt.Printf(\" Peer ID: %s\\n\", id)\n\ttp, err := libp2ptls.New(libp2ptls.ID, priv, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tln, err := net.Listen(\"tcp\", fmt.Sprintf(\"localhost:%d\", *port))\n\tif err != nil {\n\t\treturn err\n\t}\n\tfmt.Printf(\"Listening for new connections on %s\\n\", ln.Addr())\n\tfmt.Printf(\"Now run the following command in a separate terminal:\\n\")\n\tfmt.Printf(\"\\tgo run cmd/tlsdiag.go client -p %d -id %s\\n\", *port, id)\n\n\tfor {\n\t\tconn, err := ln.Accept()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfmt.Printf(\"Accepted raw connection from %s\\n\", conn.RemoteAddr())\n\t\tgo func() {\n\t\t\tif err := handleConn(tp, conn); err != nil {\n\t\t\t\tfmt.Printf(\"Error handling connection from %s: %s\\n\", conn.RemoteAddr(), err)\n\t\t\t}\n\t\t}()\n\t}\n}\n\nfunc handleConn(tp *libp2ptls.Transport, conn net.Conn) error {\n\tctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)\n\tdefer cancel()\n\tsconn, err := tp.SecureInbound(ctx, conn, \"\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tfmt.Printf(\"Authenticated client: %s\\n\", sconn.RemotePeer())\n\tfmt.Fprintf(sconn, \"Hello client!\")\n\tfmt.Printf(\"Closing connection to %s\\n\", conn.RemoteAddr())\n\treturn sconn.Close()\n}\n"
  },
  {
    "path": "p2p/security/tls/cmd/tlsdiag.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/libp2p/go-libp2p/p2p/security/tls/cmd/tlsdiag\"\n)\n\nfunc main() {\n\tif len(os.Args) <= 1 {\n\t\tfmt.Println(\"missing argument: client / server\")\n\t\treturn\n\t}\n\n\trole := os.Args[1]\n\t// remove the role argument from os.Args\n\tos.Args = append([]string{os.Args[0]}, os.Args[2:]...)\n\n\tvar err error\n\tswitch role {\n\tcase \"client\":\n\t\terr = tlsdiag.StartClient()\n\tcase \"server\":\n\t\terr = tlsdiag.StartServer()\n\tdefault:\n\t\tfmt.Println(\"invalid argument. Expected client / server\")\n\t\treturn\n\t}\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "p2p/security/tls/conn.go",
    "content": "package libp2ptls\n\nimport (\n\t\"crypto/tls\"\n\n\tci \"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/sec\"\n)\n\ntype conn struct {\n\t*tls.Conn\n\n\tlocalPeer       peer.ID\n\tremotePeer      peer.ID\n\tremotePubKey    ci.PubKey\n\tconnectionState network.ConnectionState\n}\n\nvar _ sec.SecureConn = &conn{}\n\nfunc (c *conn) LocalPeer() peer.ID {\n\treturn c.localPeer\n}\n\nfunc (c *conn) RemotePeer() peer.ID {\n\treturn c.remotePeer\n}\n\nfunc (c *conn) RemotePublicKey() ci.PubKey {\n\treturn c.remotePubKey\n}\n\nfunc (c *conn) ConnState() network.ConnectionState {\n\treturn c.connectionState\n}\n"
  },
  {
    "path": "p2p/security/tls/crypto.go",
    "content": "package libp2ptls\n\nimport (\n\t\"crypto\"\n\t\"crypto/ecdsa\"\n\t\"crypto/elliptic\"\n\t\"crypto/rand\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"crypto/x509/pkix\"\n\t\"encoding/asn1\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"math/big\"\n\t\"os\"\n\t\"runtime/debug\"\n\t\"time\"\n\n\tic \"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/sec\"\n)\n\nconst certValidityPeriod = 100 * 365 * 24 * time.Hour // ~100 years\nconst certificatePrefix = \"libp2p-tls-handshake:\"\nconst alpn string = \"libp2p\"\n\nvar extensionID = getPrefixedExtensionID([]int{1, 1})\nvar extensionCritical bool // so we can mark the extension critical in tests\n\ntype signedKey struct {\n\tPubKey    []byte\n\tSignature []byte\n}\n\n// Identity is used to secure connections\ntype Identity struct {\n\tconfig tls.Config\n}\n\n// IdentityConfig is used to configure an Identity\ntype IdentityConfig struct {\n\tCertTemplate *x509.Certificate\n\tKeyLogWriter io.Writer\n}\n\n// IdentityOption transforms an IdentityConfig to apply optional settings.\ntype IdentityOption func(r *IdentityConfig)\n\n// WithCertTemplate specifies the template to use when generating a new certificate.\nfunc WithCertTemplate(template *x509.Certificate) IdentityOption {\n\treturn func(c *IdentityConfig) {\n\t\tc.CertTemplate = template\n\t}\n}\n\n// WithKeyLogWriter optionally specifies a destination for TLS master secrets\n// in NSS key log format that can be used to allow external programs\n// such as Wireshark to decrypt TLS connections.\n// See https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format.\n// Use of KeyLogWriter compromises security and should only be\n// used for debugging.\nfunc WithKeyLogWriter(w io.Writer) IdentityOption {\n\treturn func(c *IdentityConfig) {\n\t\tc.KeyLogWriter = w\n\t}\n}\n\n// NewIdentity creates a new identity\nfunc NewIdentity(privKey ic.PrivKey, opts ...IdentityOption) (*Identity, error) {\n\tconfig := IdentityConfig{}\n\tfor _, opt := range opts {\n\t\topt(&config)\n\t}\n\n\tvar err error\n\tif config.CertTemplate == nil {\n\t\tconfig.CertTemplate, err = certTemplate()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tcert, err := keyToCertificate(privKey, config.CertTemplate)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &Identity{\n\t\tconfig: tls.Config{\n\t\t\tMinVersion:         tls.VersionTLS13,\n\t\t\tInsecureSkipVerify: true, // This is not insecure here. We will verify the cert chain ourselves.\n\t\t\tClientAuth:         tls.RequireAnyClientCert,\n\t\t\tCertificates:       []tls.Certificate{*cert},\n\t\t\tVerifyPeerCertificate: func(_ [][]byte, _ [][]*x509.Certificate) error {\n\t\t\t\tpanic(\"tls config not specialized for peer\")\n\t\t\t},\n\t\t\tNextProtos:             []string{alpn},\n\t\t\tSessionTicketsDisabled: true,\n\t\t\tKeyLogWriter:           config.KeyLogWriter,\n\t\t},\n\t}, nil\n}\n\n// ConfigForPeer creates a new single-use tls.Config that verifies the peer's\n// certificate chain and returns the peer's public key via the channel. If the\n// peer ID is empty, the returned config will accept any peer.\n//\n// It should be used to create a new tls.Config before securing either an\n// incoming or outgoing connection.\nfunc (i *Identity) ConfigForPeer(remote peer.ID) (*tls.Config, <-chan ic.PubKey) {\n\tkeyCh := make(chan ic.PubKey, 1)\n\t// We need to check the peer ID in the VerifyPeerCertificate callback.\n\t// The tls.Config it is also used for listening, and we might also have concurrent dials.\n\t// Clone it so we can check for the specific peer ID we're dialing here.\n\tconf := i.config.Clone()\n\t// We're using InsecureSkipVerify, so the verifiedChains parameter will always be empty.\n\t// We need to parse the certificates ourselves from the raw certs.\n\tconf.VerifyPeerCertificate = func(rawCerts [][]byte, _ [][]*x509.Certificate) (err error) {\n\t\tdefer func() {\n\t\t\tif rerr := recover(); rerr != nil {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"panic when processing peer certificate in TLS handshake: %s\\n%s\\n\", rerr, debug.Stack())\n\t\t\t\terr = fmt.Errorf(\"panic when processing peer certificate in TLS handshake: %s\", rerr)\n\n\t\t\t}\n\t\t}()\n\n\t\tdefer close(keyCh)\n\n\t\tchain := make([]*x509.Certificate, len(rawCerts))\n\t\tfor i := range rawCerts {\n\t\t\tcert, err := x509.ParseCertificate(rawCerts[i])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tchain[i] = cert\n\t\t}\n\n\t\tpubKey, err := PubKeyFromCertChain(chain)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif remote != \"\" && !remote.MatchesPublicKey(pubKey) {\n\t\t\tpeerID, err := peer.IDFromPublicKey(pubKey)\n\t\t\tif err != nil {\n\t\t\t\tpeerID = peer.ID(fmt.Sprintf(\"(not determined: %s)\", err.Error()))\n\t\t\t}\n\t\t\treturn sec.ErrPeerIDMismatch{Expected: remote, Actual: peerID}\n\t\t}\n\t\tkeyCh <- pubKey\n\t\treturn nil\n\t}\n\treturn conf, keyCh\n}\n\n// PubKeyFromCertChain verifies the certificate chain and extract the remote's public key.\nfunc PubKeyFromCertChain(chain []*x509.Certificate) (ic.PubKey, error) {\n\tif len(chain) != 1 {\n\t\treturn nil, errors.New(\"expected one certificates in the chain\")\n\t}\n\tcert := chain[0]\n\tpool := x509.NewCertPool()\n\tpool.AddCert(cert)\n\tvar found bool\n\tvar keyExt pkix.Extension\n\t// find the libp2p key extension, skipping all unknown extensions\n\tfor _, ext := range cert.Extensions {\n\t\tif extensionIDEqual(ext.Id, extensionID) {\n\t\t\tkeyExt = ext\n\t\t\tfound = true\n\t\t\tfor i, oident := range cert.UnhandledCriticalExtensions {\n\t\t\t\tif oident.Equal(ext.Id) {\n\t\t\t\t\t// delete the extension from UnhandledCriticalExtensions\n\t\t\t\t\tcert.UnhandledCriticalExtensions = append(cert.UnhandledCriticalExtensions[:i], cert.UnhandledCriticalExtensions[i+1:]...)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\tif !found {\n\t\treturn nil, errors.New(\"expected certificate to contain the key extension\")\n\t}\n\tif _, err := cert.Verify(x509.VerifyOptions{Roots: pool}); err != nil {\n\t\t// If we return an x509 error here, it will be sent on the wire.\n\t\t// Wrap the error to avoid that.\n\t\treturn nil, fmt.Errorf(\"certificate verification failed: %s\", err)\n\t}\n\n\tvar sk signedKey\n\tif _, err := asn1.Unmarshal(keyExt.Value, &sk); err != nil {\n\t\treturn nil, fmt.Errorf(\"unmarshalling signed certificate failed: %s\", err)\n\t}\n\tpubKey, err := ic.UnmarshalPublicKey(sk.PubKey)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unmarshalling public key failed: %s\", err)\n\t}\n\tcertKeyPub, err := x509.MarshalPKIXPublicKey(cert.PublicKey)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvalid, err := pubKey.Verify(append([]byte(certificatePrefix), certKeyPub...), sk.Signature)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"signature verification failed: %s\", err)\n\t}\n\tif !valid {\n\t\treturn nil, errors.New(\"signature invalid\")\n\t}\n\treturn pubKey, nil\n}\n\n// GenerateSignedExtension uses the provided private key to sign the public key, and returns the\n// signature within a pkix.Extension.\n// This extension is included in a certificate to cryptographically tie it to the libp2p private key.\nfunc GenerateSignedExtension(sk ic.PrivKey, pubKey crypto.PublicKey) (pkix.Extension, error) {\n\tkeyBytes, err := ic.MarshalPublicKey(sk.GetPublic())\n\tif err != nil {\n\t\treturn pkix.Extension{}, err\n\t}\n\tcertKeyPub, err := x509.MarshalPKIXPublicKey(pubKey)\n\tif err != nil {\n\t\treturn pkix.Extension{}, err\n\t}\n\tsignature, err := sk.Sign(append([]byte(certificatePrefix), certKeyPub...))\n\tif err != nil {\n\t\treturn pkix.Extension{}, err\n\t}\n\tvalue, err := asn1.Marshal(signedKey{\n\t\tPubKey:    keyBytes,\n\t\tSignature: signature,\n\t})\n\tif err != nil {\n\t\treturn pkix.Extension{}, err\n\t}\n\n\treturn pkix.Extension{Id: extensionID, Critical: extensionCritical, Value: value}, nil\n}\n\n// keyToCertificate generates a new ECDSA private key and corresponding x509 certificate.\n// The certificate includes an extension that cryptographically ties it to the provided libp2p\n// private key to authenticate TLS connections.\nfunc keyToCertificate(sk ic.PrivKey, certTmpl *x509.Certificate) (*tls.Certificate, error) {\n\tcertKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// after calling CreateCertificate, these will end up in Certificate.Extensions\n\textension, err := GenerateSignedExtension(sk, certKey.Public())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcertTmpl.ExtraExtensions = append(certTmpl.ExtraExtensions, extension)\n\n\tcertDER, err := x509.CreateCertificate(rand.Reader, certTmpl, certTmpl, certKey.Public(), certKey)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &tls.Certificate{\n\t\tCertificate: [][]byte{certDER},\n\t\tPrivateKey:  certKey,\n\t}, nil\n}\n\n// certTemplate returns the template for generating an Identity's TLS certificates.\nfunc certTemplate() (*x509.Certificate, error) {\n\tbigNum := big.NewInt(1 << 62)\n\tsn, err := rand.Int(rand.Reader, bigNum)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsubjectSN, err := rand.Int(rand.Reader, bigNum)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &x509.Certificate{\n\t\tSerialNumber: sn,\n\t\tNotBefore:    time.Now().Add(-time.Hour),\n\t\tNotAfter:     time.Now().Add(certValidityPeriod),\n\t\t// According to RFC 3280, the issuer field must be set,\n\t\t// see https://datatracker.ietf.org/doc/html/rfc3280#section-4.1.2.4.\n\t\tSubject: pkix.Name{SerialNumber: subjectSN.String()},\n\t}, nil\n}\n"
  },
  {
    "path": "p2p/security/tls/crypto_test.go",
    "content": "package libp2ptls\n\nimport (\n\t\"crypto/x509\"\n\t\"encoding/hex\"\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto/pb\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestNewIdentityCertificates(t *testing.T) {\n\t_, key := createPeer(t)\n\tcn := \"a.test.name\"\n\temail := \"unittest@example.com\"\n\n\tt.Run(\"NewIdentity with default template\", func(t *testing.T) {\n\t\t// Generate an identity using the default template\n\t\tid, err := NewIdentity(key)\n\t\trequire.NoError(t, err)\n\n\t\t// Extract the x509 certificate\n\t\tx509Cert, err := x509.ParseCertificate(id.config.Certificates[0].Certificate[0])\n\t\trequire.NoError(t, err)\n\n\t\t// verify the common name and email are not set\n\t\trequire.Empty(t, x509Cert.Subject.CommonName)\n\t\trequire.Empty(t, x509Cert.EmailAddresses)\n\t})\n\n\tt.Run(\"NewIdentity with custom template\", func(t *testing.T) {\n\t\ttmpl, err := certTemplate()\n\t\trequire.NoError(t, err)\n\n\t\ttmpl.Subject.CommonName = cn\n\t\ttmpl.EmailAddresses = []string{email}\n\n\t\t// Generate an identity using the custom template\n\t\tid, err := NewIdentity(key, WithCertTemplate(tmpl))\n\t\trequire.NoError(t, err)\n\n\t\t// Extract the x509 certificate\n\t\tx509Cert, err := x509.ParseCertificate(id.config.Certificates[0].Certificate[0])\n\t\trequire.NoError(t, err)\n\n\t\t// verify the common name and email are set\n\t\tassert.Equal(t, cn, x509Cert.Subject.CommonName)\n\t\tassert.Equal(t, email, x509Cert.EmailAddresses[0])\n\t})\n}\n\nfunc TestVectors(t *testing.T) {\n\ttype testcase struct {\n\t\tname    string\n\t\tdata    string\n\t\tpeerID  string\n\t\tkeyType pb.KeyType\n\t\terror   string\n\t}\n\n\ttestcases := []testcase{\n\t\t{\n\t\t\tname:    \"ED25519 Peer ID\",\n\t\t\tdata:    \"308201ae30820156a0030201020204499602d2300a06082a8648ce3d040302302031123010060355040a13096c69627032702e696f310a300806035504051301313020170d3735303130313133303030305a180f34303936303130313133303030305a302031123010060355040a13096c69627032702e696f310a300806035504051301313059301306072a8648ce3d020106082a8648ce3d030107034200040c901d423c831ca85e27c73c263ba132721bb9d7a84c4f0380b2a6756fd601331c8870234dec878504c174144fa4b14b66a651691606d8173e55bd37e381569ea37c307a3078060a2b0601040183a25a0101046a3068042408011220a77f1d92fedb59dddaea5a1c4abd1ac2fbde7d7b879ed364501809923d7c11b90440d90d2769db992d5e6195dbb08e706b6651e024fda6cfb8846694a435519941cac215a8207792e42849cccc6cd8136c6e4bde92a58c5e08cfd4206eb5fe0bf909300a06082a8648ce3d0403020346003043021f50f6b6c52711a881778718238f650c9fb48943ae6ee6d28427dc6071ae55e702203625f116a7a454db9c56986c82a25682f7248ea1cb764d322ea983ed36a31b77\",\n\t\t\tpeerID:  \"12D3KooWM6CgA9iBFZmcYAHA6A2qvbAxqfkmrYiRQuz3XEsk4Ksv\",\n\t\t\tkeyType: pb.KeyType_Ed25519,\n\t\t},\n\t\t{\n\t\t\tname:    \"ECDSA Peer ID\",\n\t\t\tdata:    \"308201f63082019da0030201020204499602d2300a06082a8648ce3d040302302031123010060355040a13096c69627032702e696f310a300806035504051301313020170d3735303130313133303030305a180f34303936303130313133303030305a302031123010060355040a13096c69627032702e696f310a300806035504051301313059301306072a8648ce3d020106082a8648ce3d030107034200040c901d423c831ca85e27c73c263ba132721bb9d7a84c4f0380b2a6756fd601331c8870234dec878504c174144fa4b14b66a651691606d8173e55bd37e381569ea381c23081bf3081bc060a2b0601040183a25a01010481ad3081aa045f0803125b3059301306072a8648ce3d020106082a8648ce3d03010703420004bf30511f909414ebdd3242178fd290f093a551cf75c973155de0bb5a96fedf6cb5d52da7563e794b512f66e60c7f55ba8a3acf3dd72a801980d205e8a1ad29f2044730450220064ea8124774caf8f50e57f436aa62350ce652418c019df5d98a3ac666c9386a022100aa59d704a931b5f72fb9222cb6cc51f954d04a4e2e5450f8805fe8918f71eaae300a06082a8648ce3d04030203470030440220799395b0b6c1e940a7e4484705f610ab51ed376f19ff9d7c16757cfbf61b8d4302206205c03fbb0f95205c779be86581d3e31c01871ad5d1f3435bcf375cb0e5088a\",\n\t\t\tpeerID:  \"QmfXbAwNjJLXfesgztEHe8HwgVDCMMpZ9Eax1HYq6hn9uE\",\n\t\t\tkeyType: pb.KeyType_ECDSA,\n\t\t},\n\t\t{\n\t\t\tname:    \"secp256k1 Peer ID\",\n\t\t\tdata:    \"308201ba3082015fa0030201020204499602d2300a06082a8648ce3d040302302031123010060355040a13096c69627032702e696f310a300806035504051301313020170d3735303130313133303030305a180f34303936303130313133303030305a302031123010060355040a13096c69627032702e696f310a300806035504051301313059301306072a8648ce3d020106082a8648ce3d030107034200040c901d423c831ca85e27c73c263ba132721bb9d7a84c4f0380b2a6756fd601331c8870234dec878504c174144fa4b14b66a651691606d8173e55bd37e381569ea38184308181307f060a2b0601040183a25a01010471306f0425080212210206dc6968726765b820f050263ececf7f71e4955892776c0970542efd689d2382044630440220145e15a991961f0d08cd15425bb95ec93f6ffa03c5a385eedc34ecf464c7a8ab022026b3109b8a3f40ef833169777eb2aa337cfb6282f188de0666d1bcec2a4690dd300a06082a8648ce3d0403020349003046022100e1a217eeef9ec9204b3f774a08b70849646b6a1e6b8b27f93dc00ed58545d9fe022100b00dafa549d0f03547878338c7b15e7502888f6d45db387e5ae6b5d46899cef0\",\n\t\t\tpeerID:  \"16Uiu2HAkutTMoTzDw1tCvSRtu6YoixJwS46S1ZFxW8hSx9fWHiPs\",\n\t\t\tkeyType: pb.KeyType_Secp256k1,\n\t\t},\n\t\t{\n\t\t\tname:  \"Invalid certificate\",\n\t\t\tdata:  \"308201f73082019da0030201020204499602d2300a06082a8648ce3d040302302031123010060355040a13096c69627032702e696f310a300806035504051301313020170d3735303130313133303030305a180f34303936303130313133303030305a302031123010060355040a13096c69627032702e696f310a300806035504051301313059301306072a8648ce3d020106082a8648ce3d030107034200040c901d423c831ca85e27c73c263ba132721bb9d7a84c4f0380b2a6756fd601331c8870234dec878504c174144fa4b14b66a651691606d8173e55bd37e381569ea381c23081bf3081bc060a2b0601040183a25a01010481ad3081aa045f0803125b3059301306072a8648ce3d020106082a8648ce3d03010703420004bf30511f909414ebdd3242178fd290f093a551cf75c973155de0bb5a96fedf6cb5d52da7563e794b512f66e60c7f55ba8a3acf3dd72a801980d205e8a1ad29f204473045022100bb6e03577b7cc7a3cd1558df0da2b117dfdcc0399bc2504ebe7de6f65cade72802206de96e2a5be9b6202adba24ee0362e490641ac45c240db71fe955f2c5cf8df6e300a06082a8648ce3d0403020348003045022100e847f267f43717358f850355bdcabbefb2cfbf8a3c043b203a14788a092fe8db022027c1d04a2d41fd6b57a7e8b3989e470325de4406e52e084e34a3fd56eef0d0df\",\n\t\t\terror: \"signature invalid\",\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tdata, err := hex.DecodeString(tc.data)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tcert, err := x509.ParseCertificate(data)\n\t\t\trequire.NoError(t, err)\n\t\t\tkey, err := PubKeyFromCertChain([]*x509.Certificate{cert})\n\t\t\tif tc.error != \"\" {\n\t\t\t\trequire.Error(t, err)\n\t\t\t\trequire.Contains(t, err.Error(), tc.error)\n\t\t\t\treturn\n\t\t\t}\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, tc.keyType, key.Type())\n\t\t\tid, err := peer.IDFromPublicKey(key)\n\t\t\trequire.NoError(t, err)\n\t\t\texpectedID, err := peer.Decode(tc.peerID)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, expectedID, id)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "p2p/security/tls/extension.go",
    "content": "package libp2ptls\n\nvar extensionPrefix = []int{1, 3, 6, 1, 4, 1, 53594}\n\n// getPrefixedExtensionID returns an Object Identifier\n// that can be used in x509 Certificates.\nfunc getPrefixedExtensionID(suffix []int) []int {\n\treturn append(extensionPrefix, suffix...)\n}\n\n// extensionIDEqual compares two extension IDs.\nfunc extensionIDEqual(a, b []int) bool {\n\tif len(a) != len(b) {\n\t\treturn false\n\t}\n\tfor i := range a {\n\t\tif a[i] != b[i] {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "p2p/security/tls/extension_test.go",
    "content": "package libp2ptls\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestExtensionGenerating(t *testing.T) {\n\trequire.Equal(t, []int{1, 3, 6, 1, 4, 1, 53594, 13, 37}, getPrefixedExtensionID([]int{13, 37}))\n}\n\nfunc TestExtensionComparison(t *testing.T) {\n\trequire.True(t, extensionIDEqual([]int{1, 2, 3, 4}, []int{1, 2, 3, 4}))\n\trequire.False(t, extensionIDEqual([]int{1, 2, 3, 4}, []int{1, 2, 3}))\n\trequire.False(t, extensionIDEqual([]int{1, 2, 3}, []int{1, 2, 3, 4}))\n\trequire.False(t, extensionIDEqual([]int{1, 2, 3, 4}, []int{4, 3, 2, 1}))\n}\n"
  },
  {
    "path": "p2p/security/tls/transport.go",
    "content": "package libp2ptls\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"runtime/debug\"\n\n\tci \"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\t\"github.com/libp2p/go-libp2p/core/sec\"\n\t\"github.com/libp2p/go-libp2p/p2p/canonicallog\"\n\ttptu \"github.com/libp2p/go-libp2p/p2p/net/upgrader\"\n\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\n// ID is the protocol ID (used when negotiating with multistream)\nconst ID = \"/tls/1.0.0\"\n\n// Transport constructs secure communication sessions for a peer.\ntype Transport struct {\n\tidentity *Identity\n\n\tlocalPeer  peer.ID\n\tprivKey    ci.PrivKey\n\tmuxers     []protocol.ID\n\tprotocolID protocol.ID\n}\n\nvar _ sec.SecureTransport = &Transport{}\n\n// New creates a TLS encrypted transport\nfunc New(id protocol.ID, key ci.PrivKey, muxers []tptu.StreamMuxer) (*Transport, error) {\n\tlocalPeer, err := peer.IDFromPrivateKey(key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tmuxerIDs := make([]protocol.ID, 0, len(muxers))\n\tfor _, m := range muxers {\n\t\tmuxerIDs = append(muxerIDs, m.ID)\n\t}\n\tt := &Transport{\n\t\tprotocolID: id,\n\t\tlocalPeer:  localPeer,\n\t\tprivKey:    key,\n\t\tmuxers:     muxerIDs,\n\t}\n\n\tidentity, err := NewIdentity(key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tt.identity = identity\n\treturn t, nil\n}\n\n// SecureInbound runs the TLS handshake as a server.\n// If p is empty, connections from any peer are accepted.\nfunc (t *Transport) SecureInbound(ctx context.Context, insecure net.Conn, p peer.ID) (sec.SecureConn, error) {\n\tconfig, keyCh := t.identity.ConfigForPeer(p)\n\tmuxers := make([]string, 0, len(t.muxers))\n\tfor _, muxer := range t.muxers {\n\t\tmuxers = append(muxers, string(muxer))\n\t}\n\t// TLS' ALPN selection lets the server select the protocol, preferring the server's preferences.\n\t// We want to prefer the client's preference though.\n\tgetConfigForClient := config.GetConfigForClient\n\tconfig.GetConfigForClient = func(info *tls.ClientHelloInfo) (*tls.Config, error) {\n\talpnLoop:\n\t\tfor _, proto := range info.SupportedProtos {\n\t\t\tfor _, m := range muxers {\n\t\t\t\tif m == proto {\n\t\t\t\t\t// Match found. Select this muxer, as it's the client's preference.\n\t\t\t\t\t// There's no need to add the \"libp2p\" entry here.\n\t\t\t\t\tconfig.NextProtos = []string{proto}\n\t\t\t\t\tbreak alpnLoop\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif getConfigForClient != nil {\n\t\t\treturn getConfigForClient(info)\n\t\t}\n\t\treturn config, nil\n\t}\n\tconfig.NextProtos = append(muxers, config.NextProtos...)\n\tcs, err := t.handshake(ctx, tls.Server(insecure, config), keyCh)\n\tif err != nil {\n\t\taddr, maErr := manet.FromNetAddr(insecure.RemoteAddr())\n\t\tif maErr == nil {\n\t\t\tcanonicallog.LogPeerStatus(100, p, addr, \"handshake_failure\", \"tls\", \"err\", err.Error())\n\t\t}\n\t\tinsecure.Close()\n\t}\n\treturn cs, err\n}\n\n// SecureOutbound runs the TLS handshake as a client.\n// Note that SecureOutbound will not return an error if the server doesn't\n// accept the certificate. This is due to the fact that in TLS 1.3, the client\n// sends its certificate and the ClientFinished in the same flight, and can send\n// application data immediately afterwards.\n// If the handshake fails, the server will close the connection. The client will\n// notice this after 1 RTT when calling Read.\nfunc (t *Transport) SecureOutbound(ctx context.Context, insecure net.Conn, p peer.ID) (sec.SecureConn, error) {\n\tconfig, keyCh := t.identity.ConfigForPeer(p)\n\tmuxers := make([]string, 0, len(t.muxers))\n\tfor _, muxer := range t.muxers {\n\t\tmuxers = append(muxers, (string)(muxer))\n\t}\n\t// Prepend the preferred muxers list to TLS config.\n\tconfig.NextProtos = append(muxers, config.NextProtos...)\n\tcs, err := t.handshake(ctx, tls.Client(insecure, config), keyCh)\n\tif err != nil {\n\t\tinsecure.Close()\n\t}\n\treturn cs, err\n}\n\nfunc (t *Transport) handshake(ctx context.Context, tlsConn *tls.Conn, keyCh <-chan ci.PubKey) (_sconn sec.SecureConn, err error) {\n\tdefer func() {\n\t\tif rerr := recover(); rerr != nil {\n\t\t\tfmt.Fprintf(os.Stderr, \"panic in TLS handshake: %s\\n%s\\n\", rerr, debug.Stack())\n\t\t\terr = fmt.Errorf(\"panic in TLS handshake: %s\", rerr)\n\n\t\t}\n\t}()\n\n\t// handshaking...\n\tif err := tlsConn.HandshakeContext(ctx); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Should be ready by this point, don't block.\n\tvar remotePubKey ci.PubKey\n\tselect {\n\tcase remotePubKey = <-keyCh:\n\tdefault:\n\t}\n\tif remotePubKey == nil {\n\t\treturn nil, errors.New(\"go-libp2p tls BUG: expected remote pub key to be set\")\n\t}\n\n\treturn t.setupConn(tlsConn, remotePubKey)\n}\n\nfunc (t *Transport) setupConn(tlsConn *tls.Conn, remotePubKey ci.PubKey) (sec.SecureConn, error) {\n\tremotePeerID, err := peer.IDFromPublicKey(remotePubKey)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tnextProto := tlsConn.ConnectionState().NegotiatedProtocol\n\t// The special ALPN extension value \"libp2p\" is used by libp2p versions\n\t// that don't support early muxer negotiation. If we see this sepcial\n\t// value selected, that means we are handshaking with a version that does\n\t// not support early muxer negotiation. In this case return empty nextProto\n\t// to indicate no muxer is selected.\n\tif nextProto == \"libp2p\" {\n\t\tnextProto = \"\"\n\t}\n\n\treturn &conn{\n\t\tConn:         tlsConn,\n\t\tlocalPeer:    t.localPeer,\n\t\tremotePeer:   remotePeerID,\n\t\tremotePubKey: remotePubKey,\n\t\tconnectionState: network.ConnectionState{\n\t\t\tStreamMultiplexer:         protocol.ID(nextProto),\n\t\t\tUsedEarlyMuxerNegotiation: nextProto != \"\",\n\t\t},\n\t}, nil\n}\n\nfunc (t *Transport) ID() protocol.ID {\n\treturn t.protocolID\n}\n"
  },
  {
    "path": "p2p/security/tls/transport_test.go",
    "content": "package libp2ptls\n\nimport (\n\t\"context\"\n\t\"crypto\"\n\t\"crypto/ecdsa\"\n\t\"crypto/elliptic\"\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"crypto/x509/pkix\"\n\t\"encoding/asn1\"\n\t\"fmt\"\n\t\"math/big\"\n\tmrand \"math/rand\"\n\t\"net\"\n\t\"runtime\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\tic \"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\t\"github.com/libp2p/go-libp2p/core/sec\"\n\ttptu \"github.com/libp2p/go-libp2p/p2p/net/upgrader\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc createPeer(t *testing.T) (peer.ID, ic.PrivKey) {\n\tvar priv ic.PrivKey\n\tvar err error\n\tswitch mrand.Int() % 4 {\n\tcase 0:\n\t\tpriv, _, err = ic.GenerateECDSAKeyPair(rand.Reader)\n\tcase 1:\n\t\tpriv, _, err = ic.GenerateRSAKeyPair(2048, rand.Reader)\n\tcase 2:\n\t\tpriv, _, err = ic.GenerateEd25519Key(rand.Reader)\n\tcase 3:\n\t\tpriv, _, err = ic.GenerateSecp256k1Key(rand.Reader)\n\t}\n\trequire.NoError(t, err)\n\tid, err := peer.IDFromPrivateKey(priv)\n\trequire.NoError(t, err)\n\tt.Logf(\"using a %s key: %s\", priv.Type(), id)\n\treturn id, priv\n}\n\nfunc connect(t *testing.T) (net.Conn, net.Conn) {\n\tln, err := net.ListenTCP(\"tcp\", nil)\n\trequire.NoError(t, err)\n\tdefer ln.Close()\n\tserverConnChan := make(chan *net.TCPConn)\n\tgo func() {\n\t\tconn, err := ln.Accept()\n\t\tassert.NoError(t, err)\n\t\tsconn := conn.(*net.TCPConn)\n\t\tserverConnChan <- sconn\n\t}()\n\tconn, err := net.DialTCP(\"tcp\", nil, ln.Addr().(*net.TCPAddr))\n\trequire.NoError(t, err)\n\tsconn := <-serverConnChan\n\t// On Windows we have to set linger to 0, otherwise we'll occasionally run into errors like the following:\n\t// \"connectex: Only one usage of each socket address (protocol/network address/port) is normally permitted.\"\n\t// See https://github.com/libp2p/go-libp2p/issues/1529.\n\tconn.SetLinger(0)\n\tsconn.SetLinger(0)\n\tt.Cleanup(func() {\n\t\tconn.Close()\n\t\tsconn.Close()\n\t})\n\treturn conn, sconn\n}\n\nfunc isWindowsTCPCloseError(err error) bool {\n\tif runtime.GOOS != \"windows\" {\n\t\treturn false\n\t}\n\treturn strings.Contains(err.Error(), \"wsarecv: An existing connection was forcibly closed by the remote host\")\n}\n\nfunc TestHandshakeSucceeds(t *testing.T) {\n\tclientID, clientKey := createPeer(t)\n\tserverID, serverKey := createPeer(t)\n\n\thandshake := func(t *testing.T, clientTransport *Transport, serverTransport *Transport) {\n\t\tclientInsecureConn, serverInsecureConn := connect(t)\n\n\t\tserverConnChan := make(chan sec.SecureConn)\n\t\tgo func() {\n\t\t\tserverConn, err := serverTransport.SecureInbound(context.Background(), serverInsecureConn, \"\")\n\t\t\trequire.NoError(t, err)\n\t\t\tserverConnChan <- serverConn\n\t\t}()\n\n\t\tclientConn, err := clientTransport.SecureOutbound(context.Background(), clientInsecureConn, serverID)\n\t\trequire.NoError(t, err)\n\t\tdefer clientConn.Close()\n\n\t\tvar serverConn sec.SecureConn\n\t\tselect {\n\t\tcase serverConn = <-serverConnChan:\n\t\tcase <-time.After(250 * time.Millisecond):\n\t\t\tt.Fatal(\"expected the server to accept a connection\")\n\t\t}\n\t\tdefer serverConn.Close()\n\n\t\trequire.Equal(t, clientConn.LocalPeer(), clientID)\n\t\trequire.Equal(t, serverConn.LocalPeer(), serverID)\n\t\trequire.Equal(t, clientConn.RemotePeer(), serverID)\n\t\trequire.Equal(t, serverConn.RemotePeer(), clientID)\n\t\trequire.True(t, clientConn.RemotePublicKey().Equals(serverKey.GetPublic()), \"server public key mismatch\")\n\t\trequire.True(t, serverConn.RemotePublicKey().Equals(clientKey.GetPublic()), \"client public key mismatch\")\n\t\t// exchange some data\n\t\t_, err = serverConn.Write([]byte(\"foobar\"))\n\t\trequire.NoError(t, err)\n\t\tb := make([]byte, 6)\n\t\t_, err = clientConn.Read(b)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"foobar\", string(b))\n\t}\n\n\t// Use standard transports with default TLS configuration\n\tclientTransport, err := New(ID, clientKey, nil)\n\trequire.NoError(t, err)\n\tserverTransport, err := New(ID, serverKey, nil)\n\trequire.NoError(t, err)\n\n\tt.Run(\"standard TLS with extension not critical\", func(t *testing.T) {\n\t\thandshake(t, clientTransport, serverTransport)\n\t})\n\n\tt.Run(\"standard TLS with extension critical\", func(t *testing.T) {\n\t\textensionCritical = true\n\t\tt.Cleanup(func() { extensionCritical = false })\n\n\t\thandshake(t, clientTransport, serverTransport)\n\t})\n\n\t// Use transports with custom TLS certificates\n\n\t// override client identity to use a custom certificate\n\tclientCertTmlp, err := certTemplate()\n\trequire.NoError(t, err)\n\n\tclientCertTmlp.Subject.CommonName = \"client.test.name\"\n\tclientCertTmlp.EmailAddresses = []string{\"client-unittest@example.com\"}\n\n\tclientTransport.identity, err = NewIdentity(clientKey, WithCertTemplate(clientCertTmlp))\n\trequire.NoError(t, err)\n\n\t// override server identity to use a custom certificate\n\tserverCertTmpl, err := certTemplate()\n\trequire.NoError(t, err)\n\n\tserverCertTmpl.Subject.CommonName = \"server.test.name\"\n\tserverCertTmpl.EmailAddresses = []string{\"server-unittest@example.com\"}\n\n\tserverTransport.identity, err = NewIdentity(serverKey, WithCertTemplate(serverCertTmpl))\n\trequire.NoError(t, err)\n\n\tt.Run(\"custom TLS with extension not critical\", func(t *testing.T) {\n\t\thandshake(t, clientTransport, serverTransport)\n\t})\n\n\tt.Run(\"custom TLS with extension critical\", func(t *testing.T) {\n\t\textensionCritical = true\n\t\tt.Cleanup(func() { extensionCritical = false })\n\n\t\thandshake(t, clientTransport, serverTransport)\n\t})\n}\n\ntype testcase struct {\n\tclientProtos   []protocol.ID\n\tserverProtos   []protocol.ID\n\texpectedResult protocol.ID\n}\n\nfunc TestHandshakeWithNextProtoSucceeds(t *testing.T) {\n\ttests := []testcase{\n\t\t{\n\t\t\tclientProtos:   []protocol.ID{\"muxer1\", \"muxer2\"},\n\t\t\tserverProtos:   []protocol.ID{\"muxer2\", \"muxer1\"},\n\t\t\texpectedResult: \"muxer1\",\n\t\t},\n\t\t{\n\t\t\tclientProtos:   []protocol.ID{\"muxer1\", \"muxer2\", \"libp2p\"},\n\t\t\tserverProtos:   []protocol.ID{\"muxer2\", \"muxer1\", \"libp2p\"},\n\t\t\texpectedResult: \"muxer1\",\n\t\t},\n\t\t{\n\t\t\tclientProtos:   []protocol.ID{\"muxer1\", \"libp2p\"},\n\t\t\tserverProtos:   []protocol.ID{\"libp2p\"},\n\t\t\texpectedResult: \"\",\n\t\t},\n\t\t{\n\t\t\tclientProtos:   []protocol.ID{\"libp2p\"},\n\t\t\tserverProtos:   []protocol.ID{\"libp2p\"},\n\t\t\texpectedResult: \"\",\n\t\t},\n\t\t{\n\t\t\tclientProtos:   []protocol.ID{\"muxer1\"},\n\t\t\tserverProtos:   []protocol.ID{},\n\t\t\texpectedResult: \"\",\n\t\t},\n\t\t{\n\t\t\tclientProtos:   []protocol.ID{},\n\t\t\tserverProtos:   []protocol.ID{\"muxer1\"},\n\t\t\texpectedResult: \"\",\n\t\t},\n\t\t{\n\t\t\tclientProtos:   []protocol.ID{\"muxer2\"},\n\t\t\tserverProtos:   []protocol.ID{\"muxer1\"},\n\t\t\texpectedResult: \"\",\n\t\t},\n\t}\n\n\tclientID, clientKey := createPeer(t)\n\tserverID, serverKey := createPeer(t)\n\n\thandshake := func(t *testing.T, clientTransport *Transport, serverTransport *Transport, expectedMuxer protocol.ID) {\n\t\tclientInsecureConn, serverInsecureConn := connect(t)\n\n\t\tserverConnChan := make(chan sec.SecureConn)\n\t\tgo func() {\n\t\t\tserverConn, err := serverTransport.SecureInbound(context.Background(), serverInsecureConn, \"\")\n\t\t\trequire.NoError(t, err)\n\t\t\tserverConnChan <- serverConn\n\t\t}()\n\n\t\tclientConn, err := clientTransport.SecureOutbound(context.Background(), clientInsecureConn, serverID)\n\t\trequire.NoError(t, err)\n\t\tdefer clientConn.Close()\n\n\t\tvar serverConn sec.SecureConn\n\t\tselect {\n\t\tcase serverConn = <-serverConnChan:\n\t\tcase <-time.After(250 * time.Millisecond):\n\t\t\tt.Fatal(\"expected the server to accept a connection\")\n\t\t}\n\t\tdefer serverConn.Close()\n\n\t\trequire.Equal(t, clientID, clientConn.LocalPeer())\n\t\trequire.Equal(t, serverID, serverConn.LocalPeer())\n\t\trequire.Equal(t, serverID, clientConn.RemotePeer())\n\t\trequire.Equal(t, clientID, serverConn.RemotePeer())\n\t\trequire.True(t, clientConn.RemotePublicKey().Equals(serverKey.GetPublic()), \"server public key mismatch\")\n\t\trequire.True(t, serverConn.RemotePublicKey().Equals(clientKey.GetPublic()), \"client public key mismatch\")\n\t\trequire.Equal(t, expectedMuxer, clientConn.ConnState().StreamMultiplexer)\n\t\trequire.Equal(t, expectedMuxer != \"\", clientConn.ConnState().UsedEarlyMuxerNegotiation)\n\t\t// exchange some data\n\t\t_, err = serverConn.Write([]byte(\"foobar\"))\n\t\trequire.NoError(t, err)\n\t\tb := make([]byte, 6)\n\t\t_, err = clientConn.Read(b)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"foobar\", string(b))\n\t}\n\n\t// Iterate through the StreamMultiplexer combinations.\n\tfor _, test := range tests {\n\t\tclientMuxers := make([]tptu.StreamMuxer, 0, len(test.clientProtos))\n\t\tfor _, id := range test.clientProtos {\n\t\t\tclientMuxers = append(clientMuxers, tptu.StreamMuxer{ID: id})\n\t\t}\n\t\tclientTransport, err := New(ID, clientKey, clientMuxers)\n\t\trequire.NoError(t, err)\n\t\tserverMuxers := make([]tptu.StreamMuxer, 0, len(test.clientProtos))\n\t\tfor _, id := range test.serverProtos {\n\t\t\tserverMuxers = append(serverMuxers, tptu.StreamMuxer{ID: id})\n\t\t}\n\t\tserverTransport, err := New(ID, serverKey, serverMuxers)\n\t\trequire.NoError(t, err)\n\n\t\tt.Run(\"TLS handshake with ALPN extension\", func(t *testing.T) {\n\t\t\thandshake(t, clientTransport, serverTransport, test.expectedResult)\n\t\t})\n\t}\n}\n\n// crypto/tls' cancellation logic works by spinning up a separate Go routine that watches the ctx.\n// If the ctx is canceled, it kills the handshake.\n// We need to make sure that the handshake doesn't complete before that Go routine picks up the cancellation.\ntype delayedConn struct {\n\tnet.Conn\n\tdelay time.Duration\n}\n\nfunc (c *delayedConn) Read(b []byte) (int, error) {\n\ttime.Sleep(c.delay)\n\treturn c.Conn.Read(b)\n}\n\nfunc TestHandshakeConnectionCancellations(t *testing.T) {\n\t_, clientKey := createPeer(t)\n\tserverID, serverKey := createPeer(t)\n\n\tclientTransport, err := New(ID, clientKey, nil)\n\trequire.NoError(t, err)\n\tserverTransport, err := New(ID, serverKey, nil)\n\trequire.NoError(t, err)\n\n\tt.Run(\"cancel outgoing connection\", func(t *testing.T) {\n\t\tclientInsecureConn, serverInsecureConn := connect(t)\n\n\t\terrChan := make(chan error)\n\t\tgo func() {\n\t\t\tconn, err := serverTransport.SecureInbound(context.Background(), serverInsecureConn, \"\")\n\t\t\t// crypto/tls' context handling works by spinning up a separate Go routine that watches the context,\n\t\t\t// and closes the underlying connection when that context is canceled.\n\t\t\t// It is therefore not guaranteed (but very likely) that this happens _during_ the TLS handshake.\n\t\t\tif err == nil {\n\t\t\t\t_, err = conn.Read([]byte{0})\n\t\t\t}\n\t\t\terrChan <- err\n\t\t}()\n\t\tctx, cancel := context.WithCancel(context.Background())\n\t\tcancel()\n\t\t_, err = clientTransport.SecureOutbound(ctx, &delayedConn{Conn: clientInsecureConn, delay: 5 * time.Millisecond}, serverID)\n\t\trequire.ErrorIs(t, err, context.Canceled)\n\t\trequire.Error(t, <-errChan)\n\t})\n\n\tt.Run(\"cancel incoming connection\", func(t *testing.T) {\n\t\tclientInsecureConn, serverInsecureConn := connect(t)\n\n\t\terrChan := make(chan error)\n\t\tgo func() {\n\t\t\tctx, cancel := context.WithCancel(context.Background())\n\t\t\tcancel()\n\t\t\tconn, err := serverTransport.SecureInbound(ctx, &delayedConn{Conn: serverInsecureConn, delay: 5 * time.Millisecond}, \"\")\n\t\t\t// crypto/tls' context handling works by spinning up a separate Go routine that watches the context,\n\t\t\t// and closes the underlying connection when that context is canceled.\n\t\t\t// It is therefore not guaranteed (but very likely) that this happens _during_ the TLS handshake.\n\t\t\tif err == nil {\n\t\t\t\t_, err = conn.Read([]byte{0})\n\t\t\t}\n\t\t\terrChan <- err\n\t\t}()\n\t\t_, err = clientTransport.SecureOutbound(context.Background(), clientInsecureConn, serverID)\n\t\trequire.Error(t, err)\n\t\trequire.ErrorIs(t, <-errChan, context.Canceled)\n\t})\n}\n\nfunc TestPeerIDMismatch(t *testing.T) {\n\t_, clientKey := createPeer(t)\n\tserverID, serverKey := createPeer(t)\n\n\tserverTransport, err := New(ID, serverKey, nil)\n\trequire.NoError(t, err)\n\tclientTransport, err := New(ID, clientKey, nil)\n\trequire.NoError(t, err)\n\n\tt.Run(\"for outgoing connections\", func(t *testing.T) {\n\t\tclientInsecureConn, serverInsecureConn := connect(t)\n\n\t\terrChan := make(chan error)\n\t\tgo func() {\n\t\t\tconn, err := serverTransport.SecureInbound(context.Background(), serverInsecureConn, \"\")\n\t\t\t// crypto/tls' context handling works by spinning up a separate Go routine that watches the context,\n\t\t\t// and closes the underlying connection when that context is canceled.\n\t\t\t// It is therefore not guaranteed (but very likely) that this happens _during_ the TLS handshake.\n\t\t\tif err == nil {\n\t\t\t\t_, err = conn.Read([]byte{0})\n\t\t\t}\n\t\t\terrChan <- err\n\t\t}()\n\n\t\t// dial, but expect the wrong peer ID\n\t\tthirdPartyID, _ := createPeer(t)\n\t\t_, err = clientTransport.SecureOutbound(context.Background(), clientInsecureConn, thirdPartyID)\n\t\trequire.Error(t, err)\n\t\tvar mismatchErr sec.ErrPeerIDMismatch\n\t\trequire.ErrorAs(t, err, &mismatchErr)\n\t\trequire.Equal(t, mismatchErr.Expected, thirdPartyID)\n\t\trequire.Equal(t, mismatchErr.Actual, serverID)\n\n\t\tvar serverErr error\n\t\tselect {\n\t\tcase serverErr = <-errChan:\n\t\tcase <-time.After(250 * time.Millisecond):\n\t\t\tt.Fatal(\"expected handshake to return on the server side\")\n\t\t}\n\t\trequire.Error(t, serverErr)\n\t\trequire.Contains(t, serverErr.Error(), \"tls: bad certificate\")\n\t})\n\n\tt.Run(\"for incoming connections\", func(t *testing.T) {\n\t\tclientInsecureConn, serverInsecureConn := connect(t)\n\n\t\terrChan := make(chan error)\n\t\tthirdPartyID, _ := createPeer(t)\n\t\tgo func() {\n\t\t\t// expect the wrong peer ID\n\t\t\t_, err := serverTransport.SecureInbound(context.Background(), serverInsecureConn, thirdPartyID)\n\t\t\terrChan <- err\n\t\t}()\n\n\t\tconn, err := clientTransport.SecureOutbound(context.Background(), clientInsecureConn, serverID)\n\t\trequire.NoError(t, err)\n\t\t_, err = conn.Read([]byte{0})\n\t\trequire.Error(t, err)\n\t\trequire.Contains(t, err.Error(), \"tls: bad certificate\")\n\n\t\tvar serverErr error\n\t\tselect {\n\t\tcase serverErr = <-errChan:\n\t\tcase <-time.After(250 * time.Millisecond):\n\t\t\tt.Fatal(\"expected handshake to return on the server side\")\n\t\t}\n\t\trequire.Error(t, serverErr)\n\t\tvar mismatchErr sec.ErrPeerIDMismatch\n\t\trequire.ErrorAs(t, serverErr, &mismatchErr)\n\t\trequire.Equal(t, mismatchErr.Expected, thirdPartyID)\n\t\trequire.Equal(t, mismatchErr.Actual, clientTransport.localPeer)\n\t})\n}\n\nfunc TestInvalidCerts(t *testing.T) {\n\t_, clientKey := createPeer(t)\n\tserverID, serverKey := createPeer(t)\n\n\ttype transform struct {\n\t\tname     string\n\t\tapply    func(*Identity)\n\t\tcheckErr func(*testing.T, error) // the error that the side validating the chain gets\n\t}\n\n\tinvalidateCertChain := func(identity *Identity) {\n\t\tswitch identity.config.Certificates[0].PrivateKey.(type) {\n\t\tcase *rsa.PrivateKey:\n\t\t\tkey, err := rsa.GenerateKey(rand.Reader, 2048)\n\t\t\trequire.NoError(t, err)\n\t\t\tidentity.config.Certificates[0].PrivateKey = key\n\t\tcase *ecdsa.PrivateKey:\n\t\t\tkey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)\n\t\t\trequire.NoError(t, err)\n\t\t\tidentity.config.Certificates[0].PrivateKey = key\n\t\tdefault:\n\t\t\tt.Fatal(\"unexpected private key type\")\n\t\t}\n\t}\n\n\ttwoCerts := func(identity *Identity) {\n\t\ttmpl := &x509.Certificate{SerialNumber: big.NewInt(1)}\n\t\tkey1, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)\n\t\trequire.NoError(t, err)\n\t\tkey2, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)\n\t\trequire.NoError(t, err)\n\t\tcert1DER, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key1.Public(), key1)\n\t\trequire.NoError(t, err)\n\t\tcert1, err := x509.ParseCertificate(cert1DER)\n\t\trequire.NoError(t, err)\n\t\tcert2DER, err := x509.CreateCertificate(rand.Reader, tmpl, cert1, key2.Public(), key1)\n\t\trequire.NoError(t, err)\n\t\tidentity.config.Certificates = []tls.Certificate{{\n\t\t\tCertificate: [][]byte{cert2DER, cert1DER},\n\t\t\tPrivateKey:  key2,\n\t\t}}\n\t}\n\n\tgetCertWithKey := func(key crypto.Signer, tmpl *x509.Certificate) tls.Certificate {\n\t\tcert, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key)\n\t\trequire.NoError(t, err)\n\t\treturn tls.Certificate{\n\t\t\tCertificate: [][]byte{cert},\n\t\t\tPrivateKey:  key,\n\t\t}\n\t}\n\n\tgetCert := func(tmpl *x509.Certificate) tls.Certificate {\n\t\tkey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)\n\t\trequire.NoError(t, err)\n\t\treturn getCertWithKey(key, tmpl)\n\t}\n\n\texpiredCert := func(identity *Identity) {\n\t\tcert := getCert(&x509.Certificate{\n\t\t\tSerialNumber: big.NewInt(1),\n\t\t\tNotBefore:    time.Now().Add(-time.Hour),\n\t\t\tNotAfter:     time.Now().Add(-time.Minute),\n\t\t\tExtraExtensions: []pkix.Extension{\n\t\t\t\t{Id: extensionID, Value: []byte(\"foobar\")},\n\t\t\t},\n\t\t})\n\t\tidentity.config.Certificates = []tls.Certificate{cert}\n\t}\n\n\tnoKeyExtension := func(identity *Identity) {\n\t\tcert := getCert(&x509.Certificate{\n\t\t\tSerialNumber: big.NewInt(1),\n\t\t\tNotBefore:    time.Now().Add(-time.Hour),\n\t\t\tNotAfter:     time.Now().Add(time.Hour),\n\t\t})\n\t\tidentity.config.Certificates = []tls.Certificate{cert}\n\t}\n\n\tunparseableKeyExtension := func(identity *Identity) {\n\t\tcert := getCert(&x509.Certificate{\n\t\t\tSerialNumber: big.NewInt(1),\n\t\t\tNotBefore:    time.Now().Add(-time.Hour),\n\t\t\tNotAfter:     time.Now().Add(time.Hour),\n\t\t\tExtraExtensions: []pkix.Extension{\n\t\t\t\t{Id: extensionID, Value: []byte(\"foobar\")},\n\t\t\t},\n\t\t})\n\t\tidentity.config.Certificates = []tls.Certificate{cert}\n\t}\n\n\tunparseableKey := func(identity *Identity) {\n\t\tdata, err := asn1.Marshal(signedKey{PubKey: []byte(\"foobar\")})\n\t\trequire.NoError(t, err)\n\t\tcert := getCert(&x509.Certificate{\n\t\t\tSerialNumber: big.NewInt(1),\n\t\t\tNotBefore:    time.Now().Add(-time.Hour),\n\t\t\tNotAfter:     time.Now().Add(time.Hour),\n\t\t\tExtraExtensions: []pkix.Extension{\n\t\t\t\t{Id: extensionID, Value: data},\n\t\t\t},\n\t\t})\n\t\tidentity.config.Certificates = []tls.Certificate{cert}\n\t}\n\n\ttooShortSignature := func(identity *Identity) {\n\t\tkey, _, err := ic.GenerateSecp256k1Key(rand.Reader)\n\t\trequire.NoError(t, err)\n\t\tkeyBytes, err := ic.MarshalPublicKey(key.GetPublic())\n\t\trequire.NoError(t, err)\n\t\tdata, err := asn1.Marshal(signedKey{\n\t\t\tPubKey:    keyBytes,\n\t\t\tSignature: []byte(\"foobar\"),\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tcert := getCert(&x509.Certificate{\n\t\t\tSerialNumber: big.NewInt(1),\n\t\t\tNotBefore:    time.Now().Add(-time.Hour),\n\t\t\tNotAfter:     time.Now().Add(time.Hour),\n\t\t\tExtraExtensions: []pkix.Extension{\n\t\t\t\t{Id: extensionID, Value: data},\n\t\t\t},\n\t\t})\n\t\tidentity.config.Certificates = []tls.Certificate{cert}\n\t}\n\n\tinvalidSignature := func(identity *Identity) {\n\t\tkey, _, err := ic.GenerateSecp256k1Key(rand.Reader)\n\t\trequire.NoError(t, err)\n\t\tkeyBytes, err := ic.MarshalPublicKey(key.GetPublic())\n\t\trequire.NoError(t, err)\n\t\tsignature, err := key.Sign([]byte(\"foobar\"))\n\t\trequire.NoError(t, err)\n\t\tdata, err := asn1.Marshal(signedKey{\n\t\t\tPubKey:    keyBytes,\n\t\t\tSignature: signature,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tcert := getCert(&x509.Certificate{\n\t\t\tSerialNumber: big.NewInt(1),\n\t\t\tNotBefore:    time.Now().Add(-time.Hour),\n\t\t\tNotAfter:     time.Now().Add(time.Hour),\n\t\t\tExtraExtensions: []pkix.Extension{\n\t\t\t\t{Id: extensionID, Value: data},\n\t\t\t},\n\t\t})\n\t\tidentity.config.Certificates = []tls.Certificate{cert}\n\t}\n\n\ttransforms := []transform{\n\t\t{\n\t\t\tname:  \"private key used in the TLS handshake doesn't match the public key in the cert\",\n\t\t\tapply: invalidateCertChain,\n\t\t\tcheckErr: func(t *testing.T, err error) {\n\t\t\t\tif err.Error() != \"tls: invalid signature by the client certificate: ECDSA verification failure\" &&\n\t\t\t\t\terr.Error() != \"tls: invalid signature by the server certificate: ECDSA verification failure\" {\n\t\t\t\t\tt.Fatalf(\"unexpected error message: %s\", err)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"certificate chain contains 2 certs\",\n\t\t\tapply: twoCerts,\n\t\t\tcheckErr: func(t *testing.T, err error) {\n\t\t\t\trequire.EqualError(t, err, \"expected one certificates in the chain\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"cert is expired\",\n\t\t\tapply: expiredCert,\n\t\t\tcheckErr: func(t *testing.T, err error) {\n\t\t\t\trequire.Contains(t, err.Error(), \"certificate has expired or is not yet valid\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"cert doesn't have the key extension\",\n\t\t\tapply: noKeyExtension,\n\t\t\tcheckErr: func(t *testing.T, err error) {\n\t\t\t\trequire.EqualError(t, err, \"expected certificate to contain the key extension\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"key extension not parseable\",\n\t\t\tapply:    unparseableKeyExtension,\n\t\t\tcheckErr: func(t *testing.T, err error) { require.Contains(t, err.Error(), \"asn1\") },\n\t\t},\n\t\t{\n\t\t\tname:  \"key protobuf not parseable\",\n\t\t\tapply: unparseableKey,\n\t\t\tcheckErr: func(t *testing.T, err error) {\n\t\t\t\trequire.Contains(t, err.Error(), \"unmarshalling public key failed: proto:\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"signature is malformed\",\n\t\t\tapply: tooShortSignature,\n\t\t\tcheckErr: func(t *testing.T, err error) {\n\t\t\t\trequire.Contains(t, err.Error(), \"signature verification failed:\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"signature is invalid\",\n\t\t\tapply: invalidSignature,\n\t\t\tcheckErr: func(t *testing.T, err error) {\n\t\t\t\trequire.Contains(t, err.Error(), \"signature invalid\")\n\t\t\t},\n\t\t},\n\t}\n\n\tfor i := range transforms {\n\t\ttr := transforms[i]\n\n\t\tt.Run(fmt.Sprintf(\"client offending: %s\", tr.name), func(t *testing.T) {\n\t\t\tserverTransport, err := New(ID, serverKey, nil)\n\t\t\trequire.NoError(t, err)\n\t\t\tclientTransport, err := New(ID, clientKey, nil)\n\t\t\trequire.NoError(t, err)\n\t\t\ttr.apply(clientTransport.identity)\n\n\t\t\tclientInsecureConn, serverInsecureConn := connect(t)\n\n\t\t\tserverErrChan := make(chan error)\n\t\t\tgo func() {\n\t\t\t\t_, err := serverTransport.SecureInbound(context.Background(), serverInsecureConn, \"\")\n\t\t\t\tserverErrChan <- err\n\t\t\t}()\n\n\t\t\tconn, err := clientTransport.SecureOutbound(context.Background(), clientInsecureConn, serverID)\n\t\t\trequire.NoError(t, err)\n\t\t\tclientErrChan := make(chan error)\n\t\t\tgo func() {\n\t\t\t\t_, err := conn.Read([]byte{0})\n\t\t\t\tclientErrChan <- err\n\t\t\t}()\n\t\t\tselect {\n\t\t\tcase err := <-clientErrChan:\n\t\t\t\trequire.Error(t, err)\n\t\t\t\tif err.Error() != \"remote error: tls: error decrypting message\" &&\n\t\t\t\t\terr.Error() != \"remote error: tls: bad certificate\" &&\n\t\t\t\t\t!isWindowsTCPCloseError(err) {\n\t\t\t\t\tt.Errorf(\"unexpected error: %s\", err.Error())\n\t\t\t\t}\n\t\t\tcase <-time.After(250 * time.Millisecond):\n\t\t\t\tt.Fatal(\"expected the server handshake to return\")\n\t\t\t}\n\n\t\t\tselect {\n\t\t\tcase err := <-serverErrChan:\n\t\t\t\trequire.Error(t, err)\n\t\t\t\ttr.checkErr(t, err)\n\t\t\tcase <-time.After(250 * time.Millisecond):\n\t\t\t\tt.Fatal(\"expected the server handshake to return\")\n\t\t\t}\n\t\t})\n\n\t\tt.Run(fmt.Sprintf(\"server offending: %s\", tr.name), func(t *testing.T) {\n\t\t\tserverTransport, err := New(ID, serverKey, nil)\n\t\t\trequire.NoError(t, err)\n\t\t\ttr.apply(serverTransport.identity)\n\t\t\tclientTransport, err := New(ID, clientKey, nil)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tclientInsecureConn, serverInsecureConn := connect(t)\n\n\t\t\terrChan := make(chan error)\n\t\t\tgo func() {\n\t\t\t\t_, err := serverTransport.SecureInbound(context.Background(), serverInsecureConn, \"\")\n\t\t\t\terrChan <- err\n\t\t\t}()\n\n\t\t\t_, err = clientTransport.SecureOutbound(context.Background(), clientInsecureConn, serverID)\n\t\t\trequire.Error(t, err)\n\t\t\ttr.checkErr(t, err)\n\n\t\t\tvar serverErr error\n\t\t\tselect {\n\t\t\tcase serverErr = <-errChan:\n\t\t\tcase <-time.After(250 * time.Millisecond):\n\t\t\t\tt.Fatal(\"expected the server handshake to return\")\n\t\t\t}\n\t\t\trequire.Error(t, serverErr)\n\t\t\tif !isWindowsTCPCloseError(serverErr) {\n\t\t\t\trequire.Contains(t, serverErr.Error(), \"remote error: tls:\")\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "p2p/test/backpressure/backpressure.go",
    "content": "package backpressure_tests\n"
  },
  {
    "path": "p2p/test/backpressure/backpressure_test.go",
    "content": "package backpressure_tests\n\nimport (\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\tbhost \"github.com/libp2p/go-libp2p/p2p/host/basic\"\n\tswarmt \"github.com/libp2p/go-libp2p/p2p/net/swarm/testing\"\n\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nvar log = logging.Logger(\"backpressure\")\n\n// TestStBackpressureStreamWrite tests whether streams see proper\n// backpressure when writing data over the network streams.\nfunc TestStBackpressureStreamWrite(t *testing.T) {\n\tctx := t.Context()\n\n\th1, err := bhost.NewHost(swarmt.GenSwarm(t), nil)\n\trequire.NoError(t, err)\n\th1.Start()\n\th2, err := bhost.NewHost(swarmt.GenSwarm(t), nil)\n\trequire.NoError(t, err)\n\th2.Start()\n\n\t// setup sender handler on 2\n\th2.SetStreamHandler(protocol.TestingID, func(s network.Stream) {\n\t\tdefer s.Reset()\n\t\t<-ctx.Done()\n\t})\n\n\th2pi := h2.Peerstore().PeerInfo(h2.ID())\n\tlog.Debug(\"dialing\", \"addrs\", h2pi.Addrs)\n\tif err := h1.Connect(ctx, h2pi); err != nil {\n\t\tt.Fatal(\"Failed to connect:\", err)\n\t}\n\n\t// open a stream, from 1->2, this is our reader\n\ts, err := h1.NewStream(ctx, h2.ID(), protocol.TestingID)\n\trequire.NoError(t, err)\n\tdefer s.Reset()\n\n\t// If nobody is reading, we should eventually time out.\n\trequire.NoError(t, s.SetWriteDeadline(time.Now().Add(100*time.Millisecond)))\n\tdata := make([]byte, 16*1024)\n\tfor range 5 * 1024 { // write at most 100MiB\n\t\tif _, err := s.Write(data); err != nil {\n\t\t\trequire.True(t, os.IsTimeout(err), err)\n\t\t\treturn\n\t\t}\n\t}\n\tt.Fatal(\"should have timed out\")\n}\n"
  },
  {
    "path": "p2p/test/basichost/basic_host_test.go",
    "content": "package basichost\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p\"\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/client\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/relay\"\n\tlibp2pquic \"github.com/libp2p/go-libp2p/p2p/transport/quic\"\n\tlibp2pwebrtc \"github.com/libp2p/go-libp2p/p2p/transport/webrtc\"\n\tlibp2pwebtransport \"github.com/libp2p/go-libp2p/p2p/transport/webtransport\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestNoStreamOverTransientConnection(t *testing.T) {\n\th1, err := libp2p.New(\n\t\tlibp2p.NoListenAddrs,\n\t\tlibp2p.EnableRelay(),\n\t\tlibp2p.ResourceManager(&network.NullResourceManager{}),\n\t)\n\trequire.NoError(t, err)\n\n\th2, err := libp2p.New(\n\t\tlibp2p.NoListenAddrs,\n\t\tlibp2p.EnableRelay(),\n\t\tlibp2p.ResourceManager(&network.NullResourceManager{}),\n\t)\n\trequire.NoError(t, err)\n\n\trelay1, err := libp2p.New()\n\trequire.NoError(t, err)\n\n\t_, err = relay.New(relay1)\n\trequire.NoError(t, err)\n\n\trelay1info := peer.AddrInfo{\n\t\tID:    relay1.ID(),\n\t\tAddrs: relay1.Addrs(),\n\t}\n\terr = h1.Connect(context.Background(), relay1info)\n\trequire.NoError(t, err)\n\n\terr = h2.Connect(context.Background(), relay1info)\n\trequire.NoError(t, err)\n\n\th2.SetStreamHandler(\"/testprotocol\", func(s network.Stream) {\n\t\tfmt.Println(\"testprotocol\")\n\n\t\t// End the example\n\t\ts.Close()\n\t})\n\n\t_, err = client.Reserve(context.Background(), h2, relay1info)\n\trequire.NoError(t, err)\n\n\trelayaddr := ma.StringCast(\"/p2p/\" + relay1info.ID.String() + \"/p2p-circuit/p2p/\" + h2.ID().String())\n\n\th2Info := peer.AddrInfo{\n\t\tID:    h2.ID(),\n\t\tAddrs: []ma.Multiaddr{relayaddr},\n\t}\n\terr = h1.Connect(context.Background(), h2Info)\n\trequire.NoError(t, err)\n\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second)\n\tdefer cancel()\n\tctx = network.WithNoDial(ctx, \"test\")\n\t_, err = h1.NewStream(ctx, h2.ID(), \"/testprotocol\")\n\n\trequire.Error(t, err)\n\n\t_, err = h1.NewStream(network.WithAllowLimitedConn(context.Background(), \"test\"), h2.ID(), \"/testprotocol\")\n\trequire.NoError(t, err)\n}\n\nfunc TestNewStreamTransientConnection(t *testing.T) {\n\th1, err := libp2p.New(\n\t\tlibp2p.ListenAddrStrings(\"/ip4/127.0.0.1/udp/0/quic-v1\"),\n\t\tlibp2p.EnableRelay(),\n\t\tlibp2p.ResourceManager(&network.NullResourceManager{}),\n\t)\n\trequire.NoError(t, err)\n\n\th2, err := libp2p.New(\n\t\tlibp2p.ListenAddrStrings(\"/ip4/127.0.0.1/udp/0/quic-v1\"),\n\t\tlibp2p.EnableRelay(),\n\t\tlibp2p.ResourceManager(&network.NullResourceManager{}),\n\t)\n\trequire.NoError(t, err)\n\n\trelay1, err := libp2p.New()\n\trequire.NoError(t, err)\n\n\t_, err = relay.New(relay1)\n\trequire.NoError(t, err)\n\n\trelay1info := peer.AddrInfo{\n\t\tID:    relay1.ID(),\n\t\tAddrs: relay1.Addrs(),\n\t}\n\terr = h1.Connect(context.Background(), relay1info)\n\trequire.NoError(t, err)\n\n\terr = h2.Connect(context.Background(), relay1info)\n\trequire.NoError(t, err)\n\n\th2.SetStreamHandler(\"/testprotocol\", func(s network.Stream) {\n\t\tfmt.Println(\"testprotocol\")\n\n\t\t// End the example\n\t\ts.Close()\n\t})\n\n\t_, err = client.Reserve(context.Background(), h2, relay1info)\n\trequire.NoError(t, err)\n\n\trelayaddr := ma.StringCast(\"/p2p/\" + relay1info.ID.String() + \"/p2p-circuit/p2p/\" + h2.ID().String())\n\n\th1.Peerstore().AddAddr(h2.ID(), relayaddr, peerstore.TempAddrTTL)\n\n\t// NewStream should block transient connections till we have a direct connection\n\tctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)\n\tdefer cancel()\n\ts, err := h1.NewStream(ctx, h2.ID(), \"/testprotocol\")\n\trequire.ErrorIs(t, err, context.DeadlineExceeded)\n\trequire.Nil(t, s)\n\n\t// NewStream should return a stream if a direct connection is established\n\t// while waiting\n\tdone := make(chan bool, 2)\n\tgo func() {\n\t\th1.Peerstore().AddAddrs(h2.ID(), h2.Addrs(), peerstore.TempAddrTTL)\n\t\tctx, cancel = context.WithTimeout(context.Background(), 5*time.Second)\n\t\tdefer cancel()\n\t\tctx = network.WithNoDial(ctx, \"test\")\n\t\ts, err = h1.NewStream(ctx, h2.ID(), \"/testprotocol\")\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, s)\n\t\tdefer s.Close()\n\t\trequire.Equal(t, network.DirInbound, s.Conn().Stat().Direction)\n\t\tdone <- true\n\t}()\n\tgo func() {\n\t\t// connect h2 to h1 simulating connection reversal\n\t\th2.Peerstore().AddAddrs(h1.ID(), h1.Addrs(), peerstore.TempAddrTTL)\n\t\tctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)\n\t\tdefer cancel()\n\t\tctx = network.WithForceDirectDial(ctx, \"test\")\n\t\terr := h2.Connect(ctx, peer.AddrInfo{ID: h1.ID()})\n\t\tassert.NoError(t, err)\n\t\tdone <- true\n\t}()\n\t<-done\n\t<-done\n}\n\nfunc TestAddrFactorCertHashAppend(t *testing.T) {\n\twtAddr := \"/ip4/1.2.3.4/udp/1/quic-v1/webtransport\"\n\twebrtcAddr := \"/ip4/1.2.3.4/udp/2/webrtc-direct\"\n\taddrsFactory := func(addrs []ma.Multiaddr) []ma.Multiaddr {\n\t\treturn append(addrs,\n\t\t\tma.StringCast(wtAddr),\n\t\t\tma.StringCast(webrtcAddr),\n\t\t)\n\t}\n\th, err := libp2p.New(\n\t\tlibp2p.AddrsFactory(addrsFactory),\n\t\tlibp2p.Transport(libp2pwebrtc.New),\n\t\tlibp2p.Transport(libp2pwebtransport.New),\n\t\tlibp2p.ListenAddrStrings(\n\t\t\t\"/ip4/0.0.0.0/udp/0/quic-v1/webtransport\",\n\t\t\t\"/ip4/0.0.0.0/udp/0/webrtc-direct\",\n\t\t),\n\t)\n\trequire.NoError(t, err)\n\trequire.Eventually(t, func() bool {\n\t\taddrs := h.Addrs()\n\t\tvar hasWebRTC, hasWebTransport bool\n\t\tfor _, addr := range addrs {\n\t\t\tif strings.HasPrefix(addr.String(), webrtcAddr) {\n\t\t\t\tif _, err := addr.ValueForProtocol(ma.P_CERTHASH); err == nil {\n\t\t\t\t\thasWebRTC = true\n\t\t\t\t}\n\t\t\t}\n\t\t\tif strings.HasPrefix(addr.String(), wtAddr) {\n\t\t\t\tif _, err := addr.ValueForProtocol(ma.P_CERTHASH); err == nil {\n\t\t\t\t\thasWebTransport = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn hasWebRTC && hasWebTransport\n\t}, 5*time.Second, 100*time.Millisecond)\n}\n\nfunc TestOnlyWebRTCDirectDialNoDelay(t *testing.T) {\n\t// This tests that only webrtc-direct dials are dialled immediately\n\t// and not delayed by dial ranker.\n\th1, err := libp2p.New(\n\t\tlibp2p.Transport(libp2pwebrtc.New),\n\t\tlibp2p.ListenAddrStrings(\n\t\t\t\"/ip4/0.0.0.0/udp/0/webrtc-direct\",\n\t\t),\n\t)\n\trequire.NoError(t, err)\n\th2, err := libp2p.New(\n\t\tlibp2p.Transport(libp2pwebrtc.New),\n\t\tlibp2p.NoListenAddrs,\n\t)\n\trequire.NoError(t, err)\n\n\tctx, cancel := context.WithTimeout(context.Background(), 150*time.Millisecond)\n\tdefer cancel()\n\terr = h2.Connect(ctx, peer.AddrInfo{ID: h1.ID(), Addrs: h1.Addrs()})\n\trequire.NoError(t, err)\n}\n\nfunc TestWebRTCWithQUICManyConnections(t *testing.T) {\n\t// Correctly fixes: https://github.com/libp2p/js-libp2p/issues/2805\n\n\t// The server has both /quic-v1 and /webrtc-direct listen addresses\n\th, err := libp2p.New(\n\t\tlibp2p.Transport(libp2pquic.NewTransport),\n\t\tlibp2p.Transport(libp2pwebrtc.New),\n\t\tlibp2p.ListenAddrStrings(\"/ip4/0.0.0.0/udp/0/quic-v1\"),\n\t\tlibp2p.ListenAddrStrings(\"/ip4/0.0.0.0/udp/0/webrtc-direct\"),\n\t\tlibp2p.ResourceManager(&network.NullResourceManager{}),\n\t)\n\trequire.NoError(t, err)\n\tdefer h.Close()\n\n\tconst N = 200\n\t// These N dialers have both /quic-v1 and /webrtc-direct transports\n\tvar dialers [N]host.Host\n\tfor i := range N {\n\t\tdialers[i], err = libp2p.New(libp2p.NoListenAddrs)\n\t\trequire.NoError(t, err)\n\t\tdefer dialers[i].Close()\n\t}\n\t// This dialer has only /webrtc-direct transport\n\td, err := libp2p.New(libp2p.Transport(libp2pwebrtc.New), libp2p.NoListenAddrs)\n\trequire.NoError(t, err)\n\tdefer d.Close()\n\n\tfor i := range N {\n\t\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\t\tdefer cancel()\n\t\t// With happy eyeballs these dialers will connect over only /quic-v1\n\t\t// and not stall the /webrtc-direct handshake goroutines.\n\t\t// it is fine if the dial fails, we just want to ensure that there's space\n\t\t// in the /webrtc-direct listen queue\n\t\t_ = dialers[i].Connect(ctx, peer.AddrInfo{ID: h.ID(), Addrs: h.Addrs()})\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\tdefer cancel()\n\t// The webrtc only dialer should be able to connect to the peer\n\terr = d.Connect(ctx, peer.AddrInfo{ID: h.ID(), Addrs: h.Addrs()})\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "p2p/test/negotiation/muxer_test.go",
    "content": "package negotiation\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p\"\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\t\"github.com/libp2p/go-libp2p/p2p/muxer/yamux\"\n\t\"github.com/libp2p/go-libp2p/p2p/security/insecure\"\n\t\"github.com/libp2p/go-libp2p/p2p/security/noise\"\n\ttls \"github.com/libp2p/go-libp2p/p2p/security/tls\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/tcp\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nvar (\n\tyamuxOpt        = libp2p.Muxer(\"/yamux\", yamux.DefaultTransport)\n\tanotherYamuxOpt = libp2p.Muxer(\"/another-yamux\", yamux.DefaultTransport)\n)\n\ntype testcase struct {\n\tName             string\n\tServerPreference []libp2p.Option\n\tClientPreference []libp2p.Option\n\n\tError    string\n\tExpected protocol.ID\n}\n\ntype security struct {\n\tName   string\n\tOption libp2p.Option\n}\n\nfunc TestMuxerNegotiation(t *testing.T) {\n\ttestcases := []testcase{\n\t\t{\n\t\t\tName:             \"server and client have the same preference\",\n\t\t\tServerPreference: []libp2p.Option{yamuxOpt, anotherYamuxOpt},\n\t\t\tClientPreference: []libp2p.Option{yamuxOpt, anotherYamuxOpt},\n\t\t\tExpected:         \"/yamux\",\n\t\t},\n\t\t{\n\t\t\tName:             \"client only supports one muxer\",\n\t\t\tServerPreference: []libp2p.Option{yamuxOpt, anotherYamuxOpt},\n\t\t\tClientPreference: []libp2p.Option{yamuxOpt},\n\t\t\tExpected:         \"/yamux\",\n\t\t},\n\t\t{\n\t\t\tName:             \"server only supports one muxer\",\n\t\t\tServerPreference: []libp2p.Option{yamuxOpt},\n\t\t\tClientPreference: []libp2p.Option{anotherYamuxOpt, yamuxOpt},\n\t\t\tExpected:         \"/yamux\",\n\t\t},\n\t\t{\n\t\t\tName:             \"client preference preferred\",\n\t\t\tServerPreference: []libp2p.Option{yamuxOpt, anotherYamuxOpt},\n\t\t\tClientPreference: []libp2p.Option{anotherYamuxOpt, yamuxOpt},\n\t\t\tExpected:         \"/another-yamux\",\n\t\t},\n\t\t{\n\t\t\tName:             \"no preference overlap\",\n\t\t\tServerPreference: []libp2p.Option{yamuxOpt},\n\t\t\tClientPreference: []libp2p.Option{anotherYamuxOpt},\n\t\t\tError:            \"failed to negotiate stream multiplexer: protocols not supported\",\n\t\t},\n\t}\n\n\tclientID, _, err := crypto.GenerateEd25519Key(rand.Reader)\n\trequire.NoError(t, err)\n\tserverID, _, err := crypto.GenerateEd25519Key(rand.Reader)\n\trequire.NoError(t, err)\n\n\tsecurities := []security{\n\t\t{Name: \"noise\", Option: libp2p.Security(\"/noise\", noise.New)},\n\t\t{Name: \"tls\", Option: libp2p.Security(\"/tls\", tls.New)},\n\t\t{Name: \"insecure\", Option: libp2p.Security(\"/insecure\", insecure.NewWithIdentity)},\n\t}\n\n\tfor _, tc := range testcases {\n\n\t\tfor _, sec := range securities {\n\n\t\t\tt.Run(fmt.Sprintf(\"%s: %s\", sec.Name, tc.Name), func(t *testing.T) {\n\t\t\t\tserver, err := libp2p.New(\n\t\t\t\t\tlibp2p.Identity(serverID),\n\t\t\t\t\tsec.Option,\n\t\t\t\t\tlibp2p.ChainOptions(tc.ServerPreference...),\n\t\t\t\t\tlibp2p.Transport(tcp.NewTCPTransport),\n\t\t\t\t\tlibp2p.ListenAddrStrings(\"/ip4/127.0.0.1/tcp/0\"),\n\t\t\t\t)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tclient, err := libp2p.New(\n\t\t\t\t\tlibp2p.Identity(clientID),\n\t\t\t\t\tsec.Option,\n\t\t\t\t\tlibp2p.ChainOptions(tc.ClientPreference...),\n\t\t\t\t\tlibp2p.Transport(tcp.NewTCPTransport),\n\t\t\t\t\tlibp2p.NoListenAddrs,\n\t\t\t\t)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\terr = client.Connect(context.Background(), peer.AddrInfo{ID: server.ID(), Addrs: server.Addrs()})\n\t\t\t\tif tc.Error != \"\" {\n\t\t\t\t\trequire.Error(t, err)\n\t\t\t\t\trequire.ErrorContains(t, err, tc.Error)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tconns := client.Network().ConnsToPeer(server.ID())\n\t\t\t\trequire.Len(t, conns, 1, \"expected exactly one connection\")\n\t\t\t\trequire.Equal(t, tc.Expected, conns[0].ConnState().StreamMultiplexer)\n\t\t\t})\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "p2p/test/negotiation/security_test.go",
    "content": "package negotiation\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p\"\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/p2p/security/noise\"\n\ttls \"github.com/libp2p/go-libp2p/p2p/security/tls\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/tcp\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nvar (\n\tnoiseOpt = libp2p.Security(\"/noise\", noise.New)\n\ttlsOpt   = libp2p.Security(\"/tls\", tls.New)\n)\n\nfunc TestSecurityNegotiation(t *testing.T) {\n\ttestcases := []testcase{\n\t\t{\n\t\t\tName:             \"server and client have the same preference\",\n\t\t\tServerPreference: []libp2p.Option{tlsOpt, noiseOpt},\n\t\t\tClientPreference: []libp2p.Option{tlsOpt, noiseOpt},\n\t\t\tExpected:         \"/tls\",\n\t\t},\n\t\t{\n\t\t\tName:             \"client only supports one security\",\n\t\t\tServerPreference: []libp2p.Option{tlsOpt, noiseOpt},\n\t\t\tClientPreference: []libp2p.Option{noiseOpt},\n\t\t\tExpected:         \"/noise\",\n\t\t},\n\t\t{\n\t\t\tName:             \"server only supports one security\",\n\t\t\tServerPreference: []libp2p.Option{noiseOpt},\n\t\t\tClientPreference: []libp2p.Option{tlsOpt, noiseOpt},\n\t\t\tExpected:         \"/noise\",\n\t\t},\n\t\t{\n\t\t\tName:             \"no  overlap\",\n\t\t\tServerPreference: []libp2p.Option{noiseOpt},\n\t\t\tClientPreference: []libp2p.Option{tlsOpt},\n\t\t\tError:            \"failed to negotiate security protocol: protocols not supported\",\n\t\t},\n\t}\n\n\tclientID, _, err := crypto.GenerateEd25519Key(rand.Reader)\n\trequire.NoError(t, err)\n\tserverID, _, err := crypto.GenerateEd25519Key(rand.Reader)\n\trequire.NoError(t, err)\n\n\tfor _, tc := range testcases {\n\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\tserver, err := libp2p.New(\n\t\t\t\tlibp2p.Identity(serverID),\n\t\t\t\tlibp2p.ChainOptions(tc.ServerPreference...),\n\t\t\t\tlibp2p.Transport(tcp.NewTCPTransport),\n\t\t\t\tlibp2p.ListenAddrStrings(\"/ip4/127.0.0.1/tcp/0\"),\n\t\t\t)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tclient, err := libp2p.New(\n\t\t\t\tlibp2p.Identity(clientID),\n\t\t\t\tlibp2p.ChainOptions(tc.ClientPreference...),\n\t\t\t\tlibp2p.Transport(tcp.NewTCPTransport),\n\t\t\t\tlibp2p.NoListenAddrs,\n\t\t\t)\n\t\t\trequire.NoError(t, err)\n\n\t\t\terr = client.Connect(context.Background(), peer.AddrInfo{ID: server.ID(), Addrs: server.Addrs()})\n\t\t\tif tc.Error != \"\" {\n\t\t\t\trequire.Error(t, err)\n\t\t\t\trequire.ErrorContains(t, err, tc.Error)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\trequire.NoError(t, err)\n\t\t\tconns := client.Network().ConnsToPeer(server.ID())\n\t\t\trequire.Len(t, conns, 1, \"expected exactly one connection\")\n\t\t\trequire.Equal(t, tc.Expected, conns[0].ConnState().Security)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "p2p/test/notifications/notification_test.go",
    "content": "package notifications\n\nimport (\n\t\"strconv\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p\"\n\t\"github.com/libp2p/go-libp2p/core/event\"\n\tlibp2pquic \"github.com/libp2p/go-libp2p/p2p/transport/quic\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/tcp\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc portFromString(t *testing.T, s string) int {\n\tt.Helper()\n\tp, err := strconv.ParseInt(s, 10, 32)\n\trequire.NoError(t, err)\n\treturn int(p)\n}\n\nfunc TestListenAddressNotif(t *testing.T) {\n\th, err := libp2p.New(\n\t\tlibp2p.ListenAddrStrings(\"/ip4/127.0.0.1/tcp/0\"),\n\t\tlibp2p.Transport(tcp.NewTCPTransport),\n\t\tlibp2p.Transport(libp2pquic.NewTransport),\n\t\tlibp2p.DisableRelay(),\n\t)\n\trequire.NoError(t, err)\n\tdefer h.Close()\n\tsub, err := h.EventBus().Subscribe(&event.EvtLocalAddressesUpdated{})\n\trequire.NoError(t, err)\n\tdefer sub.Close()\n\n\tvar initialAddr ma.Multiaddr\n\t// make sure the event is emitted for the initial listen address\n\tselect {\n\tcase e := <-sub.Out():\n\t\tev := e.(event.EvtLocalAddressesUpdated)\n\t\trequire.Empty(t, ev.Removed)\n\t\trequire.Len(t, ev.Current, 1)\n\t\trequire.Equal(t, event.Added, ev.Current[0].Action)\n\t\tinitialAddr = ev.Current[0].Address\n\t\tportStr, err := initialAddr.ValueForProtocol(ma.P_TCP)\n\t\trequire.NoError(t, err)\n\t\trequire.NotZero(t, portFromString(t, portStr))\n\tcase <-time.After(500 * time.Millisecond):\n\t\tt.Fatal(\"timeout\")\n\t}\n\tlistenAddrs, err := h.Network().InterfaceListenAddresses()\n\trequire.NoError(t, err)\n\trequire.Equal(t, []ma.Multiaddr{initialAddr}, listenAddrs)\n\n\t// now start listening on another address\n\trequire.NoError(t, h.Network().Listen(ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1\")))\n\tvar addedAddr ma.Multiaddr\n\tselect {\n\tcase e := <-sub.Out():\n\t\tev := e.(event.EvtLocalAddressesUpdated)\n\t\trequire.Empty(t, ev.Removed)\n\t\trequire.Len(t, ev.Current, 2)\n\t\tvar maintainedAddr ma.Multiaddr\n\t\tfor _, e := range ev.Current {\n\t\t\tswitch e.Action {\n\t\t\tcase event.Added:\n\t\t\t\taddedAddr = e.Address\n\t\t\tcase event.Maintained:\n\t\t\t\tmaintainedAddr = e.Address\n\t\t\tdefault:\n\t\t\t\tt.Fatal(\"unexpected action\")\n\t\t\t}\n\t\t}\n\t\trequire.Equal(t, initialAddr, maintainedAddr)\n\t\t_, err = addedAddr.ValueForProtocol(ma.P_QUIC_V1)\n\t\trequire.NoError(t, err)\n\t\tportStr, err := addedAddr.ValueForProtocol(ma.P_UDP)\n\t\trequire.NoError(t, err)\n\t\trequire.NotZero(t, portFromString(t, portStr))\n\tcase <-time.After(500 * time.Millisecond):\n\t\tt.Fatal(\"timeout\")\n\t}\n\n\tlistenAddrs, err = h.Network().InterfaceListenAddresses()\n\trequire.NoError(t, err)\n\trequire.Len(t, listenAddrs, 2)\n\trequire.Contains(t, listenAddrs, initialAddr)\n\trequire.Contains(t, listenAddrs, addedAddr)\n}\n"
  },
  {
    "path": "p2p/test/quic/quic_test.go",
    "content": "package quic_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\tlibp2pquic \"github.com/libp2p/go-libp2p/p2p/transport/quic\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/quicreuse\"\n\twebtransport \"github.com/libp2p/go-libp2p/p2p/transport/webtransport\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc getQUICMultiaddrCode(addr ma.Multiaddr) int {\n\tif _, err := addr.ValueForProtocol(ma.P_QUIC); err == nil {\n\t\treturn ma.P_QUIC\n\t}\n\tif _, err := addr.ValueForProtocol(ma.P_QUIC_V1); err == nil {\n\t\treturn ma.P_QUIC_V1\n\t}\n\treturn 0\n}\n\nfunc TestQUICAndWebTransport(t *testing.T) {\n\th1, err := libp2p.New(\n\t\tlibp2p.QUICReuse(quicreuse.NewConnManager),\n\t\tlibp2p.Transport(libp2pquic.NewTransport),\n\t\tlibp2p.Transport(webtransport.New),\n\t\tlibp2p.ListenAddrStrings(\n\t\t\t\"/ip4/127.0.0.1/udp/12347/quic-v1\",\n\t\t\t\"/ip4/127.0.0.1/udp/12347/quic-v1/webtransport\",\n\t\t),\n\t)\n\trequire.NoError(t, err)\n\tdefer h1.Close()\n\n\taddrs := h1.Addrs()\n\trequire.Len(t, addrs, 2)\n\tvar quicV1Addr, webtransportAddr ma.Multiaddr\n\tfor _, addr := range addrs {\n\t\tif _, err := addr.ValueForProtocol(ma.P_WEBTRANSPORT); err == nil {\n\t\t\twebtransportAddr = addr\n\t\t} else if _, err := addr.ValueForProtocol(ma.P_QUIC_V1); err == nil {\n\t\t\tquicV1Addr = addr\n\t\t}\n\t}\n\trequire.NotNil(t, webtransportAddr, \"expected to have a WebTransport address\")\n\trequire.NotNil(t, quicV1Addr, \"expected to have a QUIC v1 address\")\n\n\tctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n\tdefer cancel()\n\t// first test that we can dial a QUIC v1\n\th2, err := libp2p.New(\n\t\tlibp2p.Transport(libp2pquic.NewTransport),\n\t\tlibp2p.NoListenAddrs,\n\t)\n\trequire.NoError(t, err)\n\trequire.NoError(t, h2.Connect(ctx, peer.AddrInfo{ID: h1.ID(), Addrs: h1.Addrs()}))\n\tfor _, conns := range [][]network.Conn{h2.Network().ConnsToPeer(h1.ID()), h1.Network().ConnsToPeer(h2.ID())} {\n\t\trequire.Len(t, conns, 1)\n\t\tif _, err := conns[0].LocalMultiaddr().ValueForProtocol(ma.P_WEBTRANSPORT); err == nil {\n\t\t\tt.Fatalf(\"expected a QUIC connection, got a WebTransport connection (%s <-> %s)\", conns[0].LocalMultiaddr(), conns[0].RemoteMultiaddr())\n\t\t}\n\t\trequire.Equal(t, ma.P_QUIC_V1, getQUICMultiaddrCode(conns[0].LocalMultiaddr()))\n\t\trequire.Equal(t, ma.P_QUIC_V1, getQUICMultiaddrCode(conns[0].RemoteMultiaddr()))\n\t}\n\th2.Close()\n\n\t// finally, test that we can dial a WebTransport connection\n\th3, err := libp2p.New(\n\t\tlibp2p.Transport(webtransport.New),\n\t\tlibp2p.NoListenAddrs,\n\t)\n\trequire.NoError(t, err)\n\trequire.NoError(t, h3.Connect(ctx, peer.AddrInfo{ID: h1.ID(), Addrs: h1.Addrs()}))\n\tfor _, conns := range [][]network.Conn{h3.Network().ConnsToPeer(h1.ID()), h1.Network().ConnsToPeer(h3.ID())} {\n\t\trequire.Len(t, conns, 1)\n\t\tif _, err := conns[0].LocalMultiaddr().ValueForProtocol(ma.P_WEBTRANSPORT); err != nil {\n\t\t\tt.Fatalf(\"expected a WebTransport connection, got a QUIC connection (%s <-> %s)\", conns[0].LocalMultiaddr(), conns[0].RemoteMultiaddr())\n\t\t}\n\t\trequire.Equal(t, ma.P_QUIC_V1, getQUICMultiaddrCode(conns[0].LocalMultiaddr()))\n\t\trequire.Equal(t, ma.P_QUIC_V1, getQUICMultiaddrCode(conns[0].RemoteMultiaddr()))\n\t}\n\th3.Close()\n}\n"
  },
  {
    "path": "p2p/test/reconnects/reconnect.go",
    "content": "// Package reconnect tests connect -> disconnect -> reconnect works\npackage reconnect\n"
  },
  {
    "path": "p2p/test/reconnects/reconnect_test.go",
    "content": "package reconnect\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"math/rand\"\n\t\"runtime\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\tbhost \"github.com/libp2p/go-libp2p/p2p/host/basic\"\n\tswarmt \"github.com/libp2p/go-libp2p/p2p/net/swarm/testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc EchoStreamHandler(stream network.Stream) {\n\t_, err := io.CopyBuffer(stream, stream, make([]byte, 64)) // use a small buffer here to avoid problems with flow control\n\tif err == nil {\n\t\tstream.Close()\n\t} else {\n\t\tstream.Reset()\n\t}\n}\n\nfunc TestReconnect5(t *testing.T) {\n\trunTest := func(t *testing.T, swarmOpt swarmt.Option) {\n\t\tt.Helper()\n\t\tconst num = 5\n\t\thosts := make([]host.Host, 0, num)\n\n\t\tfor range num {\n\t\t\th, err := bhost.NewHost(swarmt.GenSwarm(t, swarmOpt), nil)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer h.Close()\n\t\t\th.Start()\n\t\t\thosts = append(hosts, h)\n\t\t\th.SetStreamHandler(protocol.TestingID, EchoStreamHandler)\n\t\t}\n\n\t\tfor range 4 {\n\t\t\trunRound(t, hosts)\n\t\t}\n\t}\n\n\tt.Run(\"using TCP\", func(t *testing.T) {\n\t\tif runtime.GOOS == \"darwin\" {\n\t\t\tt.Skip(\"TCP RST handling is flaky in OSX, see https://github.com/golang/go/issues/50254\")\n\t\t}\n\t\trunTest(t, swarmt.OptDisableQUIC)\n\t})\n\n\tt.Run(\"using QUIC\", func(t *testing.T) {\n\t\trunTest(t, swarmt.OptDisableTCP)\n\t})\n}\n\nfunc runRound(t *testing.T, hosts []host.Host) {\n\tfor _, h1 := range hosts {\n\t\th1.SetStreamHandler(protocol.TestingID, EchoStreamHandler)\n\n\t\tfor _, h2 := range hosts {\n\t\t\th1.Peerstore().AddAddrs(h2.ID(), h2.Addrs(), time.Hour)\n\t\t}\n\t}\n\n\tconst (\n\t\tnumStreams = 5\n\t\tmaxDataLen = 64 << 10\n\t)\n\trnd := rand.New(rand.NewSource(12345))\n\t// exchange some data\n\tfor _, h1 := range hosts {\n\t\tfor _, h2 := range hosts {\n\t\t\tif h1 == h2 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tvar wg sync.WaitGroup\n\t\t\twg.Add(numStreams)\n\t\t\tfor range numStreams {\n\t\t\t\tdata := make([]byte, rand.Intn(maxDataLen)+1)\n\t\t\t\trnd.Read(data)\n\t\t\t\tgo func() {\n\t\t\t\t\tdefer wg.Done()\n\t\t\t\t\tstr, err := h1.NewStream(context.Background(), h2.ID(), protocol.TestingID)\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\tdefer str.Close()\n\t\t\t\t\t_, err = str.Write(data)\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t}()\n\t\t\t}\n\t\t\twg.Wait()\n\t\t}\n\t}\n\n\t// disconnect all hosts\n\tfor _, h1 := range hosts {\n\t\t// close connection\n\t\tcs := h1.Network().Conns()\n\t\tfor _, c := range cs {\n\t\t\tc.Close()\n\t\t}\n\t}\n\n\trequire.Eventually(t, func() bool {\n\t\tfor _, h1 := range hosts {\n\t\t\tfor _, h2 := range hosts {\n\t\t\t\tif len(h1.Network().ConnsToPeer(h2.ID())) > 0 {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true\n\t}, 5000*time.Millisecond, 10*time.Millisecond)\n}\n"
  },
  {
    "path": "p2p/test/resource-manager/echo.go",
    "content": "package itest\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n)\n\nconst (\n\tEchoService = \"test.echo\"\n\tEchoProtoID = \"/test/echo\"\n)\n\nvar (\n\techoLog = logging.Logger(\"echo\")\n)\n\ntype Echo struct {\n\tHost host.Host\n\n\tmx     sync.Mutex\n\tstatus EchoStatus\n\n\tbeforeReserve, beforeRead, beforeWrite, beforeDone func() error\n\tdone                                               func()\n}\n\ntype EchoStatus struct {\n\tStreamsIn                 int\n\tEchosIn, EchosOut         int\n\tIOErrors                  int\n\tResourceServiceErrors     int\n\tResourceReservationErrors int\n}\n\nfunc NewEcho(h host.Host) *Echo {\n\te := &Echo{Host: h}\n\th.SetStreamHandler(EchoProtoID, e.handleStream)\n\treturn e\n}\n\nfunc (e *Echo) Status() EchoStatus {\n\te.mx.Lock()\n\tdefer e.mx.Unlock()\n\n\treturn e.status\n}\n\nfunc (e *Echo) BeforeReserve(f func() error) {\n\te.mx.Lock()\n\tdefer e.mx.Unlock()\n\n\te.beforeReserve = f\n}\n\nfunc (e *Echo) BeforeRead(f func() error) {\n\te.mx.Lock()\n\tdefer e.mx.Unlock()\n\n\te.beforeRead = f\n}\n\nfunc (e *Echo) BeforeWrite(f func() error) {\n\te.mx.Lock()\n\tdefer e.mx.Unlock()\n\n\te.beforeWrite = f\n}\n\nfunc (e *Echo) BeforeDone(f func() error) {\n\te.mx.Lock()\n\tdefer e.mx.Unlock()\n\n\te.beforeDone = f\n}\n\nfunc (e *Echo) Done(f func()) {\n\te.mx.Lock()\n\tdefer e.mx.Unlock()\n\n\te.done = f\n}\n\nfunc (e *Echo) getBeforeReserve() func() error {\n\te.mx.Lock()\n\tdefer e.mx.Unlock()\n\n\treturn e.beforeReserve\n}\n\nfunc (e *Echo) getBeforeRead() func() error {\n\te.mx.Lock()\n\tdefer e.mx.Unlock()\n\n\treturn e.beforeRead\n}\n\nfunc (e *Echo) getBeforeWrite() func() error {\n\te.mx.Lock()\n\tdefer e.mx.Unlock()\n\n\treturn e.beforeWrite\n}\n\nfunc (e *Echo) getBeforeDone() func() error {\n\te.mx.Lock()\n\tdefer e.mx.Unlock()\n\n\treturn e.beforeDone\n}\n\nfunc (e *Echo) getDone() func() {\n\te.mx.Lock()\n\tdefer e.mx.Unlock()\n\n\treturn e.done\n}\n\nfunc (e *Echo) handleStream(s network.Stream) {\n\tdefer s.Close()\n\n\tif done := e.getDone(); done != nil {\n\t\tdefer done()\n\t}\n\n\te.mx.Lock()\n\te.status.StreamsIn++\n\te.mx.Unlock()\n\n\tif beforeReserve := e.getBeforeReserve(); beforeReserve != nil {\n\t\tif err := beforeReserve(); err != nil {\n\t\t\techoLog.Debug(\"error syncing before reserve\", \"err\", err)\n\n\t\t\ts.Reset()\n\t\t\treturn\n\t\t}\n\t}\n\n\tif err := s.Scope().SetService(EchoService); err != nil {\n\t\techoLog.Debug(\"error attaching stream to echo service\", \"err\", err)\n\n\t\te.mx.Lock()\n\t\te.status.ResourceServiceErrors++\n\t\te.mx.Unlock()\n\n\t\ts.Reset()\n\t\treturn\n\t}\n\n\tif err := s.Scope().ReserveMemory(4096, network.ReservationPriorityAlways); err != nil {\n\t\techoLog.Debug(\"error reserving memory\", \"err\", err)\n\n\t\te.mx.Lock()\n\t\te.status.ResourceReservationErrors++\n\t\te.mx.Unlock()\n\n\t\ts.Reset()\n\t\treturn\n\t}\n\n\tif beforeRead := e.getBeforeRead(); beforeRead != nil {\n\t\tif err := beforeRead(); err != nil {\n\t\t\techoLog.Debug(\"error syncing before read\", \"err\", err)\n\n\t\t\ts.Reset()\n\t\t\treturn\n\t\t}\n\t}\n\n\tbuf := make([]byte, 4096)\n\n\ts.SetReadDeadline(time.Now().Add(5 * time.Second))\n\tn, err := s.Read(buf)\n\tswitch {\n\tcase err == io.EOF:\n\t\tif n == 0 {\n\t\t\treturn\n\t\t}\n\n\tcase err != nil:\n\t\techoLog.Debug(\"I/O error\", \"err\", err)\n\n\t\te.mx.Lock()\n\t\te.status.IOErrors++\n\t\te.mx.Unlock()\n\n\t\ts.Reset()\n\t\treturn\n\t}\n\n\te.mx.Lock()\n\te.status.EchosIn++\n\te.mx.Unlock()\n\n\tif beforeWrite := e.getBeforeWrite(); beforeWrite != nil {\n\t\tif err := beforeWrite(); err != nil {\n\t\t\techoLog.Debug(\"error syncing before write\", \"err\", err)\n\n\t\t\ts.Reset()\n\t\t\treturn\n\t\t}\n\t}\n\n\ts.SetWriteDeadline(time.Now().Add(5 * time.Second))\n\t_, err = s.Write(buf[:n])\n\tif err != nil {\n\t\techoLog.Debug(\"I/O error\", \"err\", err)\n\n\t\te.mx.Lock()\n\t\te.status.IOErrors++\n\t\te.mx.Unlock()\n\n\t\ts.Reset()\n\t\treturn\n\t}\n\n\te.mx.Lock()\n\te.status.EchosOut++\n\te.mx.Unlock()\n\n\ts.CloseWrite()\n\n\tif beforeDone := e.getBeforeDone(); beforeDone != nil {\n\t\tif err := beforeDone(); err != nil {\n\t\t\techoLog.Debug(\"error syncing before done\", \"err\", err)\n\n\t\t\ts.Reset()\n\t\t}\n\t}\n}\n\nfunc (e *Echo) Echo(p peer.ID, what string) error {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second)\n\tdefer cancel()\n\n\ts, err := e.Host.NewStream(ctx, p, EchoProtoID)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer s.Close()\n\n\tif err := s.Scope().SetService(EchoService); err != nil {\n\t\techoLog.Debug(\"error attaching stream to echo service\", \"err\", err)\n\n\t\ts.Reset()\n\t\treturn err\n\t}\n\n\tif err := s.Scope().ReserveMemory(4096, network.ReservationPriorityAlways); err != nil {\n\t\techoLog.Debug(\"error reserving memory\", \"err\", err)\n\n\t\ts.Reset()\n\t\treturn err\n\t}\n\n\ts.SetWriteDeadline(time.Now().Add(5 * time.Second))\n\t_, err = s.Write([]byte(what))\n\tif err != nil {\n\t\treturn err\n\t}\n\ts.CloseWrite()\n\n\tbuf := make([]byte, 4096)\n\n\ts.SetReadDeadline(time.Now().Add(5 * time.Second))\n\tn, err := s.Read(buf)\n\tswitch {\n\tcase err == io.EOF:\n\t\tif n == 0 {\n\t\t\treturn err\n\t\t}\n\n\tcase err != nil:\n\t\techoLog.Debug(\"I/O error\", \"err\", err)\n\n\t\ts.Reset()\n\t\treturn err\n\t}\n\n\tif what != string(buf[:n]) {\n\t\treturn fmt.Errorf(\"echo output doesn't match input\")\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "p2p/test/resource-manager/echo_test.go",
    "content": "package itest\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/tcp\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc createEchos(t *testing.T, count int, makeOpts ...func(int) libp2p.Option) []*Echo {\n\tresult := make([]*Echo, 0, count)\n\n\tfor i := range count {\n\t\topts := make([]libp2p.Option, 0, len(makeOpts)+2)\n\t\t// only use a single transport, otherwise we might end up with a TCP and a QUIC connection to the same host\n\t\topts = append(opts, libp2p.Transport(tcp.NewTCPTransport), libp2p.DefaultListenAddrs)\n\t\tfor _, makeOpt := range makeOpts {\n\t\t\topts = append(opts, makeOpt(i))\n\t\t}\n\n\t\th, err := libp2p.New(opts...)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\te := NewEcho(h)\n\t\tresult = append(result, e)\n\t}\n\n\tfor i := range count {\n\t\tfor j := range count {\n\t\t\tif i == j {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tresult[i].Host.Peerstore().AddAddrs(result[j].Host.ID(), result[j].Host.Addrs(), peerstore.PermanentAddrTTL)\n\t\t}\n\t}\n\n\treturn result\n}\n\nfunc closeEchos(echos []*Echo) {\n\tfor _, e := range echos {\n\t\te.Host.Close()\n\t}\n}\n\nfunc checkEchoStatus(t *testing.T, e *Echo, expected EchoStatus) {\n\tt.Helper()\n\trequire.Equal(t, expected, e.Status())\n}\n\nfunc TestEcho(t *testing.T) {\n\techos := createEchos(t, 2)\n\tdefer closeEchos(echos)\n\n\tif err := echos[0].Host.Connect(context.TODO(), peer.AddrInfo{ID: echos[1].Host.ID()}); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif err := echos[0].Echo(echos[1].Host.ID(), \"hello libp2p\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcheckEchoStatus(t, echos[1], EchoStatus{\n\t\tStreamsIn: 1,\n\t\tEchosIn:   1,\n\t\tEchosOut:  1,\n\t})\n}\n"
  },
  {
    "path": "p2p/test/resource-manager/rcmgr_test.go",
    "content": "package itest\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\trcmgr \"github.com/libp2p/go-libp2p/p2p/host/resource-manager\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc makeRcmgrOption(t *testing.T, cfg rcmgr.ConcreteLimitConfig) func(int) libp2p.Option {\n\treturn func(i int) libp2p.Option {\n\t\tvar opts []rcmgr.Option\n\t\tif os.Getenv(\"LIBP2P_TEST_RCMGR_TRACE\") == \"1\" {\n\t\t\topts = append(opts, rcmgr.WithTrace(fmt.Sprintf(\"%s-%d.json.gz\", t.Name(), i)))\n\t\t}\n\n\t\tmgr, err := rcmgr.NewResourceManager(rcmgr.NewFixedLimiter(cfg), opts...)\n\t\trequire.NoError(t, err)\n\t\treturn libp2p.ResourceManager(mgr)\n\t}\n}\n\nfunc closeRcmgrs(echos []*Echo) {\n\tfor _, e := range echos {\n\t\te.Host.Network().ResourceManager().Close()\n\t}\n}\n\nfunc waitForConnection(t *testing.T, src, dest *Echo) {\n\trequire.Eventually(t, func() bool {\n\t\treturn src.Host.Network().Connectedness(dest.Host.ID()) == network.Connected &&\n\t\t\tdest.Host.Network().Connectedness(src.Host.ID()) == network.Connected\n\t}, time.Second, 10*time.Millisecond)\n}\n\nfunc TestResourceManagerConnInbound(t *testing.T) {\n\t// this test checks that we can not exceed the inbound conn limit at system level\n\t// we specify: 1 conn per peer, 3 conns total, and we try to create 4 conns\n\tcfg := rcmgr.PartialLimitConfig{\n\t\tSystem: rcmgr.ResourceLimits{\n\t\t\tConnsInbound:    3,\n\t\t\tConnsOutbound:   1024,\n\t\t\tConns:           1024,\n\t\t\tStreamsOutbound: rcmgr.Unlimited,\n\t\t},\n\t\tPeerDefault: rcmgr.ResourceLimits{\n\t\t\tConnsInbound:  1,\n\t\t\tConnsOutbound: 1,\n\t\t\tConns:         1,\n\t\t},\n\t}.Build(rcmgr.DefaultLimits.AutoScale())\n\n\techos := createEchos(t, 5, makeRcmgrOption(t, cfg))\n\tdefer closeEchos(echos)\n\tdefer closeRcmgrs(echos)\n\n\tfor i := 1; i < 4; i++ {\n\t\terr := echos[i].Host.Connect(context.Background(), peer.AddrInfo{ID: echos[0].Host.ID()})\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\twaitForConnection(t, echos[i], echos[0])\n\t}\n\n\tfor i := 1; i < 4; i++ {\n\t\tcount := len(echos[i].Host.Network().ConnsToPeer(echos[0].Host.ID()))\n\t\tif count != 1 {\n\t\t\tt.Fatalf(\"expected %d connections to peer, got %d\", 1, count)\n\t\t}\n\t}\n\n\terr := echos[4].Host.Connect(context.Background(), peer.AddrInfo{ID: echos[0].Host.ID()})\n\tif err == nil {\n\t\tt.Fatal(\"expected ResourceManager to block incoming connection\")\n\t}\n}\n\nfunc TestResourceManagerConnOutbound(t *testing.T) {\n\t// this test checks that we can not exceed the inbound conn limit at system level\n\t// we specify: 1 conn per peer, 3 conns total, and we try to create 4 conns\n\tcfg := rcmgr.PartialLimitConfig{\n\t\tSystem: rcmgr.ResourceLimits{\n\t\t\tConnsInbound:  1024,\n\t\t\tConnsOutbound: 3,\n\t\t\tConns:         1024,\n\t\t},\n\t\tPeerDefault: rcmgr.ResourceLimits{\n\t\t\tConnsInbound:  1,\n\t\t\tConnsOutbound: 1,\n\t\t\tConns:         1,\n\t\t},\n\t}.Build(rcmgr.DefaultLimits.AutoScale())\n\techos := createEchos(t, 5, makeRcmgrOption(t, cfg))\n\tdefer closeEchos(echos)\n\tdefer closeRcmgrs(echos)\n\n\tfor i := 1; i < 4; i++ {\n\t\terr := echos[0].Host.Connect(context.Background(), peer.AddrInfo{ID: echos[i].Host.ID()})\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\twaitForConnection(t, echos[0], echos[i])\n\t}\n\n\tfor i := 1; i < 4; i++ {\n\t\tcount := len(echos[i].Host.Network().ConnsToPeer(echos[0].Host.ID()))\n\t\tif count != 1 {\n\t\t\tt.Fatalf(\"expected %d connections to peer, got %d\", 1, count)\n\t\t}\n\t}\n\n\terr := echos[0].Host.Connect(context.Background(), peer.AddrInfo{ID: echos[4].Host.ID()})\n\tif err == nil {\n\t\tt.Fatal(\"expected ResourceManager to block incoming connection\")\n\t}\n}\n\nfunc TestResourceManagerServiceInbound(t *testing.T) {\n\t// this test checks that we can not exceed the inbound stream limit at service level\n\t// we specify: 3 streams for the service, and we try to create 4 streams\n\tcfg := rcmgr.PartialLimitConfig{\n\t\tServiceDefault: rcmgr.ResourceLimits{\n\t\t\tStreamsInbound:  3,\n\t\t\tStreamsOutbound: 1024,\n\t\t\tStreams:         1024,\n\t\t},\n\t}.Build(rcmgr.DefaultLimits.AutoScale())\n\techos := createEchos(t, 5, makeRcmgrOption(t, cfg))\n\tdefer closeEchos(echos)\n\tdefer closeRcmgrs(echos)\n\n\tfor i := 1; i < 5; i++ {\n\t\terr := echos[i].Host.Connect(context.Background(), peer.AddrInfo{ID: echos[0].Host.ID()})\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\twaitForConnection(t, echos[i], echos[0])\n\t}\n\n\tready := make(chan struct{})\n\techos[0].BeforeDone(waitForChannel(ready, time.Minute))\n\n\tvar eg sync.WaitGroup\n\techos[0].Done(eg.Done)\n\n\tvar once sync.Once\n\tvar wg sync.WaitGroup\n\tfor i := 1; i < 5; i++ {\n\t\teg.Add(1)\n\t\twg.Add(1)\n\t\tgo func(i int) {\n\t\t\tdefer wg.Done()\n\n\t\t\terr := echos[i].Echo(echos[0].Host.ID(), \"hello libp2p\")\n\t\t\tif err != nil {\n\t\t\t\tt.Log(err)\n\t\t\t\tonce.Do(func() {\n\t\t\t\t\tclose(ready)\n\t\t\t\t})\n\t\t\t}\n\t\t}(i)\n\t}\n\twg.Wait()\n\teg.Wait()\n\n\tcheckEchoStatus(t, echos[0], EchoStatus{\n\t\tStreamsIn:             4,\n\t\tEchosIn:               3,\n\t\tEchosOut:              3,\n\t\tResourceServiceErrors: 1,\n\t})\n}\n\nfunc TestResourceManagerServicePeerInbound(t *testing.T) {\n\t// this test checks that we cannot exceed the per peer inbound stream limit at service level\n\t// we specify: 2 streams per peer for echo, and we try to create 3 streams\n\tcfg := rcmgr.DefaultLimits\n\tcfg.AddServicePeerLimit(\n\t\tEchoService,\n\t\trcmgr.BaseLimit{StreamsInbound: 2, StreamsOutbound: 1024, Streams: 1024, Memory: 9999999},\n\t\trcmgr.BaseLimitIncrease{},\n\t)\n\tlimits := cfg.AutoScale()\n\n\techos := createEchos(t, 5, makeRcmgrOption(t, limits))\n\tdefer closeEchos(echos)\n\tdefer closeRcmgrs(echos)\n\n\tfor i := 1; i < 5; i++ {\n\t\terr := echos[i].Host.Connect(context.Background(), peer.AddrInfo{ID: echos[0].Host.ID()})\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\twaitForConnection(t, echos[i], echos[0])\n\t}\n\n\techos[0].BeforeDone(waitForBarrier(4, time.Minute))\n\n\tvar eg sync.WaitGroup\n\techos[0].Done(eg.Done)\n\n\tvar wg sync.WaitGroup\n\tfor i := 1; i < 5; i++ {\n\t\teg.Add(1)\n\t\twg.Add(1)\n\t\tgo func(i int) {\n\t\t\tdefer wg.Done()\n\n\t\t\terr := echos[i].Echo(echos[0].Host.ID(), \"hello libp2p\")\n\t\t\tif err != nil {\n\t\t\t\tt.Log(err)\n\t\t\t}\n\t\t}(i)\n\t}\n\twg.Wait()\n\teg.Wait()\n\n\tcheckEchoStatus(t, echos[0], EchoStatus{\n\t\tStreamsIn:             4,\n\t\tEchosIn:               4,\n\t\tEchosOut:              4,\n\t\tResourceServiceErrors: 0,\n\t})\n\n\tready := make(chan struct{})\n\techos[0].BeforeDone(waitForChannel(ready, time.Minute))\n\n\tvar once sync.Once\n\tfor range 3 {\n\t\teg.Add(1)\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\n\t\t\terr := echos[2].Echo(echos[0].Host.ID(), \"hello libp2p\")\n\t\t\tif err != nil {\n\t\t\t\tt.Log(err)\n\t\t\t\tonce.Do(func() {\n\t\t\t\t\tclose(ready)\n\t\t\t\t})\n\t\t\t}\n\t\t}()\n\t}\n\twg.Wait()\n\teg.Wait()\n\n\tcheckEchoStatus(t, echos[0], EchoStatus{\n\t\tStreamsIn:             7,\n\t\tEchosIn:               6,\n\t\tEchosOut:              6,\n\t\tResourceServiceErrors: 1,\n\t})\n}\n\nfunc waitForBarrier(count int32, timeout time.Duration) func() error {\n\tready := make(chan struct{})\n\tvar wait atomic.Int32\n\twait.Store(count)\n\treturn func() error {\n\t\tif wait.Add(-1) == 0 {\n\t\t\tclose(ready)\n\t\t}\n\n\t\tselect {\n\t\tcase <-ready:\n\t\t\treturn nil\n\t\tcase <-time.After(timeout):\n\t\t\treturn fmt.Errorf(\"timeout\")\n\t\t}\n\t}\n}\n\nfunc waitForChannel(ready chan struct{}, timeout time.Duration) func() error {\n\treturn func() error {\n\t\tselect {\n\t\tcase <-ready:\n\t\t\treturn nil\n\t\tcase <-time.After(timeout):\n\t\t\treturn fmt.Errorf(\"timeout\")\n\t\t}\n\t}\n}\n\nfunc TestReadmeExample(_ *testing.T) {\n\t// Start with the default scaling limits.\n\tscalingLimits := rcmgr.DefaultLimits\n\n\t// Add limits around included libp2p protocols\n\tlibp2p.SetDefaultServiceLimits(&scalingLimits)\n\n\t// Turn the scaling limits into a concrete set of limits using `.AutoScale`. This\n\t// scales the limits proportional to your system memory.\n\tscaledDefaultLimits := scalingLimits.AutoScale()\n\n\t// Tweak certain settings\n\tcfg := rcmgr.PartialLimitConfig{\n\t\tSystem: rcmgr.ResourceLimits{\n\t\t\t// Allow unlimited outbound streams\n\t\t\tStreamsOutbound: rcmgr.Unlimited,\n\t\t},\n\t\t// Everything else is default. The exact values will come from `scaledDefaultLimits` above.\n\t}\n\n\t// Create our limits by using our cfg and replacing the default values with values from `scaledDefaultLimits`\n\tlimits := cfg.Build(scaledDefaultLimits)\n\n\t// The resource manager expects a limiter, se we create one from our limits.\n\tlimiter := rcmgr.NewFixedLimiter(limits)\n\n\t// Metrics are enabled by default. If you want to disable metrics, use the\n\t// WithMetricsDisabled option\n\t// Initialize the resource manager\n\trm, err := rcmgr.NewResourceManager(limiter, rcmgr.WithMetricsDisabled())\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Create a libp2p host\n\thost, err := libp2p.New(libp2p.ResourceManager(rm))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\thost.Close()\n}\n"
  },
  {
    "path": "p2p/test/security/bench_test.go",
    "content": "package benchmark\n\nimport (\n\t\"context\"\n\tcrand \"crypto/rand\"\n\t\"io\"\n\t\"net\"\n\t\"sync\"\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/sec\"\n\t\"github.com/libp2p/go-libp2p/p2p/security/noise\"\n\ttls \"github.com/libp2p/go-libp2p/p2p/security/tls\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\ntype Factory func(*testing.B, crypto.PrivKey) sec.SecureTransport\n\nfunc benchmarkThroughput(b *testing.B, size int, factory Factory) {\n\tprivA, pubA, err := crypto.GenerateEd25519Key(crand.Reader)\n\tassert.NoError(b, err)\n\tidA, err := peer.IDFromPublicKey(pubA)\n\tassert.NoError(b, err)\n\ttptA := factory(b, privA)\n\n\tprivB, pubB, err := crypto.GenerateEd25519Key(crand.Reader)\n\tassert.NoError(b, err)\n\tidB, err := peer.IDFromPublicKey(pubB)\n\tassert.NoError(b, err)\n\ttptB := factory(b, privB)\n\n\t// pipe here serialize the decryption and encryption, we might want both parallelised to reduce context switching impact on the benchmark.\n\t// https://github.com/golang/go/issues/34502 would be ideal for the parallel usecase.\n\tp1, p2 := net.Pipe()\n\tvar ready sync.Mutex    // wait for completed handshake\n\tvar finished sync.Mutex // wait until all data has been received\n\tready.Lock()\n\tfinished.Lock()\n\tgo func() {\n\t\tdefer finished.Unlock()\n\t\tconn, err := tptB.SecureInbound(context.Background(), p2, idA)\n\t\tassert.NoError(b, err)\n\t\tready.Unlock()\n\n\t\t_, err = io.Copy(io.Discard, conn)\n\t\tassert.NoError(b, err)\n\t}()\n\n\tconn, err := tptA.SecureOutbound(context.Background(), p1, idB)\n\tassert.NoError(b, err)\n\tready.Lock()\n\n\tbuf := make([]byte, size)\n\tb.SetBytes(int64(len(buf)))\n\tb.ResetTimer()\n\n\tfor i := b.N; i != 0; i-- {\n\t\t_, err = conn.Write(buf[:])\n\t\tassert.NoError(b, err)\n\t}\n\tconn.Close()\n\n\tfinished.Lock()\n}\nfunc benchmarkHandshakes(b *testing.B, factory Factory) {\n\tprivA, pubA, err := crypto.GenerateEd25519Key(crand.Reader)\n\tassert.NoError(b, err)\n\tidA, err := peer.IDFromPublicKey(pubA)\n\tassert.NoError(b, err)\n\ttptA := factory(b, privA)\n\n\tprivB, pubB, err := crypto.GenerateEd25519Key(crand.Reader)\n\tassert.NoError(b, err)\n\tidB, err := peer.IDFromPublicKey(pubB)\n\tassert.NoError(b, err)\n\ttptB := factory(b, privB)\n\n\tpipes := make(chan net.Conn, 1)\n\n\tvar finished sync.Mutex // wait until all data has been transferred\n\tfinished.Lock()\n\tgo func() {\n\t\tdefer finished.Unlock()\n\t\tvar throwAway [1]byte\n\t\tfor p := range pipes {\n\t\t\tconn, err := tptB.SecureInbound(context.Background(), p, idA)\n\t\t\tassert.NoError(b, err)\n\t\t\t_, err = conn.Read(throwAway[:]) // read because currently the tls transport handshake when calling Read.\n\t\t\tassert.ErrorIs(b, err, io.EOF)\n\t\t}\n\t}()\n\tb.ResetTimer()\n\n\tfor i := b.N; i != 0; i-- {\n\t\tp1, p2 := net.Pipe()\n\t\tpipes <- p2\n\t\tconn, err := tptA.SecureOutbound(context.Background(), p1, idB)\n\t\tassert.NoError(b, err)\n\t\tassert.NoError(b, conn.Close())\n\t}\n\tclose(pipes)\n\n\tfinished.Lock()\n}\n\nfunc bench(b *testing.B, factory Factory) {\n\tb.Run(\"throughput\", func(b *testing.B) {\n\t\tb.Run(\"32KiB\", func(b *testing.B) { benchmarkThroughput(b, 32*1024, factory) })\n\t\tb.Run(\"1MiB\", func(b *testing.B) { benchmarkThroughput(b, 1024*1024, factory) })\n\t})\n\tb.Run(\"handshakes\", func(b *testing.B) { benchmarkHandshakes(b, factory) })\n}\n\nfunc BenchmarkNoise(b *testing.B) {\n\tbench(b, func(b *testing.B, priv crypto.PrivKey) sec.SecureTransport {\n\t\ttpt, err := noise.New(\"\", priv, nil)\n\t\tassert.NoError(b, err)\n\t\treturn tpt\n\t})\n}\n\nfunc BenchmarkTLS(b *testing.B) {\n\tbench(b, func(b *testing.B, priv crypto.PrivKey) sec.SecureTransport {\n\t\ttpt, err := tls.New(\"\", priv, nil)\n\t\tassert.NoError(b, err)\n\t\treturn tpt\n\t})\n}\n"
  },
  {
    "path": "p2p/test/swarm/swarm_test.go",
    "content": "package swarm_test\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\trcmgr \"github.com/libp2p/go-libp2p/p2p/host/resource-manager\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/client\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/relay\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestDialPeerTransientConnection(t *testing.T) {\n\th1, err := libp2p.New(\n\t\tlibp2p.NoListenAddrs,\n\t\tlibp2p.EnableRelay(),\n\t)\n\trequire.NoError(t, err)\n\n\th2, err := libp2p.New(\n\t\tlibp2p.NoListenAddrs,\n\t\tlibp2p.EnableRelay(),\n\t)\n\trequire.NoError(t, err)\n\n\trelay1, err := libp2p.New()\n\trequire.NoError(t, err)\n\n\t_, err = relay.New(relay1)\n\trequire.NoError(t, err)\n\n\trelay1info := peer.AddrInfo{\n\t\tID:    relay1.ID(),\n\t\tAddrs: relay1.Addrs(),\n\t}\n\terr = h1.Connect(context.Background(), relay1info)\n\trequire.NoError(t, err)\n\n\terr = h2.Connect(context.Background(), relay1info)\n\trequire.NoError(t, err)\n\n\t_, err = client.Reserve(context.Background(), h2, relay1info)\n\trequire.NoError(t, err)\n\n\trelayaddr := ma.StringCast(\"/p2p/\" + relay1info.ID.String() + \"/p2p-circuit/p2p/\" + h2.ID().String())\n\n\th1.Peerstore().AddAddr(h2.ID(), relayaddr, peerstore.TempAddrTTL)\n\n\t// swarm.DialPeer should connect over transient connections\n\tconn1, err := h1.Network().DialPeer(context.Background(), h2.ID())\n\trequire.NoError(t, err)\n\trequire.NotNil(t, conn1)\n\n\t// Test that repeated calls return the same connection.\n\tconn2, err := h1.Network().DialPeer(context.Background(), h2.ID())\n\trequire.NoError(t, err)\n\trequire.NotNil(t, conn2)\n\n\trequire.Equal(t, conn1, conn2)\n\n\t// swarm.DialPeer should fail if forceDirect is used\n\tctx := network.WithForceDirectDial(context.Background(), \"test\")\n\tconn, err := h1.Network().DialPeer(ctx, h2.ID())\n\trequire.Error(t, err)\n\trequire.Nil(t, conn)\n}\n\nfunc TestNewStreamTransientConnection(t *testing.T) {\n\th1, err := libp2p.New(\n\t\tlibp2p.ListenAddrStrings(\"/ip4/127.0.0.1/udp/0/quic-v1\"),\n\t\tlibp2p.EnableRelay(),\n\t)\n\trequire.NoError(t, err)\n\n\th2, err := libp2p.New(\n\t\tlibp2p.ListenAddrStrings(\"/ip4/127.0.0.1/udp/0/quic-v1\"),\n\t\tlibp2p.EnableRelay(),\n\t)\n\trequire.NoError(t, err)\n\n\trelay1, err := libp2p.New()\n\trequire.NoError(t, err)\n\n\t_, err = relay.New(relay1)\n\trequire.NoError(t, err)\n\n\trelay1info := peer.AddrInfo{\n\t\tID:    relay1.ID(),\n\t\tAddrs: relay1.Addrs(),\n\t}\n\terr = h1.Connect(context.Background(), relay1info)\n\trequire.NoError(t, err)\n\n\terr = h2.Connect(context.Background(), relay1info)\n\trequire.NoError(t, err)\n\n\t_, err = client.Reserve(context.Background(), h2, relay1info)\n\trequire.NoError(t, err)\n\n\trelayaddr := ma.StringCast(\"/p2p/\" + relay1info.ID.String() + \"/p2p-circuit/p2p/\" + h2.ID().String())\n\n\th1.Peerstore().AddAddr(h2.ID(), relayaddr, peerstore.TempAddrTTL)\n\n\t// WithAllowLimitedConn should succeed\n\tctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)\n\tdefer cancel()\n\tctx = network.WithAllowLimitedConn(ctx, \"test\")\n\ts, err := h1.Network().NewStream(ctx, h2.ID())\n\trequire.NoError(t, err)\n\trequire.NotNil(t, s)\n\tdefer s.Close()\n\n\t// Without WithAllowLimitedConn should fail with context deadline exceeded\n\tctx, cancel = context.WithTimeout(context.Background(), 200*time.Millisecond)\n\tdefer cancel()\n\ts, err = h1.Network().NewStream(ctx, h2.ID())\n\trequire.ErrorIs(t, err, context.DeadlineExceeded)\n\trequire.Nil(t, s)\n\n\t// Provide h2's direct address to h1.\n\th1.Peerstore().AddAddrs(h2.ID(), h2.Addrs(), peerstore.TempAddrTTL)\n\t// network.NoDial should also fail\n\tctx, cancel = context.WithTimeout(context.Background(), 200*time.Millisecond)\n\tdefer cancel()\n\tctx = network.WithNoDial(ctx, \"test\")\n\ts, err = h1.Network().NewStream(ctx, h2.ID())\n\trequire.ErrorIs(t, err, context.DeadlineExceeded)\n\trequire.Nil(t, s)\n\n\tdone := make(chan bool, 2)\n\t// NewStream should return a stream if an incoming direct connection is established\n\tgo func() {\n\t\tctx, cancel = context.WithTimeout(context.Background(), 5*time.Second)\n\t\tdefer cancel()\n\t\tctx = network.WithNoDial(ctx, \"test\")\n\t\ts, err = h1.Network().NewStream(ctx, h2.ID())\n\t\tassert.NoError(t, err)\n\t\tassert.NotNil(t, s)\n\t\tdefer s.Close()\n\t\trequire.Equal(t, network.DirInbound, s.Conn().Stat().Direction)\n\t\tdone <- true\n\t}()\n\tgo func() {\n\t\t// connect h2 to h1 simulating connection reversal\n\t\th2.Peerstore().AddAddrs(h1.ID(), h1.Addrs(), peerstore.TempAddrTTL)\n\t\tctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)\n\t\tdefer cancel()\n\t\tctx = network.WithForceDirectDial(ctx, \"test\")\n\t\terr := h2.Connect(ctx, peer.AddrInfo{ID: h1.ID()})\n\t\tassert.NoError(t, err)\n\t\tdone <- true\n\t}()\n\n\t<-done\n\t<-done\n}\n\nfunc TestLimitStreamsWhenHangingHandlers(t *testing.T) {\n\tvar partial rcmgr.PartialLimitConfig\n\tconst streamLimit = 10\n\tpartial.System.Streams = streamLimit\n\tmgr, err := rcmgr.NewResourceManager(rcmgr.NewFixedLimiter(partial.Build(rcmgr.InfiniteLimits)))\n\trequire.NoError(t, err)\n\n\tmaddr, err := ma.NewMultiaddr(\"/ip4/127.0.0.1/udp/0/quic-v1\")\n\trequire.NoError(t, err)\n\n\treceiver, err := libp2p.New(\n\t\tlibp2p.ResourceManager(mgr),\n\t\tlibp2p.ListenAddrs(maddr),\n\t)\n\trequire.NoError(t, err)\n\tt.Cleanup(func() { receiver.Close() })\n\n\tvar wg sync.WaitGroup\n\twg.Add(1)\n\n\tconst pid = \"/test\"\n\treceiver.SetStreamHandler(pid, func(s network.Stream) {\n\t\tdefer s.Close()\n\t\ts.Write([]byte{42})\n\t\twg.Wait()\n\t})\n\n\t// Open streamLimit streams\n\tsuccess := 0\n\t// we make a lot of tries because identify and identify push take up a few streams\n\tfor i := 0; i < 1000 && success < streamLimit; i++ {\n\t\tmgr, err = rcmgr.NewResourceManager(rcmgr.NewFixedLimiter(rcmgr.InfiniteLimits))\n\t\trequire.NoError(t, err)\n\n\t\tsender, err := libp2p.New(libp2p.ResourceManager(mgr))\n\t\trequire.NoError(t, err)\n\t\tt.Cleanup(func() { sender.Close() })\n\n\t\tsender.Peerstore().AddAddrs(receiver.ID(), receiver.Addrs(), peerstore.PermanentAddrTTL)\n\n\t\ts, err := sender.NewStream(context.Background(), receiver.ID(), pid)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tvar b [1]byte\n\t\t_, err = io.ReadFull(s, b[:])\n\t\tif err == nil {\n\t\t\tsuccess++\n\t\t}\n\t\tsender.Close()\n\t}\n\trequire.Equal(t, streamLimit, success)\n\t// We have the maximum number of streams open. Next call should fail.\n\tmgr, err = rcmgr.NewResourceManager(rcmgr.NewFixedLimiter(rcmgr.InfiniteLimits))\n\trequire.NoError(t, err)\n\n\tsender, err := libp2p.New(libp2p.ResourceManager(mgr))\n\trequire.NoError(t, err)\n\tt.Cleanup(func() { sender.Close() })\n\n\tsender.Peerstore().AddAddrs(receiver.ID(), receiver.Addrs(), peerstore.PermanentAddrTTL)\n\n\t_, err = sender.NewStream(context.Background(), receiver.ID(), pid)\n\trequire.Error(t, err)\n\n\t// Close the open streams\n\twg.Done()\n\n\t// Next call should succeed\n\trequire.Eventually(t, func() bool {\n\t\ts, err := sender.NewStream(context.Background(), receiver.ID(), pid)\n\t\tif err == nil {\n\t\t\ts.Close()\n\t\t\treturn true\n\t\t}\n\t\treturn false\n\t}, 5*time.Second, 100*time.Millisecond)\n}\n"
  },
  {
    "path": "p2p/test/transport/deadline_test.go",
    "content": "package transport_integration\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestReadWriteDeadlines(t *testing.T) {\n\t// Send a lot of data so that writes have to flush (can't just buffer it all)\n\tsendBuf := make([]byte, 10<<20)\n\tfor _, tc := range transportsToTest {\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\tlistener := tc.HostGenerator(t, TransportTestCaseOpts{})\n\t\t\tdefer listener.Close()\n\t\t\tdialer := tc.HostGenerator(t, TransportTestCaseOpts{NoListen: true})\n\t\t\tdefer dialer.Close()\n\n\t\t\trequire.NoError(t, dialer.Connect(context.Background(), peer.AddrInfo{\n\t\t\t\tID:    listener.ID(),\n\t\t\t\tAddrs: listener.Addrs(),\n\t\t\t}))\n\n\t\t\t// This simply stalls\n\t\t\tlistener.SetStreamHandler(\"/stall\", func(s network.Stream) {\n\t\t\t\ttime.Sleep(time.Hour)\n\t\t\t\ts.Close()\n\t\t\t})\n\n\t\t\tt.Run(\"ReadDeadline\", func(t *testing.T) {\n\t\t\t\ts, err := dialer.NewStream(context.Background(), listener.ID(), \"/stall\")\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tdefer s.Close()\n\n\t\t\t\tstart := time.Now()\n\t\t\t\t// Set a deadline\n\t\t\t\ts.SetReadDeadline(time.Now().Add(10 * time.Millisecond))\n\t\t\t\tbuf := make([]byte, 1)\n\t\t\t\t_, err = s.Read(buf)\n\t\t\t\trequire.Error(t, err)\n\t\t\t\tvar nerr net.Error\n\t\t\t\trequire.ErrorAs(t, err, &nerr)\n\t\t\t\trequire.True(t, nerr.Timeout())\n\t\t\t\trequire.Less(t, time.Since(start), 1*time.Second)\n\t\t\t})\n\n\t\t\tt.Run(\"WriteDeadline\", func(t *testing.T) {\n\t\t\t\ts, err := dialer.NewStream(context.Background(), listener.ID(), \"/stall\")\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tdefer s.Close()\n\n\t\t\t\t// Set a deadline\n\t\t\t\ts.SetWriteDeadline(time.Now().Add(10 * time.Millisecond))\n\t\t\t\tstart := time.Now()\n\t\t\t\t_, err = s.Write(sendBuf)\n\t\t\t\trequire.Error(t, err)\n\t\t\t\trequire.True(t, err.(net.Error).Timeout())\n\t\t\t\trequire.Less(t, time.Since(start), 1*time.Second)\n\t\t\t})\n\n\t\t\t// Like the above, but with SetDeadline\n\t\t\tt.Run(\"SetDeadline\", func(t *testing.T) {\n\t\t\t\tfor _, op := range []string{\"Read\", \"Write\"} {\n\t\t\t\t\tt.Run(op, func(t *testing.T) {\n\t\t\t\t\t\ts, err := dialer.NewStream(context.Background(), listener.ID(), \"/stall\")\n\t\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\t\tdefer s.Close()\n\n\t\t\t\t\t\t// Set a deadline\n\t\t\t\t\t\ts.SetDeadline(time.Now().Add(10 * time.Millisecond))\n\t\t\t\t\t\tstart := time.Now()\n\n\t\t\t\t\t\tif op == \"Read\" {\n\t\t\t\t\t\t\tbuf := make([]byte, 1)\n\t\t\t\t\t\t\t_, err = s.Read(buf)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t_, err = s.Write(sendBuf)\n\t\t\t\t\t\t}\n\t\t\t\t\t\trequire.Error(t, err)\n\t\t\t\t\t\tvar nerr net.Error\n\t\t\t\t\t\trequire.ErrorAs(t, err, &nerr)\n\t\t\t\t\t\trequire.True(t, nerr.Timeout())\n\t\t\t\t\t\trequire.Less(t, time.Since(start), 1*time.Second)\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "p2p/test/transport/gating_test.go",
    "content": "package transport_integration\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"net/netip\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/protocol\"\n\t\"github.com/libp2p/go-libp2p/p2p/net/swarm\"\n\n\t\"github.com/libp2p/go-libp2p-testing/race\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/multiformats/go-multiaddr/matest\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/mock/gomock\"\n)\n\n//go:generate go run go.uber.org/mock/mockgen -package transport_integration -destination mock_connection_gater_test.go github.com/libp2p/go-libp2p/core/connmgr ConnectionGater\n\n// normalize removes the certhash and replaces /wss with /tls/ws\nfunc normalize(addr ma.Multiaddr) ma.Multiaddr {\n\tfor {\n\t\tif _, err := addr.ValueForProtocol(ma.P_CERTHASH); err != nil {\n\t\t\tbreak\n\t\t}\n\t\taddr, _ = ma.SplitLast(addr)\n\t}\n\n\t// replace /wss with /tls/ws\n\tvar components ma.Multiaddr\n\tma.ForEach(addr, func(c ma.Component) bool {\n\t\tif c.Protocol().Code == ma.P_WSS {\n\t\t\tcomponents = append(components, ma.StringCast(\"/tls/ws\")...)\n\t\t} else {\n\t\t\tcomponents = append(components, c)\n\t\t}\n\t\treturn true\n\t})\n\treturn components\n}\n\nfunc addrPort(addr ma.Multiaddr) netip.AddrPort {\n\ta := netip.Addr{}\n\tp := uint16(0)\n\tma.ForEach(addr, func(c ma.Component) bool {\n\t\tif c.Protocol().Code == ma.P_IP4 || c.Protocol().Code == ma.P_IP6 {\n\t\t\ta, _ = netip.AddrFromSlice(c.RawValue())\n\t\t\treturn false\n\t\t}\n\t\tif c.Protocol().Code == ma.P_UDP || c.Protocol().Code == ma.P_TCP {\n\t\t\tp = binary.BigEndian.Uint16(c.RawValue())\n\t\t\treturn true\n\t\t}\n\t\treturn false\n\t})\n\treturn netip.AddrPortFrom(a, p)\n}\n\nfunc TestInterceptPeerDial(t *testing.T) {\n\tif race.WithRace() {\n\t\tt.Skip(\"The upgrader spawns a new Go routine, which leads to race conditions when using GoMock.\")\n\t}\n\tfor _, tc := range transportsToTest {\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\tctrl := gomock.NewController(t)\n\t\t\tdefer ctrl.Finish()\n\t\t\tconnGater := NewMockConnectionGater(ctrl)\n\n\t\t\th1 := tc.HostGenerator(t, TransportTestCaseOpts{NoListen: true, ConnGater: connGater})\n\t\t\th2 := tc.HostGenerator(t, TransportTestCaseOpts{})\n\t\t\trequire.Len(t, h2.Addrs(), 1)\n\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), time.Second)\n\t\t\tdefer cancel()\n\t\t\tconnGater.EXPECT().InterceptPeerDial(h2.ID())\n\t\t\trequire.ErrorIs(t, h1.Connect(ctx, peer.AddrInfo{ID: h2.ID(), Addrs: h2.Addrs()}), swarm.ErrGaterDisallowedConnection)\n\t\t})\n\t}\n}\n\nfunc TestInterceptAddrDial(t *testing.T) {\n\tif race.WithRace() {\n\t\tt.Skip(\"The upgrader spawns a new Go routine, which leads to race conditions when using GoMock.\")\n\t}\n\tfor _, tc := range transportsToTest {\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\tctrl := gomock.NewController(t)\n\t\t\tdefer ctrl.Finish()\n\t\t\tconnGater := NewMockConnectionGater(ctrl)\n\n\t\t\th1 := tc.HostGenerator(t, TransportTestCaseOpts{NoListen: true, ConnGater: connGater})\n\t\t\th2 := tc.HostGenerator(t, TransportTestCaseOpts{})\n\t\t\trequire.Len(t, h2.Addrs(), 1)\n\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), time.Second)\n\t\t\tdefer cancel()\n\t\t\tgomock.InOrder(\n\t\t\t\tconnGater.EXPECT().InterceptPeerDial(h2.ID()).Return(true),\n\t\t\t\tconnGater.EXPECT().InterceptAddrDial(h2.ID(), matest.MultiaddrMatcher{Multiaddr: h2.Addrs()[0]}),\n\t\t\t)\n\t\t\trequire.ErrorIs(t, h1.Connect(ctx, peer.AddrInfo{ID: h2.ID(), Addrs: h2.Addrs()}), swarm.ErrNoGoodAddresses)\n\t\t})\n\t}\n}\n\nfunc TestInterceptSecuredOutgoing(t *testing.T) {\n\tif race.WithRace() {\n\t\tt.Skip(\"The upgrader spawns a new Go routine, which leads to race conditions when using GoMock.\")\n\t}\n\tfor _, tc := range transportsToTest {\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\tctrl := gomock.NewController(t)\n\t\t\tdefer ctrl.Finish()\n\t\t\tconnGater := NewMockConnectionGater(ctrl)\n\n\t\t\th1 := tc.HostGenerator(t, TransportTestCaseOpts{NoListen: true, ConnGater: connGater})\n\t\t\th2 := tc.HostGenerator(t, TransportTestCaseOpts{})\n\t\t\tdefer h1.Close()\n\t\t\tdefer h2.Close()\n\t\t\trequire.Len(t, h2.Addrs(), 1)\n\t\t\trequire.Len(t, h2.Addrs(), 1)\n\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), time.Second)\n\t\t\tdefer cancel()\n\t\t\tgomock.InOrder(\n\t\t\t\tconnGater.EXPECT().InterceptPeerDial(h2.ID()).Return(true),\n\t\t\t\tconnGater.EXPECT().InterceptAddrDial(h2.ID(), gomock.Any()).Return(true),\n\t\t\t\tconnGater.EXPECT().InterceptSecured(network.DirOutbound, h2.ID(), gomock.Any()).Do(func(_ network.Direction, _ peer.ID, addrs network.ConnMultiaddrs) {\n\t\t\t\t\trequire.Equal(t, normalize(h2.Addrs()[0]), normalize(addrs.RemoteMultiaddr()))\n\t\t\t\t}),\n\t\t\t)\n\t\t\terr := h1.Connect(ctx, peer.AddrInfo{ID: h2.ID(), Addrs: h2.Addrs()})\n\t\t\trequire.Error(t, err)\n\t\t\trequire.NotErrorIs(t, err, context.DeadlineExceeded)\n\t\t})\n\t}\n}\n\nfunc TestInterceptUpgradedOutgoing(t *testing.T) {\n\tif race.WithRace() {\n\t\tt.Skip(\"The upgrader spawns a new Go routine, which leads to race conditions when using GoMock.\")\n\t}\n\tfor _, tc := range transportsToTest {\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\tctrl := gomock.NewController(t)\n\t\t\tdefer ctrl.Finish()\n\t\t\tconnGater := NewMockConnectionGater(ctrl)\n\n\t\t\th1 := tc.HostGenerator(t, TransportTestCaseOpts{NoListen: true, ConnGater: connGater})\n\t\t\th2 := tc.HostGenerator(t, TransportTestCaseOpts{})\n\t\t\tdefer h1.Close()\n\t\t\tdefer h2.Close()\n\t\t\trequire.Len(t, h2.Addrs(), 1)\n\t\t\trequire.Len(t, h2.Addrs(), 1)\n\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), time.Second)\n\t\t\tdefer cancel()\n\t\t\tgomock.InOrder(\n\t\t\t\tconnGater.EXPECT().InterceptPeerDial(h2.ID()).Return(true),\n\t\t\t\tconnGater.EXPECT().InterceptAddrDial(h2.ID(), gomock.Any()).Return(true),\n\t\t\t\tconnGater.EXPECT().InterceptSecured(network.DirOutbound, h2.ID(), gomock.Any()).Return(true),\n\t\t\t\tconnGater.EXPECT().InterceptUpgraded(gomock.Any()).Do(func(c network.Conn) {\n\t\t\t\t\t// remove the certhash component from WebTransport addresses\n\t\t\t\t\trequire.Equal(t, normalize(h2.Addrs()[0]).String(), normalize(c.RemoteMultiaddr()).String())\n\t\t\t\t\trequire.Equal(t, h1.ID(), c.LocalPeer())\n\t\t\t\t\trequire.Equal(t, h2.ID(), c.RemotePeer())\n\t\t\t\t}))\n\t\t\terr := h1.Connect(ctx, peer.AddrInfo{ID: h2.ID(), Addrs: h2.Addrs()})\n\t\t\trequire.Error(t, err)\n\t\t\trequire.NotErrorIs(t, err, context.DeadlineExceeded)\n\t\t})\n\t}\n}\n\nfunc TestInterceptAccept(t *testing.T) {\n\tif race.WithRace() {\n\t\tt.Skip(\"The upgrader spawns a new Go routine, which leads to race conditions when using GoMock.\")\n\t}\n\tfor _, tc := range transportsToTest {\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\tctrl := gomock.NewController(t)\n\t\t\tdefer ctrl.Finish()\n\t\t\tconnGater := NewMockConnectionGater(ctrl)\n\n\t\t\th1 := tc.HostGenerator(t, TransportTestCaseOpts{NoListen: true})\n\t\t\th2 := tc.HostGenerator(t, TransportTestCaseOpts{ConnGater: connGater})\n\t\t\tdefer h1.Close()\n\t\t\tdefer h2.Close()\n\t\t\trequire.Len(t, h2.Addrs(), 1)\n\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), time.Second)\n\t\t\tdefer cancel()\n\t\t\t// The basic host dials the first connection.\n\t\t\tif strings.Contains(tc.Name, \"WebRTC\") {\n\t\t\t\t// In WebRTC, retransmissions of the STUN packet might cause us to create multiple connections,\n\t\t\t\t// if the first connection attempt is rejected.\n\t\t\t\tconnGater.EXPECT().InterceptAccept(gomock.Any()).Do(func(addrs network.ConnMultiaddrs) {\n\t\t\t\t\trequire.Equal(t, normalize(h2.Addrs()[0]), normalize(addrs.LocalMultiaddr()))\n\t\t\t\t}).AnyTimes()\n\t\t\t} else if strings.Contains(tc.Name, \"WebSocket\") {\n\t\t\t\tconnGater.EXPECT().InterceptAccept(gomock.Any()).Do(func(addrs network.ConnMultiaddrs) {\n\t\t\t\t\trequire.Equal(t, addrPort(h2.Addrs()[0]), addrPort(addrs.LocalMultiaddr()))\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tconnGater.EXPECT().InterceptAccept(gomock.Any()).Do(func(addrs network.ConnMultiaddrs) {\n\t\t\t\t\t// remove the certhash component from WebTransport addresses\n\t\t\t\t\tmatest.AssertEqualMultiaddr(t, normalize(h2.Addrs()[0]), normalize(addrs.LocalMultiaddr()))\n\t\t\t\t})\n\t\t\t}\n\n\t\t\th1.Peerstore().AddAddrs(h2.ID(), h2.Addrs(), time.Hour)\n\t\t\t_, err := h1.NewStream(ctx, h2.ID(), protocol.TestingID)\n\t\t\trequire.Error(t, err)\n\t\t\tif _, err := h2.Addrs()[0].ValueForProtocol(ma.P_WEBRTC_DIRECT); err != nil {\n\t\t\t\t// WebRTC rejects connection attempt before an error can be sent to the client.\n\t\t\t\t// This means that the connection attempt will time out.\n\t\t\t\trequire.NotErrorIs(t, err, context.DeadlineExceeded)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestInterceptSecuredIncoming(t *testing.T) {\n\tif race.WithRace() {\n\t\tt.Skip(\"The upgrader spawns a new Go routine, which leads to race conditions when using GoMock.\")\n\t}\n\tfor _, tc := range transportsToTest {\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\tctrl := gomock.NewController(t)\n\t\t\tdefer ctrl.Finish()\n\t\t\tconnGater := NewMockConnectionGater(ctrl)\n\n\t\t\th1 := tc.HostGenerator(t, TransportTestCaseOpts{NoListen: true})\n\t\t\th2 := tc.HostGenerator(t, TransportTestCaseOpts{ConnGater: connGater})\n\t\t\tdefer h1.Close()\n\t\t\tdefer h2.Close()\n\t\t\trequire.Len(t, h2.Addrs(), 1)\n\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), time.Second)\n\t\t\tdefer cancel()\n\t\t\tgomock.InOrder(\n\t\t\t\tconnGater.EXPECT().InterceptAccept(gomock.Any()).Return(true),\n\t\t\t\tconnGater.EXPECT().InterceptSecured(network.DirInbound, h1.ID(), gomock.Any()).Do(func(_ network.Direction, _ peer.ID, addrs network.ConnMultiaddrs) {\n\t\t\t\t\t// remove the certhash component from WebTransport addresses\n\t\t\t\t\tmatest.AssertEqualMultiaddr(t, normalize(h2.Addrs()[0]), normalize(addrs.LocalMultiaddr()))\n\t\t\t\t}),\n\t\t\t)\n\t\t\th1.Peerstore().AddAddrs(h2.ID(), h2.Addrs(), time.Hour)\n\t\t\t_, err := h1.NewStream(ctx, h2.ID(), protocol.TestingID)\n\t\t\trequire.Error(t, err)\n\t\t\trequire.NotErrorIs(t, err, context.DeadlineExceeded)\n\t\t})\n\t}\n}\n\nfunc TestInterceptUpgradedIncoming(t *testing.T) {\n\tif race.WithRace() {\n\t\tt.Skip(\"The upgrader spawns a new Go routine, which leads to race conditions when using GoMock.\")\n\t}\n\tfor _, tc := range transportsToTest {\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\tctrl := gomock.NewController(t)\n\t\t\tdefer ctrl.Finish()\n\t\t\tconnGater := NewMockConnectionGater(ctrl)\n\n\t\t\th1 := tc.HostGenerator(t, TransportTestCaseOpts{NoListen: true})\n\t\t\th2 := tc.HostGenerator(t, TransportTestCaseOpts{ConnGater: connGater})\n\t\t\tdefer h1.Close()\n\t\t\tdefer h2.Close()\n\t\t\trequire.Len(t, h2.Addrs(), 1)\n\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), time.Second)\n\t\t\tdefer cancel()\n\t\t\tgomock.InOrder(\n\t\t\t\tconnGater.EXPECT().InterceptAccept(gomock.Any()).Return(true),\n\t\t\t\tconnGater.EXPECT().InterceptSecured(network.DirInbound, h1.ID(), gomock.Any()).Return(true),\n\t\t\t\tconnGater.EXPECT().InterceptUpgraded(gomock.Any()).Do(func(c network.Conn) {\n\t\t\t\t\t// remove the certhash component from WebTransport addresses\n\t\t\t\t\trequire.Equal(t, normalize(h2.Addrs()[0]).String(), normalize(c.LocalMultiaddr()).String())\n\t\t\t\t\trequire.Equal(t, h1.ID(), c.RemotePeer())\n\t\t\t\t\trequire.Equal(t, h2.ID(), c.LocalPeer())\n\t\t\t\t}),\n\t\t\t)\n\t\t\th1.Peerstore().AddAddrs(h2.ID(), h2.Addrs(), time.Hour)\n\t\t\t_, err := h1.NewStream(ctx, h2.ID(), protocol.TestingID)\n\t\t\trequire.Error(t, err)\n\t\t\trequire.NotErrorIs(t, err, context.DeadlineExceeded)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "p2p/test/transport/mock_connection_gater_test.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github.com/libp2p/go-libp2p/core/connmgr (interfaces: ConnectionGater)\n//\n// Generated by this command:\n//\n//\tmockgen -package transport_integration -destination mock_connection_gater_test.go github.com/libp2p/go-libp2p/core/connmgr ConnectionGater\n//\n\n// Package transport_integration is a generated GoMock package.\npackage transport_integration\n\nimport (\n\treflect \"reflect\"\n\n\tcontrol \"github.com/libp2p/go-libp2p/core/control\"\n\tnetwork \"github.com/libp2p/go-libp2p/core/network\"\n\tpeer \"github.com/libp2p/go-libp2p/core/peer\"\n\tmultiaddr \"github.com/multiformats/go-multiaddr\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockConnectionGater is a mock of ConnectionGater interface.\ntype MockConnectionGater struct {\n\tctrl     *gomock.Controller\n\trecorder *MockConnectionGaterMockRecorder\n\tisgomock struct{}\n}\n\n// MockConnectionGaterMockRecorder is the mock recorder for MockConnectionGater.\ntype MockConnectionGaterMockRecorder struct {\n\tmock *MockConnectionGater\n}\n\n// NewMockConnectionGater creates a new mock instance.\nfunc NewMockConnectionGater(ctrl *gomock.Controller) *MockConnectionGater {\n\tmock := &MockConnectionGater{ctrl: ctrl}\n\tmock.recorder = &MockConnectionGaterMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockConnectionGater) EXPECT() *MockConnectionGaterMockRecorder {\n\treturn m.recorder\n}\n\n// InterceptAccept mocks base method.\nfunc (m *MockConnectionGater) InterceptAccept(arg0 network.ConnMultiaddrs) bool {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"InterceptAccept\", arg0)\n\tret0, _ := ret[0].(bool)\n\treturn ret0\n}\n\n// InterceptAccept indicates an expected call of InterceptAccept.\nfunc (mr *MockConnectionGaterMockRecorder) InterceptAccept(arg0 any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"InterceptAccept\", reflect.TypeOf((*MockConnectionGater)(nil).InterceptAccept), arg0)\n}\n\n// InterceptAddrDial mocks base method.\nfunc (m *MockConnectionGater) InterceptAddrDial(arg0 peer.ID, arg1 multiaddr.Multiaddr) bool {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"InterceptAddrDial\", arg0, arg1)\n\tret0, _ := ret[0].(bool)\n\treturn ret0\n}\n\n// InterceptAddrDial indicates an expected call of InterceptAddrDial.\nfunc (mr *MockConnectionGaterMockRecorder) InterceptAddrDial(arg0, arg1 any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"InterceptAddrDial\", reflect.TypeOf((*MockConnectionGater)(nil).InterceptAddrDial), arg0, arg1)\n}\n\n// InterceptPeerDial mocks base method.\nfunc (m *MockConnectionGater) InterceptPeerDial(p peer.ID) bool {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"InterceptPeerDial\", p)\n\tret0, _ := ret[0].(bool)\n\treturn ret0\n}\n\n// InterceptPeerDial indicates an expected call of InterceptPeerDial.\nfunc (mr *MockConnectionGaterMockRecorder) InterceptPeerDial(p any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"InterceptPeerDial\", reflect.TypeOf((*MockConnectionGater)(nil).InterceptPeerDial), p)\n}\n\n// InterceptSecured mocks base method.\nfunc (m *MockConnectionGater) InterceptSecured(arg0 network.Direction, arg1 peer.ID, arg2 network.ConnMultiaddrs) bool {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"InterceptSecured\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(bool)\n\treturn ret0\n}\n\n// InterceptSecured indicates an expected call of InterceptSecured.\nfunc (mr *MockConnectionGaterMockRecorder) InterceptSecured(arg0, arg1, arg2 any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"InterceptSecured\", reflect.TypeOf((*MockConnectionGater)(nil).InterceptSecured), arg0, arg1, arg2)\n}\n\n// InterceptUpgraded mocks base method.\nfunc (m *MockConnectionGater) InterceptUpgraded(arg0 network.Conn) (bool, control.DisconnectReason) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"InterceptUpgraded\", arg0)\n\tret0, _ := ret[0].(bool)\n\tret1, _ := ret[1].(control.DisconnectReason)\n\treturn ret0, ret1\n}\n\n// InterceptUpgraded indicates an expected call of InterceptUpgraded.\nfunc (mr *MockConnectionGaterMockRecorder) InterceptUpgraded(arg0 any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"InterceptUpgraded\", reflect.TypeOf((*MockConnectionGater)(nil).InterceptUpgraded), arg0)\n}\n"
  },
  {
    "path": "p2p/test/transport/rcmgr_test.go",
    "content": "package transport_integration\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\tmocknetwork \"github.com/libp2p/go-libp2p/core/network/mocks\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/identify\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/ping\"\n\t\"github.com/multiformats/go-multiaddr/matest\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/mock/gomock\"\n)\n\nfunc TestResourceManagerIsUsed(t *testing.T) {\n\tfor _, tc := range transportsToTest {\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\tfor _, testDialer := range []bool{true, false} {\n\t\t\t\tt.Run(tc.Name+fmt.Sprintf(\" test_dialer=%v\", testDialer), func(t *testing.T) {\n\n\t\t\t\t\tvar reservedMemory, releasedMemory atomic.Int32\n\t\t\t\t\tdefer func() {\n\t\t\t\t\t\trequire.Equal(t, reservedMemory.Load(), releasedMemory.Load())\n\t\t\t\t\t\trequire.NotEqual(t, 0, reservedMemory.Load())\n\t\t\t\t\t}()\n\n\t\t\t\t\tctrl := gomock.NewController(t)\n\t\t\t\t\tdefer ctrl.Finish()\n\t\t\t\t\trcmgr := mocknetwork.NewMockResourceManager(ctrl)\n\t\t\t\t\trcmgr.EXPECT().Close()\n\n\t\t\t\t\tvar listener, dialer host.Host\n\t\t\t\t\tvar expectedPeer peer.ID\n\t\t\t\t\tvar expectedDir network.Direction\n\t\t\t\t\tvar expectedAddr gomock.Matcher\n\t\t\t\t\tif testDialer {\n\t\t\t\t\t\tlistener = tc.HostGenerator(t, TransportTestCaseOpts{NoRcmgr: true})\n\t\t\t\t\t\tdialer = tc.HostGenerator(t, TransportTestCaseOpts{NoListen: true, ResourceManager: rcmgr})\n\t\t\t\t\t\texpectedPeer = listener.ID()\n\t\t\t\t\t\texpectedDir = network.DirOutbound\n\t\t\t\t\t\texpectedAddr = matest.MultiaddrMatcher{Multiaddr: listener.Addrs()[0]}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlistener = tc.HostGenerator(t, TransportTestCaseOpts{ResourceManager: rcmgr})\n\t\t\t\t\t\tdialer = tc.HostGenerator(t, TransportTestCaseOpts{NoListen: true, NoRcmgr: true})\n\t\t\t\t\t\texpectedPeer = dialer.ID()\n\t\t\t\t\t\texpectedDir = network.DirInbound\n\t\t\t\t\t\texpectedAddr = gomock.Any()\n\t\t\t\t\t}\n\n\t\t\t\t\tpeerScope := mocknetwork.NewMockPeerScope(ctrl)\n\t\t\t\t\tpeerScope.EXPECT().ReserveMemory(gomock.Any(), gomock.Any()).AnyTimes().Do(func(amount int, _ uint8) {\n\t\t\t\t\t\treservedMemory.Add(int32(amount))\n\t\t\t\t\t})\n\t\t\t\t\tpeerScope.EXPECT().ReleaseMemory(gomock.Any()).AnyTimes().Do(func(amount int) {\n\t\t\t\t\t\treleasedMemory.Add(int32(amount))\n\t\t\t\t\t})\n\t\t\t\t\tpeerScope.EXPECT().BeginSpan().AnyTimes().DoAndReturn(func() (network.ResourceScopeSpan, error) {\n\t\t\t\t\t\ts := mocknetwork.NewMockResourceScopeSpan(ctrl)\n\t\t\t\t\t\ts.EXPECT().BeginSpan().AnyTimes().Return(mocknetwork.NewMockResourceScopeSpan(ctrl), nil)\n\t\t\t\t\t\t// No need to track these memory reservations since we assert that Done is called\n\t\t\t\t\t\ts.EXPECT().ReserveMemory(gomock.Any(), gomock.Any())\n\t\t\t\t\t\ts.EXPECT().Done()\n\t\t\t\t\t\treturn s, nil\n\t\t\t\t\t})\n\t\t\t\t\tvar calledSetPeer atomic.Bool\n\n\t\t\t\t\tconnScope := mocknetwork.NewMockConnManagementScope(ctrl)\n\t\t\t\t\tconnScope.EXPECT().SetPeer(expectedPeer).Do(func(peer.ID) {\n\t\t\t\t\t\tcalledSetPeer.Store(true)\n\t\t\t\t\t})\n\t\t\t\t\tconnScope.EXPECT().PeerScope().AnyTimes().DoAndReturn(func() network.PeerScope {\n\t\t\t\t\t\tif calledSetPeer.Load() {\n\t\t\t\t\t\t\treturn peerScope\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t})\n\t\t\t\t\tif tc.Name == \"WebRTC\" {\n\t\t\t\t\t\t// webrtc receive buffer is a fix sized buffer allocated up front\n\t\t\t\t\t\tconnScope.EXPECT().ReserveMemory(gomock.Any(), gomock.Any())\n\t\t\t\t\t}\n\t\t\t\t\tconnScope.EXPECT().Done().MinTimes(1)\n\t\t\t\t\t// udp transports won't have FD\n\t\t\t\t\tudpTransportRegex := regexp.MustCompile(`QUIC|WebTransport|WebRTC`)\n\t\t\t\t\texpectFd := !udpTransportRegex.MatchString(tc.Name)\n\n\t\t\t\t\tif !testDialer && (strings.Contains(tc.Name, \"QUIC\") || strings.Contains(tc.Name, \"WebTransport\")) {\n\t\t\t\t\t\trcmgr.EXPECT().VerifySourceAddress(gomock.Any()).Return(false)\n\t\t\t\t\t}\n\t\t\t\t\trcmgr.EXPECT().OpenConnection(expectedDir, expectFd, expectedAddr).Return(connScope, nil)\n\n\t\t\t\t\tvar allStreamsDone sync.WaitGroup\n\t\t\t\t\trcmgr.EXPECT().OpenStream(expectedPeer, gomock.Any()).AnyTimes().DoAndReturn(func(_ peer.ID, _ network.Direction) (network.StreamManagementScope, error) {\n\t\t\t\t\t\tallStreamsDone.Add(1)\n\t\t\t\t\t\tstreamScope := mocknetwork.NewMockStreamManagementScope(ctrl)\n\t\t\t\t\t\t// No need to track these memory reservations since we assert that Done is called\n\t\t\t\t\t\tstreamScope.EXPECT().ReserveMemory(gomock.Any(), gomock.Any()).AnyTimes()\n\t\t\t\t\t\tstreamScope.EXPECT().ReleaseMemory(gomock.Any()).AnyTimes()\n\t\t\t\t\t\tstreamScope.EXPECT().BeginSpan().AnyTimes().DoAndReturn(func() (network.ResourceScopeSpan, error) {\n\t\t\t\t\t\t\ts := mocknetwork.NewMockResourceScopeSpan(ctrl)\n\t\t\t\t\t\t\ts.EXPECT().BeginSpan().AnyTimes().Return(mocknetwork.NewMockResourceScopeSpan(ctrl), nil)\n\t\t\t\t\t\t\ts.EXPECT().Done()\n\t\t\t\t\t\t\treturn s, nil\n\t\t\t\t\t\t})\n\n\t\t\t\t\t\tstreamScope.EXPECT().SetService(gomock.Any()).MaxTimes(1)\n\t\t\t\t\t\tstreamScope.EXPECT().SetProtocol(gomock.Any())\n\n\t\t\t\t\t\tstreamScope.EXPECT().Done().Do(func() {\n\t\t\t\t\t\t\tallStreamsDone.Done()\n\t\t\t\t\t\t})\n\t\t\t\t\t\treturn streamScope, nil\n\t\t\t\t\t})\n\n\t\t\t\t\trequire.NoError(t, dialer.Connect(context.Background(), peer.AddrInfo{\n\t\t\t\t\t\tID:    listener.ID(),\n\t\t\t\t\t\tAddrs: listener.Addrs(),\n\t\t\t\t\t}))\n\t\t\t\t\t// Wait for any in progress identifies to finish.\n\t\t\t\t\t// We shouldn't have to do this, but basic host currently\n\t\t\t\t\t// always does an identify.\n\t\t\t\t\t<-dialer.(interface{ IDService() identify.IDService }).IDService().IdentifyWait(dialer.Network().ConnsToPeer(listener.ID())[0])\n\t\t\t\t\t<-listener.(interface{ IDService() identify.IDService }).IDService().IdentifyWait(listener.Network().ConnsToPeer(dialer.ID())[0])\n\t\t\t\t\t<-ping.Ping(context.Background(), dialer, listener.ID())\n\t\t\t\t\terr := dialer.Network().ClosePeer(listener.ID())\n\t\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\t\t// Wait a bit for any pending .Adds before we call .Wait to avoid a data race.\n\t\t\t\t\t// This shouldn't be necessary since it should be impossible\n\t\t\t\t\t// for an OpenStream to happen *after* a ClosePeer, however\n\t\t\t\t\t// in practice it does and leads to test flakiness.\n\t\t\t\t\ttime.Sleep(10 * time.Millisecond)\n\t\t\t\t\tallStreamsDone.Wait()\n\t\t\t\t\tdialer.Close()\n\t\t\t\t\tlistener.Close()\n\t\t\t\t})\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "p2p/test/transport/transport_test.go",
    "content": "package transport_integration\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/ecdsa\"\n\t\"crypto/elliptic\"\n\t\"crypto/rand\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"crypto/x509/pkix\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"math/big\"\n\t\"net\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\tlibp2ptls \"github.com/libp2p/go-libp2p/p2p/security/tls\"\n\n\t\"github.com/libp2p/go-libp2p\"\n\t\"github.com/libp2p/go-libp2p/config\"\n\t\"github.com/libp2p/go-libp2p/core/connmgr\"\n\t\"github.com/libp2p/go-libp2p/core/event\"\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\tmocknetwork \"github.com/libp2p/go-libp2p/core/network/mocks\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\t\"github.com/libp2p/go-libp2p/core/sec\"\n\trcmgr \"github.com/libp2p/go-libp2p/p2p/host/resource-manager\"\n\t\"github.com/libp2p/go-libp2p/p2p/muxer/yamux\"\n\t\"github.com/libp2p/go-libp2p/p2p/net/swarm\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/ping\"\n\t\"github.com/libp2p/go-libp2p/p2p/security/noise\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/quicreuse\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/tcp\"\n\tlibp2pwebrtc \"github.com/libp2p/go-libp2p/p2p/transport/webrtc\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/websocket\"\n\t\"go.uber.org/mock/gomock\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype TransportTestCase struct {\n\tName          string\n\tHostGenerator func(t *testing.T, opts TransportTestCaseOpts) host.Host\n}\n\ntype TransportTestCaseOpts struct {\n\tNoListen        bool\n\tNoRcmgr         bool\n\tConnGater       connmgr.ConnectionGater\n\tResourceManager network.ResourceManager\n}\n\nfunc transformOpts(opts TransportTestCaseOpts) []config.Option {\n\tvar libp2pOpts []libp2p.Option\n\n\tif opts.NoRcmgr {\n\t\tlibp2pOpts = append(libp2pOpts, libp2p.ResourceManager(&network.NullResourceManager{}))\n\t}\n\tif opts.ConnGater != nil {\n\t\tlibp2pOpts = append(libp2pOpts, libp2p.ConnectionGater(opts.ConnGater))\n\t}\n\n\tif opts.ResourceManager != nil {\n\t\tlibp2pOpts = append(libp2pOpts, libp2p.ResourceManager(opts.ResourceManager))\n\t}\n\treturn libp2pOpts\n}\n\nfunc selfSignedTLSConfig(t *testing.T) *tls.Config {\n\tt.Helper()\n\tpriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)\n\trequire.NoError(t, err)\n\n\tnotBefore := time.Now()\n\tnotAfter := notBefore.Add(365 * 24 * time.Hour)\n\n\tserialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)\n\tserialNumber, err := rand.Int(rand.Reader, serialNumberLimit)\n\trequire.NoError(t, err)\n\n\tcertTemplate := x509.Certificate{\n\t\tSerialNumber: serialNumber,\n\t\tSubject: pkix.Name{\n\t\t\tOrganization: []string{\"Test\"},\n\t\t},\n\t\tNotBefore:             notBefore,\n\t\tNotAfter:              notAfter,\n\t\tKeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,\n\t\tExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},\n\t\tBasicConstraintsValid: true,\n\t}\n\n\tderBytes, err := x509.CreateCertificate(rand.Reader, &certTemplate, &certTemplate, &priv.PublicKey, priv)\n\trequire.NoError(t, err)\n\n\tcert := tls.Certificate{\n\t\tCertificate: [][]byte{derBytes},\n\t\tPrivateKey:  priv,\n\t}\n\n\ttlsConfig := &tls.Config{\n\t\tCertificates: []tls.Certificate{cert},\n\t}\n\treturn tlsConfig\n}\n\nvar transportsToTest = []TransportTestCase{\n\t{\n\t\tName: \"TCP / Noise / Yamux\",\n\t\tHostGenerator: func(t *testing.T, opts TransportTestCaseOpts) host.Host {\n\t\t\tlibp2pOpts := transformOpts(opts)\n\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.Security(noise.ID, noise.New))\n\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.Muxer(yamux.ID, yamux.DefaultTransport))\n\t\t\tif opts.NoListen {\n\t\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.NoListenAddrs)\n\t\t\t} else {\n\t\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.ListenAddrStrings(\"/ip4/127.0.0.1/tcp/0\"))\n\t\t\t}\n\t\t\th, err := libp2p.New(libp2pOpts...)\n\t\t\trequire.NoError(t, err)\n\t\t\treturn h\n\t\t},\n\t},\n\t{\n\t\tName: \"TCP / TLS / Yamux\",\n\t\tHostGenerator: func(t *testing.T, opts TransportTestCaseOpts) host.Host {\n\t\t\tlibp2pOpts := transformOpts(opts)\n\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.Security(libp2ptls.ID, libp2ptls.New))\n\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.Muxer(yamux.ID, yamux.DefaultTransport))\n\t\t\tif opts.NoListen {\n\t\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.NoListenAddrs)\n\t\t\t} else {\n\t\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.ListenAddrStrings(\"/ip4/127.0.0.1/tcp/0\"))\n\t\t\t}\n\t\t\th, err := libp2p.New(libp2pOpts...)\n\t\t\trequire.NoError(t, err)\n\t\t\treturn h\n\t\t},\n\t},\n\t{\n\t\tName: \"TCP-Shared / TLS / Yamux\",\n\t\tHostGenerator: func(t *testing.T, opts TransportTestCaseOpts) host.Host {\n\t\t\tlibp2pOpts := transformOpts(opts)\n\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.ShareTCPListener())\n\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.Security(libp2ptls.ID, libp2ptls.New))\n\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.Muxer(yamux.ID, yamux.DefaultTransport))\n\t\t\tif opts.NoListen {\n\t\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.NoListenAddrs)\n\t\t\t} else {\n\t\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.ListenAddrStrings(\"/ip4/127.0.0.1/tcp/0\"))\n\t\t\t}\n\t\t\th, err := libp2p.New(libp2pOpts...)\n\t\t\trequire.NoError(t, err)\n\t\t\treturn h\n\t\t},\n\t},\n\t{\n\t\tName: \"TCP-Shared-WithMetrics / TLS / Yamux\",\n\t\tHostGenerator: func(t *testing.T, opts TransportTestCaseOpts) host.Host {\n\t\t\tlibp2pOpts := transformOpts(opts)\n\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.ShareTCPListener())\n\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.Security(libp2ptls.ID, libp2ptls.New))\n\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.Muxer(yamux.ID, yamux.DefaultTransport))\n\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.Transport(tcp.NewTCPTransport, tcp.WithMetrics()))\n\t\t\tif opts.NoListen {\n\t\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.NoListenAddrs)\n\t\t\t} else {\n\t\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.ListenAddrStrings(\"/ip4/127.0.0.1/tcp/0\"))\n\t\t\t}\n\t\t\th, err := libp2p.New(libp2pOpts...)\n\t\t\trequire.NoError(t, err)\n\t\t\treturn h\n\t\t},\n\t},\n\t{\n\t\tName: \"TCP-WithMetrics / TLS / Yamux\",\n\t\tHostGenerator: func(t *testing.T, opts TransportTestCaseOpts) host.Host {\n\t\t\tlibp2pOpts := transformOpts(opts)\n\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.Security(libp2ptls.ID, libp2ptls.New))\n\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.Muxer(yamux.ID, yamux.DefaultTransport))\n\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.Transport(tcp.NewTCPTransport, tcp.WithMetrics()))\n\t\t\tif opts.NoListen {\n\t\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.NoListenAddrs)\n\t\t\t} else {\n\t\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.ListenAddrStrings(\"/ip4/127.0.0.1/tcp/0\"))\n\t\t\t}\n\t\t\th, err := libp2p.New(libp2pOpts...)\n\t\t\trequire.NoError(t, err)\n\t\t\treturn h\n\t\t},\n\t},\n\t{\n\t\tName: \"WebSocket-Shared\",\n\t\tHostGenerator: func(t *testing.T, opts TransportTestCaseOpts) host.Host {\n\t\t\tlibp2pOpts := transformOpts(opts)\n\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.ShareTCPListener())\n\t\t\tif opts.NoListen {\n\t\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.NoListenAddrs)\n\t\t\t} else {\n\t\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.ListenAddrStrings(\"/ip4/127.0.0.1/tcp/0/ws\"))\n\t\t\t}\n\t\t\th, err := libp2p.New(libp2pOpts...)\n\t\t\trequire.NoError(t, err)\n\t\t\treturn h\n\t\t},\n\t},\n\t{\n\t\tName: \"WebSocket-Secured-Shared\",\n\t\tHostGenerator: func(t *testing.T, opts TransportTestCaseOpts) host.Host {\n\t\t\tlibp2pOpts := transformOpts(opts)\n\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.ShareTCPListener())\n\t\t\tif opts.NoListen {\n\t\t\t\tconfig := tls.Config{InsecureSkipVerify: true}\n\t\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.NoListenAddrs, libp2p.Transport(websocket.New, websocket.WithTLSClientConfig(&config)))\n\t\t\t} else {\n\t\t\t\tconfig := selfSignedTLSConfig(t)\n\t\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.ListenAddrStrings(\"/ip4/127.0.0.1/tcp/0/sni/localhost/tls/ws\"), libp2p.Transport(websocket.New, websocket.WithTLSConfig(config)))\n\t\t\t}\n\t\t\th, err := libp2p.New(libp2pOpts...)\n\t\t\trequire.NoError(t, err)\n\t\t\treturn h\n\t\t},\n\t},\n\t{\n\t\tName: \"WebSocket\",\n\t\tHostGenerator: func(t *testing.T, opts TransportTestCaseOpts) host.Host {\n\t\t\tlibp2pOpts := transformOpts(opts)\n\t\t\tif opts.NoListen {\n\t\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.NoListenAddrs)\n\t\t\t} else {\n\t\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.ListenAddrStrings(\"/ip4/127.0.0.1/tcp/0/ws\"))\n\t\t\t}\n\t\t\th, err := libp2p.New(libp2pOpts...)\n\t\t\trequire.NoError(t, err)\n\t\t\treturn h\n\t\t},\n\t},\n\t{\n\t\tName: \"WebSocket-Secured\",\n\t\tHostGenerator: func(t *testing.T, opts TransportTestCaseOpts) host.Host {\n\t\t\tlibp2pOpts := transformOpts(opts)\n\t\t\tif opts.NoListen {\n\t\t\t\tconfig := tls.Config{InsecureSkipVerify: true}\n\t\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.NoListenAddrs, libp2p.Transport(websocket.New, websocket.WithTLSClientConfig(&config)))\n\t\t\t} else {\n\t\t\t\tconfig := selfSignedTLSConfig(t)\n\t\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.ListenAddrStrings(\"/ip4/127.0.0.1/tcp/0/sni/localhost/tls/ws\"), libp2p.Transport(websocket.New, websocket.WithTLSConfig(config)))\n\t\t\t}\n\t\t\th, err := libp2p.New(libp2pOpts...)\n\t\t\trequire.NoError(t, err)\n\t\t\treturn h\n\t\t},\n\t},\n\t{\n\t\tName: \"QUIC\",\n\t\tHostGenerator: func(t *testing.T, opts TransportTestCaseOpts) host.Host {\n\t\t\tlibp2pOpts := transformOpts(opts)\n\t\t\tif opts.NoListen {\n\t\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.NoListenAddrs)\n\t\t\t} else {\n\t\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.ListenAddrStrings(\"/ip4/127.0.0.1/udp/0/quic-v1\"))\n\t\t\t}\n\t\t\th, err := libp2p.New(libp2pOpts...)\n\t\t\trequire.NoError(t, err)\n\t\t\treturn h\n\t\t},\n\t},\n\t{\n\t\tName: \"QUIC-CustomReuse\",\n\t\tHostGenerator: func(t *testing.T, opts TransportTestCaseOpts) host.Host {\n\t\t\tlibp2pOpts := transformOpts(opts)\n\t\t\tif opts.NoListen {\n\t\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.NoListenAddrs, libp2p.QUICReuse(quicreuse.NewConnManager))\n\t\t\t} else {\n\t\t\t\tqr := libp2p.QUICReuse(quicreuse.NewConnManager)\n\t\t\t\tif !opts.NoRcmgr && opts.ResourceManager != nil {\n\t\t\t\t\tqr = libp2p.QUICReuse(\n\t\t\t\t\t\tquicreuse.NewConnManager,\n\t\t\t\t\t\tquicreuse.VerifySourceAddress(opts.ResourceManager.VerifySourceAddress))\n\t\t\t\t}\n\t\t\t\tlibp2pOpts = append(libp2pOpts,\n\t\t\t\t\tqr,\n\t\t\t\t\tlibp2p.ListenAddrStrings(\"/ip4/127.0.0.1/udp/0/quic-v1\"),\n\t\t\t\t)\n\t\t\t}\n\t\t\th, err := libp2p.New(libp2pOpts...)\n\t\t\trequire.NoError(t, err)\n\t\t\treturn h\n\t\t},\n\t},\n\t{\n\t\tName: \"WebTransport\",\n\t\tHostGenerator: func(t *testing.T, opts TransportTestCaseOpts) host.Host {\n\t\t\tlibp2pOpts := transformOpts(opts)\n\t\t\tif opts.NoListen {\n\t\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.NoListenAddrs)\n\t\t\t} else {\n\t\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.ListenAddrStrings(\"/ip4/127.0.0.1/udp/0/quic-v1/webtransport\"))\n\t\t\t}\n\t\t\th, err := libp2p.New(libp2pOpts...)\n\t\t\trequire.NoError(t, err)\n\t\t\treturn h\n\t\t},\n\t},\n\t{\n\t\tName: \"WebTransport-CustomReuse\",\n\t\tHostGenerator: func(t *testing.T, opts TransportTestCaseOpts) host.Host {\n\t\t\tlibp2pOpts := transformOpts(opts)\n\t\t\tif opts.NoListen {\n\t\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.NoListenAddrs, libp2p.QUICReuse(quicreuse.NewConnManager))\n\t\t\t} else {\n\t\t\t\tqr := libp2p.QUICReuse(quicreuse.NewConnManager)\n\t\t\t\tif !opts.NoRcmgr && opts.ResourceManager != nil {\n\t\t\t\t\tqr = libp2p.QUICReuse(\n\t\t\t\t\t\tquicreuse.NewConnManager,\n\t\t\t\t\t\tquicreuse.VerifySourceAddress(opts.ResourceManager.VerifySourceAddress),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tlibp2pOpts = append(libp2pOpts,\n\t\t\t\t\tqr,\n\t\t\t\t\tlibp2p.ListenAddrStrings(\"/ip4/127.0.0.1/udp/0/quic-v1/webtransport\"),\n\t\t\t\t)\n\t\t\t}\n\t\t\th, err := libp2p.New(libp2pOpts...)\n\t\t\trequire.NoError(t, err)\n\t\t\treturn h\n\t\t},\n\t},\n\t{\n\t\tName: \"WebRTC\",\n\t\tHostGenerator: func(t *testing.T, opts TransportTestCaseOpts) host.Host {\n\t\t\tlibp2pOpts := transformOpts(opts)\n\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.Transport(libp2pwebrtc.New))\n\t\t\tif opts.NoListen {\n\t\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.NoListenAddrs)\n\t\t\t} else {\n\t\t\t\tlibp2pOpts = append(libp2pOpts, libp2p.ListenAddrStrings(\"/ip4/127.0.0.1/udp/0/webrtc-direct\"))\n\t\t\t}\n\t\t\th, err := libp2p.New(libp2pOpts...)\n\t\t\trequire.NoError(t, err)\n\t\t\treturn h\n\t\t},\n\t},\n}\n\nfunc TestPing(t *testing.T) {\n\tfor _, tc := range transportsToTest {\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\th1 := tc.HostGenerator(t, TransportTestCaseOpts{})\n\t\t\th2 := tc.HostGenerator(t, TransportTestCaseOpts{NoListen: true})\n\t\t\tdefer h1.Close()\n\t\t\tdefer h2.Close()\n\n\t\t\trequire.NoError(t, h2.Connect(context.Background(), peer.AddrInfo{\n\t\t\t\tID:    h1.ID(),\n\t\t\t\tAddrs: h1.Addrs(),\n\t\t\t}))\n\n\t\t\tctx := context.Background()\n\t\t\tres := <-ping.Ping(ctx, h2, h1.ID())\n\t\t\trequire.NoError(t, res.Error)\n\t\t})\n\t}\n}\n\nfunc TestBigPing(t *testing.T) {\n\t// 64k buffers\n\tsendBuf := make([]byte, 64<<10)\n\trecvBuf := make([]byte, 64<<10)\n\tconst totalSends = 64\n\n\t// Fill with random bytes\n\t_, err := rand.Read(sendBuf)\n\trequire.NoError(t, err)\n\n\tfor _, tc := range transportsToTest {\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\th1 := tc.HostGenerator(t, TransportTestCaseOpts{})\n\t\t\th2 := tc.HostGenerator(t, TransportTestCaseOpts{NoListen: true})\n\t\t\tdefer h1.Close()\n\t\t\tdefer h2.Close()\n\n\t\t\trequire.NoError(t, h2.Connect(context.Background(), peer.AddrInfo{\n\t\t\t\tID:    h1.ID(),\n\t\t\t\tAddrs: h1.Addrs(),\n\t\t\t}))\n\n\t\t\th1.SetStreamHandler(\"/big-ping\", func(s network.Stream) {\n\t\t\t\tio.Copy(s, s)\n\t\t\t\ts.Close()\n\t\t\t})\n\n\t\t\terrCh := make(chan error, 1)\n\t\t\tallocs := testing.AllocsPerRun(10, func() {\n\t\t\t\ts, err := h2.NewStream(context.Background(), h1.ID(), \"/big-ping\")\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tdefer s.Close()\n\n\t\t\t\tgo func() {\n\t\t\t\t\tfor range totalSends {\n\t\t\t\t\t\t_, err := io.ReadFull(s, recvBuf)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\terrCh <- err\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif !bytes.Equal(sendBuf, recvBuf) {\n\t\t\t\t\t\t\terrCh <- fmt.Errorf(\"received data does not match sent data\")\n\t\t\t\t\t\t}\n\n\t\t\t\t\t}\n\t\t\t\t\t_, err = s.Read([]byte{0})\n\t\t\t\t\terrCh <- err\n\t\t\t\t}()\n\n\t\t\t\tfor range totalSends {\n\t\t\t\t\ts.Write(sendBuf)\n\t\t\t\t}\n\t\t\t\ts.CloseWrite()\n\t\t\t\trequire.ErrorIs(t, <-errCh, io.EOF)\n\t\t\t})\n\n\t\t\tif int(allocs) > (len(sendBuf)*totalSends)/4 {\n\t\t\t\tt.Logf(\"Expected fewer allocs, got: %f\", allocs)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestLotsOfDataManyStreams tests sending a lot of data on multiple streams.\nfunc TestLotsOfDataManyStreams(t *testing.T) {\n\t// Skip on windows because of https://github.com/libp2p/go-libp2p/issues/2341\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"Skipping on windows because of https://github.com/libp2p/go-libp2p/issues/2341\")\n\t}\n\n\t// 64k buffer\n\tconst bufSize = 64 << 10\n\tsendBuf := [bufSize]byte{}\n\tconst totalStreams = 500\n\tconst parallel = 8\n\t// Total sends are > 20MiB\n\trequire.Greater(t, len(sendBuf)*totalStreams, 20<<20)\n\tt.Log(\"Total sends:\", len(sendBuf)*totalStreams)\n\n\t// Fill with random bytes\n\t_, err := rand.Read(sendBuf[:])\n\trequire.NoError(t, err)\n\n\tfor _, tc := range transportsToTest {\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\th1 := tc.HostGenerator(t, TransportTestCaseOpts{})\n\t\t\th2 := tc.HostGenerator(t, TransportTestCaseOpts{NoListen: true})\n\t\t\tdefer h1.Close()\n\t\t\tdefer h2.Close()\n\t\t\tstart := time.Now()\n\t\t\tdefer func() {\n\t\t\t\tt.Log(\"Total time:\", time.Since(start))\n\t\t\t}()\n\n\t\t\trequire.NoError(t, h2.Connect(context.Background(), peer.AddrInfo{\n\t\t\t\tID:    h1.ID(),\n\t\t\t\tAddrs: h1.Addrs(),\n\t\t\t}))\n\n\t\t\th1.SetStreamHandler(\"/big-ping\", func(s network.Stream) {\n\t\t\t\tio.Copy(s, s)\n\t\t\t\ts.Close()\n\t\t\t})\n\n\t\t\tsem := make(chan struct{}, parallel)\n\t\t\tvar wg sync.WaitGroup\n\t\t\tfor range totalStreams {\n\t\t\t\twg.Add(1)\n\t\t\t\tsem <- struct{}{}\n\t\t\t\tgo func() {\n\t\t\t\t\tdefer wg.Done()\n\t\t\t\t\trecvBuf := [bufSize]byte{}\n\t\t\t\t\tdefer func() { <-sem }()\n\n\t\t\t\t\ts, err := h2.NewStream(context.Background(), h1.ID(), \"/big-ping\")\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\tdefer s.Close()\n\n\t\t\t\t\t_, err = s.Write(sendBuf[:])\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\ts.CloseWrite()\n\n\t\t\t\t\t_, err = io.ReadFull(s, recvBuf[:])\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\trequire.Equal(t, sendBuf, recvBuf)\n\n\t\t\t\t\t_, err = s.Read([]byte{0})\n\t\t\t\t\trequire.ErrorIs(t, err, io.EOF)\n\t\t\t\t}()\n\t\t\t}\n\n\t\t\twg.Wait()\n\t\t})\n\t}\n}\n\nfunc TestManyStreams(t *testing.T) {\n\tconst streamCount = 128\n\tfor _, tc := range transportsToTest {\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\th1 := tc.HostGenerator(t, TransportTestCaseOpts{NoRcmgr: true})\n\t\t\th2 := tc.HostGenerator(t, TransportTestCaseOpts{NoListen: true, NoRcmgr: true})\n\t\t\tdefer h1.Close()\n\t\t\tdefer h2.Close()\n\n\t\t\trequire.NoError(t, h2.Connect(context.Background(), peer.AddrInfo{\n\t\t\t\tID:    h1.ID(),\n\t\t\t\tAddrs: h1.Addrs(),\n\t\t\t}))\n\n\t\t\th1.SetStreamHandler(\"echo\", func(s network.Stream) {\n\t\t\t\tio.Copy(s, s)\n\t\t\t\ts.CloseWrite()\n\t\t\t})\n\n\t\t\tstreams := make([]network.Stream, streamCount)\n\t\t\tfor i := range streamCount {\n\t\t\t\ts, err := h2.NewStream(context.Background(), h1.ID(), \"echo\")\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tstreams[i] = s\n\t\t\t}\n\n\t\t\twg := sync.WaitGroup{}\n\t\t\twg.Add(streamCount)\n\t\t\terrCh := make(chan error, 1)\n\t\t\tfor _, s := range streams {\n\t\t\t\tgo func(s network.Stream) {\n\t\t\t\t\tdefer wg.Done()\n\n\t\t\t\t\ts.Write([]byte(\"hello\"))\n\t\t\t\t\ts.CloseWrite()\n\t\t\t\t\tb, err := io.ReadAll(s)\n\t\t\t\t\tif err == nil {\n\t\t\t\t\t\tif !bytes.Equal(b, []byte(\"hello\")) {\n\t\t\t\t\t\t\terr = fmt.Errorf(\"received data does not match sent data\")\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tselect {\n\t\t\t\t\t\tcase errCh <- err:\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}(s)\n\t\t\t}\n\t\t\twg.Wait()\n\t\t\tclose(errCh)\n\n\t\t\trequire.NoError(t, <-errCh)\n\t\t\tfor _, s := range streams {\n\t\t\t\trequire.NoError(t, s.Close())\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestMoreStreamsThanOurLimits tests handling more streams than our and the\n// peer's resource limits. It spawns 1024 Go routines that try to open a stream\n// and send and receive data. If they encounter an error they'll try again after\n// a sleep. If the transport is well behaved, eventually all Go routines will\n// have sent and received a message.\nfunc TestMoreStreamsThanOurLimits(t *testing.T) {\n\tconst streamCount = 1024\n\tfor _, tc := range transportsToTest {\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\tif strings.Contains(tc.Name, \"WebRTC\") {\n\t\t\t\tt.Skip(\"This test potentially exhausts the uint16 WebRTC stream ID space.\")\n\t\t\t}\n\t\t\tlistenerLimits := rcmgr.PartialLimitConfig{\n\t\t\t\tPeerDefault: rcmgr.ResourceLimits{\n\t\t\t\t\tStreams:         32,\n\t\t\t\t\tStreamsInbound:  16,\n\t\t\t\t\tStreamsOutbound: 16,\n\t\t\t\t},\n\t\t\t}\n\t\t\tr, err := rcmgr.NewResourceManager(rcmgr.NewFixedLimiter(listenerLimits.Build(rcmgr.DefaultLimits.AutoScale())))\n\t\t\trequire.NoError(t, err)\n\t\t\tlistener := tc.HostGenerator(t, TransportTestCaseOpts{ResourceManager: r})\n\t\t\tdialer := tc.HostGenerator(t, TransportTestCaseOpts{NoListen: true, NoRcmgr: true})\n\t\t\tdefer listener.Close()\n\t\t\tdefer dialer.Close()\n\n\t\t\trequire.NoError(t, dialer.Connect(context.Background(), peer.AddrInfo{\n\t\t\t\tID:    listener.ID(),\n\t\t\t\tAddrs: listener.Addrs(),\n\t\t\t}))\n\n\t\t\tvar handledStreams atomic.Int32\n\t\t\tvar sawFirstErr atomic.Bool\n\n\t\t\tworkQueue := make(chan struct{}, streamCount)\n\t\t\tfor range streamCount {\n\t\t\t\tworkQueue <- struct{}{}\n\t\t\t}\n\t\t\tclose(workQueue)\n\n\t\t\tlistener.SetStreamHandler(\"echo\", func(s network.Stream) {\n\t\t\t\t// Wait a bit so that we have more parallel streams open at the same time\n\t\t\t\ttime.Sleep(time.Millisecond * 10)\n\t\t\t\tio.Copy(s, s)\n\t\t\t\ts.Close()\n\t\t\t})\n\n\t\t\twg := sync.WaitGroup{}\n\t\t\terrCh := make(chan error, 1)\n\t\t\tvar completedStreams atomic.Int32\n\n\t\t\tconst maxWorkerCount = streamCount\n\t\t\tworkerCount := 4\n\n\t\t\tvar startWorker func(workerIdx int)\n\t\t\tstartWorker = func(workerIdx int) {\n\t\t\t\twg.Add(1)\n\t\t\t\tdefer wg.Done()\n\t\t\t\tfor {\n\t\t\t\t\t_, ok := <-workQueue\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\t// Inline function so we can use defer\n\t\t\t\t\tfunc() {\n\t\t\t\t\t\tvar didErr bool\n\t\t\t\t\t\tdefer completedStreams.Add(1)\n\t\t\t\t\t\tdefer func() {\n\t\t\t\t\t\t\t// Only the first worker adds more workers\n\t\t\t\t\t\t\tif workerIdx == 0 && !didErr && !sawFirstErr.Load() {\n\t\t\t\t\t\t\t\tnextWorkerCount := workerCount * 2\n\t\t\t\t\t\t\t\tif nextWorkerCount < maxWorkerCount {\n\t\t\t\t\t\t\t\t\tfor i := workerCount; i < nextWorkerCount; i++ {\n\t\t\t\t\t\t\t\t\t\tgo startWorker(i)\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tworkerCount = nextWorkerCount\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}()\n\n\t\t\t\t\t\tvar s network.Stream\n\t\t\t\t\t\tvar err error\n\t\t\t\t\t\t// maxRetries is an arbitrary retry amount if there's any error.\n\t\t\t\t\t\tmaxRetries := streamCount * 4\n\t\t\t\t\t\tshouldRetry := func(_ error) bool {\n\t\t\t\t\t\t\tdidErr = true\n\t\t\t\t\t\t\tsawFirstErr.Store(true)\n\t\t\t\t\t\t\tmaxRetries--\n\t\t\t\t\t\t\tif maxRetries == 0 || len(errCh) > 0 {\n\t\t\t\t\t\t\t\tselect {\n\t\t\t\t\t\t\t\tcase errCh <- errors.New(\"max retries exceeded\"):\n\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn false\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn true\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tfor {\n\t\t\t\t\t\t\ts, err = dialer.NewStream(context.Background(), listener.ID(), \"echo\")\n\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\tif shouldRetry(err) {\n\t\t\t\t\t\t\t\t\ttime.Sleep(50 * time.Millisecond)\n\t\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tt.Logf(\"opening stream failed: %v\", err)\n\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\terr = func(s network.Stream) error {\n\t\t\t\t\t\t\t\tdefer s.Close()\n\t\t\t\t\t\t\t\terr = s.SetDeadline(time.Now().Add(100 * time.Millisecond))\n\t\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t_, err = s.Write([]byte(\"hello\"))\n\t\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\terr = s.CloseWrite()\n\t\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tb, err := io.ReadAll(s)\n\t\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif !bytes.Equal(b, []byte(\"hello\")) {\n\t\t\t\t\t\t\t\t\treturn errors.New(\"received data does not match sent data\")\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\thandledStreams.Add(1)\n\n\t\t\t\t\t\t\t\treturn nil\n\t\t\t\t\t\t\t}(s)\n\t\t\t\t\t\t\tif err != nil && shouldRetry(err) {\n\t\t\t\t\t\t\t\ttime.Sleep(50 * time.Millisecond)\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t}()\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Create any initial parallel workers\n\t\t\tfor i := 1; i < workerCount; i++ {\n\t\t\t\tgo startWorker(i)\n\t\t\t}\n\n\t\t\t// Start the first worker\n\t\t\tstartWorker(0)\n\n\t\t\twg.Wait()\n\t\t\tclose(errCh)\n\n\t\t\trequire.NoError(t, <-errCh)\n\t\t\trequire.Equal(t, streamCount, int(handledStreams.Load()))\n\t\t\trequire.True(t, sawFirstErr.Load(), \"Expected to see an error from the peer\")\n\t\t})\n\t}\n}\n\nfunc TestListenerStreamResets(t *testing.T) {\n\tfor _, tc := range transportsToTest {\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\th1 := tc.HostGenerator(t, TransportTestCaseOpts{})\n\t\t\th2 := tc.HostGenerator(t, TransportTestCaseOpts{NoListen: true})\n\t\t\tdefer h1.Close()\n\t\t\tdefer h2.Close()\n\n\t\t\trequire.NoError(t, h2.Connect(context.Background(), peer.AddrInfo{\n\t\t\t\tID:    h1.ID(),\n\t\t\t\tAddrs: h1.Addrs(),\n\t\t\t}))\n\n\t\t\th1.SetStreamHandler(\"reset\", func(s network.Stream) {\n\t\t\t\ts.Reset()\n\t\t\t})\n\n\t\t\ts, err := h2.NewStream(context.Background(), h1.ID(), \"reset\")\n\t\t\tif err != nil {\n\t\t\t\trequire.ErrorIs(t, err, network.ErrReset)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t_, err = s.Read([]byte{0})\n\t\t\trequire.ErrorIs(t, err, network.ErrReset)\n\t\t})\n\t}\n}\n\nfunc TestDialerStreamResets(t *testing.T) {\n\tfor _, tc := range transportsToTest {\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\th1 := tc.HostGenerator(t, TransportTestCaseOpts{})\n\t\t\th2 := tc.HostGenerator(t, TransportTestCaseOpts{NoListen: true})\n\t\t\tdefer h1.Close()\n\t\t\tdefer h2.Close()\n\n\t\t\trequire.NoError(t, h2.Connect(context.Background(), peer.AddrInfo{\n\t\t\t\tID:    h1.ID(),\n\t\t\t\tAddrs: h1.Addrs(),\n\t\t\t}))\n\n\t\t\terrCh := make(chan error, 1)\n\t\t\tacceptedCh := make(chan struct{}, 1)\n\t\t\th1.SetStreamHandler(\"echo\", func(s network.Stream) {\n\t\t\t\tacceptedCh <- struct{}{}\n\t\t\t\t_, err := io.Copy(s, s)\n\t\t\t\terrCh <- err\n\t\t\t})\n\n\t\t\ts, err := h2.NewStream(context.Background(), h1.ID(), \"echo\")\n\t\t\trequire.NoError(t, err)\n\t\t\ts.Write([]byte{})\n\t\t\t<-acceptedCh\n\t\t\ts.Reset()\n\t\t\trequire.ErrorIs(t, <-errCh, network.ErrReset)\n\t\t})\n\t}\n}\n\nfunc TestStreamReadDeadline(t *testing.T) {\n\tfor _, tc := range transportsToTest {\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\th1 := tc.HostGenerator(t, TransportTestCaseOpts{})\n\t\t\th2 := tc.HostGenerator(t, TransportTestCaseOpts{NoListen: true})\n\t\t\tdefer h1.Close()\n\t\t\tdefer h2.Close()\n\n\t\t\trequire.NoError(t, h2.Connect(context.Background(), peer.AddrInfo{\n\t\t\t\tID:    h1.ID(),\n\t\t\t\tAddrs: h1.Addrs(),\n\t\t\t}))\n\n\t\t\th1.SetStreamHandler(\"echo\", func(s network.Stream) {\n\t\t\t\tio.Copy(s, s)\n\t\t\t})\n\n\t\t\ts, err := h2.NewStream(context.Background(), h1.ID(), \"echo\")\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NoError(t, s.SetReadDeadline(time.Now().Add(100*time.Millisecond)))\n\t\t\t_, err = s.Read([]byte{0})\n\t\t\trequire.Error(t, err)\n\t\t\trequire.Contains(t, err.Error(), \"deadline\")\n\t\t\tvar nerr net.Error\n\t\t\trequire.ErrorAs(t, err, &nerr, \"expected a net.Error\")\n\t\t\trequire.True(t, nerr.Timeout(), \"expected net.Error.Timeout() == true\")\n\t\t\t// now test that the stream is still usable\n\t\t\ts.SetReadDeadline(time.Time{})\n\t\t\t_, err = s.Write([]byte(\"foobar\"))\n\t\t\trequire.NoError(t, err)\n\t\t\tb := make([]byte, 6)\n\t\t\t_, err = s.Read(b)\n\t\t\trequire.Equal(t, \"foobar\", string(b))\n\t\t\trequire.NoError(t, err)\n\t\t})\n\t}\n}\n\nfunc TestDiscoverPeerIDFromSecurityNegotiation(t *testing.T) {\n\t// extracts the peerID of the dialed peer from the error\n\textractPeerIDFromError := func(inputErr error) (peer.ID, error) {\n\t\tvar dialErr *swarm.DialError\n\t\tif !errors.As(inputErr, &dialErr) {\n\t\t\treturn \"\", inputErr\n\t\t}\n\t\tinnerErr := dialErr.DialErrors[0].Cause\n\n\t\tvar peerIDMismatchErr sec.ErrPeerIDMismatch\n\t\tif errors.As(innerErr, &peerIDMismatchErr) {\n\t\t\treturn peerIDMismatchErr.Actual, nil\n\t\t}\n\n\t\treturn \"\", inputErr\n\t}\n\n\tfor _, tc := range transportsToTest {\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\th1 := tc.HostGenerator(t, TransportTestCaseOpts{})\n\t\t\th2 := tc.HostGenerator(t, TransportTestCaseOpts{NoListen: true})\n\t\t\tdefer h1.Close()\n\t\t\tdefer h2.Close()\n\n\t\t\t// runs a test to verify we can extract the peer ID from a target with just its address\n\t\t\tt.Helper()\n\t\t\tctx := t.Context()\n\n\t\t\t// Use a bogus peer ID so that when we connect to the target we get an error telling\n\t\t\t// us the targets real peer ID\n\t\t\tbogusPeerId, err := peer.Decode(\"QmadAdJ3f63JyNs65X7HHzqDwV53ynvCcKtNFvdNaz3nhk\")\n\t\t\trequire.NoError(t, err, \"the hard coded bogus peerID is invalid\")\n\n\t\t\tai := &peer.AddrInfo{\n\t\t\t\tID:    bogusPeerId,\n\t\t\t\tAddrs: []ma.Multiaddr{h1.Addrs()[0]},\n\t\t\t}\n\n\t\t\t// Try connecting with the bogus peer ID\n\t\t\terr = h2.Connect(ctx, *ai)\n\t\t\trequire.Error(t, err, \"somehow we successfully connected to a bogus peerID!\")\n\n\t\t\t// Extract the actual peer ID from the error\n\t\t\tnewPeerId, err := extractPeerIDFromError(err)\n\t\t\trequire.NoError(t, err)\n\t\t\tai.ID = newPeerId\n\t\t\t// Make sure the new ID is what we expected\n\t\t\trequire.Equal(t, h1.ID(), ai.ID)\n\n\t\t\t// and just to double-check try connecting again to make sure it works\n\t\t\trequire.NoError(t, h2.Connect(ctx, *ai))\n\t\t})\n\t}\n}\n\n// TestCloseConnWhenBlocked tests that the server closes the connection when the rcmgr blocks it.\nfunc TestCloseConnWhenBlocked(t *testing.T) {\n\tfor _, tc := range transportsToTest {\n\t\t// WebRTC doesn't have a connection when rcmgr blocks it, so there's nothing to close.\n\t\tif tc.Name == \"WebRTC\" {\n\t\t\tcontinue\n\t\t}\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\tctrl := gomock.NewController(t)\n\t\t\tdefer ctrl.Finish()\n\t\t\tmockRcmgr := mocknetwork.NewMockResourceManager(ctrl)\n\t\t\tif matched, _ := regexp.MatchString(`^(QUIC|WebTransport)`, tc.Name); matched {\n\t\t\t\tmockRcmgr.EXPECT().VerifySourceAddress(gomock.Any()).AnyTimes().Return(false)\n\t\t\t\t// If the initial TLS ClientHello is split into two quic-go might call the transport multiple times to open a\n\t\t\t\t// connection. This will only be called multiple times if the connection is rejected. If were were to accept\n\t\t\t\t// the connection, this would have been called only once.\n\t\t\t\tmockRcmgr.EXPECT().OpenConnection(network.DirInbound, gomock.Any(), gomock.Any()).Return(nil, errors.New(\"connection blocked\")).AnyTimes()\n\t\t\t} else {\n\t\t\t\tmockRcmgr.EXPECT().OpenConnection(network.DirInbound, gomock.Any(), gomock.Any()).Return(nil, errors.New(\"connection blocked\"))\n\t\t\t}\n\t\t\tmockRcmgr.EXPECT().Close().AnyTimes()\n\n\t\t\tserver := tc.HostGenerator(t, TransportTestCaseOpts{ResourceManager: mockRcmgr})\n\t\t\tclient := tc.HostGenerator(t, TransportTestCaseOpts{NoListen: true})\n\t\t\tdefer server.Close()\n\t\t\tdefer client.Close()\n\n\t\t\tclient.Peerstore().AddAddrs(server.ID(), server.Addrs(), peerstore.PermanentAddrTTL)\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\t\t\tdefer cancel()\n\t\t\t_, err := client.NewStream(ctx, server.ID(), ping.ID)\n\t\t\trequire.Error(t, err)\n\t\t\trequire.False(t, errors.Is(err, context.DeadlineExceeded), \"expected error to be not be context deadline exceeded\")\n\t\t})\n\t}\n}\n\n// TestConnDroppedWhenBlocked is similar to TestCloseConnWhenBlocked, but for\n// transports like WebRTC we don't have a connection when we block it.  Instead\n// we just ignore the connection attempt. This tests that the client hits the\n// connection attempt deadline and neither server nor client see a successful\n// connection attempt\nfunc TestConnDroppedWhenBlocked(t *testing.T) {\n\tfor _, tc := range transportsToTest {\n\t\tif tc.Name != \"WebRTC\" {\n\t\t\tcontinue\n\t\t}\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\tctrl := gomock.NewController(t)\n\t\t\tdefer ctrl.Finish()\n\t\t\tmockRcmgr := mocknetwork.NewMockResourceManager(ctrl)\n\t\t\tmockRcmgr.EXPECT().OpenConnection(network.DirInbound, gomock.Any(), gomock.Any()).AnyTimes().DoAndReturn(func(network.Direction, bool, ma.Multiaddr) (network.ConnManagementScope, error) {\n\t\t\t\t// Block the connection\n\t\t\t\treturn nil, fmt.Errorf(\"connections blocked\")\n\t\t\t})\n\t\t\tmockRcmgr.EXPECT().Close().AnyTimes()\n\n\t\t\tserver := tc.HostGenerator(t, TransportTestCaseOpts{ResourceManager: mockRcmgr})\n\t\t\tclient := tc.HostGenerator(t, TransportTestCaseOpts{NoListen: true})\n\t\t\tdefer server.Close()\n\t\t\tdefer client.Close()\n\n\t\t\tserverSub, err := server.EventBus().Subscribe(new(event.EvtPeerConnectednessChanged))\n\t\t\trequire.NoError(t, err)\n\t\t\tclientSub, err := client.EventBus().Subscribe(new(event.EvtPeerConnectednessChanged))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tclient.Peerstore().AddAddrs(server.ID(), server.Addrs(), peerstore.PermanentAddrTTL)\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)\n\t\t\tdefer cancel()\n\t\t\t_, err = client.NewStream(ctx, server.ID(), ping.ID)\n\t\t\trequire.Error(t, err)\n\t\t\trequire.True(t, errors.Is(err, context.DeadlineExceeded), \"The client should have hit the deadline when connecting\")\n\t\t\tselect {\n\t\t\tcase <-serverSub.Out():\n\t\t\t\tt.Fatal(\"expected no connected event. Connection should have failed\")\n\t\t\tcase <-clientSub.Out():\n\t\t\t\tt.Fatal(\"expected no connected event. Connection should have failed\")\n\t\t\tcase <-time.After(time.Second):\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestConnClosedWhenRemoteCloses tests that a connection is closed locally when it's closed by remote\nfunc TestConnClosedWhenRemoteCloses(t *testing.T) {\n\tfor _, tc := range transportsToTest {\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\tserver := tc.HostGenerator(t, TransportTestCaseOpts{})\n\t\t\tclient := tc.HostGenerator(t, TransportTestCaseOpts{NoListen: true})\n\t\t\tdefer server.Close()\n\t\t\tdefer client.Close()\n\n\t\t\tclient.Peerstore().AddAddrs(server.ID(), server.Addrs(), peerstore.PermanentAddrTTL)\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\t\t\tdefer cancel()\n\t\t\terr := client.Connect(ctx, peer.AddrInfo{ID: server.ID(), Addrs: server.Addrs()})\n\t\t\trequire.NoError(t, err)\n\n\t\t\trequire.Eventually(t, func() bool {\n\t\t\t\treturn server.Network().Connectedness(client.ID()) != network.NotConnected\n\t\t\t}, 5*time.Second, 50*time.Millisecond)\n\t\t\tfor _, c := range client.Network().ConnsToPeer(server.ID()) {\n\t\t\t\tc.Close()\n\t\t\t}\n\t\t\trequire.Eventually(t, func() bool {\n\t\t\t\treturn server.Network().Connectedness(client.ID()) == network.NotConnected\n\t\t\t}, 5*time.Second, 50*time.Millisecond)\n\t\t})\n\t}\n}\n\nfunc TestErrorCodes(t *testing.T) {\n\tassertStreamErrors := func(s network.Stream, expectedError error) {\n\t\tbuf := make([]byte, 10)\n\t\t_, err := s.Read(buf)\n\t\trequire.ErrorIs(t, err, expectedError)\n\n\t\t_, err = s.Write(buf)\n\t\trequire.ErrorIs(t, err, expectedError)\n\t}\n\n\tfor _, tc := range transportsToTest {\n\t\tif strings.HasPrefix(tc.Name, \"WebTransport\") {\n\t\t\tt.Skipf(\"skipping: %s, not implemented\", tc.Name)\n\t\t\tcontinue\n\t\t}\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\tserver := tc.HostGenerator(t, TransportTestCaseOpts{})\n\t\t\tclient := tc.HostGenerator(t, TransportTestCaseOpts{NoListen: true})\n\t\t\tdefer server.Close()\n\t\t\tdefer client.Close()\n\n\t\t\tclient.Peerstore().AddAddrs(server.ID(), server.Addrs(), peerstore.PermanentAddrTTL)\n\n\t\t\t// setup stream handler\n\t\t\tremoteStreamQ := make(chan network.Stream)\n\t\t\tserver.SetStreamHandler(\"/test\", func(s network.Stream) {\n\t\t\t\tb := make([]byte, 10)\n\t\t\t\tn, err := s.Read(b)\n\t\t\t\tif !assert.NoError(t, err) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t_, err = s.Write(b[:n])\n\t\t\t\tif !assert.NoError(t, err) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tremoteStreamQ <- s\n\t\t\t})\n\n\t\t\t// pingPong writes and reads \"hello\" on the stream\n\t\t\tpingPong := func(s network.Stream) {\n\t\t\t\tbuf := []byte(\"hello\")\n\t\t\t\t_, err := s.Write(buf)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\t_, err = s.Read(buf)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, buf, []byte(\"hello\"))\n\t\t\t}\n\n\t\t\tt.Run(\"StreamResetWithError\", func(t *testing.T) {\n\t\t\t\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\t\t\t\tdefer cancel()\n\t\t\t\ts, err := client.NewStream(ctx, server.ID(), \"/test\")\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tpingPong(s)\n\n\t\t\t\tremoteStream := <-remoteStreamQ\n\t\t\t\tdefer remoteStream.Reset()\n\n\t\t\t\terr = s.ResetWithError(42)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tassertStreamErrors(s, &network.StreamError{\n\t\t\t\t\tErrorCode: 42,\n\t\t\t\t\tRemote:    false,\n\t\t\t\t})\n\n\t\t\t\tassertStreamErrors(remoteStream, &network.StreamError{\n\t\t\t\t\tErrorCode: 42,\n\t\t\t\t\tRemote:    true,\n\t\t\t\t})\n\t\t\t})\n\t\t\tt.Run(\"StreamResetWithErrorByRemote\", func(t *testing.T) {\n\t\t\t\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\t\t\t\tdefer cancel()\n\t\t\t\ts, err := client.NewStream(ctx, server.ID(), \"/test\")\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tpingPong(s)\n\n\t\t\t\tremoteStream := <-remoteStreamQ\n\n\t\t\t\terr = remoteStream.ResetWithError(42)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tassertStreamErrors(s, &network.StreamError{\n\t\t\t\t\tErrorCode: 42,\n\t\t\t\t\tRemote:    true,\n\t\t\t\t})\n\n\t\t\t\tassertStreamErrors(remoteStream, &network.StreamError{\n\t\t\t\t\tErrorCode: 42,\n\t\t\t\t\tRemote:    false,\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tt.Run(\"StreamResetByConnCloseWithError\", func(t *testing.T) {\n\t\t\t\tif tc.Name == \"WebRTC\" {\n\t\t\t\t\tt.Skipf(\"skipping: %s, not implemented\", tc.Name)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\t\t\t\tdefer cancel()\n\t\t\t\ts, err := client.NewStream(ctx, server.ID(), \"/test\")\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tpingPong(s)\n\n\t\t\t\tremoteStream := <-remoteStreamQ\n\t\t\t\tdefer remoteStream.Reset()\n\n\t\t\t\terr = s.Conn().CloseWithError(42)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tassertStreamErrors(s, &network.ConnError{\n\t\t\t\t\tErrorCode: 42,\n\t\t\t\t\tRemote:    false,\n\t\t\t\t})\n\n\t\t\t\tassertStreamErrors(remoteStream, &network.ConnError{\n\t\t\t\t\tErrorCode: 42,\n\t\t\t\t\tRemote:    true,\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tt.Run(\"NewStreamErrorByConnCloseWithError\", func(t *testing.T) {\n\t\t\t\tif tc.Name == \"WebRTC\" {\n\t\t\t\t\tt.Skipf(\"skipping: %s, not implemented\", tc.Name)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\t\t\t\tdefer cancel()\n\t\t\t\ts, err := client.NewStream(ctx, server.ID(), \"/test\")\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tpingPong(s)\n\n\t\t\t\terr = s.Conn().CloseWithError(42)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tremoteStream := <-remoteStreamQ\n\t\t\t\tdefer remoteStream.Reset()\n\n\t\t\t\tlocalErr := &network.ConnError{\n\t\t\t\t\tErrorCode: 42,\n\t\t\t\t\tRemote:    false,\n\t\t\t\t}\n\n\t\t\t\tremoteErr := &network.ConnError{\n\t\t\t\t\tErrorCode: 42,\n\t\t\t\t\tRemote:    true,\n\t\t\t\t}\n\n\t\t\t\t// assert these first to ensure that remote has closed the connection\n\t\t\t\tassertStreamErrors(remoteStream, remoteErr)\n\n\t\t\t\t_, err = s.Conn().NewStream(ctx)\n\t\t\t\trequire.ErrorIs(t, err, localErr)\n\n\t\t\t\t_, err = remoteStream.Conn().NewStream(ctx)\n\t\t\t\trequire.ErrorIs(t, err, remoteErr)\n\t\t\t})\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "p2p/test/webtransport/webtransport_test.go",
    "content": "package webtransport_test\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/benbjohnson/clock\"\n\t\"github.com/libp2p/go-libp2p\"\n\tic \"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/test\"\n\tlibp2pwebtransport \"github.com/libp2p/go-libp2p/p2p/transport/webtransport\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc extractCertHashes(addr ma.Multiaddr) []string {\n\tvar certHashesStr []string\n\tma.ForEach(addr, func(c ma.Component) bool {\n\t\tif c.Protocol().Code == ma.P_CERTHASH {\n\t\t\tcertHashesStr = append(certHashesStr, c.Value())\n\t\t}\n\t\treturn true\n\t})\n\treturn certHashesStr\n}\n\nfunc TestDeterministicCertsAfterReboot(t *testing.T) {\n\tpriv, _, err := test.RandTestKeyPair(ic.Ed25519, 256)\n\trequire.NoError(t, err)\n\n\tcl := clock.NewMock()\n\t// Move one year ahead to avoid edge cases around epoch\n\tcl.Add(time.Hour * 24 * 365)\n\th, err := libp2p.New(libp2p.NoTransports, libp2p.Transport(libp2pwebtransport.New, libp2pwebtransport.WithClock(cl)), libp2p.Identity(priv))\n\trequire.NoError(t, err)\n\terr = h.Network().Listen(ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1/webtransport\"))\n\trequire.NoError(t, err)\n\n\tprevCerthashes := extractCertHashes(h.Addrs()[0])\n\th.Close()\n\n\th, err = libp2p.New(libp2p.NoTransports, libp2p.Transport(libp2pwebtransport.New, libp2pwebtransport.WithClock(cl)), libp2p.Identity(priv))\n\trequire.NoError(t, err)\n\tdefer h.Close()\n\terr = h.Network().Listen(ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1/webtransport\"))\n\trequire.NoError(t, err)\n\n\tnextCertHashes := extractCertHashes(h.Addrs()[0])\n\n\tfor i := range prevCerthashes {\n\t\trequire.Equal(t, prevCerthashes[i], nextCertHashes[i])\n\t}\n}\n"
  },
  {
    "path": "p2p/transport/quic/cmd/client/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\n\tcmdlib \"github.com/libp2p/go-libp2p/p2p/transport/quic/cmd/lib\"\n)\n\nfunc main() {\n\tif len(os.Args) != 3 {\n\t\tfmt.Printf(\"Usage: %s <multiaddr> <peer id>\", os.Args[0])\n\t\treturn\n\t}\n\tif err := cmdlib.RunClient(os.Args[1], os.Args[2]); err != nil {\n\t\tlog.Fatal(err.Error())\n\t}\n}\n"
  },
  {
    "path": "p2p/transport/quic/cmd/lib/lib.go",
    "content": "package cmdlib\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\n\tic \"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\ttpt \"github.com/libp2p/go-libp2p/core/transport\"\n\tlibp2pquic \"github.com/libp2p/go-libp2p/p2p/transport/quic\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/quicreuse\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/quic-go/quic-go\"\n)\n\nfunc RunClient(raddr string, p string) error {\n\tpeerID, err := peer.Decode(p)\n\tif err != nil {\n\t\treturn err\n\t}\n\taddr, err := ma.NewMultiaddr(raddr)\n\tif err != nil {\n\t\treturn err\n\t}\n\tpriv, _, err := ic.GenerateECDSAKeyPair(rand.Reader)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treuse, err := quicreuse.NewConnManager(quic.StatelessResetKey{}, quic.TokenGeneratorKey{})\n\tif err != nil {\n\t\treturn err\n\t}\n\tt, err := libp2pquic.NewTransport(priv, reuse, nil, nil, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlog.Printf(\"Dialing %s\\n\", addr.String())\n\tconn, err := t.Dial(context.Background(), addr, peerID)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer conn.Close()\n\tstr, err := conn.OpenStream(context.Background())\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer str.Close()\n\tconst msg = \"Hello world!\"\n\tlog.Printf(\"Sending: %s\\n\", msg)\n\tif _, err := str.Write([]byte(msg)); err != nil {\n\t\treturn err\n\t}\n\tif err := str.CloseWrite(); err != nil {\n\t\treturn err\n\t}\n\tdata, err := io.ReadAll(str)\n\tif err != nil {\n\t\treturn err\n\t}\n\tlog.Printf(\"Received: %s\\n\", data)\n\treturn nil\n}\n\nfunc RunServer(port string, location chan peer.AddrInfo) error {\n\taddr, err := ma.NewMultiaddr(fmt.Sprintf(\"/ip4/0.0.0.0/udp/%s/quic-v1\", port))\n\tif err != nil {\n\t\treturn err\n\t}\n\tpriv, _, err := ic.GenerateECDSAKeyPair(rand.Reader)\n\tif err != nil {\n\t\treturn err\n\t}\n\tpeerID, err := peer.IDFromPrivateKey(priv)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treuse, err := quicreuse.NewConnManager(quic.StatelessResetKey{}, quic.TokenGeneratorKey{})\n\tif err != nil {\n\t\treturn err\n\t}\n\tt, err := libp2pquic.NewTransport(priv, reuse, nil, nil, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tln, err := t.Listen(addr)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfmt.Printf(\"Listening. Now run: go run cmd/client/main.go %s %s\\n\", ln.Multiaddr(), peerID)\n\tif location != nil {\n\t\tlocation <- peer.AddrInfo{ID: peerID, Addrs: []ma.Multiaddr{ln.Multiaddr()}}\n\t}\n\tfor {\n\t\tconn, err := ln.Accept()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tlog.Printf(\"Accepted new connection from %s (%s)\\n\", conn.RemotePeer(), conn.RemoteMultiaddr())\n\t\tgo func() {\n\t\t\tif err := handleConn(conn); err != nil {\n\t\t\t\tlog.Printf(\"handling conn failed: %s\", err.Error())\n\t\t\t}\n\t\t}()\n\t}\n}\n\nfunc handleConn(conn tpt.CapableConn) error {\n\tstr, err := conn.AcceptStream()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdata, err := io.ReadAll(str)\n\tif err != nil {\n\t\treturn err\n\t}\n\tlog.Printf(\"Received: %s\\n\", data)\n\tif _, err := str.Write(data); err != nil {\n\t\treturn err\n\t}\n\treturn str.Close()\n}\n"
  },
  {
    "path": "p2p/transport/quic/cmd/lib/lib_test.go",
    "content": "package cmdlib\n\nimport (\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/multiformats/go-multiaddr\"\n)\n\nfunc TestCmd(t *testing.T) {\n\tserverLocation := make(chan peer.AddrInfo)\n\tgo RunServer(\"0\", serverLocation)\n\n\tl := <-serverLocation\n\n\tip, rest := multiaddr.SplitFirst(l.Addrs[0])\n\tif ip.Protocol().Code == multiaddr.P_IP4 && ip.Value() == \"0.0.0.0\" {\n\t\t// Windows can't dial to 0.0.0.0 so replace with localhost\n\t\tvar err error\n\t\tc, err := multiaddr.NewComponent(\"ip4\", \"127.0.0.1\")\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tip = c\n\t}\n\n\terr := RunClient(ip.Encapsulate(rest).String(), l.ID.String())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n"
  },
  {
    "path": "p2p/transport/quic/cmd/server/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\n\tcmdlib \"github.com/libp2p/go-libp2p/p2p/transport/quic/cmd/lib\"\n)\n\nfunc main() {\n\tif len(os.Args) != 2 {\n\t\tfmt.Printf(\"Usage: %s <port>\", os.Args[0])\n\t\treturn\n\t}\n\tif err := cmdlib.RunServer(os.Args[1], nil); err != nil {\n\t\tlog.Fatal(err.Error())\n\t}\n}\n"
  },
  {
    "path": "p2p/transport/quic/conn.go",
    "content": "package libp2pquic\n\nimport (\n\t\"context\"\n\n\tic \"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\ttpt \"github.com/libp2p/go-libp2p/core/transport\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/quic-go/quic-go\"\n)\n\ntype conn struct {\n\tquicConn  *quic.Conn\n\ttransport *transport\n\tscope     network.ConnManagementScope\n\n\tlocalPeer      peer.ID\n\tlocalMultiaddr ma.Multiaddr\n\n\tremotePeerID    peer.ID\n\tremotePubKey    ic.PubKey\n\tremoteMultiaddr ma.Multiaddr\n}\n\nfunc (c *conn) As(target any) bool {\n\tif t, ok := target.(**quic.Conn); ok {\n\t\t*t = c.quicConn\n\t\treturn true\n\t}\n\n\treturn false\n}\n\nvar _ tpt.CapableConn = &conn{}\n\n// Close closes the connection.\n// It must be called even if the peer closed the connection in order for\n// garbage collection to properly work in this package.\nfunc (c *conn) Close() error {\n\treturn c.closeWithError(0, \"\")\n}\n\n// CloseWithError closes the connection\n// It must be called even if the peer closed the connection in order for\n// garbage collection to properly work in this package.\nfunc (c *conn) CloseWithError(errCode network.ConnErrorCode) error {\n\treturn c.closeWithError(quic.ApplicationErrorCode(errCode), \"\")\n}\n\nfunc (c *conn) closeWithError(errCode quic.ApplicationErrorCode, errString string) error {\n\tc.transport.removeConn(c.quicConn)\n\terr := c.quicConn.CloseWithError(errCode, errString)\n\tc.scope.Done()\n\treturn err\n}\n\n// IsClosed returns whether a connection is fully closed.\nfunc (c *conn) IsClosed() bool {\n\treturn c.quicConn.Context().Err() != nil\n}\n\nfunc (c *conn) allowWindowIncrease(size uint64) bool {\n\treturn c.scope.ReserveMemory(int(size), network.ReservationPriorityMedium) == nil\n}\n\n// OpenStream creates a new stream.\nfunc (c *conn) OpenStream(ctx context.Context) (network.MuxedStream, error) {\n\tqstr, err := c.quicConn.OpenStreamSync(ctx)\n\tif err != nil {\n\t\treturn nil, parseStreamError(err)\n\t}\n\treturn &stream{Stream: qstr}, nil\n}\n\n// AcceptStream accepts a stream opened by the other side.\nfunc (c *conn) AcceptStream() (network.MuxedStream, error) {\n\tqstr, err := c.quicConn.AcceptStream(context.Background())\n\tif err != nil {\n\t\treturn nil, parseStreamError(err)\n\t}\n\treturn &stream{Stream: qstr}, nil\n}\n\n// LocalPeer returns our peer ID\nfunc (c *conn) LocalPeer() peer.ID { return c.localPeer }\n\n// RemotePeer returns the peer ID of the remote peer.\nfunc (c *conn) RemotePeer() peer.ID { return c.remotePeerID }\n\n// RemotePublicKey returns the public key of the remote peer.\nfunc (c *conn) RemotePublicKey() ic.PubKey { return c.remotePubKey }\n\n// LocalMultiaddr returns the local Multiaddr associated\nfunc (c *conn) LocalMultiaddr() ma.Multiaddr { return c.localMultiaddr }\n\n// RemoteMultiaddr returns the remote Multiaddr associated\nfunc (c *conn) RemoteMultiaddr() ma.Multiaddr { return c.remoteMultiaddr }\n\nfunc (c *conn) Transport() tpt.Transport { return c.transport }\n\nfunc (c *conn) Scope() network.ConnScope { return c.scope }\n\n// ConnState is the state of security connection.\nfunc (c *conn) ConnState() network.ConnectionState {\n\tt := \"quic-v1\"\n\tif _, err := c.LocalMultiaddr().ValueForProtocol(ma.P_QUIC); err == nil {\n\t\tt = \"quic\"\n\t}\n\treturn network.ConnectionState{Transport: t}\n}\n"
  },
  {
    "path": "p2p/transport/quic/conn_test.go",
    "content": "package libp2pquic\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/rand\"\n\t\"errors\"\n\t\"io\"\n\tmrand \"math/rand\"\n\t\"net\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\tic \"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\tmocknetwork \"github.com/libp2p/go-libp2p/core/network/mocks\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\ttpt \"github.com/libp2p/go-libp2p/core/transport\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/quicreuse\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/quic-go/quic-go\"\n\tquicproxy \"github.com/quic-go/quic-go/integrationtests/tools/proxy\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/mock/gomock\"\n)\n\n//go:generate sh -c \"go run go.uber.org/mock/mockgen -package libp2pquic -destination mock_connection_gater_test.go github.com/libp2p/go-libp2p/core/connmgr ConnectionGater && go run golang.org/x/tools/cmd/goimports -w mock_connection_gater_test.go\"\n\ntype connTestCase struct {\n\tName    string\n\tOptions []quicreuse.Option\n}\n\nvar connTestCases = []*connTestCase{\n\t{\"reuseport_on\", []quicreuse.Option{}},\n\t{\"reuseport_off\", []quicreuse.Option{quicreuse.DisableReuseport()}},\n}\n\nfunc createPeer(t *testing.T) (peer.ID, ic.PrivKey) {\n\tvar priv ic.PrivKey\n\tvar err error\n\tswitch mrand.Int() % 4 {\n\tcase 0:\n\t\tpriv, _, err = ic.GenerateECDSAKeyPair(rand.Reader)\n\tcase 1:\n\t\tpriv, _, err = ic.GenerateRSAKeyPair(2048, rand.Reader)\n\tcase 2:\n\t\tpriv, _, err = ic.GenerateEd25519Key(rand.Reader)\n\tcase 3:\n\t\tpriv, _, err = ic.GenerateSecp256k1Key(rand.Reader)\n\t}\n\trequire.NoError(t, err)\n\tid, err := peer.IDFromPrivateKey(priv)\n\trequire.NoError(t, err)\n\tt.Logf(\"using a %s key: %s\", priv.Type(), id)\n\treturn id, priv\n}\n\nfunc runServer(t *testing.T, tr tpt.Transport, addr string) tpt.Listener {\n\tt.Helper()\n\n\tln, err := tr.Listen(ma.StringCast(addr))\n\trequire.NoError(t, err)\n\treturn ln\n}\n\nfunc newConnManager(t *testing.T, opts ...quicreuse.Option) *quicreuse.ConnManager {\n\tt.Helper()\n\tcm, err := quicreuse.NewConnManager(quic.StatelessResetKey{}, quic.TokenGeneratorKey{}, opts...)\n\trequire.NoError(t, err)\n\tt.Cleanup(func() { cm.Close() })\n\treturn cm\n}\n\nfunc TestHandshake(t *testing.T) {\n\tfor _, tc := range connTestCases {\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\ttestHandshake(t, tc)\n\t\t})\n\t}\n}\n\nfunc testHandshake(t *testing.T, tc *connTestCase) {\n\tserverID, serverKey := createPeer(t)\n\tclientID, clientKey := createPeer(t)\n\n\tserverTransport, err := NewTransport(serverKey, newConnManager(t, tc.Options...), nil, nil, nil)\n\trequire.NoError(t, err)\n\tdefer serverTransport.(io.Closer).Close()\n\n\thandshake := func(t *testing.T, ln tpt.Listener) {\n\t\tclientTransport, err := NewTransport(clientKey, newConnManager(t, tc.Options...), nil, nil, nil)\n\t\trequire.NoError(t, err)\n\t\tdefer clientTransport.(io.Closer).Close()\n\t\tconn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID)\n\t\trequire.NoError(t, err)\n\t\tdefer conn.Close()\n\t\tserverConn, err := ln.Accept()\n\t\trequire.NoError(t, err)\n\t\tdefer serverConn.Close()\n\n\t\trequire.Equal(t, conn.LocalPeer(), clientID)\n\t\trequire.Equal(t, conn.RemotePeer(), serverID)\n\t\trequire.True(t, conn.RemotePublicKey().Equals(serverKey.GetPublic()), \"remote public key doesn't match\")\n\n\t\trequire.Equal(t, serverConn.LocalPeer(), serverID)\n\t\trequire.Equal(t, serverConn.RemotePeer(), clientID)\n\t\trequire.True(t, serverConn.RemotePublicKey().Equals(clientKey.GetPublic()), \"remote public key doesn't match\")\n\t}\n\n\tt.Run(\"on IPv4\", func(t *testing.T) {\n\t\tln := runServer(t, serverTransport, \"/ip4/127.0.0.1/udp/0/quic-v1\")\n\t\tdefer ln.Close()\n\t\thandshake(t, ln)\n\t})\n\n\tt.Run(\"on IPv6\", func(t *testing.T) {\n\t\tln := runServer(t, serverTransport, \"/ip6/::1/udp/0/quic-v1\")\n\t\tdefer ln.Close()\n\t\thandshake(t, ln)\n\t})\n}\n\nfunc TestResourceManagerSuccess(t *testing.T) {\n\tfor _, tc := range connTestCases {\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\ttestResourceManagerSuccess(t, tc)\n\t\t})\n\t}\n}\n\nfunc testResourceManagerSuccess(t *testing.T, tc *connTestCase) {\n\tserverID, serverKey := createPeer(t)\n\tclientID, clientKey := createPeer(t)\n\n\tctrl := gomock.NewController(t)\n\tdefer ctrl.Finish()\n\n\tserverRcmgr := mocknetwork.NewMockResourceManager(ctrl)\n\tserverTransport, err := NewTransport(serverKey, newConnManager(t, tc.Options...), nil, nil, serverRcmgr)\n\trequire.NoError(t, err)\n\tdefer serverTransport.(io.Closer).Close()\n\tln, err := serverTransport.Listen(ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1\"))\n\trequire.NoError(t, err)\n\tdefer ln.Close()\n\n\tclientRcmgr := mocknetwork.NewMockResourceManager(ctrl)\n\tclientTransport, err := NewTransport(clientKey, newConnManager(t, tc.Options...), nil, nil, clientRcmgr)\n\trequire.NoError(t, err)\n\tdefer clientTransport.(io.Closer).Close()\n\n\tconnChan := make(chan tpt.CapableConn)\n\tserverConnScope := mocknetwork.NewMockConnManagementScope(ctrl)\n\tgo func() {\n\t\tserverRcmgr.EXPECT().OpenConnection(network.DirInbound, false, gomock.Not(ln.Multiaddr())).Return(serverConnScope, nil)\n\t\tserverConnScope.EXPECT().SetPeer(clientID)\n\t\tserverConn, err := ln.Accept()\n\t\trequire.NoError(t, err)\n\t\tconnChan <- serverConn\n\t}()\n\n\tconnScope := mocknetwork.NewMockConnManagementScope(ctrl)\n\tclientRcmgr.EXPECT().OpenConnection(network.DirOutbound, false, ln.Multiaddr()).Return(connScope, nil)\n\tconnScope.EXPECT().SetPeer(serverID)\n\tconn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID)\n\trequire.NoError(t, err)\n\tserverConn := <-connChan\n\tt.Log(\"received conn\")\n\tconnScope.EXPECT().Done().MinTimes(1) // for dialed connections, we might call Done multiple times\n\tconn.Close()\n\tserverConnScope.EXPECT().Done()\n\tserverConn.Close()\n}\n\nfunc TestResourceManagerDialDenied(t *testing.T) {\n\tfor _, tc := range connTestCases {\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\ttestResourceManagerDialDenied(t, tc)\n\t\t})\n\t}\n}\n\nfunc testResourceManagerDialDenied(t *testing.T, tc *connTestCase) {\n\t_, clientKey := createPeer(t)\n\tctrl := gomock.NewController(t)\n\tdefer ctrl.Finish()\n\n\trcmgr := mocknetwork.NewMockResourceManager(ctrl)\n\tclientTransport, err := NewTransport(clientKey, newConnManager(t, tc.Options...), nil, nil, rcmgr)\n\trequire.NoError(t, err)\n\tdefer clientTransport.(io.Closer).Close()\n\n\tconnScope := mocknetwork.NewMockConnManagementScope(ctrl)\n\ttarget := ma.StringCast(\"/ip4/127.0.0.1/udp/1234/quic-v1\")\n\n\trcmgr.EXPECT().OpenConnection(network.DirOutbound, false, target).Return(connScope, nil)\n\trerr := errors.New(\"nope\")\n\tp := peer.ID(\"server\")\n\tconnScope.EXPECT().SetPeer(p).Return(rerr)\n\tconnScope.EXPECT().Done()\n\n\t_, err = clientTransport.Dial(context.Background(), target, p)\n\trequire.ErrorIs(t, err, rerr)\n\n}\n\nfunc TestResourceManagerAcceptDenied(t *testing.T) {\n\tfor _, tc := range connTestCases {\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\ttestResourceManagerAcceptDenied(t, tc)\n\t\t})\n\t}\n}\n\nfunc testResourceManagerAcceptDenied(t *testing.T, tc *connTestCase) {\n\tserverID, serverKey := createPeer(t)\n\tclientID, clientKey := createPeer(t)\n\tctrl := gomock.NewController(t)\n\tdefer ctrl.Finish()\n\n\tclientRcmgr := mocknetwork.NewMockResourceManager(ctrl)\n\tclientTransport, err := NewTransport(clientKey, newConnManager(t, tc.Options...), nil, nil, clientRcmgr)\n\trequire.NoError(t, err)\n\tdefer clientTransport.(io.Closer).Close()\n\n\tserverRcmgr := mocknetwork.NewMockResourceManager(ctrl)\n\tserverConnScope := mocknetwork.NewMockConnManagementScope(ctrl)\n\trerr := errors.New(\"denied\")\n\tgomock.InOrder(\n\t\tserverRcmgr.EXPECT().OpenConnection(network.DirInbound, false, gomock.Any()).Return(serverConnScope, nil),\n\t\tserverConnScope.EXPECT().SetPeer(clientID).Return(rerr),\n\t\tserverConnScope.EXPECT().Done(),\n\t)\n\tserverTransport, err := NewTransport(serverKey, newConnManager(t, tc.Options...), nil, nil, serverRcmgr)\n\trequire.NoError(t, err)\n\tdefer serverTransport.(io.Closer).Close()\n\tln, err := serverTransport.Listen(ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1\"))\n\trequire.NoError(t, err)\n\tdefer ln.Close()\n\tconnChan := make(chan tpt.CapableConn)\n\tgo func() {\n\t\tln.Accept()\n\t\tclose(connChan)\n\t}()\n\n\tclientConnScope := mocknetwork.NewMockConnManagementScope(ctrl)\n\tclientRcmgr.EXPECT().OpenConnection(network.DirOutbound, false, ln.Multiaddr()).Return(clientConnScope, nil)\n\tclientConnScope.EXPECT().SetPeer(serverID)\n\t// In rare instances, the connection gating error will already occur on Dial.\n\t// In that case, Done is called on the connection scope.\n\tclientConnScope.EXPECT().Done().MaxTimes(1)\n\tconn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID)\n\t// In rare instances, the connection gating error will already occur on Dial.\n\tif err == nil {\n\t\t_, err = conn.AcceptStream()\n\t\trequire.Error(t, err)\n\t}\n\tselect {\n\tcase <-connChan:\n\t\tt.Fatal(\"didn't expect to accept a connection\")\n\tdefault:\n\t}\n}\n\nfunc TestStreams(t *testing.T) {\n\tfor _, tc := range connTestCases {\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\ttestStreams(t, tc)\n\t\t})\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\ttestStreamsErrorCode(t, tc)\n\t\t})\n\t}\n}\n\nfunc testStreams(t *testing.T, tc *connTestCase) {\n\tserverID, serverKey := createPeer(t)\n\t_, clientKey := createPeer(t)\n\n\tserverTransport, err := NewTransport(serverKey, newConnManager(t, tc.Options...), nil, nil, nil)\n\trequire.NoError(t, err)\n\tdefer serverTransport.(io.Closer).Close()\n\tln := runServer(t, serverTransport, \"/ip4/127.0.0.1/udp/0/quic-v1\")\n\tdefer ln.Close()\n\n\tclientTransport, err := NewTransport(clientKey, newConnManager(t, tc.Options...), nil, nil, nil)\n\trequire.NoError(t, err)\n\tdefer clientTransport.(io.Closer).Close()\n\tconn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID)\n\trequire.NoError(t, err)\n\tdefer conn.Close()\n\tserverConn, err := ln.Accept()\n\trequire.NoError(t, err)\n\tdefer serverConn.Close()\n\n\tstr, err := conn.OpenStream(context.Background())\n\trequire.NoError(t, err)\n\t_, err = str.Write([]byte(\"foobar\"))\n\trequire.NoError(t, err)\n\tstr.Close()\n\tsstr, err := serverConn.AcceptStream()\n\trequire.NoError(t, err)\n\tdata, err := io.ReadAll(sstr)\n\trequire.NoError(t, err)\n\trequire.Equal(t, data, []byte(\"foobar\"))\n}\n\nfunc testStreamsErrorCode(t *testing.T, tc *connTestCase) {\n\tserverID, serverKey := createPeer(t)\n\t_, clientKey := createPeer(t)\n\n\tserverTransport, err := NewTransport(serverKey, newConnManager(t, tc.Options...), nil, nil, nil)\n\trequire.NoError(t, err)\n\tdefer serverTransport.(io.Closer).Close()\n\tln := runServer(t, serverTransport, \"/ip4/127.0.0.1/udp/0/quic-v1\")\n\tdefer ln.Close()\n\n\tclientTransport, err := NewTransport(clientKey, newConnManager(t, tc.Options...), nil, nil, nil)\n\trequire.NoError(t, err)\n\tdefer clientTransport.(io.Closer).Close()\n\tconn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID)\n\trequire.NoError(t, err)\n\tdefer conn.Close()\n\tserverConn, err := ln.Accept()\n\trequire.NoError(t, err)\n\tdefer serverConn.Close()\n\n\tstr, err := conn.OpenStream(context.Background())\n\trequire.NoError(t, err)\n\terr = str.ResetWithError(42)\n\trequire.NoError(t, err)\n\n\tsstr, err := serverConn.AcceptStream()\n\trequire.NoError(t, err)\n\t_, err = io.ReadAll(sstr)\n\trequire.Error(t, err)\n\tse := &network.StreamError{}\n\tif errors.As(err, &se) {\n\t\trequire.Equal(t, se.ErrorCode, network.StreamErrorCode(42))\n\t\trequire.True(t, se.Remote)\n\t} else {\n\t\tt.Fatalf(\"expected error to be of network.StreamError type, got %T, %v\", err, err)\n\t}\n\n}\n\nfunc TestHandshakeFailPeerIDMismatch(t *testing.T) {\n\tfor _, tc := range connTestCases {\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\ttestHandshakeFailPeerIDMismatch(t, tc)\n\t\t})\n\t}\n}\n\nfunc testHandshakeFailPeerIDMismatch(t *testing.T, tc *connTestCase) {\n\t_, serverKey := createPeer(t)\n\t_, clientKey := createPeer(t)\n\tthirdPartyID, _ := createPeer(t)\n\n\tserverTransport, err := NewTransport(serverKey, newConnManager(t, tc.Options...), nil, nil, nil)\n\trequire.NoError(t, err)\n\tdefer serverTransport.(io.Closer).Close()\n\tln := runServer(t, serverTransport, \"/ip4/127.0.0.1/udp/0/quic-v1\")\n\n\tclientTransport, err := NewTransport(clientKey, newConnManager(t, tc.Options...), nil, nil, nil)\n\trequire.NoError(t, err)\n\t// dial, but expect the wrong peer ID\n\t_, err = clientTransport.Dial(context.Background(), ln.Multiaddr(), thirdPartyID)\n\trequire.Error(t, err)\n\trequire.Contains(t, err.Error(), \"CRYPTO_ERROR\")\n\tdefer clientTransport.(io.Closer).Close()\n\n\tacceptErr := make(chan error)\n\tgo func() {\n\t\t_, err := ln.Accept()\n\t\tacceptErr <- err\n\t}()\n\n\tselect {\n\tcase <-acceptErr:\n\t\tt.Fatal(\"didn't expect Accept to return before being closed\")\n\tcase <-time.After(100 * time.Millisecond):\n\t}\n\n\trequire.NoError(t, ln.Close())\n\trequire.Error(t, <-acceptErr)\n}\n\nfunc TestConnectionGating(t *testing.T) {\n\tfor _, tc := range connTestCases {\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\ttestConnectionGating(t, tc)\n\t\t})\n\t}\n}\n\nfunc testConnectionGating(t *testing.T, tc *connTestCase) {\n\tserverID, serverKey := createPeer(t)\n\t_, clientKey := createPeer(t)\n\n\tmockCtrl := gomock.NewController(t)\n\tdefer mockCtrl.Finish()\n\tcg := NewMockConnectionGater(mockCtrl)\n\n\tt.Run(\"accepted connections\", func(t *testing.T) {\n\t\tserverTransport, err := NewTransport(serverKey, newConnManager(t, tc.Options...), nil, cg, nil)\n\t\tdefer serverTransport.(io.Closer).Close()\n\t\trequire.NoError(t, err)\n\t\tln := runServer(t, serverTransport, \"/ip4/127.0.0.1/udp/0/quic-v1\")\n\t\tdefer ln.Close()\n\n\t\tcg.EXPECT().InterceptAccept(gomock.Any())\n\n\t\taccepted := make(chan struct{})\n\t\tgo func() {\n\t\t\tdefer close(accepted)\n\t\t\t_, err := ln.Accept()\n\t\t\trequire.NoError(t, err)\n\t\t}()\n\n\t\tclientTransport, err := NewTransport(clientKey, newConnManager(t, tc.Options...), nil, nil, nil)\n\t\trequire.NoError(t, err)\n\t\tdefer clientTransport.(io.Closer).Close()\n\t\t// make sure that connection attempts fails\n\t\tconn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID)\n\t\t// In rare instances, the connection gating error will already occur on Dial.\n\t\t// In most cases, it will be returned by AcceptStream.\n\t\tif err == nil {\n\t\t\t_, err = conn.AcceptStream()\n\t\t}\n\t\trequire.Contains(t, err.Error(), \"connection gated\")\n\n\t\t// now allow the address and make sure the connection goes through\n\t\tcg.EXPECT().InterceptAccept(gomock.Any()).Return(true)\n\t\tcg.EXPECT().InterceptSecured(gomock.Any(), gomock.Any(), gomock.Any()).Return(true)\n\t\tconn, err = clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID)\n\t\trequire.NoError(t, err)\n\t\tdefer conn.Close()\n\t\trequire.Eventually(t, func() bool {\n\t\t\tselect {\n\t\t\tcase <-accepted:\n\t\t\t\treturn true\n\t\t\tdefault:\n\t\t\t\treturn false\n\t\t\t}\n\t\t}, time.Second, 10*time.Millisecond)\n\t})\n\n\tt.Run(\"secured connections\", func(t *testing.T) {\n\t\tserverTransport, err := NewTransport(serverKey, newConnManager(t, tc.Options...), nil, nil, nil)\n\t\trequire.NoError(t, err)\n\t\tdefer serverTransport.(io.Closer).Close()\n\t\tln := runServer(t, serverTransport, \"/ip4/127.0.0.1/udp/0/quic-v1\")\n\t\tdefer ln.Close()\n\n\t\tcg := NewMockConnectionGater(mockCtrl)\n\t\tcg.EXPECT().InterceptSecured(gomock.Any(), gomock.Any(), gomock.Any())\n\n\t\tclientTransport, err := NewTransport(clientKey, newConnManager(t, tc.Options...), nil, cg, nil)\n\t\trequire.NoError(t, err)\n\t\tdefer clientTransport.(io.Closer).Close()\n\n\t\t// make sure that connection attempts fails\n\t\t_, err = clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID)\n\t\trequire.Error(t, err)\n\t\trequire.Contains(t, err.Error(), \"connection gated\")\n\n\t\t// now allow the peerId and make sure the connection goes through\n\t\tcg.EXPECT().InterceptSecured(gomock.Any(), gomock.Any(), gomock.Any()).Return(true)\n\t\tconn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID)\n\t\trequire.NoError(t, err)\n\t\tconn.Close()\n\t})\n}\n\nfunc TestDialTwo(t *testing.T) {\n\tfor _, tc := range connTestCases {\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\ttestDialTwo(t, tc)\n\t\t})\n\t}\n}\n\nfunc testDialTwo(t *testing.T, tc *connTestCase) {\n\tserverID, serverKey := createPeer(t)\n\t_, clientKey := createPeer(t)\n\tserverID2, serverKey2 := createPeer(t)\n\n\tserverTransport, err := NewTransport(serverKey, newConnManager(t, tc.Options...), nil, nil, nil)\n\trequire.NoError(t, err)\n\tdefer serverTransport.(io.Closer).Close()\n\tln1 := runServer(t, serverTransport, \"/ip4/127.0.0.1/udp/0/quic-v1\")\n\tdefer ln1.Close()\n\tserverTransport2, err := NewTransport(serverKey2, newConnManager(t, tc.Options...), nil, nil, nil)\n\trequire.NoError(t, err)\n\tdefer serverTransport2.(io.Closer).Close()\n\tln2 := runServer(t, serverTransport2, \"/ip4/127.0.0.1/udp/0/quic-v1\")\n\tdefer ln2.Close()\n\n\tdata := bytes.Repeat([]byte{'a'}, 5*1<<20) // 5 MB\n\t// wait for both servers to accept a connection\n\t// then send some data\n\tgo func() {\n\t\tserverConn1, err := ln1.Accept()\n\t\trequire.NoError(t, err)\n\t\tserverConn2, err := ln2.Accept()\n\t\trequire.NoError(t, err)\n\n\t\tfor _, c := range []tpt.CapableConn{serverConn1, serverConn2} {\n\t\t\tgo func(conn tpt.CapableConn) {\n\t\t\t\tstr, err := conn.OpenStream(context.Background())\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tdefer str.Close()\n\t\t\t\t_, err = str.Write(data)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}(c)\n\t\t}\n\t}()\n\n\tclientTransport, err := NewTransport(clientKey, newConnManager(t, tc.Options...), nil, nil, nil)\n\trequire.NoError(t, err)\n\tdefer clientTransport.(io.Closer).Close()\n\tc1, err := clientTransport.Dial(context.Background(), ln1.Multiaddr(), serverID)\n\trequire.NoError(t, err)\n\tdefer c1.Close()\n\tc2, err := clientTransport.Dial(context.Background(), ln2.Multiaddr(), serverID2)\n\trequire.NoError(t, err)\n\tdefer c2.Close()\n\n\tdone := make(chan struct{}, 2)\n\t// receive the data on both connections at the same time\n\tfor _, c := range []tpt.CapableConn{c1, c2} {\n\t\tgo func(conn tpt.CapableConn) {\n\t\t\tstr, err := conn.AcceptStream()\n\t\t\trequire.NoError(t, err)\n\t\t\tstr.CloseWrite()\n\t\t\td, err := io.ReadAll(str)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, d, data)\n\t\t\tdone <- struct{}{}\n\t\t}(c)\n\t}\n\n\tfor range 2 {\n\t\trequire.Eventually(t, func() bool {\n\t\t\tselect {\n\t\t\tcase <-done:\n\t\t\t\treturn true\n\t\t\tdefault:\n\t\t\t\treturn false\n\t\t\t}\n\t\t}, 15*time.Second, 50*time.Millisecond)\n\t}\n}\n\nfunc TestStatelessReset(t *testing.T) {\n\tfor _, tc := range connTestCases {\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\ttestStatelessReset(t, tc)\n\t\t})\n\t}\n}\n\nfunc newUDPConnLocalhost(t testing.TB, port int) (*net.UDPConn, func()) {\n\tt.Helper()\n\tconn, err := net.ListenUDP(\"udp\", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: port})\n\trequire.NoError(t, err)\n\treturn conn, func() { conn.Close() }\n}\n\nfunc testStatelessReset(t *testing.T, tc *connTestCase) {\n\tserverID, serverKey := createPeer(t)\n\t_, clientKey := createPeer(t)\n\n\tserverTransport, err := NewTransport(serverKey, newConnManager(t, tc.Options...), nil, nil, nil)\n\trequire.NoError(t, err)\n\tdefer serverTransport.(io.Closer).Close()\n\tln := runServer(t, serverTransport, \"/ip4/127.0.0.1/udp/0/quic-v1\")\n\n\tvar drop uint32\n\tdropCallback := func(quicproxy.Direction, net.Addr, net.Addr, []byte) bool { return atomic.LoadUint32(&drop) > 0 }\n\tproxyConn, cleanup := newUDPConnLocalhost(t, 0)\n\tproxy := quicproxy.Proxy{\n\t\tConn:       proxyConn,\n\t\tServerAddr: ln.Addr().(*net.UDPAddr),\n\t\tDropPacket: dropCallback,\n\t}\n\terr = proxy.Start()\n\trequire.NoError(t, err)\n\n\t// establish a connection\n\tclientTransport, err := NewTransport(clientKey, newConnManager(t, tc.Options...), nil, nil, nil)\n\trequire.NoError(t, err)\n\tdefer clientTransport.(io.Closer).Close()\n\tproxyAddr, err := quicreuse.ToQuicMultiaddr(proxy.LocalAddr(), quic.Version1)\n\trequire.NoError(t, err)\n\tconn, err := clientTransport.Dial(context.Background(), proxyAddr, serverID)\n\trequire.NoError(t, err)\n\tconnChan := make(chan tpt.CapableConn)\n\tgo func() {\n\t\tconn, err := ln.Accept()\n\t\trequire.NoError(t, err)\n\t\tstr, err := conn.OpenStream(context.Background())\n\t\trequire.NoError(t, err)\n\t\t_, err = conn.LocalMultiaddr().ValueForProtocol(ma.P_QUIC_V1)\n\t\trequire.NoError(t, err)\n\t\tstr.Write([]byte(\"foobar\"))\n\t\tconnChan <- conn\n\t}()\n\n\tstr, err := conn.AcceptStream()\n\trequire.NoError(t, err)\n\t_, err = str.Read(make([]byte, 6))\n\trequire.NoError(t, err)\n\n\t// Stop forwarding packets and close the server.\n\t// This prevents the CONNECTION_CLOSE from reaching the client.\n\tatomic.StoreUint32(&drop, 1)\n\tln.Close()\n\t(<-connChan).Close()\n\tproxyLocalPort := proxy.LocalAddr().(*net.UDPAddr).Port\n\tproxy.Close()\n\tcleanup()\n\n\t// Start another listener (on a different port).\n\tln, err = serverTransport.Listen(ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1\"))\n\trequire.NoError(t, err)\n\tdefer ln.Close()\n\t// Now that the new server is up, re-enable packet forwarding.\n\tatomic.StoreUint32(&drop, 0)\n\n\tproxyConn, cleanup = newUDPConnLocalhost(t, proxyLocalPort)\n\tdefer cleanup()\n\t// Recreate the proxy, such that its client-facing port stays constant.\n\tproxyBis := quicproxy.Proxy{\n\t\tConn:       proxyConn,\n\t\tServerAddr: ln.Addr().(*net.UDPAddr),\n\t\tDropPacket: dropCallback,\n\t}\n\terr = proxyBis.Start()\n\trequire.NoError(t, err)\n\tdefer proxyBis.Close()\n\n\t// Trigger something (not too small) to be sent, so that we receive the stateless reset.\n\t// The new server doesn't have any state for the previously established connection.\n\t// We expect it to send a stateless reset.\n\t_, rerr := str.Write([]byte(\"Lorem ipsum dolor sit amet.\"))\n\tif rerr == nil {\n\t\t_, rerr = str.Read([]byte{0, 0})\n\t}\n\trequire.Error(t, rerr)\n\tvar statelessResetErr *quic.StatelessResetError\n\trequire.ErrorAs(t, rerr, &statelessResetErr)\n}\n\n// Hole punching is only expected to work with reuseport enabled.\n// We don't need to test `DisableReuseport` option.\nfunc TestHolePunching(t *testing.T) {\n\tserverID, serverKey := createPeer(t)\n\tclientID, clientKey := createPeer(t)\n\n\tt1, err := NewTransport(serverKey, newConnManager(t), nil, nil, nil)\n\trequire.NoError(t, err)\n\tdefer t1.(io.Closer).Close()\n\tladdr, err := ma.NewMultiaddr(\"/ip4/127.0.0.1/udp/0/quic-v1\")\n\trequire.NoError(t, err)\n\tln1, err := t1.Listen(laddr)\n\trequire.NoError(t, err)\n\tdone1 := make(chan struct{})\n\tgo func() {\n\t\tdefer close(done1)\n\t\t_, err := ln1.Accept()\n\t\trequire.Error(t, err, \"didn't expect to accept any connections\")\n\t}()\n\n\tt2, err := NewTransport(clientKey, newConnManager(t), nil, nil, nil)\n\trequire.NoError(t, err)\n\tdefer t2.(io.Closer).Close()\n\tln2, err := t2.Listen(laddr)\n\trequire.NoError(t, err)\n\tdone2 := make(chan struct{})\n\tgo func() {\n\t\tdefer close(done2)\n\t\t_, err := ln2.Accept()\n\t\trequire.Error(t, err, \"didn't expect to accept any connections\")\n\t}()\n\tconnChan := make(chan tpt.CapableConn)\n\tgo func() {\n\t\tconn, err := t2.Dial(\n\t\t\tnetwork.WithSimultaneousConnect(context.Background(), false, \"\"),\n\t\t\tln1.Multiaddr(),\n\t\t\tserverID,\n\t\t)\n\t\trequire.NoError(t, err)\n\t\tconnChan <- conn\n\t}()\n\t// Make sure the server role (the dial on t2) has progressed far enough.\n\t// If it hasn't created the hole punch map entry, the connection will be accepted as a regular connection,\n\t// which would make this test fail.\n\trequire.Eventually(t, func() bool {\n\t\ttr := t2.(*transport)\n\t\ttr.holePunchingMx.Lock()\n\t\tdefer tr.holePunchingMx.Unlock()\n\t\treturn len(tr.holePunching) > 0\n\t}, time.Second, 10*time.Millisecond)\n\n\tconn1, err := t1.Dial(\n\t\tnetwork.WithSimultaneousConnect(context.Background(), true, \"\"),\n\t\tln2.Multiaddr(),\n\t\tclientID,\n\t)\n\trequire.NoError(t, err)\n\tdefer conn1.Close()\n\trequire.Equal(t, conn1.RemotePeer(), clientID)\n\tvar conn2 tpt.CapableConn\n\trequire.Eventually(t, func() bool {\n\t\tselect {\n\t\tcase conn2 = <-connChan:\n\t\t\treturn true\n\t\tdefault:\n\t\t\treturn false\n\t\t}\n\t}, time.Second, 10*time.Millisecond)\n\tdefer conn2.Close()\n\trequire.Equal(t, conn2.RemotePeer(), serverID)\n\tln1.Close()\n\tln2.Close()\n\t<-done1\n\t<-done2\n}\n"
  },
  {
    "path": "p2p/transport/quic/listener.go",
    "content": "package libp2pquic\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net\"\n\n\tic \"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\ttpt \"github.com/libp2p/go-libp2p/core/transport\"\n\tp2ptls \"github.com/libp2p/go-libp2p/p2p/security/tls\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/quicreuse\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/quic-go/quic-go\"\n)\n\n// A listener listens for QUIC connections.\ntype listener struct {\n\treuseListener   quicreuse.Listener\n\ttransport       *transport\n\trcmgr           network.ResourceManager\n\tprivKey         ic.PrivKey\n\tlocalPeer       peer.ID\n\tlocalMultiaddrs map[quic.Version]ma.Multiaddr\n}\n\nfunc newListener(ln quicreuse.Listener, t *transport, localPeer peer.ID, key ic.PrivKey, rcmgr network.ResourceManager) (listener, error) {\n\tlocalMultiaddrs := make(map[quic.Version]ma.Multiaddr)\n\tfor _, addr := range ln.Multiaddrs() {\n\t\tif _, err := addr.ValueForProtocol(ma.P_QUIC_V1); err == nil {\n\t\t\tlocalMultiaddrs[quic.Version1] = addr\n\t\t}\n\t}\n\n\treturn listener{\n\t\treuseListener:   ln,\n\t\ttransport:       t,\n\t\trcmgr:           rcmgr,\n\t\tprivKey:         key,\n\t\tlocalPeer:       localPeer,\n\t\tlocalMultiaddrs: localMultiaddrs,\n\t}, nil\n}\n\n// Accept accepts new connections.\nfunc (l *listener) Accept() (tpt.CapableConn, error) {\n\tfor {\n\t\tqconn, err := l.reuseListener.Accept(context.Background())\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tc, err := l.wrapConn(qconn)\n\t\tif err != nil {\n\t\t\tlog.Debug(\"failed to setup connection\", \"err\", err)\n\t\t\tqconn.CloseWithError(quic.ApplicationErrorCode(network.ConnResourceLimitExceeded), \"\")\n\t\t\tcontinue\n\t\t}\n\t\tl.transport.addConn(qconn, c)\n\t\tif l.transport.gater != nil && !(l.transport.gater.InterceptAccept(c) && l.transport.gater.InterceptSecured(network.DirInbound, c.remotePeerID, c)) {\n\t\t\tc.closeWithError(quic.ApplicationErrorCode(network.ConnGated), \"connection gated\")\n\t\t\tcontinue\n\t\t}\n\n\t\t// return through active hole punching if any\n\t\tkey := holePunchKey{addr: qconn.RemoteAddr().String(), peer: c.remotePeerID}\n\t\tvar wasHolePunch bool\n\t\tl.transport.holePunchingMx.Lock()\n\t\tholePunch, ok := l.transport.holePunching[key]\n\t\tif ok && !holePunch.fulfilled {\n\t\t\tholePunch.connCh <- c\n\t\t\twasHolePunch = true\n\t\t\tholePunch.fulfilled = true\n\t\t}\n\t\tl.transport.holePunchingMx.Unlock()\n\t\tif wasHolePunch {\n\t\t\tcontinue\n\t\t}\n\t\treturn c, nil\n\t}\n}\n\n// wrapConn wraps a QUIC connection into a libp2p [tpt.CapableConn].\n// If wrapping fails. The caller is responsible for cleaning up the\n// connection.\nfunc (l *listener) wrapConn(qconn *quic.Conn) (*conn, error) {\n\tremoteMultiaddr, err := quicreuse.ToQuicMultiaddr(qconn.RemoteAddr(), qconn.ConnectionState().Version)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tconnScope, err := network.UnwrapConnManagementScope(qconn.Context())\n\tif err != nil {\n\t\tconnScope = nil\n\t\t// Don't error here.\n\t\t// Setup scope if we don't have scope from quicreuse.\n\t\t// This is better than failing so that users that don't use quicreuse.ConnContext option with the resource\n\t\t// manager work correctly.\n\t}\n\tif connScope == nil {\n\t\tconnScope, err = l.rcmgr.OpenConnection(network.DirInbound, false, remoteMultiaddr)\n\t\tif err != nil {\n\t\t\tlog.Debug(\"resource manager blocked incoming connection\", \"addr\", qconn.RemoteAddr(), \"err\", err)\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tc, err := l.wrapConnWithScope(qconn, connScope, remoteMultiaddr)\n\tif err != nil {\n\t\tconnScope.Done()\n\t\treturn nil, err\n\t}\n\n\treturn c, nil\n}\n\nfunc (l *listener) wrapConnWithScope(qconn *quic.Conn, connScope network.ConnManagementScope, remoteMultiaddr ma.Multiaddr) (*conn, error) {\n\t// The tls.Config used to establish this connection already verified the certificate chain.\n\t// Since we don't have any way of knowing which tls.Config was used though,\n\t// we have to re-determine the peer's identity here.\n\t// Therefore, this is expected to never fail.\n\tremotePubKey, err := p2ptls.PubKeyFromCertChain(qconn.ConnectionState().TLS.PeerCertificates)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tremotePeerID, err := peer.IDFromPublicKey(remotePubKey)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := connScope.SetPeer(remotePeerID); err != nil {\n\t\tlog.Debug(\"resource manager blocked incoming connection for peer\", \"peer\", remotePeerID, \"addr\", qconn.RemoteAddr(), \"err\", err)\n\t\treturn nil, err\n\t}\n\n\tlocalMultiaddr, found := l.localMultiaddrs[qconn.ConnectionState().Version]\n\tif !found {\n\t\treturn nil, errors.New(\"unknown QUIC version:\" + qconn.ConnectionState().Version.String())\n\t}\n\n\treturn &conn{\n\t\tquicConn:        qconn,\n\t\ttransport:       l.transport,\n\t\tscope:           connScope,\n\t\tlocalPeer:       l.localPeer,\n\t\tlocalMultiaddr:  localMultiaddr,\n\t\tremoteMultiaddr: remoteMultiaddr,\n\t\tremotePeerID:    remotePeerID,\n\t\tremotePubKey:    remotePubKey,\n\t}, nil\n}\n\n// Close closes the listener.\nfunc (l *listener) Close() error {\n\treturn l.reuseListener.Close()\n}\n\n// Addr returns the address of this listener.\nfunc (l *listener) Addr() net.Addr {\n\treturn l.reuseListener.Addr()\n}\n"
  },
  {
    "path": "p2p/transport/quic/listener_test.go",
    "content": "package libp2pquic\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"crypto/x509\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"testing\"\n\t\"time\"\n\n\tic \"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\tmocknetwork \"github.com/libp2p/go-libp2p/core/network/mocks\"\n\ttpt \"github.com/libp2p/go-libp2p/core/transport\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/quicreuse\"\n\t\"github.com/quic-go/quic-go\"\n\t\"go.uber.org/mock/gomock\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc newTransport(t *testing.T, rcmgr network.ResourceManager) tpt.Transport {\n\trsaKey, err := rsa.GenerateKey(rand.Reader, 2048)\n\trequire.NoError(t, err)\n\tkey, err := ic.UnmarshalRsaPrivateKey(x509.MarshalPKCS1PrivateKey(rsaKey))\n\trequire.NoError(t, err)\n\ttr, err := NewTransport(key, newConnManager(t), nil, nil, rcmgr)\n\trequire.NoError(t, err)\n\treturn tr\n}\n\nfunc TestListenAddr(t *testing.T) {\n\ttr := newTransport(t, nil)\n\tdefer tr.(io.Closer).Close()\n\n\tt.Run(\"for IPv4\", func(t *testing.T) {\n\t\tlocalAddrV1 := ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1\")\n\t\tln, err := tr.Listen(localAddrV1)\n\t\trequire.NoError(t, err)\n\t\tdefer ln.Close()\n\t\tport := ln.Addr().(*net.UDPAddr).Port\n\t\trequire.NotZero(t, port)\n\n\t\tvar multiaddrsStrings []string\n\t\tfor _, a := range []ma.Multiaddr{ln.Multiaddr()} {\n\t\t\tmultiaddrsStrings = append(multiaddrsStrings, a.String())\n\t\t}\n\t\trequire.Contains(t, multiaddrsStrings, fmt.Sprintf(\"/ip4/127.0.0.1/udp/%d/quic-v1\", port))\n\t})\n\n\tt.Run(\"for IPv6\", func(t *testing.T) {\n\t\tlocalAddrV1 := ma.StringCast(\"/ip6/::/udp/0/quic-v1\")\n\t\tln, err := tr.Listen(localAddrV1)\n\t\trequire.NoError(t, err)\n\t\tdefer ln.Close()\n\t\tport := ln.Addr().(*net.UDPAddr).Port\n\t\trequire.NotZero(t, port)\n\t\tvar multiaddrsStrings []string\n\t\tfor _, a := range []ma.Multiaddr{ln.Multiaddr()} {\n\t\t\tmultiaddrsStrings = append(multiaddrsStrings, a.String())\n\t\t}\n\t\trequire.Contains(t, multiaddrsStrings, fmt.Sprintf(\"/ip6/::/udp/%d/quic-v1\", port))\n\t})\n}\n\nfunc TestAccepting(t *testing.T) {\n\ttr := newTransport(t, nil)\n\tdefer tr.(io.Closer).Close()\n\tln, err := tr.Listen(ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1\"))\n\trequire.NoError(t, err)\n\tdone := make(chan struct{})\n\tgo func() {\n\t\tln.Accept()\n\t\tclose(done)\n\t}()\n\ttime.Sleep(100 * time.Millisecond)\n\tselect {\n\tcase <-done:\n\t\tt.Fatal(\"Accept didn't block\")\n\tdefault:\n\t}\n\trequire.NoError(t, ln.Close())\n\tselect {\n\tcase <-done:\n\tcase <-time.After(100 * time.Millisecond):\n\t\tt.Fatal(\"Accept didn't return after the listener was closed\")\n\t}\n}\n\nfunc TestAcceptAfterClose(t *testing.T) {\n\ttr := newTransport(t, nil)\n\tdefer tr.(io.Closer).Close()\n\tln, err := tr.Listen(ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1\"))\n\trequire.NoError(t, err)\n\trequire.NoError(t, ln.Close())\n\t_, err = ln.Accept()\n\trequire.Error(t, err)\n}\n\nfunc TestCorrectNumberOfVirtualListeners(t *testing.T) {\n\ttr := newTransport(t, nil)\n\ttpt := tr.(*transport)\n\tdefer tr.(io.Closer).Close()\n\n\tlocalAddrV1 := ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1\")\n\tln, err := tr.Listen(localAddrV1)\n\trequire.NoError(t, err)\n\tudpAddr, _, err := quicreuse.FromQuicMultiaddr(localAddrV1)\n\trequire.NoError(t, err)\n\n\trequire.NoError(t, err)\n\trequire.Len(t, tpt.listeners[udpAddr.String()], 1)\n\tln.Close()\n\trequire.Empty(t, tpt.listeners[udpAddr.String()])\n}\n\nfunc TestCleanupConnWhenBlocked(t *testing.T) {\n\tctrl := gomock.NewController(t)\n\tdefer ctrl.Finish()\n\tmockRcmgr := mocknetwork.NewMockResourceManager(ctrl)\n\tmockRcmgr.EXPECT().OpenConnection(network.DirInbound, false, gomock.Any()).DoAndReturn(func(network.Direction, bool, ma.Multiaddr) (network.ConnManagementScope, error) {\n\t\t// Block the connection\n\t\treturn nil, fmt.Errorf(\"connections blocked\")\n\t})\n\n\tserver := newTransport(t, mockRcmgr)\n\tserverTpt := server.(*transport)\n\tdefer server.(io.Closer).Close()\n\n\tlocalAddrV1 := ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1\")\n\tln, err := server.Listen(localAddrV1)\n\trequire.NoError(t, err)\n\tdefer ln.Close()\n\tgo ln.Accept()\n\n\tclient := newTransport(t, nil)\n\tctx := context.Background()\n\n\tvar quicErr *quic.ApplicationError = &quic.ApplicationError{}\n\tconn, err := client.Dial(ctx, ln.Multiaddr(), serverTpt.localPeer)\n\tif err != nil && errors.As(err, &quicErr) {\n\t\t// We hit our expected application error\n\t\treturn\n\t}\n\n\t// No error yet, let's continue using the conn\n\ts, err := conn.OpenStream(ctx)\n\tif err != nil && errors.As(err, &quicErr) {\n\t\t// We hit our expected application error\n\t\treturn\n\t}\n\n\t// No error yet, let's continue using the conn\n\ts.SetReadDeadline(time.Now().Add(10 * time.Second))\n\tb := [1]byte{}\n\t_, err = s.Read(b[:])\n\tconnError := &network.ConnError{}\n\tif err != nil && errors.As(err, &connError) {\n\t\t// We hit our expected application error\n\t\treturn\n\t}\n\n\tt.Fatalf(\"expected network.ConnError, got %v\", err)\n}\n"
  },
  {
    "path": "p2p/transport/quic/mock_connection_gater_test.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github.com/libp2p/go-libp2p/core/connmgr (interfaces: ConnectionGater)\n//\n// Generated by this command:\n//\n//\tmockgen -package libp2pquic -destination mock_connection_gater_test.go github.com/libp2p/go-libp2p/core/connmgr ConnectionGater\n//\n\n// Package libp2pquic is a generated GoMock package.\npackage libp2pquic\n\nimport (\n\treflect \"reflect\"\n\n\tcontrol \"github.com/libp2p/go-libp2p/core/control\"\n\tnetwork \"github.com/libp2p/go-libp2p/core/network\"\n\tpeer \"github.com/libp2p/go-libp2p/core/peer\"\n\tmultiaddr \"github.com/multiformats/go-multiaddr\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockConnectionGater is a mock of ConnectionGater interface.\ntype MockConnectionGater struct {\n\tctrl     *gomock.Controller\n\trecorder *MockConnectionGaterMockRecorder\n\tisgomock struct{}\n}\n\n// MockConnectionGaterMockRecorder is the mock recorder for MockConnectionGater.\ntype MockConnectionGaterMockRecorder struct {\n\tmock *MockConnectionGater\n}\n\n// NewMockConnectionGater creates a new mock instance.\nfunc NewMockConnectionGater(ctrl *gomock.Controller) *MockConnectionGater {\n\tmock := &MockConnectionGater{ctrl: ctrl}\n\tmock.recorder = &MockConnectionGaterMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockConnectionGater) EXPECT() *MockConnectionGaterMockRecorder {\n\treturn m.recorder\n}\n\n// InterceptAccept mocks base method.\nfunc (m *MockConnectionGater) InterceptAccept(arg0 network.ConnMultiaddrs) bool {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"InterceptAccept\", arg0)\n\tret0, _ := ret[0].(bool)\n\treturn ret0\n}\n\n// InterceptAccept indicates an expected call of InterceptAccept.\nfunc (mr *MockConnectionGaterMockRecorder) InterceptAccept(arg0 any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"InterceptAccept\", reflect.TypeOf((*MockConnectionGater)(nil).InterceptAccept), arg0)\n}\n\n// InterceptAddrDial mocks base method.\nfunc (m *MockConnectionGater) InterceptAddrDial(arg0 peer.ID, arg1 multiaddr.Multiaddr) bool {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"InterceptAddrDial\", arg0, arg1)\n\tret0, _ := ret[0].(bool)\n\treturn ret0\n}\n\n// InterceptAddrDial indicates an expected call of InterceptAddrDial.\nfunc (mr *MockConnectionGaterMockRecorder) InterceptAddrDial(arg0, arg1 any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"InterceptAddrDial\", reflect.TypeOf((*MockConnectionGater)(nil).InterceptAddrDial), arg0, arg1)\n}\n\n// InterceptPeerDial mocks base method.\nfunc (m *MockConnectionGater) InterceptPeerDial(p peer.ID) bool {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"InterceptPeerDial\", p)\n\tret0, _ := ret[0].(bool)\n\treturn ret0\n}\n\n// InterceptPeerDial indicates an expected call of InterceptPeerDial.\nfunc (mr *MockConnectionGaterMockRecorder) InterceptPeerDial(p any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"InterceptPeerDial\", reflect.TypeOf((*MockConnectionGater)(nil).InterceptPeerDial), p)\n}\n\n// InterceptSecured mocks base method.\nfunc (m *MockConnectionGater) InterceptSecured(arg0 network.Direction, arg1 peer.ID, arg2 network.ConnMultiaddrs) bool {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"InterceptSecured\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(bool)\n\treturn ret0\n}\n\n// InterceptSecured indicates an expected call of InterceptSecured.\nfunc (mr *MockConnectionGaterMockRecorder) InterceptSecured(arg0, arg1, arg2 any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"InterceptSecured\", reflect.TypeOf((*MockConnectionGater)(nil).InterceptSecured), arg0, arg1, arg2)\n}\n\n// InterceptUpgraded mocks base method.\nfunc (m *MockConnectionGater) InterceptUpgraded(arg0 network.Conn) (bool, control.DisconnectReason) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"InterceptUpgraded\", arg0)\n\tret0, _ := ret[0].(bool)\n\tret1, _ := ret[1].(control.DisconnectReason)\n\treturn ret0, ret1\n}\n\n// InterceptUpgraded indicates an expected call of InterceptUpgraded.\nfunc (mr *MockConnectionGaterMockRecorder) InterceptUpgraded(arg0 any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"InterceptUpgraded\", reflect.TypeOf((*MockConnectionGater)(nil).InterceptUpgraded), arg0)\n}\n"
  },
  {
    "path": "p2p/transport/quic/stream.go",
    "content": "package libp2pquic\n\nimport (\n\t\"errors\"\n\t\"math\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\n\t\"github.com/quic-go/quic-go\"\n)\n\nconst (\n\treset quic.StreamErrorCode = 0\n)\n\ntype stream struct {\n\t*quic.Stream\n}\n\nvar _ network.MuxedStream = stream{}\n\nfunc parseStreamError(err error) error {\n\tif err == nil {\n\t\treturn err\n\t}\n\tse := &quic.StreamError{}\n\tif errors.As(err, &se) {\n\t\tvar code network.StreamErrorCode\n\t\tif se.ErrorCode > math.MaxUint32 {\n\t\t\tcode = network.StreamCodeOutOfRange\n\t\t} else {\n\t\t\tcode = network.StreamErrorCode(se.ErrorCode)\n\t\t}\n\t\terr = &network.StreamError{\n\t\t\tErrorCode:      code,\n\t\t\tRemote:         se.Remote,\n\t\t\tTransportError: se,\n\t\t}\n\t}\n\tae := &quic.ApplicationError{}\n\tif errors.As(err, &ae) {\n\t\tvar code network.ConnErrorCode\n\t\tif ae.ErrorCode > math.MaxUint32 {\n\t\t\tcode = network.ConnCodeOutOfRange\n\t\t} else {\n\t\t\tcode = network.ConnErrorCode(ae.ErrorCode)\n\t\t}\n\t\terr = &network.ConnError{\n\t\t\tErrorCode:      code,\n\t\t\tRemote:         ae.Remote,\n\t\t\tTransportError: ae,\n\t\t}\n\t}\n\treturn err\n}\n\nfunc (s stream) Read(b []byte) (n int, err error) {\n\tn, err = s.Stream.Read(b)\n\treturn n, parseStreamError(err)\n}\n\nfunc (s stream) Write(b []byte) (n int, err error) {\n\tn, err = s.Stream.Write(b)\n\treturn n, parseStreamError(err)\n}\n\nfunc (s stream) Reset() error {\n\ts.Stream.CancelRead(reset)\n\ts.Stream.CancelWrite(reset)\n\treturn nil\n}\n\nfunc (s stream) ResetWithError(errCode network.StreamErrorCode) error {\n\ts.Stream.CancelRead(quic.StreamErrorCode(errCode))\n\ts.Stream.CancelWrite(quic.StreamErrorCode(errCode))\n\treturn nil\n}\n\nfunc (s stream) Close() error {\n\ts.Stream.CancelRead(reset)\n\treturn s.Stream.Close()\n}\n\nfunc (s stream) CloseRead() error {\n\ts.Stream.CancelRead(reset)\n\treturn nil\n}\n\nfunc (s stream) CloseWrite() error {\n\treturn s.Stream.Close()\n}\n"
  },
  {
    "path": "p2p/transport/quic/transport.go",
    "content": "package libp2pquic\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"net\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/connmgr\"\n\tic \"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/pnet\"\n\ttpt \"github.com/libp2p/go-libp2p/core/transport\"\n\tp2ptls \"github.com/libp2p/go-libp2p/p2p/security/tls\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/quicreuse\"\n\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmafmt \"github.com/multiformats/go-multiaddr-fmt\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n\t\"github.com/quic-go/quic-go\"\n)\n\nconst ListenOrder = 1\n\nvar log = logging.Logger(\"quic-transport\")\n\nvar ErrHolePunching = errors.New(\"hole punching attempted; no active dial\")\n\nvar HolePunchTimeout = 5 * time.Second\n\n// The Transport implements the tpt.Transport interface for QUIC connections.\ntype transport struct {\n\tprivKey     ic.PrivKey\n\tlocalPeer   peer.ID\n\tidentity    *p2ptls.Identity\n\tconnManager *quicreuse.ConnManager\n\tgater       connmgr.ConnectionGater\n\trcmgr       network.ResourceManager\n\n\tholePunchingMx sync.Mutex\n\tholePunching   map[holePunchKey]*activeHolePunch\n\n\trndMx sync.Mutex\n\trnd   rand.Rand\n\n\tconnMx sync.Mutex\n\tconns  map[*quic.Conn]*conn\n\n\tlistenersMu sync.Mutex\n\t// map of UDPAddr as string to a virtualListeners\n\tlisteners map[string][]*virtualListener\n}\n\nvar _ tpt.Transport = &transport{}\n\ntype holePunchKey struct {\n\taddr string\n\tpeer peer.ID\n}\n\ntype activeHolePunch struct {\n\tconnCh    chan tpt.CapableConn\n\tfulfilled bool\n}\n\n// NewTransport creates a new QUIC transport\nfunc NewTransport(key ic.PrivKey, connManager *quicreuse.ConnManager, psk pnet.PSK, gater connmgr.ConnectionGater, rcmgr network.ResourceManager) (tpt.Transport, error) {\n\tif len(psk) > 0 {\n\t\tlog.Error(\"QUIC doesn't support private networks yet.\")\n\t\treturn nil, errors.New(\"QUIC doesn't support private networks yet\")\n\t}\n\tlocalPeer, err := peer.IDFromPrivateKey(key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tidentity, err := p2ptls.NewIdentity(key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif rcmgr == nil {\n\t\trcmgr = &network.NullResourceManager{}\n\t}\n\n\treturn &transport{\n\t\tprivKey:      key,\n\t\tlocalPeer:    localPeer,\n\t\tidentity:     identity,\n\t\tconnManager:  connManager,\n\t\tgater:        gater,\n\t\trcmgr:        rcmgr,\n\t\tconns:        make(map[*quic.Conn]*conn),\n\t\tholePunching: make(map[holePunchKey]*activeHolePunch),\n\t\trnd:          *rand.New(rand.NewSource(time.Now().UnixNano())),\n\n\t\tlisteners: make(map[string][]*virtualListener),\n\t}, nil\n}\n\nfunc (t *transport) ListenOrder() int {\n\treturn ListenOrder\n}\n\n// Dial dials a new QUIC connection\nfunc (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (_c tpt.CapableConn, _err error) {\n\tif ok, isClient, _ := network.GetSimultaneousConnect(ctx); ok && !isClient {\n\t\treturn t.holePunch(ctx, raddr, p)\n\t}\n\n\tscope, err := t.rcmgr.OpenConnection(network.DirOutbound, false, raddr)\n\tif err != nil {\n\t\tlog.Debug(\"resource manager blocked outgoing connection\", \"peer\", p, \"addr\", raddr, \"err\", err)\n\t\treturn nil, err\n\t}\n\n\tc, err := t.dialWithScope(ctx, raddr, p, scope)\n\tif err != nil {\n\t\tscope.Done()\n\t\treturn nil, err\n\t}\n\treturn c, nil\n}\n\nfunc (t *transport) dialWithScope(ctx context.Context, raddr ma.Multiaddr, p peer.ID, scope network.ConnManagementScope) (tpt.CapableConn, error) {\n\tif err := scope.SetPeer(p); err != nil {\n\t\tlog.Debug(\"resource manager blocked outgoing connection for peer\", \"peer\", p, \"addr\", raddr, \"err\", err)\n\t\treturn nil, err\n\t}\n\n\ttlsConf, keyCh := t.identity.ConfigForPeer(p)\n\tctx = quicreuse.WithAssociation(ctx, t)\n\tpconn, err := t.connManager.DialQUIC(ctx, raddr, tlsConf, t.allowWindowIncrease)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Should be ready by this point, don't block.\n\tvar remotePubKey ic.PubKey\n\tselect {\n\tcase remotePubKey = <-keyCh:\n\tdefault:\n\t}\n\tif remotePubKey == nil {\n\t\tpconn.CloseWithError(1, \"\")\n\t\treturn nil, errors.New(\"p2p/transport/quic BUG: expected remote pub key to be set\")\n\t}\n\n\tlocalMultiaddr, err := quicreuse.ToQuicMultiaddr(pconn.LocalAddr(), pconn.ConnectionState().Version)\n\tif err != nil {\n\t\tpconn.CloseWithError(1, \"\")\n\t\treturn nil, err\n\t}\n\tc := &conn{\n\t\tquicConn:        pconn,\n\t\ttransport:       t,\n\t\tscope:           scope,\n\t\tlocalPeer:       t.localPeer,\n\t\tlocalMultiaddr:  localMultiaddr,\n\t\tremotePubKey:    remotePubKey,\n\t\tremotePeerID:    p,\n\t\tremoteMultiaddr: raddr,\n\t}\n\tif t.gater != nil && !t.gater.InterceptSecured(network.DirOutbound, p, c) {\n\t\tpconn.CloseWithError(quic.ApplicationErrorCode(network.ConnGated), \"connection gated\")\n\t\treturn nil, fmt.Errorf(\"secured connection gated\")\n\t}\n\tt.addConn(pconn, c)\n\treturn c, nil\n}\n\nfunc (t *transport) addConn(conn *quic.Conn, c *conn) {\n\tt.connMx.Lock()\n\tt.conns[conn] = c\n\tt.connMx.Unlock()\n}\n\nfunc (t *transport) removeConn(conn *quic.Conn) {\n\tt.connMx.Lock()\n\tdelete(t.conns, conn)\n\tt.connMx.Unlock()\n}\n\nfunc (t *transport) holePunch(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tpt.CapableConn, error) {\n\tnetwork, saddr, err := manet.DialArgs(raddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\taddr, err := net.ResolveUDPAddr(network, saddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ttr, err := t.connManager.TransportWithAssociationForDial(t, network, addr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer tr.DecreaseCount()\n\n\tctx, cancel := context.WithTimeout(ctx, HolePunchTimeout)\n\tdefer cancel()\n\n\tkey := holePunchKey{addr: addr.String(), peer: p}\n\tt.holePunchingMx.Lock()\n\tif _, ok := t.holePunching[key]; ok {\n\t\tt.holePunchingMx.Unlock()\n\t\treturn nil, fmt.Errorf(\"already punching hole for %s\", addr)\n\t}\n\tconnCh := make(chan tpt.CapableConn, 1)\n\tt.holePunching[key] = &activeHolePunch{connCh: connCh}\n\tt.holePunchingMx.Unlock()\n\n\tvar timer *time.Timer\n\tdefer func() {\n\t\tif timer != nil {\n\t\t\ttimer.Stop()\n\t\t}\n\t}()\n\n\tpayload := make([]byte, 64)\n\tvar punchErr error\nloop:\n\tfor i := 0; ; i++ {\n\t\tt.rndMx.Lock()\n\t\t_, err := t.rnd.Read(payload)\n\t\tt.rndMx.Unlock()\n\t\tif err != nil {\n\t\t\tpunchErr = err\n\t\t\tbreak\n\t\t}\n\t\tif _, err := tr.WriteTo(payload, addr); err != nil {\n\t\t\tpunchErr = err\n\t\t\tbreak\n\t\t}\n\n\t\tmaxSleep := min(\n\t\t\t// in ms\n\t\t\t10*(i+1)*(i+1), 200)\n\t\td := 10*time.Millisecond + time.Duration(rand.Intn(maxSleep))*time.Millisecond\n\t\tif timer == nil {\n\t\t\ttimer = time.NewTimer(d)\n\t\t} else {\n\t\t\ttimer.Reset(d)\n\t\t}\n\t\tselect {\n\t\tcase c := <-connCh:\n\t\t\tt.holePunchingMx.Lock()\n\t\t\tdelete(t.holePunching, key)\n\t\t\tt.holePunchingMx.Unlock()\n\t\t\treturn c, nil\n\t\tcase <-timer.C:\n\t\tcase <-ctx.Done():\n\t\t\tpunchErr = ErrHolePunching\n\t\t\tbreak loop\n\t\t}\n\t}\n\t// we only arrive here if punchErr != nil\n\tt.holePunchingMx.Lock()\n\tdefer func() {\n\t\tdelete(t.holePunching, key)\n\t\tt.holePunchingMx.Unlock()\n\t}()\n\tselect {\n\tcase c := <-t.holePunching[key].connCh:\n\t\treturn c, nil\n\tdefault:\n\t\treturn nil, punchErr\n\t}\n}\n\n// Don't use mafmt.QUIC as we don't want to dial DNS addresses. Just /ip{4,6}/udp/quic-v1\nvar dialMatcher = mafmt.And(mafmt.IP, mafmt.Base(ma.P_UDP), mafmt.Base(ma.P_QUIC_V1))\n\n// CanDial determines if we can dial to an address\nfunc (t *transport) CanDial(addr ma.Multiaddr) bool {\n\treturn dialMatcher.Matches(addr)\n}\n\n// Listen listens for new QUIC connections on the passed multiaddr.\nfunc (t *transport) Listen(addr ma.Multiaddr) (tpt.Listener, error) {\n\tvar tlsConf tls.Config\n\ttlsConf.GetConfigForClient = func(_ *tls.ClientHelloInfo) (*tls.Config, error) {\n\t\t// return a tls.Config that verifies the peer's certificate chain.\n\t\t// Note that since we have no way of associating an incoming QUIC connection with\n\t\t// the peer ID calculated here, we don't actually receive the peer's public key\n\t\t// from the key chan.\n\t\tconf, _ := t.identity.ConfigForPeer(\"\")\n\t\treturn conf, nil\n\t}\n\ttlsConf.NextProtos = []string{\"libp2p\"}\n\tudpAddr, version, err := quicreuse.FromQuicMultiaddr(addr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tt.listenersMu.Lock()\n\tdefer t.listenersMu.Unlock()\n\tlisteners := t.listeners[udpAddr.String()]\n\tvar underlyingListener *listener\n\tvar acceptRunner *acceptLoopRunner\n\tif len(listeners) != 0 {\n\t\t// We already have an underlying listener, let's use it\n\t\tunderlyingListener = listeners[0].listener\n\t\tacceptRunner = listeners[0].acceptRunnner\n\t\t// Make sure our underlying listener is listening on the specified QUIC version\n\t\tif _, ok := underlyingListener.localMultiaddrs[version]; !ok {\n\t\t\treturn nil, fmt.Errorf(\"can't listen on quic version %v, underlying listener doesn't support it\", version)\n\t\t}\n\t} else {\n\t\tln, err := t.connManager.ListenQUICAndAssociate(t, addr, &tlsConf, t.allowWindowIncrease)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tl, err := newListener(ln, t, t.localPeer, t.privKey, t.rcmgr)\n\t\tif err != nil {\n\t\t\t_ = ln.Close()\n\t\t\treturn nil, err\n\t\t}\n\t\tunderlyingListener = &l\n\n\t\tacceptRunner = &acceptLoopRunner{\n\t\t\tacceptSem: make(chan struct{}, 1),\n\t\t\tmuxer:     make(map[quic.Version]chan acceptVal),\n\t\t}\n\t}\n\n\tl := &virtualListener{\n\t\tlistener:      underlyingListener,\n\t\tversion:       version,\n\t\tudpAddr:       udpAddr.String(),\n\t\tt:             t,\n\t\tacceptRunnner: acceptRunner,\n\t\tacceptChan:    acceptRunner.AcceptForVersion(version),\n\t}\n\n\tlisteners = append(listeners, l)\n\tt.listeners[udpAddr.String()] = listeners\n\n\treturn l, nil\n}\n\nfunc (t *transport) allowWindowIncrease(conn *quic.Conn, size uint64) bool {\n\t// If the QUIC connection tries to increase the window before we've inserted it\n\t// into our connections map (which we do right after dialing / accepting it),\n\t// we have no way to account for that memory. This should be very rare.\n\t// Block this attempt. The connection can request more memory later.\n\tt.connMx.Lock()\n\tc, ok := t.conns[conn]\n\tt.connMx.Unlock()\n\tif !ok {\n\t\treturn false\n\t}\n\treturn c.allowWindowIncrease(size)\n}\n\n// Proxy returns true if this transport proxies.\nfunc (t *transport) Proxy() bool {\n\treturn false\n}\n\n// Protocols returns the set of protocols handled by this transport.\nfunc (t *transport) Protocols() []int {\n\treturn t.connManager.Protocols()\n}\n\nfunc (t *transport) String() string {\n\treturn \"QUIC\"\n}\n\nfunc (t *transport) Close() error {\n\treturn nil\n}\n\nfunc (t *transport) CloseVirtualListener(l *virtualListener) error {\n\tt.listenersMu.Lock()\n\tdefer t.listenersMu.Unlock()\n\n\tvar err error\n\tlisteners := t.listeners[l.udpAddr]\n\tif len(listeners) == 1 {\n\t\t// This is the last virtual listener here, so we can close the underlying listener\n\t\terr = l.listener.Close()\n\t\tdelete(t.listeners, l.udpAddr)\n\t\treturn err\n\t}\n\n\tfor i := 0; i < len(listeners); i++ {\n\t\t// Swap remove\n\t\tif l == listeners[i] {\n\t\t\tlisteners[i] = listeners[len(listeners)-1]\n\t\t\tlisteners = listeners[:len(listeners)-1]\n\t\t\tt.listeners[l.udpAddr] = listeners\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn nil\n\n}\n"
  },
  {
    "path": "p2p/transport/quic/transport_test.go",
    "content": "package libp2pquic\n\nimport (\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"crypto/x509\"\n\t\"io\"\n\t\"testing\"\n\n\tic \"github.com/libp2p/go-libp2p/core/crypto\"\n\ttpt \"github.com/libp2p/go-libp2p/core/transport\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc getTransport(t *testing.T) tpt.Transport {\n\tt.Helper()\n\trsaKey, err := rsa.GenerateKey(rand.Reader, 2048)\n\trequire.NoError(t, err)\n\tkey, err := ic.UnmarshalRsaPrivateKey(x509.MarshalPKCS1PrivateKey(rsaKey))\n\trequire.NoError(t, err)\n\ttr, err := NewTransport(key, newConnManager(t), nil, nil, nil)\n\trequire.NoError(t, err)\n\treturn tr\n}\n\nfunc TestQUICProtocol(t *testing.T) {\n\ttr := getTransport(t)\n\tdefer tr.(io.Closer).Close()\n\n\tprotocols := tr.Protocols()\n\tif len(protocols) > 1 {\n\t\tt.Fatalf(\"expected at most one protocol, got %v\", protocols)\n\t}\n\tif protocols[0] != ma.P_QUIC_V1 {\n\t\tt.Fatalf(\"expected the supported protocol to be QUIC v1, got %d\", protocols[0])\n\t}\n}\n\nfunc TestCanDial(t *testing.T) {\n\ttr := getTransport(t)\n\tdefer tr.(io.Closer).Close()\n\n\tinvalid := []string{\n\t\t\"/ip4/127.0.0.1/udp/1234\",\n\t\t\"/ip4/5.5.5.5/tcp/1234\",\n\t\t\"/dns/google.com/udp/443/quic-v1\",\n\t\t\"/ip4/127.0.0.1/udp/1234/quic\",\n\t}\n\tvalid := []string{\n\t\t\"/ip4/127.0.0.1/udp/1234/quic-v1\",\n\t\t\"/ip4/5.5.5.5/udp/0/quic-v1\",\n\t}\n\tfor _, s := range invalid {\n\t\tinvalidAddr, err := ma.NewMultiaddr(s)\n\t\trequire.NoError(t, err)\n\t\tif tr.CanDial(invalidAddr) {\n\t\t\tt.Errorf(\"didn't expect to be able to dial a non-quic address (%s)\", invalidAddr)\n\t\t}\n\t}\n\tfor _, s := range valid {\n\t\tvalidAddr, err := ma.NewMultiaddr(s)\n\t\trequire.NoError(t, err)\n\t\tif !tr.CanDial(validAddr) {\n\t\t\tt.Errorf(\"expected to be able to dial QUIC address (%s)\", validAddr)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "p2p/transport/quic/virtuallistener.go",
    "content": "package libp2pquic\n\nimport (\n\t\"sync\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\ttpt \"github.com/libp2p/go-libp2p/core/transport\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/quicreuse\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/quic-go/quic-go\"\n)\n\nconst acceptBufferPerVersion = 4\n\n// virtualListener is a listener that exposes a single multiaddr but uses another listener under the hood\ntype virtualListener struct {\n\t*listener\n\tudpAddr       string\n\tversion       quic.Version\n\tt             *transport\n\tacceptRunnner *acceptLoopRunner\n\tacceptChan    chan acceptVal\n}\n\nvar _ tpt.Listener = &virtualListener{}\n\nfunc (l *virtualListener) Multiaddr() ma.Multiaddr {\n\treturn l.listener.localMultiaddrs[l.version]\n}\n\nfunc (l *virtualListener) Close() error {\n\tl.acceptRunnner.RmAcceptForVersion(l.version, tpt.ErrListenerClosed)\n\treturn l.t.CloseVirtualListener(l)\n}\n\nfunc (l *virtualListener) Accept() (tpt.CapableConn, error) {\n\treturn l.acceptRunnner.Accept(l.listener, l.version, l.acceptChan)\n}\n\ntype acceptVal struct {\n\tconn tpt.CapableConn\n\terr  error\n}\n\ntype acceptLoopRunner struct {\n\tacceptSem chan struct{}\n\n\tmuxerMu     sync.Mutex\n\tmuxer       map[quic.Version]chan acceptVal\n\tmuxerClosed bool\n}\n\nfunc (r *acceptLoopRunner) AcceptForVersion(v quic.Version) chan acceptVal {\n\tr.muxerMu.Lock()\n\tdefer r.muxerMu.Unlock()\n\n\tch := make(chan acceptVal, acceptBufferPerVersion)\n\n\tif _, ok := r.muxer[v]; ok {\n\t\tpanic(\"unexpected chan already found in accept muxer\")\n\t}\n\n\tr.muxer[v] = ch\n\treturn ch\n}\n\nfunc (r *acceptLoopRunner) RmAcceptForVersion(v quic.Version, err error) {\n\tr.muxerMu.Lock()\n\tdefer r.muxerMu.Unlock()\n\n\tif r.muxerClosed {\n\t\t// Already closed, all versions are removed\n\t\treturn\n\t}\n\n\tch, ok := r.muxer[v]\n\tif !ok {\n\t\tpanic(\"expected chan in accept muxer\")\n\t}\n\tch <- acceptVal{err: err}\n\tdelete(r.muxer, v)\n}\n\nfunc (r *acceptLoopRunner) sendErrAndClose(err error) {\n\tr.muxerMu.Lock()\n\tdefer r.muxerMu.Unlock()\n\tr.muxerClosed = true\n\tfor k, ch := range r.muxer {\n\t\tselect {\n\t\tcase ch <- acceptVal{err: err}:\n\t\tdefault:\n\t\t}\n\t\tdelete(r.muxer, k)\n\t\tclose(ch)\n\t}\n}\n\n// innerAccept is the inner logic of the Accept loop. Assume caller holds the\n// acceptSemaphore. May return both a nil conn and nil error if it didn't find a\n// conn with the expected version\nfunc (r *acceptLoopRunner) innerAccept(l *listener, expectedVersion quic.Version, bufferedConnChan chan acceptVal) (tpt.CapableConn, error) {\n\tselect {\n\t// Check if we have a buffered connection first from an earlier Accept call\n\tcase v, ok := <-bufferedConnChan:\n\t\tif !ok {\n\t\t\treturn nil, tpt.ErrListenerClosed\n\t\t}\n\t\treturn v.conn, v.err\n\tdefault:\n\t}\n\n\tconn, err := l.Accept()\n\n\tif err != nil {\n\t\tr.sendErrAndClose(err)\n\t\treturn nil, err\n\t}\n\n\t_, version, err := quicreuse.FromQuicMultiaddr(conn.RemoteMultiaddr())\n\tif err != nil {\n\t\tr.sendErrAndClose(err)\n\t\treturn nil, err\n\t}\n\n\tif version == expectedVersion {\n\t\treturn conn, nil\n\t}\n\n\t// This wasn't the version we were expecting, lets queue it up for a\n\t// future Accept call with a different version\n\tr.muxerMu.Lock()\n\tch, ok := r.muxer[version]\n\tr.muxerMu.Unlock()\n\n\tif !ok {\n\t\t// Nothing to handle this connection version. Close it\n\t\tconn.Close()\n\t\treturn nil, nil\n\t}\n\n\t// Non blocking\n\tselect {\n\tcase ch <- acceptVal{conn: conn}:\n\tdefault:\n\t\tconn.CloseWithError(network.ConnRateLimited)\n\t\t// accept queue filled up, drop the connection\n\t\tlog.Warn(\"Accept queue filled. Dropping connection.\")\n\t}\n\n\treturn nil, nil\n}\n\nfunc (r *acceptLoopRunner) Accept(l *listener, expectedVersion quic.Version, bufferedConnChan chan acceptVal) (tpt.CapableConn, error) {\n\tfor {\n\t\tvar conn tpt.CapableConn\n\t\tvar err error\n\t\tselect {\n\t\tcase r.acceptSem <- struct{}{}:\n\t\t\tconn, err = r.innerAccept(l, expectedVersion, bufferedConnChan)\n\t\t\t<-r.acceptSem\n\n\t\t\tif conn == nil && err == nil {\n\t\t\t\t// Didn't find a conn for the expected version and there was no error, lets try again\n\t\t\t\tcontinue\n\t\t\t}\n\t\tcase v, ok := <-bufferedConnChan:\n\t\t\tif !ok {\n\t\t\t\treturn nil, tpt.ErrListenerClosed\n\t\t\t}\n\t\t\tconn = v.conn\n\t\t\terr = v.err\n\t\t}\n\t\treturn conn, err\n\t}\n}\n"
  },
  {
    "path": "p2p/transport/quicreuse/config.go",
    "content": "package quicreuse\n\nimport (\n\t\"time\"\n\n\t\"github.com/quic-go/quic-go\"\n)\n\nvar quicConfig = &quic.Config{\n\tMaxIncomingStreams:         256,\n\tMaxIncomingUniStreams:      5,              // allow some unidirectional streams, in case we speak WebTransport\n\tMaxStreamReceiveWindow:     10 * (1 << 20), // 10 MB\n\tMaxConnectionReceiveWindow: 15 * (1 << 20), // 15 MB\n\tKeepAlivePeriod:            15 * time.Second,\n\tVersions:                   []quic.Version{quic.Version1},\n\t// We don't use datagrams (yet), but this is necessary for WebTransport\n\tEnableDatagrams: true,\n\t// Required for WebTransport\n\tEnableStreamResetPartialDelivery: true,\n}\n"
  },
  {
    "path": "p2p/transport/quicreuse/connmgr.go",
    "content": "// Package quicreuse provides `quicreuse.ConnManager`, which provides functionality\n// for reusing QUIC transports for various purposes, like listening & dialing, having\n// multiple QUIC listeners on the same address with different ALPNs, and sharing the\n// same address with non QUIC transports like WebRTC.\n\npackage quicreuse\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"io\"\n\t\"net\"\n\t\"sync\"\n\n\t\"github.com/libp2p/go-libp2p/gologshim\"\n\t\"github.com/libp2p/go-netroute\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/quic-go/quic-go\"\n\t\"github.com/quic-go/quic-go/qlog\"\n\t\"golang.org/x/time/rate\"\n)\n\nvar log = gologshim.Logger(\"quicreuse\")\n\ntype QUICListener interface {\n\tAccept(ctx context.Context) (*quic.Conn, error)\n\tClose() error\n\tAddr() net.Addr\n}\n\nvar _ QUICListener = &quic.Listener{}\n\ntype QUICTransport interface {\n\tListen(tlsConf *tls.Config, conf *quic.Config) (QUICListener, error)\n\tDial(ctx context.Context, addr net.Addr, tlsConf *tls.Config, conf *quic.Config) (*quic.Conn, error)\n\tWriteTo(b []byte, addr net.Addr) (int, error)\n\tReadNonQUICPacket(ctx context.Context, b []byte) (int, net.Addr, error)\n\tio.Closer\n}\n\n// ConnManager enables QUIC and WebTransport transports to listen on the same port, reusing\n// listen addresses for dialing, and provides a PacketConn for sharing the listen address\n// with other protocols like WebRTC.\n// Reusing the listen address for dialing helps with address discovery and hole punching. For details\n// of the reuse logic see `ListenQUICAndAssociate` and `DialQUIC`.\n// If reuseport is disabled using the `DisableReuseport` option, listen addresses are not used for\n// dialing.\ntype ConnManager struct {\n\treuseUDP4       *reuse\n\treuseUDP6       *reuse\n\tenableReuseport bool\n\n\tlistenUDP          listenUDP\n\tsourceIPSelectorFn func() (SourceIPSelector, error)\n\n\tenableMetrics bool\n\tregisterer    prometheus.Registerer\n\n\tserverConfig *quic.Config\n\tclientConfig *quic.Config\n\n\tquicListenersMu sync.Mutex\n\tquicListeners   map[string]quicListenerEntry\n\n\tsrk         quic.StatelessResetKey\n\ttokenKey    quic.TokenGeneratorKey\n\tconnContext connContextFunc\n\n\tverifySourceAddress func(addr net.Addr) bool\n\n\tqlogTracerDir string\n}\n\ntype quicListenerEntry struct {\n\trefCount int\n\tln       *quicListener\n}\n\nfunc defaultListenUDP(network string, laddr *net.UDPAddr) (net.PacketConn, error) {\n\treturn net.ListenUDP(network, laddr)\n}\n\nfunc defaultSourceIPSelectorFn() (SourceIPSelector, error) {\n\tr, err := netroute.New()\n\treturn &netrouteSourceIPSelector{routes: r}, err\n}\n\nconst (\n\tunverifiedAddressNewConnectionRPS   = 1000\n\tunverifiedAddressNewConnectionBurst = 1000\n)\n\n// NewConnManager returns a new ConnManager\nfunc NewConnManager(statelessResetKey quic.StatelessResetKey, tokenKey quic.TokenGeneratorKey, opts ...Option) (*ConnManager, error) {\n\tcm := &ConnManager{\n\t\tenableReuseport:    true,\n\t\tquicListeners:      make(map[string]quicListenerEntry),\n\t\tsrk:                statelessResetKey,\n\t\ttokenKey:           tokenKey,\n\t\tregisterer:         prometheus.DefaultRegisterer,\n\t\tlistenUDP:          defaultListenUDP,\n\t\tsourceIPSelectorFn: defaultSourceIPSelectorFn,\n\t}\n\tfor _, o := range opts {\n\t\tif err := o(cm); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tquicConf := quicConfig.Clone()\n\t// quic-go takes care of disabling this if QLOGDIR Environment variable isn't present\n\tquicConf.Tracer = qlog.DefaultConnectionTracer\n\tserverConfig := quicConf.Clone()\n\n\tcm.clientConfig = quicConf\n\tcm.serverConfig = serverConfig\n\n\t// Verify source addresses when under high load.\n\t// This is ensures that the number of spoofed/unverified addresses that are passed to downstream rate limiters\n\t// are limited, which enables IP address based rate limiting.\n\tsourceAddrRateLimiter := rate.NewLimiter(unverifiedAddressNewConnectionRPS, unverifiedAddressNewConnectionBurst)\n\tvsa := cm.verifySourceAddress\n\tcm.verifySourceAddress = func(addr net.Addr) bool {\n\t\tif sourceAddrRateLimiter.Allow() {\n\t\t\tif vsa != nil {\n\t\t\t\treturn vsa(addr)\n\t\t\t}\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t}\n\tif cm.enableReuseport {\n\t\tcm.reuseUDP4 = newReuse(&statelessResetKey, &tokenKey, cm.listenUDP, cm.sourceIPSelectorFn, cm.connContext, cm.verifySourceAddress)\n\t\tcm.reuseUDP6 = newReuse(&statelessResetKey, &tokenKey, cm.listenUDP, cm.sourceIPSelectorFn, cm.connContext, cm.verifySourceAddress)\n\t}\n\treturn cm, nil\n}\n\nfunc (c *ConnManager) getReuse(network string) (*reuse, error) {\n\tswitch network {\n\tcase \"udp4\":\n\t\treturn c.reuseUDP4, nil\n\tcase \"udp6\":\n\t\treturn c.reuseUDP6, nil\n\tdefault:\n\t\treturn nil, errors.New(\"invalid network: must be either udp4 or udp6\")\n\t}\n}\n\n// LendTransport is an advanced method used to lend an existing QUICTransport\n// to the ConnManager. The ConnManager will close the returned channel when it\n// is done with the transport, so that the owner may safely close the transport.\nfunc (c *ConnManager) LendTransport(network string, tr QUICTransport, conn net.PacketConn) (<-chan struct{}, error) {\n\tc.quicListenersMu.Lock()\n\tdefer c.quicListenersMu.Unlock()\n\n\tlocalAddr, ok := conn.LocalAddr().(*net.UDPAddr)\n\tif !ok {\n\t\treturn nil, errors.New(\"expected a conn.LocalAddr() to return a *net.UDPAddr\")\n\t}\n\n\trefCountedTr := &refcountedTransport{\n\t\tQUICTransport:    tr,\n\t\tpacketConn:       conn,\n\t\tborrowDoneSignal: make(chan struct{}),\n\t}\n\n\tvar reuse *reuse\n\treuse, err := c.getReuse(network)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn refCountedTr.borrowDoneSignal, reuse.AddTransport(refCountedTr, localAddr)\n}\n\n// ListenQUIC listens for quic connections with the provided `tlsConf.NextProtos` ALPNs on `addr`. The same addr can be shared between\n// different ALPNs.\nfunc (c *ConnManager) ListenQUIC(addr ma.Multiaddr, tlsConf *tls.Config, allowWindowIncrease func(conn *quic.Conn, delta uint64) bool) (Listener, error) {\n\treturn c.ListenQUICAndAssociate(nil, addr, tlsConf, allowWindowIncrease)\n}\n\n// ListenQUICAndAssociate listens for quic connections with the provided `tlsConf.NextProtos` ALPNs on `addr`. The same addr can be shared between\n// different ALPNs.\n// The QUIC Transport used for listening is tagged with the `association`. Any subsequent `TransportWithAssociationForDial`,\n// or `DialQUIC` calls with the same `association` will reuse the QUIC Transport used by this method.\n// A common use of associations is to ensure /quic dials use the quic listening address and /webtransport dials use the\n// WebTransport listening address.\nfunc (c *ConnManager) ListenQUICAndAssociate(association any, addr ma.Multiaddr, tlsConf *tls.Config, allowWindowIncrease func(conn *quic.Conn, delta uint64) bool) (Listener, error) {\n\tnetw, host, err := manet.DialArgs(addr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tladdr, err := net.ResolveUDPAddr(netw, host)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tc.quicListenersMu.Lock()\n\tdefer c.quicListenersMu.Unlock()\n\n\tkey := laddr.String()\n\tentry, ok := c.quicListeners[key]\n\tif !ok {\n\t\ttr, err := c.transportForListen(netw, laddr)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tln, err := newQuicListener(tr, c.serverConfig)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tkey = tr.LocalAddr().String()\n\t\tentry = quicListenerEntry{ln: ln}\n\t}\n\tif c.enableReuseport && association != nil {\n\t\tif _, ok := entry.ln.transport.(*refcountedTransport); !ok {\n\t\t\tlog.Warn(\"reuseport is enabled, association is non-nil, but the transport is not a refcountedTransport.\")\n\t\t}\n\t}\n\tl, err := entry.ln.Add(association, tlsConf, allowWindowIncrease, func() {\n\t\tc.onListenerClosed(key)\n\t})\n\tif err != nil {\n\t\tif entry.refCount <= 0 {\n\t\t\tentry.ln.Close()\n\t\t}\n\t\treturn nil, err\n\t}\n\tentry.refCount++\n\tc.quicListeners[key] = entry\n\treturn l, nil\n}\n\nfunc (c *ConnManager) onListenerClosed(key string) {\n\tc.quicListenersMu.Lock()\n\tdefer c.quicListenersMu.Unlock()\n\n\tentry := c.quicListeners[key]\n\tentry.refCount = entry.refCount - 1\n\tif entry.refCount <= 0 {\n\t\tdelete(c.quicListeners, key)\n\t\tentry.ln.Close()\n\t} else {\n\t\tc.quicListeners[key] = entry\n\t}\n}\n\n// SharedNonQUICPacketConn returns a `net.PacketConn` for `laddr` for non QUIC uses.\nfunc (c *ConnManager) SharedNonQUICPacketConn(_ string, laddr *net.UDPAddr) (net.PacketConn, error) {\n\tc.quicListenersMu.Lock()\n\tdefer c.quicListenersMu.Unlock()\n\tkey := laddr.String()\n\tentry, ok := c.quicListeners[key]\n\tif !ok {\n\t\treturn nil, errors.New(\"expected to be able to share with a QUIC listener, but no QUIC listener found. The QUIC listener should start first\")\n\t}\n\tt := entry.ln.transport\n\tif t, ok := t.(*refcountedTransport); ok {\n\t\tt.IncreaseCount()\n\t\tctx, cancel := context.WithCancel(context.Background())\n\t\treturn &nonQUICPacketConn{\n\t\t\tctx:             ctx,\n\t\t\tctxCancel:       cancel,\n\t\t\towningTransport: t,\n\t\t\ttr:              t.QUICTransport,\n\t\t}, nil\n\t}\n\treturn nil, errors.New(\"expected to be able to share with a QUIC listener, but the QUIC listener is not using a refcountedTransport. `DisableReuseport` should not be set\")\n}\n\nfunc (c *ConnManager) transportForListen(network string, laddr *net.UDPAddr) (RefCountedQUICTransport, error) {\n\tif c.enableReuseport {\n\t\treuse, err := c.getReuse(network)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ttr, err := reuse.TransportForListen(network, laddr)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn tr, nil\n\t}\n\n\tconn, err := c.listenUDP(network, laddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn c.newSingleOwnerTransport(conn), nil\n}\n\ntype associationKey struct{}\n\n// WithAssociation returns a new context with the given association. Used in\n// DialQUIC to prefer a transport that has the given association.\nfunc WithAssociation(ctx context.Context, association any) context.Context {\n\treturn context.WithValue(ctx, associationKey{}, association)\n}\n\n// DialQUIC dials `raddr`. Use `WithAssociation` to select a specific transport that was previously used for listening.\n// see the documentation for `ListenQUICAndAssociate` for details on associate.\n// The priority order for reusing the transport is as follows:\n// - Listening transport with the same association\n// - Any other listening transport\n// - Any transport previously used for dialing\n// If none of these are available, it'll create a new transport.\nfunc (c *ConnManager) DialQUIC(ctx context.Context, raddr ma.Multiaddr, tlsConf *tls.Config, allowWindowIncrease func(conn *quic.Conn, delta uint64) bool) (*quic.Conn, error) {\n\tnaddr, v, err := FromQuicMultiaddr(raddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tnetw, _, err := manet.DialArgs(raddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tquicConf := c.clientConfig.Clone()\n\tquicConf.AllowConnectionWindowIncrease = allowWindowIncrease\n\n\tif v == quic.Version1 {\n\t\t// The endpoint has explicit support for QUIC v1, so we'll only use that version.\n\t\tquicConf.Versions = []quic.Version{quic.Version1}\n\t} else {\n\t\treturn nil, errors.New(\"unknown QUIC version\")\n\t}\n\n\tvar tr RefCountedQUICTransport\n\tassociation := ctx.Value(associationKey{})\n\ttr, err = c.TransportWithAssociationForDial(association, netw, naddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tconn, err := tr.Dial(ctx, naddr, tlsConf, quicConf)\n\tif err != nil {\n\t\ttr.DecreaseCount()\n\t\treturn nil, err\n\t}\n\treturn conn, nil\n}\n\n// TransportForDial returns a transport for dialing `raddr`.\n// If reuseport is enabled, it attempts to reuse the QUIC Transport used for\n// previous listens or dials.\nfunc (c *ConnManager) TransportForDial(network string, raddr *net.UDPAddr) (RefCountedQUICTransport, error) {\n\treturn c.TransportWithAssociationForDial(nil, network, raddr)\n}\n\n// TransportWithAssociationForDial returns a transport for dialing `raddr`.\n// If reuseport is enabled, it attempts to reuse the QUIC Transport previously used for listening with `ListenQuicAndAssociate`\n// with the same `association`. If it fails to do so, it uses any other previously used transport.\nfunc (c *ConnManager) TransportWithAssociationForDial(association any, network string, raddr *net.UDPAddr) (RefCountedQUICTransport, error) {\n\tif c.enableReuseport {\n\t\treuse, err := c.getReuse(network)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn reuse.TransportWithAssociationForDial(association, network, raddr)\n\t}\n\n\tvar laddr *net.UDPAddr\n\tswitch network {\n\tcase \"udp4\":\n\t\tladdr = &net.UDPAddr{IP: net.IPv4zero, Port: 0}\n\tcase \"udp6\":\n\t\tladdr = &net.UDPAddr{IP: net.IPv6zero, Port: 0}\n\t}\n\tconn, err := c.listenUDP(network, laddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn c.newSingleOwnerTransport(conn), nil\n}\n\nfunc (c *ConnManager) newSingleOwnerTransport(conn net.PacketConn) *singleOwnerTransport {\n\treturn &singleOwnerTransport{\n\t\tTransport: &wrappedQUICTransport{\n\t\t\tTransport: newQUICTransport(\n\t\t\t\tconn,\n\t\t\t\t&c.tokenKey,\n\t\t\t\t&c.srk,\n\t\t\t\tc.connContext,\n\t\t\t\tc.verifySourceAddress,\n\t\t\t),\n\t\t},\n\t\tpacketConn: conn}\n}\n\n// Protocols returns the supported QUIC protocols. The only supported protocol at the moment is /quic-v1.\nfunc (c *ConnManager) Protocols() []int {\n\treturn []int{ma.P_QUIC_V1}\n}\n\nfunc (c *ConnManager) Close() error {\n\tif !c.enableReuseport {\n\t\treturn nil\n\t}\n\tif err := c.reuseUDP6.Close(); err != nil {\n\t\treturn err\n\t}\n\treturn c.reuseUDP4.Close()\n}\n\nfunc (c *ConnManager) ClientConfig() *quic.Config {\n\treturn c.clientConfig\n}\n\n// wrappedQUICTransport wraps a `quic.Transport` to confirm to `QUICTransport`\ntype wrappedQUICTransport struct {\n\t*quic.Transport\n}\n\nvar _ QUICTransport = (*wrappedQUICTransport)(nil)\n\nfunc (t *wrappedQUICTransport) Listen(tlsConf *tls.Config, conf *quic.Config) (QUICListener, error) {\n\treturn t.Transport.Listen(tlsConf, conf)\n}\n\nfunc newQUICTransport(\n\tconn net.PacketConn,\n\ttokenGeneratorKey *quic.TokenGeneratorKey,\n\tstatelessResetKey *quic.StatelessResetKey,\n\tconnContext connContextFunc,\n\tverifySourceAddress func(addr net.Addr) bool,\n) *quic.Transport {\n\treturn &quic.Transport{\n\t\tConn:                conn,\n\t\tTokenGeneratorKey:   tokenGeneratorKey,\n\t\tStatelessResetKey:   statelessResetKey,\n\t\tConnContext:         connContext,\n\t\tVerifySourceAddress: verifySourceAddress,\n\t}\n}\n"
  },
  {
    "path": "p2p/transport/quicreuse/connmgr_test.go",
    "content": "package quicreuse\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"runtime\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\tlibp2ptls \"github.com/libp2p/go-libp2p/p2p/security/tls\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n\t\"github.com/quic-go/quic-go\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc checkClosed(t *testing.T, cm *ConnManager) {\n\tfor _, r := range []*reuse{cm.reuseUDP4, cm.reuseUDP6} {\n\t\tif r == nil {\n\t\t\tcontinue\n\t\t}\n\t\tr.mutex.Lock()\n\t\tfor _, tr := range r.globalListeners {\n\t\t\trequire.Zero(t, tr.GetCount())\n\t\t}\n\t\tfor _, trs := range r.unicast {\n\t\t\tfor _, tr := range trs {\n\t\t\t\trequire.Zero(t, tr.GetCount())\n\t\t\t}\n\t\t}\n\t\tr.mutex.Unlock()\n\t}\n\trequire.Eventually(t, func() bool { return !isGarbageCollectorRunning() }, 200*time.Millisecond, 10*time.Millisecond)\n}\n\nfunc TestListenOnSameProto(t *testing.T) {\n\tt.Run(\"with reuseport\", func(t *testing.T) {\n\t\ttestListenOnSameProto(t, true)\n\t})\n\n\tt.Run(\"without reuseport\", func(t *testing.T) {\n\t\ttestListenOnSameProto(t, false)\n\t})\n}\n\nfunc testListenOnSameProto(t *testing.T, enableReuseport bool) {\n\tvar opts []Option\n\tif !enableReuseport {\n\t\topts = append(opts, DisableReuseport())\n\t}\n\tcm, err := NewConnManager(quic.StatelessResetKey{}, quic.TokenGeneratorKey{}, opts...)\n\trequire.NoError(t, err)\n\tdefer checkClosed(t, cm)\n\tdefer cm.Close()\n\n\tconst alpn = \"proto\"\n\n\tln1, err := cm.ListenQUIC(ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1\"), &tls.Config{NextProtos: []string{alpn}}, nil)\n\trequire.NoError(t, err)\n\tdefer func() { _ = ln1.Close() }()\n\n\taddr := ma.StringCast(fmt.Sprintf(\"/ip4/127.0.0.1/udp/%d/quic-v1\", ln1.Addr().(*net.UDPAddr).Port))\n\t_, err = cm.ListenQUIC(addr, &tls.Config{NextProtos: []string{alpn}}, nil)\n\trequire.EqualError(t, err, \"already listening for protocol \"+alpn)\n\n\t// listening on a different address works\n\tln2, err := cm.ListenQUIC(ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1\"), &tls.Config{NextProtos: []string{alpn}}, nil)\n\trequire.NoError(t, err)\n\tdefer func() { _ = ln2.Close() }()\n}\n\n// The conn passed to quic-go should be a conn that quic-go can be\n// type-asserted to a UDPConn. That way, it can use all kinds of optimizations.\nfunc TestConnectionPassedToQUICForListening(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"skipping on windows. Windows doesn't support these optimizations\")\n\t}\n\tcm, err := NewConnManager(quic.StatelessResetKey{}, quic.TokenGeneratorKey{}, DisableReuseport())\n\trequire.NoError(t, err)\n\tdefer cm.Close()\n\n\traddr := ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1\")\n\n\tnaddr, _, err := FromQuicMultiaddr(raddr)\n\trequire.NoError(t, err)\n\tnetw, _, err := manet.DialArgs(raddr)\n\trequire.NoError(t, err)\n\n\t_, err = cm.ListenQUIC(raddr, &tls.Config{NextProtos: []string{\"proto\"}}, nil)\n\trequire.NoError(t, err)\n\tquicTr, err := cm.transportForListen(netw, naddr)\n\trequire.NoError(t, err)\n\tdefer quicTr.Close()\n\tif _, ok := quicTr.(*singleOwnerTransport).Transport.(*wrappedQUICTransport).Conn.(quic.OOBCapablePacketConn); !ok {\n\t\tt.Fatal(\"connection passed to quic-go cannot be type asserted to a *net.UDPConn\")\n\t}\n}\n\nfunc TestAcceptErrorGetCleanedUp(t *testing.T) {\n\traddr := ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1\")\n\n\tcm, err := NewConnManager(quic.StatelessResetKey{}, quic.TokenGeneratorKey{}, DisableReuseport())\n\trequire.NoError(t, err)\n\tdefer cm.Close()\n\n\toriginalNumberOfGoroutines := runtime.NumGoroutine()\n\tt.Log(\"num goroutines:\", originalNumberOfGoroutines)\n\n\t// This spawns a background goroutine for the listener\n\tl, err := cm.ListenQUIC(raddr, &tls.Config{NextProtos: []string{\"proto\"}}, nil)\n\trequire.NoError(t, err)\n\n\t// We spawned a goroutine for the listener\n\trequire.Greater(t, runtime.NumGoroutine(), originalNumberOfGoroutines)\n\tl.Close()\n\n\t// Now make sure we have less goroutines than before\n\t// Manually doing the same as require.Eventually, except avoiding adding a goroutine\n\tgoRoutinesCleanedUp := false\n\tfor range 50 {\n\t\tt.Log(\"num goroutines:\", runtime.NumGoroutine())\n\t\tif runtime.NumGoroutine() <= originalNumberOfGoroutines {\n\t\t\tgoRoutinesCleanedUp = true\n\t\t\tbreak\n\t\t}\n\t\ttime.Sleep(100 * time.Millisecond)\n\t}\n\n\trequire.True(t, goRoutinesCleanedUp, \"goroutines were not cleaned up\")\n}\n\n// The connection passed to quic-go needs to be type-assertable to a net.UDPConn,\n// in order to enable features like batch processing and ECN.\nfunc TestConnectionPassedToQUICForDialing(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"skipping on windows. Windows doesn't support these optimizations\")\n\t}\n\tfor _, reuse := range []bool{true, false} {\n\t\tt.Run(fmt.Sprintf(\"reuseport: %t\", reuse), func(t *testing.T) {\n\t\t\tvar cm *ConnManager\n\t\t\tvar err error\n\t\t\tif reuse {\n\t\t\t\tcm, err = NewConnManager(quic.StatelessResetKey{}, quic.TokenGeneratorKey{})\n\t\t\t} else {\n\t\t\t\tcm, err = NewConnManager(quic.StatelessResetKey{}, quic.TokenGeneratorKey{}, DisableReuseport())\n\t\t\t}\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer func() { _ = cm.Close() }()\n\n\t\t\traddr := ma.StringCast(\"/ip4/127.0.0.1/udp/1234/quic-v1\")\n\n\t\t\tnaddr, _, err := FromQuicMultiaddr(raddr)\n\t\t\trequire.NoError(t, err)\n\t\t\tnetw, _, err := manet.DialArgs(raddr)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tquicTr, err := cm.TransportForDial(netw, naddr)\n\n\t\t\trequire.NoError(t, err, \"dial error\")\n\t\t\tdefer func() { _ = quicTr.Close() }()\n\t\t\tif reuse {\n\t\t\t\tif _, ok := quicTr.(*refcountedTransport).QUICTransport.(*wrappedQUICTransport).Conn.(quic.OOBCapablePacketConn); !ok {\n\t\t\t\t\tt.Fatal(\"connection passed to quic-go cannot be type asserted to a *net.UDPConn\")\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif _, ok := quicTr.(*singleOwnerTransport).Transport.(*wrappedQUICTransport).Conn.(quic.OOBCapablePacketConn); !ok {\n\t\t\t\t\tt.Fatal(\"connection passed to quic-go cannot be type asserted to a *net.UDPConn\")\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc getTLSConfForProto(t *testing.T, alpn string) (peer.ID, *tls.Config) {\n\tt.Helper()\n\tpriv, _, err := crypto.GenerateEd25519Key(rand.Reader)\n\trequire.NoError(t, err)\n\tid, err := peer.IDFromPrivateKey(priv)\n\trequire.NoError(t, err)\n\t// We use the libp2p TLS certificate here, just because it's convenient.\n\tidentity, err := libp2ptls.NewIdentity(priv)\n\trequire.NoError(t, err)\n\tvar tlsConf tls.Config\n\ttlsConf.NextProtos = []string{alpn}\n\ttlsConf.GetConfigForClient = func(_ *tls.ClientHelloInfo) (*tls.Config, error) {\n\t\tc, _ := identity.ConfigForPeer(\"\")\n\t\tc.NextProtos = tlsConf.NextProtos\n\t\treturn c, nil\n\t}\n\treturn id, &tlsConf\n}\n\nfunc connectWithProtocol(t *testing.T, addr net.Addr, alpn string) (peer.ID, error) {\n\tt.Helper()\n\tclientKey, _, err := crypto.GenerateEd25519Key(rand.Reader)\n\trequire.NoError(t, err)\n\tclientIdentity, err := libp2ptls.NewIdentity(clientKey)\n\trequire.NoError(t, err)\n\ttlsConf, peerChan := clientIdentity.ConfigForPeer(\"\")\n\tcconn, err := net.ListenUDP(\"udp4\", nil)\n\ttlsConf.NextProtos = []string{alpn}\n\trequire.NoError(t, err)\n\tctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)\n\tc, err := quic.Dial(ctx, cconn, addr, tlsConf, nil)\n\tcancel()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer c.CloseWithError(0, \"\")\n\trequire.Equal(t, alpn, c.ConnectionState().TLS.NegotiatedProtocol)\n\tserverID, err := peer.IDFromPublicKey(<-peerChan)\n\trequire.NoError(t, err)\n\treturn serverID, nil\n}\n\nfunc TestListener(t *testing.T) {\n\tt.Run(\"with reuseport\", func(t *testing.T) {\n\t\ttestListener(t, true)\n\t})\n\n\tt.Run(\"without reuseport\", func(t *testing.T) {\n\t\ttestListener(t, false)\n\t})\n}\n\nfunc testListener(t *testing.T, enableReuseport bool) {\n\tvar opts []Option\n\tif !enableReuseport {\n\t\topts = append(opts, DisableReuseport())\n\t}\n\tcm, err := NewConnManager(quic.StatelessResetKey{}, quic.TokenGeneratorKey{}, opts...)\n\trequire.NoError(t, err)\n\n\tid1, tlsConf1 := getTLSConfForProto(t, \"proto1\")\n\tln1, err := cm.ListenQUIC(ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1\"), tlsConf1, nil)\n\trequire.NoError(t, err)\n\n\tid2, tlsConf2 := getTLSConfForProto(t, \"proto2\")\n\tln2, err := cm.ListenQUIC(\n\t\tma.StringCast(fmt.Sprintf(\"/ip4/127.0.0.1/udp/%d/quic-v1\", ln1.Addr().(*net.UDPAddr).Port)),\n\t\ttlsConf2,\n\t\tnil,\n\t)\n\trequire.NoError(t, err)\n\trequire.Equal(t, ln1.Addr(), ln2.Addr())\n\n\t// Test that the right certificate is served.\n\tid, err := connectWithProtocol(t, ln1.Addr(), \"proto1\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, id1, id)\n\tid, err = connectWithProtocol(t, ln1.Addr(), \"proto2\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, id2, id)\n\t// No such protocol registered.\n\t_, err = connectWithProtocol(t, ln1.Addr(), \"proto3\")\n\trequire.Error(t, err)\n\n\t// Now close the first listener to test that it's properly deregistered.\n\trequire.NoError(t, ln1.Close())\n\t_, err = connectWithProtocol(t, ln1.Addr(), \"proto1\")\n\trequire.Error(t, err)\n\t// connecting to the other listener should still be possible\n\tid, err = connectWithProtocol(t, ln1.Addr(), \"proto2\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, id2, id)\n\n\tln2.Close()\n\tcm.Close()\n\n\tcheckClosed(t, cm)\n}\n\nfunc TestExternalTransport(t *testing.T) {\n\tconn, err := net.ListenUDP(\"udp4\", &net.UDPAddr{IP: net.IPv4zero})\n\trequire.NoError(t, err)\n\tdefer conn.Close()\n\tport := conn.LocalAddr().(*net.UDPAddr).Port\n\ttr := &quic.Transport{Conn: conn}\n\tdefer tr.Close()\n\n\tcm, err := NewConnManager(quic.StatelessResetKey{}, quic.TokenGeneratorKey{})\n\trequire.NoError(t, err)\n\tdoneWithTr, err := cm.LendTransport(\"udp4\", &wrappedQUICTransport{tr}, conn)\n\trequire.NoError(t, err)\n\n\t// make sure this transport is used when listening on the same port\n\tln, err := cm.ListenQUICAndAssociate(\n\t\t\"quic\",\n\t\tma.StringCast(fmt.Sprintf(\"/ip4/0.0.0.0/udp/%d\", port)),\n\t\t&tls.Config{NextProtos: []string{\"libp2p\"}},\n\t\tfunc(*quic.Conn, uint64) bool { return false },\n\t)\n\trequire.NoError(t, err)\n\tdefer ln.Close()\n\trequire.Equal(t, port, ln.Addr().(*net.UDPAddr).Port)\n\n\t// make sure this transport is used when dialing out\n\tudpLn, err := net.ListenUDP(\"udp4\", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1)})\n\trequire.NoError(t, err)\n\tdefer udpLn.Close()\n\taddrChan := make(chan net.Addr, 1)\n\tgo func() {\n\t\t_, addr, _ := udpLn.ReadFrom(make([]byte, 2000))\n\t\taddrChan <- addr\n\t}()\n\tctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond)\n\tdefer cancel()\n\t_, err = cm.DialQUIC(\n\t\tctx,\n\t\tma.StringCast(fmt.Sprintf(\"/ip4/127.0.0.1/udp/%d/quic-v1\", udpLn.LocalAddr().(*net.UDPAddr).Port)),\n\t\t&tls.Config{NextProtos: []string{\"libp2p\"}},\n\t\tfunc(*quic.Conn, uint64) bool { return false },\n\t)\n\trequire.ErrorIs(t, err, context.DeadlineExceeded)\n\n\tselect {\n\tcase addr := <-addrChan:\n\t\trequire.Equal(t, port, addr.(*net.UDPAddr).Port)\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timeout\")\n\t}\n\n\tcm.Close()\n\tselect {\n\tcase <-doneWithTr:\n\tdefault:\n\t\tt.Fatal(\"doneWithTr not closed\")\n\t}\n}\n\nfunc TestAssociate(t *testing.T) {\n\ttestAssociate := func(lnAddr1, lnAddr2 ma.Multiaddr, dialAddr *net.UDPAddr) {\n\t\tcm, err := NewConnManager(quic.StatelessResetKey{}, quic.TokenGeneratorKey{})\n\t\trequire.NoError(t, err)\n\t\tdefer cm.Close()\n\n\t\tlp2pTLS := &tls.Config{NextProtos: []string{\"libp2p\"}}\n\t\tassoc1 := \"test-1\"\n\t\tln1, err := cm.ListenQUICAndAssociate(assoc1, lnAddr1, lp2pTLS, nil)\n\t\trequire.NoError(t, err)\n\t\tdefer ln1.Close()\n\t\taddrs := ln1.Multiaddrs()\n\t\trequire.Len(t, addrs, 1)\n\n\t\taddr := addrs[0]\n\t\tassoc2 := \"test-2\"\n\t\th3TLS := &tls.Config{NextProtos: []string{\"h3\"}}\n\t\tln2, err := cm.ListenQUICAndAssociate(assoc2, addr, h3TLS, nil)\n\t\trequire.NoError(t, err)\n\t\tdefer ln2.Close()\n\n\t\ttr1, err := cm.TransportWithAssociationForDial(assoc1, \"udp4\", dialAddr)\n\t\trequire.NoError(t, err)\n\t\tdefer tr1.Close()\n\t\trequire.Equal(t, tr1.LocalAddr().String(), ln1.Addr().String())\n\n\t\ttr2, err := cm.TransportWithAssociationForDial(assoc2, \"udp4\", dialAddr)\n\t\trequire.NoError(t, err)\n\t\tdefer tr2.Close()\n\t\trequire.Equal(t, tr2.LocalAddr().String(), ln2.Addr().String())\n\n\t\tln3, err := cm.ListenQUICAndAssociate(assoc1, lnAddr2, lp2pTLS, nil)\n\t\trequire.NoError(t, err)\n\t\tdefer ln3.Close()\n\n\t\t// an unused association should also return the same transport\n\t\t// association is only a preference for a specific transport, not an exclusion criteria\n\t\ttr3, err := cm.TransportWithAssociationForDial(\"unused\", \"udp4\", dialAddr)\n\t\trequire.NoError(t, err)\n\t\tdefer tr3.Close()\n\t\trequire.Contains(t, []string{ln2.Addr().String(), ln3.Addr().String()}, tr3.LocalAddr().String())\n\t}\n\n\tt.Run(\"MultipleUnspecifiedListeners\", func(_ *testing.T) {\n\t\ttestAssociate(ma.StringCast(\"/ip4/0.0.0.0/udp/0/quic-v1\"),\n\t\t\tma.StringCast(\"/ip4/0.0.0.0/udp/0/quic-v1\"),\n\t\t\t&net.UDPAddr{IP: net.IPv4(1, 1, 1, 1), Port: 1})\n\t})\n\tt.Run(\"MultipleSpecificListeners\", func(_ *testing.T) {\n\t\ttestAssociate(ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1\"),\n\t\t\tma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1\"),\n\t\t\t&net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1},\n\t\t)\n\t})\n}\n\nfunc TestConnContext(t *testing.T) {\n\tfor _, reuse := range []bool{true, false} {\n\t\tt.Run(fmt.Sprintf(\"reuseport:%t_error\", reuse), func(t *testing.T) {\n\t\t\topts := []Option{\n\t\t\t\tConnContext(func(ctx context.Context, _ *quic.ClientInfo) (context.Context, error) {\n\t\t\t\t\treturn ctx, errors.New(\"test error\")\n\t\t\t\t})}\n\t\t\tif !reuse {\n\t\t\t\topts = append(opts, DisableReuseport())\n\t\t\t}\n\t\t\tcm, err := NewConnManager(\n\t\t\t\tquic.StatelessResetKey{},\n\t\t\t\tquic.TokenGeneratorKey{},\n\t\t\t\topts...,\n\t\t\t)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer func() { _ = cm.Close() }()\n\n\t\t\tproto1 := \"proto1\"\n\t\t\t_, proto1TLS := getTLSConfForProto(t, proto1)\n\t\t\tln1, err := cm.ListenQUIC(\n\t\t\t\tma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1\"),\n\t\t\t\tproto1TLS,\n\t\t\t\tnil,\n\t\t\t)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer ln1.Close()\n\t\t\tproto2 := \"proto2\"\n\t\t\t_, proto2TLS := getTLSConfForProto(t, proto2)\n\t\t\tln2, err := cm.ListenQUIC(\n\t\t\t\tma.StringCast(fmt.Sprintf(\"/ip4/127.0.0.1/udp/%d/quic-v1\", ln1.Addr().(*net.UDPAddr).Port)),\n\t\t\t\tproto2TLS,\n\t\t\t\tnil,\n\t\t\t)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer ln2.Close()\n\n\t\t\t_, err = connectWithProtocol(t, ln1.Addr(), proto1)\n\t\t\trequire.ErrorContains(t, err, \"CONNECTION_REFUSED\")\n\n\t\t\t_, err = connectWithProtocol(t, ln1.Addr(), proto2)\n\t\t\trequire.ErrorContains(t, err, \"CONNECTION_REFUSED\")\n\t\t})\n\t\tt.Run(fmt.Sprintf(\"reuseport:%t_success\", reuse), func(t *testing.T) {\n\t\t\ttype ctxKey struct{}\n\t\t\topts := []Option{\n\t\t\t\tConnContext(func(ctx context.Context, _ *quic.ClientInfo) (context.Context, error) {\n\t\t\t\t\treturn context.WithValue(ctx, ctxKey{}, \"success\"), nil\n\t\t\t\t})}\n\t\t\tif !reuse {\n\t\t\t\topts = append(opts, DisableReuseport())\n\t\t\t}\n\t\t\tcm, err := NewConnManager(\n\t\t\t\tquic.StatelessResetKey{},\n\t\t\t\tquic.TokenGeneratorKey{},\n\t\t\t\topts...,\n\t\t\t)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer func() { _ = cm.Close() }()\n\n\t\t\tproto1 := \"proto1\"\n\t\t\t_, proto1TLS := getTLSConfForProto(t, proto1)\n\t\t\tln1, err := cm.ListenQUIC(\n\t\t\t\tma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1\"),\n\t\t\t\tproto1TLS,\n\t\t\t\tnil,\n\t\t\t)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer ln1.Close()\n\n\t\t\tclientKey, _, err := crypto.GenerateEd25519Key(rand.Reader)\n\t\t\trequire.NoError(t, err)\n\t\t\tclientIdentity, err := libp2ptls.NewIdentity(clientKey)\n\t\t\trequire.NoError(t, err)\n\t\t\ttlsConf, peerChan := clientIdentity.ConfigForPeer(\"\")\n\t\t\tcconn, err := net.ListenUDP(\"udp4\", nil)\n\t\t\ttlsConf.NextProtos = []string{proto1}\n\t\t\trequire.NoError(t, err)\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)\n\t\t\tconn, err := quic.Dial(ctx, cconn, ln1.Addr(), tlsConf, nil)\n\t\t\tcancel()\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer conn.CloseWithError(0, \"\")\n\n\t\t\trequire.Equal(t, proto1, conn.ConnectionState().TLS.NegotiatedProtocol)\n\t\t\t_, err = peer.IDFromPublicKey(<-peerChan)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tacceptCtx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\t\t\tc, err := ln1.Accept(acceptCtx)\n\t\t\tcancel()\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer c.CloseWithError(0, \"\")\n\n\t\t\trequire.Equal(t, \"success\", c.Context().Value(ctxKey{}))\n\t\t})\n\t}\n}\n\nfunc TestAssociationCleanup(t *testing.T) {\n\tcm, err := NewConnManager(quic.StatelessResetKey{}, quic.TokenGeneratorKey{})\n\trequire.NoError(t, err)\n\tdefer cm.Close()\n\n\t// Create 3 listeners with 3 different associations\n\tlp2pTLS := &tls.Config{NextProtos: []string{\"libp2p\"}}\n\tassoc1 := \"test-association-1\"\n\tassoc2 := \"test-association-2\"\n\tassoc3 := \"test-association-3\"\n\n\tln1, err := cm.ListenQUICAndAssociate(assoc1, ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1\"), lp2pTLS, nil)\n\trequire.NoError(t, err)\n\tdefer ln1.Close()\n\n\taddr := ln1.Multiaddrs()[0]\n\tport, err := addr.ValueForProtocol(ma.P_UDP)\n\trequire.NoError(t, err)\n\n\th3TLS := &tls.Config{NextProtos: []string{\"h3\"}}\n\tln2, err := cm.ListenQUICAndAssociate(assoc2, ma.StringCast(fmt.Sprintf(\"/ip4/127.0.0.1/udp/%s/quic-v1\", port)), h3TLS, nil)\n\trequire.NoError(t, err)\n\tdefer ln2.Close()\n\n\tln3, err := cm.ListenQUICAndAssociate(assoc3, ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1\"), lp2pTLS, nil)\n\trequire.NoError(t, err)\n\tdefer ln3.Close()\n\n\t// Get the listen addresses for verification\n\taddr1 := ln1.Addr().String()\n\taddr2 := ln2.Addr().String()\n\taddr3 := ln3.Addr().String()\n\trequire.Equal(t, addr1, addr2)\n\n\t// Test that dialing with assoc1 uses the first listener's address\n\tdialAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1234}\n\n\tnumTries := 100\n\n\tfor range numTries {\n\t\ttr, err := cm.TransportWithAssociationForDial(assoc1, \"udp4\", dialAddr)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, addr1, tr.LocalAddr().String(), \"assoc1 should use addr1\")\n\t}\n\n\t// Close the first listener\n\tln1.Close()\n\n\t// Call TransportWithAssociationForDial 10 times with assoc1 and check if we get at least one different address\n\tfoundDifferentAddr := false\n\tfor range numTries {\n\t\ttr, err := cm.TransportWithAssociationForDial(assoc1, \"udp4\", dialAddr)\n\t\trequire.NoError(t, err)\n\t\tactualAddr := tr.LocalAddr().String()\n\t\tif actualAddr != addr1 {\n\t\t\tfoundDifferentAddr = true\n\t\t\tbreak\n\t\t}\n\t}\n\trequire.True(t, foundDifferentAddr, \"assoc1 should use a different address than addr1 at least once after ln1 is closed\")\n\n\tfor range numTries {\n\t\t// Test that dialing with assoc2 still uses the second listener's address\n\t\ttr2Still, err := cm.TransportWithAssociationForDial(assoc2, \"udp4\", dialAddr)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, addr2, tr2Still.LocalAddr().String(), \"assoc2 should still use addr2\")\n\t}\n\n\t// Close the second listener\n\tln2.Close()\n\n\t// Call TransportWithAssociationForDial 10 times with assoc2 and check if we get at least one different address\n\tfoundDifferentAddr2 := false\n\tfor range numTries {\n\t\ttr, err := cm.TransportWithAssociationForDial(assoc2, \"udp4\", dialAddr)\n\t\trequire.NoError(t, err)\n\t\tactualAddr := tr.LocalAddr().String()\n\t\tif actualAddr != addr2 {\n\t\t\tfoundDifferentAddr2 = true\n\t\t}\n\t}\n\trequire.True(t, foundDifferentAddr2, \"assoc2 should use a different address than addr2 at least once after ln2 is closed\")\n\n\tfor range numTries {\n\t\t// Test that dialing with assoc3 still uses the third listener's address\n\t\ttr3Still, err := cm.TransportWithAssociationForDial(assoc3, \"udp4\", dialAddr)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, addr3, tr3Still.LocalAddr().String(), \"assoc3 should still use addr3\")\n\t}\n}\n\nfunc TestConnManagerIsolation(t *testing.T) {\n\t// Create two separate ConnManager instances\n\tcm1, err := NewConnManager(quic.StatelessResetKey{}, quic.TokenGeneratorKey{})\n\trequire.NoError(t, err)\n\tdefer cm1.Close()\n\n\tcm2, err := NewConnManager(quic.StatelessResetKey{}, quic.TokenGeneratorKey{})\n\trequire.NoError(t, err)\n\tdefer cm2.Close()\n\n\t// Create listeners in both ConnManagers\n\tlp2pTLS := &tls.Config{NextProtos: []string{\"libp2p\"}}\n\tassoc1 := \"cm1-association\"\n\tassoc2 := \"cm2-association\"\n\n\tln1, err := cm1.ListenQUICAndAssociate(assoc1, ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1\"), lp2pTLS, nil)\n\trequire.NoError(t, err)\n\tdefer ln1.Close()\n\n\tln2, err := cm2.ListenQUICAndAssociate(assoc2, ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1\"), lp2pTLS, nil)\n\trequire.NoError(t, err)\n\tdefer ln2.Close()\n\n\t// Verify that each ConnManager has its own isolated associations\n\n\t// Verify associations are isolated\n\tcm1.quicListenersMu.Lock()\n\tkey1 := ln1.Addr().String()\n\tentry1 := cm1.quicListeners[key1]\n\ttr1, ok := entry1.ln.transport.(*refcountedTransport)\n\trequire.True(t, ok)\n\trequire.True(t, tr1.hasAssociation(assoc1))\n\trequire.False(t, tr1.hasAssociation(assoc2))\n\tcm1.quicListenersMu.Unlock()\n\n\tcm2.quicListenersMu.Lock()\n\tkey2 := ln2.Addr().String()\n\tentry2 := cm2.quicListeners[key2]\n\ttr2, ok := entry2.ln.transport.(*refcountedTransport)\n\trequire.True(t, ok)\n\trequire.True(t, tr2.hasAssociation(assoc2))\n\trequire.False(t, tr2.hasAssociation(assoc1))\n\tcm2.quicListenersMu.Unlock()\n}\n"
  },
  {
    "path": "p2p/transport/quicreuse/listener.go",
    "content": "package quicreuse\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/transport\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/quic-go/quic-go\"\n)\n\ntype Listener interface {\n\tAccept(context.Context) (*quic.Conn, error)\n\tAddr() net.Addr\n\tMultiaddrs() []ma.Multiaddr\n\tio.Closer\n}\n\ntype protoConf struct {\n\tln                  *listener\n\ttlsConf             *tls.Config\n\tallowWindowIncrease func(conn *quic.Conn, delta uint64) bool\n}\n\ntype quicListener struct {\n\tl         QUICListener\n\ttransport RefCountedQUICTransport\n\trunning   chan struct{}\n\taddrs     []ma.Multiaddr\n\n\tprotocolsMu sync.Mutex\n\tprotocols   map[string]protoConf\n}\n\nfunc newQuicListener(tr RefCountedQUICTransport, quicConfig *quic.Config) (*quicListener, error) {\n\tlocalMultiaddrs := make([]ma.Multiaddr, 0, 2)\n\ta, err := ToQuicMultiaddr(tr.LocalAddr(), quic.Version1)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tlocalMultiaddrs = append(localMultiaddrs, a)\n\tcl := &quicListener{\n\t\tprotocols: map[string]protoConf{},\n\t\trunning:   make(chan struct{}),\n\t\ttransport: tr,\n\t\taddrs:     localMultiaddrs,\n\t}\n\ttlsConf := &tls.Config{\n\t\tSessionTicketsDisabled: true, // This is set for the config for client, but we set it here as well: https://github.com/quic-go/quic-go/issues/4029\n\t\tGetConfigForClient: func(info *tls.ClientHelloInfo) (*tls.Config, error) {\n\t\t\tcl.protocolsMu.Lock()\n\t\t\tdefer cl.protocolsMu.Unlock()\n\t\t\tfor _, proto := range info.SupportedProtos {\n\t\t\t\tif entry, ok := cl.protocols[proto]; ok {\n\t\t\t\t\tconf := entry.tlsConf\n\t\t\t\t\tif conf.GetConfigForClient != nil {\n\t\t\t\t\t\treturn conf.GetConfigForClient(info)\n\t\t\t\t\t}\n\t\t\t\t\treturn conf, nil\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil, fmt.Errorf(\"no supported protocol found. offered: %+v\", info.SupportedProtos)\n\t\t},\n\t}\n\tquicConf := quicConfig.Clone()\n\tquicConf.AllowConnectionWindowIncrease = cl.allowWindowIncrease\n\tln, err := tr.Listen(tlsConf, quicConf)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcl.l = ln\n\tgo cl.Run() // This go routine shuts down once the underlying quic.Listener is closed (or returns an error).\n\treturn cl, nil\n}\n\nfunc (l *quicListener) allowWindowIncrease(conn *quic.Conn, delta uint64) bool {\n\tl.protocolsMu.Lock()\n\tdefer l.protocolsMu.Unlock()\n\n\tconf, ok := l.protocols[conn.ConnectionState().TLS.NegotiatedProtocol]\n\tif !ok {\n\t\treturn false\n\t}\n\treturn conf.allowWindowIncrease(conn, delta)\n}\n\nfunc (l *quicListener) Add(association any, tlsConf *tls.Config, allowWindowIncrease func(conn *quic.Conn, delta uint64) bool, onRemove func()) (*listener, error) {\n\tl.protocolsMu.Lock()\n\tdefer l.protocolsMu.Unlock()\n\n\tif len(tlsConf.NextProtos) == 0 {\n\t\treturn nil, errors.New(\"no ALPN found in tls.Config\")\n\t}\n\n\tfor _, proto := range tlsConf.NextProtos {\n\t\tif _, ok := l.protocols[proto]; ok {\n\t\t\treturn nil, fmt.Errorf(\"already listening for protocol %s\", proto)\n\t\t}\n\t}\n\n\tln := &listener{\n\t\tqueue:             make(chan *quic.Conn, queueLen),\n\t\tacceptLoopRunning: l.running,\n\t\taddr:              l.l.Addr(),\n\t\taddrs:             l.addrs,\n\t}\n\tif association != nil {\n\t\tif tr, ok := l.transport.(*refcountedTransport); ok {\n\t\t\ttr.associateForListener(association, ln)\n\t\t}\n\t}\n\n\tln.remove = func() {\n\t\tif association != nil {\n\t\t\tif tr, ok := l.transport.(*refcountedTransport); ok {\n\t\t\t\ttr.RemoveAssociationsForListener(ln)\n\t\t\t}\n\t\t}\n\t\tl.protocolsMu.Lock()\n\t\tfor _, proto := range tlsConf.NextProtos {\n\t\t\tdelete(l.protocols, proto)\n\t\t}\n\t\tl.protocolsMu.Unlock()\n\t\tonRemove()\n\t}\n\n\tfor _, proto := range tlsConf.NextProtos {\n\t\tl.protocols[proto] = protoConf{\n\t\t\tln:                  ln,\n\t\t\ttlsConf:             tlsConf,\n\t\t\tallowWindowIncrease: allowWindowIncrease,\n\t\t}\n\t}\n\treturn ln, nil\n}\n\nfunc (l *quicListener) Run() error {\n\tdefer close(l.running)\n\tdefer l.transport.DecreaseCount()\n\tfor {\n\t\tconn, err := l.l.Accept(context.Background())\n\t\tif err != nil {\n\t\t\tif errors.Is(err, quic.ErrServerClosed) || strings.Contains(err.Error(), \"use of closed network connection\") {\n\t\t\t\treturn transport.ErrListenerClosed\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\t\tproto := conn.ConnectionState().TLS.NegotiatedProtocol\n\n\t\tl.protocolsMu.Lock()\n\t\tln, ok := l.protocols[proto]\n\t\tif !ok {\n\t\t\tl.protocolsMu.Unlock()\n\t\t\treturn fmt.Errorf(\"negotiated unknown protocol: %s\", proto)\n\t\t}\n\t\tln.ln.add(conn)\n\t\tl.protocolsMu.Unlock()\n\t}\n}\n\nfunc (l *quicListener) Close() error {\n\terr := l.l.Close()\n\t<-l.running // wait for Run to return\n\treturn err\n}\n\nconst queueLen = 16\n\n// A listener for a single ALPN protocol (set).\ntype listener struct {\n\tqueue             chan *quic.Conn\n\tacceptLoopRunning chan struct{}\n\taddr              net.Addr\n\taddrs             []ma.Multiaddr\n\tremove            func()\n\tcloseOnce         sync.Once\n}\n\nvar _ Listener = &listener{}\n\nfunc (l *listener) add(c *quic.Conn) {\n\tselect {\n\tcase l.queue <- c:\n\tdefault:\n\t\tc.CloseWithError(1, \"queue full\")\n\t}\n}\n\nfunc (l *listener) Accept(ctx context.Context) (*quic.Conn, error) {\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn nil, ctx.Err()\n\tcase <-l.acceptLoopRunning:\n\t\treturn nil, transport.ErrListenerClosed\n\tcase c, ok := <-l.queue:\n\t\tif !ok {\n\t\t\treturn nil, transport.ErrListenerClosed\n\t\t}\n\t\treturn c, nil\n\t}\n}\n\nfunc (l *listener) Addr() net.Addr {\n\treturn l.addr\n}\n\nfunc (l *listener) Multiaddrs() []ma.Multiaddr {\n\treturn l.addrs\n}\n\nfunc (l *listener) Close() error {\n\tl.closeOnce.Do(func() {\n\t\tl.remove()\n\t\tclose(l.queue)\n\t\t// drain the queue\n\t\tfor conn := range l.queue {\n\t\t\tconn.CloseWithError(quic.ApplicationErrorCode(network.ConnShutdown), \"closing\")\n\t\t}\n\t})\n\treturn nil\n}\n"
  },
  {
    "path": "p2p/transport/quicreuse/nonquic_packetconn.go",
    "content": "package quicreuse\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"time\"\n)\n\n// nonQUICPacketConn is a net.PacketConn that can be used to read and write\n// non-QUIC packets on a quic.Transport. This lets us reuse this UDP port for\n// other transports like WebRTC.\ntype nonQUICPacketConn struct {\n\towningTransport RefCountedQUICTransport\n\ttr              QUICTransport\n\tctx             context.Context\n\tctxCancel       context.CancelFunc\n\treadCtx         context.Context\n\treadCancel      context.CancelFunc\n}\n\n// Close implements net.PacketConn.\nfunc (n *nonQUICPacketConn) Close() error {\n\tn.ctxCancel()\n\n\t// Don't actually close the underlying transport since someone else might be using it.\n\t// reuse has it's own gc to close unused transports.\n\tn.owningTransport.DecreaseCount()\n\treturn nil\n}\n\n// LocalAddr implements net.PacketConn.\nfunc (n *nonQUICPacketConn) LocalAddr() net.Addr {\n\treturn n.owningTransport.LocalAddr()\n}\n\n// ReadFrom implements net.PacketConn.\nfunc (n *nonQUICPacketConn) ReadFrom(p []byte) (int, net.Addr, error) {\n\tctx := n.readCtx\n\tif ctx == nil {\n\t\tctx = n.ctx\n\t}\n\treturn n.tr.ReadNonQUICPacket(ctx, p)\n}\n\n// SetDeadline implements net.PacketConn.\nfunc (n *nonQUICPacketConn) SetDeadline(t time.Time) error {\n\t// Only used for reads.\n\treturn n.SetReadDeadline(t)\n}\n\n// SetReadDeadline implements net.PacketConn.\nfunc (n *nonQUICPacketConn) SetReadDeadline(t time.Time) error {\n\tif t.IsZero() && n.readCtx != nil {\n\t\tn.readCancel()\n\t\tn.readCtx = nil\n\t}\n\tn.readCtx, n.readCancel = context.WithDeadline(n.ctx, t)\n\treturn nil\n}\n\n// SetWriteDeadline implements net.PacketConn.\nfunc (n *nonQUICPacketConn) SetWriteDeadline(_ time.Time) error {\n\t// Unused. quic-go doesn't support deadlines for writes.\n\treturn nil\n}\n\n// WriteTo implements net.PacketConn.\nfunc (n *nonQUICPacketConn) WriteTo(p []byte, addr net.Addr) (int, error) {\n\treturn n.tr.WriteTo(p, addr)\n}\n\nvar _ net.PacketConn = &nonQUICPacketConn{}\n"
  },
  {
    "path": "p2p/transport/quicreuse/options.go",
    "content": "package quicreuse\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net\"\n\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/quic-go/quic-go\"\n)\n\ntype Option func(*ConnManager) error\n\ntype listenUDP func(network string, laddr *net.UDPAddr) (net.PacketConn, error)\n\nfunc OverrideListenUDP(f listenUDP) Option {\n\treturn func(m *ConnManager) error {\n\t\tm.listenUDP = f\n\t\treturn nil\n\t}\n}\n\nfunc OverrideSourceIPSelector(f func() (SourceIPSelector, error)) Option {\n\treturn func(m *ConnManager) error {\n\t\tm.sourceIPSelectorFn = f\n\t\treturn nil\n\t}\n}\n\nfunc WithQlogTracerDir(dir string) Option {\n\treturn func(m *ConnManager) error {\n\t\tm.qlogTracerDir = dir\n\t\treturn nil\n\t}\n}\n\nfunc DisableReuseport() Option {\n\treturn func(m *ConnManager) error {\n\t\tm.enableReuseport = false\n\t\treturn nil\n\t}\n}\n\n// ConnContext sets the context for all connections accepted by listeners. This doesn't affect the\n// context for dialed connections. To reject a connection, return a non nil error.\nfunc ConnContext(f func(ctx context.Context, clientInfo *quic.ClientInfo) (context.Context, error)) Option {\n\treturn func(m *ConnManager) error {\n\t\tif m.connContext != nil {\n\t\t\treturn errors.New(\"cannot set ConnContext more than once\")\n\t\t}\n\t\tm.connContext = f\n\t\treturn nil\n\t}\n}\n\n// VerifySourceAddress returns whether to verify the source address for incoming connection requests.\n// For more details see: `quic.Transport.VerifySourceAddress`\nfunc VerifySourceAddress(f func(addr net.Addr) bool) Option {\n\treturn func(m *ConnManager) error {\n\t\tm.verifySourceAddress = f\n\t\treturn nil\n\t}\n}\n\n// EnableMetrics enables Prometheus metrics collection. If reg is nil,\n// prometheus.DefaultRegisterer will be used as the registerer.\nfunc EnableMetrics(reg prometheus.Registerer) Option {\n\treturn func(m *ConnManager) error {\n\t\tm.enableMetrics = true\n\t\tif reg != nil {\n\t\t\tm.registerer = reg\n\t\t}\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "p2p/transport/quicreuse/quic_multiaddr.go",
    "content": "package quicreuse\n\nimport (\n\t\"errors\"\n\t\"net\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n\t\"github.com/quic-go/quic-go\"\n)\n\nvar (\n\tquicV1MA = ma.StringCast(\"/quic-v1\")\n)\n\nfunc ToQuicMultiaddr(na net.Addr, version quic.Version) (ma.Multiaddr, error) {\n\tudpMA, err := manet.FromNetAddr(na)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tswitch version {\n\tcase quic.Version1:\n\t\treturn udpMA.Encapsulate(quicV1MA), nil\n\tdefault:\n\t\treturn nil, errors.New(\"unknown QUIC version\")\n\t}\n}\n\nfunc FromQuicMultiaddr(addr ma.Multiaddr) (*net.UDPAddr, quic.Version, error) {\n\tvar version quic.Version\n\tpartsBeforeQUIC := make([]ma.Component, 0, 2)\nloop:\n\tfor _, c := range addr {\n\t\tswitch c.Protocol().Code {\n\t\tcase ma.P_QUIC_V1:\n\t\t\tversion = quic.Version1\n\t\t\tbreak loop\n\t\tdefault:\n\t\t\tpartsBeforeQUIC = append(partsBeforeQUIC, c)\n\t\t}\n\t}\n\tif len(partsBeforeQUIC) == 0 {\n\t\treturn nil, version, errors.New(\"no addr before QUIC component\")\n\t}\n\tif version == 0 {\n\t\t// Not found\n\t\treturn nil, version, errors.New(\"unknown QUIC version\")\n\t}\n\tnetAddr, err := manet.ToNetAddr(partsBeforeQUIC)\n\tif err != nil {\n\t\treturn nil, version, err\n\t}\n\tudpAddr, ok := netAddr.(*net.UDPAddr)\n\tif !ok {\n\t\treturn nil, 0, errors.New(\"not a *net.UDPAddr\")\n\t}\n\treturn udpAddr, version, nil\n}\n"
  },
  {
    "path": "p2p/transport/quicreuse/quic_multiaddr_test.go",
    "content": "package quicreuse\n\nimport (\n\t\"net\"\n\t\"testing\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/quic-go/quic-go\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestConvertToQuicMultiaddr(t *testing.T) {\n\taddr := &net.UDPAddr{IP: net.IPv4(192, 168, 0, 42), Port: 1337}\n\tmaddr, err := ToQuicMultiaddr(addr, quic.Version1)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"/ip4/192.168.0.42/udp/1337/quic-v1\", maddr.String())\n}\n\nfunc TestConvertToQuicV1Multiaddr(t *testing.T) {\n\taddr := &net.UDPAddr{IP: net.IPv4(192, 168, 0, 42), Port: 1337}\n\tmaddr, err := ToQuicMultiaddr(addr, quic.Version1)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"/ip4/192.168.0.42/udp/1337/quic-v1\", maddr.String())\n}\n\nfunc TestConvertFromQuicV1Multiaddr(t *testing.T) {\n\tmaddr, err := ma.NewMultiaddr(\"/ip4/192.168.0.42/udp/1337/quic-v1\")\n\trequire.NoError(t, err)\n\tudpAddr, v, err := FromQuicMultiaddr(maddr)\n\trequire.NoError(t, err)\n\trequire.Equal(t, net.IPv4(192, 168, 0, 42), udpAddr.IP)\n\trequire.Equal(t, 1337, udpAddr.Port)\n\trequire.Equal(t, quic.Version1, v)\n}\n"
  },
  {
    "path": "p2p/transport/quicreuse/reuse.go",
    "content": "package quicreuse\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-netroute\"\n\t\"github.com/quic-go/quic-go\"\n)\n\ntype RefCountedQUICTransport interface {\n\tLocalAddr() net.Addr\n\n\t// Used to send packets directly around QUIC. Useful for hole punching.\n\tWriteTo([]byte, net.Addr) (int, error)\n\n\tClose() error\n\n\t// count transport reference\n\tDecreaseCount()\n\tIncreaseCount()\n\n\tDial(ctx context.Context, addr net.Addr, tlsConf *tls.Config, conf *quic.Config) (*quic.Conn, error)\n\tListen(tlsConf *tls.Config, conf *quic.Config) (QUICListener, error)\n}\n\ntype singleOwnerTransport struct {\n\tTransport QUICTransport\n\n\t// Used to write packets directly around QUIC.\n\tpacketConn net.PacketConn\n}\n\nvar _ QUICTransport = &singleOwnerTransport{}\nvar _ RefCountedQUICTransport = (*singleOwnerTransport)(nil)\n\nfunc (c *singleOwnerTransport) IncreaseCount() {}\nfunc (c *singleOwnerTransport) DecreaseCount() { c.Transport.Close() }\nfunc (c *singleOwnerTransport) LocalAddr() net.Addr {\n\treturn c.packetConn.LocalAddr()\n}\n\nfunc (c *singleOwnerTransport) Dial(ctx context.Context, addr net.Addr, tlsConf *tls.Config, conf *quic.Config) (*quic.Conn, error) {\n\treturn c.Transport.Dial(ctx, addr, tlsConf, conf)\n}\n\nfunc (c *singleOwnerTransport) ReadNonQUICPacket(ctx context.Context, b []byte) (int, net.Addr, error) {\n\treturn c.Transport.ReadNonQUICPacket(ctx, b)\n}\n\nfunc (c *singleOwnerTransport) Close() error {\n\treturn errors.Join(c.Transport.Close(), c.packetConn.Close())\n}\n\nfunc (c *singleOwnerTransport) WriteTo(b []byte, addr net.Addr) (int, error) {\n\treturn c.Transport.WriteTo(b, addr)\n}\n\nfunc (c *singleOwnerTransport) Listen(tlsConf *tls.Config, conf *quic.Config) (QUICListener, error) {\n\treturn c.Transport.Listen(tlsConf, conf)\n}\n\n// Constant. Defined as variables to simplify testing.\nvar (\n\tgarbageCollectInterval = 30 * time.Second\n\tmaxUnusedDuration      = 10 * time.Second\n)\n\ntype refcountedTransport struct {\n\tQUICTransport\n\n\t// Used to write packets directly around QUIC.\n\tpacketConn net.PacketConn\n\n\tmutex       sync.Mutex\n\trefCount    int\n\tunusedSince time.Time\n\n\t// Only set for transports we are borrowing.\n\t// If set, we will _never_ close the underlying transport. We only close this\n\t// channel to signal to the owner that we are done with it.\n\tborrowDoneSignal chan struct{}\n\n\t// Store associations as association -> set of listener objects\n\tassociations map[any]map[*listener]struct{}\n}\n\ntype connContextFunc = func(context.Context, *quic.ClientInfo) (context.Context, error)\n\n// associateForListener associates an arbitrary value with this transport for a specific listener.\n// This lets us \"tag\" the refcountedTransport when listening so we can use it\n// later for dialing. The listener parameter allows proper cleanup when the listener closes.\n// Necessary for holepunching and learning about our own observed listening address.\nfunc (c *refcountedTransport) associateForListener(a any, ln *listener) {\n\tif a == nil {\n\t\treturn\n\t}\n\tc.mutex.Lock()\n\tdefer c.mutex.Unlock()\n\tif c.associations == nil {\n\t\tc.associations = make(map[any]map[*listener]struct{})\n\t}\n\tif c.associations[a] == nil {\n\t\tc.associations[a] = make(map[*listener]struct{})\n\t}\n\tc.associations[a][ln] = struct{}{}\n}\n\n// RemoveAssociationsForListener removes ALL associations added by a specific listener\nfunc (c *refcountedTransport) RemoveAssociationsForListener(ln *listener) {\n\tc.mutex.Lock()\n\tdefer c.mutex.Unlock()\n\n\t// Remove this listener from all associations\n\tfor association, listeners := range c.associations {\n\t\tdelete(listeners, ln)\n\t\t// If no listeners remain for this association, remove the association entirely\n\t\tif len(listeners) == 0 {\n\t\t\tdelete(c.associations, association)\n\t\t}\n\t}\n}\n\n// hasAssociation returns true if the transport has the given association.\n// If it is a nil association, it will always return true.\nfunc (c *refcountedTransport) hasAssociation(a any) bool {\n\tif a == nil {\n\t\treturn true\n\t}\n\tc.mutex.Lock()\n\tdefer c.mutex.Unlock()\n\tlisteners, ok := c.associations[a]\n\treturn ok && len(listeners) > 0\n}\n\nfunc (c *refcountedTransport) IncreaseCount() {\n\tc.mutex.Lock()\n\tc.refCount++\n\tc.unusedSince = time.Time{}\n\tc.mutex.Unlock()\n}\n\nfunc (c *refcountedTransport) Close() error {\n\tif c.borrowDoneSignal != nil {\n\t\tclose(c.borrowDoneSignal)\n\t\treturn nil\n\t}\n\n\treturn errors.Join(c.QUICTransport.Close(), c.packetConn.Close())\n}\n\nfunc (c *refcountedTransport) WriteTo(b []byte, addr net.Addr) (int, error) {\n\treturn c.QUICTransport.WriteTo(b, addr)\n}\n\nfunc (c *refcountedTransport) LocalAddr() net.Addr {\n\treturn c.packetConn.LocalAddr()\n}\n\nfunc (c *refcountedTransport) Listen(tlsConf *tls.Config, conf *quic.Config) (QUICListener, error) {\n\treturn c.QUICTransport.Listen(tlsConf, conf)\n}\n\nfunc (c *refcountedTransport) DecreaseCount() {\n\tc.mutex.Lock()\n\tc.refCount--\n\tif c.refCount == 0 {\n\t\tc.unusedSince = time.Now()\n\t}\n\tc.mutex.Unlock()\n}\n\nfunc (c *refcountedTransport) ShouldGarbageCollect(now time.Time) bool {\n\tc.mutex.Lock()\n\tdefer c.mutex.Unlock()\n\treturn !c.unusedSince.IsZero() && c.unusedSince.Add(maxUnusedDuration).Before(now)\n}\n\ntype reuse struct {\n\tmutex sync.Mutex\n\n\tcloseChan  chan struct{}\n\tgcStopChan chan struct{}\n\n\tlistenUDP listenUDP\n\n\tsourceIPSelectorFn func() (SourceIPSelector, error)\n\n\troutes  SourceIPSelector\n\tunicast map[string] /* IP.String() */ map[int] /* port */ *refcountedTransport\n\t// globalListeners contains transports that are listening on 0.0.0.0 / ::\n\tglobalListeners map[int]*refcountedTransport\n\t// globalDialers contains transports that we've dialed out from. These transports are listening on 0.0.0.0 / ::\n\t// On Dial, transports are reused from this map if no transport is available in the globalListeners\n\t// On Listen, transports are reused from this map if the requested port is 0, and then moved to globalListeners\n\tglobalDialers map[int]*refcountedTransport\n\n\tstatelessResetKey   *quic.StatelessResetKey\n\ttokenGeneratorKey   *quic.TokenGeneratorKey\n\tconnContext         connContextFunc\n\tverifySourceAddress func(addr net.Addr) bool\n}\n\nfunc newReuse(srk *quic.StatelessResetKey, tokenKey *quic.TokenGeneratorKey, listenUDP listenUDP, sourceIPSelectorFn func() (SourceIPSelector, error),\n\tconnContext connContextFunc, verifySourceAddress func(addr net.Addr) bool) *reuse {\n\tr := &reuse{\n\t\tunicast:             make(map[string]map[int]*refcountedTransport),\n\t\tglobalListeners:     make(map[int]*refcountedTransport),\n\t\tglobalDialers:       make(map[int]*refcountedTransport),\n\t\tcloseChan:           make(chan struct{}),\n\t\tgcStopChan:          make(chan struct{}),\n\t\tlistenUDP:           listenUDP,\n\t\tsourceIPSelectorFn:  sourceIPSelectorFn,\n\t\tstatelessResetKey:   srk,\n\t\ttokenGeneratorKey:   tokenKey,\n\t\tconnContext:         connContext,\n\t\tverifySourceAddress: verifySourceAddress,\n\t}\n\tgo r.gc()\n\treturn r\n}\n\nfunc (r *reuse) gc() {\n\tdefer func() {\n\t\tr.mutex.Lock()\n\t\tfor _, tr := range r.globalListeners {\n\t\t\ttr.Close()\n\t\t}\n\t\tfor _, tr := range r.globalDialers {\n\t\t\ttr.Close()\n\t\t}\n\t\tfor _, trs := range r.unicast {\n\t\t\tfor _, tr := range trs {\n\t\t\t\ttr.Close()\n\t\t\t}\n\t\t}\n\t\tr.mutex.Unlock()\n\t\tclose(r.gcStopChan)\n\t}()\n\tticker := time.NewTicker(garbageCollectInterval)\n\tdefer ticker.Stop()\n\n\tfor {\n\t\tselect {\n\t\tcase <-r.closeChan:\n\t\t\treturn\n\t\tcase <-ticker.C:\n\t\t\tnow := time.Now()\n\t\t\tr.mutex.Lock()\n\t\t\tfor key, tr := range r.globalListeners {\n\t\t\t\tif tr.ShouldGarbageCollect(now) {\n\t\t\t\t\ttr.Close()\n\t\t\t\t\tdelete(r.globalListeners, key)\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor key, tr := range r.globalDialers {\n\t\t\t\tif tr.ShouldGarbageCollect(now) {\n\t\t\t\t\ttr.Close()\n\t\t\t\t\tdelete(r.globalDialers, key)\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor ukey, trs := range r.unicast {\n\t\t\t\tfor key, tr := range trs {\n\t\t\t\t\tif tr.ShouldGarbageCollect(now) {\n\t\t\t\t\t\ttr.Close()\n\t\t\t\t\t\tdelete(trs, key)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif len(trs) == 0 {\n\t\t\t\t\tdelete(r.unicast, ukey)\n\t\t\t\t\t// If we've dropped all transports with a unicast binding,\n\t\t\t\t\t// assume our routes may have changed.\n\t\t\t\t\tif len(r.unicast) == 0 {\n\t\t\t\t\t\tr.routes = nil\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Ignore the error, there's nothing we can do about\n\t\t\t\t\t\t// it.\n\t\t\t\t\t\tr.routes, _ = r.sourceIPSelectorFn()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tr.mutex.Unlock()\n\t\t}\n\t}\n}\n\nfunc (r *reuse) TransportWithAssociationForDial(association any, network string, raddr *net.UDPAddr) (*refcountedTransport, error) {\n\tvar ip *net.IP\n\n\t// Only bother looking up the source address if we actually _have_ non 0.0.0.0 listeners.\n\t// Otherwise, save some time.\n\n\tr.mutex.Lock()\n\trouter := r.routes\n\tr.mutex.Unlock()\n\n\tif router != nil {\n\t\tsrc, err := router.PreferredSourceIPForDestination(raddr)\n\t\tif err == nil && !src.IsUnspecified() {\n\t\t\tip = &src\n\t\t}\n\t}\n\n\tr.mutex.Lock()\n\tdefer r.mutex.Unlock()\n\n\ttr, err := r.transportForDialLocked(association, network, ip)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ttr.IncreaseCount()\n\treturn tr, nil\n}\n\nfunc (r *reuse) transportForDialLocked(association any, network string, source *net.IP) (*refcountedTransport, error) {\n\tif source != nil {\n\t\t// We already have at least one suitable transport...\n\t\tif trs, ok := r.unicast[source.String()]; ok {\n\t\t\t// Prefer a transport that has the given association. We want to\n\t\t\t// reuse the transport the association used for listening.\n\t\t\tfor _, tr := range trs {\n\t\t\t\tif tr.hasAssociation(association) {\n\t\t\t\t\treturn tr, nil\n\t\t\t\t}\n\t\t\t}\n\t\t\t// We don't have a transport with the association, use any one\n\t\t\tfor _, tr := range trs {\n\t\t\t\treturn tr, nil\n\t\t\t}\n\t\t}\n\t}\n\n\t// Use a transport listening on 0.0.0.0 (or ::).\n\t// Again, prefer a transport that has the given association.\n\tfor _, tr := range r.globalListeners {\n\t\tif tr.hasAssociation(association) {\n\t\t\treturn tr, nil\n\t\t}\n\t}\n\t// We don't have a transport with the association, use any one\n\tfor _, tr := range r.globalListeners {\n\t\treturn tr, nil\n\t}\n\n\t// Use a transport we've previously dialed from\n\tfor _, tr := range r.globalDialers {\n\t\treturn tr, nil\n\t}\n\n\t// We don't have a transport that we can use for dialing.\n\t// Dial a new connection from a random port.\n\tvar addr *net.UDPAddr\n\tswitch network {\n\tcase \"udp4\":\n\t\taddr = &net.UDPAddr{IP: net.IPv4zero, Port: 0}\n\tcase \"udp6\":\n\t\taddr = &net.UDPAddr{IP: net.IPv6zero, Port: 0}\n\t}\n\tconn, err := r.listenUDP(network, addr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ttr := r.newTransport(conn)\n\tr.globalDialers[conn.LocalAddr().(*net.UDPAddr).Port] = tr\n\treturn tr, nil\n}\n\nfunc (r *reuse) AddTransport(tr *refcountedTransport, laddr *net.UDPAddr) error {\n\tr.mutex.Lock()\n\tdefer r.mutex.Unlock()\n\n\tif !laddr.IP.IsUnspecified() {\n\t\treturn errors.New(\"adding transport for specific IP not supported\")\n\t}\n\tif _, ok := r.globalDialers[laddr.Port]; ok {\n\t\treturn fmt.Errorf(\"already have global dialer for port %d\", laddr.Port)\n\t}\n\tr.globalDialers[laddr.Port] = tr\n\treturn nil\n}\n\nfunc (r *reuse) TransportForListen(network string, laddr *net.UDPAddr) (*refcountedTransport, error) {\n\tr.mutex.Lock()\n\tdefer r.mutex.Unlock()\n\n\t// Check if we can reuse a transport we have already dialed out from.\n\t// We reuse a transport from globalDialers when the requested port is 0 or the requested\n\t// port is already in the globalDialers.\n\t// If we are reusing a transport from globalDialers, we move the globalDialers entry to\n\t// globalListeners\n\tif laddr.IP.IsUnspecified() {\n\t\tvar rTr *refcountedTransport\n\t\tvar localAddr *net.UDPAddr\n\n\t\tif laddr.Port == 0 {\n\t\t\t// the requested port is 0, we can reuse any transport\n\t\t\tfor _, tr := range r.globalDialers {\n\t\t\t\trTr = tr\n\t\t\t\tlocalAddr = rTr.LocalAddr().(*net.UDPAddr)\n\t\t\t\tdelete(r.globalDialers, localAddr.Port)\n\t\t\t\tbreak\n\t\t\t}\n\t\t} else if _, ok := r.globalDialers[laddr.Port]; ok {\n\t\t\trTr = r.globalDialers[laddr.Port]\n\t\t\tlocalAddr = rTr.LocalAddr().(*net.UDPAddr)\n\t\t\tdelete(r.globalDialers, localAddr.Port)\n\t\t}\n\t\t// found a match\n\t\tif rTr != nil {\n\t\t\trTr.IncreaseCount()\n\t\t\tr.globalListeners[localAddr.Port] = rTr\n\t\t\treturn rTr, nil\n\t\t}\n\t}\n\n\tconn, err := r.listenUDP(network, laddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ttr := r.newTransport(conn)\n\ttr.IncreaseCount()\n\n\tlocalAddr := conn.LocalAddr().(*net.UDPAddr)\n\t// Deal with listen on a global address\n\tif localAddr.IP.IsUnspecified() {\n\t\t// The kernel already checked that the laddr is not already listen\n\t\t// so we need not check here (when we create ListenUDP).\n\t\tr.globalListeners[localAddr.Port] = tr\n\t\treturn tr, nil\n\t}\n\n\t// Deal with listen on a unicast address\n\tif _, ok := r.unicast[localAddr.IP.String()]; !ok {\n\t\tr.unicast[localAddr.IP.String()] = make(map[int]*refcountedTransport)\n\t\t// Assume the system's routes may have changed if we're adding a new listener.\n\t\t// Ignore the error, there's nothing we can do.\n\t\tr.routes, _ = r.sourceIPSelectorFn()\n\t}\n\n\t// The kernel already checked that the laddr is not already listen\n\t// so we need not check here (when we create ListenUDP).\n\tr.unicast[localAddr.IP.String()][localAddr.Port] = tr\n\treturn tr, nil\n}\n\nfunc (r *reuse) newTransport(conn net.PacketConn) *refcountedTransport {\n\treturn &refcountedTransport{\n\t\tQUICTransport: &wrappedQUICTransport{\n\t\t\tTransport: newQUICTransport(\n\t\t\t\tconn,\n\t\t\t\tr.tokenGeneratorKey,\n\t\t\t\tr.statelessResetKey,\n\t\t\t\tr.connContext,\n\t\t\t\tr.verifySourceAddress,\n\t\t\t),\n\t\t},\n\t\tpacketConn: conn,\n\t}\n}\n\nfunc (r *reuse) Close() error {\n\tclose(r.closeChan)\n\t<-r.gcStopChan\n\treturn nil\n}\n\ntype SourceIPSelector interface {\n\tPreferredSourceIPForDestination(dst *net.UDPAddr) (net.IP, error)\n}\n\ntype netrouteSourceIPSelector struct {\n\troutes netroute.Router\n}\n\nfunc (s *netrouteSourceIPSelector) PreferredSourceIPForDestination(dst *net.UDPAddr) (net.IP, error) {\n\t_, _, src, err := s.routes.Route(dst.IP)\n\treturn src, err\n}\n"
  },
  {
    "path": "p2p/transport/quicreuse/reuse_test.go",
    "content": "package quicreuse\n\nimport (\n\t\"bytes\"\n\t\"net\"\n\t\"os\"\n\t\"runtime/pprof\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-netroute\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc (c *refcountedTransport) GetCount() int {\n\tc.mutex.Lock()\n\tdefer c.mutex.Unlock()\n\treturn c.refCount\n}\n\nfunc closeAllConns(reuse *reuse) {\n\treuse.mutex.Lock()\n\tfor _, tr := range reuse.globalListeners {\n\t\tfor tr.GetCount() > 0 {\n\t\t\ttr.DecreaseCount()\n\t\t}\n\t}\n\tfor _, tr := range reuse.globalDialers {\n\t\tfor tr.GetCount() > 0 {\n\t\t\ttr.DecreaseCount()\n\t\t}\n\t}\n\tfor _, trs := range reuse.unicast {\n\t\tfor _, tr := range trs {\n\t\t\tfor tr.GetCount() > 0 {\n\t\t\t\ttr.DecreaseCount()\n\t\t\t}\n\t\t}\n\t}\n\treuse.mutex.Unlock()\n}\n\nfunc platformHasRoutingTables() bool {\n\t_, err := netroute.New()\n\treturn err == nil\n}\n\nfunc isGarbageCollectorRunning() bool {\n\tvar b bytes.Buffer\n\tpprof.Lookup(\"goroutine\").WriteTo(&b, 1)\n\treturn strings.Contains(b.String(), \"quicreuse.(*reuse).gc\")\n}\n\nfunc cleanup(t *testing.T, reuse *reuse) {\n\tt.Cleanup(func() {\n\t\tcloseAllConns(reuse)\n\t\treuse.Close()\n\t\trequire.False(t, isGarbageCollectorRunning(), \"reuse gc still running\")\n\t})\n}\n\nfunc TestReuseListenOnAllIPv4(t *testing.T) {\n\treuse := newReuse(nil, nil, defaultListenUDP, defaultSourceIPSelectorFn, nil, nil)\n\trequire.Eventually(t, isGarbageCollectorRunning, 500*time.Millisecond, 50*time.Millisecond, \"expected garbage collector to be running\")\n\tcleanup(t, reuse)\n\n\taddr, err := net.ResolveUDPAddr(\"udp4\", \"0.0.0.0:0\")\n\trequire.NoError(t, err)\n\tconn, err := reuse.TransportForListen(\"udp4\", addr)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 1, conn.GetCount())\n}\n\nfunc TestReuseListenOnAllIPv6(t *testing.T) {\n\treuse := newReuse(nil, nil, defaultListenUDP, defaultSourceIPSelectorFn, nil, nil)\n\trequire.Eventually(t, isGarbageCollectorRunning, 500*time.Millisecond, 50*time.Millisecond, \"expected garbage collector to be running\")\n\tcleanup(t, reuse)\n\n\taddr, err := net.ResolveUDPAddr(\"udp6\", \"[::]:1234\")\n\trequire.NoError(t, err)\n\ttr, err := reuse.TransportForListen(\"udp6\", addr)\n\trequire.NoError(t, err)\n\tdefer tr.Close()\n\trequire.Equal(t, 1, tr.GetCount())\n}\n\nfunc TestReuseCreateNewGlobalConnOnDial(t *testing.T) {\n\treuse := newReuse(nil, nil, defaultListenUDP, defaultSourceIPSelectorFn, nil, nil)\n\tcleanup(t, reuse)\n\n\taddr, err := net.ResolveUDPAddr(\"udp4\", \"1.1.1.1:1234\")\n\trequire.NoError(t, err)\n\tconn, err := reuse.TransportWithAssociationForDial(nil, \"udp4\", addr)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 1, conn.GetCount())\n\tladdr := conn.LocalAddr().(*net.UDPAddr)\n\trequire.Equal(t, \"0.0.0.0\", laddr.IP.String())\n\trequire.NotEqual(t, 0, laddr.Port)\n}\n\nfunc TestReuseConnectionWhenDialing(t *testing.T) {\n\treuse := newReuse(nil, nil, defaultListenUDP, defaultSourceIPSelectorFn, nil, nil)\n\tcleanup(t, reuse)\n\n\taddr, err := net.ResolveUDPAddr(\"udp4\", \"0.0.0.0:0\")\n\trequire.NoError(t, err)\n\tltr, err := reuse.TransportForListen(\"udp4\", addr)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 1, ltr.GetCount())\n\t// dial\n\traddr, err := net.ResolveUDPAddr(\"udp4\", \"1.1.1.1:1234\")\n\trequire.NoError(t, err)\n\ttr, err := reuse.TransportWithAssociationForDial(nil, \"udp4\", raddr)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 2, tr.GetCount())\n}\n\nfunc TestReuseConnectionWhenListening(t *testing.T) {\n\treuse := newReuse(nil, nil, defaultListenUDP, defaultSourceIPSelectorFn, nil, nil)\n\tcleanup(t, reuse)\n\n\traddr, err := net.ResolveUDPAddr(\"udp4\", \"1.1.1.1:1234\")\n\trequire.NoError(t, err)\n\ttr, err := reuse.TransportWithAssociationForDial(nil, \"udp4\", raddr)\n\trequire.NoError(t, err)\n\tladdr := &net.UDPAddr{IP: net.IPv4zero, Port: tr.LocalAddr().(*net.UDPAddr).Port}\n\tlconn, err := reuse.TransportForListen(\"udp4\", laddr)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 2, lconn.GetCount())\n\trequire.Equal(t, 2, tr.GetCount())\n}\n\nfunc TestReuseConnectionWhenDialBeforeListen(t *testing.T) {\n\treuse := newReuse(nil, nil, defaultListenUDP, defaultSourceIPSelectorFn, nil, nil)\n\tcleanup(t, reuse)\n\n\t// dial any address\n\traddr, err := net.ResolveUDPAddr(\"udp4\", \"1.1.1.1:1234\")\n\trequire.NoError(t, err)\n\trTr, err := reuse.TransportWithAssociationForDial(nil, \"udp4\", raddr)\n\trequire.NoError(t, err)\n\n\t// open a listener\n\tladdr := &net.UDPAddr{IP: net.IPv4zero, Port: 1234}\n\tlTr, err := reuse.TransportForListen(\"udp4\", laddr)\n\trequire.NoError(t, err)\n\n\t// new dials should go via the listener connection\n\traddr, err = net.ResolveUDPAddr(\"udp4\", \"1.1.1.1:1235\")\n\trequire.NoError(t, err)\n\ttr, err := reuse.TransportWithAssociationForDial(nil, \"udp4\", raddr)\n\trequire.NoError(t, err)\n\trequire.Equal(t, lTr, tr)\n\trequire.Equal(t, 2, tr.GetCount())\n\n\t// a listener on an unspecified port should reuse the dialer\n\tladdr2 := &net.UDPAddr{IP: net.IPv4zero, Port: 0}\n\tlconn2, err := reuse.TransportForListen(\"udp4\", laddr2)\n\trequire.NoError(t, err)\n\trequire.Equal(t, rTr, lconn2)\n\trequire.Equal(t, 2, lconn2.GetCount())\n}\n\nfunc TestReuseListenOnSpecificInterface(t *testing.T) {\n\tif !platformHasRoutingTables() {\n\t\tt.Skip(\"this test only works on platforms that support routing tables\")\n\t}\n\treuse := newReuse(nil, nil, defaultListenUDP, defaultSourceIPSelectorFn, nil, nil)\n\tcleanup(t, reuse)\n\n\trouter, err := netroute.New()\n\trequire.NoError(t, err)\n\n\traddr, err := net.ResolveUDPAddr(\"udp4\", \"1.1.1.1:1234\")\n\trequire.NoError(t, err)\n\t_, _, ip, err := router.Route(raddr.IP)\n\trequire.NoError(t, err)\n\t// listen\n\taddr, err := net.ResolveUDPAddr(\"udp4\", ip.String()+\":0\")\n\trequire.NoError(t, err)\n\tlconn, err := reuse.TransportForListen(\"udp4\", addr)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 1, lconn.GetCount())\n\t// dial\n\tconn, err := reuse.TransportWithAssociationForDial(nil, \"udp4\", raddr)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 2, conn.GetCount())\n}\n\nfunc TestReuseGarbageCollect(t *testing.T) {\n\tmaxUnusedDurationOrig := maxUnusedDuration\n\tgarbageCollectIntervalOrig := garbageCollectInterval\n\tt.Cleanup(func() {\n\t\tmaxUnusedDuration = maxUnusedDurationOrig\n\t\tgarbageCollectInterval = garbageCollectIntervalOrig\n\t})\n\tgarbageCollectInterval = 50 * time.Millisecond\n\tmaxUnusedDuration = 100 * time.Millisecond\n\tif os.Getenv(\"CI\") != \"\" {\n\t\t// Increase these timeouts if in CI\n\t\tgarbageCollectInterval = 10 * garbageCollectInterval\n\t\tmaxUnusedDuration = 10 * maxUnusedDuration\n\t}\n\n\treuse := newReuse(nil, nil, defaultListenUDP, defaultSourceIPSelectorFn, nil, nil)\n\tcleanup(t, reuse)\n\n\tnumGlobals := func() int {\n\t\treuse.mutex.Lock()\n\t\tdefer reuse.mutex.Unlock()\n\t\treturn len(reuse.globalListeners) + len(reuse.globalDialers)\n\t}\n\n\traddr, err := net.ResolveUDPAddr(\"udp4\", \"1.2.3.4:1234\")\n\trequire.NoError(t, err)\n\tdTr, err := reuse.TransportWithAssociationForDial(nil, \"udp4\", raddr)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 1, dTr.GetCount())\n\n\taddr, err := net.ResolveUDPAddr(\"udp4\", \"0.0.0.0:1234\")\n\trequire.NoError(t, err)\n\tlTr, err := reuse.TransportForListen(\"udp4\", addr)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 1, lTr.GetCount())\n\n\tcloseTime := time.Now()\n\tlTr.DecreaseCount()\n\tdTr.DecreaseCount()\n\n\tfor {\n\t\tnum := numGlobals()\n\t\tif closeTime.Add(maxUnusedDuration).Before(time.Now()) {\n\t\t\tbreak\n\t\t}\n\t\trequire.Equal(t, 2, num)\n\t\ttime.Sleep(2 * time.Millisecond)\n\t}\n\trequire.Eventually(t, func() bool { return numGlobals() == 0 }, 4*garbageCollectInterval, 10*time.Millisecond)\n}\n"
  },
  {
    "path": "p2p/transport/tcp/metrics.go",
    "content": "//go:build !windows && !riscv64 && !loong64\n\npackage tcp\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/transport\"\n\t\"github.com/marten-seemann/tcp\"\n\t\"github.com/mikioh/tcpinfo\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n)\n\nvar (\n\tnewConns      *prometheus.CounterVec\n\tclosedConns   *prometheus.CounterVec\n\tsegsSentDesc  *prometheus.Desc\n\tsegsRcvdDesc  *prometheus.Desc\n\tbytesSentDesc *prometheus.Desc\n\tbytesRcvdDesc *prometheus.Desc\n)\n\nconst collectFrequency = 10 * time.Second\n\nvar defaultCollector *aggregatingCollector\n\nvar initMetricsOnce sync.Once\n\nfunc initMetrics() {\n\tsegsSentDesc = prometheus.NewDesc(\"tcp_sent_segments_total\", \"TCP segments sent\", nil, nil)\n\tsegsRcvdDesc = prometheus.NewDesc(\"tcp_rcvd_segments_total\", \"TCP segments received\", nil, nil)\n\tbytesSentDesc = prometheus.NewDesc(\"tcp_sent_bytes\", \"TCP bytes sent\", nil, nil)\n\tbytesRcvdDesc = prometheus.NewDesc(\"tcp_rcvd_bytes\", \"TCP bytes received\", nil, nil)\n\n\tdefaultCollector = newAggregatingCollector()\n\tprometheus.MustRegister(defaultCollector)\n\n\tconst direction = \"direction\"\n\n\tnewConns = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tName: \"tcp_connections_new_total\",\n\t\t\tHelp: \"TCP new connections\",\n\t\t},\n\t\t[]string{direction},\n\t)\n\tprometheus.MustRegister(newConns)\n\tclosedConns = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tName: \"tcp_connections_closed_total\",\n\t\t\tHelp: \"TCP connections closed\",\n\t\t},\n\t\t[]string{direction},\n\t)\n\tprometheus.MustRegister(closedConns)\n}\n\ntype aggregatingCollector struct {\n\tcronOnce sync.Once\n\n\tmutex                sync.Mutex\n\thighestID            uint64\n\tconns                map[uint64] /* id */ *tracingConn\n\trtts                 prometheus.Histogram\n\tconnDurations        prometheus.Histogram\n\tsegsSent, segsRcvd   uint64\n\tbytesSent, bytesRcvd uint64\n}\n\nvar _ prometheus.Collector = &aggregatingCollector{}\n\nfunc newAggregatingCollector() *aggregatingCollector {\n\tc := &aggregatingCollector{\n\t\tconns: make(map[uint64]*tracingConn),\n\t\trtts: prometheus.NewHistogram(prometheus.HistogramOpts{\n\t\t\tName:    \"tcp_rtt\",\n\t\t\tHelp:    \"TCP round trip time\",\n\t\t\tBuckets: prometheus.ExponentialBuckets(0.001, 1.25, 40), // 1ms to ~6000ms\n\t\t}),\n\t\tconnDurations: prometheus.NewHistogram(prometheus.HistogramOpts{\n\t\t\tName:    \"tcp_connection_duration\",\n\t\t\tHelp:    \"TCP Connection Duration\",\n\t\t\tBuckets: prometheus.ExponentialBuckets(1, 1.5, 40), // 1s to ~12 weeks\n\t\t}),\n\t}\n\treturn c\n}\n\nfunc (c *aggregatingCollector) AddConn(t *tracingConn) uint64 {\n\tc.mutex.Lock()\n\tdefer c.mutex.Unlock()\n\tc.highestID++\n\tc.conns[c.highestID] = t\n\treturn c.highestID\n}\n\nfunc (c *aggregatingCollector) removeConn(id uint64) {\n\tdelete(c.conns, id)\n}\n\nfunc (c *aggregatingCollector) Describe(descs chan<- *prometheus.Desc) {\n\tdescs <- c.rtts.Desc()\n\tdescs <- c.connDurations.Desc()\n\tif hasSegmentCounter {\n\t\tdescs <- segsSentDesc\n\t\tdescs <- segsRcvdDesc\n\t}\n\tif hasByteCounter {\n\t\tdescs <- bytesSentDesc\n\t\tdescs <- bytesRcvdDesc\n\t}\n}\n\nfunc (c *aggregatingCollector) cron() {\n\tticker := time.NewTicker(collectFrequency)\n\tdefer ticker.Stop()\n\n\tfor now := range ticker.C {\n\t\tc.gatherMetrics(now)\n\t}\n}\n\nfunc (c *aggregatingCollector) gatherMetrics(now time.Time) {\n\tc.mutex.Lock()\n\tdefer c.mutex.Unlock()\n\n\tc.segsSent = 0\n\tc.segsRcvd = 0\n\tc.bytesSent = 0\n\tc.bytesRcvd = 0\n\tfor _, conn := range c.conns {\n\t\tinfo, err := conn.getTCPInfo()\n\t\tif err != nil {\n\t\t\tif strings.Contains(err.Error(), \"use of closed network connection\") {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tlog.Error(\"Failed to get TCP info\", \"error\", err)\n\t\t\tcontinue\n\t\t}\n\t\tif hasSegmentCounter {\n\t\t\tc.segsSent += getSegmentsSent(info)\n\t\t\tc.segsRcvd += getSegmentsRcvd(info)\n\t\t}\n\t\tif hasByteCounter {\n\t\t\tc.bytesSent += getBytesSent(info)\n\t\t\tc.bytesRcvd += getBytesRcvd(info)\n\t\t}\n\t\tc.rtts.Observe(info.RTT.Seconds())\n\t\tc.connDurations.Observe(now.Sub(conn.startTime).Seconds())\n\t}\n}\n\nfunc (c *aggregatingCollector) Collect(metrics chan<- prometheus.Metric) {\n\t// Start collecting the metrics collection the first time Collect is called.\n\tc.cronOnce.Do(func() {\n\t\tc.gatherMetrics(time.Now())\n\t\tgo c.cron()\n\t})\n\n\tc.mutex.Lock()\n\tdefer c.mutex.Unlock()\n\n\tmetrics <- c.rtts\n\tmetrics <- c.connDurations\n\tif hasSegmentCounter {\n\t\tsegsSentMetric, err := prometheus.NewConstMetric(segsSentDesc, prometheus.CounterValue, float64(c.segsSent))\n\t\tif err != nil {\n\t\t\tlog.Error(\"creating tcp_sent_segments_total metric failed\", \"error\", err)\n\t\t\treturn\n\t\t}\n\t\tsegsRcvdMetric, err := prometheus.NewConstMetric(segsRcvdDesc, prometheus.CounterValue, float64(c.segsRcvd))\n\t\tif err != nil {\n\t\t\tlog.Error(\"creating tcp_rcvd_segments_total metric failed\", \"error\", err)\n\t\t\treturn\n\t\t}\n\t\tmetrics <- segsSentMetric\n\t\tmetrics <- segsRcvdMetric\n\t}\n\tif hasByteCounter {\n\t\tbytesSentMetric, err := prometheus.NewConstMetric(bytesSentDesc, prometheus.CounterValue, float64(c.bytesSent))\n\t\tif err != nil {\n\t\t\tlog.Error(\"creating tcp_sent_bytes metric failed\", \"error\", err)\n\t\t\treturn\n\t\t}\n\t\tbytesRcvdMetric, err := prometheus.NewConstMetric(bytesRcvdDesc, prometheus.CounterValue, float64(c.bytesRcvd))\n\t\tif err != nil {\n\t\t\tlog.Error(\"creating tcp_rcvd_bytes metric failed\", \"error\", err)\n\t\t\treturn\n\t\t}\n\t\tmetrics <- bytesSentMetric\n\t\tmetrics <- bytesRcvdMetric\n\t}\n}\n\nfunc (c *aggregatingCollector) ClosedConn(conn *tracingConn, direction string) {\n\tc.mutex.Lock()\n\tc.removeConn(conn.id)\n\tc.mutex.Unlock()\n\tclosedConns.WithLabelValues(direction).Inc()\n}\n\ntype tracingConn struct {\n\tid uint64\n\n\tcollector *aggregatingCollector\n\n\tstartTime time.Time\n\tisClient  bool\n\n\tmanet.Conn\n\ttcpConn   *tcp.Conn\n\tcloseOnce sync.Once\n\tcloseErr  error\n}\n\n// newTracingConn wraps a manet.Conn with a tracingConn. A nil collector will use the default collector.\nfunc newTracingConn(c manet.Conn, collector *aggregatingCollector, isClient bool) (*tracingConn, error) {\n\tinitMetricsOnce.Do(func() { initMetrics() })\n\tconn, err := tcp.NewConn(c)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ttc := &tracingConn{\n\t\tstartTime: time.Now(),\n\t\tisClient:  isClient,\n\t\tConn:      c,\n\t\ttcpConn:   conn,\n\t\tcollector: collector,\n\t}\n\tif tc.collector == nil {\n\t\ttc.collector = defaultCollector\n\t}\n\ttc.id = tc.collector.AddConn(tc)\n\tnewConns.WithLabelValues(tc.getDirection()).Inc()\n\treturn tc, nil\n}\n\nfunc (c *tracingConn) getDirection() string {\n\tif c.isClient {\n\t\treturn \"outgoing\"\n\t}\n\treturn \"incoming\"\n}\n\nfunc (c *tracingConn) Close() error {\n\tc.closeOnce.Do(func() {\n\t\tc.collector.ClosedConn(c, c.getDirection())\n\t\tc.closeErr = c.Conn.Close()\n\t})\n\treturn c.closeErr\n}\n\nfunc (c *tracingConn) getTCPInfo() (*tcpinfo.Info, error) {\n\tvar o tcpinfo.Info\n\tvar b [256]byte\n\ti, err := c.tcpConn.Option(o.Level(), o.Name(), b[:])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tinfo := i.(*tcpinfo.Info)\n\treturn info, nil\n}\n\ntype tracingListener struct {\n\ttransport.GatedMaListener\n\tcollector *aggregatingCollector\n}\n\n// newTracingListener wraps a manet.Listener with a tracingListener. A nil collector will use the default collector.\nfunc newTracingListener(l transport.GatedMaListener, collector *aggregatingCollector) *tracingListener {\n\treturn &tracingListener{GatedMaListener: l, collector: collector}\n}\n\nfunc (l *tracingListener) Accept() (manet.Conn, network.ConnManagementScope, error) {\n\tconn, scope, err := l.GatedMaListener.Accept()\n\tif err != nil {\n\t\tif scope != nil {\n\t\t\tscope.Done()\n\t\t\tlog.Error(\"BUG: got non-nil scope but also an error\", \"error\", err)\n\t\t}\n\t\treturn nil, nil, err\n\t}\n\n\ttc, err := newTracingConn(conn, l.collector, false)\n\tif err != nil {\n\t\tlog.Error(\"failed to create tracingConn\", \"conn_type\", fmt.Sprintf(\"%T\", conn), \"error\", err)\n\t\tconn.Close()\n\t\tscope.Done()\n\t\treturn nil, nil, err\n\t}\n\treturn tc, scope, nil\n}\n"
  },
  {
    "path": "p2p/transport/tcp/metrics_darwin.go",
    "content": "//go:build darwin\n\npackage tcp\n\nimport \"github.com/mikioh/tcpinfo\"\n\nconst (\n\thasSegmentCounter = true\n\thasByteCounter    = true\n)\n\nfunc getSegmentsSent(info *tcpinfo.Info) uint64 { return info.Sys.SegsSent }\nfunc getSegmentsRcvd(info *tcpinfo.Info) uint64 { return info.Sys.SegsReceived }\nfunc getBytesSent(info *tcpinfo.Info) uint64    { return info.Sys.BytesSent }\nfunc getBytesRcvd(info *tcpinfo.Info) uint64    { return info.Sys.BytesReceived }\n"
  },
  {
    "path": "p2p/transport/tcp/metrics_general.go",
    "content": "//go:build !linux && !darwin && !windows && !riscv64 && !loong64\n\npackage tcp\n\nimport \"github.com/mikioh/tcpinfo\"\n\nconst (\n\thasSegmentCounter = false\n\thasByteCounter    = false\n)\n\nfunc getSegmentsSent(_ *tcpinfo.Info) uint64 { return 0 }\nfunc getSegmentsRcvd(_ *tcpinfo.Info) uint64 { return 0 }\nfunc getBytesSent(_ *tcpinfo.Info) uint64    { return 0 }\nfunc getBytesRcvd(_ *tcpinfo.Info) uint64    { return 0 }\n"
  },
  {
    "path": "p2p/transport/tcp/metrics_linux.go",
    "content": "//go:build linux\n\npackage tcp\n\nimport \"github.com/mikioh/tcpinfo\"\n\nconst (\n\thasSegmentCounter = true\n\thasByteCounter    = false\n)\n\nfunc getSegmentsSent(info *tcpinfo.Info) uint64 { return uint64(info.Sys.SegsOut) }\nfunc getSegmentsRcvd(info *tcpinfo.Info) uint64 { return uint64(info.Sys.SegsIn) }\nfunc getBytesSent(_ *tcpinfo.Info) uint64       { return 0 }\nfunc getBytesRcvd(_ *tcpinfo.Info) uint64       { return 0 }\n"
  },
  {
    "path": "p2p/transport/tcp/metrics_none.go",
    "content": "// riscv64 see: https://github.com/marten-seemann/tcp/pull/1\n\n//go:build windows || riscv64 || loong64\n\npackage tcp\n\nimport (\n\t\"github.com/libp2p/go-libp2p/core/transport\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\ntype aggregatingCollector struct{}\n\nfunc newTracingConn(c manet.Conn, collector *aggregatingCollector, isClient bool) (manet.Conn, error) {\n\treturn c, nil\n}\nfunc newTracingListener(l transport.GatedMaListener, collector *aggregatingCollector) transport.GatedMaListener {\n\treturn l\n}\n"
  },
  {
    "path": "p2p/transport/tcp/metrics_test.go",
    "content": "package tcp\n\nimport (\n\t\"testing\"\n\n\ttptu \"github.com/libp2p/go-libp2p/p2p/net/upgrader\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/tcpreuse\"\n\tttransport \"github.com/libp2p/go-libp2p/p2p/transport/testsuite\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestTcpTransportCollectsMetricsWithSharedTcpSocket(t *testing.T) {\n\n\tpeerA, ia := makeInsecureMuxer(t)\n\t_, ib := makeInsecureMuxer(t)\n\n\tupg, err := tptu.New(ia, muxers, nil, nil, nil)\n\trequire.NoError(t, err)\n\tsharedTCPSocketA := tcpreuse.NewConnMgr(false, upg)\n\tsharedTCPSocketB := tcpreuse.NewConnMgr(false, upg)\n\n\tua, err := tptu.New(ia, muxers, nil, nil, nil)\n\trequire.NoError(t, err)\n\tta, err := NewTCPTransport(ua, nil, sharedTCPSocketA, WithMetrics())\n\trequire.NoError(t, err)\n\tub, err := tptu.New(ib, muxers, nil, nil, nil)\n\trequire.NoError(t, err)\n\ttb, err := NewTCPTransport(ub, nil, sharedTCPSocketB, WithMetrics())\n\trequire.NoError(t, err)\n\n\tzero := \"/ip4/127.0.0.1/tcp/0\"\n\n\t// Not running any test that needs more than 1 conn because the testsuite\n\t// opens multiple conns via multiple listeners, which is not expected to work\n\t// with the shared TCP socket.\n\tsubtestsToRun := []ttransport.TransportSubTestFn{\n\t\tttransport.SubtestProtocols,\n\t\tttransport.SubtestBasic,\n\t\tttransport.SubtestCancel,\n\t\tttransport.SubtestPingPong,\n\n\t\t// Stolen from the stream muxer test suite.\n\t\tttransport.SubtestStress1Conn1Stream1Msg,\n\t\tttransport.SubtestStress1Conn1Stream100Msg,\n\t\tttransport.SubtestStress1Conn100Stream100Msg,\n\t\tttransport.SubtestStress1Conn1000Stream10Msg,\n\t\tttransport.SubtestStress1Conn100Stream100Msg10MB,\n\t\tttransport.SubtestStreamOpenStress,\n\t\tttransport.SubtestStreamReset,\n\t}\n\n\tttransport.SubtestTransportWithFs(t, ta, tb, zero, peerA, subtestsToRun)\n}\n"
  },
  {
    "path": "p2p/transport/tcp/tcp.go",
    "content": "package tcp\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"runtime\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/transport\"\n\t\"github.com/libp2p/go-libp2p/p2p/net/reuseport\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/tcpreuse\"\n\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmafmt \"github.com/multiformats/go-multiaddr-fmt\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\nconst defaultConnectTimeout = 5 * time.Second\n\nvar log = logging.Logger(\"tcp-tpt\")\n\nconst keepAlivePeriod = 30 * time.Second\n\ntype canKeepAlive interface {\n\tSetKeepAlive(bool) error\n\tSetKeepAlivePeriod(time.Duration) error\n}\n\nvar _ canKeepAlive = &net.TCPConn{}\n\n// Deprecated: Use tcpreuse.ReuseportIsAvailable\nvar ReuseportIsAvailable = tcpreuse.ReuseportIsAvailable\n\nfunc tryKeepAlive(conn net.Conn, keepAlive bool) {\n\tkeepAliveConn, ok := conn.(canKeepAlive)\n\tif !ok {\n\t\tlog.Error(\"can't set TCP keepalives. net.Conn doesn't support SetKeepAlive\", \"conn_type\", fmt.Sprintf(\"%T\", conn))\n\t\treturn\n\t}\n\tif err := keepAliveConn.SetKeepAlive(keepAlive); err != nil {\n\t\t// Sometimes we seem to get \"invalid argument\" results from this function on Darwin.\n\t\t// This might be due to a closed connection, but I can't reproduce that on Linux.\n\t\t//\n\t\t// But there's nothing we can do about invalid arguments, so we'll drop this to a\n\t\t// debug.\n\t\tif errors.Is(err, os.ErrInvalid) || errors.Is(err, syscall.EINVAL) {\n\t\t\tlog.Debug(\"failed to enable TCP keepalive\", \"error\", err)\n\t\t} else {\n\t\t\tlog.Error(\"failed to enable TCP keepalive\", \"error\", err)\n\t\t}\n\t\treturn\n\t}\n\n\tif runtime.GOOS != \"openbsd\" {\n\t\tif err := keepAliveConn.SetKeepAlivePeriod(keepAlivePeriod); err != nil {\n\t\t\tlog.Error(\"failed set keepalive period\", \"error\", err)\n\t\t}\n\t}\n}\n\n// try to set linger on the connection, if possible.\nfunc tryLinger(conn net.Conn, sec int) {\n\ttype canLinger interface {\n\t\tSetLinger(int) error\n\t}\n\n\tif lingerConn, ok := conn.(canLinger); ok {\n\t\t_ = lingerConn.SetLinger(sec)\n\t}\n}\n\ntype tcpGatedMaListener struct {\n\ttransport.GatedMaListener\n\tsec int\n}\n\nfunc (ll *tcpGatedMaListener) Accept() (manet.Conn, network.ConnManagementScope, error) {\n\tc, scope, err := ll.GatedMaListener.Accept()\n\tif err != nil {\n\t\tif scope != nil {\n\t\t\tlog.Error(\"BUG: got non-nil scope but also an error\", \"error\", err)\n\t\t\tscope.Done()\n\t\t}\n\t\treturn nil, nil, err\n\t}\n\ttryLinger(c, ll.sec)\n\ttryKeepAlive(c, true)\n\treturn c, scope, nil\n}\n\ntype Option func(*TcpTransport) error\n\nfunc DisableReuseport() Option {\n\treturn func(tr *TcpTransport) error {\n\t\ttr.disableReuseport = true\n\t\treturn nil\n\t}\n}\n\nfunc WithConnectionTimeout(d time.Duration) Option {\n\treturn func(tr *TcpTransport) error {\n\t\ttr.connectTimeout = d\n\t\treturn nil\n\t}\n}\n\nfunc WithMetrics() Option {\n\treturn func(tr *TcpTransport) error {\n\t\ttr.enableMetrics = true\n\t\treturn nil\n\t}\n}\n\n// WithDialerForAddr sets a custom dialer for the given address.\n// If set, it will be the *ONLY* dialer used.\nfunc WithDialerForAddr(d DialerForAddr) Option {\n\treturn func(tr *TcpTransport) error {\n\t\ttr.overrideDialerForAddr = d\n\t\treturn nil\n\t}\n}\n\ntype ContextDialer interface {\n\tDialContext(ctx context.Context, network, address string) (net.Conn, error)\n}\n\n// DialerForAddr is a function that returns a dialer for a given address.\n// Implementations must return either a ContextDialer or an error. It is\n// invalid to return nil, nil.\ntype DialerForAddr func(raddr ma.Multiaddr) (ContextDialer, error)\n\n// TcpTransport is the TCP transport.\ntype TcpTransport struct {\n\t// Connection upgrader for upgrading insecure stream connections to\n\t// secure multiplex connections.\n\tupgrader transport.Upgrader\n\n\t// optional custom dialer to use for dialing. If set, it will be the *ONLY* dialer\n\t// used. The transport will not attempt to reuse the listen port to\n\t// dial or the shared TCP transport for dialing.\n\toverrideDialerForAddr DialerForAddr\n\n\tdisableReuseport bool // Explicitly disable reuseport.\n\tenableMetrics    bool\n\n\t// share and demultiplex TCP listeners across multiple transports\n\tsharedTcp *tcpreuse.ConnMgr\n\n\t// TCP connect timeout\n\tconnectTimeout time.Duration\n\n\trcmgr network.ResourceManager\n\n\treuse reuseport.Transport\n\n\tmetricsCollector *aggregatingCollector\n}\n\nvar _ transport.Transport = &TcpTransport{}\nvar _ transport.DialUpdater = &TcpTransport{}\n\n// NewTCPTransport creates a tcp transport object that tracks dialers and listeners\n// created.\nfunc NewTCPTransport(upgrader transport.Upgrader, rcmgr network.ResourceManager, sharedTCP *tcpreuse.ConnMgr, opts ...Option) (*TcpTransport, error) {\n\tif rcmgr == nil {\n\t\trcmgr = &network.NullResourceManager{}\n\t}\n\ttr := &TcpTransport{\n\t\tupgrader:       upgrader,\n\t\tconnectTimeout: defaultConnectTimeout, // can be set by using the WithConnectionTimeout option\n\t\trcmgr:          rcmgr,\n\t\tsharedTcp:      sharedTCP,\n\t}\n\tfor _, o := range opts {\n\t\tif err := o(tr); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn tr, nil\n}\n\nvar dialMatcher = mafmt.And(mafmt.IP, mafmt.Base(ma.P_TCP))\n\n// CanDial returns true if this transport believes it can dial the given\n// multiaddr.\nfunc (t *TcpTransport) CanDial(addr ma.Multiaddr) bool {\n\treturn dialMatcher.Matches(addr)\n}\n\nfunc (t *TcpTransport) customDial(ctx context.Context, raddr ma.Multiaddr) (manet.Conn, error) {\n\t// get the net.Dial friendly arguments from the remote addr\n\trnet, rnaddr, err := manet.DialArgs(raddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdialer, err := t.overrideDialerForAddr(raddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif dialer == nil {\n\t\treturn nil, fmt.Errorf(\"dialer for address %s is nil\", raddr)\n\t}\n\n\t// ok, Dial!\n\tvar nconn net.Conn\n\tswitch rnet {\n\tcase \"tcp\", \"tcp4\", \"tcp6\", \"udp\", \"udp4\", \"udp6\", \"unix\":\n\t\tnconn, err = dialer.DialContext(ctx, rnet, rnaddr)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unrecognized network: %s\", rnet)\n\t}\n\n\treturn manet.WrapNetConn(nconn)\n}\n\nfunc (t *TcpTransport) maDial(ctx context.Context, raddr ma.Multiaddr) (manet.Conn, error) {\n\t// Apply the deadline iff applicable\n\tif t.connectTimeout > 0 {\n\t\tvar cancel context.CancelFunc\n\t\tctx, cancel = context.WithTimeout(ctx, t.connectTimeout)\n\t\tdefer cancel()\n\t}\n\n\tif t.overrideDialerForAddr != nil {\n\t\treturn t.customDial(ctx, raddr)\n\t}\n\n\tif t.sharedTcp != nil {\n\t\treturn t.sharedTcp.DialContext(ctx, raddr)\n\t}\n\n\tif t.UseReuseport() {\n\t\treturn t.reuse.DialContext(ctx, raddr)\n\t}\n\tvar d manet.Dialer\n\treturn d.DialContext(ctx, raddr)\n}\n\n// Dial dials the peer at the remote address.\nfunc (t *TcpTransport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (transport.CapableConn, error) {\n\treturn t.DialWithUpdates(ctx, raddr, p, nil)\n}\n\nfunc (t *TcpTransport) DialWithUpdates(ctx context.Context, raddr ma.Multiaddr, p peer.ID, updateChan chan<- transport.DialUpdate) (transport.CapableConn, error) {\n\tconnScope, err := t.rcmgr.OpenConnection(network.DirOutbound, true, raddr)\n\tif err != nil {\n\t\tlog.Debug(\"resource manager blocked outgoing connection\", \"peer\", p, \"addr\", raddr, \"error\", err)\n\t\treturn nil, err\n\t}\n\n\tc, err := t.dialWithScope(ctx, raddr, p, connScope, updateChan)\n\tif err != nil {\n\t\tconnScope.Done()\n\t\treturn nil, err\n\t}\n\treturn c, nil\n}\n\nfunc (t *TcpTransport) dialWithScope(ctx context.Context, raddr ma.Multiaddr, p peer.ID, connScope network.ConnManagementScope, updateChan chan<- transport.DialUpdate) (transport.CapableConn, error) {\n\tif err := connScope.SetPeer(p); err != nil {\n\t\tlog.Debug(\"resource manager blocked outgoing connection for peer\", \"peer\", p, \"addr\", raddr, \"error\", err)\n\t\treturn nil, err\n\t}\n\tconn, err := t.maDial(ctx, raddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// Set linger to 0 so we never get stuck in the TIME-WAIT state. When\n\t// linger is 0, connections are _reset_ instead of closed with a FIN.\n\t// This means we can immediately reuse the 5-tuple and reconnect.\n\ttryLinger(conn, 0)\n\ttryKeepAlive(conn, true)\n\tc := conn\n\tif t.enableMetrics {\n\t\tvar err error\n\t\tc, err = newTracingConn(conn, t.metricsCollector, true)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tif updateChan != nil {\n\t\tselect {\n\t\tcase updateChan <- transport.DialUpdate{Kind: transport.UpdateKindHandshakeProgressed, Addr: raddr}:\n\t\tdefault:\n\t\t\t// It is better to skip the update than to delay upgrading the connection\n\t\t}\n\t}\n\tdirection := network.DirOutbound\n\tif ok, isClient, _ := network.GetSimultaneousConnect(ctx); ok && !isClient {\n\t\tdirection = network.DirInbound\n\t}\n\treturn t.upgrader.Upgrade(ctx, t, c, direction, p, connScope)\n}\n\n// UseReuseport returns true if reuseport is enabled and available.\nfunc (t *TcpTransport) UseReuseport() bool {\n\treturn !t.disableReuseport && tcpreuse.ReuseportIsAvailable()\n}\n\nfunc (t *TcpTransport) unsharedMAListen(laddr ma.Multiaddr) (manet.Listener, error) {\n\tif t.UseReuseport() {\n\t\treturn t.reuse.Listen(laddr)\n\t}\n\treturn manet.Listen(laddr)\n}\n\n// Listen listens on the given multiaddr.\nfunc (t *TcpTransport) Listen(laddr ma.Multiaddr) (transport.Listener, error) {\n\tvar list transport.GatedMaListener\n\tvar err error\n\tif t.sharedTcp != nil {\n\t\tlist, err = t.sharedTcp.DemultiplexedListen(laddr, tcpreuse.DemultiplexedConnType_MultistreamSelect)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t} else {\n\t\tmal, err := t.unsharedMAListen(laddr)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tlist = t.upgrader.GateMaListener(mal)\n\t}\n\n\t// Always wrap the listener with tcpGatedMaListener to apply TCP-specific configurations\n\ttcpList := &tcpGatedMaListener{list, 0}\n\n\tif t.enableMetrics {\n\t\t// Wrap with tracing listener if metrics are enabled\n\t\treturn t.upgrader.UpgradeGatedMaListener(t, newTracingListener(tcpList, t.metricsCollector)), nil\n\t}\n\n\t// Regular path without metrics\n\treturn t.upgrader.UpgradeGatedMaListener(t, tcpList), nil\n}\n\n// Protocols returns the list of terminal protocols this transport can dial.\nfunc (t *TcpTransport) Protocols() []int {\n\treturn []int{ma.P_TCP}\n}\n\n// Proxy always returns false for the TCP transport.\nfunc (t *TcpTransport) Proxy() bool {\n\treturn false\n}\n\nfunc (t *TcpTransport) String() string {\n\treturn \"TCP\"\n}\n"
  },
  {
    "path": "p2p/transport/tcp/tcp_test.go",
    "content": "package tcp\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net\"\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\tmocknetwork \"github.com/libp2p/go-libp2p/core/network/mocks\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/sec\"\n\t\"github.com/libp2p/go-libp2p/core/transport\"\n\t\"github.com/libp2p/go-libp2p/p2p/muxer/yamux\"\n\ttptu \"github.com/libp2p/go-libp2p/p2p/net/upgrader\"\n\t\"github.com/libp2p/go-libp2p/p2p/security/insecure\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/tcpreuse\"\n\tttransport \"github.com/libp2p/go-libp2p/p2p/transport/testsuite\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/mock/gomock\"\n)\n\nvar muxers = []tptu.StreamMuxer{{ID: \"/yamux\", Muxer: yamux.DefaultTransport}}\n\nfunc TestTcpTransport(t *testing.T) {\n\tfor range 2 {\n\t\tpeerA, ia := makeInsecureMuxer(t)\n\t\t_, ib := makeInsecureMuxer(t)\n\n\t\tua, err := tptu.New(ia, muxers, nil, nil, nil)\n\t\trequire.NoError(t, err)\n\t\tta, err := NewTCPTransport(ua, nil, nil)\n\t\trequire.NoError(t, err)\n\t\tub, err := tptu.New(ib, muxers, nil, nil, nil)\n\t\trequire.NoError(t, err)\n\t\ttb, err := NewTCPTransport(ub, nil, nil)\n\t\trequire.NoError(t, err)\n\n\t\tzero := \"/ip4/127.0.0.1/tcp/0\"\n\t\tttransport.SubtestTransport(t, ta, tb, zero, peerA)\n\n\t\ttcpreuse.EnvReuseportVal = false\n\t}\n\ttcpreuse.EnvReuseportVal = true\n}\n\nfunc TestTcpTransportWithMetrics(t *testing.T) {\n\tpeerA, ia := makeInsecureMuxer(t)\n\t_, ib := makeInsecureMuxer(t)\n\n\tua, err := tptu.New(ia, muxers, nil, nil, nil)\n\trequire.NoError(t, err)\n\tta, err := NewTCPTransport(ua, nil, nil, WithMetrics())\n\trequire.NoError(t, err)\n\tub, err := tptu.New(ib, muxers, nil, nil, nil)\n\trequire.NoError(t, err)\n\ttb, err := NewTCPTransport(ub, nil, nil, WithMetrics())\n\trequire.NoError(t, err)\n\n\tzero := \"/ip4/127.0.0.1/tcp/0\"\n\tttransport.SubtestTransport(t, ta, tb, zero, peerA)\n}\n\nfunc TestResourceManager(t *testing.T) {\n\tctrl := gomock.NewController(t)\n\tdefer ctrl.Finish()\n\n\tpeerA, ia := makeInsecureMuxer(t)\n\t_, ib := makeInsecureMuxer(t)\n\n\tua, err := tptu.New(ia, muxers, nil, nil, nil)\n\trequire.NoError(t, err)\n\tta, err := NewTCPTransport(ua, nil, nil)\n\trequire.NoError(t, err)\n\tln, err := ta.Listen(ma.StringCast(\"/ip4/127.0.0.1/tcp/0\"))\n\trequire.NoError(t, err)\n\tdefer ln.Close()\n\n\tub, err := tptu.New(ib, muxers, nil, nil, nil)\n\trequire.NoError(t, err)\n\trcmgr := mocknetwork.NewMockResourceManager(ctrl)\n\ttb, err := NewTCPTransport(ub, rcmgr, nil)\n\trequire.NoError(t, err)\n\n\tt.Run(\"success\", func(t *testing.T) {\n\t\tscope := mocknetwork.NewMockConnManagementScope(ctrl)\n\t\trcmgr.EXPECT().OpenConnection(network.DirOutbound, true, ln.Multiaddr()).Return(scope, nil)\n\t\tscope.EXPECT().SetPeer(peerA)\n\t\tscope.EXPECT().PeerScope().Return(&network.NullScope{}).AnyTimes() // called by the upgrader\n\t\tconn, err := tb.Dial(context.Background(), ln.Multiaddr(), peerA)\n\t\trequire.NoError(t, err)\n\t\tscope.EXPECT().Done()\n\t\tdefer conn.Close()\n\t})\n\n\tt.Run(\"connection denied\", func(t *testing.T) {\n\t\trerr := errors.New(\"nope\")\n\t\trcmgr.EXPECT().OpenConnection(network.DirOutbound, true, ln.Multiaddr()).Return(nil, rerr)\n\t\t_, err = tb.Dial(context.Background(), ln.Multiaddr(), peerA)\n\t\trequire.ErrorIs(t, err, rerr)\n\t})\n\n\tt.Run(\"peer denied\", func(t *testing.T) {\n\t\tscope := mocknetwork.NewMockConnManagementScope(ctrl)\n\t\trcmgr.EXPECT().OpenConnection(network.DirOutbound, true, ln.Multiaddr()).Return(scope, nil)\n\t\trerr := errors.New(\"nope\")\n\t\tscope.EXPECT().SetPeer(peerA).Return(rerr)\n\t\tscope.EXPECT().Done()\n\t\t_, err = tb.Dial(context.Background(), ln.Multiaddr(), peerA)\n\t\trequire.ErrorIs(t, err, rerr)\n\t})\n}\n\nfunc TestTcpTransportCantDialDNS(t *testing.T) {\n\tfor range 2 {\n\t\tdnsa, err := ma.NewMultiaddr(\"/dns4/example.com/tcp/1234\")\n\t\trequire.NoError(t, err)\n\n\t\tvar u transport.Upgrader\n\t\ttpt, err := NewTCPTransport(u, nil, nil)\n\t\trequire.NoError(t, err)\n\n\t\tif tpt.CanDial(dnsa) {\n\t\t\tt.Fatal(\"shouldn't be able to dial dns\")\n\t\t}\n\n\t\ttcpreuse.EnvReuseportVal = false\n\t}\n\ttcpreuse.EnvReuseportVal = true\n}\n\nfunc TestTcpTransportCantListenUtp(t *testing.T) {\n\tfor range 2 {\n\t\tutpa, err := ma.NewMultiaddr(\"/ip4/127.0.0.1/udp/0/utp\")\n\t\trequire.NoError(t, err)\n\n\t\tvar u transport.Upgrader\n\t\ttpt, err := NewTCPTransport(u, nil, nil)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tpt.Listen(utpa)\n\t\trequire.Error(t, err, \"shouldn't be able to listen on utp addr with tcp transport\")\n\n\t\ttcpreuse.EnvReuseportVal = false\n\t}\n\ttcpreuse.EnvReuseportVal = true\n}\n\nfunc TestDialWithUpdates(t *testing.T) {\n\tpeerA, ia := makeInsecureMuxer(t)\n\t_, ib := makeInsecureMuxer(t)\n\n\tua, err := tptu.New(ia, muxers, nil, nil, nil)\n\trequire.NoError(t, err)\n\tta, err := NewTCPTransport(ua, nil, nil)\n\trequire.NoError(t, err)\n\tln, err := ta.Listen(ma.StringCast(\"/ip4/127.0.0.1/tcp/0\"))\n\trequire.NoError(t, err)\n\tdefer ln.Close()\n\n\tub, err := tptu.New(ib, muxers, nil, nil, nil)\n\trequire.NoError(t, err)\n\ttb, err := NewTCPTransport(ub, nil, nil)\n\trequire.NoError(t, err)\n\n\tupdCh := make(chan transport.DialUpdate, 1)\n\tconn, err := tb.DialWithUpdates(context.Background(), ln.Multiaddr(), peerA, updCh)\n\tupd := <-updCh\n\trequire.Equal(t, transport.UpdateKindHandshakeProgressed, upd.Kind)\n\trequire.NotNil(t, conn)\n\trequire.NoError(t, err)\n\n\tacceptAndClose := func() manet.Listener {\n\t\tli, err := manet.Listen(ma.StringCast(\"/ip4/127.0.0.1/tcp/0\"))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tgo func() {\n\t\t\tconn, err := li.Accept()\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconn.Close()\n\t\t}()\n\t\treturn li\n\t}\n\tli := acceptAndClose()\n\tdefer li.Close()\n\t// This dial will fail as acceptAndClose will not upgrade the connection\n\tconn, err = tb.DialWithUpdates(context.Background(), li.Multiaddr(), peerA, updCh)\n\tupd = <-updCh\n\trequire.Equal(t, transport.UpdateKindHandshakeProgressed, upd.Kind)\n\trequire.Nil(t, conn)\n\trequire.Error(t, err)\n}\n\nfunc makeInsecureMuxer(t *testing.T) (peer.ID, []sec.SecureTransport) {\n\tt.Helper()\n\tpriv, _, err := crypto.GenerateKeyPair(crypto.Ed25519, 256)\n\trequire.NoError(t, err)\n\tid, err := peer.IDFromPrivateKey(priv)\n\trequire.NoError(t, err)\n\treturn id, []sec.SecureTransport{insecure.NewWithIdentity(insecure.ID, id, priv)}\n}\n\ntype errDialer struct {\n\terr error\n}\n\nfunc (d errDialer) DialContext(_ context.Context, _, _ string) (net.Conn, error) {\n\treturn nil, d.err\n}\n\nfunc TestCustomOverrideTCPDialer(t *testing.T) {\n\tt.Run(\"success\", func(t *testing.T) {\n\t\tpeerA, ia := makeInsecureMuxer(t)\n\t\tua, err := tptu.New(ia, muxers, nil, nil, nil)\n\t\trequire.NoError(t, err)\n\t\tta, err := NewTCPTransport(ua, nil, nil)\n\t\trequire.NoError(t, err)\n\t\tln, err := ta.Listen(ma.StringCast(\"/ip4/127.0.0.1/tcp/0\"))\n\t\trequire.NoError(t, err)\n\t\tdefer ln.Close()\n\n\t\t_, ib := makeInsecureMuxer(t)\n\t\tub, err := tptu.New(ib, muxers, nil, nil, nil)\n\t\trequire.NoError(t, err)\n\t\tcalled := false\n\t\tcustomDialer := func(_ ma.Multiaddr) (ContextDialer, error) {\n\t\t\tcalled = true\n\t\t\treturn &net.Dialer{}, nil\n\t\t}\n\t\ttb, err := NewTCPTransport(ub, nil, nil, WithDialerForAddr(customDialer))\n\t\trequire.NoError(t, err)\n\n\t\tconn, err := tb.Dial(context.Background(), ln.Multiaddr(), peerA)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, conn)\n\t\trequire.True(t, called, \"custom dialer should have been called\")\n\t\tconn.Close()\n\t})\n\n\tt.Run(\"errors\", func(t *testing.T) {\n\t\tpeerA, ia := makeInsecureMuxer(t)\n\t\tua, err := tptu.New(ia, muxers, nil, nil, nil)\n\t\trequire.NoError(t, err)\n\t\tta, err := NewTCPTransport(ua, nil, nil)\n\t\trequire.NoError(t, err)\n\t\tln, err := ta.Listen(ma.StringCast(\"/ip4/127.0.0.1/tcp/0\"))\n\t\trequire.NoError(t, err)\n\t\tdefer ln.Close()\n\n\t\tfor _, test := range []string{\"error in factory\", \"error in custom dialer\"} {\n\t\t\tt.Run(test, func(t *testing.T) {\n\t\t\t\t_, ib := makeInsecureMuxer(t)\n\t\t\t\tub, err := tptu.New(ib, muxers, nil, nil, nil)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tcustomErr := errors.New(\"custom dialer error\")\n\t\t\t\tcustomDialer := func(_ ma.Multiaddr) (ContextDialer, error) {\n\t\t\t\t\tif test == \"error in factory\" {\n\t\t\t\t\t\treturn nil, customErr\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn errDialer{err: customErr}, nil\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ttb, err := NewTCPTransport(ub, nil, nil, WithDialerForAddr(customDialer))\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tconn, err := tb.Dial(context.Background(), ln.Multiaddr(), peerA)\n\t\t\t\trequire.Error(t, err)\n\t\t\t\trequire.ErrorContains(t, err, customErr.Error())\n\t\t\t\trequire.Nil(t, conn)\n\t\t\t})\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "p2p/transport/tcpreuse/connwithscope.go",
    "content": "package tcpreuse\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/tcpreuse/internal/sampledconn\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\ntype connWithScope struct {\n\tsampledconn.ManetTCPConnInterface\n\tConnScope network.ConnManagementScope\n}\n\nfunc (c *connWithScope) Close() error {\n\tdefer c.ConnScope.Done()\n\treturn c.ManetTCPConnInterface.Close()\n}\n\nfunc manetConnWithScope(c manet.Conn, scope network.ConnManagementScope) (*connWithScope, error) {\n\tif tcpconn, ok := c.(sampledconn.ManetTCPConnInterface); ok {\n\t\treturn &connWithScope{tcpconn, scope}, nil\n\t}\n\n\treturn nil, fmt.Errorf(\"manet.Conn is not a TCP Conn\")\n}\n"
  },
  {
    "path": "p2p/transport/tcpreuse/demultiplex.go",
    "content": "package tcpreuse\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/p2p/transport/tcpreuse/internal/sampledconn\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\n// This is reading the first 3 bytes of the first packet after the handshake.\n// It's set to the default TCP connect timeout in the TCP Transport.\n//\n// A var so we can change it in tests.\nvar identifyConnTimeout = 5 * time.Second\n\ntype DemultiplexedConnType int\n\nconst (\n\tDemultiplexedConnType_Unknown DemultiplexedConnType = iota\n\tDemultiplexedConnType_MultistreamSelect\n\tDemultiplexedConnType_HTTP\n\tDemultiplexedConnType_TLS\n)\n\nfunc (t DemultiplexedConnType) String() string {\n\tswitch t {\n\tcase DemultiplexedConnType_MultistreamSelect:\n\t\treturn \"MultistreamSelect\"\n\tcase DemultiplexedConnType_HTTP:\n\t\treturn \"HTTP\"\n\tcase DemultiplexedConnType_TLS:\n\t\treturn \"TLS\"\n\tdefault:\n\t\treturn fmt.Sprintf(\"Unknown(%d)\", int(t))\n\t}\n}\n\nfunc (t DemultiplexedConnType) IsKnown() bool {\n\treturn t >= 1 || t <= 3\n}\n\n// identifyConnType attempts to identify the connection type by peeking at the\n// first few bytes.\n// Its Callers must not use the passed in Conn after this function returns.\n// If an error is returned, the connection will be closed.\nfunc identifyConnType(c manet.Conn) (DemultiplexedConnType, manet.Conn, error) {\n\tif err := c.SetReadDeadline(time.Now().Add(identifyConnTimeout)); err != nil {\n\t\tcloseErr := c.Close()\n\t\treturn 0, nil, errors.Join(err, closeErr)\n\t}\n\n\ts, peekedConn, err := sampledconn.PeekBytes(c)\n\tif err != nil {\n\t\tcloseErr := c.Close()\n\t\treturn 0, nil, errors.Join(err, closeErr)\n\t}\n\n\tif err := peekedConn.SetReadDeadline(time.Time{}); err != nil {\n\t\tcloseErr := peekedConn.Close()\n\t\treturn 0, nil, errors.Join(err, closeErr)\n\t}\n\n\tif IsMultistreamSelect(s) {\n\t\treturn DemultiplexedConnType_MultistreamSelect, peekedConn, nil\n\t}\n\tif IsTLS(s) {\n\t\treturn DemultiplexedConnType_TLS, peekedConn, nil\n\t}\n\tif IsHTTP(s) {\n\t\treturn DemultiplexedConnType_HTTP, peekedConn, nil\n\t}\n\treturn DemultiplexedConnType_Unknown, peekedConn, nil\n}\n\n// Matchers are implemented here instead of in the transports so we can easily fuzz them together.\ntype Prefix = [3]byte\n\nfunc IsMultistreamSelect(s Prefix) bool {\n\treturn string(s[:]) == \"\\x13/m\"\n}\n\nfunc IsHTTP(s Prefix) bool {\n\tswitch string(s[:]) {\n\tcase \"GET\", \"HEA\", \"POS\", \"PUT\", \"DEL\", \"CON\", \"OPT\", \"TRA\", \"PAT\":\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc IsTLS(s Prefix) bool {\n\tswitch string(s[:]) {\n\tcase \"\\x16\\x03\\x01\", \"\\x16\\x03\\x02\", \"\\x16\\x03\\x03\":\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n"
  },
  {
    "path": "p2p/transport/tcpreuse/demultiplex_test.go",
    "content": "package tcpreuse\n\nimport \"testing\"\n\nfunc FuzzClash(f *testing.F) {\n\t// make untyped literals type correctly\n\tadd := func(a, b, c byte) { f.Add(a, b, c) }\n\n\t// multistream-select\n\tadd('\\x13', '/', 'm')\n\t// http\n\tadd('G', 'E', 'T')\n\tadd('H', 'E', 'A')\n\tadd('P', 'O', 'S')\n\tadd('P', 'U', 'T')\n\tadd('D', 'E', 'L')\n\tadd('C', 'O', 'N')\n\tadd('O', 'P', 'T')\n\tadd('T', 'R', 'A')\n\tadd('P', 'A', 'T')\n\t// tls\n\tadd('\\x16', '\\x03', '\\x01')\n\tadd('\\x16', '\\x03', '\\x02')\n\tadd('\\x16', '\\x03', '\\x03')\n\tadd('\\x16', '\\x03', '\\x04')\n\n\tf.Fuzz(func(t *testing.T, a, b, c byte) {\n\t\ts := Prefix{a, b, c}\n\t\tvar total uint\n\n\t\tms := IsMultistreamSelect(s)\n\t\tif ms {\n\t\t\ttotal++\n\t\t}\n\n\t\thttp := IsHTTP(s)\n\t\tif http {\n\t\t\ttotal++\n\t\t}\n\n\t\ttls := IsTLS(s)\n\t\tif tls {\n\t\t\ttotal++\n\t\t}\n\n\t\tif total > 1 {\n\t\t\tt.Errorf(\"clash on: %q; ms: %v; http: %v; tls: %v\", s, ms, http, tls)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "p2p/transport/tcpreuse/dialer.go",
    "content": "package tcpreuse\n\nimport (\n\t\"context\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\n// DialContext is like Dial but takes a context.\nfunc (t *ConnMgr) DialContext(ctx context.Context, raddr ma.Multiaddr) (manet.Conn, error) {\n\tif t.useReuseport() {\n\t\treturn t.reuse.DialContext(ctx, raddr)\n\t}\n\tvar d manet.Dialer\n\treturn d.DialContext(ctx, raddr)\n}\n"
  },
  {
    "path": "p2p/transport/tcpreuse/internal/sampledconn/sampledconn.go",
    "content": "package sampledconn\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"net\"\n\t\"syscall\"\n\t\"time\"\n\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\nconst peekSize = 3\n\ntype PeekedBytes = [peekSize]byte\n\nvar ErrNotTCPConn = errors.New(\"passed conn is not a TCPConn\")\n\nfunc PeekBytes(conn manet.Conn) (PeekedBytes, manet.Conn, error) {\n\tif c, ok := conn.(ManetTCPConnInterface); ok {\n\t\treturn newWrappedSampledConn(c)\n\t}\n\n\treturn PeekedBytes{}, nil, ErrNotTCPConn\n}\n\ntype wrappedSampledConn struct {\n\tManetTCPConnInterface\n\tpeekedBytes PeekedBytes\n\tbytesPeeked uint8\n}\n\n// tcpConnInterface is the interface for TCPConn's functions\n// NOTE: `SyscallConn() (syscall.RawConn, error)` is here to make using this as\n// a TCP Conn easier, but it's a potential footgun as you could skipped the\n// peeked bytes if using the fallback\ntype tcpConnInterface interface {\n\tnet.Conn\n\tsyscall.Conn\n\n\tCloseRead() error\n\tCloseWrite() error\n\n\tSetLinger(sec int) error\n\tSetKeepAlive(keepalive bool) error\n\tSetKeepAlivePeriod(d time.Duration) error\n\tSetNoDelay(noDelay bool) error\n\tMultipathTCP() (bool, error)\n\n\tio.ReaderFrom\n\tio.WriterTo\n}\n\ntype ManetTCPConnInterface interface {\n\tmanet.Conn\n\ttcpConnInterface\n}\n\nfunc newWrappedSampledConn(conn ManetTCPConnInterface) (PeekedBytes, *wrappedSampledConn, error) {\n\ts := &wrappedSampledConn{ManetTCPConnInterface: conn}\n\tn, err := io.ReadFull(conn, s.peekedBytes[:])\n\tif err != nil {\n\t\tif n == 0 && err == io.EOF {\n\t\t\terr = io.ErrUnexpectedEOF\n\t\t}\n\t\treturn s.peekedBytes, nil, err\n\t}\n\treturn s.peekedBytes, s, nil\n}\n\nfunc (sc *wrappedSampledConn) Read(b []byte) (int, error) {\n\tif int(sc.bytesPeeked) != len(sc.peekedBytes) {\n\t\tred := copy(b, sc.peekedBytes[sc.bytesPeeked:])\n\t\tsc.bytesPeeked += uint8(red)\n\t\treturn red, nil\n\t}\n\n\treturn sc.ManetTCPConnInterface.Read(b)\n}\n"
  },
  {
    "path": "p2p/transport/tcpreuse/internal/sampledconn/sampledconn_test.go",
    "content": "package sampledconn\n\nimport (\n\t\"io\"\n\t\"syscall\"\n\t\"testing\"\n\t\"time\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSampledConn(t *testing.T) {\n\ttestCases := []string{\n\t\t\"platform\",\n\t\t\"fallback\",\n\t}\n\n\t// Start a TCP server\n\tlistener, err := manet.Listen(ma.StringCast(\"/ip4/127.0.0.1/tcp/0\"))\n\tassert.NoError(t, err)\n\tdefer listener.Close()\n\n\tserverAddr := listener.Multiaddr()\n\n\t// Server goroutine\n\tgo func() {\n\t\tfor range testCases {\n\t\t\tconn, err := listener.Accept()\n\t\t\tassert.NoError(t, err)\n\t\t\tdefer conn.Close()\n\n\t\t\t// Write some data to the connection\n\t\t\t_, err = conn.Write([]byte(\"hello\"))\n\t\t\tassert.NoError(t, err)\n\t\t}\n\t}()\n\n\t// Give the server a moment to start\n\ttime.Sleep(100 * time.Millisecond)\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc, func(t *testing.T) {\n\t\t\t// Create a TCP client\n\t\t\tclientConn, err := manet.Dial(serverAddr)\n\t\t\tassert.NoError(t, err)\n\t\t\tdefer clientConn.Close()\n\n\t\t\tif tc == \"platform\" {\n\t\t\t\t// Wrap the client connection in SampledConn\n\t\t\t\tpeeked, clientConn, err := PeekBytes(clientConn.(interface {\n\t\t\t\t\tmanet.Conn\n\t\t\t\t\tsyscall.Conn\n\t\t\t\t}))\n\t\t\t\tassert.NoError(t, err)\n\t\t\t\tassert.Equal(t, \"hel\", string(peeked[:]))\n\n\t\t\t\tbuf := make([]byte, 5)\n\t\t\t\t_, err = io.ReadFull(clientConn, buf)\n\t\t\t\tassert.NoError(t, err)\n\t\t\t\tassert.Equal(t, \"hello\", string(buf))\n\t\t\t} else {\n\t\t\t\t// Wrap the client connection in SampledConn\n\t\t\t\tsample, sampledConn, err := newWrappedSampledConn(clientConn.(ManetTCPConnInterface))\n\t\t\t\tassert.NoError(t, err)\n\t\t\t\tassert.Equal(t, \"hel\", string(sample[:]))\n\n\t\t\t\tbuf := make([]byte, 5)\n\t\t\t\t_, err = io.ReadFull(sampledConn, buf)\n\t\t\t\tassert.NoError(t, err)\n\t\t\t\tassert.Equal(t, \"hello\", string(buf))\n\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc spawnServerAndClientConn(t *testing.T) (serverConn manet.Conn, clientConn manet.Conn) {\n\tserverConnChan := make(chan manet.Conn, 1)\n\n\tlistener, err := manet.Listen(ma.StringCast(\"/ip4/127.0.0.1/tcp/0\"))\n\tassert.NoError(t, err)\n\tdefer listener.Close()\n\n\tserverAddr := listener.Multiaddr()\n\n\t// Server goroutine\n\tgo func() {\n\t\tconn, err := listener.Accept()\n\t\tassert.NoError(t, err)\n\t\tserverConnChan <- conn\n\t}()\n\n\t// Give the server a moment to start\n\ttime.Sleep(100 * time.Millisecond)\n\n\t// Create a TCP client\n\tclientConn, err = manet.Dial(serverAddr)\n\tassert.NoError(t, err)\n\n\treturn <-serverConnChan, clientConn\n}\n\nfunc TestHandleNoBytes(t *testing.T) {\n\tserverConn, clientConn := spawnServerAndClientConn(t)\n\tdefer clientConn.Close()\n\n\t// Server goroutine\n\tgo func() {\n\t\tserverConn.Close()\n\t}()\n\t_, _, err := PeekBytes(clientConn.(interface {\n\t\tmanet.Conn\n\t\tsyscall.Conn\n\t}))\n\tassert.ErrorIs(t, err, io.ErrUnexpectedEOF)\n}\n\nfunc TestHandle1ByteAndClose(t *testing.T) {\n\tserverConn, clientConn := spawnServerAndClientConn(t)\n\tdefer clientConn.Close()\n\n\t// Server goroutine\n\tgo func() {\n\t\tdefer serverConn.Close()\n\t\t_, err := serverConn.Write([]byte(\"h\"))\n\t\tassert.NoError(t, err)\n\t}()\n\t_, _, err := PeekBytes(clientConn.(interface {\n\t\tmanet.Conn\n\t\tsyscall.Conn\n\t}))\n\tassert.ErrorIs(t, err, io.ErrUnexpectedEOF)\n}\n\nfunc TestSlowBytes(t *testing.T) {\n\tserverConn, clientConn := spawnServerAndClientConn(t)\n\n\tinterval := 100 * time.Millisecond\n\n\t// Server goroutine\n\tgo func() {\n\t\tdefer serverConn.Close()\n\n\t\ttime.Sleep(interval)\n\t\t_, err := serverConn.Write([]byte(\"h\"))\n\t\tassert.NoError(t, err)\n\t\ttime.Sleep(interval)\n\t\t_, err = serverConn.Write([]byte(\"e\"))\n\t\tassert.NoError(t, err)\n\t\ttime.Sleep(interval)\n\t\t_, err = serverConn.Write([]byte(\"l\"))\n\t\tassert.NoError(t, err)\n\t\ttime.Sleep(interval)\n\t\t_, err = serverConn.Write([]byte(\"lo\"))\n\t\tassert.NoError(t, err)\n\t}()\n\n\tdefer clientConn.Close()\n\n\terr := clientConn.SetReadDeadline(time.Now().Add(interval * 10))\n\trequire.NoError(t, err)\n\n\tpeeked, clientConn, err := PeekBytes(clientConn.(interface {\n\t\tmanet.Conn\n\t\tsyscall.Conn\n\t}))\n\tassert.NoError(t, err)\n\tassert.Equal(t, \"hel\", string(peeked[:]))\n\n\tbuf := make([]byte, 5)\n\t_, err = io.ReadFull(clientConn, buf)\n\tassert.NoError(t, err)\n\tassert.Equal(t, \"hello\", string(buf))\n}\n"
  },
  {
    "path": "p2p/transport/tcpreuse/listener.go",
    "content": "package tcpreuse\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/transport\"\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n\t\"github.com/libp2p/go-libp2p/p2p/net/reuseport\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\nconst acceptQueueSize = 64 // It is fine to read 3 bytes from 64 connections in parallel.\n\n// How long we wait for a connection to be accepted before dropping it.\nconst acceptTimeout = 30 * time.Second\n\nvar log = logging.Logger(\"tcp-demultiplex\")\n\n// ConnMgr enables you to share the same listen address between TCP and WebSocket transports.\ntype ConnMgr struct {\n\tenableReuseport bool\n\treuse           reuseport.Transport\n\tupgrader        transport.Upgrader\n\n\tmx        sync.Mutex\n\tlisteners map[string]*multiplexedListener\n}\n\nfunc NewConnMgr(enableReuseport bool, upgrader transport.Upgrader) *ConnMgr {\n\treturn &ConnMgr{\n\t\tenableReuseport: enableReuseport,\n\t\treuse:           reuseport.Transport{},\n\t\tupgrader:        upgrader,\n\t\tlisteners:       make(map[string]*multiplexedListener),\n\t}\n}\n\nfunc (t *ConnMgr) gatedMaListen(listenAddr ma.Multiaddr) (transport.GatedMaListener, error) {\n\tvar mal manet.Listener\n\tvar err error\n\tif t.useReuseport() {\n\t\tmal, err = t.reuse.Listen(listenAddr)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t} else {\n\t\tmal, err = manet.Listen(listenAddr)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn t.upgrader.GateMaListener(mal), nil\n}\n\nfunc (t *ConnMgr) useReuseport() bool {\n\treturn t.enableReuseport && ReuseportIsAvailable()\n}\n\nfunc getTCPAddr(listenAddr ma.Multiaddr) (ma.Multiaddr, error) {\n\thaveTCP := false\n\taddr, _ := ma.SplitFunc(listenAddr, func(c ma.Component) bool {\n\t\tif haveTCP {\n\t\t\treturn true\n\t\t}\n\t\tif c.Protocol().Code == ma.P_TCP {\n\t\t\thaveTCP = true\n\t\t}\n\t\treturn false\n\t})\n\tif !haveTCP {\n\t\treturn nil, fmt.Errorf(\"invalid listen addr %s, need tcp address\", listenAddr)\n\t}\n\treturn addr, nil\n}\n\n// DemultiplexedListen returns a listener for laddr listening for `connType` connections. The connections\n// accepted from returned listeners need to be upgraded with a `transport.Upgrader`.\n// NOTE: All listeners for port 0 share the same underlying socket, so they have the same specific port.\nfunc (t *ConnMgr) DemultiplexedListen(laddr ma.Multiaddr, connType DemultiplexedConnType) (transport.GatedMaListener, error) {\n\tif !connType.IsKnown() {\n\t\treturn nil, fmt.Errorf(\"unknown connection type: %s\", connType)\n\t}\n\tladdr, err := getTCPAddr(laddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tt.mx.Lock()\n\tdefer t.mx.Unlock()\n\tml, ok := t.listeners[laddr.String()]\n\tif ok {\n\t\tdl, err := ml.DemultiplexedListen(connType)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn dl, nil\n\t}\n\n\tgmal, err := t.gatedMaListen(laddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tctx, cancel := context.WithCancel(context.Background())\n\tcancelFunc := func() error {\n\t\tcancel()\n\t\tt.mx.Lock()\n\t\tdefer t.mx.Unlock()\n\t\tdelete(t.listeners, laddr.String())\n\t\tdelete(t.listeners, gmal.Multiaddr().String())\n\t\treturn gmal.Close()\n\t}\n\tml = &multiplexedListener{\n\t\tGatedMaListener: gmal,\n\t\tlisteners:       make(map[DemultiplexedConnType]*demultiplexedListener),\n\t\tctx:             ctx,\n\t\tcloseFn:         cancelFunc,\n\t}\n\tt.listeners[laddr.String()] = ml\n\tt.listeners[gmal.Multiaddr().String()] = ml\n\n\tdl, err := ml.DemultiplexedListen(connType)\n\tif err != nil {\n\t\tcerr := ml.Close()\n\t\treturn nil, errors.Join(err, cerr)\n\t}\n\n\tml.wg.Add(1)\n\tgo ml.run()\n\n\treturn dl, nil\n}\n\nvar _ transport.GatedMaListener = &demultiplexedListener{}\n\ntype multiplexedListener struct {\n\ttransport.GatedMaListener\n\tlisteners map[DemultiplexedConnType]*demultiplexedListener\n\tmx        sync.RWMutex\n\n\tctx     context.Context\n\tcloseFn func() error\n\twg      sync.WaitGroup\n}\n\nvar ErrListenerExists = errors.New(\"listener already exists for this conn type on this address\")\n\nfunc (m *multiplexedListener) DemultiplexedListen(connType DemultiplexedConnType) (transport.GatedMaListener, error) {\n\tif !connType.IsKnown() {\n\t\treturn nil, fmt.Errorf(\"unknown connection type: %s\", connType)\n\t}\n\n\tm.mx.Lock()\n\tdefer m.mx.Unlock()\n\tif _, ok := m.listeners[connType]; ok {\n\t\treturn nil, ErrListenerExists\n\t}\n\n\tctx, cancel := context.WithCancel(m.ctx)\n\tl := &demultiplexedListener{\n\t\tbuffer:     make(chan *connWithScope),\n\t\tinner:      m.GatedMaListener,\n\t\tctx:        ctx,\n\t\tcancelFunc: cancel,\n\t\tcloseFn:    func() error { m.removeDemultiplexedListener(connType); return nil },\n\t}\n\n\tm.listeners[connType] = l\n\n\treturn l, nil\n}\n\nfunc (m *multiplexedListener) run() error {\n\tdefer m.Close()\n\tdefer m.wg.Done()\n\tacceptQueue := make(chan struct{}, acceptQueueSize)\n\tfor {\n\t\tc, connScope, err := m.GatedMaListener.Accept()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tctx, cancelCtx := context.WithTimeout(m.ctx, acceptTimeout)\n\t\tselect {\n\t\tcase acceptQueue <- struct{}{}:\n\t\tcase <-ctx.Done():\n\t\t\tcancelCtx()\n\t\t\tconnScope.Done()\n\t\t\tc.Close()\n\t\t\tlog.Debug(\"accept queue full, dropping connection\", \"remote_addr\", c.RemoteMultiaddr())\n\t\t\tcontinue\n\t\tcase <-m.ctx.Done():\n\t\t\tcancelCtx()\n\t\t\tconnScope.Done()\n\t\t\tc.Close()\n\t\t\tlog.Debug(\"listener closed; dropping connection\", \"remote_addr\", c.RemoteMultiaddr())\n\t\t\tcontinue\n\t\t}\n\n\t\tm.wg.Add(1)\n\t\tgo func() {\n\t\t\tdefer func() { <-acceptQueue }()\n\t\t\tdefer m.wg.Done()\n\t\t\tdefer cancelCtx()\n\t\t\tt, c, err := identifyConnType(c)\n\t\t\tif err != nil {\n\t\t\t\t// conn closed by identifyConnType\n\t\t\t\tconnScope.Done()\n\t\t\t\tlog.Debug(\"error demultiplexing connection\", \"error\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconnWithScope, err := manetConnWithScope(c, connScope)\n\t\t\tif err != nil {\n\t\t\t\tconnScope.Done()\n\t\t\t\tcloseErr := c.Close()\n\t\t\t\terr = errors.Join(err, closeErr)\n\t\t\t\tlog.Debug(\"error wrapping connection with scope\", \"error\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tm.mx.RLock()\n\t\t\tdemux, ok := m.listeners[t]\n\t\t\tm.mx.RUnlock()\n\t\t\tif !ok {\n\t\t\t\tcloseErr := connWithScope.Close()\n\t\t\t\tif closeErr != nil {\n\t\t\t\t\tlog.Debug(\"no registered listener for demultiplex connection. Error closing the connection\", \"type\", t, \"close_error\", closeErr)\n\t\t\t\t} else {\n\t\t\t\t\tlog.Debug(\"no registered listener for demultiplex connection\", \"type\", t)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tselect {\n\t\t\tcase demux.buffer <- connWithScope:\n\t\t\tcase <-ctx.Done():\n\t\t\t\tlog.Debug(\"accept timeout; dropping connection\", \"remote\", connWithScope.RemoteMultiaddr())\n\t\t\t\tconnWithScope.Close()\n\t\t\t}\n\t\t}()\n\t}\n}\n\nfunc (m *multiplexedListener) Close() error {\n\tm.mx.Lock()\n\tfor _, l := range m.listeners {\n\t\tl.cancelFunc()\n\t}\n\terr := m.closeListener()\n\tm.mx.Unlock()\n\tm.wg.Wait()\n\treturn err\n}\n\nfunc (m *multiplexedListener) closeListener() error {\n\tlerr := m.GatedMaListener.Close()\n\tcerr := m.closeFn()\n\treturn errors.Join(lerr, cerr)\n}\n\nfunc (m *multiplexedListener) removeDemultiplexedListener(c DemultiplexedConnType) {\n\tm.mx.Lock()\n\tdefer m.mx.Unlock()\n\n\tdelete(m.listeners, c)\n\tif len(m.listeners) == 0 {\n\t\tm.closeListener()\n\t\tm.mx.Unlock()\n\t\tm.wg.Wait()\n\t\tm.mx.Lock()\n\t}\n}\n\ntype demultiplexedListener struct {\n\tbuffer     chan *connWithScope\n\tinner      transport.GatedMaListener\n\tctx        context.Context\n\tcancelFunc context.CancelFunc\n\tcloseFn    func() error\n}\n\nfunc (m *demultiplexedListener) Accept() (manet.Conn, network.ConnManagementScope, error) {\n\tselect {\n\tcase c := <-m.buffer:\n\t\treturn c.ManetTCPConnInterface, c.ConnScope, nil\n\tcase <-m.ctx.Done():\n\t\treturn nil, nil, transport.ErrListenerClosed\n\t}\n}\n\nfunc (m *demultiplexedListener) Close() error {\n\tm.cancelFunc()\n\treturn m.closeFn()\n}\n\nfunc (m *demultiplexedListener) Multiaddr() ma.Multiaddr {\n\treturn m.inner.Multiaddr()\n}\n\nfunc (m *demultiplexedListener) Addr() net.Addr {\n\treturn m.inner.Addr()\n}\n"
  },
  {
    "path": "p2p/transport/tcpreuse/listener_test.go",
    "content": "package tcpreuse\n\nimport (\n\t\"context\"\n\t\"crypto/ecdsa\"\n\t\"crypto/elliptic\"\n\t\"crypto/rand\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"crypto/x509/pkix\"\n\t\"fmt\"\n\t\"math/big\"\n\t\"net\"\n\t\"net/http\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gorilla/websocket\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/transport\"\n\ttptu \"github.com/libp2p/go-libp2p/p2p/net/upgrader\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n\t\"github.com/multiformats/go-multistream\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc selfSignedTLSConfig(t *testing.T) *tls.Config {\n\tt.Helper()\n\tpriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)\n\trequire.NoError(t, err)\n\n\tcertTemplate := x509.Certificate{\n\t\tSerialNumber: &big.Int{},\n\t\tSubject: pkix.Name{\n\t\t\tOrganization: []string{\"Test\"},\n\t\t},\n\t\tKeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,\n\t\tExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},\n\t\tBasicConstraintsValid: true,\n\t}\n\n\tderBytes, err := x509.CreateCertificate(rand.Reader, &certTemplate, &certTemplate, &priv.PublicKey, priv)\n\trequire.NoError(t, err)\n\n\tcert := tls.Certificate{\n\t\tCertificate: [][]byte{derBytes},\n\t\tPrivateKey:  priv,\n\t}\n\n\ttlsConfig := &tls.Config{\n\t\tCertificates: []tls.Certificate{cert},\n\t}\n\treturn tlsConfig\n}\n\ntype maListener struct {\n\ttransport.GatedMaListener\n}\n\nvar _ manet.Listener = &maListener{}\n\nfunc (ml *maListener) Accept() (manet.Conn, error) {\n\tc, _, err := ml.GatedMaListener.Accept()\n\treturn c, err\n}\n\ntype wsHandler struct{ conns chan *websocket.Conn }\n\nfunc (wh wsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\tu := websocket.Upgrader{}\n\tc, _ := u.Upgrade(w, r, http.Header{})\n\twh.conns <- c\n}\n\nfunc upgrader(t *testing.T) transport.Upgrader {\n\tt.Helper()\n\tupd, err := tptu.New(nil, nil, nil, &network.NullResourceManager{}, nil)\n\trequire.NoError(t, err)\n\treturn upd\n}\n\nfunc TestListenerSingle(t *testing.T) {\n\tlistenAddr := ma.StringCast(\"/ip4/0.0.0.0/tcp/0\")\n\tconst N = 64\n\tfor _, enableReuseport := range []bool{true, false} {\n\t\tt.Run(fmt.Sprintf(\"multistream-reuseport:%v\", enableReuseport), func(t *testing.T) {\n\t\t\tcm := NewConnMgr(enableReuseport, upgrader(t))\n\t\t\tl, err := cm.DemultiplexedListen(listenAddr, DemultiplexedConnType_MultistreamSelect)\n\t\t\trequire.NoError(t, err)\n\t\t\tgo func() {\n\t\t\t\td := net.Dialer{}\n\t\t\t\tfor i := range N {\n\t\t\t\t\tgo func() {\n\t\t\t\t\t\tctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)\n\t\t\t\t\t\tdefer cancel()\n\t\t\t\t\t\tconn, err := d.DialContext(ctx, l.Addr().Network(), l.Addr().String())\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tt.Error(\"failed to dial\", err, i)\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlconn := multistream.NewMSSelect(conn, \"a\")\n\t\t\t\t\t\tbuf := make([]byte, 10)\n\t\t\t\t\t\t_, err = lconn.Write([]byte(\"hello-multistream\"))\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tt.Error(err)\n\t\t\t\t\t\t}\n\t\t\t\t\t\t_, err = lconn.Read(buf)\n\t\t\t\t\t\tif err == nil {\n\t\t\t\t\t\t\tt.Error(\"expected EOF got nil\")\n\t\t\t\t\t\t}\n\t\t\t\t\t}()\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\tvar wg sync.WaitGroup\n\t\t\tfor range N {\n\t\t\t\tc, _, err := l.Accept()\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\twg.Add(1)\n\t\t\t\tgo func() {\n\t\t\t\t\tdefer wg.Done()\n\t\t\t\t\tcc := multistream.NewMSSelect(c, \"a\")\n\t\t\t\t\tdefer cc.Close()\n\t\t\t\t\tbuf := make([]byte, 30)\n\t\t\t\t\tn, err := cc.Read(buf)\n\t\t\t\t\tif !assert.NoError(t, err) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tif !assert.Equal(t, \"hello-multistream\", string(buf[:n])) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t}\n\t\t\twg.Wait()\n\t\t})\n\n\t\tt.Run(fmt.Sprintf(\"WebSocket-reuseport:%v\", enableReuseport), func(t *testing.T) {\n\t\t\tcm := NewConnMgr(enableReuseport, upgrader(t))\n\t\t\tl, err := cm.DemultiplexedListen(listenAddr, DemultiplexedConnType_HTTP)\n\t\t\trequire.NoError(t, err)\n\t\t\twh := wsHandler{conns: make(chan *websocket.Conn, acceptQueueSize)}\n\t\t\tgo func() {\n\t\t\t\thttp.Serve(manet.NetListener(&maListener{GatedMaListener: l}), wh)\n\t\t\t}()\n\t\t\tgo func() {\n\t\t\t\td := websocket.Dialer{}\n\t\t\t\tfor i := range N {\n\t\t\t\t\tgo func() {\n\t\t\t\t\t\tctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)\n\t\t\t\t\t\tdefer cancel()\n\t\t\t\t\t\tconn, _, err := d.DialContext(ctx, fmt.Sprintf(\"ws://%s\", l.Addr().String()), http.Header{})\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tt.Error(\"failed to dial\", err, i)\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t\terr = conn.WriteMessage(websocket.TextMessage, []byte(\"hello\"))\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tt.Error(err)\n\t\t\t\t\t\t}\n\t\t\t\t\t\t_, _, err = conn.ReadMessage()\n\t\t\t\t\t\tif err == nil {\n\t\t\t\t\t\t\tt.Error(\"expected EOF got nil\")\n\t\t\t\t\t\t}\n\t\t\t\t\t}()\n\t\t\t\t}\n\t\t\t}()\n\t\t\tvar wg sync.WaitGroup\n\t\t\tfor range N {\n\t\t\t\tc := <-wh.conns\n\t\t\t\twg.Add(1)\n\t\t\t\tgo func() {\n\t\t\t\t\tdefer wg.Done()\n\t\t\t\t\tdefer c.Close()\n\t\t\t\t\tmsgType, buf, err := c.ReadMessage()\n\t\t\t\t\tif !assert.NoError(t, err) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tif !assert.Equal(t, msgType, websocket.TextMessage) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tif !assert.Equal(t, \"hello\", string(buf)) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t}\n\t\t\twg.Wait()\n\t\t})\n\n\t\tt.Run(fmt.Sprintf(\"WebSocketTLS-reuseport:%v\", enableReuseport), func(t *testing.T) {\n\t\t\tcm := NewConnMgr(enableReuseport, upgrader(t))\n\t\t\tl, err := cm.DemultiplexedListen(listenAddr, DemultiplexedConnType_TLS)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer l.Close()\n\t\t\twh := wsHandler{conns: make(chan *websocket.Conn, acceptQueueSize)}\n\t\t\tgo func() {\n\t\t\t\ts := http.Server{Handler: wh, TLSConfig: selfSignedTLSConfig(t)}\n\t\t\t\ts.ServeTLS(manet.NetListener(&maListener{GatedMaListener: l}), \"\", \"\")\n\t\t\t}()\n\t\t\tgo func() {\n\t\t\t\td := websocket.Dialer{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}\n\t\t\t\tfor i := range N {\n\t\t\t\t\tgo func() {\n\t\t\t\t\t\tctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)\n\t\t\t\t\t\tdefer cancel()\n\t\t\t\t\t\tconn, _, err := d.DialContext(ctx, fmt.Sprintf(\"wss://%s\", l.Addr().String()), http.Header{})\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tt.Error(\"failed to dial\", err, i)\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t\terr = conn.WriteMessage(websocket.TextMessage, []byte(\"hello\"))\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tt.Error(err)\n\t\t\t\t\t\t}\n\t\t\t\t\t\t_, _, err = conn.ReadMessage()\n\t\t\t\t\t\tif err == nil {\n\t\t\t\t\t\t\tt.Error(\"expected EOF got nil\")\n\t\t\t\t\t\t}\n\t\t\t\t\t}()\n\t\t\t\t}\n\t\t\t}()\n\t\t\tvar wg sync.WaitGroup\n\t\t\tfor range N {\n\t\t\t\tc := <-wh.conns\n\t\t\t\twg.Add(1)\n\t\t\t\tgo func() {\n\t\t\t\t\tdefer wg.Done()\n\t\t\t\t\tdefer c.Close()\n\t\t\t\t\tmsgType, buf, err := c.ReadMessage()\n\t\t\t\t\tif !assert.NoError(t, err) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tif !assert.Equal(t, msgType, websocket.TextMessage) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tif !assert.Equal(t, \"hello\", string(buf)) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t}\n\t\t\twg.Wait()\n\t\t})\n\t}\n}\n\nfunc TestListenerMultiplexed(t *testing.T) {\n\tlistenAddr := ma.StringCast(\"/ip4/0.0.0.0/tcp/0\")\n\tconst N = 20\n\tfor _, enableReuseport := range []bool{true, false} {\n\t\tcm := NewConnMgr(enableReuseport, upgrader(t))\n\t\tmsl, err := cm.DemultiplexedListen(listenAddr, DemultiplexedConnType_MultistreamSelect)\n\t\trequire.NoError(t, err)\n\t\tdefer msl.Close()\n\n\t\twsl, err := cm.DemultiplexedListen(listenAddr, DemultiplexedConnType_HTTP)\n\t\trequire.NoError(t, err)\n\t\tdefer wsl.Close()\n\t\trequire.Equal(t, wsl.Multiaddr(), msl.Multiaddr())\n\t\twh := wsHandler{conns: make(chan *websocket.Conn, acceptQueueSize)}\n\t\tgo func() {\n\t\t\thttp.Serve(manet.NetListener(&maListener{GatedMaListener: wsl}), wh)\n\t\t}()\n\n\t\twssl, err := cm.DemultiplexedListen(listenAddr, DemultiplexedConnType_TLS)\n\t\trequire.NoError(t, err)\n\t\tdefer wssl.Close()\n\t\trequire.Equal(t, wssl.Multiaddr(), wsl.Multiaddr())\n\t\twhs := wsHandler{conns: make(chan *websocket.Conn, acceptQueueSize)}\n\t\tgo func() {\n\t\t\ts := http.Server{Handler: whs, TLSConfig: selfSignedTLSConfig(t)}\n\t\t\ts.ServeTLS(manet.NetListener(&maListener{GatedMaListener: wssl}), \"\", \"\")\n\t\t}()\n\n\t\t// multistream connections\n\t\tgo func() {\n\t\t\td := net.Dialer{}\n\t\t\tfor i := range N {\n\t\t\t\tgo func() {\n\t\t\t\t\tctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)\n\t\t\t\t\tdefer cancel()\n\t\t\t\t\tconn, err := d.DialContext(ctx, msl.Addr().Network(), msl.Addr().String())\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Error(\"failed to dial\", err, i)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tlconn := multistream.NewMSSelect(conn, \"a\")\n\t\t\t\t\tbuf := make([]byte, 10)\n\t\t\t\t\t_, err = lconn.Write([]byte(\"multistream\"))\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Error(err)\n\t\t\t\t\t}\n\t\t\t\t\t_, err = lconn.Read(buf)\n\t\t\t\t\tif err == nil {\n\t\t\t\t\t\tt.Error(\"expected EOF got nil\")\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t}\n\t\t}()\n\n\t\t// ws connections\n\t\tgo func() {\n\t\t\td := websocket.Dialer{}\n\t\t\tfor i := range N {\n\t\t\t\tgo func() {\n\t\t\t\t\tctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)\n\t\t\t\t\tdefer cancel()\n\t\t\t\t\tconn, _, err := d.DialContext(ctx, fmt.Sprintf(\"ws://%s\", msl.Addr().String()), http.Header{})\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Error(\"failed to dial\", err, i)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\terr = conn.WriteMessage(websocket.TextMessage, []byte(\"websocket\"))\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Error(err)\n\t\t\t\t\t}\n\t\t\t\t\t_, _, err = conn.ReadMessage()\n\t\t\t\t\tif err == nil {\n\t\t\t\t\t\tt.Error(\"expected EOF got nil\")\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t}\n\t\t}()\n\n\t\t// wss connections\n\t\tgo func() {\n\t\t\td := websocket.Dialer{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}\n\t\t\tfor i := range N {\n\t\t\t\tgo func() {\n\t\t\t\t\tctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)\n\t\t\t\t\tdefer cancel()\n\t\t\t\t\tconn, _, err := d.DialContext(ctx, fmt.Sprintf(\"wss://%s\", msl.Addr().String()), http.Header{})\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Error(\"failed to dial\", err, i)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\terr = conn.WriteMessage(websocket.TextMessage, []byte(\"websocket-tls\"))\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Error(err)\n\t\t\t\t\t}\n\t\t\t\t\t_, _, err = conn.ReadMessage()\n\t\t\t\t\tif err == nil {\n\t\t\t\t\t\tt.Error(\"expected EOF got nil\")\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t}\n\t\t}()\n\n\t\tvar wg sync.WaitGroup\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tfor range N {\n\t\t\t\tc, _, err := msl.Accept()\n\t\t\t\tif !assert.NoError(t, err) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\twg.Add(1)\n\t\t\t\tgo func() {\n\t\t\t\t\tdefer wg.Done()\n\t\t\t\t\tcc := multistream.NewMSSelect(c, \"a\")\n\t\t\t\t\tdefer cc.Close()\n\t\t\t\t\tbuf := make([]byte, 20)\n\t\t\t\t\tn, err := cc.Read(buf)\n\t\t\t\t\tif !assert.NoError(t, err) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tif !assert.Equal(t, \"multistream\", string(buf[:n])) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t}\n\t\t}()\n\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tfor range N {\n\t\t\t\tc := <-wh.conns\n\t\t\t\twg.Add(1)\n\t\t\t\tgo func() {\n\t\t\t\t\tdefer wg.Done()\n\t\t\t\t\tdefer c.Close()\n\t\t\t\t\tmsgType, buf, err := c.ReadMessage()\n\t\t\t\t\tif !assert.NoError(t, err) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tif !assert.Equal(t, msgType, websocket.TextMessage) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tif !assert.Equal(t, \"websocket\", string(buf)) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t}\n\t\t}()\n\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tfor range N {\n\t\t\t\tc := <-whs.conns\n\t\t\t\twg.Add(1)\n\t\t\t\tgo func() {\n\t\t\t\t\tdefer wg.Done()\n\t\t\t\t\tdefer c.Close()\n\t\t\t\t\tmsgType, buf, err := c.ReadMessage()\n\t\t\t\t\tif !assert.NoError(t, err) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tif !assert.Equal(t, msgType, websocket.TextMessage) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tif !assert.Equal(t, \"websocket-tls\", string(buf)) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t}\n\t\t}()\n\t\twg.Wait()\n\t}\n}\n\nfunc TestListenerClose(t *testing.T) {\n\ttestClose := func(listenAddr ma.Multiaddr) {\n\t\t// listen on port 0\n\t\tcm := NewConnMgr(false, upgrader(t))\n\t\tml, err := cm.DemultiplexedListen(listenAddr, DemultiplexedConnType_MultistreamSelect)\n\t\trequire.NoError(t, err)\n\t\twl, err := cm.DemultiplexedListen(listenAddr, DemultiplexedConnType_HTTP)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, wl.Multiaddr(), ml.Multiaddr())\n\t\twl.Close()\n\n\t\twl, err = cm.DemultiplexedListen(listenAddr, DemultiplexedConnType_HTTP)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, wl.Multiaddr(), ml.Multiaddr())\n\n\t\tml.Close()\n\n\t\tmll, err := cm.DemultiplexedListen(listenAddr, DemultiplexedConnType_MultistreamSelect)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, wl.Multiaddr(), ml.Multiaddr())\n\n\t\tmll.Close()\n\t\twl.Close()\n\n\t\tml, err = cm.DemultiplexedListen(listenAddr, DemultiplexedConnType_MultistreamSelect)\n\t\trequire.NoError(t, err)\n\n\t\t// Now listen on the specific port previously used\n\t\tlistenAddr = ml.Multiaddr()\n\t\twl, err = cm.DemultiplexedListen(listenAddr, DemultiplexedConnType_HTTP)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, wl.Multiaddr(), ml.Multiaddr())\n\t\twl.Close()\n\n\t\twl, err = cm.DemultiplexedListen(listenAddr, DemultiplexedConnType_HTTP)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, wl.Multiaddr(), ml.Multiaddr())\n\n\t\tml.Close()\n\t\twl.Close()\n\t}\n\tlistenAddrs := []ma.Multiaddr{ma.StringCast(\"/ip4/0.0.0.0/tcp/0\"), ma.StringCast(\"/ip6/::/tcp/0\")}\n\tfor _, listenAddr := range listenAddrs {\n\t\ttestClose(listenAddr)\n\t}\n}\n\nfunc setDeferReset[T any](t testing.TB, ptr *T, val T) {\n\tt.Helper()\n\torig := *ptr\n\t*ptr = val\n\tt.Cleanup(func() { *ptr = orig })\n}\n\n// TestHitTimeout asserts that we don't panic in case we fail to peek at the connection.\nfunc TestHitTimeout(t *testing.T) {\n\tsetDeferReset(t, &identifyConnTimeout, 100*time.Millisecond)\n\t// listen on port 0\n\tcm := NewConnMgr(false, upgrader(t))\n\n\tlistenAddr := ma.StringCast(\"/ip4/127.0.0.1/tcp/0\")\n\tml, err := cm.DemultiplexedListen(listenAddr, DemultiplexedConnType_MultistreamSelect)\n\trequire.NoError(t, err)\n\tdefer ml.Close()\n\n\ttcpConn, err := net.Dial(ml.Addr().Network(), ml.Addr().String())\n\trequire.NoError(t, err)\n\n\t// Stall tcp conn for over the timeout.\n\ttime.Sleep(identifyConnTimeout + 100*time.Millisecond)\n\n\ttcpConn.Close()\n}\n"
  },
  {
    "path": "p2p/transport/tcpreuse/reuseport.go",
    "content": "package tcpreuse\n\nimport (\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/libp2p/go-reuseport\"\n)\n\n// envReuseport is the env variable name used to turn off reuse port.\n// It default to true.\nconst envReuseport = \"LIBP2P_TCP_REUSEPORT\"\n\n// EnvReuseportVal stores the value of envReuseport. defaults to true.\nvar EnvReuseportVal = true\n\nfunc init() {\n\tv := strings.ToLower(os.Getenv(envReuseport))\n\tif v == \"false\" || v == \"f\" || v == \"0\" {\n\t\tEnvReuseportVal = false\n\t\tlog.Info(\"REUSEPORT disabled\", \"LIBP2P_TCP_REUSEPORT\", v)\n\t}\n}\n\n// ReuseportIsAvailable returns whether reuseport is available to be used. This\n// is here because we want to be able to turn reuseport on and off selectively.\n// For now we use an ENV variable, as this handles our pressing need:\n//\n//\tLIBP2P_TCP_REUSEPORT=false ipfs daemon\n//\n// If this becomes a sought after feature, we could add this to the config.\n// In the end, reuseport is a stop-gap.\nfunc ReuseportIsAvailable() bool {\n\treturn EnvReuseportVal && reuseport.Available()\n}\n"
  },
  {
    "path": "p2p/transport/testsuite/stream_suite.go",
    "content": "package ttransport\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\tcrand \"crypto/rand\"\n\tmrand \"math/rand\"\n\n\t\"github.com/libp2p/go-libp2p-testing/race\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/transport\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\nvar randomness []byte\n\nvar StressTestTimeout = 1 * time.Minute\n\nfunc init() {\n\t// read 1MB of randomness\n\trandomness = make([]byte, 1<<20)\n\tif _, err := crand.Read(randomness); err != nil {\n\t\tpanic(err)\n\t}\n\n\tif timeout := os.Getenv(\"TEST_STRESS_TIMEOUT_MS\"); timeout != \"\" {\n\t\tif v, err := strconv.ParseInt(timeout, 10, 32); err == nil {\n\t\t\tStressTestTimeout = time.Duration(v) * time.Millisecond\n\t\t}\n\t}\n}\n\ntype Options struct {\n\tConnNum   int\n\tStreamNum int\n\tMsgNum    int\n\tMsgMin    int\n\tMsgMax    int\n}\n\nfunc fullClose(t *testing.T, s network.MuxedStream) {\n\tif err := s.CloseWrite(); err != nil {\n\t\tt.Error(err)\n\t\ts.Reset()\n\t\treturn\n\t}\n\tb, err := io.ReadAll(s)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tif len(b) != 0 {\n\t\tt.Error(\"expected to be done reading\")\n\t}\n\tif err := s.Close(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc randBuf(size int) []byte {\n\tn := len(randomness) - size\n\tif size < 1 {\n\t\tpanic(fmt.Errorf(\"requested too large buffer (%d). max is %d\", size, len(randomness)))\n\t}\n\n\tstart := mrand.Intn(n)\n\treturn randomness[start : start+size]\n}\n\nfunc echoStream(t *testing.T, s network.MuxedStream) {\n\t// echo everything\n\tif _, err := io.Copy(s, s); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc echo(t *testing.T, c transport.CapableConn) {\n\tvar wg sync.WaitGroup\n\tdefer wg.Wait()\n\tfor {\n\t\tstr, err := c.AcceptStream()\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tdefer str.Close()\n\t\t\techoStream(t, str)\n\t\t}()\n\t}\n}\n\nfunc serve(t *testing.T, l transport.Listener) {\n\tvar wg sync.WaitGroup\n\tdefer wg.Wait()\n\n\tfor {\n\t\tc, err := l.Accept()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tdefer c.Close()\n\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\techo(t, c)\n\t\t}()\n\t}\n}\n\nfunc SubtestStress(t *testing.T, ta, tb transport.Transport, maddr ma.Multiaddr, peerA peer.ID, opt Options) {\n\tmsgsize := 1 << 11\n\n\trateLimitN := 5000 // max of 5k funcs, because -race has 8k max.\n\trateLimitChan := make(chan struct{}, rateLimitN)\n\tfor range rateLimitN {\n\t\trateLimitChan <- struct{}{}\n\t}\n\n\trateLimit := func(f func()) {\n\t\t<-rateLimitChan\n\t\tf()\n\t\trateLimitChan <- struct{}{}\n\t}\n\n\twriteStream := func(s network.MuxedStream, bufs chan<- []byte) {\n\t\tfor i := 0; i < opt.MsgNum; i++ {\n\t\t\tbuf := randBuf(msgsize)\n\t\t\tbufs <- buf\n\t\t\tif _, err := s.Write(buf); err != nil {\n\t\t\t\tt.Errorf(\"s.Write(buf): %s\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\treadStream := func(s network.MuxedStream, bufs <-chan []byte) {\n\t\tbuf2 := make([]byte, msgsize)\n\t\ti := 0\n\t\tfor buf1 := range bufs {\n\t\t\ti++\n\n\t\t\tif _, err := io.ReadFull(s, buf2); err != nil {\n\t\t\t\tt.Errorf(\"io.ReadFull(s, buf2): %s\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !bytes.Equal(buf1, buf2) {\n\t\t\t\tt.Errorf(\"buffers not equal (%x != %x)\", buf1[:3], buf2[:3])\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\topenStreamAndRW := func(c network.MuxedConn) {\n\t\ts, err := c.OpenStream(context.Background())\n\t\tif err != nil {\n\t\t\tt.Errorf(\"failed to create NewStream: %s\", err)\n\t\t\treturn\n\t\t}\n\n\t\tbufs := make(chan []byte, opt.MsgNum)\n\t\tgo func() {\n\t\t\twriteStream(s, bufs)\n\t\t\tclose(bufs)\n\t\t}()\n\n\t\treadStream(s, bufs)\n\t\tfullClose(t, s)\n\t}\n\n\topenConnAndRW := func() {\n\t\tvar wg sync.WaitGroup\n\t\tdefer wg.Wait()\n\n\t\tl, err := ta.Listen(maddr)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\treturn\n\t\t}\n\t\tdefer l.Close()\n\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tserve(t, l)\n\t\t}()\n\n\t\tc, err := tb.Dial(context.Background(), l.Multiaddr(), peerA)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\treturn\n\t\t}\n\t\tdefer c.Close()\n\n\t\t// serve the outgoing conn, because some muxers assume\n\t\t// that we _always_ call serve. (this is an error?)\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\techo(t, c)\n\t\t}()\n\n\t\tvar openWg sync.WaitGroup\n\t\tfor i := 0; i < opt.StreamNum; i++ {\n\t\t\topenWg.Add(1)\n\t\t\tgo rateLimit(func() {\n\t\t\t\tdefer openWg.Done()\n\t\t\t\topenStreamAndRW(c)\n\t\t\t})\n\t\t}\n\t\topenWg.Wait()\n\t}\n\n\tvar wg sync.WaitGroup\n\tdefer wg.Wait()\n\tfor i := 0; i < opt.ConnNum; i++ {\n\t\twg.Add(1)\n\t\tgo rateLimit(func() {\n\t\t\tdefer wg.Done()\n\t\t\topenConnAndRW()\n\t\t})\n\t}\n}\n\nfunc SubtestStreamOpenStress(t *testing.T, ta, tb transport.Transport, maddr ma.Multiaddr, peerA peer.ID) {\n\tl, err := ta.Listen(maddr)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer l.Close()\n\n\tcount := 10000\n\tworkers := 5\n\n\tif race.WithRace() {\n\t\t// the race detector can only deal with 8128 simultaneous goroutines, so let's make sure we don't go overboard.\n\t\tcount = 1000\n\t}\n\n\tvar (\n\t\tconnA, connB transport.CapableConn\n\t)\n\n\taccepted := make(chan error, 1)\n\tgo func() {\n\t\tvar err error\n\t\tconnA, err = l.Accept()\n\t\taccepted <- err\n\t}()\n\tconnB, err = tb.Dial(context.Background(), l.Multiaddr(), peerA)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\terr = <-accepted\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdefer func() {\n\t\tif connA != nil {\n\t\t\tconnA.Close()\n\t\t}\n\t\tif connB != nil {\n\t\t\tconnB.Close()\n\t\t}\n\t}()\n\n\tvar wg sync.WaitGroup\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tfor range workers {\n\t\t\twg.Add(1)\n\t\t\tgo func() {\n\t\t\t\tdefer wg.Done()\n\t\t\t\tfor i := 0; i < count; i++ {\n\t\t\t\t\ts, err := connA.OpenStream(context.Background())\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Error(err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\twg.Add(1)\n\t\t\t\t\tgo func() {\n\t\t\t\t\t\tdefer wg.Done()\n\t\t\t\t\t\tfullClose(t, s)\n\t\t\t\t\t}()\n\t\t\t\t}\n\t\t\t}()\n\t\t}\n\t}()\n\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tfor i := 0; i < count*workers; i++ {\n\t\t\tstr, err := connB.AcceptStream()\n\t\t\tif err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\twg.Add(1)\n\t\t\tgo func() {\n\t\t\t\tdefer wg.Done()\n\t\t\t\tfullClose(t, str)\n\t\t\t}()\n\t\t}\n\t}()\n\n\ttimeout := time.After(StressTestTimeout)\n\tdone := make(chan struct{})\n\n\tgo func() {\n\t\twg.Wait()\n\t\tclose(done)\n\t}()\n\n\tselect {\n\tcase <-timeout:\n\t\tt.Fatal(\"timed out receiving streams\")\n\tcase <-done:\n\t}\n}\n\nfunc SubtestStreamReset(t *testing.T, ta, tb transport.Transport, maddr ma.Multiaddr, peerA peer.ID) {\n\tvar wg sync.WaitGroup\n\tdefer wg.Wait()\n\n\tl, err := ta.Listen(maddr)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer l.Close()\n\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\n\t\tmuxa, err := l.Accept()\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\treturn\n\t\t}\n\t\tdefer muxa.Close()\n\n\t\ts, err := muxa.OpenStream(context.Background())\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\treturn\n\t\t}\n\t\tdefer s.Close()\n\n\t\t// Some transports won't open the stream until we write. That's\n\t\t// fine.\n\t\t_, _ = s.Write([]byte(\"foo\"))\n\n\t\ttime.Sleep(time.Millisecond * 50)\n\n\t\t_, err = s.Write([]byte(\"bar\"))\n\t\tif err == nil {\n\t\t\tt.Error(\"should have failed to write\")\n\t\t}\n\n\t}()\n\n\tmuxb, err := tb.Dial(context.Background(), l.Multiaddr(), peerA)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer muxb.Close()\n\n\tstr, err := muxb.AcceptStream()\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\tstr.Reset()\n}\n\nfunc SubtestStress1Conn1Stream1Msg(t *testing.T, ta, tb transport.Transport, maddr ma.Multiaddr, peerA peer.ID) {\n\tSubtestStress(t, ta, tb, maddr, peerA, Options{\n\t\tConnNum:   1,\n\t\tStreamNum: 1,\n\t\tMsgNum:    1,\n\t\tMsgMax:    100,\n\t\tMsgMin:    100,\n\t})\n}\n\nfunc SubtestStress1Conn1Stream100Msg(t *testing.T, ta, tb transport.Transport, maddr ma.Multiaddr, peerA peer.ID) {\n\tSubtestStress(t, ta, tb, maddr, peerA, Options{\n\t\tConnNum:   1,\n\t\tStreamNum: 1,\n\t\tMsgNum:    100,\n\t\tMsgMax:    100,\n\t\tMsgMin:    100,\n\t})\n}\n\nfunc SubtestStress1Conn100Stream100Msg(t *testing.T, ta, tb transport.Transport, maddr ma.Multiaddr, peerA peer.ID) {\n\tSubtestStress(t, ta, tb, maddr, peerA, Options{\n\t\tConnNum:   1,\n\t\tStreamNum: 100,\n\t\tMsgNum:    100,\n\t\tMsgMax:    100,\n\t\tMsgMin:    100,\n\t})\n}\n\nfunc SubtestStressManyConn10Stream50Msg(t *testing.T, ta, tb transport.Transport, maddr ma.Multiaddr, peerA peer.ID) {\n\tconnNum := 5\n\tif runtime.GOOS == \"linux\" {\n\t\t// Linux can handle a higher number of conns here than other platforms in CI.\n\t\t// See https://github.com/libp2p/go-libp2p/issues/1498.\n\t\tconnNum = 50\n\t}\n\tSubtestStress(t, ta, tb, maddr, peerA, Options{\n\t\tConnNum:   connNum,\n\t\tStreamNum: 10,\n\t\tMsgNum:    50,\n\t\tMsgMax:    100,\n\t\tMsgMin:    100,\n\t})\n}\n\nfunc SubtestStress1Conn1000Stream10Msg(t *testing.T, ta, tb transport.Transport, maddr ma.Multiaddr, peerA peer.ID) {\n\tSubtestStress(t, ta, tb, maddr, peerA, Options{\n\t\tConnNum:   1,\n\t\tStreamNum: 1000,\n\t\tMsgNum:    10,\n\t\tMsgMax:    100,\n\t\tMsgMin:    100,\n\t})\n}\n\nfunc SubtestStress1Conn100Stream100Msg10MB(t *testing.T, ta, tb transport.Transport, maddr ma.Multiaddr, peerA peer.ID) {\n\tSubtestStress(t, ta, tb, maddr, peerA, Options{\n\t\tConnNum:   1,\n\t\tStreamNum: 100,\n\t\tMsgNum:    100,\n\t\tMsgMax:    10000,\n\t\tMsgMin:    1000,\n\t})\n}\n"
  },
  {
    "path": "p2p/transport/testsuite/transport_suite.go",
    "content": "package ttransport\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"sync\"\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/transport\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\nvar testData = []byte(\"this is some test data\")\n\nfunc SubtestProtocols(t *testing.T, ta, tb transport.Transport, maddr ma.Multiaddr, _ peer.ID) {\n\trawIPAddr, _ := ma.NewMultiaddr(\"/ip4/1.2.3.4\")\n\tif ta.CanDial(rawIPAddr) || tb.CanDial(rawIPAddr) {\n\t\tt.Error(\"nothing should be able to dial raw IP\")\n\t}\n\n\ttprotos := make(map[int]bool)\n\tfor _, p := range ta.Protocols() {\n\t\ttprotos[p] = true\n\t}\n\n\tif !ta.Proxy() {\n\t\tprotos := maddr.Protocols()\n\t\tproto := protos[len(protos)-1]\n\t\tif !tprotos[proto.Code] {\n\t\t\tt.Errorf(\"transport should have reported that it supports protocol '%s' (%d)\", proto.Name, proto.Code)\n\t\t}\n\t} else {\n\t\tfound := false\n\t\tfor _, proto := range maddr.Protocols() {\n\t\t\tif tprotos[proto.Code] {\n\t\t\t\tfound = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !found {\n\t\t\tt.Errorf(\"didn't find any matching proxy protocols in maddr: %s\", maddr)\n\t\t}\n\t}\n}\n\nfunc SubtestBasic(t *testing.T, ta, tb transport.Transport, maddr ma.Multiaddr, peerA peer.ID) {\n\tctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\n\tlist, err := ta.Listen(maddr)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer list.Close()\n\n\tvar (\n\t\tconnA, connB transport.CapableConn\n\t\tdone         = make(chan struct{})\n\t)\n\tdefer func() {\n\t\t<-done\n\t\tif connA != nil {\n\t\t\tconnA.Close()\n\t\t}\n\t\tif connB != nil {\n\t\t\tconnB.Close()\n\t\t}\n\t}()\n\n\tgo func() {\n\t\tdefer close(done)\n\t\tvar err error\n\t\tconnB, err = list.Accept()\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\treturn\n\t\t}\n\t\ts, err := connB.AcceptStream()\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\treturn\n\t\t}\n\n\t\tbuf, err := io.ReadAll(s)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\treturn\n\t\t}\n\n\t\tif !bytes.Equal(testData, buf) {\n\t\t\tt.Errorf(\"expected %s, got %s\", testData, buf)\n\t\t}\n\n\t\tn, err := s.Write(testData)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\treturn\n\t\t}\n\t\tif n != len(testData) {\n\t\t\tt.Error(err)\n\t\t\treturn\n\t\t}\n\n\t\terr = s.Close()\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t}()\n\n\tif !tb.CanDial(list.Multiaddr()) {\n\t\tt.Error(\"CanDial should have returned true\")\n\t}\n\n\tconnA, err = tb.Dial(ctx, list.Multiaddr(), peerA)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ts, err := connA.OpenStream(context.Background())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tn, err := s.Write(testData)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn\n\t}\n\n\tif n != len(testData) {\n\t\tt.Fatalf(\"failed to write enough data (a->b)\")\n\t\treturn\n\t}\n\n\tif err = s.CloseWrite(); err != nil {\n\t\tt.Fatal(err)\n\t\treturn\n\t}\n\n\tbuf, err := io.ReadAll(s)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn\n\t}\n\tif !bytes.Equal(testData, buf) {\n\t\tt.Errorf(\"expected %s, got %s\", testData, buf)\n\t}\n\n\tif err = s.Close(); err != nil {\n\t\tt.Fatal(err)\n\t\treturn\n\t}\n}\n\nfunc SubtestPingPong(t *testing.T, ta, tb transport.Transport, maddr ma.Multiaddr, peerA peer.ID) {\n\tstreams := 100\n\n\tctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\n\tlist, err := ta.Listen(maddr)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer list.Close()\n\n\tvar (\n\t\tconnA, connB transport.CapableConn\n\t)\n\tdefer func() {\n\t\tif connA != nil {\n\t\t\tconnA.Close()\n\t\t}\n\t\tif connB != nil {\n\t\t\tconnB.Close()\n\t\t}\n\t}()\n\n\tvar wg sync.WaitGroup\n\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tvar err error\n\t\tconnA, err = list.Accept()\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\treturn\n\t\t}\n\n\t\tvar sWg sync.WaitGroup\n\t\tfor range streams {\n\t\t\ts, err := connA.AcceptStream()\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tsWg.Add(1)\n\t\t\tgo func() {\n\t\t\t\tdefer sWg.Done()\n\n\t\t\t\tdata, err := io.ReadAll(s)\n\t\t\t\tif err != nil {\n\t\t\t\t\ts.Reset()\n\t\t\t\t\tt.Error(err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif !bytes.HasPrefix(data, testData) {\n\t\t\t\t\tt.Errorf(\"expected %q to have prefix %q\", string(data), string(testData))\n\t\t\t\t}\n\n\t\t\t\tn, err := s.Write(data)\n\t\t\t\tif err != nil {\n\t\t\t\t\ts.Reset()\n\t\t\t\t\tt.Error(err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif n != len(data) {\n\t\t\t\t\ts.Reset()\n\t\t\t\t\tt.Error(err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\ts.Close()\n\t\t\t}()\n\t\t}\n\t\tsWg.Wait()\n\t}()\n\n\tif !tb.CanDial(list.Multiaddr()) {\n\t\tt.Error(\"CanDial should have returned true\")\n\t}\n\n\tconnB, err = tb.Dial(ctx, list.Multiaddr(), peerA)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor i := range streams {\n\t\ts, err := connB.OpenStream(context.Background())\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twg.Add(1)\n\t\tgo func(i int) {\n\t\t\tdefer wg.Done()\n\t\t\tdata := fmt.Appendf(nil, \"%s - %d\", testData, i)\n\t\t\tn, err := s.Write(data)\n\t\t\tif err != nil {\n\t\t\t\ts.Reset()\n\t\t\t\tt.Error(err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif n != len(data) {\n\t\t\t\ts.Reset()\n\t\t\t\tt.Error(\"failed to write enough data (a->b)\")\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err = s.CloseWrite(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tret, err := io.ReadAll(s)\n\t\t\tif err != nil {\n\t\t\t\ts.Reset()\n\t\t\t\tt.Error(err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !bytes.Equal(data, ret) {\n\t\t\t\tt.Errorf(\"expected %q, got %q\", string(data), string(ret))\n\t\t\t}\n\n\t\t\tif err = s.Close(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t\treturn\n\t\t\t}\n\t\t}(i)\n\t}\n\twg.Wait()\n}\n\nfunc SubtestCancel(t *testing.T, ta, tb transport.Transport, maddr ma.Multiaddr, peerA peer.ID) {\n\tlist, err := ta.Listen(maddr)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer list.Close()\n\n\tctx, cancel := context.WithCancel(context.Background())\n\tcancel()\n\tc, err := tb.Dial(ctx, list.Multiaddr(), peerA)\n\tif err == nil {\n\t\tc.Close()\n\t\tt.Fatal(\"dial should have failed\")\n\t}\n}\n"
  },
  {
    "path": "p2p/transport/testsuite/utils_suite.go",
    "content": "package ttransport\n\nimport (\n\t\"reflect\"\n\t\"runtime\"\n\t\"testing\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/transport\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\ntype TransportSubTestFn func(t *testing.T, ta, tb transport.Transport, maddr ma.Multiaddr, peerA peer.ID)\n\nvar Subtests = []TransportSubTestFn{\n\tSubtestProtocols,\n\tSubtestBasic,\n\tSubtestCancel,\n\tSubtestPingPong,\n\n\t// Stolen from the stream muxer test suite.\n\tSubtestStress1Conn1Stream1Msg,\n\tSubtestStress1Conn1Stream100Msg,\n\tSubtestStress1Conn100Stream100Msg,\n\tSubtestStressManyConn10Stream50Msg,\n\tSubtestStress1Conn1000Stream10Msg,\n\tSubtestStress1Conn100Stream100Msg10MB,\n\tSubtestStreamOpenStress,\n\tSubtestStreamReset,\n}\n\nfunc getFunctionName(i any) string {\n\treturn runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()\n}\n\nfunc SubtestTransport(t *testing.T, ta, tb transport.Transport, addr string, peerA peer.ID) {\n\tt.Helper()\n\tSubtestTransportWithFs(t, ta, tb, addr, peerA, Subtests)\n}\n\nfunc SubtestTransportWithFs(t *testing.T, ta, tb transport.Transport, addr string, peerA peer.ID, tests []TransportSubTestFn) {\n\tmaddr, err := ma.NewMultiaddr(addr)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor _, f := range tests {\n\t\tt.Run(getFunctionName(f), func(t *testing.T) {\n\t\t\tf(t, ta, tb, maddr, peerA)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "p2p/transport/webrtc/connection.go",
    "content": "package libp2pwebrtc\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"net\"\n\t\"sync\"\n\t\"sync/atomic\"\n\n\tic \"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\ttpt \"github.com/libp2p/go-libp2p/core/transport\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/pion/datachannel\"\n\t\"github.com/pion/sctp\"\n\t\"github.com/pion/webrtc/v4\"\n)\n\nvar _ tpt.CapableConn = &connection{}\n\nconst maxAcceptQueueLen = 256\n\ntype errConnectionTimeout struct{}\n\nvar _ net.Error = &errConnectionTimeout{}\n\nfunc (errConnectionTimeout) Error() string   { return \"connection timeout\" }\nfunc (errConnectionTimeout) Timeout() bool   { return true }\nfunc (errConnectionTimeout) Temporary() bool { return false }\n\nvar errConnClosed = errors.New(\"connection closed\")\n\ntype dataChannel struct {\n\tstream  datachannel.ReadWriteCloser\n\tchannel *webrtc.DataChannel\n}\n\ntype connection struct {\n\tpc        *webrtc.PeerConnection\n\ttransport *WebRTCTransport\n\tscope     network.ConnManagementScope\n\n\tcloseOnce sync.Once\n\tcloseErr  error\n\n\tlocalPeer      peer.ID\n\tlocalMultiaddr ma.Multiaddr\n\n\tremotePeer      peer.ID\n\tremoteKey       ic.PubKey\n\tremoteMultiaddr ma.Multiaddr\n\n\tm            sync.Mutex\n\tstreams      map[uint16]*stream\n\tnextStreamID atomic.Int32\n\n\tacceptQueue chan dataChannel\n\n\tctx    context.Context\n\tcancel context.CancelFunc\n}\n\nfunc newConnection(\n\tdirection network.Direction,\n\tpc *webrtc.PeerConnection,\n\ttransport *WebRTCTransport,\n\tscope network.ConnManagementScope,\n\n\tlocalPeer peer.ID,\n\tlocalMultiaddr ma.Multiaddr,\n\n\tremotePeer peer.ID,\n\tremoteKey ic.PubKey,\n\tremoteMultiaddr ma.Multiaddr,\n\tincomingDataChannels chan dataChannel,\n\tpeerConnectionClosedCh chan struct{},\n) (*connection, error) {\n\tctx, cancel := context.WithCancel(context.Background())\n\tc := &connection{\n\t\tpc:        pc,\n\t\ttransport: transport,\n\t\tscope:     scope,\n\n\t\tlocalPeer:      localPeer,\n\t\tlocalMultiaddr: localMultiaddr,\n\n\t\tremotePeer:      remotePeer,\n\t\tremoteKey:       remoteKey,\n\t\tremoteMultiaddr: remoteMultiaddr,\n\t\tctx:             ctx,\n\t\tcancel:          cancel,\n\t\tstreams:         make(map[uint16]*stream),\n\n\t\tacceptQueue: incomingDataChannels,\n\t}\n\tswitch direction {\n\tcase network.DirInbound:\n\t\tc.nextStreamID.Store(1)\n\tcase network.DirOutbound:\n\t\t// stream ID 0 is used for the Noise handshake stream\n\t\tc.nextStreamID.Store(2)\n\t}\n\n\tpc.OnConnectionStateChange(c.onConnectionStateChange)\n\tpc.SCTP().OnClose(func(err error) {\n\t\tif err != nil {\n\t\t\tc.closeWithError(fmt.Errorf(\"%w: %w\", errConnClosed, err))\n\t\t}\n\t\tc.closeWithError(errConnClosed)\n\t})\n\tselect {\n\tcase <-peerConnectionClosedCh:\n\t\tc.Close()\n\t\treturn nil, errConnClosed\n\tdefault:\n\t}\n\treturn c, nil\n}\n\n// ConnState implements transport.CapableConn\nfunc (c *connection) ConnState() network.ConnectionState {\n\treturn network.ConnectionState{Transport: \"webrtc-direct\"}\n}\n\n// Close closes the underlying peerconnection.\nfunc (c *connection) Close() error {\n\tc.closeWithError(errConnClosed)\n\treturn nil\n}\n\nfunc (c *connection) As(target any) bool {\n\tif target, ok := target.(**webrtc.PeerConnection); ok {\n\t\t*target = c.pc\n\t\treturn true\n\t}\n\treturn false\n}\n\n// CloseWithError closes the connection ignoring the error code. As there's no way to signal\n// the remote peer on closing the underlying peerconnection, we ignore the error code.\nfunc (c *connection) CloseWithError(_ network.ConnErrorCode) error {\n\treturn c.Close()\n}\n\n// closeWithError is used to Close the connection when the underlying DTLS connection fails\nfunc (c *connection) closeWithError(err error) {\n\tc.closeOnce.Do(func() {\n\t\tc.closeErr = err\n\t\t// cancel must be called after closeErr is set. This ensures interested goroutines waiting on\n\t\t// ctx.Done can read closeErr without holding the conn lock.\n\t\tc.cancel()\n\t\t// closing peerconnection will close the datachannels associated with the streams\n\t\tc.pc.Close()\n\n\t\tc.m.Lock()\n\t\tstreams := c.streams\n\t\tc.streams = nil\n\t\tc.m.Unlock()\n\t\tfor _, s := range streams {\n\t\t\ts.closeForShutdown(err)\n\t\t}\n\t\tc.scope.Done()\n\t})\n}\n\nfunc (c *connection) IsClosed() bool {\n\treturn c.ctx.Err() != nil\n}\n\nfunc (c *connection) OpenStream(ctx context.Context) (network.MuxedStream, error) {\n\tif c.IsClosed() {\n\t\treturn nil, c.closeErr\n\t}\n\n\tid := c.nextStreamID.Add(2) - 2\n\tif id > math.MaxUint16 {\n\t\treturn nil, errors.New(\"exhausted stream ID space\")\n\t}\n\tstreamID := uint16(id)\n\tdc, err := c.pc.CreateDataChannel(\"\", &webrtc.DataChannelInit{ID: &streamID})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\trwc, err := c.detachChannel(ctx, dc)\n\tif err != nil {\n\t\t// There's a race between webrtc.SCTP.OnClose callback and the underlying\n\t\t// association closing. It's nicer to close the connection here.\n\t\tif errors.Is(err, sctp.ErrStreamClosed) {\n\t\t\tc.closeWithError(errConnClosed)\n\t\t\treturn nil, c.closeErr\n\t\t}\n\t\tdc.Close()\n\t\treturn nil, fmt.Errorf(\"detach channel failed for stream(%d): %w\", streamID, err)\n\t}\n\tstr := newStream(dc, rwc, maxSendMessageSize, func() { c.removeStream(streamID) })\n\tif err := c.addStream(str); err != nil {\n\t\tstr.Reset()\n\t\treturn nil, fmt.Errorf(\"failed to add stream(%d) to connection: %w\", streamID, err)\n\t}\n\treturn str, nil\n}\n\nfunc (c *connection) AcceptStream() (network.MuxedStream, error) {\n\tselect {\n\tcase <-c.ctx.Done():\n\t\treturn nil, c.closeErr\n\tcase dc := <-c.acceptQueue:\n\t\tstr := newStream(dc.channel, dc.stream, maxSendMessageSize, func() { c.removeStream(*dc.channel.ID()) })\n\t\tif err := c.addStream(str); err != nil {\n\t\t\tstr.Reset()\n\t\t\treturn nil, err\n\t\t}\n\t\treturn str, nil\n\t}\n}\n\nfunc (c *connection) LocalPeer() peer.ID            { return c.localPeer }\nfunc (c *connection) RemotePeer() peer.ID           { return c.remotePeer }\nfunc (c *connection) RemotePublicKey() ic.PubKey    { return c.remoteKey }\nfunc (c *connection) LocalMultiaddr() ma.Multiaddr  { return c.localMultiaddr }\nfunc (c *connection) RemoteMultiaddr() ma.Multiaddr { return c.remoteMultiaddr }\nfunc (c *connection) Scope() network.ConnScope      { return c.scope }\nfunc (c *connection) Transport() tpt.Transport      { return c.transport }\n\nfunc (c *connection) addStream(str *stream) error {\n\tc.m.Lock()\n\tdefer c.m.Unlock()\n\tif c.streams == nil {\n\t\treturn c.closeErr\n\t}\n\tif _, ok := c.streams[str.id]; ok {\n\t\treturn errors.New(\"stream ID already exists\")\n\t}\n\tc.streams[str.id] = str\n\treturn nil\n}\n\nfunc (c *connection) removeStream(id uint16) {\n\tc.m.Lock()\n\tdefer c.m.Unlock()\n\tdelete(c.streams, id)\n}\n\nfunc (c *connection) onConnectionStateChange(state webrtc.PeerConnectionState) {\n\tif state == webrtc.PeerConnectionStateFailed || state == webrtc.PeerConnectionStateClosed {\n\t\tc.closeWithError(errConnectionTimeout{})\n\t}\n}\n\n// detachChannel detaches an outgoing channel by taking into account the context\n// passed to `OpenStream` as well the closure of the underlying peerconnection\n//\n// The underlying SCTP stream for a datachannel implements a net.Conn interface.\n// However, the datachannel creates a goroutine which continuously reads from\n// the SCTP stream and surfaces the data using an OnMessage callback.\n//\n// The actual abstractions are as follows: webrtc.DataChannel\n// wraps pion.DataChannel, which wraps sctp.Stream.\n//\n// The goroutine for reading, Detach method,\n// and the OnMessage callback are present at the webrtc.DataChannel level.\n// Detach provides us abstracted access to the underlying pion.DataChannel,\n// which allows us to issue Read calls to the datachannel.\n// This was desired because it was not feasible to introduce backpressure\n// with the OnMessage callbacks. The tradeoff is a change in the semantics of\n// the OnOpen callback, and having to force close Read locally.\nfunc (c *connection) detachChannel(ctx context.Context, dc *webrtc.DataChannel) (datachannel.ReadWriteCloser, error) {\n\tdone := make(chan struct{})\n\n\tvar rwc datachannel.ReadWriteCloser\n\tvar err error\n\t// OnOpen will return immediately for detached datachannels\n\t// refer: https://github.com/pion/webrtc/blob/7ab3174640b3ce15abebc2516a2ca3939b5f105f/datachannel.go#L278-L282\n\tdc.OnOpen(func() {\n\t\trwc, err = dc.Detach()\n\t\t// this is safe since the function should return instantly if the peerconnection is closed\n\t\tclose(done)\n\t})\n\tselect {\n\tcase <-c.ctx.Done():\n\t\treturn nil, c.closeErr\n\tcase <-ctx.Done():\n\t\treturn nil, ctx.Err()\n\tcase <-done:\n\t\treturn rwc, err\n\t}\n}\n"
  },
  {
    "path": "p2p/transport/webrtc/fingerprint.go",
    "content": "package libp2pwebrtc\n\nimport (\n\t\"crypto\"\n\t\"crypto/x509\"\n\t\"errors\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/multiformats/go-multibase\"\n\tmh \"github.com/multiformats/go-multihash\"\n\t\"github.com/pion/webrtc/v4\"\n)\n\n// parseFingerprint is forked from pion to avoid bytes to string alloc,\n// and to avoid the entire hex interspersing when we do not need it anyway\n\nvar errHashUnavailable = errors.New(\"fingerprint: hash algorithm is not linked into the binary\")\n\n// parseFingerprint creates a fingerprint for a certificate using the specified hash algorithm\nfunc parseFingerprint(cert *x509.Certificate, algo crypto.Hash) ([]byte, error) {\n\tif !algo.Available() {\n\t\treturn nil, errHashUnavailable\n\t}\n\th := algo.New()\n\t// Hash.Writer is specified to be never returning an error.\n\t// https://golang.org/pkg/hash/#Hash\n\th.Write(cert.Raw)\n\treturn h.Sum(nil), nil\n}\n\nfunc decodeRemoteFingerprint(maddr ma.Multiaddr) (*mh.DecodedMultihash, error) {\n\tremoteFingerprintMultibase, err := maddr.ValueForProtocol(ma.P_CERTHASH)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t_, data, err := multibase.Decode(remoteFingerprintMultibase)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn mh.Decode(data)\n}\n\nfunc encodeDTLSFingerprint(fp webrtc.DTLSFingerprint) (string, error) {\n\tdigest, err := decodeInterspersedHexFromASCIIString(fp.Value)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tencoded, err := mh.Encode(digest, mh.SHA2_256)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn multibase.Encode(multibase.Base64url, encoded)\n}\n"
  },
  {
    "path": "p2p/transport/webrtc/hex.go",
    "content": "package libp2pwebrtc\n\n// The code in this file is adapted from the Go standard library's hex package.\n// As found in https://cs.opensource.google/go/go/+/refs/tags/go1.20.2:src/encoding/hex/hex.go\n//\n// The reason we adapted the original code is to allow us to deal with interspersed requirements\n// while at the same time hex encoding/decoding, without having to do so in two passes.\n\nimport (\n\t\"encoding/hex\"\n\t\"errors\"\n)\n\n// encodeInterspersedHex encodes a byte slice into a string of hex characters,\n// separating each encoded byte with a colon (':').\n//\n// Example: { 0x01, 0x02, 0x03 } -> \"01:02:03\"\nfunc encodeInterspersedHex(src []byte) string {\n\tif len(src) == 0 {\n\t\treturn \"\"\n\t}\n\ts := hex.EncodeToString(src)\n\tn := len(s)\n\t// Determine number of colons\n\tcolons := n / 2\n\tif n%2 == 0 {\n\t\tcolons--\n\t}\n\tbuffer := make([]byte, n+colons)\n\n\tfor i, j := 0, 0; i < n; i, j = i+2, j+3 {\n\t\tcopy(buffer[j:j+2], s[i:i+2])\n\t\tif j+3 < len(buffer) {\n\t\t\tbuffer[j+2] = ':'\n\t\t}\n\t}\n\treturn string(buffer)\n}\n\nvar errUnexpectedIntersperseHexChar = errors.New(\"unexpected character in interspersed hex string\")\n\n// decodeInterspersedHexFromASCIIString decodes an ASCII string of hex characters into a byte slice,\n// where the hex characters are expected to be separated by a colon (':').\n//\n// NOTE that this function returns an error in case the input string contains non-ASCII characters.\n//\n// Example: \"01:02:03\" -> { 0x01, 0x02, 0x03 }\nfunc decodeInterspersedHexFromASCIIString(s string) ([]byte, error) {\n\tn := len(s)\n\tbuffer := make([]byte, n/3*2+n%3)\n\tj := 0\n\tfor i := range n {\n\t\tif i%3 == 2 {\n\t\t\tif s[i] != ':' {\n\t\t\t\treturn nil, errUnexpectedIntersperseHexChar\n\t\t\t}\n\t\t} else {\n\t\t\tif s[i] == ':' {\n\t\t\t\treturn nil, errUnexpectedIntersperseHexChar\n\t\t\t}\n\t\t\tbuffer[j] = s[i]\n\t\t\tj++\n\t\t}\n\t}\n\tdst := make([]byte, hex.DecodedLen(len(buffer)))\n\tif _, err := hex.Decode(dst, buffer); err != nil {\n\t\treturn nil, err\n\t}\n\treturn dst, nil\n}\n"
  },
  {
    "path": "p2p/transport/webrtc/hex_test.go",
    "content": "package libp2pwebrtc\n\nimport (\n\t\"encoding/hex\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestEncodeInterspersedHex(t *testing.T) {\n\tb, err := hex.DecodeString(\"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"ba:78:16:bf:8f:01:cf:ea:41:41:40:de:5d:ae:22:23:b0:03:61:a3:96:17:7a:9c:b4:10:ff:61:f2:00:15:ad\", encodeInterspersedHex(b))\n}\n\nfunc BenchmarkEncodeInterspersedHex(b *testing.B) {\n\tdata, err := hex.DecodeString(\"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad\")\n\trequire.NoError(b, err)\n\n\tfor i := 0; i < b.N; i++ {\n\t\tencodeInterspersedHex(data)\n\t}\n}\n\nfunc TestDecodeInterpersedHexStringLowerCase(t *testing.T) {\n\tb, err := decodeInterspersedHexFromASCIIString(\"ba:78:16:bf:8f:01:cf:ea:41:41:40:de:5d:ae:22:23:b0:03:61:a3:96:17:7a:9c:b4:10:ff:61:f2:00:15:ad\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad\", hex.EncodeToString(b))\n}\n\nfunc TestDecodeInterpersedHexStringMixedCase(t *testing.T) {\n\tb, err := decodeInterspersedHexFromASCIIString(\"Ba:78:16:BF:8F:01:cf:ea:41:41:40:De:5d:ae:22:23:b0:03:61:a3:96:17:7a:9c:b4:10:FF:61:f2:00:15:ad\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad\", hex.EncodeToString(b))\n}\n\nfunc TestDecodeInterpersedHexStringOneByte(t *testing.T) {\n\tb, err := decodeInterspersedHexFromASCIIString(\"ba\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"ba\", hex.EncodeToString(b))\n}\n\nfunc TestDecodeInterpersedHexBytesLowerCase(t *testing.T) {\n\tb, err := decodeInterspersedHexFromASCIIString(\"ba:78:16:bf:8f:01:cf:ea:41:41:40:de:5d:ae:22:23:b0:03:61:a3:96:17:7a:9c:b4:10:ff:61:f2:00:15:ad\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad\", hex.EncodeToString(b))\n}\n\nfunc BenchmarkDecode(b *testing.B) {\n\tfor i := 0; i < b.N; i++ {\n\t\t_, err := decodeInterspersedHexFromASCIIString(\"ba:78:16:bf:8f:01:cf:ea:41:41:40:de:5d:ae:22:23:b0:03:61:a3:96:17:7a:9c:b4:10:ff:61:f2:00:15:ad\")\n\t\trequire.NoError(b, err)\n\t}\n}\n\nfunc TestDecodeInterpersedHexBytesMixedCase(t *testing.T) {\n\tb, err := decodeInterspersedHexFromASCIIString(\"Ba:78:16:BF:8F:01:cf:ea:41:41:40:De:5d:ae:22:23:b0:03:61:a3:96:17:7a:9c:b4:10:FF:61:f2:00:15:ad\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad\", hex.EncodeToString(b))\n}\n\nfunc TestDecodeInterpersedHexBytesOneByte(t *testing.T) {\n\tb, err := decodeInterspersedHexFromASCIIString(\"ba\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"ba\", hex.EncodeToString(b))\n}\n\nfunc TestEncodeInterperseHexNilSlice(t *testing.T) {\n\trequire.Equal(t, \"\", encodeInterspersedHex(nil))\n\trequire.Equal(t, \"\", encodeInterspersedHex([]byte{}))\n}\n\nfunc TestDecodeInterspersedHexEmpty(t *testing.T) {\n\tb, err := decodeInterspersedHexFromASCIIString(\"\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte{}, b)\n}\n\nfunc TestDecodeInterpersedHexFromASCIIStringEmpty(t *testing.T) {\n\tb, err := decodeInterspersedHexFromASCIIString(\"\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte{}, b)\n}\n\nfunc TestDecodeInterpersedHexInvalid(t *testing.T) {\n\tfor _, v := range []string{\"0\", \"0000\", \"000\"} {\n\t\t_, err := decodeInterspersedHexFromASCIIString(v)\n\t\trequire.Error(t, err)\n\t}\n}\n\nfunc TestDecodeInterpersedHexValid(t *testing.T) {\n\tb, err := decodeInterspersedHexFromASCIIString(\"00\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte{0}, b)\n}\n\nfunc TestDecodeInterpersedHexFromASCIIStringInvalid(t *testing.T) {\n\tfor _, v := range []string{\"0\", \"0000\", \"000\"} {\n\t\t_, err := decodeInterspersedHexFromASCIIString(v)\n\t\trequire.Error(t, err)\n\t}\n}\n\nfunc TestDecodeInterpersedHexFromASCIIStringValid(t *testing.T) {\n\tb, err := decodeInterspersedHexFromASCIIString(\"00\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte{0}, b)\n}\n\nfunc FuzzInterpersedHex(f *testing.F) {\n\tf.Fuzz(func(t *testing.T, b []byte) {\n\t\tdecoded, err := decodeInterspersedHexFromASCIIString(string(b))\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tencoded := encodeInterspersedHex(decoded)\n\t\trequire.Equal(t, strings.ToLower(string(b)), encoded)\n\t})\n}\n\nfunc FuzzInterspersedHexASCII(f *testing.F) {\n\tf.Fuzz(func(t *testing.T, s string) {\n\t\tdecoded, err := decodeInterspersedHexFromASCIIString(s)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tencoded := encodeInterspersedHex(decoded)\n\t\trequire.Equal(t, strings.ToLower(s), encoded)\n\t})\n}\n"
  },
  {
    "path": "p2p/transport/webrtc/listener.go",
    "content": "package libp2pwebrtc\n\nimport (\n\t\"context\"\n\t\"crypto\"\n\t\"encoding/hex\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\ttpt \"github.com/libp2p/go-libp2p/core/transport\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/webrtc/udpmux\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n\t\"github.com/multiformats/go-multibase\"\n\t\"github.com/multiformats/go-multihash\"\n\t\"github.com/pion/webrtc/v4\"\n)\n\ntype connMultiaddrs struct {\n\tlocal, remote ma.Multiaddr\n}\n\nvar _ network.ConnMultiaddrs = &connMultiaddrs{}\n\nfunc (c *connMultiaddrs) LocalMultiaddr() ma.Multiaddr  { return c.local }\nfunc (c *connMultiaddrs) RemoteMultiaddr() ma.Multiaddr { return c.remote }\n\nconst (\n\tcandidateSetupTimeout = 10 * time.Second\n\t// This is higher than other transports(64) as there's no way to detect a peer that has gone away after\n\t// sending the initial connection request message(STUN Binding request). Such peers take up a goroutine\n\t// till connection timeout. As the number of handshakes in parallel is still guarded by the resource\n\t// manager, this higher number is okay.\n\tDefaultMaxInFlightConnections = 128\n)\n\ntype listener struct {\n\ttransport *WebRTCTransport\n\n\tmux *udpmux.UDPMux\n\n\tconfig                    webrtc.Configuration\n\tlocalFingerprint          webrtc.DTLSFingerprint\n\tlocalFingerprintMultibase string\n\n\tlocalAddr      net.Addr\n\tlocalMultiaddr ma.Multiaddr\n\n\t// buffered incoming connections\n\tacceptQueue chan tpt.CapableConn\n\n\t// used to control the lifecycle of the listener\n\tctx    context.Context\n\tcancel context.CancelFunc\n\twg     sync.WaitGroup\n}\n\nvar _ tpt.Listener = &listener{}\n\nfunc newListener(transport *WebRTCTransport, laddr ma.Multiaddr, socket net.PacketConn, config webrtc.Configuration) (*listener, error) {\n\tlocalFingerprints, err := config.Certificates[0].GetFingerprints()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlocalMh, err := hex.DecodeString(strings.ReplaceAll(localFingerprints[0].Value, \":\", \"\"))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tlocalMhBuf, err := multihash.Encode(localMh, multihash.SHA2_256)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tlocalFpMultibase, err := multibase.Encode(multibase.Base64url, localMhBuf)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tl := &listener{\n\t\ttransport:                 transport,\n\t\tconfig:                    config,\n\t\tlocalFingerprint:          localFingerprints[0],\n\t\tlocalFingerprintMultibase: localFpMultibase,\n\t\tlocalMultiaddr:            laddr,\n\t\tlocalAddr:                 socket.LocalAddr(),\n\t\tacceptQueue:               make(chan tpt.CapableConn),\n\t}\n\n\tl.ctx, l.cancel = context.WithCancel(context.Background())\n\tl.mux = udpmux.NewUDPMux(socket)\n\tl.mux.Start()\n\n\tl.wg.Add(1)\n\tgo func() {\n\t\tdefer l.wg.Done()\n\t\tl.listen()\n\t}()\n\n\treturn l, err\n}\n\nfunc (l *listener) listen() {\n\t// Accepting a connection requires instantiating a peerconnection and a noise connection\n\t// which is expensive. We therefore limit the number of in-flight connection requests. A\n\t// connection is considered to be in flight from the instant it is handled until it is\n\t// dequeued by a call to Accept, or errors out in some way.\n\tinFlightSemaphore := make(chan struct{}, l.transport.maxInFlightConnections)\n\tfor {\n\t\tselect {\n\t\tcase inFlightSemaphore <- struct{}{}:\n\t\tcase <-l.ctx.Done():\n\t\t\treturn\n\t\t}\n\n\t\tcandidate, err := l.mux.Accept(l.ctx)\n\t\tif err != nil {\n\t\t\tif l.ctx.Err() == nil {\n\t\t\t\tlog.Debug(\"accepting candidate failed\", \"error\", err)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tgo func() {\n\t\t\tdefer func() { <-inFlightSemaphore }()\n\n\t\t\tctx, cancel := context.WithTimeout(l.ctx, candidateSetupTimeout)\n\t\t\tdefer cancel()\n\n\t\t\tconn, err := l.handleCandidate(ctx, candidate)\n\t\t\tif err != nil {\n\t\t\t\tl.mux.RemoveConnByUfrag(candidate.Ufrag)\n\t\t\t\tlog.Debug(\"could not accept connection\", \"ufrag\", candidate.Ufrag, \"error\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tselect {\n\t\t\tcase <-l.ctx.Done():\n\t\t\t\tlog.Debug(\"dropping connection, listener closed\")\n\t\t\t\tconn.Close()\n\t\t\tcase l.acceptQueue <- conn:\n\t\t\t\t// acceptQueue is an unbuffered channel, so this blocks until the connection is accepted.\n\t\t\t}\n\t\t}()\n\t}\n}\n\nfunc (l *listener) handleCandidate(ctx context.Context, candidate udpmux.Candidate) (tpt.CapableConn, error) {\n\tremoteMultiaddr, err := manet.FromNetAddr(candidate.Addr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif l.transport.gater != nil {\n\t\tlocalAddr, _ := ma.SplitFunc(l.localMultiaddr, func(c ma.Component) bool { return c.Protocol().Code == ma.P_CERTHASH })\n\t\tif !l.transport.gater.InterceptAccept(&connMultiaddrs{local: localAddr, remote: remoteMultiaddr}) {\n\t\t\t// The connection attempt is rejected before we can send the client an error.\n\t\t\t// This means that the connection attempt will time out.\n\t\t\treturn nil, errors.New(\"connection gated\")\n\t\t}\n\t}\n\tscope, err := l.transport.rcmgr.OpenConnection(network.DirInbound, false, remoteMultiaddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tconn, err := l.setupConnection(ctx, scope, remoteMultiaddr, candidate)\n\tif err != nil {\n\t\tscope.Done()\n\t\treturn nil, err\n\t}\n\tif l.transport.gater != nil && !l.transport.gater.InterceptSecured(network.DirInbound, conn.RemotePeer(), conn) {\n\t\tconn.Close()\n\t\treturn nil, errors.New(\"connection gated\")\n\t}\n\treturn conn, nil\n}\n\nfunc (l *listener) setupConnection(\n\tctx context.Context, scope network.ConnManagementScope,\n\tremoteMultiaddr ma.Multiaddr, candidate udpmux.Candidate,\n) (tConn tpt.CapableConn, err error) {\n\tvar w webRTCConnection\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tif w.PeerConnection != nil {\n\t\t\t\t_ = w.PeerConnection.Close()\n\t\t\t}\n\t\t\tif tConn != nil {\n\t\t\t\t_ = tConn.Close()\n\t\t\t}\n\t\t}\n\t}()\n\n\tsettingEngine := webrtc.SettingEngine{LoggerFactory: pionLoggerFactory}\n\tsettingEngine.SetAnsweringDTLSRole(webrtc.DTLSRoleServer)\n\tsettingEngine.SetICECredentials(candidate.Ufrag, candidate.Ufrag)\n\tsettingEngine.SetLite(true)\n\tsettingEngine.SetICEUDPMux(l.mux)\n\tsettingEngine.SetIncludeLoopbackCandidate(true)\n\tsettingEngine.DisableCertificateFingerprintVerification(true)\n\tsettingEngine.SetICETimeouts(\n\t\tl.transport.peerConnectionTimeouts.Disconnect,\n\t\tl.transport.peerConnectionTimeouts.Failed,\n\t\tl.transport.peerConnectionTimeouts.Keepalive,\n\t)\n\t// This is higher than the path MTU due to a bug in the sctp chunking logic.\n\t// Remove this after https://github.com/pion/sctp/pull/301 is included\n\t// in a release.\n\tsettingEngine.SetReceiveMTU(udpmux.ReceiveBufSize)\n\tsettingEngine.DetachDataChannels()\n\tsettingEngine.SetSCTPMaxReceiveBufferSize(sctpReceiveBufferSize)\n\tif err := scope.ReserveMemory(sctpReceiveBufferSize, network.ReservationPriorityMedium); err != nil {\n\t\treturn nil, err\n\t}\n\n\tw, err = newWebRTCConnection(settingEngine, l.config)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"instantiating peer connection failed: %w\", err)\n\t}\n\n\terrC := addOnConnectionStateChangeCallback(w.PeerConnection)\n\t// Infer the client SDP from the incoming STUN message by setting the ice-ufrag.\n\tif err := w.PeerConnection.SetRemoteDescription(webrtc.SessionDescription{\n\t\tSDP:  createClientSDP(candidate.Addr, candidate.Ufrag),\n\t\tType: webrtc.SDPTypeOffer,\n\t}); err != nil {\n\t\treturn nil, err\n\t}\n\tanswer, err := w.PeerConnection.CreateAnswer(nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := w.PeerConnection.SetLocalDescription(answer); err != nil {\n\t\treturn nil, err\n\t}\n\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn nil, ctx.Err()\n\tcase err := <-errC:\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"peer connection failed for ufrag: %s\", candidate.Ufrag)\n\t\t}\n\t}\n\n\t// Run the noise handshake.\n\trwc, err := detachHandshakeDataChannel(ctx, w.HandshakeDataChannel)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\thandshakeChannel := newStream(w.HandshakeDataChannel, rwc, maxSendMessageSize, nil)\n\t// we do not yet know A's peer ID so accept any inbound\n\tremotePubKey, err := l.transport.noiseHandshake(ctx, w.PeerConnection, handshakeChannel, \"\", crypto.SHA256, true)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tremotePeer, err := peer.IDFromPublicKey(remotePubKey)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// earliest point where we know the remote's peerID\n\tif err := scope.SetPeer(remotePeer); err != nil {\n\t\treturn nil, err\n\t}\n\n\tlocalMultiaddrWithoutCerthash, _ := ma.SplitFunc(l.localMultiaddr, func(c ma.Component) bool { return c.Protocol().Code == ma.P_CERTHASH })\n\tconn, err := newConnection(\n\t\tnetwork.DirInbound,\n\t\tw.PeerConnection,\n\t\tl.transport,\n\t\tscope,\n\t\tl.transport.localPeerId,\n\t\tlocalMultiaddrWithoutCerthash,\n\t\tremotePeer,\n\t\tremotePubKey,\n\t\tremoteMultiaddr,\n\t\tw.IncomingDataChannels,\n\t\tw.PeerConnectionClosedCh,\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn conn, err\n}\n\nfunc (l *listener) Accept() (tpt.CapableConn, error) {\n\tselect {\n\tcase <-l.ctx.Done():\n\t\treturn nil, tpt.ErrListenerClosed\n\tcase conn := <-l.acceptQueue:\n\t\treturn conn, nil\n\t}\n}\n\nfunc (l *listener) Close() error {\n\tselect {\n\tcase <-l.ctx.Done():\n\tdefault:\n\t}\n\tl.cancel()\n\tl.mux.Close()\n\tl.wg.Wait()\nloop:\n\tfor {\n\t\tselect {\n\t\tcase conn := <-l.acceptQueue:\n\t\t\tconn.Close()\n\t\tdefault:\n\t\t\tbreak loop\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (l *listener) Addr() net.Addr {\n\treturn l.localAddr\n}\n\nfunc (l *listener) Multiaddr() ma.Multiaddr {\n\treturn l.localMultiaddr\n}\n\n// addOnConnectionStateChangeCallback adds the OnConnectionStateChange to the PeerConnection.\n// The channel returned here:\n// * is closed when the state changes to Connection\n// * receives an error when the state changes to Failed or Closed or Disconnected\nfunc addOnConnectionStateChangeCallback(pc *webrtc.PeerConnection) <-chan error {\n\terrC := make(chan error, 1)\n\tvar once sync.Once\n\tpc.OnConnectionStateChange(func(_ webrtc.PeerConnectionState) {\n\t\tswitch pc.ConnectionState() {\n\t\tcase webrtc.PeerConnectionStateConnected:\n\t\t\tonce.Do(func() { close(errC) })\n\t\t// PeerConnectionStateFailed happens when we fail to negotiate the connection.\n\t\t// PeerConnectionStateDisconnected happens when we disconnect immediately after connecting.\n\t\t// PeerConnectionStateClosed happens when we close the peer connection locally, not when remote closes. We don't need\n\t\t// to error in this case, but it's a no-op, so it doesn't hurt.\n\t\tcase webrtc.PeerConnectionStateFailed, webrtc.PeerConnectionStateClosed, webrtc.PeerConnectionStateDisconnected:\n\t\t\tonce.Do(func() {\n\t\t\t\terrC <- errors.New(\"peerconnection failed\")\n\t\t\t\tclose(errC)\n\t\t\t})\n\t\t}\n\t})\n\treturn errC\n}\n"
  },
  {
    "path": "p2p/transport/webrtc/logger.go",
    "content": "package libp2pwebrtc\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log/slog\"\n\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n\tpionLogging \"github.com/pion/logging\"\n)\n\nvar log = logging.Logger(\"webrtc-transport\")\n\n// pionLog is the logger provided to pion for internal logging\nvar pionLog = logging.Logger(\"webrtc-transport-pion\")\n\n// pionLogger adapts pion's logger to go-libp2p's semantics.\n// Pion logs routine connection events (client disconnects, protocol mismatches,\n// state races) as ERROR/WARN, but these are normal operational noise from a\n// service perspective. We downgrade all pion logs to DEBUG to prevent log spam\n// while preserving debuggability when needed.\ntype pionLogger struct {\n\t*slog.Logger\n}\n\nvar pLog = pionLogger{pionLog}\n\nvar _ pionLogging.LeveledLogger = pLog\n\nfunc (l pionLogger) Debug(s string) {\n\tl.Logger.Debug(s)\n}\n\nfunc (l pionLogger) Debugf(s string, args ...any) {\n\tif l.Logger.Enabled(context.Background(), slog.LevelDebug) {\n\t\tl.Logger.Debug(fmt.Sprintf(s, args...))\n\t}\n}\n\nfunc (l pionLogger) Error(s string) {\n\tl.Logger.Debug(s)\n}\n\nfunc (l pionLogger) Errorf(s string, args ...any) {\n\tif l.Logger.Enabled(context.Background(), slog.LevelDebug) {\n\t\tl.Logger.Debug(fmt.Sprintf(s, args...))\n\t}\n}\n\nfunc (l pionLogger) Info(s string) {\n\tl.Logger.Debug(s)\n}\n\nfunc (l pionLogger) Infof(s string, args ...any) {\n\tif l.Logger.Enabled(context.Background(), slog.LevelDebug) {\n\t\tl.Logger.Debug(fmt.Sprintf(s, args...))\n\t}\n}\n\nfunc (l pionLogger) Warn(s string) {\n\tl.Logger.Debug(s)\n}\n\nfunc (l pionLogger) Warnf(s string, args ...any) {\n\tif l.Logger.Enabled(context.Background(), slog.LevelDebug) {\n\t\tl.Logger.Debug(fmt.Sprintf(s, args...))\n\t}\n}\n\nfunc (l pionLogger) Trace(s string) {\n\tl.Logger.Debug(s)\n}\nfunc (l pionLogger) Tracef(s string, args ...any) {\n\tif l.Logger.Enabled(context.Background(), slog.LevelDebug) {\n\t\tl.Logger.Debug(fmt.Sprintf(s, args...))\n\t}\n}\n\n// loggerFactory returns pLog for all new logger instances\ntype loggerFactory struct{}\n\n// NewLogger returns pLog for all new logger instances. Internally pion creates lots of\n// separate logging objects unnecessarily. To avoid the allocations we use a single log\n// object for all of pion logging.\nfunc (loggerFactory) NewLogger(_ string) pionLogging.LeveledLogger {\n\treturn pLog\n}\n\nvar _ pionLogging.LoggerFactory = loggerFactory{}\n\nvar pionLoggerFactory = loggerFactory{}\n"
  },
  {
    "path": "p2p/transport/webrtc/pb/message.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.6\n// \tprotoc        v5.29.2\n// source: p2p/transport/webrtc/pb/message.proto\n\npackage pb\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Message_Flag int32\n\nconst (\n\t// The sender will no longer send messages on the stream.\n\tMessage_FIN Message_Flag = 0\n\t// The sender will no longer read messages on the stream. Incoming data is\n\t// being discarded on receipt.\n\tMessage_STOP_SENDING Message_Flag = 1\n\t// The sender abruptly terminates the sending part of the stream. The\n\t// receiver can discard any data that it already received on that stream.\n\tMessage_RESET Message_Flag = 2\n\t// Sending the FIN_ACK flag acknowledges the previous receipt of a message\n\t// with the FIN flag set. Receiving a FIN_ACK flag gives the recipient\n\t// confidence that the remote has received all sent messages.\n\tMessage_FIN_ACK Message_Flag = 3\n)\n\n// Enum value maps for Message_Flag.\nvar (\n\tMessage_Flag_name = map[int32]string{\n\t\t0: \"FIN\",\n\t\t1: \"STOP_SENDING\",\n\t\t2: \"RESET\",\n\t\t3: \"FIN_ACK\",\n\t}\n\tMessage_Flag_value = map[string]int32{\n\t\t\"FIN\":          0,\n\t\t\"STOP_SENDING\": 1,\n\t\t\"RESET\":        2,\n\t\t\"FIN_ACK\":      3,\n\t}\n)\n\nfunc (x Message_Flag) Enum() *Message_Flag {\n\tp := new(Message_Flag)\n\t*p = x\n\treturn p\n}\n\nfunc (x Message_Flag) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (Message_Flag) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_p2p_transport_webrtc_pb_message_proto_enumTypes[0].Descriptor()\n}\n\nfunc (Message_Flag) Type() protoreflect.EnumType {\n\treturn &file_p2p_transport_webrtc_pb_message_proto_enumTypes[0]\n}\n\nfunc (x Message_Flag) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Do not use.\nfunc (x *Message_Flag) UnmarshalJSON(b []byte) error {\n\tnum, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)\n\tif err != nil {\n\t\treturn err\n\t}\n\t*x = Message_Flag(num)\n\treturn nil\n}\n\n// Deprecated: Use Message_Flag.Descriptor instead.\nfunc (Message_Flag) EnumDescriptor() ([]byte, []int) {\n\treturn file_p2p_transport_webrtc_pb_message_proto_rawDescGZIP(), []int{0, 0}\n}\n\ntype Message struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tFlag          *Message_Flag          `protobuf:\"varint,1,opt,name=flag,enum=Message_Flag\" json:\"flag,omitempty\"`\n\tMessage       []byte                 `protobuf:\"bytes,2,opt,name=message\" json:\"message,omitempty\"`\n\tErrorCode     *uint32                `protobuf:\"varint,3,opt,name=errorCode\" json:\"errorCode,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Message) Reset() {\n\t*x = Message{}\n\tmi := &file_p2p_transport_webrtc_pb_message_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Message) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Message) ProtoMessage() {}\n\nfunc (x *Message) ProtoReflect() protoreflect.Message {\n\tmi := &file_p2p_transport_webrtc_pb_message_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Message.ProtoReflect.Descriptor instead.\nfunc (*Message) Descriptor() ([]byte, []int) {\n\treturn file_p2p_transport_webrtc_pb_message_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Message) GetFlag() Message_Flag {\n\tif x != nil && x.Flag != nil {\n\t\treturn *x.Flag\n\t}\n\treturn Message_FIN\n}\n\nfunc (x *Message) GetMessage() []byte {\n\tif x != nil {\n\t\treturn x.Message\n\t}\n\treturn nil\n}\n\nfunc (x *Message) GetErrorCode() uint32 {\n\tif x != nil && x.ErrorCode != nil {\n\t\treturn *x.ErrorCode\n\t}\n\treturn 0\n}\n\nvar File_p2p_transport_webrtc_pb_message_proto protoreflect.FileDescriptor\n\nconst file_p2p_transport_webrtc_pb_message_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"%p2p/transport/webrtc/pb/message.proto\\\"\\x9f\\x01\\n\" +\n\t\"\\aMessage\\x12!\\n\" +\n\t\"\\x04flag\\x18\\x01 \\x01(\\x0e2\\r.Message.FlagR\\x04flag\\x12\\x18\\n\" +\n\t\"\\amessage\\x18\\x02 \\x01(\\fR\\amessage\\x12\\x1c\\n\" +\n\t\"\\terrorCode\\x18\\x03 \\x01(\\rR\\terrorCode\\\"9\\n\" +\n\t\"\\x04Flag\\x12\\a\\n\" +\n\t\"\\x03FIN\\x10\\x00\\x12\\x10\\n\" +\n\t\"\\fSTOP_SENDING\\x10\\x01\\x12\\t\\n\" +\n\t\"\\x05RESET\\x10\\x02\\x12\\v\\n\" +\n\t\"\\aFIN_ACK\\x10\\x03B5Z3github.com/libp2p/go-libp2p/p2p/transport/webrtc/pb\"\n\nvar (\n\tfile_p2p_transport_webrtc_pb_message_proto_rawDescOnce sync.Once\n\tfile_p2p_transport_webrtc_pb_message_proto_rawDescData []byte\n)\n\nfunc file_p2p_transport_webrtc_pb_message_proto_rawDescGZIP() []byte {\n\tfile_p2p_transport_webrtc_pb_message_proto_rawDescOnce.Do(func() {\n\t\tfile_p2p_transport_webrtc_pb_message_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_p2p_transport_webrtc_pb_message_proto_rawDesc), len(file_p2p_transport_webrtc_pb_message_proto_rawDesc)))\n\t})\n\treturn file_p2p_transport_webrtc_pb_message_proto_rawDescData\n}\n\nvar file_p2p_transport_webrtc_pb_message_proto_enumTypes = make([]protoimpl.EnumInfo, 1)\nvar file_p2p_transport_webrtc_pb_message_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_p2p_transport_webrtc_pb_message_proto_goTypes = []any{\n\t(Message_Flag)(0), // 0: Message.Flag\n\t(*Message)(nil),   // 1: Message\n}\nvar file_p2p_transport_webrtc_pb_message_proto_depIdxs = []int32{\n\t0, // 0: Message.flag:type_name -> Message.Flag\n\t1, // [1:1] is the sub-list for method output_type\n\t1, // [1:1] is the sub-list for method input_type\n\t1, // [1:1] is the sub-list for extension type_name\n\t1, // [1:1] is the sub-list for extension extendee\n\t0, // [0:1] is the sub-list for field type_name\n}\n\nfunc init() { file_p2p_transport_webrtc_pb_message_proto_init() }\nfunc file_p2p_transport_webrtc_pb_message_proto_init() {\n\tif File_p2p_transport_webrtc_pb_message_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_p2p_transport_webrtc_pb_message_proto_rawDesc), len(file_p2p_transport_webrtc_pb_message_proto_rawDesc)),\n\t\t\tNumEnums:      1,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_p2p_transport_webrtc_pb_message_proto_goTypes,\n\t\tDependencyIndexes: file_p2p_transport_webrtc_pb_message_proto_depIdxs,\n\t\tEnumInfos:         file_p2p_transport_webrtc_pb_message_proto_enumTypes,\n\t\tMessageInfos:      file_p2p_transport_webrtc_pb_message_proto_msgTypes,\n\t}.Build()\n\tFile_p2p_transport_webrtc_pb_message_proto = out.File\n\tfile_p2p_transport_webrtc_pb_message_proto_goTypes = nil\n\tfile_p2p_transport_webrtc_pb_message_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "p2p/transport/webrtc/pb/message.proto",
    "content": "syntax = \"proto2\";\n\noption go_package = \"github.com/libp2p/go-libp2p/p2p/transport/webrtc/pb\";\n\nmessage Message {\n  enum Flag {\n    // The sender will no longer send messages on the stream.\n    FIN = 0;\n    // The sender will no longer read messages on the stream. Incoming data is\n    // being discarded on receipt.\n    STOP_SENDING = 1;\n    // The sender abruptly terminates the sending part of the stream. The\n    // receiver can discard any data that it already received on that stream.\n    RESET = 2;\n    // Sending the FIN_ACK flag acknowledges the previous receipt of a message\n    // with the FIN flag set. Receiving a FIN_ACK flag gives the recipient\n    // confidence that the remote has received all sent messages.\n    FIN_ACK = 3;\n  }\n\n  optional Flag flag=1;\n\n  optional bytes message = 2;\n\n  optional uint32 errorCode = 3;\n}\n"
  },
  {
    "path": "p2p/transport/webrtc/sdp.go",
    "content": "package libp2pwebrtc\n\nimport (\n\t\"crypto\"\n\t\"fmt\"\n\t\"net\"\n\t\"strings\"\n\n\t\"github.com/multiformats/go-multihash\"\n)\n\n// clientSDP describes an SDP format string which can be used\n// to infer a client's SDP offer from the incoming STUN message.\n// The fingerprint used to render a client SDP is arbitrary since\n// it fingerprint verification is disabled in favour of a noise\n// handshake. The max message size is fixed to 16384 bytes.\nconst clientSDP = `v=0\no=- 0 0 IN %[1]s %[2]s\ns=-\nc=IN %[1]s %[2]s\nt=0 0\n\nm=application %[3]d UDP/DTLS/SCTP webrtc-datachannel\na=mid:0\na=ice-options:ice2\na=ice-ufrag:%[4]s\na=ice-pwd:%[4]s\na=fingerprint:sha-256 ba:78:16:bf:8f:01:cf:ea:41:41:40:de:5d:ae:22:23:b0:03:61:a3:96:17:7a:9c:b4:10:ff:61:f2:00:15:ad\na=setup:actpass\na=sctp-port:5000\na=max-message-size:16384\n`\n\nfunc createClientSDP(addr *net.UDPAddr, ufrag string) string {\n\tipVersion := \"IP4\"\n\tif addr.IP.To4() == nil {\n\t\tipVersion = \"IP6\"\n\t}\n\treturn fmt.Sprintf(\n\t\tclientSDP,\n\t\tipVersion,\n\t\taddr.IP,\n\t\taddr.Port,\n\t\tufrag,\n\t)\n}\n\n// serverSDP defines an SDP format string used by a dialer\n// to infer the SDP answer of a server based on the provided\n// multiaddr, and the locally set ICE credentials. The max\n// message size is fixed to 16384 bytes.\nconst serverSDP = `v=0\no=- 0 0 IN %[1]s %[2]s\ns=-\nt=0 0\na=ice-lite\nm=application %[3]d UDP/DTLS/SCTP webrtc-datachannel\nc=IN %[1]s %[2]s\na=mid:0\na=ice-options:ice2\na=ice-ufrag:%[4]s\na=ice-pwd:%[4]s\na=fingerprint:%[5]s\n\na=setup:passive\na=sctp-port:5000\na=max-message-size:16384\na=candidate:1 1 UDP 1 %[2]s %[3]d typ host\na=end-of-candidates\n`\n\nfunc createServerSDP(addr *net.UDPAddr, ufrag string, fingerprint multihash.DecodedMultihash) (string, error) {\n\tipVersion := \"IP4\"\n\tif addr.IP.To4() == nil {\n\t\tipVersion = \"IP6\"\n\t}\n\n\tsdpString, err := getSupportedSDPString(fingerprint.Code)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tvar builder strings.Builder\n\tbuilder.Grow(len(fingerprint.Digest)*3 + 8)\n\tbuilder.WriteString(sdpString)\n\tbuilder.WriteByte(' ')\n\tbuilder.WriteString(encodeInterspersedHex(fingerprint.Digest))\n\tfp := builder.String()\n\n\treturn fmt.Sprintf(\n\t\tserverSDP,\n\t\tipVersion,\n\t\taddr.IP,\n\t\taddr.Port,\n\t\tufrag,\n\t\tfp,\n\t), nil\n}\n\n// getSupportedSDPHash converts a multihash code to the\n// corresponding crypto.Hash for supported protocols. If a\n// crypto.Hash cannot be found, it returns `(0, false)`\nfunc getSupportedSDPHash(code uint64) (crypto.Hash, bool) {\n\tswitch code {\n\tcase multihash.MD5:\n\t\treturn crypto.MD5, true\n\tcase multihash.SHA1:\n\t\treturn crypto.SHA1, true\n\tcase multihash.SHA3_224:\n\t\treturn crypto.SHA3_224, true\n\tcase multihash.SHA2_256:\n\t\treturn crypto.SHA256, true\n\tcase multihash.SHA3_384:\n\t\treturn crypto.SHA3_384, true\n\tcase multihash.SHA2_512:\n\t\treturn crypto.SHA512, true\n\tdefault:\n\t\treturn 0, false\n\t}\n}\n\n// getSupportedSDPString converts a multihash code\n// to a string format recognised by pion for fingerprint\n// algorithms\nfunc getSupportedSDPString(code uint64) (string, error) {\n\t// values based on (cryto.Hash).String()\n\tswitch code {\n\tcase multihash.MD5:\n\t\treturn \"md5\", nil\n\tcase multihash.SHA1:\n\t\treturn \"sha-1\", nil\n\tcase multihash.SHA3_224:\n\t\treturn \"sha3-224\", nil\n\tcase multihash.SHA2_256:\n\t\treturn \"sha-256\", nil\n\tcase multihash.SHA3_384:\n\t\treturn \"sha3-384\", nil\n\tcase multihash.SHA2_512:\n\t\treturn \"sha-512\", nil\n\tdefault:\n\t\treturn \"\", fmt.Errorf(\"unsupported hash code (%d)\", code)\n\t}\n}\n"
  },
  {
    "path": "p2p/transport/webrtc/sdp_test.go",
    "content": "package libp2pwebrtc\n\nimport (\n\t\"encoding/hex\"\n\t\"net\"\n\t\"testing\"\n\n\t\"github.com/multiformats/go-multihash\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nconst expectedServerSDP = `v=0\no=- 0 0 IN IP4 0.0.0.0\ns=-\nt=0 0\na=ice-lite\nm=application 37826 UDP/DTLS/SCTP webrtc-datachannel\nc=IN IP4 0.0.0.0\na=mid:0\na=ice-options:ice2\na=ice-ufrag:d2c0fc07-8bb3-42ae-bae2-a6fce8a0b581\na=ice-pwd:d2c0fc07-8bb3-42ae-bae2-a6fce8a0b581\na=fingerprint:sha-256 ba:78:16:bf:8f:01:cf:ea:41:41:40:de:5d:ae:22:23:b0:03:61:a3:96:17:7a:9c:b4:10:ff:61:f2:00:15:ad\n\na=setup:passive\na=sctp-port:5000\na=max-message-size:16384\na=candidate:1 1 UDP 1 0.0.0.0 37826 typ host\na=end-of-candidates\n`\n\nfunc TestRenderServerSDP(t *testing.T) {\n\tencoded, err := hex.DecodeString(\"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad\")\n\trequire.NoError(t, err)\n\n\ttestMultihash := multihash.DecodedMultihash{\n\t\tCode:   multihash.SHA2_256,\n\t\tName:   multihash.Codes[multihash.SHA2_256],\n\t\tDigest: encoded,\n\t\tLength: len(encoded),\n\t}\n\taddr := &net.UDPAddr{IP: net.IPv4(0, 0, 0, 0), Port: 37826}\n\tufrag := \"d2c0fc07-8bb3-42ae-bae2-a6fce8a0b581\"\n\tfingerprint := testMultihash\n\n\tsdp, err := createServerSDP(addr, ufrag, fingerprint)\n\trequire.NoError(t, err)\n\trequire.Equal(t, expectedServerSDP, sdp)\n}\n\nconst expectedClientSDP = `v=0\no=- 0 0 IN IP4 0.0.0.0\ns=-\nc=IN IP4 0.0.0.0\nt=0 0\n\nm=application 37826 UDP/DTLS/SCTP webrtc-datachannel\na=mid:0\na=ice-options:ice2\na=ice-ufrag:d2c0fc07-8bb3-42ae-bae2-a6fce8a0b581\na=ice-pwd:d2c0fc07-8bb3-42ae-bae2-a6fce8a0b581\na=fingerprint:sha-256 ba:78:16:bf:8f:01:cf:ea:41:41:40:de:5d:ae:22:23:b0:03:61:a3:96:17:7a:9c:b4:10:ff:61:f2:00:15:ad\na=setup:actpass\na=sctp-port:5000\na=max-message-size:16384\n`\n\nfunc TestRenderClientSDP(t *testing.T) {\n\taddr := &net.UDPAddr{IP: net.IPv4(0, 0, 0, 0), Port: 37826}\n\tufrag := \"d2c0fc07-8bb3-42ae-bae2-a6fce8a0b581\"\n\tsdp := createClientSDP(addr, ufrag)\n\trequire.Equal(t, expectedClientSDP, sdp)\n}\n\nfunc BenchmarkRenderClientSDP(b *testing.B) {\n\taddr := &net.UDPAddr{IP: net.IPv4(0, 0, 0, 0), Port: 37826}\n\tufrag := \"d2c0fc07-8bb3-42ae-bae2-a6fce8a0b581\"\n\n\tfor i := 0; i < b.N; i++ {\n\t\tcreateClientSDP(addr, ufrag)\n\t}\n}\n\nfunc BenchmarkRenderServerSDP(b *testing.B) {\n\tencoded, _ := hex.DecodeString(\"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad\")\n\n\ttestMultihash := multihash.DecodedMultihash{\n\t\tCode:   multihash.SHA2_256,\n\t\tName:   multihash.Codes[multihash.SHA2_256],\n\t\tDigest: encoded,\n\t\tLength: len(encoded),\n\t}\n\taddr := &net.UDPAddr{IP: net.IPv4(0, 0, 0, 0), Port: 37826}\n\tufrag := \"d2c0fc07-8bb3-42ae-bae2-a6fce8a0b581\"\n\tfingerprint := testMultihash\n\n\tfor i := 0; i < b.N; i++ {\n\t\tcreateServerSDP(addr, ufrag, fingerprint)\n\t}\n\n}\n"
  },
  {
    "path": "p2p/transport/webrtc/stream.go",
    "content": "package libp2pwebrtc\n\nimport (\n\t\"errors\"\n\t\"os\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/webrtc/pb\"\n\t\"github.com/libp2p/go-msgio/pbio\"\n\n\t\"github.com/pion/datachannel\"\n\t\"github.com/pion/webrtc/v4\"\n)\n\nconst (\n\t// maxSendMessageSize is the maximum message size of the Protobuf message we send / receive.\n\t// NOTE: Change `varintOverhead` if you change this.\n\tmaxSendMessageSize = 16384\n\t// Proto overhead assumption is 5 bytes\n\tprotoOverhead = 5\n\t// Varint overhead is assumed to be 2 bytes. This is safe since\n\t// 1. This is only used and when writing message, and\n\t// 2. We only send messages in chunks of `maxMessageSize - varintOverhead`\n\t// which includes the data and the protobuf header. Since `maxMessageSize`\n\t// is less than or equal to 2 ^ 14, the varint will not be more than\n\t// 2 bytes in length.\n\tvarintOverhead = 2\n\n\t// maxTotalControlMessagesSize is the maximum total size of all control messages we will\n\t// write on this stream.\n\t// 4 control messages of size 10 bytes + 10 bytes buffer. This number doesn't need to be\n\t// exact. In the worst case, we enqueue these many bytes more in the webrtc peer connection\n\t// send queue.\n\tmaxTotalControlMessagesSize = 50\n\n\t// maxFINACKWait is the maximum amount of time a stream will wait to read\n\t// FIN_ACK before closing the data channel\n\tmaxFINACKWait = 10 * time.Second\n\n\t// maxReceiveMessageSize is the maximum message size of the Protobuf message we receive.\n\tmaxReceiveMessageSize = 256<<10 + 1<<10 // 1kB buffer\n)\n\ntype receiveState uint8\n\nconst (\n\treceiveStateReceiving receiveState = iota\n\treceiveStateDataRead               // received and read the FIN\n\treceiveStateReset                  // either by calling CloseRead locally, or by receiving\n)\n\ntype sendState uint8\n\nconst (\n\tsendStateSending sendState = iota\n\tsendStateDataSent\n\tsendStateDataReceived\n\tsendStateReset\n)\n\n// Package pion detached data channel into a net.Conn\n// and then a network.MuxedStream\ntype stream struct {\n\tmx sync.Mutex\n\n\t// readerMx ensures that only a single goroutine reads from the reader. Read is not threadsafe\n\t// But we may need to read from reader for control messages from a different goroutine.\n\treaderMx  sync.Mutex\n\treader    pbio.Reader\n\treadError error\n\n\t// this buffer is limited up to a single message. Reason we need it\n\t// is because a reader might read a message midway, and so we need a\n\t// wait to buffer that for as long as the remaining part is not (yet) read\n\tnextMessage  *pb.Message\n\treceiveState receiveState\n\n\twriter             pbio.Writer // concurrent writes prevented by mx\n\twriteStateChanged  chan struct{}\n\tsendState          sendState\n\twriteDeadline      time.Time\n\twriteError         error\n\tmaxSendMessageSize int\n\n\tcontrolMessageReaderOnce sync.Once\n\t// controlMessageReaderEndTime is the end time for reading FIN_ACK from the control\n\t// message reader. We cannot rely on SetReadDeadline to do this since that is prone to\n\t// race condition where a previous deadline timer fires after the latest call to\n\t// SetReadDeadline\n\t// See: https://github.com/pion/sctp/pull/290\n\tcontrolMessageReaderEndTime time.Time\n\n\tonDoneOnce          sync.Once\n\tonDone              func()\n\tid                  uint16 // for logging purposes\n\tdataChannel         *datachannel.DataChannel\n\tcloseForShutdownErr error\n}\n\nvar _ network.MuxedStream = &stream{}\n\nfunc newStream(\n\tchannel *webrtc.DataChannel,\n\trwc datachannel.ReadWriteCloser,\n\tmaxSendMessageSize int,\n\tonDone func(),\n) *stream {\n\ts := &stream{\n\t\treader:             pbio.NewDelimitedReader(rwc, maxReceiveMessageSize),\n\t\twriter:             pbio.NewDelimitedWriter(rwc),\n\t\twriteStateChanged:  make(chan struct{}, 1),\n\t\tid:                 *channel.ID(),\n\t\tdataChannel:        rwc.(*datachannel.DataChannel),\n\t\tonDone:             onDone,\n\t\tmaxSendMessageSize: maxSendMessageSize,\n\t}\n\ts.dataChannel.SetBufferedAmountLowThreshold(uint64(s.sendBufferLowThreshold()))\n\ts.dataChannel.OnBufferedAmountLow(func() {\n\t\ts.notifyWriteStateChanged()\n\t})\n\treturn s\n}\n\nfunc (s *stream) Close() error {\n\ts.mx.Lock()\n\tisClosed := s.closeForShutdownErr != nil\n\ts.mx.Unlock()\n\tif isClosed {\n\t\treturn nil\n\t}\n\tdefer s.cleanup()\n\tcloseWriteErr := s.CloseWrite()\n\tcloseReadErr := s.CloseRead()\n\tif closeWriteErr != nil || closeReadErr != nil {\n\t\ts.Reset()\n\t\treturn errors.Join(closeWriteErr, closeReadErr)\n\t}\n\n\ts.mx.Lock()\n\tif s.controlMessageReaderEndTime.IsZero() {\n\t\ts.controlMessageReaderEndTime = time.Now().Add(maxFINACKWait)\n\t\ts.setDataChannelReadDeadline(time.Now().Add(-1 * time.Hour))\n\t}\n\ts.mx.Unlock()\n\treturn nil\n}\n\nfunc (s *stream) Reset() error {\n\treturn s.ResetWithError(0)\n}\n\nfunc (s *stream) ResetWithError(errCode network.StreamErrorCode) error {\n\ts.mx.Lock()\n\tisClosed := s.closeForShutdownErr != nil\n\ts.mx.Unlock()\n\tif isClosed {\n\t\treturn nil\n\t}\n\n\tdefer s.cleanup()\n\tcancelWriteErr := s.cancelWrite(errCode)\n\tcloseReadErr := s.closeRead(errCode, false)\n\ts.setDataChannelReadDeadline(time.Now().Add(-1 * time.Hour))\n\treturn errors.Join(closeReadErr, cancelWriteErr)\n}\n\nfunc (s *stream) closeForShutdown(closeErr error) {\n\tdefer s.cleanup()\n\n\ts.mx.Lock()\n\tdefer s.mx.Unlock()\n\n\ts.closeForShutdownErr = closeErr\n\ts.notifyWriteStateChanged()\n}\n\nfunc (s *stream) SetDeadline(t time.Time) error {\n\t_ = s.SetReadDeadline(t)\n\treturn s.SetWriteDeadline(t)\n}\n\n// processIncomingFlag processes the flag(FIN/RST/etc) on msg.\n// It needs to be called while the mutex is locked.\nfunc (s *stream) processIncomingFlag(msg *pb.Message) {\n\tif msg.Flag == nil {\n\t\treturn\n\t}\n\n\tswitch msg.GetFlag() {\n\tcase pb.Message_STOP_SENDING:\n\t\t// We must process STOP_SENDING after sending a FIN(sendStateDataSent). Remote peer\n\t\t// may not send a FIN_ACK once it has sent a STOP_SENDING\n\t\tif s.sendState == sendStateSending || s.sendState == sendStateDataSent {\n\t\t\ts.sendState = sendStateReset\n\t\t\ts.writeError = &network.StreamError{Remote: true, ErrorCode: network.StreamErrorCode(msg.GetErrorCode())}\n\t\t}\n\t\ts.notifyWriteStateChanged()\n\tcase pb.Message_FIN_ACK:\n\t\ts.sendState = sendStateDataReceived\n\t\ts.notifyWriteStateChanged()\n\tcase pb.Message_FIN:\n\t\tif s.receiveState == receiveStateReceiving {\n\t\t\ts.receiveState = receiveStateDataRead\n\t\t}\n\t\tif err := s.writer.WriteMsg(&pb.Message{Flag: pb.Message_FIN_ACK.Enum()}); err != nil {\n\t\t\tlog.Debug(\"failed to send FIN_ACK\", \"error\", err)\n\t\t\t// Remote has finished writing all the data It'll stop waiting for the\n\t\t\t// FIN_ACK eventually or will be notified when we close the datachannel\n\t\t}\n\t\ts.spawnControlMessageReader()\n\tcase pb.Message_RESET:\n\t\tif s.receiveState == receiveStateReceiving {\n\t\t\ts.receiveState = receiveStateReset\n\t\t\ts.readError = &network.StreamError{Remote: true, ErrorCode: network.StreamErrorCode(msg.GetErrorCode())}\n\t\t}\n\t\tif s.sendState == sendStateSending || s.sendState == sendStateDataSent {\n\t\t\ts.sendState = sendStateReset\n\t\t\ts.writeError = &network.StreamError{Remote: true, ErrorCode: network.StreamErrorCode(msg.GetErrorCode())}\n\t\t}\n\t\ts.spawnControlMessageReader()\n\t}\n}\n\n// spawnControlMessageReader is used for processing control messages after the reader is closed.\nfunc (s *stream) spawnControlMessageReader() {\n\ts.controlMessageReaderOnce.Do(func() {\n\t\t// Spawn a goroutine to ensure that we're not holding any locks\n\t\tgo func() {\n\t\t\t// cleanup the sctp deadline timer goroutine\n\t\t\tdefer s.setDataChannelReadDeadline(time.Time{})\n\n\t\t\tdefer s.dataChannel.Close()\n\n\t\t\t// Unblock any Read call waiting on reader.ReadMsg\n\t\t\ts.setDataChannelReadDeadline(time.Now().Add(-1 * time.Hour))\n\n\t\t\ts.readerMx.Lock()\n\t\t\t// We have the lock: any readers blocked on reader.ReadMsg have exited.\n\t\t\ts.mx.Lock()\n\t\t\tdefer s.mx.Unlock()\n\t\t\t// From this point onwards only this goroutine will do reader.ReadMsg.\n\t\t\t// We just wanted to ensure any exising readers have exited.\n\t\t\t// Read calls from this point onwards will exit immediately on checking\n\t\t\t// s.readState\n\t\t\ts.readerMx.Unlock()\n\n\t\t\tif s.nextMessage != nil {\n\t\t\t\ts.processIncomingFlag(s.nextMessage)\n\t\t\t\ts.nextMessage = nil\n\t\t\t}\n\t\t\tvar msg pb.Message\n\t\t\tfor {\n\t\t\t\t// Connection closed. No need to cleanup the data channel.\n\t\t\t\tif s.closeForShutdownErr != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// Write half of the stream completed.\n\t\t\t\tif s.sendState == sendStateDataReceived || s.sendState == sendStateReset {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// FIN_ACK wait deadling exceeded.\n\t\t\t\tif !s.controlMessageReaderEndTime.IsZero() && time.Now().After(s.controlMessageReaderEndTime) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\ts.setDataChannelReadDeadline(s.controlMessageReaderEndTime)\n\t\t\t\ts.mx.Unlock()\n\t\t\t\terr := s.reader.ReadMsg(&msg)\n\t\t\t\ts.mx.Lock()\n\t\t\t\tif err != nil {\n\t\t\t\t\t// We have to manually manage deadline exceeded errors since pion/sctp can\n\t\t\t\t\t// return deadline exceeded error for cancelled deadlines\n\t\t\t\t\t// see: https://github.com/pion/sctp/pull/290/files\n\t\t\t\t\tif errors.Is(err, os.ErrDeadlineExceeded) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\ts.processIncomingFlag(&msg)\n\t\t\t}\n\t\t}()\n\t})\n}\n\nfunc (s *stream) cleanup() {\n\ts.onDoneOnce.Do(func() {\n\t\tif s.onDone != nil {\n\t\t\ts.onDone()\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "p2p/transport/webrtc/stream_read.go",
    "content": "package libp2pwebrtc\n\nimport (\n\t\"io\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/webrtc/pb\"\n)\n\nfunc (s *stream) Read(b []byte) (int, error) {\n\ts.readerMx.Lock()\n\tdefer s.readerMx.Unlock()\n\n\ts.mx.Lock()\n\tdefer s.mx.Unlock()\n\n\tif s.closeForShutdownErr != nil {\n\t\treturn 0, s.closeForShutdownErr\n\t}\n\tswitch s.receiveState {\n\tcase receiveStateDataRead:\n\t\treturn 0, io.EOF\n\tcase receiveStateReset:\n\t\treturn 0, s.readError\n\t}\n\n\tif len(b) == 0 {\n\t\treturn 0, nil\n\t}\n\n\tvar read int\n\tfor {\n\t\tif s.nextMessage == nil {\n\t\t\t// load the next message\n\t\t\ts.mx.Unlock()\n\t\t\tvar msg pb.Message\n\t\t\terr := s.reader.ReadMsg(&msg)\n\t\t\ts.mx.Lock()\n\t\t\tif err != nil {\n\t\t\t\t// connection was closed\n\t\t\t\tif s.closeForShutdownErr != nil {\n\t\t\t\t\treturn 0, s.closeForShutdownErr\n\t\t\t\t}\n\t\t\t\tif err == io.EOF {\n\t\t\t\t\t// if the channel was properly closed, return EOF\n\t\t\t\t\tif s.receiveState == receiveStateDataRead {\n\t\t\t\t\t\treturn 0, io.EOF\n\t\t\t\t\t}\n\t\t\t\t\t// This case occurs when remote closes the datachannel without writing a FIN\n\t\t\t\t\t// message. Some implementations discard the buffered data on closing the\n\t\t\t\t\t// datachannel. For these implementations a stream reset will be observed as an\n\t\t\t\t\t// abrupt closing of the datachannel.\n\t\t\t\t\ts.receiveState = receiveStateReset\n\t\t\t\t\ts.readError = &network.StreamError{Remote: true}\n\t\t\t\t\treturn 0, s.readError\n\t\t\t\t}\n\t\t\t\tif s.receiveState == receiveStateReset {\n\t\t\t\t\treturn 0, s.readError\n\t\t\t\t}\n\t\t\t\tif s.receiveState == receiveStateDataRead {\n\t\t\t\t\treturn 0, io.EOF\n\t\t\t\t}\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\ts.nextMessage = &msg\n\t\t}\n\n\t\tif len(s.nextMessage.Message) > 0 {\n\t\t\tn := copy(b, s.nextMessage.Message)\n\t\t\tread += n\n\t\t\ts.nextMessage.Message = s.nextMessage.Message[n:]\n\t\t\treturn read, nil\n\t\t}\n\n\t\t// process flags on the message after reading all the data\n\t\ts.processIncomingFlag(s.nextMessage)\n\t\ts.nextMessage = nil\n\t\tif s.closeForShutdownErr != nil {\n\t\t\treturn read, s.closeForShutdownErr\n\t\t}\n\t\tswitch s.receiveState {\n\t\tcase receiveStateDataRead:\n\t\t\treturn read, io.EOF\n\t\tcase receiveStateReset:\n\t\t\treturn read, s.readError\n\t\t}\n\t}\n}\n\nfunc (s *stream) SetReadDeadline(t time.Time) error {\n\ts.mx.Lock()\n\tdefer s.mx.Unlock()\n\tif s.receiveState == receiveStateReceiving {\n\t\ts.setDataChannelReadDeadline(t)\n\t}\n\treturn nil\n}\n\nfunc (s *stream) setDataChannelReadDeadline(t time.Time) error {\n\treturn s.dataChannel.SetReadDeadline(t)\n}\n\nfunc (s *stream) CloseRead() error {\n\treturn s.closeRead(0, false)\n}\n\nfunc (s *stream) closeRead(errCode network.StreamErrorCode, remote bool) error {\n\ts.mx.Lock()\n\tdefer s.mx.Unlock()\n\tvar err error\n\tif s.receiveState == receiveStateReceiving && s.closeForShutdownErr == nil {\n\t\tcode := uint32(errCode)\n\t\terr = s.writer.WriteMsg(&pb.Message{Flag: pb.Message_STOP_SENDING.Enum(), ErrorCode: &code})\n\t\ts.receiveState = receiveStateReset\n\t\ts.readError = &network.StreamError{Remote: remote, ErrorCode: errCode}\n\t}\n\ts.spawnControlMessageReader()\n\treturn err\n}\n"
  },
  {
    "path": "p2p/transport/webrtc/stream_test.go",
    "content": "package libp2pwebrtc\n\nimport (\n\t\"crypto/rand\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/p2p/transport/webrtc/pb\"\n\t\"github.com/libp2p/go-msgio/pbio\"\n\t\"google.golang.org/protobuf/proto\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\n\t\"github.com/pion/datachannel\"\n\t\"github.com/pion/sctp\"\n\t\"github.com/pion/webrtc/v4\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype detachedChan struct {\n\trwc datachannel.ReadWriteCloser\n\tdc  *webrtc.DataChannel\n}\n\nfunc getDetachedDataChannels(t *testing.T) (detachedChan, detachedChan) {\n\ts := webrtc.SettingEngine{}\n\ts.SetIncludeLoopbackCandidate(true)\n\ts.DetachDataChannels()\n\tapi := webrtc.NewAPI(webrtc.WithSettingEngine(s))\n\n\tofferPC, err := api.NewPeerConnection(webrtc.Configuration{})\n\trequire.NoError(t, err)\n\tt.Cleanup(func() { offerPC.Close() })\n\tofferRWCChan := make(chan detachedChan, 1)\n\tofferDC, err := offerPC.CreateDataChannel(\"data\", nil)\n\trequire.NoError(t, err)\n\tofferDC.OnOpen(func() {\n\t\trwc, err := offerDC.Detach()\n\t\trequire.NoError(t, err)\n\t\tofferRWCChan <- detachedChan{rwc: rwc, dc: offerDC}\n\t})\n\n\tanswerPC, err := api.NewPeerConnection(webrtc.Configuration{})\n\trequire.NoError(t, err)\n\n\tanswerChan := make(chan detachedChan, 1)\n\tanswerPC.OnDataChannel(func(dc *webrtc.DataChannel) {\n\t\tdc.OnOpen(func() {\n\t\t\trwc, err := dc.Detach()\n\t\t\trequire.NoError(t, err)\n\t\t\tanswerChan <- detachedChan{rwc: rwc, dc: dc}\n\t\t})\n\t})\n\tt.Cleanup(func() { answerPC.Close() })\n\n\t// Set ICE Candidate handlers. As soon as a PeerConnection has gathered a candidate send it to the other peer\n\tanswerPC.OnICECandidate(func(candidate *webrtc.ICECandidate) {\n\t\tif candidate != nil {\n\t\t\trequire.NoError(t, offerPC.AddICECandidate(candidate.ToJSON()))\n\t\t}\n\t})\n\tofferPC.OnICECandidate(func(candidate *webrtc.ICECandidate) {\n\t\tif candidate != nil {\n\t\t\trequire.NoError(t, answerPC.AddICECandidate(candidate.ToJSON()))\n\t\t}\n\t})\n\n\t// Set the handler for Peer connection state\n\t// This will notify you when the peer has connected/disconnected\n\tofferPC.OnConnectionStateChange(func(s webrtc.PeerConnectionState) {\n\t\tif s == webrtc.PeerConnectionStateFailed {\n\t\t\tt.Log(\"peer connection failed on offerer\")\n\t\t}\n\t})\n\n\t// Set the handler for Peer connection state\n\t// This will notify you when the peer has connected/disconnected\n\tanswerPC.OnConnectionStateChange(func(s webrtc.PeerConnectionState) {\n\t\tif s == webrtc.PeerConnectionStateFailed {\n\t\t\tt.Log(\"peer connection failed on answerer\")\n\t\t}\n\t})\n\n\t// Now, create an offer\n\toffer, err := offerPC.CreateOffer(nil)\n\trequire.NoError(t, err)\n\trequire.NoError(t, answerPC.SetRemoteDescription(offer))\n\trequire.NoError(t, offerPC.SetLocalDescription(offer))\n\n\tanswer, err := answerPC.CreateAnswer(nil)\n\trequire.NoError(t, err)\n\trequire.NoError(t, offerPC.SetRemoteDescription(answer))\n\trequire.NoError(t, answerPC.SetLocalDescription(answer))\n\n\treturn <-answerChan, <-offerRWCChan\n}\n\n// assertDataChannelOpen checks if the datachannel is open.\n// It sends empty messages on the data channel to check if the channel is still open.\n// The control message reader goroutine depends on exclusive access to datachannel.Read\n// so we have to depend on Write to determine whether the channel has been closed.\nfunc assertDataChannelOpen(t *testing.T, dc *datachannel.DataChannel) {\n\tt.Helper()\n\temptyMsg := &pb.Message{}\n\tmsg, err := proto.Marshal(emptyMsg)\n\tif err != nil {\n\t\tt.Fatal(\"unexpected mashalling error\", err)\n\t}\n\tfor range 3 {\n\t\t_, err := dc.Write(msg)\n\t\tif err != nil {\n\t\t\tt.Fatal(\"unexpected write err: \", err)\n\t\t}\n\t\ttime.Sleep(50 * time.Millisecond)\n\t}\n}\n\n// assertDataChannelClosed checks if the datachannel is closed.\n// It sends empty messages on the data channel to check if the channel has been closed.\n// The control message reader goroutine depends on exclusive access to datachannel.Read\n// so we have to depend on Write to determine whether the channel has been closed.\nfunc assertDataChannelClosed(t *testing.T, dc *datachannel.DataChannel) {\n\tt.Helper()\n\temptyMsg := &pb.Message{}\n\tmsg, err := proto.Marshal(emptyMsg)\n\tif err != nil {\n\t\tt.Fatal(\"unexpected mashalling error\", err)\n\t}\n\tfor range 5 {\n\t\t_, err := dc.Write(msg)\n\t\tif err != nil {\n\t\t\tif errors.Is(err, sctp.ErrStreamClosed) {\n\t\t\t\treturn\n\t\t\t} else {\n\t\t\t\tt.Fatal(\"unexpected write err: \", err)\n\t\t\t}\n\t\t}\n\t\ttime.Sleep(50 * time.Millisecond)\n\t}\n}\n\nfunc TestStreamSimpleReadWriteClose(t *testing.T) {\n\tclient, server := getDetachedDataChannels(t)\n\n\tvar clientDone, serverDone atomic.Bool\n\tclientStr := newStream(client.dc, client.rwc, maxSendMessageSize, func() { clientDone.Store(true) })\n\tserverStr := newStream(server.dc, server.rwc, maxSendMessageSize, func() { serverDone.Store(true) })\n\n\t// send a foobar from the client\n\tn, err := clientStr.Write([]byte(\"foobar\"))\n\trequire.NoError(t, err)\n\trequire.Equal(t, 6, n)\n\trequire.NoError(t, clientStr.CloseWrite())\n\t// writing after closing should error\n\t_, err = clientStr.Write([]byte(\"foobar\"))\n\trequire.Error(t, err)\n\trequire.False(t, clientDone.Load())\n\n\t// now read all the data on the server side\n\tb, err := io.ReadAll(serverStr)\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte(\"foobar\"), b)\n\t// reading again should give another io.EOF\n\tn, err = serverStr.Read(make([]byte, 10))\n\trequire.Zero(t, n)\n\trequire.ErrorIs(t, err, io.EOF)\n\trequire.False(t, serverDone.Load())\n\n\t// send something back\n\t_, err = serverStr.Write([]byte(\"lorem ipsum\"))\n\trequire.NoError(t, err)\n\trequire.NoError(t, serverStr.CloseWrite())\n\n\t// and read it at the client\n\trequire.False(t, clientDone.Load())\n\tb, err = io.ReadAll(clientStr)\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte(\"lorem ipsum\"), b)\n\n\t// stream is only cleaned up on calling Close or Reset\n\tclientStr.Close()\n\tserverStr.Close()\n\trequire.Eventually(t, func() bool { return clientDone.Load() }, 5*time.Second, 100*time.Millisecond)\n\t// Need to call Close for cleanup. Otherwise the FIN_ACK is never read\n\trequire.NoError(t, serverStr.Close())\n\trequire.Eventually(t, func() bool { return serverDone.Load() }, 5*time.Second, 100*time.Millisecond)\n}\n\nfunc TestStreamPartialReads(t *testing.T) {\n\tclient, server := getDetachedDataChannels(t)\n\n\tclientStr := newStream(client.dc, client.rwc, maxSendMessageSize, func() {})\n\tserverStr := newStream(server.dc, server.rwc, maxSendMessageSize, func() {})\n\n\t_, err := serverStr.Write([]byte(\"foobar\"))\n\trequire.NoError(t, err)\n\trequire.NoError(t, serverStr.CloseWrite())\n\n\tn, err := clientStr.Read([]byte{}) // empty read\n\trequire.NoError(t, err)\n\trequire.Zero(t, n)\n\tb := make([]byte, 3)\n\tn, err = clientStr.Read(b)\n\trequire.Equal(t, 3, n)\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte(\"foo\"), b)\n\tb, err = io.ReadAll(clientStr)\n\trequire.NoError(t, err)\n\trequire.Equal(t, []byte(\"bar\"), b)\n}\n\nfunc TestStreamSkipEmptyFrames(t *testing.T) {\n\tclient, server := getDetachedDataChannels(t)\n\n\tclientStr := newStream(client.dc, client.rwc, maxSendMessageSize, func() {})\n\tserverStr := newStream(server.dc, server.rwc, maxSendMessageSize, func() {})\n\n\tfor range 10 {\n\t\trequire.NoError(t, serverStr.writer.WriteMsg(&pb.Message{}))\n\t}\n\trequire.NoError(t, serverStr.writer.WriteMsg(&pb.Message{Message: []byte(\"foo\")}))\n\tfor range 10 {\n\t\trequire.NoError(t, serverStr.writer.WriteMsg(&pb.Message{}))\n\t}\n\trequire.NoError(t, serverStr.writer.WriteMsg(&pb.Message{Message: []byte(\"bar\")}))\n\tfor range 10 {\n\t\trequire.NoError(t, serverStr.writer.WriteMsg(&pb.Message{}))\n\t}\n\trequire.NoError(t, serverStr.writer.WriteMsg(&pb.Message{Flag: pb.Message_FIN.Enum()}))\n\n\tvar read []byte\n\tvar count int\n\tfor range 100 {\n\t\tb := make([]byte, 10)\n\t\tcount++\n\t\tn, err := clientStr.Read(b)\n\t\tread = append(read, b[:n]...)\n\t\tif err == io.EOF {\n\t\t\tbreak\n\t\t}\n\t\trequire.NoError(t, err)\n\t}\n\trequire.LessOrEqual(t, count, 3, \"should've taken a maximum of 3 reads\")\n\trequire.Equal(t, []byte(\"foobar\"), read)\n}\n\nfunc TestStreamReadReturnsOnClose(t *testing.T) {\n\tclient, _ := getDetachedDataChannels(t)\n\n\tclientStr := newStream(client.dc, client.rwc, maxSendMessageSize, func() {})\n\terrChan := make(chan error, 1)\n\tgo func() {\n\t\t_, err := clientStr.Read([]byte{0})\n\t\terrChan <- err\n\t}()\n\ttime.Sleep(100 * time.Millisecond) // give the Read call some time to hit the loop\n\trequire.NoError(t, clientStr.Close())\n\tselect {\n\tcase err := <-errChan:\n\t\trequire.ErrorIs(t, err, network.ErrReset)\n\tcase <-time.After(500 * time.Millisecond):\n\t\tt.Fatal(\"timeout\")\n\t}\n\n\t_, err := clientStr.Read([]byte{0})\n\trequire.ErrorIs(t, err, network.ErrReset)\n}\n\nfunc TestStreamResets(t *testing.T) {\n\tclient, server := getDetachedDataChannels(t)\n\n\tvar clientDone, serverDone atomic.Bool\n\tclientStr := newStream(client.dc, client.rwc, maxSendMessageSize, func() { clientDone.Store(true) })\n\tserverStr := newStream(server.dc, server.rwc, maxSendMessageSize, func() { serverDone.Store(true) })\n\n\t// send a foobar from the client\n\t_, err := clientStr.Write([]byte(\"foobar\"))\n\trequire.NoError(t, err)\n\t_, err = serverStr.Write([]byte(\"lorem ipsum\"))\n\trequire.NoError(t, err)\n\trequire.NoError(t, clientStr.Reset()) // resetting resets both directions\n\trequire.True(t, clientDone.Load())\n\t// attempting to write more data should result in a reset error\n\t_, err = clientStr.Write([]byte(\"foobar\"))\n\trequire.ErrorIs(t, err, network.ErrReset)\n\t// read what the server sent\n\tb, err := io.ReadAll(clientStr)\n\trequire.Empty(t, b)\n\trequire.ErrorIs(t, err, network.ErrReset)\n\n\t// read the data on the server side\n\trequire.False(t, serverDone.Load())\n\tb, err = io.ReadAll(serverStr)\n\trequire.Equal(t, []byte(\"foobar\"), b)\n\trequire.ErrorIs(t, err, network.ErrReset)\n\trequire.Eventually(t, func() bool {\n\t\t_, err := serverStr.Write([]byte(\"foobar\"))\n\t\treturn errors.Is(err, network.ErrReset)\n\t}, time.Second, 50*time.Millisecond)\n\tserverStr.Close()\n\trequire.Eventually(t, func() bool {\n\t\treturn serverDone.Load()\n\t}, time.Second, 50*time.Millisecond)\n}\n\nfunc TestStreamReadDeadlineAsync(t *testing.T) {\n\tclient, server := getDetachedDataChannels(t)\n\n\tclientStr := newStream(client.dc, client.rwc, maxSendMessageSize, func() {})\n\tserverStr := newStream(server.dc, server.rwc, maxSendMessageSize, func() {})\n\n\ttimeout := 100 * time.Millisecond\n\tif os.Getenv(\"CI\") != \"\" {\n\t\ttimeout *= 5\n\t}\n\tstart := time.Now()\n\tclientStr.SetReadDeadline(start.Add(timeout))\n\t_, err := clientStr.Read([]byte{0})\n\trequire.ErrorIs(t, err, os.ErrDeadlineExceeded)\n\ttook := time.Since(start)\n\trequire.GreaterOrEqual(t, took, timeout)\n\trequire.LessOrEqual(t, took, timeout*3/2)\n\t// repeated calls should return immediately\n\tstart = time.Now()\n\t_, err = clientStr.Read([]byte{0})\n\trequire.ErrorIs(t, err, os.ErrDeadlineExceeded)\n\trequire.LessOrEqual(t, time.Since(start), timeout/3)\n\t// clear the deadline\n\tclientStr.SetReadDeadline(time.Time{})\n\t_, err = serverStr.Write([]byte(\"foobar\"))\n\trequire.NoError(t, err)\n\t_, err = clientStr.Read([]byte{0})\n\trequire.NoError(t, err)\n\trequire.LessOrEqual(t, time.Since(start), timeout/3)\n}\n\nfunc TestStreamWriteDeadlineAsync(t *testing.T) {\n\tclient, server := getDetachedDataChannels(t)\n\n\tclientStr := newStream(client.dc, client.rwc, maxSendMessageSize, func() {})\n\tserverStr := newStream(server.dc, server.rwc, maxSendMessageSize, func() {})\n\t_ = serverStr\n\n\tb := make([]byte, 1024)\n\trand.Read(b)\n\tstart := time.Now()\n\ttimeout := 100 * time.Millisecond\n\tif os.Getenv(\"CI\") != \"\" {\n\t\ttimeout *= 5\n\t}\n\tclientStr.SetWriteDeadline(start.Add(timeout))\n\tvar hitDeadline bool\n\tfor i := range 2000 {\n\t\tif _, err := clientStr.Write(b); err != nil {\n\t\t\tt.Logf(\"wrote %d kB\", i)\n\t\t\trequire.ErrorIs(t, err, os.ErrDeadlineExceeded)\n\t\t\thitDeadline = true\n\t\t\tbreak\n\t\t}\n\t}\n\trequire.True(t, hitDeadline)\n\ttook := time.Since(start)\n\trequire.GreaterOrEqual(t, took, timeout)\n\trequire.LessOrEqual(t, took, timeout*3/2)\n}\n\nfunc TestStreamReadAfterClose(t *testing.T) {\n\tclient, server := getDetachedDataChannels(t)\n\n\tclientStr := newStream(client.dc, client.rwc, maxSendMessageSize, func() {})\n\tserverStr := newStream(server.dc, server.rwc, maxSendMessageSize, func() {})\n\n\tserverStr.Close()\n\tb := make([]byte, 1)\n\t_, err := clientStr.Read(b)\n\trequire.Equal(t, io.EOF, err)\n\t_, err = clientStr.Read(nil)\n\trequire.Equal(t, io.EOF, err)\n\n\tclient, server = getDetachedDataChannels(t)\n\n\tclientStr = newStream(client.dc, client.rwc, maxSendMessageSize, func() {})\n\tserverStr = newStream(server.dc, server.rwc, maxSendMessageSize, func() {})\n\n\tserverStr.Reset()\n\tb = make([]byte, 1)\n\t_, err = clientStr.Read(b)\n\trequire.ErrorIs(t, err, network.ErrReset)\n\t_, err = clientStr.Read(nil)\n\trequire.ErrorIs(t, err, network.ErrReset)\n}\n\nfunc TestStreamCloseAfterFINACK(t *testing.T) {\n\tclient, server := getDetachedDataChannels(t)\n\n\tdone := make(chan bool, 1)\n\tclientStr := newStream(client.dc, client.rwc, maxSendMessageSize, func() { done <- true })\n\tserverStr := newStream(server.dc, server.rwc, maxSendMessageSize, func() {})\n\n\tgo func() {\n\t\terr := clientStr.Close()\n\t\tassert.NoError(t, err)\n\t}()\n\n\tselect {\n\tcase <-done:\n\tcase <-time.After(200 * time.Millisecond):\n\t\tt.Fatalf(\"Close should signal OnDone immediately\")\n\t}\n\n\t// Reading FIN_ACK on server should trigger data channel close on the client\n\tb := make([]byte, 1)\n\t_, err := serverStr.Read(b)\n\trequire.Error(t, err)\n\trequire.ErrorIs(t, err, io.EOF)\n\tassertDataChannelClosed(t, client.rwc.(*datachannel.DataChannel))\n}\n\n// TestStreamFinAckAfterStopSending tests that FIN_ACK is sent even after the write half\n// of the stream is closed.\nfunc TestStreamFinAckAfterStopSending(t *testing.T) {\n\tclient, server := getDetachedDataChannels(t)\n\n\tdone := make(chan bool, 1)\n\tclientStr := newStream(client.dc, client.rwc, maxSendMessageSize, func() { done <- true })\n\tserverStr := newStream(server.dc, server.rwc, maxSendMessageSize, func() {})\n\n\tgo func() {\n\t\tclientStr.CloseRead()\n\t\tclientStr.Write([]byte(\"hello world\"))\n\t\tdone <- true\n\t\terr := clientStr.Close()\n\t\tassert.NoError(t, err)\n\t}()\n\t<-done\n\n\tselect {\n\tcase <-done:\n\tcase <-time.After(500 * time.Millisecond):\n\t\tt.Errorf(\"Close should signal onDone immediately\")\n\t}\n\n\t// serverStr has write half closed and read half open\n\t// serverStr should still send FIN_ACK\n\tb := make([]byte, 24)\n\t_, err := serverStr.Read(b)\n\trequire.NoError(t, err)\n\tserverStr.Close() // Sends stop_sending, fin\n\tassertDataChannelClosed(t, server.rwc.(*datachannel.DataChannel))\n\tassertDataChannelClosed(t, client.rwc.(*datachannel.DataChannel))\n}\n\nfunc TestStreamConcurrentClose(t *testing.T) {\n\tclient, server := getDetachedDataChannels(t)\n\n\tstart := make(chan bool, 2)\n\tdone := make(chan bool, 2)\n\tclientStr := newStream(client.dc, client.rwc, maxSendMessageSize, func() { done <- true })\n\tserverStr := newStream(server.dc, server.rwc, maxSendMessageSize, func() { done <- true })\n\n\tgo func() {\n\t\tstart <- true\n\t\tclientStr.Close()\n\t}()\n\tgo func() {\n\t\tstart <- true\n\t\tserverStr.Close()\n\t}()\n\t<-start\n\t<-start\n\n\tselect {\n\tcase <-done:\n\tcase <-time.After(2 * time.Second):\n\t\tt.Fatalf(\"concurrent close should succeed quickly\")\n\t}\n\tselect {\n\tcase <-done:\n\tcase <-time.After(2 * time.Second):\n\t\tt.Fatalf(\"concurrent close should succeed quickly\")\n\t}\n\n\t// Wait for FIN_ACK AND datachannel close\n\tassertDataChannelClosed(t, client.rwc.(*datachannel.DataChannel))\n\tassertDataChannelClosed(t, server.rwc.(*datachannel.DataChannel))\n\n}\n\nfunc TestStreamResetAfterClose(t *testing.T) {\n\tclient, server := getDetachedDataChannels(t)\n\n\tdone := make(chan bool, 2)\n\tclientStr := newStream(client.dc, client.rwc, maxSendMessageSize, func() { done <- true })\n\tclientStr.Close()\n\n\tselect {\n\tcase <-done:\n\tcase <-time.After(500 * time.Millisecond):\n\t\tt.Fatalf(\"Close should run cleanup immediately\")\n\t}\n\t// The server data channel should still be open\n\tassertDataChannelOpen(t, server.rwc.(*datachannel.DataChannel))\n\tclientStr.Reset()\n\t// Reset closes the datachannels\n\tassertDataChannelClosed(t, server.rwc.(*datachannel.DataChannel))\n\tassertDataChannelClosed(t, client.rwc.(*datachannel.DataChannel))\n\tselect {\n\tcase <-done:\n\t\tt.Fatalf(\"onDone should not be called twice\")\n\tcase <-time.After(50 * time.Millisecond):\n\t}\n}\n\nfunc TestStreamDataChannelCloseOnFINACK(t *testing.T) {\n\tclient, server := getDetachedDataChannels(t)\n\n\tdone := make(chan bool, 1)\n\tclientStr := newStream(client.dc, client.rwc, maxSendMessageSize, func() { done <- true })\n\n\tclientStr.Close()\n\n\tselect {\n\tcase <-time.After(500 * time.Millisecond):\n\t\tt.Fatalf(\"Close should run cleanup immediately\")\n\tcase <-done:\n\t}\n\n\t// sending FIN_ACK closes the datachannel\n\tserverWriter := pbio.NewDelimitedWriter(server.rwc)\n\terr := serverWriter.WriteMsg(&pb.Message{Flag: pb.Message_FIN_ACK.Enum()})\n\trequire.NoError(t, err)\n\n\tassertDataChannelClosed(t, server.rwc.(*datachannel.DataChannel))\n\tassertDataChannelClosed(t, client.rwc.(*datachannel.DataChannel))\n}\n\nfunc TestStreamChunking(t *testing.T) {\n\tfor _, msgSize := range []int{16 << 10, 32 << 10, 64 << 10, 128 << 10, 256 << 10} {\n\t\tt.Run(fmt.Sprintf(\"msgSize=%d\", msgSize), func(t *testing.T) {\n\t\t\tclient, server := getDetachedDataChannels(t)\n\t\t\tdefer client.dc.Close()\n\t\t\tdefer server.dc.Close()\n\n\t\t\tclientStr := newStream(client.dc, client.rwc, msgSize, nil)\n\t\t\t// server should read large messages even if it can only send 16 kB messages.\n\t\t\tserverStr := newStream(server.dc, server.rwc, 16<<10, nil)\n\n\t\t\tN := msgSize + 1000\n\t\t\tinput := make([]byte, N)\n\t\t\t_, err := rand.Read(input)\n\t\t\trequire.NoError(t, err)\n\t\t\tgo func() {\n\t\t\t\tn, err := clientStr.Write(input)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, n, len(input))\n\t\t\t}()\n\n\t\t\tdata := make([]byte, N)\n\t\t\tn, err := serverStr.Read(data)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.LessOrEqual(t, n, msgSize)\n\t\t\t// shouldn't be much less than msgSize\n\t\t\trequire.GreaterOrEqual(t, n, msgSize-100)\n\t\t\t_, err = serverStr.Read(data[n:])\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, input, data)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "p2p/transport/webrtc/stream_write.go",
    "content": "package libp2pwebrtc\n\nimport (\n\t\"errors\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/webrtc/pb\"\n)\n\nvar errWriteAfterClose = errors.New(\"write after close\")\n\n// If we have less space than minMessageSize, we don't put a new message on the data channel.\n// Instead, we wait until more space opens up.\nconst minMessageSize = 1 << 10\n\nfunc (s *stream) Write(b []byte) (int, error) {\n\ts.mx.Lock()\n\tdefer s.mx.Unlock()\n\n\tif s.closeForShutdownErr != nil {\n\t\treturn 0, s.closeForShutdownErr\n\t}\n\tswitch s.sendState {\n\tcase sendStateReset:\n\t\treturn 0, s.writeError\n\tcase sendStateDataSent, sendStateDataReceived:\n\t\treturn 0, errWriteAfterClose\n\t}\n\n\tif !s.writeDeadline.IsZero() && time.Now().After(s.writeDeadline) {\n\t\treturn 0, os.ErrDeadlineExceeded\n\t}\n\n\tvar writeDeadlineTimer *time.Timer\n\tdefer func() {\n\t\tif writeDeadlineTimer != nil {\n\t\t\twriteDeadlineTimer.Stop()\n\t\t}\n\t}()\n\n\tvar n int\n\tvar msg pb.Message\n\tfor len(b) > 0 {\n\t\tif s.closeForShutdownErr != nil {\n\t\t\treturn n, s.closeForShutdownErr\n\t\t}\n\t\tswitch s.sendState {\n\t\tcase sendStateReset:\n\t\t\treturn n, s.writeError\n\t\tcase sendStateDataSent, sendStateDataReceived:\n\t\t\treturn n, errWriteAfterClose\n\t\t}\n\n\t\twriteDeadline := s.writeDeadline\n\t\t// deadline deleted, stop and remove the timer\n\t\tif writeDeadline.IsZero() && writeDeadlineTimer != nil {\n\t\t\twriteDeadlineTimer.Stop()\n\t\t\twriteDeadlineTimer = nil\n\t\t}\n\t\tvar writeDeadlineChan <-chan time.Time\n\t\tif !writeDeadline.IsZero() {\n\t\t\tif writeDeadlineTimer == nil {\n\t\t\t\twriteDeadlineTimer = time.NewTimer(time.Until(writeDeadline))\n\t\t\t} else {\n\t\t\t\tif !writeDeadlineTimer.Stop() {\n\t\t\t\t\t<-writeDeadlineTimer.C\n\t\t\t\t}\n\t\t\t\twriteDeadlineTimer.Reset(time.Until(writeDeadline))\n\t\t\t}\n\t\t\twriteDeadlineChan = writeDeadlineTimer.C\n\t\t}\n\n\t\tavailableSpace := s.availableSendSpace()\n\t\tif availableSpace < minMessageSize {\n\t\t\ts.mx.Unlock()\n\t\t\tselect {\n\t\t\tcase <-writeDeadlineChan:\n\t\t\t\ts.mx.Lock()\n\t\t\t\treturn n, os.ErrDeadlineExceeded\n\t\t\tcase <-s.writeStateChanged:\n\t\t\t}\n\t\t\ts.mx.Lock()\n\t\t\tcontinue\n\t\t}\n\t\tend := min(s.maxSendMessageSize, availableSpace)\n\t\tend -= protoOverhead + varintOverhead\n\t\tif end > len(b) {\n\t\t\tend = len(b)\n\t\t}\n\t\tmsg = pb.Message{Message: b[:end]}\n\t\tif err := s.writer.WriteMsg(&msg); err != nil {\n\t\t\treturn n, err\n\t\t}\n\t\tn += end\n\t\tb = b[end:]\n\t}\n\treturn n, nil\n}\n\nfunc (s *stream) SetWriteDeadline(t time.Time) error {\n\ts.mx.Lock()\n\tdefer s.mx.Unlock()\n\ts.writeDeadline = t\n\ts.notifyWriteStateChanged()\n\treturn nil\n}\n\n// sendBufferSize() is the maximum data we enqueue on the underlying data channel for writes.\n// The underlying SCTP layer has an unbounded buffer for writes. We limit the amount enqueued\n// per stream is limited to avoid a single stream monopolizing the entire connection.\nfunc (s *stream) sendBufferSize() int {\n\treturn 2 * s.maxSendMessageSize\n}\n\n// sendBufferLowThreshold() is the threshold below which we write more data on the underlying\n// data channel. We want a notification as soon as we can write 1 full sized message.\nfunc (s *stream) sendBufferLowThreshold() int {\n\treturn s.sendBufferSize() - s.maxSendMessageSize\n}\n\nfunc (s *stream) availableSendSpace() int {\n\tbuffered := int(s.dataChannel.BufferedAmount())\n\tavailableSpace := s.sendBufferSize() - buffered\n\tif availableSpace+maxTotalControlMessagesSize < 0 { // this should never happen, but better check\n\t\tlog.Error(\"data channel buffered more data than the maximum amount\", \"max\", s.sendBufferSize(), \"buffered\", buffered)\n\t}\n\treturn availableSpace\n}\n\nfunc (s *stream) cancelWrite(errCode network.StreamErrorCode) error {\n\ts.mx.Lock()\n\tdefer s.mx.Unlock()\n\n\t// There's no need to reset the write half if the write half has been closed\n\t// successfully or has been reset previously\n\tif s.sendState == sendStateDataReceived || s.sendState == sendStateReset {\n\t\treturn nil\n\t}\n\ts.sendState = sendStateReset\n\ts.writeError = &network.StreamError{Remote: false, ErrorCode: errCode}\n\t// Remove reference to this stream from data channel\n\ts.dataChannel.OnBufferedAmountLow(nil)\n\ts.notifyWriteStateChanged()\n\tcode := uint32(errCode)\n\treturn s.writer.WriteMsg(&pb.Message{Flag: pb.Message_RESET.Enum(), ErrorCode: &code})\n}\n\nfunc (s *stream) CloseWrite() error {\n\ts.mx.Lock()\n\tdefer s.mx.Unlock()\n\n\tif s.sendState != sendStateSending {\n\t\treturn nil\n\t}\n\ts.sendState = sendStateDataSent\n\t// Remove reference to this stream from data channel\n\ts.dataChannel.OnBufferedAmountLow(nil)\n\ts.notifyWriteStateChanged()\n\treturn s.writer.WriteMsg(&pb.Message{Flag: pb.Message_FIN.Enum()})\n}\n\nfunc (s *stream) notifyWriteStateChanged() {\n\tselect {\n\tcase s.writeStateChanged <- struct{}{}:\n\tdefault:\n\t}\n}\n"
  },
  {
    "path": "p2p/transport/webrtc/transport.go",
    "content": "// Package libp2pwebrtc implements the WebRTC transport for go-libp2p,\n// as described in https://github.com/libp2p/specs/tree/master/webrtc.\npackage libp2pwebrtc\n\nimport (\n\t\"context\"\n\t\"crypto\"\n\t\"crypto/ecdsa\"\n\t\"crypto/elliptic\"\n\t\"crypto/rand\"\n\t\"crypto/x509\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"time\"\n\n\tmrand \"math/rand/v2\"\n\n\t\"google.golang.org/protobuf/proto\"\n\n\t\"github.com/libp2p/go-libp2p/core/connmgr\"\n\tic \"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/pnet\"\n\t\"github.com/libp2p/go-libp2p/core/sec\"\n\ttpt \"github.com/libp2p/go-libp2p/core/transport\"\n\t\"github.com/libp2p/go-libp2p/p2p/security/noise\"\n\tlibp2pquic \"github.com/libp2p/go-libp2p/p2p/transport/quic\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/webrtc/pb\"\n\t\"github.com/libp2p/go-msgio\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n\t\"github.com/multiformats/go-multihash\"\n\n\t\"github.com/pion/datachannel\"\n\t\"github.com/pion/webrtc/v4\"\n)\n\nvar webrtcComponent *ma.Component\n\nfunc init() {\n\tvar err error\n\twebrtcComponent, err = ma.NewComponent(ma.ProtocolWithCode(ma.P_WEBRTC_DIRECT).Name, \"\")\n\tif err != nil {\n\t\tfmt.Fprintln(os.Stderr, err.Error())\n\t\tos.Executable()\n\t}\n}\n\nconst (\n\t// handshakeChannelNegotiated is used to specify that the\n\t// handshake data channel does not need negotiation via DCEP.\n\t// A constant is used since the `DataChannelInit` struct takes\n\t// references instead of values.\n\thandshakeChannelNegotiated = true\n\t// handshakeChannelID is the agreed ID for the handshake data\n\t// channel. A constant is used since the `DataChannelInit` struct takes\n\t// references instead of values. We specify the type here as this\n\t// value is only ever copied and passed by reference\n\thandshakeChannelID = uint16(0)\n)\n\n// timeout values for the peerconnection\n// https://github.com/pion/webrtc/blob/v3.1.50/settingengine.go#L102-L109\nconst (\n\tDefaultDisconnectedTimeout = 20 * time.Second\n\tDefaultFailedTimeout       = 30 * time.Second\n\tDefaultKeepaliveTimeout    = 15 * time.Second\n\n\t// sctpReceiveBufferSize is the size of the buffer for incoming messages.\n\t//\n\t// This is enough space for enqueuing 10 full sized messages.\n\t// Besides throughput, this only matters if an application is using multiple dependent\n\t// streams, say streams 1 & 2. It reads from stream 1 only after receiving message from\n\t// stream 2. A buffer of 10 messages should serve all such situations.\n\tsctpReceiveBufferSize = 10 * maxReceiveMessageSize\n)\n\ntype WebRTCTransport struct {\n\twebrtcConfig webrtc.Configuration\n\trcmgr        network.ResourceManager\n\tgater        connmgr.ConnectionGater\n\tprivKey      ic.PrivKey\n\tnoiseTpt     *noise.Transport\n\tlocalPeerId  peer.ID\n\n\tlistenUDP func(network string, laddr *net.UDPAddr) (net.PacketConn, error)\n\n\t// timeouts\n\tpeerConnectionTimeouts iceTimeouts\n\n\t// in-flight connections\n\tmaxInFlightConnections uint32\n}\n\nvar _ tpt.Transport = &WebRTCTransport{}\n\ntype Option func(*WebRTCTransport) error\n\ntype iceTimeouts struct {\n\tDisconnect time.Duration\n\tFailed     time.Duration\n\tKeepalive  time.Duration\n}\n\ntype ListenUDPFn func(network string, laddr *net.UDPAddr) (net.PacketConn, error)\n\nfunc New(privKey ic.PrivKey, psk pnet.PSK, gater connmgr.ConnectionGater, rcmgr network.ResourceManager, listenUDP ListenUDPFn, opts ...Option) (*WebRTCTransport, error) {\n\tif psk != nil {\n\t\tlog.Error(\"WebRTC doesn't support private networks yet.\")\n\t\treturn nil, fmt.Errorf(\"WebRTC doesn't support private networks yet\")\n\t}\n\tif rcmgr == nil {\n\t\trcmgr = &network.NullResourceManager{}\n\t}\n\tlocalPeerID, err := peer.IDFromPrivateKey(privKey)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"get local peer ID: %w\", err)\n\t}\n\t// We use elliptic P-256 since it is widely supported by browsers.\n\t//\n\t// Implementation note: Testing with the browser,\n\t// it seems like Chromium only supports ECDSA P-256 or RSA key signatures in the webrtc TLS certificate.\n\t// We tried using P-228 and P-384 which caused the DTLS handshake to fail with Illegal Parameter\n\t//\n\t// Please refer to this is a list of suggested algorithms for the WebCrypto API.\n\t// The algorithm for generating a certificate for an RTCPeerConnection\n\t// must adhere to the WebCrpyto API. From my observation,\n\t// RSA and ECDSA P-256 is supported on almost all browsers.\n\t// Ed25519 is not present on the list.\n\tpk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"generate key for cert: %w\", err)\n\t}\n\tcert, err := webrtc.GenerateCertificate(pk)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"generate certificate: %w\", err)\n\t}\n\tconfig := webrtc.Configuration{\n\t\tCertificates: []webrtc.Certificate{*cert},\n\t}\n\tnoiseTpt, err := noise.New(noise.ID, privKey, nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to create noise transport: %w\", err)\n\t}\n\ttransport := &WebRTCTransport{\n\t\trcmgr:        rcmgr,\n\t\tgater:        gater,\n\t\twebrtcConfig: config,\n\t\tprivKey:      privKey,\n\t\tnoiseTpt:     noiseTpt,\n\t\tlocalPeerId:  localPeerID,\n\n\t\tlistenUDP: listenUDP,\n\t\tpeerConnectionTimeouts: iceTimeouts{\n\t\t\tDisconnect: DefaultDisconnectedTimeout,\n\t\t\tFailed:     DefaultFailedTimeout,\n\t\t\tKeepalive:  DefaultKeepaliveTimeout,\n\t\t},\n\n\t\tmaxInFlightConnections: DefaultMaxInFlightConnections,\n\t}\n\tfor _, opt := range opts {\n\t\tif err := opt(transport); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn transport, nil\n}\n\nfunc (t *WebRTCTransport) ListenOrder() int {\n\treturn libp2pquic.ListenOrder + 1 // We want to listen after QUIC listens so we can possibly reuse the same port.\n}\n\nfunc (t *WebRTCTransport) Protocols() []int {\n\treturn []int{ma.P_WEBRTC_DIRECT}\n}\n\nfunc (t *WebRTCTransport) Proxy() bool {\n\treturn false\n}\n\nfunc (t *WebRTCTransport) CanDial(addr ma.Multiaddr) bool {\n\tisValid, n := IsWebRTCDirectMultiaddr(addr)\n\treturn isValid && n > 0\n}\n\n// Listen returns a listener for addr.\n//\n// The IP, Port combination for addr must be exclusive to this listener as a WebRTC listener cannot\n// be multiplexed on the same port as other UDP based transports like QUIC and WebTransport.\n// See https://github.com/libp2p/go-libp2p/issues/2446 for details.\nfunc (t *WebRTCTransport) Listen(addr ma.Multiaddr) (tpt.Listener, error) {\n\taddr, wrtcComponent := ma.SplitLast(addr)\n\tisWebrtc := wrtcComponent.Equal(webrtcComponent)\n\tif !isWebrtc {\n\t\treturn nil, fmt.Errorf(\"must listen on webrtc multiaddr\")\n\t}\n\tnw, host, err := manet.DialArgs(addr)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"listener could not fetch dialargs: %w\", err)\n\t}\n\tudpAddr, err := net.ResolveUDPAddr(nw, host)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"listener could not resolve udp address: %w\", err)\n\t}\n\n\tsocket, err := t.listenUDP(nw, udpAddr)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"listen on udp: %w\", err)\n\t}\n\n\tlistener, err := t.listenSocket(socket)\n\tif err != nil {\n\t\tsocket.Close()\n\t\treturn nil, err\n\t}\n\treturn listener, nil\n}\n\nfunc (t *WebRTCTransport) listenSocket(socket net.PacketConn) (tpt.Listener, error) {\n\tlistenerMultiaddr, err := manet.FromNetAddr(socket.LocalAddr())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlistenerFingerprint, err := t.getCertificateFingerprint()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tencodedLocalFingerprint, err := encodeDTLSFingerprint(listenerFingerprint)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcertComp, err := ma.NewComponent(ma.ProtocolWithCode(ma.P_CERTHASH).Name, encodedLocalFingerprint)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tlistenerMultiaddr = listenerMultiaddr.AppendComponent(webrtcComponent, certComp)\n\n\treturn newListener(\n\t\tt,\n\t\tlistenerMultiaddr,\n\t\tsocket,\n\t\tt.webrtcConfig,\n\t)\n}\n\nfunc (t *WebRTCTransport) Dial(ctx context.Context, remoteMultiaddr ma.Multiaddr, p peer.ID) (tpt.CapableConn, error) {\n\tscope, err := t.rcmgr.OpenConnection(network.DirOutbound, false, remoteMultiaddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := scope.SetPeer(p); err != nil {\n\t\tscope.Done()\n\t\treturn nil, err\n\t}\n\tconn, err := t.dial(ctx, scope, remoteMultiaddr, p)\n\tif err != nil {\n\t\tscope.Done()\n\t\treturn nil, err\n\t}\n\treturn conn, nil\n}\n\nfunc (t *WebRTCTransport) dial(ctx context.Context, scope network.ConnManagementScope, remoteMultiaddr ma.Multiaddr, p peer.ID) (tConn tpt.CapableConn, err error) {\n\tvar w webRTCConnection\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tif w.PeerConnection != nil {\n\t\t\t\t_ = w.PeerConnection.Close()\n\t\t\t}\n\t\t\tif tConn != nil {\n\t\t\t\t_ = tConn.Close()\n\t\t\t\ttConn = nil\n\t\t\t}\n\t\t}\n\t}()\n\n\tremoteMultihash, err := decodeRemoteFingerprint(remoteMultiaddr)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"decode fingerprint: %w\", err)\n\t}\n\tremoteHashFunction, ok := getSupportedSDPHash(remoteMultihash.Code)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"unsupported hash function: %w\", nil)\n\t}\n\n\trnw, rhost, err := manet.DialArgs(remoteMultiaddr)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"generate dial args: %w\", err)\n\t}\n\n\traddr, err := net.ResolveUDPAddr(rnw, rhost)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"resolve udp address: %w\", err)\n\t}\n\n\t// Instead of encoding the local fingerprint we\n\t// generate a random UUID as the connection ufrag.\n\t// The only requirement here is that the ufrag and password\n\t// must be equal, which will allow the server to determine\n\t// the password using the STUN message.\n\tufrag := genUfrag()\n\n\tsettingEngine := webrtc.SettingEngine{\n\t\tLoggerFactory: pionLoggerFactory,\n\t}\n\tsettingEngine.SetICECredentials(ufrag, ufrag)\n\tsettingEngine.DetachDataChannels()\n\t// use the first best address candidate\n\tsettingEngine.SetPrflxAcceptanceMinWait(0)\n\tsettingEngine.SetICETimeouts(\n\t\tt.peerConnectionTimeouts.Disconnect,\n\t\tt.peerConnectionTimeouts.Failed,\n\t\tt.peerConnectionTimeouts.Keepalive,\n\t)\n\t// By default, webrtc will not collect candidates on the loopback address.\n\t// This is disallowed in the ICE specification. However, implementations\n\t// do not strictly follow this, for eg. Chrome gathers TCP loopback candidates.\n\t// If you run pion on a system with only the loopback interface UP,\n\t// it will not connect to anything.\n\tsettingEngine.SetIncludeLoopbackCandidate(true)\n\tsettingEngine.SetSCTPMaxReceiveBufferSize(sctpReceiveBufferSize)\n\tif err := scope.ReserveMemory(sctpReceiveBufferSize, network.ReservationPriorityMedium); err != nil {\n\t\treturn nil, err\n\t}\n\n\tw, err = newWebRTCConnection(settingEngine, t.webrtcConfig)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"instantiating peer connection failed: %w\", err)\n\t}\n\n\terrC := addOnConnectionStateChangeCallback(w.PeerConnection)\n\n\t// do offer-answer exchange\n\toffer, err := w.PeerConnection.CreateOffer(nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"create offer: %w\", err)\n\t}\n\n\terr = w.PeerConnection.SetLocalDescription(offer)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"set local description: %w\", err)\n\t}\n\n\tanswerSDPString, err := createServerSDP(raddr, ufrag, *remoteMultihash)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"render server SDP: %w\", err)\n\t}\n\n\tanswer := webrtc.SessionDescription{SDP: answerSDPString, Type: webrtc.SDPTypeAnswer}\n\terr = w.PeerConnection.SetRemoteDescription(answer)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"set remote description: %w\", err)\n\t}\n\n\t// await peerconnection opening\n\tselect {\n\tcase err := <-errC:\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\tcase <-ctx.Done():\n\t\treturn nil, errors.New(\"peerconnection opening timed out\")\n\t}\n\n\t// We are connected, run the noise handshake\n\tdetached, err := detachHandshakeDataChannel(ctx, w.HandshakeDataChannel)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tchannel := newStream(w.HandshakeDataChannel, detached, maxSendMessageSize, nil)\n\n\tremotePubKey, err := t.noiseHandshake(ctx, w.PeerConnection, channel, p, remoteHashFunction, false)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Setup local and remote address for the connection\n\tcp, err := w.HandshakeDataChannel.Transport().Transport().ICETransport().GetSelectedCandidatePair()\n\tif cp == nil {\n\t\treturn nil, errors.New(\"ice connection did not have selected candidate pair: nil result\")\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"ice connection did not have selected candidate pair: error: %w\", err)\n\t}\n\t// the local address of the selected candidate pair should be the local address for the connection\n\tlocalAddr, err := manet.FromNetAddr(&net.UDPAddr{IP: net.ParseIP(cp.Local.Address), Port: int(cp.Local.Port)})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tremoteMultiaddrWithoutCerthash, _ := ma.SplitFunc(remoteMultiaddr, func(c ma.Component) bool { return c.Protocol().Code == ma.P_CERTHASH })\n\n\tconn, err := newConnection(\n\t\tnetwork.DirOutbound,\n\t\tw.PeerConnection,\n\t\tt,\n\t\tscope,\n\t\tt.localPeerId,\n\t\tlocalAddr,\n\t\tp,\n\t\tremotePubKey,\n\t\tremoteMultiaddrWithoutCerthash,\n\t\tw.IncomingDataChannels,\n\t\tw.PeerConnectionClosedCh,\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif t.gater != nil && !t.gater.InterceptSecured(network.DirOutbound, p, conn) {\n\t\treturn nil, fmt.Errorf(\"secured connection gated\")\n\t}\n\treturn conn, nil\n}\n\nfunc genUfrag() string {\n\tconst (\n\t\tuFragAlphabet = \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\"\n\t\tuFragPrefix   = \"libp2p+webrtc+v1/\"\n\t\tuFragIdLength = 32\n\t\tuFragLength   = len(uFragPrefix) + uFragIdLength\n\t)\n\n\tseed := [32]byte{}\n\trand.Read(seed[:])\n\tr := mrand.New(mrand.New(mrand.NewChaCha8(seed)))\n\tb := make([]byte, uFragLength)\n\tfor i := range len(uFragPrefix) {\n\t\tb[i] = uFragPrefix[i]\n\t}\n\tfor i := len(uFragPrefix); i < uFragLength; i++ {\n\t\tb[i] = uFragAlphabet[r.IntN(len(uFragAlphabet))]\n\t}\n\treturn string(b)\n}\n\nfunc (t *WebRTCTransport) getCertificateFingerprint() (webrtc.DTLSFingerprint, error) {\n\tfps, err := t.webrtcConfig.Certificates[0].GetFingerprints()\n\tif err != nil {\n\t\treturn webrtc.DTLSFingerprint{}, err\n\t}\n\treturn fps[0], nil\n}\n\nfunc (t *WebRTCTransport) generateNoisePrologue(pc *webrtc.PeerConnection, hash crypto.Hash, inbound bool) ([]byte, error) {\n\traw := pc.SCTP().Transport().GetRemoteCertificate()\n\tcert, err := x509.ParseCertificate(raw)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// NOTE: should we want we can fork the cert code as well to avoid\n\t// all the extra allocations due to unneeded string interspersing (hex)\n\tlocalFp, err := t.getCertificateFingerprint()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tremoteFpBytes, err := parseFingerprint(cert, hash)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlocalFpBytes, err := decodeInterspersedHexFromASCIIString(localFp.Value)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlocalEncoded, err := multihash.Encode(localFpBytes, multihash.SHA2_256)\n\tif err != nil {\n\t\tlog.Debug(\"could not encode multihash for local fingerprint\")\n\t\treturn nil, err\n\t}\n\tremoteEncoded, err := multihash.Encode(remoteFpBytes, multihash.SHA2_256)\n\tif err != nil {\n\t\tlog.Debug(\"could not encode multihash for remote fingerprint\")\n\t\treturn nil, err\n\t}\n\n\tresult := []byte(\"libp2p-webrtc-noise:\")\n\tif inbound {\n\t\tresult = append(result, remoteEncoded...)\n\t\tresult = append(result, localEncoded...)\n\t} else {\n\t\tresult = append(result, localEncoded...)\n\t\tresult = append(result, remoteEncoded...)\n\t}\n\treturn result, nil\n}\n\nfunc (t *WebRTCTransport) noiseHandshake(ctx context.Context, pc *webrtc.PeerConnection, s *stream, peer peer.ID, hash crypto.Hash, inbound bool) (ic.PubKey, error) {\n\tprologue, err := t.generateNoisePrologue(pc, hash, inbound)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"generate prologue: %w\", err)\n\t}\n\topts := make([]noise.SessionOption, 0, 2)\n\topts = append(opts, noise.Prologue(prologue))\n\tif peer == \"\" {\n\t\topts = append(opts, noise.DisablePeerIDCheck())\n\t}\n\tsessionTransport, err := t.noiseTpt.WithSessionOptions(opts...)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to instantiate Noise transport: %w\", err)\n\t}\n\tvar secureConn sec.SecureConn\n\tif inbound {\n\t\tsecureConn, err = sessionTransport.SecureOutbound(ctx, netConnWrapper{s}, peer)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to secure inbound connection: %w\", err)\n\t\t}\n\t} else {\n\t\tsecureConn, err = sessionTransport.SecureInbound(ctx, netConnWrapper{s}, peer)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to secure outbound connection: %w\", err)\n\t\t}\n\t}\n\treturn secureConn.RemotePublicKey(), nil\n}\n\nfunc (t *WebRTCTransport) AddCertHashes(addr ma.Multiaddr) (ma.Multiaddr, bool) {\n\tlistenerFingerprint, err := t.getCertificateFingerprint()\n\tif err != nil {\n\t\treturn nil, false\n\t}\n\n\tencodedLocalFingerprint, err := encodeDTLSFingerprint(listenerFingerprint)\n\tif err != nil {\n\t\treturn nil, false\n\t}\n\n\tcertComp, err := ma.NewComponent(ma.ProtocolWithCode(ma.P_CERTHASH).Name, encodedLocalFingerprint)\n\tif err != nil {\n\t\treturn nil, false\n\t}\n\treturn addr.Encapsulate(certComp), true\n}\n\ntype netConnWrapper struct {\n\t*stream\n}\n\nfunc (netConnWrapper) LocalAddr() net.Addr  { return nil }\nfunc (netConnWrapper) RemoteAddr() net.Addr { return nil }\nfunc (w netConnWrapper) Close() error {\n\t// Close called while running the security handshake is an error and we should Reset the\n\t// stream in that case rather than gracefully closing\n\tw.stream.Reset()\n\treturn nil\n}\n\n// detachHandshakeDataChannel detaches the handshake data channel\nfunc detachHandshakeDataChannel(ctx context.Context, dc *webrtc.DataChannel) (datachannel.ReadWriteCloser, error) {\n\tdone := make(chan struct{})\n\tvar rwc datachannel.ReadWriteCloser\n\tvar err error\n\tdc.OnOpen(func() {\n\t\tdefer close(done)\n\t\trwc, err = dc.Detach()\n\t})\n\t// this is safe since for detached datachannels, the peerconnection runs the onOpen\n\t// callback immediately if the SCTP transport is also connected.\n\tselect {\n\tcase <-done:\n\t\treturn rwc, err\n\tcase <-ctx.Done():\n\t\treturn nil, ctx.Err()\n\t}\n}\n\n// webRTCConnection holds the webrtc.PeerConnection with the handshake channel and the queue for\n// incoming data channels created by the peer.\n//\n// When creating a webrtc.PeerConnection, It is important to set the OnDataChannel handler upfront\n// before connecting with the peer. If the handler's set up after connecting with the peer, there's\n// a small window of time where datachannels created by the peer may not surface to us and cause a\n// memory leak.\ntype webRTCConnection struct {\n\tPeerConnection         *webrtc.PeerConnection\n\tHandshakeDataChannel   *webrtc.DataChannel\n\tIncomingDataChannels   chan dataChannel\n\tPeerConnectionClosedCh chan struct{}\n}\n\nfunc newWebRTCConnection(settings webrtc.SettingEngine, config webrtc.Configuration) (webRTCConnection, error) {\n\tapi := webrtc.NewAPI(webrtc.WithSettingEngine(settings))\n\tpc, err := api.NewPeerConnection(config)\n\tif err != nil {\n\t\treturn webRTCConnection{}, fmt.Errorf(\"failed to create peer connection: %w\", err)\n\t}\n\n\tnegotiated, id := handshakeChannelNegotiated, handshakeChannelID\n\thandshakeDataChannel, err := pc.CreateDataChannel(\"\", &webrtc.DataChannelInit{\n\t\tNegotiated: &negotiated,\n\t\tID:         &id,\n\t})\n\tif err != nil {\n\t\tpc.Close()\n\t\treturn webRTCConnection{}, fmt.Errorf(\"failed to create handshake channel: %w\", err)\n\t}\n\n\tincomingDataChannels := make(chan dataChannel, maxAcceptQueueLen)\n\tpc.OnDataChannel(func(dc *webrtc.DataChannel) {\n\t\tdc.OnOpen(func() {\n\t\t\trwc, err := dc.Detach()\n\t\t\tif err != nil {\n\t\t\t\tlog.Warn(\"could not detach datachannel\", \"id\", *dc.ID())\n\t\t\t\treturn\n\t\t\t}\n\t\t\tselect {\n\t\t\tcase incomingDataChannels <- dataChannel{rwc, dc}:\n\t\t\tdefault:\n\t\t\t\tlog.Warn(\"connection busy, rejecting stream\")\n\t\t\t\tb, _ := proto.Marshal(&pb.Message{Flag: pb.Message_RESET.Enum()})\n\t\t\t\tw := msgio.NewWriter(rwc)\n\t\t\t\tw.WriteMsg(b)\n\t\t\t\trwc.Close()\n\t\t\t}\n\t\t})\n\t})\n\n\tconnectionClosedCh := make(chan struct{}, 1)\n\tpc.SCTP().OnClose(func(_ error) {\n\t\t// We only need one message. Closing a connection is a problem as pion might invoke the callback more than once.\n\t\tselect {\n\t\tcase connectionClosedCh <- struct{}{}:\n\t\tdefault:\n\t\t}\n\t})\n\treturn webRTCConnection{\n\t\tPeerConnection:         pc,\n\t\tHandshakeDataChannel:   handshakeDataChannel,\n\t\tIncomingDataChannels:   incomingDataChannels,\n\t\tPeerConnectionClosedCh: connectionClosedCh,\n\t}, nil\n}\n\n// IsWebRTCDirectMultiaddr returns whether addr is a /webrtc-direct multiaddr with the count of certhashes\n// in addr\nfunc IsWebRTCDirectMultiaddr(addr ma.Multiaddr) (bool, int) {\n\tvar foundUDP, foundWebRTC bool\n\tcertHashCount := 0\n\tma.ForEach(addr, func(c ma.Component) bool {\n\t\tif !foundUDP {\n\t\t\tif c.Protocol().Code == ma.P_UDP {\n\t\t\t\tfoundUDP = true\n\t\t\t}\n\t\t\treturn true\n\t\t}\n\t\tif !foundWebRTC && foundUDP {\n\t\t\t// protocol after udp must be webrtc-direct\n\t\t\tif c.Protocol().Code != ma.P_WEBRTC_DIRECT {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tfoundWebRTC = true\n\t\t\treturn true\n\t\t}\n\t\tif foundWebRTC {\n\t\t\tif c.Protocol().Code == ma.P_CERTHASH {\n\t\t\t\tcertHashCount++\n\t\t\t} else {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\t})\n\treturn foundUDP && foundWebRTC, certHashCount\n}\n"
  },
  {
    "path": "p2p/transport/webrtc/transport_test.go",
    "content": "package libp2pwebrtc\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"encoding/hex\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\ttpt \"github.com/libp2p/go-libp2p/core/transport\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n\t\"github.com/multiformats/go-multibase\"\n\t\"github.com/multiformats/go-multihash\"\n\tquicproxy \"github.com/quic-go/quic-go/integrationtests/tools/proxy\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"golang.org/x/crypto/sha3\"\n)\n\nvar netListenUDP ListenUDPFn = func(network string, laddr *net.UDPAddr) (net.PacketConn, error) {\n\treturn net.ListenUDP(network, laddr)\n}\n\nfunc getTransport(t *testing.T, opts ...Option) (*WebRTCTransport, peer.ID) {\n\tt.Helper()\n\tprivKey, _, err := crypto.GenerateKeyPair(crypto.Ed25519, -1)\n\trequire.NoError(t, err)\n\trcmgr := &network.NullResourceManager{}\n\ttransport, err := New(privKey, nil, nil, rcmgr, netListenUDP, opts...)\n\trequire.NoError(t, err)\n\tpeerID, err := peer.IDFromPrivateKey(privKey)\n\trequire.NoError(t, err)\n\tt.Cleanup(func() { rcmgr.Close() })\n\treturn transport, peerID\n}\n\nfunc TestNullRcmgrTransport(t *testing.T) {\n\tprivKey, _, err := crypto.GenerateKeyPair(crypto.Ed25519, -1)\n\trequire.NoError(t, err)\n\ttransport, err := New(privKey, nil, nil, nil, netListenUDP)\n\trequire.NoError(t, err)\n\n\tlistenTransport, pid := getTransport(t)\n\tln, err := listenTransport.Listen(ma.StringCast(\"/ip4/127.0.0.1/udp/0/webrtc-direct\"))\n\trequire.NoError(t, err)\n\tgo func() {\n\t\tc, err := ln.Accept()\n\t\tif !assert.NoError(t, err) {\n\t\t\tt.Error(err)\n\t\t}\n\t\tt.Cleanup(func() { c.Close() })\n\t}()\n\tc, err := transport.Dial(context.Background(), ln.Multiaddr(), pid)\n\trequire.NoError(t, err)\n\tc.Close()\n}\n\nfunc TestIsWebRTCDirectMultiaddr(t *testing.T) {\n\tinvalid := []string{\n\t\t\"/ip4/1.2.3.4/tcp/10/\",\n\t\t\"/ip6/1::3/udp/100/quic-v1/\",\n\t\t\"/ip4/1.2.3.4/udp/1/quic-v1/webrtc-direct\",\n\t}\n\n\tvalid := []struct {\n\t\taddr  string\n\t\tcount int\n\t}{\n\t\t{\n\t\t\taddr:  \"/ip4/1.2.3.4/udp/1234/webrtc-direct\",\n\t\t\tcount: 0,\n\t\t},\n\t\t{\n\t\t\taddr:  \"/dns/test.test/udp/1234/webrtc-direct\",\n\t\t\tcount: 0,\n\t\t},\n\t\t{\n\t\t\taddr:  \"/ip4/1.2.3.4/udp/1234/webrtc-direct/certhash/uEiAsGPzpiPGQzSlVHRXrUCT5EkTV7YFrV4VZ3hpEKTd_zg\",\n\t\t\tcount: 1,\n\t\t},\n\t\t{\n\t\t\taddr:  \"/ip6/0:0:0:0:0:0:0:1/udp/1234/webrtc-direct/certhash/uEiAsGPzpiPGQzSlVHRXrUCT5EkTV7YFrV4VZ3hpEKTd_zg\",\n\t\t\tcount: 1,\n\t\t},\n\t\t{\n\t\t\taddr:  \"/dns/test.test/udp/1234/webrtc-direct/certhash/uEiAsGPzpiPGQzSlVHRXrUCT5EkTV7YFrV4VZ3hpEKTd_zg\",\n\t\t\tcount: 1,\n\t\t},\n\t\t{\n\t\t\taddr:  \"/dns/test.test/udp/1234/webrtc-direct/certhash/uEiAsGPzpiPGQzSlVHRXrUCT5EkTV7YFrV4VZ3hpEKTd_zg/certhash/uEiAsGPzpiPGQzSlVHRXrUCT5EkTV7ZGrV4VZ3hpEKTd_zg\",\n\t\t\tcount: 2,\n\t\t},\n\t}\n\n\tfor _, addr := range invalid {\n\t\ta := ma.StringCast(addr)\n\t\tisValid, n := IsWebRTCDirectMultiaddr(a)\n\t\trequire.Equal(t, 0, n)\n\t\trequire.False(t, isValid)\n\t}\n\n\tfor _, tc := range valid {\n\t\ta := ma.StringCast(tc.addr)\n\t\tisValid, n := IsWebRTCDirectMultiaddr(a)\n\t\trequire.Equal(t, tc.count, n)\n\t\trequire.True(t, isValid)\n\t}\n}\n\nfunc TestTransportWebRTC_CanDial(t *testing.T) {\n\ttr, _ := getTransport(t)\n\tinvalid := []string{\n\t\t\"/ip4/1.2.3.4/udp/1234/webrtc-direct\",\n\t\t\"/dns/test.test/udp/1234/webrtc-direct\",\n\t}\n\n\tvalid := []string{\n\t\t\"/ip4/1.2.3.4/udp/1234/webrtc-direct/certhash/uEiAsGPzpiPGQzSlVHRXrUCT5EkTV7YFrV4VZ3hpEKTd_zg\",\n\t\t\"/ip6/0:0:0:0:0:0:0:1/udp/1234/webrtc-direct/certhash/uEiAsGPzpiPGQzSlVHRXrUCT5EkTV7YFrV4VZ3hpEKTd_zg\",\n\t\t\"/ip6/::1/udp/1234/webrtc-direct/certhash/uEiAsGPzpiPGQzSlVHRXrUCT5EkTV7YFrV4VZ3hpEKTd_zg\",\n\t\t\"/dns/test.test/udp/1234/webrtc-direct/certhash/uEiAsGPzpiPGQzSlVHRXrUCT5EkTV7YFrV4VZ3hpEKTd_zg\",\n\t}\n\n\tfor _, addr := range invalid {\n\t\ta := ma.StringCast(addr)\n\t\trequire.False(t, tr.CanDial(a))\n\t}\n\n\tfor _, addr := range valid {\n\t\ta := ma.StringCast(addr)\n\t\trequire.True(t, tr.CanDial(a), addr)\n\t}\n}\n\nfunc TestTransportAddCertHasher(t *testing.T) {\n\ttr, _ := getTransport(t)\n\taddrs := []string{\n\t\t\"/ip4/1.2.3.4/udp/1/webrtc-direct\",\n\t\t\"/ip6/1::3/udp/2/webrtc-direct\",\n\t}\n\tfor _, a := range addrs {\n\t\taddr, added := tr.AddCertHashes(ma.StringCast(a))\n\t\trequire.True(t, added)\n\t\t_, err := addr.ValueForProtocol(ma.P_CERTHASH)\n\t\trequire.NoError(t, err)\n\t\trequire.True(t, strings.HasPrefix(addr.String(), a))\n\t}\n}\n\nfunc TestTransportWebRTC_ListenFailsOnNonWebRTCMultiaddr(t *testing.T) {\n\ttr, _ := getTransport(t)\n\ttestAddrs := []string{\n\t\t\"/ip4/0.0.0.0/udp/0\",\n\t\t\"/ip4/0.0.0.0/tcp/0/wss\",\n\t}\n\tfor _, addr := range testAddrs {\n\t\tlistenMultiaddr, err := ma.NewMultiaddr(addr)\n\t\trequire.NoError(t, err)\n\t\tlistener, err := tr.Listen(listenMultiaddr)\n\t\trequire.Error(t, err)\n\t\trequire.Nil(t, listener)\n\t}\n}\n\n// using assert inside goroutines, refer: https://github.com/stretchr/testify/issues/772#issuecomment-945166599\nfunc TestTransportWebRTC_DialFailsOnUnsupportedHashFunction(t *testing.T) {\n\ttr, _ := getTransport(t)\n\thash := sha3.New512()\n\tcerthash := func() string {\n\t\t_, err := hash.Write([]byte(\"test-data\"))\n\t\trequire.NoError(t, err)\n\t\tmh, err := multihash.Encode(hash.Sum([]byte{}), multihash.SHA3_512)\n\t\trequire.NoError(t, err)\n\t\tcerthash, err := multibase.Encode(multibase.Base58BTC, mh)\n\t\trequire.NoError(t, err)\n\t\treturn certhash\n\t}()\n\ttestaddr, err := ma.NewMultiaddr(\"/ip4/1.2.3.4/udp/1234/webrtc-direct/certhash/\" + certhash)\n\trequire.NoError(t, err)\n\t_, err = tr.Dial(context.Background(), testaddr, \"\")\n\trequire.ErrorContains(t, err, \"unsupported hash function\")\n}\n\nfunc TestTransportWebRTC_CanListenSingle(t *testing.T) {\n\ttr, listeningPeer := getTransport(t)\n\ttr1, connectingPeer := getTransport(t)\n\tlistenMultiaddr := ma.StringCast(\"/ip4/127.0.0.1/udp/0/webrtc-direct\")\n\n\tlistener, err := tr.Listen(listenMultiaddr)\n\trequire.NoError(t, err)\n\tdefer listener.Close()\n\n\tdone := make(chan struct{})\n\tgo func() {\n\t\t_, err := tr1.Dial(context.Background(), listener.Multiaddr(), listeningPeer)\n\t\tassert.NoError(t, err)\n\t\tclose(done)\n\t}()\n\n\tconn, err := listener.Accept()\n\trequire.NoError(t, err)\n\trequire.NotNil(t, conn)\n\n\trequire.Equal(t, connectingPeer, conn.RemotePeer())\n\tselect {\n\tcase <-done:\n\tcase <-time.After(10 * time.Second):\n\t\tt.FailNow()\n\t}\n}\n\n// WithListenerMaxInFlightConnections sets the maximum number of connections that are in-flight, i.e\n// they are being negotiated, or are waiting to be accepted.\nfunc WithListenerMaxInFlightConnections(m uint32) Option {\n\treturn func(t *WebRTCTransport) error {\n\t\tif m == 0 {\n\t\t\tt.maxInFlightConnections = DefaultMaxInFlightConnections\n\t\t} else {\n\t\t\tt.maxInFlightConnections = m\n\t\t}\n\t\treturn nil\n\t}\n}\n\nfunc TestTransportWebRTC_CanListenMultiple(t *testing.T) {\n\tcount := 3\n\ttr, listeningPeer := getTransport(t, WithListenerMaxInFlightConnections(uint32(count)))\n\n\tlistenMultiaddr := ma.StringCast(\"/ip4/127.0.0.1/udp/0/webrtc-direct\")\n\tlistener, err := tr.Listen(listenMultiaddr)\n\trequire.NoError(t, err)\n\tdefer listener.Close()\n\n\tctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\n\tvar wg sync.WaitGroup\n\tgo func() {\n\t\tfor range count {\n\t\t\tconn, err := listener.Accept()\n\t\t\tassert.NoError(t, err)\n\t\t\tassert.NotNil(t, conn)\n\t\t\tdefer conn.Close()\n\t\t}\n\t\twg.Wait()\n\t\tcancel()\n\t}()\n\n\tfor range count {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tctr, _ := getTransport(t)\n\t\t\tconn, err := ctr.Dial(ctx, listener.Multiaddr(), listeningPeer)\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\tdefault:\n\t\t\t\tassert.NoError(t, err)\n\t\t\t\tassert.NotNil(t, conn)\n\t\t\t\tt.Cleanup(func() { conn.Close() })\n\t\t\t}\n\t\t}()\n\t}\n\n\tselect {\n\tcase <-ctx.Done():\n\tcase <-time.After(30 * time.Second):\n\t\tt.Fatalf(\"timed out\")\n\t}\n}\n\nfunc TestTransportWebRTC_CanCreateSuccessiveConnections(t *testing.T) {\n\ttr, listeningPeer := getTransport(t)\n\tlistenMultiaddr := ma.StringCast(\"/ip4/127.0.0.1/udp/0/webrtc-direct\")\n\tlistener, err := tr.Listen(listenMultiaddr)\n\trequire.NoError(t, err)\n\tdefer listener.Close()\n\n\tcount := 2\n\n\tvar wg sync.WaitGroup\n\twg.Add(count)\n\tgo func() {\n\t\tfor range count {\n\t\t\tctr, _ := getTransport(t)\n\t\t\tconn, err := ctr.Dial(context.Background(), listener.Multiaddr(), listeningPeer)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, conn.RemotePeer(), listeningPeer)\n\t\t\tt.Cleanup(func() { conn.Close() })\n\t\t\twg.Done()\n\t\t}\n\t}()\n\n\tfor range count {\n\t\tconn, err := listener.Accept()\n\t\trequire.NoError(t, err)\n\t\tdefer conn.Close()\n\t}\n\twg.Wait()\n}\n\nfunc TestTransportWebRTC_ListenerCanCreateStreams(t *testing.T) {\n\ttr, listeningPeer := getTransport(t)\n\ttr1, connectingPeer := getTransport(t)\n\tlistenMultiaddr := ma.StringCast(\"/ip4/127.0.0.1/udp/0/webrtc-direct\")\n\tlistener, err := tr.Listen(listenMultiaddr)\n\trequire.NoError(t, err)\n\tdefer listener.Close()\n\n\tstreamChan := make(chan network.MuxedStream)\n\tgo func() {\n\t\tconn, err := tr1.Dial(context.Background(), listener.Multiaddr(), listeningPeer)\n\t\trequire.NoError(t, err)\n\t\tt.Cleanup(func() { conn.Close() })\n\t\tt.Logf(\"connection opened by dialer\")\n\n\t\tstream, err := conn.AcceptStream()\n\t\trequire.NoError(t, err)\n\t\tt.Logf(\"dialer accepted stream\")\n\t\tstreamChan <- stream\n\t}()\n\n\tconn, err := listener.Accept()\n\trequire.NoError(t, err)\n\tdefer conn.Close()\n\trequire.Equal(t, connectingPeer, conn.RemotePeer())\n\tt.Logf(\"listener accepted connection\")\n\n\tstream, err := conn.OpenStream(context.Background())\n\trequire.NoError(t, err)\n\tt.Logf(\"listener opened stream\")\n\t_, err = stream.Write([]byte(\"test\"))\n\trequire.NoError(t, err)\n\n\tvar str network.MuxedStream\n\tselect {\n\tcase str = <-streamChan:\n\tcase <-time.After(3 * time.Second):\n\t\tt.Fatal(\"stream opening timed out\")\n\t}\n\tbuf := make([]byte, 100)\n\tstream.SetReadDeadline(time.Now().Add(3 * time.Second))\n\tn, err := str.Read(buf)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"test\", string(buf[:n]))\n}\n\nfunc TestTransportWebRTC_DialerCanCreateStreams(t *testing.T) {\n\ttr, listeningPeer := getTransport(t)\n\tlistenMultiaddr := ma.StringCast(\"/ip4/127.0.0.1/udp/0/webrtc-direct\")\n\tlistener, err := tr.Listen(listenMultiaddr)\n\trequire.NoError(t, err)\n\tdefer listener.Close()\n\n\ttr1, connectingPeer := getTransport(t)\n\tdone := make(chan struct{})\n\n\tgo func() {\n\t\tlconn, err := listener.Accept()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, connectingPeer, lconn.RemotePeer())\n\t\tdefer lconn.Close()\n\n\t\tstream, err := lconn.AcceptStream()\n\t\trequire.NoError(t, err)\n\t\tbuf := make([]byte, 100)\n\t\tn, err := stream.Read(buf)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"test\", string(buf[:n]))\n\n\t\tclose(done)\n\t}()\n\n\tgo func() {\n\t\tconn, err := tr1.Dial(context.Background(), listener.Multiaddr(), listeningPeer)\n\t\trequire.NoError(t, err)\n\t\tdefer conn.Close()\n\t\tt.Logf(\"dialer opened connection\")\n\t\tstream, err := conn.OpenStream(context.Background())\n\t\trequire.NoError(t, err)\n\t\tt.Logf(\"dialer opened stream\")\n\t\t_, err = stream.Write([]byte(\"test\"))\n\t\trequire.NoError(t, err)\n\t\t<-done\n\t}()\n\n\tselect {\n\tcase <-done:\n\tcase <-time.After(10 * time.Second):\n\t\tt.Fatal(\"timed out\")\n\t}\n}\n\nfunc TestTransportWebRTC_DialerCanCreateStreamsMultiple(t *testing.T) {\n\ttr, listeningPeer := getTransport(t)\n\tlistenMultiaddr := ma.StringCast(\"/ip4/127.0.0.1/udp/0/webrtc-direct\")\n\tlistener, err := tr.Listen(listenMultiaddr)\n\trequire.NoError(t, err)\n\tdefer listener.Close()\n\n\ttr1, connectingPeer := getTransport(t)\n\treaderDone := make(chan struct{})\n\n\tconst (\n\t\tnumListeners = 10\n\t\tnumStreams   = 100\n\t\tnumWriters   = 10\n\t\tsize         = 20 << 10\n\t)\n\n\tgo func() {\n\t\tlconn, err := listener.Accept()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, connectingPeer, lconn.RemotePeer())\n\t\tdefer lconn.Close()\n\t\tvar wg sync.WaitGroup\n\t\tvar doneStreams atomic.Int32\n\t\tfor range numListeners {\n\t\t\twg.Add(1)\n\t\t\tgo func() {\n\t\t\t\tdefer wg.Done()\n\t\t\t\tfor {\n\t\t\t\t\tvar nn int32\n\t\t\t\t\tif nn = doneStreams.Add(1); nn > int32(numStreams) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\ts, err := lconn.AcceptStream()\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\tn, err := io.Copy(s, s)\n\t\t\t\t\trequire.Equal(t, n, int64(size))\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\ts.Close()\n\t\t\t\t}\n\t\t\t}()\n\t\t}\n\t\twg.Wait()\n\t\treaderDone <- struct{}{}\n\t}()\n\n\tconn, err := tr1.Dial(context.Background(), listener.Multiaddr(), listeningPeer)\n\trequire.NoError(t, err)\n\tdefer conn.Close()\n\n\tvar writerWG sync.WaitGroup\n\tvar cnt atomic.Int32\n\tvar streamsStarted atomic.Int32\n\tfor range numWriters {\n\t\twriterWG.Add(1)\n\t\tgo func() {\n\t\t\tdefer writerWG.Done()\n\t\t\tbuf := make([]byte, size)\n\t\t\tfor {\n\t\t\t\tvar nn int32\n\t\t\t\tif nn = streamsStarted.Add(1); nn > int32(numStreams) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\trand.Read(buf)\n\n\t\t\t\ts, err := conn.OpenStream(context.Background())\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tn, err := s.Write(buf)\n\t\t\t\trequire.Equal(t, n, size)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\ts.CloseWrite()\n\t\t\t\tresp := make([]byte, size+10)\n\t\t\t\tn, err = io.ReadFull(s, resp)\n\t\t\t\trequire.ErrorIs(t, err, io.ErrUnexpectedEOF)\n\t\t\t\trequire.Equal(t, n, size)\n\t\t\t\tif string(buf) != string(resp[:size]) {\n\t\t\t\t\tt.Errorf(\"bytes not equal: %d %d\", len(buf), len(resp))\n\t\t\t\t}\n\t\t\t\ts.Close()\n\t\t\t\tt.Log(\"completed stream: \", cnt.Add(1), s.(*stream).id)\n\t\t\t}\n\t\t}()\n\t}\n\twriterWG.Wait()\n\tselect {\n\tcase <-readerDone:\n\tcase <-time.After(100 * time.Second):\n\t\tt.Fatal(\"timed out\")\n\t}\n}\n\nfunc TestTransportWebRTC_Deadline(t *testing.T) {\n\ttr, listeningPeer := getTransport(t)\n\tlistenMultiaddr := ma.StringCast(\"/ip4/127.0.0.1/udp/0/webrtc-direct\")\n\tlistener, err := tr.Listen(listenMultiaddr)\n\trequire.NoError(t, err)\n\tdefer listener.Close()\n\ttr1, connectingPeer := getTransport(t)\n\n\tt.Run(\"SetReadDeadline\", func(t *testing.T) {\n\t\tgo func() {\n\t\t\tlconn, err := listener.Accept()\n\t\t\trequire.NoError(t, err)\n\t\t\tt.Cleanup(func() { lconn.Close() })\n\t\t\trequire.Equal(t, connectingPeer, lconn.RemotePeer())\n\t\t\t_, err = lconn.AcceptStream()\n\t\t\trequire.NoError(t, err)\n\t\t}()\n\n\t\tconn, err := tr1.Dial(context.Background(), listener.Multiaddr(), listeningPeer)\n\t\trequire.NoError(t, err)\n\t\tdefer conn.Close()\n\t\tstream, err := conn.OpenStream(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\t// deadline set to the past\n\t\tstream.SetReadDeadline(time.Now().Add(-200 * time.Millisecond))\n\t\t_, err = stream.Read([]byte{0, 0})\n\t\trequire.ErrorIs(t, err, os.ErrDeadlineExceeded)\n\n\t\t// future deadline exceeded\n\t\tstream.SetReadDeadline(time.Now().Add(100 * time.Millisecond))\n\t\t_, err = stream.Read([]byte{0, 0})\n\t\trequire.ErrorIs(t, err, os.ErrDeadlineExceeded)\n\t})\n\n\tt.Run(\"SetWriteDeadline\", func(t *testing.T) {\n\t\tgo func() {\n\t\t\tlconn, err := listener.Accept()\n\t\t\trequire.NoError(t, err)\n\t\t\tt.Cleanup(func() { lconn.Close() })\n\t\t\trequire.Equal(t, connectingPeer, lconn.RemotePeer())\n\t\t\t_, err = lconn.AcceptStream()\n\t\t\trequire.NoError(t, err)\n\t\t}()\n\n\t\tconn, err := tr1.Dial(context.Background(), listener.Multiaddr(), listeningPeer)\n\t\trequire.NoError(t, err)\n\t\tdefer conn.Close()\n\t\tstream, err := conn.OpenStream(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\tstream.SetWriteDeadline(time.Now().Add(100 * time.Millisecond))\n\t\tlargeBuffer := make([]byte, 20*1024*1024)\n\t\t_, err = stream.Write(largeBuffer)\n\t\trequire.ErrorIs(t, err, os.ErrDeadlineExceeded)\n\n\t\tstream.SetWriteDeadline(time.Now().Add(-200 * time.Millisecond))\n\t\tsmallBuffer := make([]byte, 1024)\n\t\t_, err = stream.Write(smallBuffer)\n\t\trequire.ErrorIs(t, err, os.ErrDeadlineExceeded)\n\t})\n}\n\nfunc TestTransportWebRTC_StreamWriteBufferContention(t *testing.T) {\n\ttr, listeningPeer := getTransport(t)\n\tlistenMultiaddr := ma.StringCast(\"/ip4/127.0.0.1/udp/0/webrtc-direct\")\n\tlistener, err := tr.Listen(listenMultiaddr)\n\trequire.NoError(t, err)\n\tdefer listener.Close()\n\n\ttr1, connectingPeer := getTransport(t)\n\n\tvar wg sync.WaitGroup\n\twg.Add(3)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tlconn, err := listener.Accept()\n\t\trequire.NoError(t, err)\n\t\tt.Cleanup(func() { lconn.Close() })\n\t\trequire.Equal(t, connectingPeer, lconn.RemotePeer())\n\t\tfor range 2 {\n\t\t\tgo func() {\n\t\t\t\tdefer wg.Done()\n\t\t\t\t_, err := lconn.AcceptStream()\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}()\n\t\t}\n\t}()\n\n\tconn, err := tr1.Dial(context.Background(), listener.Multiaddr(), listeningPeer)\n\trequire.NoError(t, err)\n\tdefer conn.Close()\n\n\terrC := make(chan error)\n\t// writers\n\tfor range 2 {\n\t\tgo func() {\n\t\t\tstream, err := conn.OpenStream(context.Background())\n\t\t\trequire.NoError(t, err)\n\n\t\t\tstream.SetWriteDeadline(time.Now().Add(200 * time.Millisecond))\n\t\t\tlargeBuffer := make([]byte, 2*1024*1024)\n\t\t\t_, err = stream.Write(largeBuffer)\n\t\t\terrC <- err\n\t\t}()\n\t}\n\n\trequire.ErrorIs(t, <-errC, os.ErrDeadlineExceeded)\n\trequire.ErrorIs(t, <-errC, os.ErrDeadlineExceeded)\n\twg.Wait()\n}\n\nfunc TestTransportWebRTC_RemoteReadsAfterClose(t *testing.T) {\n\ttr, listeningPeer := getTransport(t)\n\tlistenMultiaddr := ma.StringCast(\"/ip4/127.0.0.1/udp/0/webrtc-direct\")\n\tlistener, err := tr.Listen(listenMultiaddr)\n\trequire.NoError(t, err)\n\tdefer listener.Close()\n\n\ttr1, _ := getTransport(t)\n\n\tdone := make(chan error)\n\tgo func() {\n\t\tlconn, err := listener.Accept()\n\t\tif err != nil {\n\t\t\tdone <- err\n\t\t\treturn\n\t\t}\n\t\tt.Cleanup(func() { lconn.Close() })\n\n\t\tstream, err := lconn.AcceptStream()\n\t\tif err != nil {\n\t\t\tdone <- err\n\t\t\treturn\n\t\t}\n\t\t_, err = stream.Write([]byte{1, 2, 3, 4})\n\t\tif err != nil {\n\t\t\tdone <- err\n\t\t\treturn\n\t\t}\n\t\terr = stream.Close()\n\t\tif err != nil {\n\t\t\tdone <- err\n\t\t\treturn\n\t\t}\n\t\tclose(done)\n\t}()\n\n\tconn, err := tr1.Dial(context.Background(), listener.Multiaddr(), listeningPeer)\n\trequire.NoError(t, err)\n\tdefer conn.Close()\n\t// create a stream\n\tstream, err := conn.OpenStream(context.Background())\n\n\trequire.NoError(t, err)\n\t// require write and close to complete\n\trequire.NoError(t, <-done)\n\tstream.SetReadDeadline(time.Now().Add(5 * time.Second))\n\n\tbuf := make([]byte, 10)\n\tn, err := stream.Read(buf)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 4, n)\n}\n\nfunc TestTransportWebRTC_RemoteReadsAfterClose2(t *testing.T) {\n\ttr, listeningPeer := getTransport(t)\n\tlistenMultiaddr := ma.StringCast(\"/ip4/127.0.0.1/udp/0/webrtc-direct\")\n\tlistener, err := tr.Listen(listenMultiaddr)\n\trequire.NoError(t, err)\n\tdefer listener.Close()\n\n\ttr1, _ := getTransport(t)\n\n\tawaitStreamClosure := make(chan struct{})\n\treadBytesResult := make(chan int)\n\tdone := make(chan error)\n\tgo func() {\n\t\tlconn, err := listener.Accept()\n\t\tif err != nil {\n\t\t\tdone <- err\n\t\t\treturn\n\t\t}\n\t\tdefer lconn.Close()\n\t\tstream, err := lconn.AcceptStream()\n\t\tif err != nil {\n\t\t\tdone <- err\n\t\t\treturn\n\t\t}\n\n\t\t<-awaitStreamClosure\n\t\tbuf := make([]byte, 16)\n\t\tn, err := stream.Read(buf)\n\t\tif err != nil {\n\t\t\tdone <- err\n\t\t\treturn\n\t\t}\n\t\treadBytesResult <- n\n\t\tclose(done)\n\t}()\n\n\tconn, err := tr1.Dial(context.Background(), listener.Multiaddr(), listeningPeer)\n\trequire.NoError(t, err)\n\tdefer conn.Close()\n\t// create a stream\n\tstream, err := conn.OpenStream(context.Background())\n\trequire.NoError(t, err)\n\t_, err = stream.Write([]byte{1, 2, 3, 4})\n\trequire.NoError(t, err)\n\terr = stream.Close()\n\trequire.NoError(t, err)\n\t// signal stream closure\n\tclose(awaitStreamClosure)\n\trequire.Equal(t, 4, <-readBytesResult)\n}\n\nfunc TestTransportWebRTC_Close(t *testing.T) {\n\ttr, listeningPeer := getTransport(t)\n\tlistenMultiaddr := ma.StringCast(\"/ip4/127.0.0.1/udp/0/webrtc-direct\")\n\tlistener, err := tr.Listen(listenMultiaddr)\n\trequire.NoError(t, err)\n\tdefer listener.Close()\n\n\ttr1, connectingPeer := getTransport(t)\n\n\tt.Run(\"RemoteClosesStream\", func(t *testing.T) {\n\t\tvar wg sync.WaitGroup\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tlconn, err := listener.Accept()\n\t\t\trequire.NoError(t, err)\n\t\t\tt.Cleanup(func() { lconn.Close() })\n\t\t\trequire.Equal(t, connectingPeer, lconn.RemotePeer())\n\t\t\tstream, err := lconn.AcceptStream()\n\t\t\trequire.NoError(t, err)\n\t\t\ttime.Sleep(100 * time.Millisecond)\n\t\t\t_ = stream.Close()\n\t\t}()\n\n\t\tbuf := make([]byte, 2)\n\n\t\tconn, err := tr1.Dial(context.Background(), listener.Multiaddr(), listeningPeer)\n\t\trequire.NoError(t, err)\n\t\tdefer conn.Close()\n\t\tstream, err := conn.OpenStream(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\terr = stream.SetReadDeadline(time.Now().Add(2 * time.Second))\n\t\trequire.NoError(t, err)\n\t\t_, err = stream.Read(buf)\n\t\trequire.ErrorIs(t, err, io.EOF)\n\n\t\twg.Wait()\n\t})\n}\n\nfunc TestTransportWebRTC_PeerConnectionDTLSFailed(t *testing.T) {\n\ttr, listeningPeer := getTransport(t)\n\tlistenMultiaddr := ma.StringCast(\"/ip4/127.0.0.1/udp/0/webrtc-direct\")\n\tln, err := tr.Listen(listenMultiaddr)\n\trequire.NoError(t, err)\n\tdefer ln.Close()\n\n\tencoded, err := hex.DecodeString(\"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad\")\n\trequire.NoError(t, err)\n\tencodedCerthash, err := multihash.Encode(encoded, multihash.SHA2_256)\n\trequire.NoError(t, err)\n\tbadEncodedCerthash, err := multibase.Encode(multibase.Base58BTC, encodedCerthash)\n\trequire.NoError(t, err)\n\tbadCerthash, err := ma.NewMultiaddr(fmt.Sprintf(\"/certhash/%s\", badEncodedCerthash))\n\trequire.NoError(t, err)\n\tbadMultiaddr, _ := ma.SplitFunc(ln.Multiaddr(), func(c ma.Component) bool { return c.Protocol().Code == ma.P_CERTHASH })\n\tbadMultiaddr = badMultiaddr.Encapsulate(badCerthash)\n\n\ttr1, _ := getTransport(t)\n\tconn, err := tr1.Dial(context.Background(), badMultiaddr, listeningPeer)\n\trequire.Error(t, err)\n\trequire.ErrorContains(t, err, \"failed\")\n\trequire.Nil(t, conn)\n}\n\nfunc newUDPConnLocalhost(t testing.TB) *net.UDPConn {\n\tt.Helper()\n\tconn, err := net.ListenUDP(\"udp\", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})\n\trequire.NoError(t, err)\n\tt.Cleanup(func() { conn.Close() })\n\treturn conn\n}\n\nfunc TestConnectionTimeoutOnListener(t *testing.T) {\n\ttr, listeningPeer := getTransport(t)\n\ttr.peerConnectionTimeouts.Disconnect = 100 * time.Millisecond\n\ttr.peerConnectionTimeouts.Failed = 150 * time.Millisecond\n\ttr.peerConnectionTimeouts.Keepalive = 50 * time.Millisecond\n\n\tlistenMultiaddr := ma.StringCast(\"/ip4/127.0.0.1/udp/0/webrtc-direct\")\n\tln, err := tr.Listen(listenMultiaddr)\n\trequire.NoError(t, err)\n\tdefer ln.Close()\n\n\tvar drop atomic.Bool\n\tproxy := quicproxy.Proxy{\n\t\tConn:       newUDPConnLocalhost(t),\n\t\tServerAddr: ln.Addr().(*net.UDPAddr),\n\t\tDropPacket: func(_ quicproxy.Direction, _, _ net.Addr, _ []byte) bool { return drop.Load() },\n\t}\n\trequire.NoError(t, proxy.Start())\n\tdefer proxy.Close()\n\n\ttr1, connectingPeer := getTransport(t)\n\tgo func() {\n\t\tctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)\n\t\tdefer cancel()\n\t\taddr, err := manet.FromNetAddr(proxy.LocalAddr())\n\t\trequire.NoError(t, err)\n\t\t_, webrtcComponent := ma.SplitFunc(ln.Multiaddr(), func(c ma.Component) bool { return c.Protocol().Code == ma.P_WEBRTC_DIRECT })\n\t\taddr = addr.Encapsulate(webrtcComponent)\n\t\tconn, err := tr1.Dial(ctx, addr, listeningPeer)\n\t\trequire.NoError(t, err)\n\t\tt.Cleanup(func() { conn.Close() })\n\t\tstr, err := conn.OpenStream(ctx)\n\t\trequire.NoError(t, err)\n\t\tstr.Write([]byte(\"foobar\"))\n\t}()\n\n\tconn, err := ln.Accept()\n\trequire.NoError(t, err)\n\trequire.Equal(t, connectingPeer, conn.RemotePeer())\n\tdefer conn.Close()\n\n\tstr, err := conn.AcceptStream()\n\trequire.NoError(t, err)\n\t_, err = str.Write([]byte(\"test\"))\n\trequire.NoError(t, err)\n\t// start dropping all packets\n\tdrop.Store(true)\n\tstart := time.Now()\n\tfor {\n\t\tif _, err := str.Write([]byte(\"test\")); err != nil {\n\t\t\tif os.IsTimeout(err) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\t// If we write when a connection timeout happens, sctp provides\n\t\t\t// a \"stream closed\" error. This occurs concurrently with the\n\t\t\t// callback we receive for connection timeout.\n\t\t\t// Test once more after sleep that we provide the correct error.\n\t\t\tif strings.Contains(err.Error(), \"stream closed\") {\n\t\t\t\ttime.Sleep(50 * time.Millisecond)\n\t\t\t\t_, err = str.Write([]byte(\"test\"))\n\t\t\t\trequire.True(t, os.IsTimeout(err), \"invalid error type: %v\", err)\n\t\t\t} else {\n\t\t\t\tt.Fatal(\"invalid error type\", err)\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\n\t\tif time.Since(start) > 5*time.Second {\n\t\t\tt.Fatal(\"timeout\")\n\t\t}\n\t\t// make sure to not write too often, we don't want to fill the flow control window\n\t\ttime.Sleep(20 * time.Millisecond)\n\t}\n\t// make sure that accepting a stream also returns an error...\n\t_, err = conn.AcceptStream()\n\trequire.True(t, os.IsTimeout(err))\n\t// ... as well as opening a new stream\n\t_, err = conn.OpenStream(context.Background())\n\trequire.True(t, os.IsTimeout(err))\n}\n\nfunc TestMaxInFlightRequests(t *testing.T) {\n\tconst count = 3\n\ttr, listeningPeer := getTransport(t,\n\t\tWithListenerMaxInFlightConnections(count),\n\t)\n\tln, err := tr.Listen(ma.StringCast(\"/ip4/127.0.0.1/udp/0/webrtc-direct\"))\n\trequire.NoError(t, err)\n\tdefer ln.Close()\n\n\tvar wg sync.WaitGroup\n\tvar success, fails atomic.Int32\n\tfor range count + 1 {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tdialer, _ := getTransport(t)\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\t\t\tdefer cancel()\n\t\t\tif conn, err := dialer.Dial(ctx, ln.Multiaddr(), listeningPeer); err == nil {\n\t\t\t\tsuccess.Add(1)\n\t\t\t\tt.Cleanup(func() { conn.Close() })\n\t\t\t} else {\n\t\t\t\tt.Log(\"failed to dial:\", err)\n\t\t\t\tfails.Add(1)\n\t\t\t}\n\t\t}()\n\t}\n\twg.Wait()\n\trequire.Equal(t, count, int(success.Load()), \"expected exactly 3 dial successes\")\n\trequire.Equal(t, 1, int(fails.Load()), \"expected exactly 1 dial failure\")\n}\n\nfunc TestGenUfrag(t *testing.T) {\n\tfor range 10 {\n\t\ts := genUfrag()\n\t\trequire.True(t, strings.HasPrefix(s, \"libp2p+webrtc+v1/\"))\n\t}\n}\n\nfunc TestManyConnections(t *testing.T) {\n\tconst numListeners = 5\n\tconst dialersPerListener = 5\n\tconst connsPerDialer = 10\n\terrCh := make(chan error, 10*numListeners*dialersPerListener*connsPerDialer)\n\tsuccessCh := make(chan struct{}, 10*numListeners*dialersPerListener*connsPerDialer)\n\tlisteners := make([]tpt.Listener, 0, numListeners)\n\tlistenerPeerIDs := make([]peer.ID, 0, numListeners)\n\n\tfor range numListeners {\n\t\ttr, lp := getTransport(t)\n\t\tlistenerPeerIDs = append(listenerPeerIDs, lp)\n\t\tln, err := tr.Listen(ma.StringCast(\"/ip4/127.0.0.1/udp/0/webrtc-direct\"))\n\t\trequire.NoError(t, err)\n\t\tdefer ln.Close()\n\t\tlisteners = append(listeners, ln)\n\t}\n\n\trunListenConn := func(conn tpt.CapableConn) {\n\t\tdefer conn.Close()\n\t\ts, err := conn.AcceptStream()\n\t\tif err != nil {\n\t\t\tt.Errorf(\"accept stream failed for listener: %s\", err)\n\t\t\terrCh <- err\n\t\t\treturn\n\t\t}\n\t\tvar b [4]byte\n\t\tif _, err := s.Read(b[:]); err != nil {\n\t\t\tt.Errorf(\"read stream failed for listener: %s\", err)\n\t\t\terrCh <- err\n\t\t\treturn\n\t\t}\n\t\ts.Write(b[:])\n\t\t_, err = s.Read(b[:]) // peer will close the connection after read\n\t\tif !assert.Error(t, err) {\n\t\t\terr = errors.New(\"invalid read: expected conn to close\")\n\t\t\terrCh <- err\n\t\t\treturn\n\t\t}\n\t\tsuccessCh <- struct{}{}\n\t}\n\n\trunDialConn := func(conn tpt.CapableConn) {\n\t\tdefer conn.Close()\n\n\t\ts, err := conn.OpenStream(context.Background())\n\t\tif err != nil {\n\t\t\tt.Errorf(\"accept stream failed for listener: %s\", err)\n\t\t\terrCh <- err\n\t\t\treturn\n\t\t}\n\t\tvar b [4]byte\n\t\tif _, err := s.Write(b[:]); err != nil {\n\t\t\tt.Errorf(\"write stream failed for dialer: %s\", err)\n\t\t\terrCh <- err\n\t\t\treturn\n\t\t}\n\t\tif _, err := s.Read(b[:]); err != nil {\n\t\t\tt.Errorf(\"read stream failed for dialer: %s\", err)\n\t\t\terrCh <- err\n\t\t\treturn\n\t\t}\n\t\ts.Close()\n\t}\n\n\trunListener := func(ln tpt.Listener) {\n\t\tfor range dialersPerListener * connsPerDialer {\n\t\t\tconn, err := ln.Accept()\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"listener failed to accept conneciton: %s\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tgo runListenConn(conn)\n\t\t}\n\t}\n\n\trunDialer := func(ln tpt.Listener, lp peer.ID) {\n\t\ttp, _ := getTransport(t)\n\t\tfor range connsPerDialer {\n\t\t\t// We want to test for deadlocks, set a high timeout\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)\n\t\t\tconn, err := tp.Dial(ctx, ln.Multiaddr(), lp)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"dial failed: %s\", err)\n\t\t\t\terrCh <- err\n\t\t\t\tcancel()\n\t\t\t\treturn\n\t\t\t}\n\t\t\trunDialConn(conn)\n\t\t\tcancel()\n\t\t}\n\t}\n\n\tfor i := range numListeners {\n\t\tgo runListener(listeners[i])\n\t}\n\tfor i := range numListeners {\n\t\tfor range dialersPerListener {\n\t\t\tgo runDialer(listeners[i], listenerPeerIDs[i])\n\t\t}\n\t}\n\n\tfor i := range numListeners * dialersPerListener * connsPerDialer {\n\t\tselect {\n\t\tcase <-successCh:\n\t\t\tt.Log(\"completed conn: \", i)\n\t\tcase err := <-errCh:\n\t\t\tt.Fatalf(\"failed: %s\", err)\n\t\tcase <-time.After(300 * time.Second):\n\t\t\tt.Fatalf(\"timed out\")\n\t\t}\n\t}\n}\n\nfunc TestConnectionClosedWhenRemoteCloses(t *testing.T) {\n\tlistenT, p := getTransport(t)\n\tlistener, err := listenT.Listen(ma.StringCast(\"/ip4/127.0.0.1/udp/0/webrtc-direct\"))\n\trequire.NoError(t, err)\n\tdefer listener.Close()\n\n\taccepted := make(chan struct{})\n\tdialer, _ := getTransport(t)\n\tvar wg sync.WaitGroup\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tc, err := listener.Accept()\n\t\tclose(accepted)\n\t\tif !assert.NoError(t, err) {\n\t\t\treturn\n\t\t}\n\t\tassert.Eventually(t, func() bool {\n\t\t\treturn c.IsClosed()\n\t\t}, 5*time.Second, 50*time.Millisecond)\n\t}()\n\n\tc, err := dialer.Dial(context.Background(), listener.Multiaddr(), p)\n\trequire.NoError(t, err)\n\t<-accepted\n\tc.Close()\n\twg.Wait()\n}\n"
  },
  {
    "path": "p2p/transport/webrtc/udpmux/mux.go",
    "content": "// The udpmux package contains the logic for multiplexing multiple WebRTC (ICE)\n// connections over a single UDP socket.\npackage udpmux\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"strings\"\n\t\"sync\"\n\n\tpool \"github.com/libp2p/go-buffer-pool\"\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n\t\"github.com/pion/ice/v4\"\n\t\"github.com/pion/stun/v3\"\n)\n\nvar log = logging.Logger(\"webrtc-udpmux\")\n\n// ReceiveBufSize is the size of the buffer used to receive packets from the PacketConn.\n// It is fine for this number to be higher than the actual path MTU as this value is not\n// used to decide the packet size on the write path.\nconst ReceiveBufSize = 1500\n\ntype Candidate struct {\n\tUfrag string\n\tAddr  *net.UDPAddr\n}\n\n// UDPMux multiplexes multiple ICE connections over a single net.PacketConn,\n// generally a UDP socket.\n//\n// The connections are indexed by (ufrag, IP address family) and by remote\n// address from which the connection has received valid STUN/RTC packets.\n//\n// When a new packet is received on the underlying net.PacketConn, we\n// first check the address map to see if there is a connection associated with the\n// remote address:\n// If found, we pass the packet to that connection.\n// Otherwise, we check to see if the packet is a STUN packet.\n// If it is, we read the ufrag from the STUN packet and use it to check if there\n// is a connection associated with the (ufrag, IP address family) pair.\n// If found we add the association to the address map.\ntype UDPMux struct {\n\tsocket net.PacketConn\n\n\tqueue chan Candidate\n\n\tmx sync.Mutex\n\t// ufragMap allows us to multiplex incoming STUN packets based on ufrag\n\tufragMap map[ufragConnKey]*muxedConnection\n\t// addrMap allows us to correctly direct incoming packets after the connection\n\t// is established and ufrag isn't available on all packets\n\taddrMap map[string]*muxedConnection\n\t// ufragAddrMap allows cleaning up all addresses from the addrMap once the connection is closed\n\t// During the ICE connectivity checks, the same ufrag might be used on multiple addresses.\n\tufragAddrMap map[ufragConnKey][]net.Addr\n\n\t// the context controls the lifecycle of the mux\n\twg     sync.WaitGroup\n\tctx    context.Context\n\tcancel context.CancelFunc\n}\n\nvar _ ice.UDPMux = &UDPMux{}\n\nfunc NewUDPMux(socket net.PacketConn) *UDPMux {\n\tctx, cancel := context.WithCancel(context.Background())\n\tmux := &UDPMux{\n\t\tctx:          ctx,\n\t\tcancel:       cancel,\n\t\tsocket:       socket,\n\t\tufragMap:     make(map[ufragConnKey]*muxedConnection),\n\t\taddrMap:      make(map[string]*muxedConnection),\n\t\tufragAddrMap: make(map[ufragConnKey][]net.Addr),\n\t\tqueue:        make(chan Candidate, 32),\n\t}\n\n\treturn mux\n}\n\nfunc (mux *UDPMux) Start() {\n\tmux.wg.Add(1)\n\tgo func() {\n\t\tdefer mux.wg.Done()\n\t\tmux.readLoop()\n\t}()\n}\n\n// GetListenAddresses implements ice.UDPMux\nfunc (mux *UDPMux) GetListenAddresses() []net.Addr {\n\treturn []net.Addr{mux.socket.LocalAddr()}\n}\n\n// GetConn implements ice.UDPMux\n// It creates a net.PacketConn for a given ufrag if an existing one cannot be found.\n// We differentiate IPv4 and IPv6 addresses, since a remote is can be reachable at multiple different\n// UDP addresses of the same IP address family (eg. server-reflexive addresses and peer-reflexive addresses).\nfunc (mux *UDPMux) GetConn(ufrag string, addr net.Addr) (net.PacketConn, error) {\n\ta, ok := addr.(*net.UDPAddr)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"unexpected address type: %T\", addr)\n\t}\n\tselect {\n\tcase <-mux.ctx.Done():\n\t\treturn nil, io.ErrClosedPipe\n\tdefault:\n\t\tisIPv6 := ok && a.IP.To4() == nil\n\t\t_, conn := mux.getOrCreateConn(ufrag, isIPv6, mux, addr)\n\t\treturn conn, nil\n\t}\n}\n\n// Close implements ice.UDPMux\nfunc (mux *UDPMux) Close() error {\n\tselect {\n\tcase <-mux.ctx.Done():\n\t\treturn nil\n\tdefault:\n\t}\n\tmux.cancel()\n\tmux.socket.Close()\n\tmux.wg.Wait()\n\treturn nil\n}\n\n// writeTo writes a packet to the underlying net.PacketConn\nfunc (mux *UDPMux) writeTo(buf []byte, addr net.Addr) (int, error) {\n\treturn mux.socket.WriteTo(buf, addr)\n}\n\nfunc (mux *UDPMux) readLoop() {\n\tfor {\n\t\tselect {\n\t\tcase <-mux.ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\n\t\tbuf := pool.Get(ReceiveBufSize)\n\n\t\tn, addr, err := mux.socket.ReadFrom(buf)\n\t\tif err != nil {\n\t\t\tif strings.Contains(err.Error(), \"use of closed network connection\") || errors.Is(err, context.Canceled) {\n\t\t\t\tlog.Debug(\"readLoop exiting: socket closed\", \"local_addr\", mux.socket.LocalAddr())\n\t\t\t} else {\n\t\t\t\tlog.Error(\"error reading from socket\", \"local_addr\", mux.socket.LocalAddr(), \"error\", err)\n\t\t\t}\n\t\t\tpool.Put(buf)\n\t\t\treturn\n\t\t}\n\t\tbuf = buf[:n]\n\n\t\tif processed := mux.processPacket(buf, addr); !processed {\n\t\t\tpool.Put(buf)\n\t\t}\n\t}\n}\n\nfunc (mux *UDPMux) processPacket(buf []byte, addr net.Addr) (processed bool) {\n\tudpAddr, ok := addr.(*net.UDPAddr)\n\tif !ok {\n\t\tlog.Error(\"received a non-UDP address\", \"addr\", addr)\n\t\treturn false\n\t}\n\tisIPv6 := udpAddr.IP.To4() == nil\n\n\t// Connections are indexed by remote address. We first\n\t// check if the remote address has a connection associated\n\t// with it. If yes, we push the received packet to the connection\n\tmux.mx.Lock()\n\tconn, ok := mux.addrMap[addr.String()]\n\tmux.mx.Unlock()\n\tif ok {\n\t\tif err := conn.Push(buf, addr); err != nil {\n\t\t\tlog.Debug(\"could not push packet\", \"error\", err)\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t}\n\n\tif !stun.IsMessage(buf) {\n\t\tlog.Debug(\"incoming message is not a STUN message\")\n\t\treturn false\n\t}\n\n\tmsg := &stun.Message{Raw: buf}\n\tif err := msg.Decode(); err != nil {\n\t\tlog.Debug(\"failed to decode STUN message\", \"error\", err)\n\t\treturn false\n\t}\n\tif msg.Type != stun.BindingRequest {\n\t\tlog.Debug(\"incoming message should be a STUN binding request\", \"got_type\", msg.Type)\n\t\treturn false\n\t}\n\n\tufrag, err := ufragFromSTUNMessage(msg)\n\tif err != nil {\n\t\tlog.Debug(\"could not find STUN username\", \"error\", err)\n\t\treturn false\n\t}\n\n\tconnCreated, conn := mux.getOrCreateConn(ufrag, isIPv6, mux, udpAddr)\n\tif connCreated {\n\t\tselect {\n\t\tcase mux.queue <- Candidate{Addr: udpAddr, Ufrag: ufrag}:\n\t\tdefault:\n\t\t\tlog.Debug(\"queue full, dropping incoming candidate\", \"ufrag\", ufrag, \"addr\", udpAddr)\n\t\t\tconn.Close()\n\t\t\treturn false\n\t\t}\n\t}\n\n\tif err := conn.Push(buf, addr); err != nil {\n\t\tlog.Debug(\"could not push packet\", \"error\", err)\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc (mux *UDPMux) Accept(ctx context.Context) (Candidate, error) {\n\tselect {\n\tcase c := <-mux.queue:\n\t\treturn c, nil\n\tcase <-ctx.Done():\n\t\treturn Candidate{}, ctx.Err()\n\tcase <-mux.ctx.Done():\n\t\treturn Candidate{}, mux.ctx.Err()\n\t}\n}\n\ntype ufragConnKey struct {\n\tufrag  string\n\tisIPv6 bool\n}\n\n// ufragFromSTUNMessage returns the local or ufrag\n// from the STUN username attribute. Local ufrag is the ufrag of the\n// peer which initiated the connectivity check, e.g in a connectivity\n// check from A to B, the username attribute will be B_ufrag:A_ufrag\n// with the local ufrag value being A_ufrag. In case of ice-lite, the\n// localUfrag value will always be the remote peer's ufrag since ICE-lite\n// implementations do not generate connectivity checks. In our specific\n// case, since the local and remote ufrag is equal, we can return\n// either value.\nfunc ufragFromSTUNMessage(msg *stun.Message) (string, error) {\n\tattr, err := msg.Get(stun.AttrUsername)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tindex := bytes.Index(attr, []byte{':'})\n\tif index == -1 {\n\t\treturn \"\", fmt.Errorf(\"invalid STUN username attribute\")\n\t}\n\treturn string(attr[index+1:]), nil\n}\n\n// RemoveConnByUfrag removes the connection associated with the ufrag and all the\n// addresses associated with that connection. This method is called by pion when\n// a peerconnection is closed.\nfunc (mux *UDPMux) RemoveConnByUfrag(ufrag string) {\n\tif ufrag == \"\" {\n\t\treturn\n\t}\n\n\tmux.mx.Lock()\n\tdefer mux.mx.Unlock()\n\n\tfor _, isIPv6 := range [...]bool{true, false} {\n\t\tkey := ufragConnKey{ufrag: ufrag, isIPv6: isIPv6}\n\t\tif conn, ok := mux.ufragMap[key]; ok {\n\t\t\tdelete(mux.ufragMap, key)\n\t\t\tfor _, addr := range mux.ufragAddrMap[key] {\n\t\t\t\tdelete(mux.addrMap, addr.String())\n\t\t\t}\n\t\t\tdelete(mux.ufragAddrMap, key)\n\t\t\tconn.close()\n\t\t}\n\t}\n}\n\nfunc (mux *UDPMux) getOrCreateConn(ufrag string, isIPv6 bool, _ *UDPMux, addr net.Addr) (created bool, _ *muxedConnection) {\n\tkey := ufragConnKey{ufrag: ufrag, isIPv6: isIPv6}\n\n\tmux.mx.Lock()\n\tdefer mux.mx.Unlock()\n\n\tif conn, ok := mux.ufragMap[key]; ok {\n\t\tmux.addrMap[addr.String()] = conn\n\t\tmux.ufragAddrMap[key] = append(mux.ufragAddrMap[key], addr)\n\t\treturn false, conn\n\t}\n\n\tconn := newMuxedConnection(mux, ufrag)\n\tmux.ufragMap[key] = conn\n\tmux.addrMap[addr.String()] = conn\n\tmux.ufragAddrMap[key] = append(mux.ufragAddrMap[key], addr)\n\treturn true, conn\n}\n"
  },
  {
    "path": "p2p/transport/webrtc/udpmux/mux_test.go",
    "content": "package udpmux\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/pion/stun/v3\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc getSTUNBindingRequest(ufrag string) *stun.Message {\n\tmsg := stun.New()\n\tmsg.SetType(stun.BindingRequest)\n\tuattr := stun.RawAttribute{\n\t\tType:  stun.AttrUsername,\n\t\tValue: fmt.Appendf(nil, \"%s:%s\", ufrag, ufrag), // This is the format we expect in our connections\n\t}\n\tuattr.AddTo(msg)\n\tmsg.Encode()\n\treturn msg\n}\n\nfunc setupMapping(t *testing.T, ufrag string, from net.PacketConn, m *UDPMux) {\n\tt.Helper()\n\tmsg := getSTUNBindingRequest(ufrag)\n\t_, err := from.WriteTo(msg.Raw, m.GetListenAddresses()[0])\n\trequire.NoError(t, err)\n}\n\nfunc newPacketConn(t *testing.T) net.PacketConn {\n\tt.Helper()\n\tudpPort0 := &net.UDPAddr{IP: net.ParseIP(\"127.0.0.1\"), Port: 0}\n\tc, err := net.ListenUDP(\"udp\", udpPort0)\n\trequire.NoError(t, err)\n\tt.Cleanup(func() { c.Close() })\n\treturn c\n}\n\nfunc TestAccept(t *testing.T) {\n\tc := newPacketConn(t)\n\tdefer c.Close()\n\tm := NewUDPMux(c)\n\tm.Start()\n\tdefer m.Close()\n\n\tufrags := []string{\"a\", \"b\", \"c\", \"d\"}\n\tconns := make([]net.PacketConn, len(ufrags))\n\tfor i, ufrag := range ufrags {\n\t\tconns[i] = newPacketConn(t)\n\t\tsetupMapping(t, ufrag, conns[i], m)\n\t}\n\tfor i, ufrag := range ufrags {\n\t\tc, err := m.Accept(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, c.Ufrag, ufrag)\n\t\trequire.Equal(t, c.Addr, conns[i].LocalAddr())\n\t}\n\n\tfor i, ufrag := range ufrags {\n\t\t// should not be accepted\n\t\tsetupMapping(t, ufrag, conns[i], m)\n\t\tctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)\n\t\tdefer cancel()\n\t\t_, err := m.Accept(ctx)\n\t\trequire.Error(t, err)\n\n\t\t// should not be accepted\n\t\tcc := newPacketConn(t)\n\t\tsetupMapping(t, ufrag, cc, m)\n\t\tctx, cancel = context.WithTimeout(context.Background(), 100*time.Millisecond)\n\t\tdefer cancel()\n\t\t_, err = m.Accept(ctx)\n\t\trequire.Error(t, err)\n\t}\n}\n\nfunc TestGetConn(t *testing.T) {\n\tc := newPacketConn(t)\n\tm := NewUDPMux(c)\n\tm.Start()\n\tdefer m.Close()\n\n\tufrags := []string{\"a\", \"b\", \"c\", \"d\"}\n\tconns := make([]net.PacketConn, len(ufrags))\n\tfor i, ufrag := range ufrags {\n\t\tconns[i] = newPacketConn(t)\n\t\tsetupMapping(t, ufrag, conns[i], m)\n\t}\n\tfor i, ufrag := range ufrags {\n\t\tc, err := m.Accept(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, c.Ufrag, ufrag)\n\t\trequire.Equal(t, c.Addr, conns[i].LocalAddr())\n\t}\n\n\tfor i, ufrag := range ufrags {\n\t\tc, err := m.GetConn(ufrag, conns[i].LocalAddr())\n\t\trequire.NoError(t, err)\n\t\tmsg := make([]byte, 100)\n\t\t_, _, err = c.ReadFrom(msg)\n\t\trequire.NoError(t, err)\n\t}\n\n\tfor i, ufrag := range ufrags {\n\t\tcc := newPacketConn(t)\n\t\t// setupMapping of cc to ufrags[0] and remove the stun binding request from the queue\n\t\tsetupMapping(t, ufrag, cc, m)\n\t\tmc, err := m.GetConn(ufrag, cc.LocalAddr())\n\t\trequire.NoError(t, err)\n\t\tmsg := make([]byte, 100)\n\t\t_, _, err = mc.ReadFrom(msg)\n\t\trequire.NoError(t, err)\n\n\t\t// Write from new connection should provide the new address on ReadFrom\n\t\t_, err = cc.WriteTo([]byte(\"test1\"), c.LocalAddr())\n\t\trequire.NoError(t, err)\n\t\tn, addr, err := mc.ReadFrom(msg)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, addr, cc.LocalAddr())\n\t\trequire.Equal(t, \"test1\", string(msg[:n]))\n\n\t\t// Write from original connection should provide the original address\n\t\t_, err = conns[i].WriteTo([]byte(\"test2\"), c.LocalAddr())\n\t\trequire.NoError(t, err)\n\t\tn, addr, err = mc.ReadFrom(msg)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, addr, conns[i].LocalAddr())\n\t\trequire.Equal(t, \"test2\", string(msg[:n]))\n\t}\n}\n\nfunc TestRemoveConnByUfrag(t *testing.T) {\n\tc := newPacketConn(t)\n\tm := NewUDPMux(c)\n\tm.Start()\n\tdefer m.Close()\n\n\t// Map each ufrag to two addresses\n\tufrag := \"a\"\n\tcount := 10\n\tconns := make([]net.PacketConn, count)\n\tfor i := range 10 {\n\t\tconns[i] = newPacketConn(t)\n\t\tsetupMapping(t, ufrag, conns[i], m)\n\t}\n\tmc, err := m.GetConn(ufrag, conns[0].LocalAddr())\n\trequire.NoError(t, err)\n\tfor i := range 10 {\n\t\tmc1, err := m.GetConn(ufrag, conns[i].LocalAddr())\n\t\trequire.NoError(t, err)\n\t\tif mc1 != mc {\n\t\t\tt.Fatalf(\"expected the two muxed connections to be same\")\n\t\t}\n\t}\n\n\t// Now remove the ufrag\n\tm.RemoveConnByUfrag(ufrag)\n\n\t// All connections should now be associated with b\n\tufrag = \"b\"\n\tfor i := range 10 {\n\t\tsetupMapping(t, ufrag, conns[i], m)\n\t}\n\tmc, err = m.GetConn(ufrag, conns[0].LocalAddr())\n\trequire.NoError(t, err)\n\tfor i := range 10 {\n\t\tmc1, err := m.GetConn(ufrag, conns[i].LocalAddr())\n\t\trequire.NoError(t, err)\n\t\tif mc1 != mc {\n\t\t\tt.Fatalf(\"expected the two muxed connections to be same\")\n\t\t}\n\t}\n\n\t// Should be different even if the address is the same\n\tmc1, err := m.GetConn(\"a\", conns[0].LocalAddr())\n\trequire.NoError(t, err)\n\tif mc1 == mc {\n\t\tt.Fatalf(\"expected the two connections to be different\")\n\t}\n}\n\nfunc TestMuxedConnection(t *testing.T) {\n\tc := newPacketConn(t)\n\tm := NewUDPMux(c)\n\tm.Start()\n\tdefer m.Close()\n\n\tmsgCount := 3\n\tconnCount := 3\n\n\tufrags := []string{\"a\", \"b\", \"c\"}\n\taddrUfragMap := make(map[string]string)\n\tufragConnsMap := make(map[string][]net.PacketConn)\n\tfor _, ufrag := range ufrags {\n\t\tfor range connCount {\n\t\t\tcc := newPacketConn(t)\n\t\t\taddrUfragMap[cc.LocalAddr().String()] = ufrag\n\t\t\tufragConnsMap[ufrag] = append(ufragConnsMap[ufrag], cc)\n\t\t}\n\t}\n\n\tdone := make(chan bool, len(ufrags))\n\tfor _, ufrag := range ufrags {\n\t\tgo func(ufrag string) {\n\t\t\tfor _, cc := range ufragConnsMap[ufrag] {\n\t\t\t\tsetupMapping(t, ufrag, cc, m)\n\t\t\t\tfor range msgCount {\n\t\t\t\t\tcc.WriteTo([]byte(ufrag), c.LocalAddr())\n\t\t\t\t}\n\t\t\t}\n\t\t\tdone <- true\n\t\t}(ufrag)\n\t}\n\tfor range ufrags {\n\t\t<-done\n\t}\n\n\tfor _, ufrag := range ufrags {\n\t\tmc, err := m.GetConn(ufrag, c.LocalAddr()) // the address is irrelevant\n\t\trequire.NoError(t, err)\n\t\tmsgs := 0\n\t\tstunRequests := 0\n\t\tmsg := make([]byte, 1500)\n\t\taddrPacketCount := make(map[string]int)\n\t\tfor range connCount {\n\t\t\tfor j := 0; j < msgCount+1; j++ {\n\t\t\t\tn, addr1, err := mc.ReadFrom(msg)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, addrUfragMap[addr1.String()], ufrag)\n\t\t\t\taddrPacketCount[addr1.String()]++\n\t\t\t\tif stun.IsMessage(msg[:n]) {\n\t\t\t\t\tstunRequests++\n\t\t\t\t} else {\n\t\t\t\t\tmsgs++\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfor addr, v := range addrPacketCount {\n\t\t\trequire.Equal(t, v, msgCount+1) // msgCount msgs + 1 STUN binding request\n\t\t\tdelete(addrUfragMap, addr)\n\t\t}\n\t\trequire.Len(t, addrPacketCount, connCount)\n\t}\n\trequire.Empty(t, addrUfragMap)\n}\n\nfunc TestRemovingUfragClosesConn(t *testing.T) {\n\tc := newPacketConn(t)\n\tm := NewUDPMux(c)\n\tm.Start()\n\tdefer m.Close()\n\tremoteAddr := &net.UDPAddr{IP: net.ParseIP(\"127.0.0.1\"), Port: 1234}\n\tconn, err := m.GetConn(\"a\", remoteAddr)\n\trequire.NoError(t, err)\n\tdefer conn.Close()\n\n\tconnClosed := make(chan bool)\n\tgo func() {\n\t\t_, _, err := conn.ReadFrom(make([]byte, 100))\n\t\tassert.ErrorIs(t, err, context.Canceled)\n\t\tclose(connClosed)\n\t}()\n\trequire.NoError(t, err)\n\tm.RemoveConnByUfrag(\"a\")\n\tselect {\n\tcase <-connClosed:\n\tcase <-time.After(1 * time.Second):\n\t\tt.Fatalf(\"expected the connection to be closed\")\n\t}\n}\n"
  },
  {
    "path": "p2p/transport/webrtc/udpmux/muxed_connection.go",
    "content": "package udpmux\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net\"\n\t\"time\"\n\n\tpool \"github.com/libp2p/go-buffer-pool\"\n)\n\ntype packet struct {\n\tbuf  []byte\n\taddr net.Addr\n}\n\nvar _ net.PacketConn = &muxedConnection{}\n\nconst queueLen = 128\n\n// muxedConnection provides a net.PacketConn abstraction\n// over packetQueue and adds the ability to store addresses\n// from which this connection (indexed by ufrag) received\n// data.\ntype muxedConnection struct {\n\tctx    context.Context\n\tcancel context.CancelFunc\n\tqueue  chan packet\n\tmux    *UDPMux\n\tufrag  string\n}\n\nvar _ net.PacketConn = &muxedConnection{}\n\nfunc newMuxedConnection(mux *UDPMux, ufrag string) *muxedConnection {\n\tctx, cancel := context.WithCancel(mux.ctx)\n\treturn &muxedConnection{\n\t\tctx:    ctx,\n\t\tcancel: cancel,\n\t\tqueue:  make(chan packet, queueLen),\n\t\tmux:    mux,\n\t\tufrag:  ufrag,\n\t}\n}\n\nfunc (c *muxedConnection) Push(buf []byte, addr net.Addr) error {\n\tif c.ctx.Err() != nil {\n\t\treturn errors.New(\"closed\")\n\t}\n\tselect {\n\tcase c.queue <- packet{buf: buf, addr: addr}:\n\t\treturn nil\n\tdefault:\n\t\treturn errors.New(\"queue full\")\n\t}\n}\n\nfunc (c *muxedConnection) ReadFrom(buf []byte) (int, net.Addr, error) {\n\tselect {\n\tcase p := <-c.queue:\n\t\tn := copy(buf, p.buf) // This might discard parts of the packet, if p is too short\n\t\tif n < len(p.buf) {\n\t\t\tlog.Debug(\"short read\", \"had\", len(p.buf), \"read\", n)\n\t\t}\n\t\tpool.Put(p.buf)\n\t\treturn n, p.addr, nil\n\tcase <-c.ctx.Done():\n\t\treturn 0, nil, c.ctx.Err()\n\t}\n}\n\nfunc (c *muxedConnection) WriteTo(p []byte, addr net.Addr) (n int, err error) {\n\treturn c.mux.writeTo(p, addr)\n}\n\nfunc (c *muxedConnection) Close() error {\n\tif c.ctx.Err() != nil {\n\t\treturn nil\n\t}\n\t// mux calls close to actually close the connection\n\t//\n\t// Removing the connection from the mux or closing the connection\n\t// must trigger the other.\n\t// Doing this here ensures we don't need to call both RemoveConnByUfrag\n\t// and close on all code paths.\n\tc.mux.RemoveConnByUfrag(c.ufrag)\n\treturn nil\n}\n\n// closes the connection. Must only be called by the mux.\nfunc (c *muxedConnection) close() {\n\tc.cancel()\n\t// drain the packet queue\n\tfor {\n\t\tselect {\n\t\tcase p := <-c.queue:\n\t\t\tpool.Put(p.buf)\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (c *muxedConnection) LocalAddr() net.Addr { return c.mux.socket.LocalAddr() }\n\nfunc (*muxedConnection) SetDeadline(_ time.Time) error {\n\t// no deadline is desired here\n\treturn nil\n}\n\nfunc (*muxedConnection) SetReadDeadline(_ time.Time) error {\n\t// no read deadline is desired here\n\treturn nil\n}\n\nfunc (*muxedConnection) SetWriteDeadline(_ time.Time) error {\n\t// no write deadline is desired here\n\treturn nil\n}\n"
  },
  {
    "path": "p2p/transport/websocket/LICENSE-APACHE",
    "content": "Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with the License. You may obtain a copy of the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.\n"
  },
  {
    "path": "p2p/transport/websocket/LICENSE-MIT",
    "content": "The MIT License (MIT)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "p2p/transport/websocket/addrs.go",
    "content": "package websocket\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"net/url\"\n\t\"strconv\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\n// Addr is an implementation of net.Addr for WebSocket.\ntype Addr struct {\n\t*url.URL\n}\n\nvar _ net.Addr = (*Addr)(nil)\n\n// Network returns the network type for a WebSocket, \"websocket\".\nfunc (addr *Addr) Network() string {\n\treturn \"websocket\"\n}\n\n// NewAddr creates an Addr with `ws` scheme (insecure).\n//\n// Deprecated. Use NewAddrWithScheme.\nfunc NewAddr(host string) *Addr {\n\t// Older versions of the transport only supported insecure connections (i.e.\n\t// WS instead of WSS). Assume that is the case here.\n\treturn NewAddrWithScheme(host, false)\n}\n\n// NewAddrWithScheme creates a new Addr using the given host string. isSecure\n// should be true for WSS connections and false for WS.\nfunc NewAddrWithScheme(host string, isSecure bool) *Addr {\n\tscheme := \"ws\"\n\tif isSecure {\n\t\tscheme = \"wss\"\n\t}\n\treturn &Addr{\n\t\tURL: &url.URL{\n\t\t\tScheme: scheme,\n\t\t\tHost:   host,\n\t\t},\n\t}\n}\n\nfunc ConvertWebsocketMultiaddrToNetAddr(maddr ma.Multiaddr) (net.Addr, error) {\n\turl, err := parseMultiaddr(maddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &Addr{URL: url}, nil\n}\n\nfunc ParseWebsocketNetAddr(a net.Addr) (ma.Multiaddr, error) {\n\twsa, ok := a.(*Addr)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"not a websocket address\")\n\t}\n\n\tvar (\n\t\ttcpma ma.Multiaddr\n\t\terr   error\n\t\tport  int\n\t\thost  = wsa.Hostname()\n\t)\n\n\t// Get the port\n\tif portStr := wsa.Port(); portStr != \"\" {\n\t\tport, err = strconv.Atoi(portStr)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to parse port '%q': %s\", portStr, err)\n\t\t}\n\t} else {\n\t\treturn nil, fmt.Errorf(\"invalid port in url: '%q'\", wsa.URL)\n\t}\n\n\t// NOTE: Ignoring IPv6 zones...\n\t// Detect if host is IP address or DNS\n\tif ip := net.ParseIP(host); ip != nil {\n\t\t// Assume IP address\n\t\ttcpma, err = manet.FromNetAddr(&net.TCPAddr{\n\t\t\tIP:   ip,\n\t\t\tPort: port,\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t} else {\n\t\t// Assume DNS name\n\t\ttcpma, err = ma.NewMultiaddr(fmt.Sprintf(\"/dns/%s/tcp/%d\", host, port))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\twsma, err := ma.NewMultiaddr(\"/\" + wsa.Scheme)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn tcpma.Encapsulate(wsma), nil\n}\n\nfunc parseMultiaddr(maddr ma.Multiaddr) (*url.URL, error) {\n\tparsed, err := parseWebsocketMultiaddr(maddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tscheme := \"ws\"\n\tif parsed.isWSS {\n\t\tscheme = \"wss\"\n\t}\n\n\tnetwork, host, err := manet.DialArgs(parsed.restMultiaddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tswitch network {\n\tcase \"tcp\", \"tcp4\", \"tcp6\":\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported websocket network %s\", network)\n\t}\n\treturn &url.URL{\n\t\tScheme: scheme,\n\t\tHost:   host,\n\t}, nil\n}\n\ntype parsedWebsocketMultiaddr struct {\n\tisWSS bool\n\t// sni is the SNI value for the TLS handshake, and for setting HTTP Host header\n\tsni *ma.Component\n\t// the rest of the multiaddr before the /tls/sni/example.com/ws or /ws or /wss\n\trestMultiaddr ma.Multiaddr\n}\n\nfunc parseWebsocketMultiaddr(a ma.Multiaddr) (parsedWebsocketMultiaddr, error) {\n\tout := parsedWebsocketMultiaddr{}\n\t// First check if we have a WSS component. If so we'll canonicalize it into a /tls/ws\n\twithoutWss := a.Decapsulate(wssComponent.Multiaddr())\n\tif !withoutWss.Equal(a) {\n\t\ta = withoutWss.Encapsulate(tlsWsAddr)\n\t}\n\n\t// Remove the ws component\n\twithoutWs := a.Decapsulate(wsComponent.Multiaddr())\n\tif withoutWs.Equal(a) {\n\t\treturn out, fmt.Errorf(\"not a websocket multiaddr\")\n\t}\n\n\trest := withoutWs\n\t// If this is not a wss then withoutWs is the rest of the multiaddr\n\tout.restMultiaddr = withoutWs\n\tfor {\n\t\tvar head *ma.Component\n\t\trest, head = ma.SplitLast(rest)\n\t\tif head == nil || len(rest) == 0 {\n\t\t\tbreak\n\t\t}\n\n\t\tif head.Protocol().Code == ma.P_SNI {\n\t\t\tout.sni = head\n\t\t} else if head.Protocol().Code == ma.P_TLS {\n\t\t\tout.isWSS = true\n\t\t\tout.restMultiaddr = rest\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn out, nil\n}\n"
  },
  {
    "path": "p2p/transport/websocket/addrs_test.go",
    "content": "package websocket\n\nimport (\n\t\"net/url\"\n\t\"testing\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n)\n\nfunc TestMultiaddrParsing(t *testing.T) {\n\taddr, err := ma.NewMultiaddr(\"/ip4/127.0.0.1/tcp/5555/ws\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\twsaddr, err := parseMultiaddr(addr)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif wsaddr.String() != \"ws://127.0.0.1:5555\" {\n\t\tt.Fatalf(\"expected ws://127.0.0.1:5555, got %s\", wsaddr)\n\t}\n}\n\ntype httpAddr struct {\n\t*url.URL\n}\n\nfunc (addr *httpAddr) Network() string {\n\treturn \"http\"\n}\n\nfunc TestParseWebsocketNetAddr(t *testing.T) {\n\tnotWs := &httpAddr{&url.URL{Host: \"http://127.0.0.1:1234\"}}\n\t_, err := ParseWebsocketNetAddr(notWs)\n\tif err.Error() != \"not a websocket address\" {\n\t\tt.Fatalf(\"expect \\\"not a websocket address\\\", got \\\"%s\\\"\", err)\n\t}\n\n\twsAddr := NewAddrWithScheme(\"127.0.0.1:5555\", false)\n\tparsed, err := ParseWebsocketNetAddr(wsAddr)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif parsed.String() != \"/ip4/127.0.0.1/tcp/5555/ws\" {\n\t\tt.Fatalf(\"expected \\\"/ip4/127.0.0.1/tcp/5555/ws\\\", got \\\"%s\\\"\", parsed.String())\n\t}\n}\n\nfunc TestConvertWebsocketMultiaddrToNetAddr(t *testing.T) {\n\taddr, err := ma.NewMultiaddr(\"/ip4/127.0.0.1/tcp/5555/ws\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\twsaddr, err := ConvertWebsocketMultiaddrToNetAddr(addr)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif wsaddr.String() != \"ws://127.0.0.1:5555\" {\n\t\tt.Fatalf(\"expected ws://127.0.0.1:5555, got %s\", wsaddr)\n\t}\n\tif wsaddr.Network() != \"websocket\" {\n\t\tt.Fatalf(\"expected network: \\\"websocket\\\", got \\\"%s\\\"\", wsaddr.Network())\n\t}\n}\n"
  },
  {
    "path": "p2p/transport/websocket/conn.go",
    "content": "package websocket\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"net\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n\n\tws \"github.com/gorilla/websocket\"\n)\n\n// GracefulCloseTimeout is the time to wait trying to gracefully close a\n// connection before simply cutting it.\nvar GracefulCloseTimeout = 100 * time.Millisecond\n\n// Conn implements net.Conn interface for gorilla/websocket.\ntype Conn struct {\n\t*ws.Conn\n\tScope              network.ConnManagementScope\n\tsecure             bool\n\tDefaultMessageType int\n\treader             io.Reader\n\tcloseOnceVal       func() error\n\tladdr              ma.Multiaddr\n\traddr              ma.Multiaddr\n\n\treadLock, writeLock sync.Mutex\n}\n\nvar _ net.Conn = (*Conn)(nil)\nvar _ manet.Conn = (*Conn)(nil)\n\n// newConn creates a Conn given a regular gorilla/websocket Conn.\nfunc newConn(raw *ws.Conn, secure bool, scope network.ConnManagementScope) *Conn {\n\tlna := NewAddrWithScheme(raw.LocalAddr().String(), secure)\n\tladdr, err := manet.FromNetAddr(lna)\n\tif err != nil {\n\t\tlog.Error(\"BUG: invalid localaddr on websocket conn\", \"local_addr\", raw.LocalAddr())\n\t\treturn nil\n\t}\n\n\trna := NewAddrWithScheme(raw.RemoteAddr().String(), secure)\n\traddr, err := manet.FromNetAddr(rna)\n\tif err != nil {\n\t\tlog.Error(\"BUG: invalid remoteaddr on websocket conn\", \"remote_addr\", raw.RemoteAddr())\n\t\treturn nil\n\t}\n\n\tc := &Conn{\n\t\tConn:               raw,\n\t\tScope:              scope,\n\t\tsecure:             secure,\n\t\tDefaultMessageType: ws.BinaryMessage,\n\t\tladdr:              laddr,\n\t\traddr:              raddr,\n\t}\n\tc.closeOnceVal = sync.OnceValue(c.closeOnceFn)\n\treturn c\n}\n\n// LocalMultiaddr implements manet.Conn.\nfunc (c *Conn) LocalMultiaddr() ma.Multiaddr {\n\treturn c.laddr\n}\n\n// RemoteMultiaddr implements manet.Conn.\nfunc (c *Conn) RemoteMultiaddr() ma.Multiaddr {\n\treturn c.raddr\n}\n\nfunc (c *Conn) Read(b []byte) (int, error) {\n\tc.readLock.Lock()\n\tdefer c.readLock.Unlock()\n\n\tif c.reader == nil {\n\t\tif err := c.prepNextReader(); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t}\n\n\tfor {\n\t\tn, err := c.reader.Read(b)\n\t\tswitch err {\n\t\tcase io.EOF:\n\t\t\tc.reader = nil\n\n\t\t\tif n > 0 {\n\t\t\t\treturn n, nil\n\t\t\t}\n\n\t\t\tif err := c.prepNextReader(); err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\n\t\t\t// explicitly looping\n\t\tdefault:\n\t\t\treturn n, err\n\t\t}\n\t}\n}\n\nfunc (c *Conn) prepNextReader() error {\n\tt, r, err := c.Conn.NextReader()\n\tif err != nil {\n\t\tif wserr, ok := err.(*ws.CloseError); ok {\n\t\t\tif wserr.Code == 1000 || wserr.Code == 1005 {\n\t\t\t\treturn io.EOF\n\t\t\t}\n\t\t}\n\t\treturn err\n\t}\n\n\tif t == ws.CloseMessage {\n\t\treturn io.EOF\n\t}\n\n\tc.reader = r\n\treturn nil\n}\n\nfunc (c *Conn) Write(b []byte) (n int, err error) {\n\tc.writeLock.Lock()\n\tdefer c.writeLock.Unlock()\n\n\tif err := c.Conn.WriteMessage(c.DefaultMessageType, b); err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn len(b), nil\n}\n\n// Close closes the connection.\n// subsequent and concurrent calls will return the same error value.\n// This method is thread-safe.\nfunc (c *Conn) Close() error {\n\treturn c.closeOnceVal()\n}\n\nfunc (c *Conn) closeOnceFn() error {\n\terr1 := c.Conn.WriteControl(\n\t\tws.CloseMessage,\n\t\tws.FormatCloseMessage(ws.CloseNormalClosure, \"closed\"),\n\t\ttime.Now().Add(GracefulCloseTimeout),\n\t)\n\terr2 := c.Conn.Close()\n\treturn errors.Join(err1, err2)\n}\n\nfunc (c *Conn) LocalAddr() net.Addr {\n\treturn NewAddrWithScheme(c.Conn.LocalAddr().String(), c.secure)\n}\n\nfunc (c *Conn) RemoteAddr() net.Addr {\n\treturn NewAddrWithScheme(c.Conn.RemoteAddr().String(), c.secure)\n}\n\nfunc (c *Conn) SetDeadline(t time.Time) error {\n\tif err := c.SetReadDeadline(t); err != nil {\n\t\treturn err\n\t}\n\n\treturn c.SetWriteDeadline(t)\n}\n\nfunc (c *Conn) SetReadDeadline(t time.Time) error {\n\t// Don't lock when setting the read deadline. That would prevent us from\n\t// interrupting an in-progress read.\n\treturn c.Conn.SetReadDeadline(t)\n}\n\nfunc (c *Conn) SetWriteDeadline(t time.Time) error {\n\t// Unlike the read deadline, we need to lock when setting the write\n\t// deadline.\n\n\tc.writeLock.Lock()\n\tdefer c.writeLock.Unlock()\n\n\treturn c.Conn.SetWriteDeadline(t)\n}\n"
  },
  {
    "path": "p2p/transport/websocket/listener.go",
    "content": "package websocket\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"sync\"\n\t\"time\"\n\n\tws \"github.com/gorilla/websocket\"\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/transport\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/tcpreuse\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\nvar log = logging.Logger(\"websocket-transport\")\n\ntype listener struct {\n\tnetListener *httpNetListener\n\tserver      http.Server\n\twsUpgrader  ws.Upgrader\n\t// The Go standard library sets the http.Server.TLSConfig no matter if this is a WS or WSS,\n\t// so we can't rely on checking if server.TLSConfig is set.\n\tisWss bool\n\n\tladdr ma.Multiaddr\n\n\tincoming chan *Conn\n\n\tcloseOnce sync.Once\n\tcloseErr  error\n\tclosed    chan struct{}\n\twsurl     *url.URL\n}\n\nvar _ transport.GatedMaListener = &listener{}\n\nfunc (pwma *parsedWebsocketMultiaddr) toMultiaddr() ma.Multiaddr {\n\tif !pwma.isWSS {\n\t\treturn pwma.restMultiaddr.AppendComponent(wsComponent)\n\t}\n\n\tif pwma.sni == nil {\n\t\treturn pwma.restMultiaddr.AppendComponent(tlsComponent, wsComponent)\n\t}\n\n\treturn pwma.restMultiaddr.AppendComponent(tlsComponent, pwma.sni, wsComponent)\n}\n\n// newListener creates a new listener from a raw net.Listener.\n// tlsConf may be nil (for unencrypted websockets).\nfunc newListener(a ma.Multiaddr, tlsConf *tls.Config, sharedTcp *tcpreuse.ConnMgr, upgrader transport.Upgrader, handshakeTimeout time.Duration) (*listener, error) {\n\tparsed, err := parseWebsocketMultiaddr(a)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif parsed.isWSS && tlsConf == nil {\n\t\treturn nil, fmt.Errorf(\"cannot listen on wss address %s without a tls.Config\", a)\n\t}\n\n\tvar gmal transport.GatedMaListener\n\tif sharedTcp == nil {\n\t\tmal, err := manet.Listen(parsed.restMultiaddr)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgmal = upgrader.GateMaListener(mal)\n\t} else {\n\t\tvar connType tcpreuse.DemultiplexedConnType\n\t\tif parsed.isWSS {\n\t\t\tconnType = tcpreuse.DemultiplexedConnType_TLS\n\t\t} else {\n\t\t\tconnType = tcpreuse.DemultiplexedConnType_HTTP\n\t\t}\n\t\tgmal, err = sharedTcp.DemultiplexedListen(parsed.restMultiaddr, connType)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\t// laddr has the correct port in case we listened on port 0\n\tladdr := gmal.Multiaddr()\n\n\t// Don't resolve dns addresses.\n\t// We want to be able to announce domain names, so the peer can validate the TLS certificate.\n\tfirst, _ := ma.SplitFirst(a)\n\tif c := first.Protocol().Code; c == ma.P_DNS || c == ma.P_DNS4 || c == ma.P_DNS6 || c == ma.P_DNSADDR {\n\t\t_, last := ma.SplitFirst(laddr)\n\t\tladdr = first.Encapsulate(last)\n\t}\n\tparsed.restMultiaddr = laddr\n\n\tlistenAddr := parsed.toMultiaddr()\n\twsurl, err := parseMultiaddr(listenAddr)\n\tif err != nil {\n\t\tgmal.Close()\n\t\treturn nil, fmt.Errorf(\"failed to parse multiaddr to URL: %v: %w\", listenAddr, err)\n\t}\n\tln := &listener{\n\t\tnetListener: &httpNetListener{\n\t\t\tGatedMaListener:  gmal,\n\t\t\thandshakeTimeout: handshakeTimeout,\n\t\t},\n\t\tladdr:    parsed.toMultiaddr(),\n\t\tincoming: make(chan *Conn),\n\t\tclosed:   make(chan struct{}),\n\t\tisWss:    parsed.isWSS,\n\t\twsurl:    wsurl,\n\t\twsUpgrader: ws.Upgrader{\n\t\t\t// Allow requests from *all* origins.\n\t\t\tCheckOrigin: func(_ *http.Request) bool {\n\t\t\t\treturn true\n\t\t\t},\n\t\t\tHandshakeTimeout: handshakeTimeout,\n\t\t},\n\t}\n\tln.server = http.Server{\n\t\tHandler: ln,\n\t\t// Use LevelDebug for http.Server errors (TLS handshake failures, connection issues).\n\t\t// These are operational noise from misbehaving/buggy remote clients, not server errors.\n\t\tErrorLog:    slog.NewLogLogger(log.Handler(), slog.LevelDebug),\n\t\tConnContext: ln.ConnContext,\n\t\tTLSConfig:   tlsConf,\n\t}\n\treturn ln, nil\n}\n\nfunc (l *listener) serve() {\n\tdefer close(l.closed)\n\tif !l.isWss {\n\t\tl.server.Serve(l.netListener)\n\t} else {\n\t\tl.server.ServeTLS(l.netListener, \"\", \"\")\n\t}\n}\n\ntype connKey struct{}\n\nfunc (l *listener) ConnContext(ctx context.Context, c net.Conn) context.Context {\n\t// prefer `*tls.Conn` over `(interface{NetConn() net.Conn})` in case `manet.Conn` is extended\n\t// to support a `NetConn() net.Conn` method.\n\tif tc, ok := c.(*tls.Conn); ok {\n\t\tc = tc.NetConn()\n\t}\n\tif nc, ok := c.(*negotiatingConn); ok {\n\t\treturn context.WithValue(ctx, connKey{}, nc)\n\t}\n\tlog.Error(\"BUG: expected net.Conn of type *websocket.negotiatingConn\", \"got_type\", fmt.Sprintf(\"%T\", c))\n\t// might as well close the connection as there's no way to proceed now.\n\tc.Close()\n\treturn ctx\n}\n\nfunc (l *listener) extractConnFromContext(ctx context.Context) (*negotiatingConn, error) {\n\tc := ctx.Value(connKey{})\n\tif c == nil {\n\t\treturn nil, fmt.Errorf(\"expected *websocket.negotiatingConn in context: got nil\")\n\t}\n\tnc, ok := c.(*negotiatingConn)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"expected *websocket.negotiatingConn in context: got %T\", c)\n\t}\n\treturn nc, nil\n}\n\nfunc (l *listener) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\tc, err := l.wsUpgrader.Upgrade(w, r, nil)\n\tif err != nil {\n\t\t// The upgrader writes a response for us.\n\t\treturn\n\t}\n\tnc, err := l.extractConnFromContext(r.Context())\n\tif err != nil {\n\t\tc.Close()\n\t\tw.WriteHeader(500)\n\t\tlog.Error(\"BUG: failed to extract conn from context\", \"remote_addr\", r.RemoteAddr, \"err\", err)\n\t\treturn\n\t}\n\n\tcs, err := nc.Unwrap()\n\tif err != nil {\n\t\tc.Close()\n\t\tw.WriteHeader(500)\n\t\tlog.Debug(\"connection timed out\", \"remote_addr\", r.RemoteAddr)\n\t\treturn\n\t}\n\n\tconn := newConn(c, l.isWss, cs.Scope)\n\tif conn == nil {\n\t\tc.Close()\n\t\tw.WriteHeader(500)\n\t\treturn\n\t}\n\n\tselect {\n\tcase l.incoming <- conn:\n\tcase <-l.closed:\n\t\tconn.Close()\n\t}\n\t// The connection has been hijacked, it's safe to return.\n}\n\nfunc (l *listener) Accept() (manet.Conn, network.ConnManagementScope, error) {\n\tselect {\n\tcase c, ok := <-l.incoming:\n\t\tif !ok {\n\t\t\treturn nil, nil, transport.ErrListenerClosed\n\t\t}\n\t\treturn c, c.Scope, nil\n\tcase <-l.closed:\n\t\treturn nil, nil, transport.ErrListenerClosed\n\t}\n}\n\nfunc (l *listener) Addr() net.Addr {\n\treturn &Addr{URL: l.wsurl}\n}\n\nfunc (l *listener) Close() error {\n\tl.closeOnce.Do(func() {\n\t\terr1 := l.netListener.Close()\n\t\terr2 := l.server.Close()\n\t\t<-l.closed\n\t\tl.closeErr = errors.Join(err1, err2)\n\t})\n\treturn l.closeErr\n}\n\nfunc (l *listener) Multiaddr() ma.Multiaddr {\n\treturn l.laddr\n}\n\n// httpNetListener is a net.Listener that adapts a transport.GatedMaListener to a net.Listener.\n// It wraps the manet.Conn, and the Scope from the underlying gated listener in a connWithScope.\ntype httpNetListener struct {\n\ttransport.GatedMaListener\n\thandshakeTimeout time.Duration\n}\n\nvar _ net.Listener = &httpNetListener{}\n\nfunc (l *httpNetListener) Accept() (net.Conn, error) {\n\tconn, scope, err := l.GatedMaListener.Accept()\n\tif err != nil {\n\t\tif scope != nil {\n\t\t\tlog.Error(\"BUG: scope non-nil when err is non nil\", \"error\", err)\n\t\t\tscope.Done()\n\t\t}\n\t\treturn nil, err\n\t}\n\tconnWithScope := connWithScope{\n\t\tConn:  conn,\n\t\tScope: scope,\n\t}\n\tctx, cancel := context.WithTimeout(context.Background(), l.handshakeTimeout)\n\treturn &negotiatingConn{\n\t\tconnWithScope: connWithScope,\n\t\tctx:           ctx,\n\t\tcancelCtx:     cancel,\n\t\tstopClose: context.AfterFunc(ctx, func() {\n\t\t\tconnWithScope.Close()\n\t\t\tlog.Debug(\"handshake timeout for conn\", \"remote_addr\", conn.RemoteAddr())\n\t\t}),\n\t}, nil\n}\n\ntype connWithScope struct {\n\tnet.Conn\n\tScope network.ConnManagementScope\n}\n\nfunc (c connWithScope) Close() error {\n\tc.Scope.Done()\n\treturn c.Conn.Close()\n}\n\ntype negotiatingConn struct {\n\tconnWithScope\n\tctx       context.Context\n\tcancelCtx context.CancelFunc\n\tstopClose func() bool\n}\n\n// Close closes the negotiating conn and the underlying connWithScope\n// This will be called in case the tls handshake or websocket upgrade fails.\nfunc (c *negotiatingConn) Close() error {\n\tdefer c.cancelCtx()\n\tif c.stopClose != nil {\n\t\tc.stopClose()\n\t}\n\treturn c.connWithScope.Close()\n}\n\nfunc (c *negotiatingConn) Unwrap() (connWithScope, error) {\n\tdefer c.cancelCtx()\n\tif c.stopClose != nil {\n\t\tif !c.stopClose() {\n\t\t\treturn connWithScope{}, errors.New(\"timed out\")\n\t\t}\n\t\tc.stopClose = nil\n\t}\n\treturn c.connWithScope, nil\n}\n"
  },
  {
    "path": "p2p/transport/websocket/websocket.go",
    "content": "// Package websocket implements a websocket based transport for go-libp2p.\npackage websocket\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/transport\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/tcpreuse\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmafmt \"github.com/multiformats/go-multiaddr-fmt\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n\n\tws \"github.com/gorilla/websocket\"\n)\n\n// WsFmt is multiaddr formatter for WsProtocol\nvar WsFmt = mafmt.And(mafmt.TCP, mafmt.Base(ma.P_WS))\n\nvar dialMatcher = mafmt.And(\n\tmafmt.Or(mafmt.IP, mafmt.DNS),\n\tmafmt.Base(ma.P_TCP),\n\tmafmt.Or(\n\t\tmafmt.Base(ma.P_WS),\n\t\tmafmt.And(\n\t\t\tmafmt.Or(\n\t\t\t\tmafmt.And(\n\t\t\t\t\tmafmt.Base(ma.P_TLS),\n\t\t\t\t\tmafmt.Base(ma.P_SNI)),\n\t\t\t\tmafmt.Base(ma.P_TLS),\n\t\t\t),\n\t\t\tmafmt.Base(ma.P_WS)),\n\t\tmafmt.Base(ma.P_WSS)))\n\nvar (\n\twssComponent, _ = ma.NewComponent(\"wss\", \"\")\n\ttlsComponent, _ = ma.NewComponent(\"tls\", \"\")\n\twsComponent, _  = ma.NewComponent(\"ws\", \"\")\n\ttlsWsAddr       = ma.Multiaddr{*tlsComponent, *wsComponent}\n)\n\nfunc init() {\n\tmanet.RegisterFromNetAddr(ParseWebsocketNetAddr, \"websocket\")\n\tmanet.RegisterToNetAddr(ConvertWebsocketMultiaddrToNetAddr, \"ws\")\n\tmanet.RegisterToNetAddr(ConvertWebsocketMultiaddrToNetAddr, \"wss\")\n}\n\ntype Option func(*WebsocketTransport) error\n\n// WithTLSClientConfig sets a TLS client configuration on the WebSocket Dialer. Only\n// relevant for non-browser usages.\n//\n// Some useful use cases include setting InsecureSkipVerify to `true`, or\n// setting user-defined trusted CA certificates.\nfunc WithTLSClientConfig(c *tls.Config) Option {\n\treturn func(t *WebsocketTransport) error {\n\t\tt.tlsClientConf = c\n\t\treturn nil\n\t}\n}\n\n// WithTLSConfig sets a TLS configuration for the WebSocket listener.\nfunc WithTLSConfig(conf *tls.Config) Option {\n\treturn func(t *WebsocketTransport) error {\n\t\tt.tlsConf = conf\n\t\treturn nil\n\t}\n}\n\nvar defaultHandshakeTimeout = 15 * time.Second\n\n// WithHandshakeTimeout sets a timeout for the websocket upgrade.\nfunc WithHandshakeTimeout(timeout time.Duration) Option {\n\treturn func(t *WebsocketTransport) error {\n\t\tt.handshakeTimeout = timeout\n\t\treturn nil\n\t}\n}\n\n// WebsocketTransport is the actual go-libp2p transport\ntype WebsocketTransport struct {\n\tupgrader         transport.Upgrader\n\trcmgr            network.ResourceManager\n\ttlsClientConf    *tls.Config\n\ttlsConf          *tls.Config\n\tsharedTcp        *tcpreuse.ConnMgr\n\thandshakeTimeout time.Duration\n}\n\nvar _ transport.Transport = (*WebsocketTransport)(nil)\n\nfunc New(u transport.Upgrader, rcmgr network.ResourceManager, sharedTCP *tcpreuse.ConnMgr, opts ...Option) (*WebsocketTransport, error) {\n\tif rcmgr == nil {\n\t\trcmgr = &network.NullResourceManager{}\n\t}\n\tt := &WebsocketTransport{\n\t\tupgrader:         u,\n\t\trcmgr:            rcmgr,\n\t\ttlsClientConf:    &tls.Config{},\n\t\tsharedTcp:        sharedTCP,\n\t\thandshakeTimeout: defaultHandshakeTimeout,\n\t}\n\tfor _, opt := range opts {\n\t\tif err := opt(t); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn t, nil\n}\n\nfunc (t *WebsocketTransport) CanDial(a ma.Multiaddr) bool {\n\treturn dialMatcher.Matches(a)\n}\n\nfunc (t *WebsocketTransport) Protocols() []int {\n\treturn []int{ma.P_WS, ma.P_WSS}\n}\n\nfunc (t *WebsocketTransport) Proxy() bool {\n\treturn false\n}\n\nfunc (t *WebsocketTransport) Resolve(_ context.Context, maddr ma.Multiaddr) ([]ma.Multiaddr, error) {\n\tparsed, err := parseWebsocketMultiaddr(maddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif !parsed.isWSS {\n\t\t// No /tls/ws component, this isn't a secure websocket multiaddr. We can just return it here\n\t\treturn []ma.Multiaddr{maddr}, nil\n\t}\n\n\tif parsed.sni == nil {\n\t\tvar err error\n\t\t// We don't have an sni component, we'll use dns\n\tloop:\n\t\tfor _, c := range parsed.restMultiaddr {\n\t\t\tswitch c.Protocol().Code {\n\t\t\tcase ma.P_DNS, ma.P_DNS4, ma.P_DNS6:\n\t\t\t\t// err shouldn't happen since this means we couldn't parse a dns hostname for an sni value.\n\t\t\t\tparsed.sni, err = ma.NewComponent(\"sni\", c.Value())\n\t\t\t\tbreak loop\n\t\t\t}\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif parsed.sni == nil {\n\t\t// we didn't find anything to set the sni with. So we just return the given multiaddr\n\t\treturn []ma.Multiaddr{maddr}, nil\n\t}\n\n\treturn []ma.Multiaddr{parsed.toMultiaddr()}, nil\n}\n\n// Dial will dial the given multiaddr and expect the given peer. If an\n// HTTPS_PROXY env is set, it will use that for the dial out.\nfunc (t *WebsocketTransport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (transport.CapableConn, error) {\n\tconnScope, err := t.rcmgr.OpenConnection(network.DirOutbound, true, raddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tc, err := t.dialWithScope(ctx, raddr, p, connScope)\n\tif err != nil {\n\t\tconnScope.Done()\n\t\treturn nil, err\n\t}\n\treturn c, nil\n}\n\nfunc (t *WebsocketTransport) dialWithScope(ctx context.Context, raddr ma.Multiaddr, p peer.ID, connScope network.ConnManagementScope) (transport.CapableConn, error) {\n\tmacon, err := t.maDial(ctx, raddr, connScope)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tconn, err := t.upgrader.Upgrade(ctx, t, macon, network.DirOutbound, p, connScope)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &capableConn{CapableConn: conn}, nil\n}\n\nfunc (t *WebsocketTransport) maDial(ctx context.Context, raddr ma.Multiaddr, scope network.ConnManagementScope) (manet.Conn, error) {\n\twsurl, err := parseMultiaddr(raddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tisWss := wsurl.Scheme == \"wss\"\n\tdialer := ws.Dialer{\n\t\tHandshakeTimeout: t.handshakeTimeout,\n\t\t// Inherit the default proxy behavior\n\t\tProxy: ws.DefaultDialer.Proxy,\n\t}\n\tif isWss {\n\t\tsni := \"\"\n\t\tsni, err = raddr.ValueForProtocol(ma.P_SNI)\n\t\tif err != nil {\n\t\t\tsni = \"\"\n\t\t}\n\n\t\tif sni != \"\" {\n\t\t\tcopytlsClientConf := t.tlsClientConf.Clone()\n\t\t\tcopytlsClientConf.ServerName = sni\n\t\t\tdialer.TLSClientConfig = copytlsClientConf\n\t\t\tipPortAddr := wsurl.Host\n\t\t\t// We set the `.Host` to the sni field so that the host header gets properly set.\n\t\t\twsurl.Host = sni + \":\" + wsurl.Port()\n\t\t\t// Setting the NetDial because we already have the resolved IP address, so we can avoid another resolution.\n\t\t\tdialer.NetDial = func(network, address string) (net.Conn, error) {\n\t\t\t\tvar tcpAddr *net.TCPAddr\n\t\t\t\tvar err error\n\t\t\t\tif address == wsurl.Host {\n\t\t\t\t\ttcpAddr, err = net.ResolveTCPAddr(network, ipPortAddr) // Use our already resolved IP address\n\t\t\t\t} else {\n\t\t\t\t\ttcpAddr, err = net.ResolveTCPAddr(network, address)\n\t\t\t\t}\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\treturn net.DialTCP(\"tcp\", nil, tcpAddr)\n\t\t\t}\n\t\t} else {\n\t\t\tdialer.TLSClientConfig = t.tlsClientConf\n\t\t}\n\t}\n\n\twscon, _, err := dialer.DialContext(ctx, wsurl.String(), nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tmnc, err := manet.WrapNetConn(newConn(wscon, isWss, scope))\n\tif err != nil {\n\t\twscon.Close()\n\t\treturn nil, err\n\t}\n\treturn mnc, nil\n}\n\nfunc (t *WebsocketTransport) gatedMaListen(a ma.Multiaddr) (transport.GatedMaListener, error) {\n\tvar tlsConf *tls.Config\n\tif t.tlsConf != nil {\n\t\ttlsConf = t.tlsConf.Clone()\n\t}\n\tl, err := newListener(a, tlsConf, t.sharedTcp, t.upgrader, t.handshakeTimeout)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tgo l.serve()\n\treturn l, nil\n}\n\nfunc (t *WebsocketTransport) Listen(a ma.Multiaddr) (transport.Listener, error) {\n\tgmal, err := t.gatedMaListen(a)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &transportListener{Listener: t.upgrader.UpgradeGatedMaListener(t, gmal)}, nil\n}\n\n// transportListener wraps a transport.Listener to provide connections with a `ConnState() network.ConnectionState` method.\ntype transportListener struct {\n\ttransport.Listener\n}\n\ntype capableConn struct {\n\ttransport.CapableConn\n}\n\nfunc (c *capableConn) ConnState() network.ConnectionState {\n\tcs := c.CapableConn.ConnState()\n\tcs.Transport = \"websocket\"\n\treturn cs\n}\n\nfunc (l *transportListener) Accept() (transport.CapableConn, error) {\n\tconn, err := l.Listener.Accept()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &capableConn{CapableConn: conn}, nil\n}\n"
  },
  {
    "path": "p2p/transport/websocket/websocket_test.go",
    "content": "package websocket\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/ecdsa\"\n\t\"crypto/elliptic\"\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"crypto/x509/pkix\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"math/big\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\tgws \"github.com/gorilla/websocket\"\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/sec\"\n\t\"github.com/libp2p/go-libp2p/core/test\"\n\t\"github.com/libp2p/go-libp2p/core/transport\"\n\t\"github.com/libp2p/go-libp2p/p2p/muxer/yamux\"\n\ttptu \"github.com/libp2p/go-libp2p/p2p/net/upgrader\"\n\t\"github.com/libp2p/go-libp2p/p2p/security/insecure\"\n\t\"github.com/libp2p/go-libp2p/p2p/security/noise\"\n\tttransport \"github.com/libp2p/go-libp2p/p2p/transport/testsuite\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc newUpgrader(t *testing.T) (peer.ID, transport.Upgrader) {\n\tt.Helper()\n\tid, m := newInsecureMuxer(t)\n\tu, err := tptu.New(m, []tptu.StreamMuxer{{ID: \"/yamux\", Muxer: yamux.DefaultTransport}}, nil, nil, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\treturn id, u\n}\n\nfunc newSecureUpgrader(t *testing.T) (peer.ID, transport.Upgrader) {\n\tt.Helper()\n\tid, m := newSecureMuxer(t)\n\tu, err := tptu.New(m, []tptu.StreamMuxer{{ID: \"/yamux\", Muxer: yamux.DefaultTransport}}, nil, nil, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\treturn id, u\n}\n\nfunc newInsecureMuxer(t *testing.T) (peer.ID, []sec.SecureTransport) {\n\tt.Helper()\n\tpriv, _, err := test.RandTestKeyPair(crypto.Ed25519, 256)\n\trequire.NoError(t, err)\n\tid, err := peer.IDFromPrivateKey(priv)\n\trequire.NoError(t, err)\n\treturn id, []sec.SecureTransport{insecure.NewWithIdentity(insecure.ID, id, priv)}\n}\n\nfunc newSecureMuxer(t *testing.T) (peer.ID, []sec.SecureTransport) {\n\tt.Helper()\n\tpriv, _, err := test.RandTestKeyPair(crypto.Ed25519, 256)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tid, err := peer.IDFromPrivateKey(priv)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tnoiseTpt, err := noise.New(noise.ID, priv, nil)\n\trequire.NoError(t, err)\n\treturn id, []sec.SecureTransport{noiseTpt}\n}\n\nfunc lastComponent(t *testing.T, a ma.Multiaddr) *ma.Component {\n\tt.Helper()\n\t_, wscomponent := ma.SplitLast(a)\n\trequire.NotNil(t, wscomponent)\n\tif wscomponent.Equal(wsComponent) {\n\t\treturn wsComponent\n\t}\n\tif wscomponent.Equal(wssComponent) {\n\t\treturn wssComponent\n\t}\n\tt.Fatal(\"expected a ws or wss component\")\n\treturn nil\n}\n\nfunc generateTLSConfig(t *testing.T) *tls.Config {\n\tt.Helper()\n\tpriv, err := rsa.GenerateKey(rand.Reader, 2048)\n\trequire.NoError(t, err)\n\ttmpl := &x509.Certificate{\n\t\tSerialNumber:          big.NewInt(1),\n\t\tSubject:               pkix.Name{},\n\t\tSignatureAlgorithm:    x509.SHA256WithRSA,\n\t\tNotBefore:             time.Now(),\n\t\tNotAfter:              time.Now().Add(time.Hour), // valid for an hour\n\t\tBasicConstraintsValid: true,\n\t}\n\tcertDER, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, priv.Public(), priv)\n\trequire.NoError(t, err)\n\treturn &tls.Config{\n\t\tCertificates: []tls.Certificate{{\n\t\t\tPrivateKey:  priv,\n\t\t\tCertificate: [][]byte{certDER},\n\t\t}},\n\t}\n}\n\nfunc TestCanDial(t *testing.T) {\n\td := &WebsocketTransport{}\n\tif !d.CanDial(ma.StringCast(\"/ip4/127.0.0.1/tcp/5555/ws\")) {\n\t\tt.Fatal(\"expected to match websocket maddr, but did not\")\n\t}\n\tif !d.CanDial(ma.StringCast(\"/ip4/127.0.0.1/tcp/5555/wss\")) {\n\t\tt.Fatal(\"expected to match secure websocket maddr, but did not\")\n\t}\n\tif d.CanDial(ma.StringCast(\"/ip4/127.0.0.1/tcp/5555\")) {\n\t\tt.Fatal(\"expected to not match tcp maddr, but did\")\n\t}\n\tif !d.CanDial(ma.StringCast(\"/ip4/127.0.0.1/tcp/5555/tls/ws\")) {\n\t\tt.Fatal(\"expected to match secure websocket maddr, but did not\")\n\t}\n\tif !d.CanDial(ma.StringCast(\"/ip4/127.0.0.1/tcp/5555/tls/sni/example.com/ws\")) {\n\t\tt.Fatal(\"expected to match secure websocket maddr with sni, but did not\")\n\t}\n\tif !d.CanDial(ma.StringCast(\"/dns4/example.com/tcp/5555/tls/sni/example.com/ws\")) {\n\t\tt.Fatal(\"expected to match secure websocket maddr with sni, but did not\")\n\t}\n\tif !d.CanDial(ma.StringCast(\"/dnsaddr/example.com/tcp/5555/tls/sni/example.com/ws\")) {\n\t\tt.Fatal(\"expected to match secure websocket maddr with sni, but did not\")\n\t}\n}\n\n// testWSSServer returns a client hello info\nfunc testWSSServer(t *testing.T, listenAddr ma.Multiaddr) (ma.Multiaddr, peer.ID, chan error) {\n\terrChan := make(chan error, 1)\n\n\tip := net.ParseIP(\"::\")\n\ttlsConf := getTLSConf(t, ip, time.Now(), time.Now().Add(time.Hour))\n\ttlsConf.GetConfigForClient = func(chi *tls.ClientHelloInfo) (*tls.Config, error) {\n\t\tif chi.ServerName != \"example.com\" {\n\t\t\terrChan <- fmt.Errorf(\"didn't get the expected sni\")\n\t\t}\n\t\treturn tlsConf, nil\n\t}\n\n\tid, u := newSecureUpgrader(t)\n\ttpt, err := New(u, &network.NullResourceManager{}, nil, WithTLSConfig(tlsConf))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tl, err := tpt.Listen(listenAddr)\n\trequire.NoError(t, err)\n\tt.Cleanup(func() {\n\t\tl.Close()\n\t})\n\tgo func() {\n\t\tconn, err := l.Accept()\n\t\tif err != nil {\n\t\t\terrChan <- fmt.Errorf(\"error in accepting conn: %w\", err)\n\t\t\treturn\n\t\t}\n\t\tdefer conn.Close()\n\n\t\tstrm, err := conn.AcceptStream()\n\t\tif err != nil {\n\t\t\terrChan <- fmt.Errorf(\"error in accepting stream: %w\", err)\n\t\t\treturn\n\t\t}\n\t\tdefer strm.Close()\n\t\tclose(errChan)\n\t}()\n\n\treturn l.Multiaddr(), id, errChan\n}\n\nfunc getTLSConf(t *testing.T, ip net.IP, start, end time.Time) *tls.Config {\n\tt.Helper()\n\tcertTempl := &x509.Certificate{\n\t\tSerialNumber:          big.NewInt(1234),\n\t\tSubject:               pkix.Name{Organization: []string{\"websocket\"}},\n\t\tNotBefore:             start,\n\t\tNotAfter:              end,\n\t\tIsCA:                  true,\n\t\tExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},\n\t\tKeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,\n\t\tBasicConstraintsValid: true,\n\t\tIPAddresses:           []net.IP{ip},\n\t}\n\tpriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)\n\trequire.NoError(t, err)\n\tcaBytes, err := x509.CreateCertificate(rand.Reader, certTempl, certTempl, &priv.PublicKey, priv)\n\trequire.NoError(t, err)\n\tcert, err := x509.ParseCertificate(caBytes)\n\trequire.NoError(t, err)\n\treturn &tls.Config{\n\t\tCertificates: []tls.Certificate{{\n\t\t\tCertificate: [][]byte{cert.Raw},\n\t\t\tPrivateKey:  priv,\n\t\t\tLeaf:        cert,\n\t\t}},\n\t}\n}\n\nfunc TestHostHeaderWss(t *testing.T) {\n\tserver := &http.Server{}\n\tl, err := net.Listen(\"tcp\", \":0\")\n\trequire.NoError(t, err)\n\tdefer server.Close()\n\n\terrChan := make(chan error, 1)\n\tgo func() {\n\t\tserver.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tdefer close(errChan)\n\t\t\tif !strings.Contains(r.Host, \"example.com\") {\n\t\t\t\terrChan <- errors.New(\"Didn't see host header\")\n\t\t\t}\n\t\t\tw.WriteHeader(http.StatusNotFound)\n\t\t})\n\t\tserver.TLSConfig = getTLSConf(t, net.ParseIP(\"127.0.0.1\"), time.Now(), time.Now().Add(time.Hour))\n\t\tserver.ServeTLS(l, \"\", \"\")\n\t}()\n\n\t_, port, err := net.SplitHostPort(l.Addr().String())\n\trequire.NoError(t, err)\n\tserverMA := ma.StringCast(\"/ip4/127.0.0.1/tcp/\" + port + \"/tls/sni/example.com/ws\")\n\n\ttlsConfig := &tls.Config{InsecureSkipVerify: true} // Our test server doesn't have a cert signed by a CA\n\t_, u := newSecureUpgrader(t)\n\ttpt, err := New(u, &network.NullResourceManager{}, nil, WithTLSClientConfig(tlsConfig))\n\trequire.NoError(t, err)\n\n\tmasToDial, err := tpt.Resolve(context.Background(), serverMA)\n\trequire.NoError(t, err)\n\n\t_, err = tpt.Dial(context.Background(), masToDial[0], test.RandPeerIDFatal(t))\n\trequire.Error(t, err)\n\n\terr = <-errChan\n\trequire.NoError(t, err)\n}\n\nfunc TestDialWss(t *testing.T) {\n\tserverMA, rid, errChan := testWSSServer(t, ma.StringCast(\"/ip4/127.0.0.1/tcp/0/tls/sni/example.com/ws\"))\n\trequire.Contains(t, serverMA.String(), \"tls\")\n\n\ttlsConfig := &tls.Config{InsecureSkipVerify: true} // Our test server doesn't have a cert signed by a CA\n\t_, u := newSecureUpgrader(t)\n\ttpt, err := New(u, &network.NullResourceManager{}, nil, WithTLSClientConfig(tlsConfig))\n\trequire.NoError(t, err)\n\n\tmasToDial, err := tpt.Resolve(context.Background(), serverMA)\n\trequire.NoError(t, err)\n\n\tconn, err := tpt.Dial(context.Background(), masToDial[0], rid)\n\trequire.NoError(t, err)\n\tdefer conn.Close()\n\n\tstream, err := conn.OpenStream(context.Background())\n\trequire.NoError(t, err)\n\tdefer stream.Close()\n\n\terr = <-errChan\n\trequire.NoError(t, err)\n}\n\nfunc TestDialWssNoClientCert(t *testing.T) {\n\tserverMA, rid, _ := testWSSServer(t, ma.StringCast(\"/ip4/127.0.0.1/tcp/0/tls/sni/example.com/ws\"))\n\trequire.Contains(t, serverMA.String(), \"tls\")\n\n\t_, u := newSecureUpgrader(t)\n\ttpt, err := New(u, &network.NullResourceManager{}, nil)\n\trequire.NoError(t, err)\n\n\tmasToDial, err := tpt.Resolve(context.Background(), serverMA)\n\trequire.NoError(t, err)\n\n\t_, err = tpt.Dial(context.Background(), masToDial[0], rid)\n\trequire.Error(t, err)\n\n\t// The server doesn't have a signed certificate\n\trequire.Contains(t, err.Error(), \"x509\")\n}\n\nfunc TestWebsocketTransport(t *testing.T) {\n\tt.Run(\"/ws\", func(t *testing.T) {\n\t\tpeerA, ua := newUpgrader(t)\n\t\tta, err := New(ua, nil, nil)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tpeerB, ub := newUpgrader(t)\n\t\ttb, err := New(ub, nil, nil)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tttransport.SubtestTransport(t, ta, tb, \"/ip4/127.0.0.1/tcp/0/ws\", peerA)\n\t\tttransport.SubtestTransport(t, tb, ta, \"/ip4/127.0.0.1/tcp/0/ws\", peerB)\n\n\t})\n\tt.Run(\"/wss\", func(t *testing.T) {\n\t\tpeerA, ua := newUpgrader(t)\n\t\ttca := generateTLSConfig(t)\n\t\tta, err := New(ua, nil, nil, WithTLSConfig(tca), WithTLSClientConfig(&tls.Config{InsecureSkipVerify: true}))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tpeerB, ub := newUpgrader(t)\n\t\ttcb := generateTLSConfig(t)\n\t\ttb, err := New(ub, nil, nil, WithTLSConfig(tcb), WithTLSClientConfig(&tls.Config{InsecureSkipVerify: true}))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tttransport.SubtestTransport(t, ta, tb, \"/ip4/127.0.0.1/tcp/0/wss\", peerA)\n\t\tttransport.SubtestTransport(t, tb, ta, \"/ip4/127.0.0.1/tcp/0/ws\", peerB)\n\t})\n}\n\nfunc isWSS(addr ma.Multiaddr) bool {\n\tif _, err := addr.ValueForProtocol(ma.P_WSS); err == nil {\n\t\treturn true\n\t}\n\tif _, err := addr.ValueForProtocol(ma.P_WS); err == nil {\n\t\treturn false\n\t}\n\tpanic(\"not a WebSocket address\")\n}\n\nfunc connectAndExchangeData(t *testing.T, laddr ma.Multiaddr, secure bool) {\n\tvar opts []Option\n\tvar tlsConf *tls.Config\n\tif secure {\n\t\ttlsConf = generateTLSConfig(t)\n\t\topts = append(opts, WithTLSConfig(tlsConf))\n\t}\n\tserver, u := newUpgrader(t)\n\ttpt, err := New(u, &network.NullResourceManager{}, nil, opts...)\n\trequire.NoError(t, err)\n\tl, err := tpt.Listen(laddr)\n\trequire.NoError(t, err)\n\tif secure {\n\t\trequire.Contains(t, l.Multiaddr().String(), \"tls\")\n\t} else {\n\t\trequire.Equal(t, lastComponent(t, l.Multiaddr()).String(), wsComponent.String())\n\t}\n\tdefer l.Close()\n\n\tmsg := []byte(\"HELLO WORLD\")\n\n\tgo func() {\n\t\tvar opts []Option\n\t\tif secure {\n\t\t\topts = append(opts, WithTLSClientConfig(&tls.Config{InsecureSkipVerify: true}))\n\t\t}\n\t\t_, u := newUpgrader(t)\n\t\ttpt, err := New(u, &network.NullResourceManager{}, nil, opts...)\n\t\trequire.NoError(t, err)\n\t\tc, err := tpt.Dial(context.Background(), l.Multiaddr(), server)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, secure, isWSS(c.LocalMultiaddr()))\n\t\trequire.Equal(t, secure, isWSS(c.RemoteMultiaddr()))\n\t\tstr, err := c.OpenStream(context.Background())\n\t\trequire.NoError(t, err)\n\t\tdefer str.Close()\n\t\t_, err = str.Write(msg)\n\t\trequire.NoError(t, err)\n\t}()\n\n\tc, err := l.Accept()\n\trequire.NoError(t, err)\n\tdefer c.Close()\n\trequire.Equal(t, secure, isWSS(c.LocalMultiaddr()))\n\trequire.Equal(t, secure, isWSS(c.RemoteMultiaddr()))\n\tstr, err := c.AcceptStream()\n\trequire.NoError(t, err)\n\tdefer str.Close()\n\n\tout, err := io.ReadAll(str)\n\trequire.NoError(t, err)\n\trequire.Equal(t, out, msg, \"got wrong message\")\n}\n\nfunc TestWebsocketConnection(t *testing.T) {\n\tt.Run(\"unencrypted\", func(t *testing.T) {\n\t\tconnectAndExchangeData(t, ma.StringCast(\"/ip4/127.0.0.1/tcp/0/ws\"), false)\n\t})\n\tt.Run(\"encrypted\", func(t *testing.T) {\n\t\tconnectAndExchangeData(t, ma.StringCast(\"/ip4/127.0.0.1/tcp/0/wss\"), true)\n\t})\n}\n\nfunc TestWebsocketListenSecureFailWithoutTLSConfig(t *testing.T) {\n\t_, u := newUpgrader(t)\n\ttpt, err := New(u, &network.NullResourceManager{}, nil)\n\trequire.NoError(t, err)\n\taddr := ma.StringCast(\"/ip4/127.0.0.1/tcp/0/wss\")\n\t_, err = tpt.Listen(addr)\n\trequire.EqualError(t, err, fmt.Sprintf(\"cannot listen on wss address %s without a tls.Config\", addr))\n}\n\nfunc TestWebsocketListenSecureAndInsecure(t *testing.T) {\n\tserverID, serverUpgrader := newUpgrader(t)\n\tserver, err := New(serverUpgrader, &network.NullResourceManager{}, nil, WithTLSConfig(generateTLSConfig(t)))\n\trequire.NoError(t, err)\n\n\tlnInsecure, err := server.Listen(ma.StringCast(\"/ip4/127.0.0.1/tcp/0/ws\"))\n\trequire.NoError(t, err)\n\tlnSecure, err := server.Listen(ma.StringCast(\"/ip4/127.0.0.1/tcp/0/wss\"))\n\trequire.NoError(t, err)\n\n\tt.Run(\"insecure\", func(t *testing.T) {\n\t\t_, clientUpgrader := newUpgrader(t)\n\t\tclient, err := New(clientUpgrader, &network.NullResourceManager{}, nil, WithTLSClientConfig(&tls.Config{InsecureSkipVerify: true}))\n\t\trequire.NoError(t, err)\n\n\t\t// dialing the insecure address should succeed\n\t\tconn, err := client.Dial(context.Background(), lnInsecure.Multiaddr(), serverID)\n\t\trequire.NoError(t, err)\n\t\tdefer conn.Close()\n\t\trequire.Equal(t, lastComponent(t, conn.RemoteMultiaddr()).String(), wsComponent.String())\n\t\trequire.Equal(t, lastComponent(t, conn.LocalMultiaddr()).String(), wsComponent.String())\n\n\t\t// dialing the secure address should fail\n\t\t_, err = client.Dial(context.Background(), lnSecure.Multiaddr(), serverID)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"secure\", func(t *testing.T) {\n\t\t_, clientUpgrader := newUpgrader(t)\n\t\tclient, err := New(clientUpgrader, &network.NullResourceManager{}, nil, WithTLSClientConfig(&tls.Config{InsecureSkipVerify: true}))\n\t\trequire.NoError(t, err)\n\n\t\t// dialing the insecure address should succeed\n\t\tconn, err := client.Dial(context.Background(), lnSecure.Multiaddr(), serverID)\n\t\trequire.NoError(t, err)\n\t\tdefer conn.Close()\n\t\trequire.Equal(t, lastComponent(t, conn.RemoteMultiaddr()).String(), wssComponent.String())\n\t\trequire.Equal(t, lastComponent(t, conn.LocalMultiaddr()).String(), wssComponent.String())\n\n\t\t// dialing the insecure address should fail\n\t\t_, err = client.Dial(context.Background(), lnInsecure.Multiaddr(), serverID)\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc TestConcurrentClose(t *testing.T) {\n\t_, u := newUpgrader(t)\n\ttpt, err := New(u, &network.NullResourceManager{}, nil)\n\trequire.NoError(t, err)\n\tl, err := tpt.gatedMaListen(ma.StringCast(\"/ip4/127.0.0.1/tcp/0/ws\"))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer l.Close()\n\n\tmsg := []byte(\"HELLO WORLD\")\n\n\tgo func() {\n\t\tfor range 100 {\n\t\t\tc, err := tpt.maDial(context.Background(), l.Multiaddr(), &network.NullScope{})\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tgo func() {\n\t\t\t\t_, _ = c.Write(msg)\n\t\t\t}()\n\t\t\tgo func() {\n\t\t\t\t_ = c.Close()\n\t\t\t}()\n\t\t}\n\t}()\n\n\tfor range 100 {\n\t\tc, _, err := l.Accept()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tc.Close()\n\t}\n}\n\nfunc TestWriteZero(t *testing.T) {\n\t_, u := newUpgrader(t)\n\ttpt, err := New(u, &network.NullResourceManager{}, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tl, err := tpt.gatedMaListen(ma.StringCast(\"/ip4/127.0.0.1/tcp/0/ws\"))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer l.Close()\n\n\tmsg := []byte(nil)\n\n\tgo func() {\n\t\tc, err := tpt.maDial(context.Background(), l.Multiaddr(), &network.NullScope{})\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\treturn\n\t\t}\n\t\tdefer c.Close()\n\n\t\tfor range 100 {\n\t\t\tn, err := c.Write(msg)\n\t\t\tif n != 0 {\n\t\t\t\tt.Errorf(\"expected to write 0 bytes, wrote %d\", n)\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\n\tc, _, err := l.Accept()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer c.Close()\n\tbuf := make([]byte, 100)\n\tn, err := c.Read(buf)\n\tif n != 0 {\n\t\tt.Errorf(\"read %d bytes, expected 0\", n)\n\t}\n\tif err != io.EOF {\n\t\tt.Errorf(\"expected EOF, got err: %s\", err)\n\t}\n}\n\nfunc TestResolveMultiaddr(t *testing.T) {\n\t// map[unresolved]resolved\n\ttestCases := map[string]string{\n\t\t\"/dns/example.com/tcp/1234/wss\":        \"/dns/example.com/tcp/1234/tls/sni/example.com/ws\",\n\t\t\"/dns4/example.com/tcp/1234/wss\":       \"/dns4/example.com/tcp/1234/tls/sni/example.com/ws\",\n\t\t\"/dns6/example.com/tcp/1234/wss\":       \"/dns6/example.com/tcp/1234/tls/sni/example.com/ws\",\n\t\t\"/dnsaddr/example.com/tcp/1234/wss\":    \"/dnsaddr/example.com/tcp/1234/wss\",\n\t\t\"/dns4/example.com/tcp/1234/tls/ws\":    \"/dns4/example.com/tcp/1234/tls/sni/example.com/ws\",\n\t\t\"/dns6/example.com/tcp/1234/tls/ws\":    \"/dns6/example.com/tcp/1234/tls/sni/example.com/ws\",\n\t\t\"/dnsaddr/example.com/tcp/1234/tls/ws\": \"/dnsaddr/example.com/tcp/1234/tls/ws\",\n\t}\n\n\tfor unresolved, expectedMA := range testCases {\n\t\tt.Run(unresolved, func(t *testing.T) {\n\n\t\t\tm1 := ma.StringCast(unresolved)\n\t\t\twsTpt := WebsocketTransport{}\n\t\t\tctx := context.Background()\n\n\t\t\taddrs, err := wsTpt.Resolve(ctx, m1)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, addrs, 1)\n\n\t\t\trequire.Equal(t, expectedMA, addrs[0].String())\n\t\t})\n\t}\n}\n\nfunc TestSocksProxy(t *testing.T) {\n\ttestCases := []string{\n\t\t\"/ip4/1.2.3.4/tcp/1/ws\",                     // No TLS\n\t\t\"/ip4/1.2.3.4/tcp/1/tls/ws\",                 // TLS no SNI\n\t\t\"/ip4/1.2.3.4/tcp/1/tls/sni/example.com/ws\", // TLS with an SNI\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc, func(t *testing.T) {\n\t\t\tproxyServer, err := net.Listen(\"tcp\", \"127.0.0.1:0\")\n\t\t\trequire.NoError(t, err)\n\t\t\tproxyServerErr := make(chan error, 1)\n\n\t\t\tgo func() {\n\t\t\t\tdefer proxyServer.Close()\n\t\t\t\tc, err := proxyServer.Accept()\n\t\t\t\tif err != nil {\n\t\t\t\t\tproxyServerErr <- err\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tdefer c.Close()\n\n\t\t\t\treq := [32]byte{}\n\t\t\t\t_, err = io.ReadFull(c, req[:3])\n\t\t\t\tif err != nil {\n\t\t\t\t\tproxyServerErr <- err\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\t// Handshake a SOCKS5 client: https://www.rfc-editor.org/rfc/rfc1928.html#section-3\n\t\t\t\tif !bytes.Equal([]byte{0x05, 0x01, 0x00}, req[:3]) {\n\t\t\t\t\tt.Log(\"expected SOCKS5 connect request\")\n\t\t\t\t\tproxyServerErr <- err\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t_, err = c.Write([]byte{0x05, 0x00})\n\t\t\t\tif err != nil {\n\t\t\t\t\tproxyServerErr <- err\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tproxyServerErr <- nil\n\t\t\t}()\n\n\t\t\torig := gws.DefaultDialer.Proxy\n\t\t\tdefer func() { gws.DefaultDialer.Proxy = orig }()\n\n\t\t\tproxyUrl, err := url.Parse(\"socks5://\" + proxyServer.Addr().String())\n\t\t\trequire.NoError(t, err)\n\t\t\tgws.DefaultDialer.Proxy = http.ProxyURL(proxyUrl)\n\n\t\t\ttlsConfig := &tls.Config{InsecureSkipVerify: true} // Our test server doesn't have a cert signed by a CA\n\t\t\t_, u := newSecureUpgrader(t)\n\t\t\ttpt, err := New(u, &network.NullResourceManager{}, nil, WithTLSClientConfig(tlsConfig))\n\t\t\trequire.NoError(t, err)\n\n\t\t\t// This can be any wss address. We aren't actually going to dial it.\n\t\t\tmaToDial := ma.StringCast(tc)\n\t\t\t_, err = tpt.Dial(context.Background(), maToDial, \"\")\n\t\t\trequire.ErrorContains(t, err, \"failed to read connect reply from SOCKS5 proxy\", \"This should error as we don't have a real socks server\")\n\n\t\t\tselect {\n\t\t\tcase <-time.After(1 * time.Second):\n\t\t\tcase err := <-proxyServerErr:\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestListenerAddr(t *testing.T) {\n\t_, upgrader := newUpgrader(t)\n\ttransport, err := New(upgrader, &network.NullResourceManager{}, nil, WithTLSConfig(generateTLSConfig(t)))\n\trequire.NoError(t, err)\n\tl1, err := transport.Listen(ma.StringCast(\"/ip4/127.0.0.1/tcp/0/ws\"))\n\trequire.NoError(t, err)\n\tdefer l1.Close()\n\trequire.Regexp(t, `^ws://127\\.0\\.0\\.1:[\\d]+$`, l1.Addr().String())\n\tl2, err := transport.Listen(ma.StringCast(\"/ip4/127.0.0.1/tcp/0/wss\"))\n\trequire.NoError(t, err)\n\tdefer l2.Close()\n\trequire.Regexp(t, `^wss://127\\.0\\.0\\.1:[\\d]+$`, l2.Addr().String())\n}\nfunc TestHandshakeTimeout(t *testing.T) {\n\thandshakeTimeout := 200 * time.Millisecond\n\t_, upgrader := newUpgrader(t)\n\ttlsconf := generateTLSConfig(t)\n\ttransport, err := New(upgrader, &network.NullResourceManager{}, nil, WithHandshakeTimeout(handshakeTimeout), WithTLSConfig(tlsconf))\n\trequire.NoError(t, err)\n\n\tfastWSDialer := gws.Dialer{\n\t\tHandshakeTimeout: 10 * handshakeTimeout,\n\t\tTLSClientConfig:  &tls.Config{InsecureSkipVerify: true},\n\t\tNetDial: func(_, addr string) (net.Conn, error) {\n\t\t\ttcpConn, err := net.Dial(\"tcp\", addr)\n\t\t\tif !assert.NoError(t, err) {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn tcpConn, nil\n\t\t},\n\t}\n\n\tslowWSDialer := gws.Dialer{\n\t\tHandshakeTimeout: 10 * handshakeTimeout,\n\t\tNetDial: func(_, addr string) (net.Conn, error) {\n\t\t\ttcpConn, err := net.Dial(\"tcp\", addr)\n\t\t\tif !assert.NoError(t, err) {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\t// wait to simulate a slow handshake\n\t\t\ttime.Sleep(2 * handshakeTimeout)\n\t\t\treturn tcpConn, nil\n\t\t},\n\t\tTLSClientConfig: &tls.Config{InsecureSkipVerify: true},\n\t}\n\tt.Run(\"ws\", func(t *testing.T) {\n\t\t// test the gatedMaListener as we're interested in the websocket handshake timeout and not the upgrader steps.\n\t\twsListener, err := transport.gatedMaListen(ma.StringCast(\"/ip4/127.0.0.1/tcp/0/ws\"))\n\t\trequire.NoError(t, err)\n\t\tdefer wsListener.Close()\n\n\t\tctx, cancel := context.WithTimeout(context.Background(), 10*handshakeTimeout)\n\t\tdefer cancel()\n\t\tconn, resp, err := fastWSDialer.DialContext(ctx, wsListener.Addr().String(), nil)\n\t\tif !assert.NoError(t, err) {\n\t\t\treturn\n\t\t}\n\t\tconn.Close()\n\t\tresp.Body.Close()\n\n\t\tctx, cancel = context.WithTimeout(context.Background(), 10*handshakeTimeout)\n\t\tdefer cancel()\n\t\tconn, resp, err = slowWSDialer.DialContext(ctx, wsListener.Addr().String(), nil)\n\t\tif err == nil {\n\t\t\tconn.Close()\n\t\t\tresp.Body.Close()\n\t\t\tt.Fatal(\"should error as the handshake will time out\")\n\t\t}\n\t})\n\n\tt.Run(\"wss\", func(t *testing.T) {\n\t\t// test the gatedMaListener as we're interested in the websocket handshake timeout and not the upgrader steps.\n\t\twsListener, err := transport.gatedMaListen(ma.StringCast(\"/ip4/127.0.0.1/tcp/0/wss\"))\n\t\trequire.NoError(t, err)\n\t\tdefer wsListener.Close()\n\n\t\t// Test that the normal dial works fine\n\t\tctx, cancel := context.WithTimeout(context.Background(), 10*handshakeTimeout)\n\t\tdefer cancel()\n\t\twsConn, resp, err := fastWSDialer.DialContext(ctx, wsListener.Addr().String(), nil)\n\t\trequire.NoError(t, err)\n\t\twsConn.Close()\n\t\tresp.Body.Close()\n\n\t\tctx, cancel = context.WithTimeout(context.Background(), 10*handshakeTimeout)\n\t\tdefer cancel()\n\t\twsConn, resp, err = slowWSDialer.DialContext(ctx, wsListener.Addr().String(), nil)\n\t\tif err == nil {\n\t\t\twsConn.Close()\n\t\t\tresp.Body.Close()\n\t\t\tt.Fatal(\"websocket handshake should have timed out\")\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "p2p/transport/webtransport/cert_manager.go",
    "content": "package libp2pwebtransport\n\nimport (\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"crypto/tls\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/benbjohnson/clock\"\n\tic \"github.com/libp2p/go-libp2p/core/crypto\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/multiformats/go-multihash\"\n)\n\n// Allow for a bit of clock skew.\n// When we generate a certificate, the NotBefore time is set to clockSkewAllowance before the current time.\n// Similarly, we stop using a certificate one clockSkewAllowance before its expiry time.\nconst clockSkewAllowance = time.Hour\nconst validityMinusTwoSkew = certValidity - (2 * clockSkewAllowance)\n\ntype certConfig struct {\n\ttlsConf *tls.Config\n\tsha256  [32]byte // cached from the tlsConf\n}\n\nfunc (c *certConfig) Start() time.Time { return c.tlsConf.Certificates[0].Leaf.NotBefore }\nfunc (c *certConfig) End() time.Time   { return c.tlsConf.Certificates[0].Leaf.NotAfter }\n\nfunc newCertConfig(key ic.PrivKey, start, end time.Time) (*certConfig, error) {\n\tconf, err := getTLSConf(key, start, end)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &certConfig{\n\t\ttlsConf: conf,\n\t\tsha256:  sha256.Sum256(conf.Certificates[0].Leaf.Raw),\n\t}, nil\n}\n\n// Certificate renewal logic:\n//  1. On startup, we generate one cert that is valid from now (-1h, to allow for clock skew), and another\n//     cert that is valid from the expiry date of the first certificate (again, with allowance for clock skew).\n//  2. Once we reach 1h before expiry of the first certificate, we switch over to the second certificate.\n//     At the same time, we stop advertising the certhash of the first cert and generate the next cert.\ntype certManager struct {\n\tclock     clock.Clock\n\tctx       context.Context\n\tctxCancel context.CancelFunc\n\trefCount  sync.WaitGroup\n\n\tmx            sync.RWMutex\n\tlastConfig    *certConfig // initially nil\n\tcurrentConfig *certConfig\n\tnextConfig    *certConfig // nil until we have passed half the certValidity of the current config\n\taddrComp      ma.Multiaddr\n\n\tserializedCertHashes [][]byte\n}\n\nfunc newCertManager(hostKey ic.PrivKey, clock clock.Clock) (*certManager, error) {\n\tm := &certManager{clock: clock}\n\tm.ctx, m.ctxCancel = context.WithCancel(context.Background())\n\tif err := m.init(hostKey); err != nil {\n\t\treturn nil, err\n\t}\n\n\tm.background(hostKey)\n\treturn m, nil\n}\n\n// getCurrentBucketStartTime returns the canonical start time of the given time as\n// bucketed by ranges of certValidity since unix epoch (plus an offset). This\n// lets you get the same time ranges across reboots without having to persist\n// state.\n// ```\n// ... v--- epoch + offset\n// ... |--------|    |--------|        ...\n// ...        |--------|    |--------| ...\n// ```\nfunc getCurrentBucketStartTime(now time.Time, offset time.Duration) time.Time {\n\tcurrentBucket := (now.UnixMilli() - offset.Milliseconds()) / validityMinusTwoSkew.Milliseconds()\n\treturn time.UnixMilli(offset.Milliseconds() + currentBucket*validityMinusTwoSkew.Milliseconds())\n}\n\nfunc (m *certManager) init(hostKey ic.PrivKey) error {\n\tstart := m.clock.Now()\n\tpubkeyBytes, err := hostKey.GetPublic().Raw()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// We want to add a random offset to each start time so that not all certs\n\t// rotate at the same time across the network. The offset represents moving\n\t// the bucket start time some `offset` earlier.\n\toffset := (time.Duration(binary.LittleEndian.Uint16(pubkeyBytes)) * time.Minute) % certValidity\n\n\t// We want the certificate have been valid for at least one clockSkewAllowance\n\tstart = start.Add(-clockSkewAllowance)\n\tstartTime := getCurrentBucketStartTime(start, offset)\n\tm.nextConfig, err = newCertConfig(hostKey, startTime, startTime.Add(certValidity))\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn m.rollConfig(hostKey)\n}\n\nfunc (m *certManager) rollConfig(hostKey ic.PrivKey) error {\n\t// We stop using the current certificate clockSkewAllowance before its expiry time.\n\t// At this point, the next certificate needs to be valid for one clockSkewAllowance.\n\tnextStart := m.nextConfig.End().Add(-2 * clockSkewAllowance)\n\tc, err := newCertConfig(hostKey, nextStart, nextStart.Add(certValidity))\n\tif err != nil {\n\t\treturn err\n\t}\n\tm.lastConfig = m.currentConfig\n\tm.currentConfig = m.nextConfig\n\tm.nextConfig = c\n\tif err := m.cacheSerializedCertHashes(); err != nil {\n\t\treturn err\n\t}\n\treturn m.cacheAddrComponent()\n}\n\nfunc (m *certManager) background(hostKey ic.PrivKey) {\n\td := m.currentConfig.End().Add(-clockSkewAllowance).Sub(m.clock.Now())\n\tlog.Debug(\"setting timer\", \"duration\", d.String())\n\tt := m.clock.Timer(d)\n\tm.refCount.Add(1)\n\n\tgo func() {\n\t\tdefer m.refCount.Done()\n\t\tdefer t.Stop()\n\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-m.ctx.Done():\n\t\t\t\treturn\n\t\t\tcase <-t.C:\n\t\t\t\tnow := m.clock.Now()\n\t\t\t\tm.mx.Lock()\n\t\t\t\tif err := m.rollConfig(hostKey); err != nil {\n\t\t\t\t\tlog.Error(\"rolling config failed\", \"error\", err)\n\t\t\t\t}\n\t\t\t\td := m.currentConfig.End().Add(-clockSkewAllowance).Sub(now)\n\t\t\t\tlog.Debug(\"rolling certificates\", \"next\", d.String())\n\t\t\t\tt.Reset(d)\n\t\t\t\tm.mx.Unlock()\n\t\t\t}\n\t\t}\n\t}()\n}\n\nfunc (m *certManager) GetConfig() *tls.Config {\n\tm.mx.RLock()\n\tdefer m.mx.RUnlock()\n\treturn m.currentConfig.tlsConf\n}\n\nfunc (m *certManager) AddrComponent() ma.Multiaddr {\n\tm.mx.RLock()\n\tdefer m.mx.RUnlock()\n\treturn m.addrComp\n}\n\nfunc (m *certManager) SerializedCertHashes() [][]byte {\n\treturn m.serializedCertHashes\n}\n\nfunc (m *certManager) cacheSerializedCertHashes() error {\n\thashes := make([][32]byte, 0, 3)\n\tif m.lastConfig != nil {\n\t\thashes = append(hashes, m.lastConfig.sha256)\n\t}\n\thashes = append(hashes, m.currentConfig.sha256)\n\tif m.nextConfig != nil {\n\t\thashes = append(hashes, m.nextConfig.sha256)\n\t}\n\n\tm.serializedCertHashes = m.serializedCertHashes[:0]\n\tfor _, certHash := range hashes {\n\t\th, err := multihash.Encode(certHash[:], multihash.SHA2_256)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to encode certificate hash: %w\", err)\n\t\t}\n\t\tm.serializedCertHashes = append(m.serializedCertHashes, h)\n\t}\n\treturn nil\n}\n\nfunc (m *certManager) cacheAddrComponent() error {\n\tvar addr ma.Multiaddr\n\tc, err := addrComponentForCert(m.currentConfig.sha256[:])\n\tif err != nil {\n\t\treturn err\n\t}\n\taddr = addr.AppendComponent(c)\n\tif m.nextConfig != nil {\n\t\tcomp, err := addrComponentForCert(m.nextConfig.sha256[:])\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\taddr = addr.AppendComponent(comp)\n\t}\n\tm.addrComp = addr\n\treturn nil\n}\n\nfunc (m *certManager) Close() error {\n\tm.ctxCancel()\n\tm.refCount.Wait()\n\treturn nil\n}\n"
  },
  {
    "path": "p2p/transport/webtransport/cert_manager_test.go",
    "content": "package libp2pwebtransport\n\nimport (\n\t\"crypto/sha256\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"testing\"\n\t\"testing/quick\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/test\"\n\n\t\"github.com/benbjohnson/clock\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/multiformats/go-multibase\"\n\t\"github.com/multiformats/go-multihash\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc certificateHashFromTLSConfig(c *tls.Config) [32]byte {\n\treturn sha256.Sum256(c.Certificates[0].Certificate[0])\n}\n\nfunc splitMultiaddr(addr ma.Multiaddr) []ma.Component {\n\tvar components []ma.Component\n\tma.ForEach(addr, func(c ma.Component) bool {\n\t\tcomponents = append(components, c)\n\t\treturn true\n\t})\n\treturn components\n}\n\nfunc certHashFromComponent(t *testing.T, comp ma.Component) []byte {\n\tt.Helper()\n\t_, data, err := multibase.Decode(comp.Value())\n\trequire.NoError(t, err)\n\tmh, err := multihash.Decode(data)\n\trequire.NoError(t, err)\n\trequire.Equal(t, uint64(multihash.SHA2_256), mh.Code)\n\treturn mh.Digest\n}\n\nfunc TestInitialCert(t *testing.T) {\n\tcl := clock.NewMock()\n\tcl.Add(1234567 * time.Hour)\n\tpriv, _, err := test.RandTestKeyPair(crypto.Ed25519, 256)\n\trequire.NoError(t, err)\n\tm, err := newCertManager(priv, cl)\n\trequire.NoError(t, err)\n\tdefer m.Close()\n\n\tconf := m.GetConfig()\n\trequire.Len(t, conf.Certificates, 1)\n\tcert := conf.Certificates[0]\n\trequire.GreaterOrEqual(t, cl.Now().Add(-clockSkewAllowance), cert.Leaf.NotBefore)\n\trequire.Equal(t, cert.Leaf.NotBefore.Add(certValidity), cert.Leaf.NotAfter)\n\taddr := m.AddrComponent()\n\tcomponents := splitMultiaddr(addr)\n\trequire.Len(t, components, 2)\n\trequire.Equal(t, ma.P_CERTHASH, components[0].Protocol().Code)\n\thash := certificateHashFromTLSConfig(conf)\n\trequire.Equal(t, hash[:], certHashFromComponent(t, components[0]))\n\trequire.Equal(t, ma.P_CERTHASH, components[1].Protocol().Code)\n}\n\nfunc TestCertRenewal(t *testing.T) {\n\tcl := clock.NewMock()\n\t// Add a year to avoid edge cases around the epoch\n\tcl.Add(time.Hour * 24 * 365)\n\tpriv, _, err := test.SeededTestKeyPair(crypto.Ed25519, 256, 0)\n\trequire.NoError(t, err)\n\tm, err := newCertManager(priv, cl)\n\trequire.NoError(t, err)\n\tdefer m.Close()\n\n\tfirstConf := m.GetConfig()\n\tfirst := splitMultiaddr(m.AddrComponent())\n\trequire.Len(t, first, 2)\n\trequire.NotEqual(t, first[0].Value(), first[1].Value(), \"the hashes should differ\")\n\t// wait for a new certificate to be generated\n\tcl.Set(m.currentConfig.End().Add(-(clockSkewAllowance + time.Second)))\n\trequire.Never(t, func() bool {\n\t\tfor i, c := range splitMultiaddr(m.AddrComponent()) {\n\t\t\tif c.Value() != first[i].Value() {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\treturn false\n\t}, 100*time.Millisecond, 10*time.Millisecond)\n\tcl.Add(2 * time.Second)\n\trequire.Eventually(t, func() bool { return m.GetConfig() != firstConf }, 200*time.Millisecond, 10*time.Millisecond)\n\tsecondConf := m.GetConfig()\n\n\tsecond := splitMultiaddr(m.AddrComponent())\n\trequire.Len(t, second, 2)\n\tfor _, c := range second {\n\t\trequire.Equal(t, ma.P_CERTHASH, c.Protocol().Code)\n\t}\n\t// check that the 2nd certificate from the beginning was rolled over to be the 1st certificate\n\trequire.Equal(t, first[1].Value(), second[0].Value())\n\trequire.NotEqual(t, first[0].Value(), second[1].Value())\n\n\tcl.Add(certValidity - 2*clockSkewAllowance + time.Second)\n\trequire.Eventually(t, func() bool { return m.GetConfig() != secondConf }, 200*time.Millisecond, 10*time.Millisecond)\n\tthird := splitMultiaddr(m.AddrComponent())\n\trequire.Len(t, third, 2)\n\tfor _, c := range third {\n\t\trequire.Equal(t, ma.P_CERTHASH, c.Protocol().Code)\n\t}\n\t// check that the 2nd certificate from the beginning was rolled over to be the 1st certificate\n\trequire.Equal(t, second[1].Value(), third[0].Value())\n}\n\nfunc TestDeterministicCertsAcrossReboots(t *testing.T) {\n\t// Run this test 100 times to make sure it's deterministic\n\truns := 100\n\tfor i := range runs {\n\t\tt.Run(fmt.Sprintf(\"Run=%d\", i), func(t *testing.T) {\n\t\t\tcl := clock.NewMock()\n\t\t\tpriv, _, err := test.SeededTestKeyPair(crypto.Ed25519, 256, 0)\n\t\t\trequire.NoError(t, err)\n\t\t\tm, err := newCertManager(priv, cl)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer m.Close()\n\n\t\t\tconf := m.GetConfig()\n\t\t\trequire.Len(t, conf.Certificates, 1)\n\t\t\toldCerts := m.serializedCertHashes\n\n\t\t\tm.Close()\n\n\t\t\tcl.Add(time.Hour)\n\t\t\t// reboot\n\t\t\tm, err = newCertManager(priv, cl)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer m.Close()\n\n\t\t\tnewCerts := m.serializedCertHashes\n\n\t\t\trequire.Equal(t, oldCerts, newCerts)\n\t\t})\n\t}\n}\n\nfunc TestDeterministicTimeBuckets(t *testing.T) {\n\tcl := clock.NewMock()\n\tcl.Add(time.Hour * 24 * 365)\n\tstartA := getCurrentBucketStartTime(cl.Now(), 0)\n\tstartB := getCurrentBucketStartTime(cl.Now().Add(time.Hour*24), 0)\n\trequire.Equal(t, startA, startB)\n\n\t// 15 Days later\n\tstartC := getCurrentBucketStartTime(cl.Now().Add(time.Hour*24*15), 0)\n\trequire.NotEqual(t, startC, startB)\n}\n\nfunc TestGetCurrentBucketStartTimeIsWithinBounds(t *testing.T) {\n\trequire.NoError(t, quick.Check(func(timeSinceUnixEpoch time.Duration, offset time.Duration) bool {\n\t\tif offset < 0 {\n\t\t\toffset = -offset\n\t\t}\n\t\tif timeSinceUnixEpoch < 0 {\n\t\t\ttimeSinceUnixEpoch = -timeSinceUnixEpoch\n\t\t}\n\n\t\toffset = offset % certValidity\n\t\t// Bound this to 100 years\n\t\ttimeSinceUnixEpoch = timeSinceUnixEpoch % (time.Hour * 24 * 365 * 100)\n\t\t// Start a bit further in the future to avoid edge cases around epoch\n\t\ttimeSinceUnixEpoch += time.Hour * 24 * 365\n\t\tstart := time.UnixMilli(timeSinceUnixEpoch.Milliseconds())\n\n\t\tbucketStart := getCurrentBucketStartTime(start.Add(-clockSkewAllowance), offset)\n\t\treturn !bucketStart.After(start.Add(-clockSkewAllowance)) || bucketStart.Equal(start.Add(-clockSkewAllowance))\n\t}, nil))\n}\n"
  },
  {
    "path": "p2p/transport/webtransport/conn.go",
    "content": "package libp2pwebtransport\n\nimport (\n\t\"context\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\ttpt \"github.com/libp2p/go-libp2p/core/transport\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/quic-go/quic-go\"\n\t\"github.com/quic-go/webtransport-go\"\n)\n\ntype connSecurityMultiaddrs struct {\n\tnetwork.ConnSecurity\n\tnetwork.ConnMultiaddrs\n}\n\ntype connMultiaddrs struct {\n\tlocal, remote ma.Multiaddr\n}\n\nvar _ network.ConnMultiaddrs = &connMultiaddrs{}\n\nfunc (c *connMultiaddrs) LocalMultiaddr() ma.Multiaddr  { return c.local }\nfunc (c *connMultiaddrs) RemoteMultiaddr() ma.Multiaddr { return c.remote }\n\ntype conn struct {\n\t*connSecurityMultiaddrs\n\n\ttransport *transport\n\tsession   *webtransport.Session\n\n\tscope network.ConnManagementScope\n\tqconn *quic.Conn\n}\n\nvar _ tpt.CapableConn = &conn{}\n\nfunc newConn(tr *transport, sess *webtransport.Session, sconn *connSecurityMultiaddrs, scope network.ConnManagementScope, qconn *quic.Conn) *conn {\n\treturn &conn{\n\t\tconnSecurityMultiaddrs: sconn,\n\t\ttransport:              tr,\n\t\tsession:                sess,\n\t\tscope:                  scope,\n\t\tqconn:                  qconn,\n\t}\n}\n\nfunc (c *conn) OpenStream(ctx context.Context) (network.MuxedStream, error) {\n\tstr, err := c.session.OpenStreamSync(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &stream{str}, nil\n}\n\nfunc (c *conn) AcceptStream() (network.MuxedStream, error) {\n\tstr, err := c.session.AcceptStream(context.Background())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &stream{str}, nil\n}\n\nfunc (c *conn) allowWindowIncrease(size uint64) bool {\n\treturn c.scope.ReserveMemory(int(size), network.ReservationPriorityMedium) == nil\n}\n\n// Close closes the connection.\n// It must be called even if the peer closed the connection in order for\n// garbage collection to properly work in this package.\nfunc (c *conn) Close() error {\n\tdefer c.scope.Done()\n\tc.transport.removeConn(c.qconn)\n\terr := c.session.CloseWithError(0, \"\")\n\t_ = c.qconn.CloseWithError(1, \"\")\n\treturn err\n}\n\nfunc (c *conn) CloseWithError(_ network.ConnErrorCode) error {\n\treturn c.Close()\n}\n\nfunc (c *conn) IsClosed() bool           { return c.session.Context().Err() != nil }\nfunc (c *conn) Scope() network.ConnScope { return c.scope }\nfunc (c *conn) Transport() tpt.Transport { return c.transport }\n\nfunc (c *conn) ConnState() network.ConnectionState {\n\treturn network.ConnectionState{Transport: \"webtransport\"}\n}\n\nfunc (c *conn) As(target any) bool {\n\tif target, ok := target.(**quic.Conn); ok {\n\t\t*target = c.qconn\n\t\treturn true\n\t}\n\tif target, ok := target.(**webtransport.Session); ok {\n\t\t*target = c.session\n\t\treturn true\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "p2p/transport/webtransport/crypto.go",
    "content": "package libp2pwebtransport\n\nimport (\n\t\"bytes\"\n\t\"crypto\"\n\t\"crypto/ecdsa\"\n\t\"crypto/elliptic\"\n\t\"crypto/sha256\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"crypto/x509/pkix\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"math/big\"\n\t\"time\"\n\n\t\"golang.org/x/crypto/hkdf\"\n\n\t\"filippo.io/keygen\"\n\n\tic \"github.com/libp2p/go-libp2p/core/crypto\"\n\n\t\"github.com/multiformats/go-multihash\"\n\t\"github.com/quic-go/quic-go/http3\"\n)\n\nconst deterministicCertInfo = \"determinisitic cert\"\n\nfunc getTLSConf(key ic.PrivKey, start, end time.Time) (*tls.Config, error) {\n\tcert, priv, err := generateCert(key, start, end)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &tls.Config{\n\t\tCertificates: []tls.Certificate{{\n\t\t\tCertificate: [][]byte{cert.Raw},\n\t\t\tPrivateKey:  priv,\n\t\t\tLeaf:        cert,\n\t\t}},\n\t\tNextProtos: []string{http3.NextProtoH3},\n\t}, nil\n}\n\n// generateCert generates certs deterministically based on the `key` and start\n// time passed in. Uses `golang.org/x/crypto/hkdf`.\nfunc generateCert(key ic.PrivKey, start, end time.Time) (*x509.Certificate, *ecdsa.PrivateKey, error) {\n\tkeyBytes, err := key.Raw()\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tstartTimeSalt := make([]byte, 8)\n\tbinary.LittleEndian.PutUint64(startTimeSalt, uint64(start.UnixNano()))\n\tdeterministicHKDFReader := newDeterministicReader(keyBytes, startTimeSalt, deterministicCertInfo)\n\n\tb := make([]byte, 8)\n\tif _, err := deterministicHKDFReader.Read(b); err != nil {\n\t\treturn nil, nil, err\n\t}\n\tserial := int64(binary.BigEndian.Uint64(b))\n\tif serial < 0 {\n\t\tserial = -serial\n\t}\n\tcertTempl := &x509.Certificate{\n\t\tSerialNumber:          big.NewInt(serial),\n\t\tSubject:               pkix.Name{},\n\t\tNotBefore:             start,\n\t\tNotAfter:              end,\n\t\tIsCA:                  true,\n\t\tExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},\n\t\tKeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,\n\t\tBasicConstraintsValid: true,\n\t}\n\n\tecdsaSeed := make([]byte, 192/8) // 192 bits of entropy required for P256\n\tif _, err := io.ReadFull(deterministicHKDFReader, ecdsaSeed); err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tcaPrivateKey, err := keygen.ECDSA(elliptic.P256(), ecdsaSeed)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tcaBytes, err := x509.CreateCertificate(deterministicHKDFReader, certTempl, certTempl, caPrivateKey.Public(), deterministicSigner{caPrivateKey})\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tca, err := x509.ParseCertificate(caBytes)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\treturn ca, caPrivateKey, nil\n}\n\ntype ErrCertHashMismatch struct {\n\tExpected []byte\n\tActual   [][]byte\n}\n\nfunc (e ErrCertHashMismatch) Error() string {\n\treturn fmt.Sprintf(\"cert hash not found: %x (expected: %#x)\", e.Expected, e.Actual)\n}\n\nfunc verifyRawCerts(rawCerts [][]byte, certHashes []multihash.DecodedMultihash) error {\n\tif len(rawCerts) < 1 {\n\t\treturn errors.New(\"no cert\")\n\t}\n\tleaf := rawCerts[len(rawCerts)-1]\n\t// The W3C WebTransport specification currently only allows SHA-256 certificates for serverCertificateHashes.\n\thash := sha256.Sum256(leaf)\n\tvar verified bool\n\tfor _, h := range certHashes {\n\t\tif h.Code == multihash.SHA2_256 && bytes.Equal(h.Digest, hash[:]) {\n\t\t\tverified = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif !verified {\n\t\tdigests := make([][]byte, 0, len(certHashes))\n\t\tfor _, h := range certHashes {\n\t\t\tdigests = append(digests, h.Digest)\n\t\t}\n\t\treturn ErrCertHashMismatch{Expected: hash[:], Actual: digests}\n\t}\n\n\tcert, err := x509.ParseCertificate(leaf)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// TODO: is this the best (and complete?) way to identify RSA certificates?\n\tswitch cert.SignatureAlgorithm {\n\tcase x509.SHA1WithRSA, x509.SHA256WithRSA, x509.SHA384WithRSA, x509.SHA512WithRSA, x509.MD2WithRSA, x509.MD5WithRSA:\n\t\treturn errors.New(\"cert uses RSA\")\n\t}\n\tif l := cert.NotAfter.Sub(cert.NotBefore); l > 14*24*time.Hour {\n\t\treturn fmt.Errorf(\"cert must not be valid for longer than 14 days (NotBefore: %s, NotAfter: %s, Length: %s)\", cert.NotBefore, cert.NotAfter, l)\n\t}\n\tnow := time.Now()\n\tif now.Before(cert.NotBefore) || now.After(cert.NotAfter) {\n\t\treturn fmt.Errorf(\"cert not valid (NotBefore: %s, NotAfter: %s)\", cert.NotBefore, cert.NotAfter)\n\t}\n\treturn nil\n}\n\nfunc newDeterministicReader(seed []byte, salt []byte, info string) io.Reader {\n\treturn hkdf.New(sha256.New, seed, salt, []byte(info))\n}\n\n// deterministicSigner wraps an ecdsa.PrivateKey and exposes a `Sign` method\n// that will produce deterministic signatures by ignoring the rand reader.\n// Go 1.24 produces deterministic ecdsa signatures when passed a nil random source.\n// See: https://go.dev/doc/go1.24#cryptoecdsapkgcryptoecdsa\ntype deterministicSigner struct {\n\tpriv *ecdsa.PrivateKey\n}\n\nvar _ crypto.Signer = deterministicSigner{}\n\nfunc (ds deterministicSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {\n\t// Ignore the rand reader to produce deterministic signatures.\n\t_ = rand\n\treturn ds.priv.Sign(nil, digest, opts)\n}\n\nfunc (ds deterministicSigner) Public() crypto.PublicKey {\n\treturn ds.priv.Public()\n}\n"
  },
  {
    "path": "p2p/transport/webtransport/crypto_test.go",
    "content": "package libp2pwebtransport\n\nimport (\n\t\"bytes\"\n\t\"crypto\"\n\t\"crypto/ecdsa\"\n\t\"crypto/elliptic\"\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"crypto/sha256\"\n\t\"crypto/x509\"\n\t\"crypto/x509/pkix\"\n\t\"fmt\"\n\t\"math/big\"\n\tmrand \"math/rand\"\n\t\"testing\"\n\t\"time\"\n\n\tic \"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/multiformats/go-multihash\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc sha256Multihash(t *testing.T, b []byte) multihash.DecodedMultihash {\n\tt.Helper()\n\thash := sha256.Sum256(b)\n\th, err := multihash.Encode(hash[:], multihash.SHA2_256)\n\trequire.NoError(t, err)\n\tdh, err := multihash.Decode(h)\n\trequire.NoError(t, err)\n\treturn *dh\n}\n\nfunc generateCertWithKey(t *testing.T, key crypto.PrivateKey, start, end time.Time) *x509.Certificate {\n\tt.Helper()\n\tserial := int64(mrand.Uint64())\n\tif serial < 0 {\n\t\tserial = -serial\n\t}\n\tcertTempl := &x509.Certificate{\n\t\tSerialNumber:          big.NewInt(serial),\n\t\tSubject:               pkix.Name{},\n\t\tNotBefore:             start,\n\t\tNotAfter:              end,\n\t\tIsCA:                  true,\n\t\tExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},\n\t\tKeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,\n\t\tBasicConstraintsValid: true,\n\t}\n\tcaBytes, err := x509.CreateCertificate(rand.Reader, certTempl, certTempl, key.(interface{ Public() crypto.PublicKey }).Public(), key)\n\trequire.NoError(t, err)\n\tca, err := x509.ParseCertificate(caBytes)\n\trequire.NoError(t, err)\n\treturn ca\n}\n\nfunc TestCertificateVerification(t *testing.T) {\n\tnow := time.Now()\n\tecdsaKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)\n\trequire.NoError(t, err)\n\trsaKey, err := rsa.GenerateKey(rand.Reader, 1024)\n\trequire.NoError(t, err)\n\n\tt.Run(\"accepting a valid cert\", func(t *testing.T) {\n\t\tvalidCert := generateCertWithKey(t, ecdsaKey, now, now.Add(14*24*time.Hour))\n\t\trequire.NoError(t, verifyRawCerts([][]byte{validCert.Raw}, []multihash.DecodedMultihash{sha256Multihash(t, validCert.Raw)}))\n\t})\n\n\tfor _, tc := range [...]struct {\n\t\tname   string\n\t\tcert   *x509.Certificate\n\t\terrStr string\n\t}{\n\t\t{\n\t\t\tname:   \"validitity period too long\",\n\t\t\tcert:   generateCertWithKey(t, ecdsaKey, now, now.Add(15*24*time.Hour)),\n\t\t\terrStr: \"cert must not be valid for longer than 14 days\",\n\t\t},\n\t\t{\n\t\t\tname:   \"uses RSA key\",\n\t\t\tcert:   generateCertWithKey(t, rsaKey, now, now.Add(14*24*time.Hour)),\n\t\t\terrStr: \"RSA\",\n\t\t},\n\t\t{\n\t\t\tname:   \"expired certificate\",\n\t\t\tcert:   generateCertWithKey(t, ecdsaKey, now.Add(-14*24*time.Hour), now),\n\t\t\terrStr: \"cert not valid\",\n\t\t},\n\t\t{\n\t\t\tname:   \"not yet valid\",\n\t\t\tcert:   generateCertWithKey(t, ecdsaKey, now.Add(time.Hour), now.Add(time.Hour+14*24*time.Hour)),\n\t\t\terrStr: \"cert not valid\",\n\t\t},\n\t} {\n\t\tt.Run(fmt.Sprintf(\"rejecting invalid certificates: %s\", tc.name), func(t *testing.T) {\n\t\t\terr := verifyRawCerts([][]byte{tc.cert.Raw}, []multihash.DecodedMultihash{sha256Multihash(t, tc.cert.Raw)})\n\t\t\trequire.Error(t, err)\n\t\t\trequire.Contains(t, err.Error(), tc.errStr)\n\t\t})\n\t}\n\n\tfor _, tc := range [...]struct {\n\t\tname   string\n\t\tcerts  [][]byte\n\t\thashes []multihash.DecodedMultihash\n\t\terrStr string\n\t}{\n\t\t{\n\t\t\tname:   \"no certificates\",\n\t\t\thashes: []multihash.DecodedMultihash{sha256Multihash(t, []byte(\"foobar\"))},\n\t\t\terrStr: \"no cert\",\n\t\t},\n\t\t{\n\t\t\tname:   \"certificate not parseable\",\n\t\t\tcerts:  [][]byte{[]byte(\"foobar\")},\n\t\t\thashes: []multihash.DecodedMultihash{sha256Multihash(t, []byte(\"foobar\"))},\n\t\t\terrStr: \"x509: malformed certificate\",\n\t\t},\n\t\t{\n\t\t\tname:   \"hash mismatch\",\n\t\t\tcerts:  [][]byte{generateCertWithKey(t, ecdsaKey, now, now.Add(15*24*time.Hour)).Raw},\n\t\t\thashes: []multihash.DecodedMultihash{sha256Multihash(t, []byte(\"foobar\"))},\n\t\t\terrStr: \"cert hash not found\",\n\t\t},\n\t} {\n\t\tt.Run(fmt.Sprintf(\"rejecting invalid certificates: %s\", tc.name), func(t *testing.T) {\n\t\t\terr := verifyRawCerts(tc.certs, tc.hashes)\n\t\t\trequire.Error(t, err)\n\t\t\trequire.Contains(t, err.Error(), tc.errStr)\n\t\t})\n\t}\n}\n\nfunc TestDeterministicCertHashes(t *testing.T) {\n\t// Run this test 1000 times since we want to make sure the signatures are deterministic\n\truns := 1000\n\tfor range runs {\n\t\tzeroSeed := [32]byte{}\n\t\tpriv, _, err := ic.GenerateEd25519Key(bytes.NewReader(zeroSeed[:]))\n\t\trequire.NoError(t, err)\n\t\tcert, certPriv, err := generateCert(priv, time.Time{}, time.Time{}.Add(time.Hour*24*14))\n\t\trequire.NoError(t, err)\n\n\t\tkeyBytes, err := x509.MarshalECPrivateKey(certPriv)\n\t\trequire.NoError(t, err)\n\n\t\tcert2, certPriv2, err := generateCert(priv, time.Time{}, time.Time{}.Add(time.Hour*24*14))\n\t\trequire.NoError(t, err)\n\n\t\trequire.Equal(t, cert2.Signature, cert.Signature)\n\t\trequire.Equal(t, cert2.Raw, cert.Raw)\n\t\tkeyBytes2, err := x509.MarshalECPrivateKey(certPriv2)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, keyBytes, keyBytes2)\n\t}\n}\n"
  },
  {
    "path": "p2p/transport/webtransport/listener.go",
    "content": "package libp2pwebtransport\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\ttpt \"github.com/libp2p/go-libp2p/core/transport\"\n\t\"github.com/libp2p/go-libp2p/p2p/security/noise\"\n\t\"github.com/libp2p/go-libp2p/p2p/security/noise/pb\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/quicreuse\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/quic-go/quic-go\"\n\t\"github.com/quic-go/quic-go/http3\"\n\t\"github.com/quic-go/webtransport-go\"\n)\n\nconst queueLen = 16\nconst handshakeTimeout = 10 * time.Second\n\ntype connKey struct{}\n\ntype listener struct {\n\ttransport       *transport\n\tisStaticTLSConf bool\n\treuseListener   quicreuse.Listener\n\n\tserver webtransport.Server\n\n\tctx       context.Context\n\tctxCancel context.CancelFunc\n\n\tserverClosed chan struct{} // is closed when server.Serve returns\n\n\taddr      net.Addr\n\tmultiaddr ma.Multiaddr\n\n\tqueue chan tpt.CapableConn\n\n\tmx           sync.Mutex\n\tpendingConns map[*quic.Conn]*negotiatingConn\n}\n\nvar _ tpt.Listener = &listener{}\n\nfunc newListener(reuseListener quicreuse.Listener, t *transport, isStaticTLSConf bool) (tpt.Listener, error) {\n\tlocalMultiaddr, err := toWebtransportMultiaddr(reuseListener.Addr())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tln := &listener{\n\t\treuseListener:   reuseListener,\n\t\ttransport:       t,\n\t\tisStaticTLSConf: isStaticTLSConf,\n\t\tqueue:           make(chan tpt.CapableConn, queueLen),\n\t\tserverClosed:    make(chan struct{}),\n\t\taddr:            reuseListener.Addr(),\n\t\tmultiaddr:       localMultiaddr,\n\t\tserver: webtransport.Server{\n\t\t\tH3: &http3.Server{\n\t\t\t\tConnContext: func(ctx context.Context, c *quic.Conn) context.Context {\n\t\t\t\t\treturn context.WithValue(ctx, connKey{}, c)\n\t\t\t\t},\n\t\t\t\tEnableDatagrams: true,\n\t\t\t},\n\t\t\tCheckOrigin: func(_ *http.Request) bool { return true },\n\t\t},\n\t\tpendingConns: make(map[*quic.Conn]*negotiatingConn),\n\t}\n\tln.ctx, ln.ctxCancel = context.WithCancel(context.Background())\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(webtransportHTTPEndpoint, ln.httpHandler)\n\tln.server.H3.Handler = mux\n\twebtransport.ConfigureHTTP3Server(ln.server.H3)\n\tgo func() {\n\t\tdefer close(ln.serverClosed)\n\t\tfor {\n\t\t\tconn, err := ln.reuseListener.Accept(context.Background())\n\t\t\tif err != nil {\n\t\t\t\tlog.Debug(\"serving failed\", \"addr\", ln.Addr(), \"error\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\terr = ln.startHandshake(conn)\n\t\t\tif err != nil {\n\t\t\t\tlog.Debug(\"failed to start handshake\", \"error\", err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tgo ln.server.ServeQUICConn(conn)\n\t\t}\n\t}()\n\treturn ln, nil\n}\n\nfunc (l *listener) startHandshake(conn *quic.Conn) error {\n\tctx, cancel := context.WithTimeout(l.ctx, handshakeTimeout)\n\tstopHandshakeTimeout := context.AfterFunc(ctx, func() {\n\t\tlog.Debug(\"failed to handshake on conn\", \"remote_addr\", conn.RemoteAddr())\n\t\tconn.CloseWithError(1, \"\")\n\t\tl.mx.Lock()\n\t\tdelete(l.pendingConns, conn)\n\t\tl.mx.Unlock()\n\t})\n\tl.mx.Lock()\n\tdefer l.mx.Unlock()\n\t// don't add to map if the context is already cancelled\n\tif ctx.Err() != nil {\n\t\tcancel()\n\t\treturn ctx.Err()\n\t}\n\tl.pendingConns[conn] = &negotiatingConn{\n\t\tConn:                 conn,\n\t\tctx:                  ctx,\n\t\tcancel:               cancel,\n\t\tstopHandshakeTimeout: stopHandshakeTimeout,\n\t}\n\treturn nil\n}\n\n// negotiatingConn is a wrapper around a *quic.Conn that lets us wrap it in\n// our own context for the duration of the upgrade process. Upgrading a quic\n// connection to an h3 connection to a webtransport session.\ntype negotiatingConn struct {\n\t*quic.Conn\n\tctx    context.Context\n\tcancel context.CancelFunc\n\t// stopHandshakeTimeout is a function that stops triggering the handshake timeout. Returns true if the handshake timeout was not triggered.\n\tstopHandshakeTimeout func() bool\n\terr                  error\n}\n\nfunc (c *negotiatingConn) StopHandshakeTimeout() error {\n\tdefer c.cancel()\n\tif c.stopHandshakeTimeout != nil {\n\t\t// cancel the handshake timeout function\n\t\tif !c.stopHandshakeTimeout() {\n\t\t\tc.err = errTimeout\n\t\t}\n\t\tc.stopHandshakeTimeout = nil\n\t}\n\tif c.err != nil {\n\t\treturn c.err\n\t}\n\treturn nil\n}\n\nvar errTimeout = errors.New(\"timeout\")\n\nfunc (l *listener) httpHandler(w http.ResponseWriter, r *http.Request) {\n\ttyp, ok := r.URL.Query()[\"type\"]\n\tif !ok || len(typ) != 1 || typ[0] != \"noise\" {\n\t\tw.WriteHeader(http.StatusBadRequest)\n\t\treturn\n\t}\n\tremoteMultiaddr, err := stringToWebtransportMultiaddr(r.RemoteAddr)\n\tif err != nil {\n\t\t// This should never happen.\n\t\tlog.Error(\"converting remote address failed\", \"remote\", r.RemoteAddr, \"error\", err)\n\t\tw.WriteHeader(http.StatusBadRequest)\n\t\treturn\n\t}\n\tif l.transport.gater != nil && !l.transport.gater.InterceptAccept(&connMultiaddrs{local: l.multiaddr, remote: remoteMultiaddr}) {\n\t\tw.WriteHeader(http.StatusForbidden)\n\t\treturn\n\t}\n\tconnScope, err := network.UnwrapConnManagementScope(r.Context())\n\tif err != nil {\n\t\tconnScope = nil\n\t\t// Don't error here.\n\t\t// Setup scope if we don't have scope from quicreuse.\n\t\t// This is better than failing so that users that don't use quicreuse.ConnContext option with the resource\n\t\t// manager still work correctly.\n\t}\n\tif connScope == nil {\n\t\tconnScope, err = l.transport.rcmgr.OpenConnection(network.DirInbound, false, remoteMultiaddr)\n\t\tif err != nil {\n\t\t\tlog.Debug(\"resource manager blocked incoming connection\", \"addr\", r.RemoteAddr, \"error\", err)\n\t\t\tw.WriteHeader(http.StatusServiceUnavailable)\n\t\t\treturn\n\t\t}\n\t}\n\terr = l.httpHandlerWithConnScope(w, r, connScope)\n\tif err != nil {\n\t\tconnScope.Done()\n\t}\n}\n\nfunc (l *listener) httpHandlerWithConnScope(w http.ResponseWriter, r *http.Request, connScope network.ConnManagementScope) error {\n\tsess, err := l.server.Upgrade(w, r)\n\tif err != nil {\n\t\tlog.Debug(\"upgrade failed\", \"error\", err)\n\t\t// TODO: think about the status code to use here\n\t\tw.WriteHeader(500)\n\t\treturn err\n\t}\n\tctx, cancel := context.WithTimeout(l.ctx, handshakeTimeout)\n\tsconn, err := l.handshake(ctx, sess)\n\tif err != nil {\n\t\tcancel()\n\t\tlog.Debug(\"handshake failed\", \"error\", err)\n\t\tsess.CloseWithError(1, \"\")\n\t\treturn err\n\t}\n\tcancel()\n\n\tif l.transport.gater != nil && !l.transport.gater.InterceptSecured(network.DirInbound, sconn.RemotePeer(), sconn) {\n\t\t// TODO: can we close with a specific error here?\n\t\tsess.CloseWithError(errorCodeConnectionGating, \"\")\n\t\treturn errors.New(\"gater blocked connection\")\n\t}\n\n\tif err := connScope.SetPeer(sconn.RemotePeer()); err != nil {\n\t\tlog.Debug(\"resource manager blocked incoming connection for peer\", \"peer\", sconn.RemotePeer(), \"addr\", r.RemoteAddr, \"error\", err)\n\t\tsess.CloseWithError(1, \"\")\n\t\treturn err\n\t}\n\n\tconnVal := r.Context().Value(connKey{})\n\tif connVal == nil {\n\t\tlog.Error(\"missing conn from context\")\n\t\tsess.CloseWithError(1, \"\")\n\t\treturn errors.New(\"invalid context\")\n\t}\n\tqconn := connVal.(*quic.Conn)\n\n\tl.mx.Lock()\n\tnconn, ok := l.pendingConns[qconn]\n\tdelete(l.pendingConns, qconn)\n\tl.mx.Unlock()\n\tif !ok {\n\t\tlog.Debug(\"handshake timed out\", \"remote_addr\", r.RemoteAddr)\n\t\tsess.CloseWithError(1, \"\")\n\t\treturn errTimeout\n\t}\n\tif err := nconn.StopHandshakeTimeout(); err != nil {\n\t\tlog.Debug(\"handshake timed out\", \"remote_addr\", r.RemoteAddr)\n\t\tsess.CloseWithError(1, \"\")\n\t\treturn err\n\t}\n\n\tconn := newConn(l.transport, sess, sconn, connScope, qconn)\n\tl.transport.addConn(qconn, conn)\n\tselect {\n\tcase l.queue <- conn:\n\tdefault:\n\t\tlog.Debug(\"accept queue full, dropping incoming connection\", \"peer\", sconn.RemotePeer(), \"addr\", r.RemoteAddr, \"error\", err)\n\t\tconn.Close()\n\t\treturn errors.New(\"accept queue full\")\n\t}\n\n\treturn nil\n}\n\nfunc (l *listener) Accept() (tpt.CapableConn, error) {\n\tselect {\n\tcase <-l.ctx.Done():\n\t\treturn nil, tpt.ErrListenerClosed\n\tcase c := <-l.queue:\n\t\treturn c, nil\n\t}\n}\n\nfunc (l *listener) handshake(ctx context.Context, sess *webtransport.Session) (*connSecurityMultiaddrs, error) {\n\tlocal, err := toWebtransportMultiaddr(sess.LocalAddr())\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error determiniting local addr: %w\", err)\n\t}\n\tremote, err := toWebtransportMultiaddr(sess.RemoteAddr())\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error determiniting remote addr: %w\", err)\n\t}\n\n\tstr, err := sess.AcceptStream(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar earlyData [][]byte\n\tif !l.isStaticTLSConf {\n\t\tearlyData = l.transport.certManager.SerializedCertHashes()\n\t}\n\n\tn, err := l.transport.noise.WithSessionOptions(noise.EarlyData(\n\t\tnil,\n\t\tnewEarlyDataSender(&pb.NoiseExtensions{WebtransportCerthashes: earlyData}),\n\t))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to initialize Noise session: %w\", err)\n\t}\n\tc, err := n.SecureInbound(ctx, webtransportStream{Stream: str, wsess: sess}, \"\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &connSecurityMultiaddrs{\n\t\tConnSecurity:   c,\n\t\tConnMultiaddrs: &connMultiaddrs{local: local, remote: remote},\n\t}, nil\n}\n\nfunc (l *listener) Addr() net.Addr {\n\treturn l.addr\n}\n\nfunc (l *listener) Multiaddr() ma.Multiaddr {\n\tif l.transport.certManager == nil {\n\t\treturn l.multiaddr\n\t}\n\treturn l.multiaddr.Encapsulate(l.transport.certManager.AddrComponent())\n}\n\nfunc (l *listener) Close() error {\n\tl.ctxCancel()\n\tl.reuseListener.Close()\n\terr := l.server.Close()\n\t<-l.serverClosed\nloop:\n\tfor {\n\t\tselect {\n\t\tcase conn := <-l.queue:\n\t\t\tconn.Close()\n\t\tdefault:\n\t\t\tbreak loop\n\t\t}\n\t}\n\treturn err\n}\n"
  },
  {
    "path": "p2p/transport/webtransport/mock_connection_gater_test.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github.com/libp2p/go-libp2p/core/connmgr (interfaces: ConnectionGater)\n//\n// Generated by this command:\n//\n//\tmockgen -package libp2pwebtransport_test -destination mock_connection_gater_test.go github.com/libp2p/go-libp2p/core/connmgr ConnectionGater\n//\n\n// Package libp2pwebtransport_test is a generated GoMock package.\npackage libp2pwebtransport_test\n\nimport (\n\treflect \"reflect\"\n\n\tcontrol \"github.com/libp2p/go-libp2p/core/control\"\n\tnetwork \"github.com/libp2p/go-libp2p/core/network\"\n\tpeer \"github.com/libp2p/go-libp2p/core/peer\"\n\tmultiaddr \"github.com/multiformats/go-multiaddr\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockConnectionGater is a mock of ConnectionGater interface.\ntype MockConnectionGater struct {\n\tctrl     *gomock.Controller\n\trecorder *MockConnectionGaterMockRecorder\n\tisgomock struct{}\n}\n\n// MockConnectionGaterMockRecorder is the mock recorder for MockConnectionGater.\ntype MockConnectionGaterMockRecorder struct {\n\tmock *MockConnectionGater\n}\n\n// NewMockConnectionGater creates a new mock instance.\nfunc NewMockConnectionGater(ctrl *gomock.Controller) *MockConnectionGater {\n\tmock := &MockConnectionGater{ctrl: ctrl}\n\tmock.recorder = &MockConnectionGaterMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockConnectionGater) EXPECT() *MockConnectionGaterMockRecorder {\n\treturn m.recorder\n}\n\n// InterceptAccept mocks base method.\nfunc (m *MockConnectionGater) InterceptAccept(arg0 network.ConnMultiaddrs) bool {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"InterceptAccept\", arg0)\n\tret0, _ := ret[0].(bool)\n\treturn ret0\n}\n\n// InterceptAccept indicates an expected call of InterceptAccept.\nfunc (mr *MockConnectionGaterMockRecorder) InterceptAccept(arg0 any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"InterceptAccept\", reflect.TypeOf((*MockConnectionGater)(nil).InterceptAccept), arg0)\n}\n\n// InterceptAddrDial mocks base method.\nfunc (m *MockConnectionGater) InterceptAddrDial(arg0 peer.ID, arg1 multiaddr.Multiaddr) bool {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"InterceptAddrDial\", arg0, arg1)\n\tret0, _ := ret[0].(bool)\n\treturn ret0\n}\n\n// InterceptAddrDial indicates an expected call of InterceptAddrDial.\nfunc (mr *MockConnectionGaterMockRecorder) InterceptAddrDial(arg0, arg1 any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"InterceptAddrDial\", reflect.TypeOf((*MockConnectionGater)(nil).InterceptAddrDial), arg0, arg1)\n}\n\n// InterceptPeerDial mocks base method.\nfunc (m *MockConnectionGater) InterceptPeerDial(p peer.ID) bool {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"InterceptPeerDial\", p)\n\tret0, _ := ret[0].(bool)\n\treturn ret0\n}\n\n// InterceptPeerDial indicates an expected call of InterceptPeerDial.\nfunc (mr *MockConnectionGaterMockRecorder) InterceptPeerDial(p any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"InterceptPeerDial\", reflect.TypeOf((*MockConnectionGater)(nil).InterceptPeerDial), p)\n}\n\n// InterceptSecured mocks base method.\nfunc (m *MockConnectionGater) InterceptSecured(arg0 network.Direction, arg1 peer.ID, arg2 network.ConnMultiaddrs) bool {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"InterceptSecured\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(bool)\n\treturn ret0\n}\n\n// InterceptSecured indicates an expected call of InterceptSecured.\nfunc (mr *MockConnectionGaterMockRecorder) InterceptSecured(arg0, arg1, arg2 any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"InterceptSecured\", reflect.TypeOf((*MockConnectionGater)(nil).InterceptSecured), arg0, arg1, arg2)\n}\n\n// InterceptUpgraded mocks base method.\nfunc (m *MockConnectionGater) InterceptUpgraded(arg0 network.Conn) (bool, control.DisconnectReason) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"InterceptUpgraded\", arg0)\n\tret0, _ := ret[0].(bool)\n\tret1, _ := ret[1].(control.DisconnectReason)\n\treturn ret0, ret1\n}\n\n// InterceptUpgraded indicates an expected call of InterceptUpgraded.\nfunc (mr *MockConnectionGaterMockRecorder) InterceptUpgraded(arg0 any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"InterceptUpgraded\", reflect.TypeOf((*MockConnectionGater)(nil).InterceptUpgraded), arg0)\n}\n"
  },
  {
    "path": "p2p/transport/webtransport/multiaddr.go",
    "content": "package libp2pwebtransport\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"strconv\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n\t\"github.com/multiformats/go-multibase\"\n\t\"github.com/multiformats/go-multihash\"\n)\n\nvar webtransportMA = ma.StringCast(\"/quic-v1/webtransport\")\n\nfunc toWebtransportMultiaddr(na net.Addr) (ma.Multiaddr, error) {\n\taddr, err := manet.FromNetAddr(na)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif _, err := addr.ValueForProtocol(ma.P_UDP); err != nil {\n\t\treturn nil, errors.New(\"not a UDP address\")\n\t}\n\treturn addr.Encapsulate(webtransportMA), nil\n}\n\nfunc stringToWebtransportMultiaddr(str string) (ma.Multiaddr, error) {\n\thost, portStr, err := net.SplitHostPort(str)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tport, err := strconv.ParseInt(portStr, 10, 32)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tip := net.ParseIP(host)\n\tif ip == nil {\n\t\treturn nil, errors.New(\"failed to parse IP\")\n\t}\n\treturn toWebtransportMultiaddr(&net.UDPAddr{IP: ip, Port: int(port)})\n}\n\nfunc extractCertHashes(addr ma.Multiaddr) ([]multihash.DecodedMultihash, error) {\n\tcertHashesStr := make([]string, 0, 2)\n\tma.ForEach(addr, func(c ma.Component) bool {\n\t\tif c.Protocol().Code == ma.P_CERTHASH {\n\t\t\tcertHashesStr = append(certHashesStr, c.Value())\n\t\t}\n\t\treturn true\n\t})\n\tcertHashes := make([]multihash.DecodedMultihash, 0, len(certHashesStr))\n\tfor _, s := range certHashesStr {\n\t\t_, ch, err := multibase.Decode(s)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to multibase-decode certificate hash: %w\", err)\n\t\t}\n\t\tdh, err := multihash.Decode(ch)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to multihash-decode certificate hash: %w\", err)\n\t\t}\n\t\tcertHashes = append(certHashes, *dh)\n\t}\n\treturn certHashes, nil\n}\n\nfunc addrComponentForCert(hash []byte) (*ma.Component, error) {\n\tmh, err := multihash.Encode(hash, multihash.SHA2_256)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcertStr, err := multibase.Encode(multibase.Base58BTC, mh)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn ma.NewComponent(ma.ProtocolWithCode(ma.P_CERTHASH).Name, certStr)\n}\n\n// IsWebtransportMultiaddr returns true if the given multiaddr is a well formed\n// webtransport multiaddr. Returns the number of certhashes found.\nfunc IsWebtransportMultiaddr(multiaddr ma.Multiaddr) (bool, int) {\n\tconst (\n\t\tinit = iota\n\t\tfoundUDP\n\t\tfoundQuicV1\n\t\tfoundWebTransport\n\t)\n\tstate := init\n\tcerthashCount := 0\n\n\tma.ForEach(multiaddr, func(c ma.Component) bool {\n\t\tswitch c.Protocol().Code {\n\t\tcase ma.P_UDP:\n\t\t\tif state == init {\n\t\t\t\tstate = foundUDP\n\t\t\t}\n\t\tcase ma.P_QUIC_V1:\n\t\t\tif state == foundUDP {\n\t\t\t\tstate = foundQuicV1\n\t\t\t}\n\t\tcase ma.P_WEBTRANSPORT:\n\t\t\tif state == foundQuicV1 {\n\t\t\t\tstate = foundWebTransport\n\t\t\t}\n\t\tcase ma.P_CERTHASH:\n\t\t\tif state == foundWebTransport {\n\t\t\t\tcerthashCount++\n\t\t\t}\n\t\t}\n\t\treturn true\n\t})\n\treturn state == foundWebTransport, certhashCount\n}\n"
  },
  {
    "path": "p2p/transport/webtransport/multiaddr_test.go",
    "content": "package libp2pwebtransport\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"testing\"\n\n\tma \"github.com/multiformats/go-multiaddr\"\n\t\"github.com/multiformats/go-multibase\"\n\t\"github.com/multiformats/go-multihash\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestWebtransportMultiaddr(t *testing.T) {\n\tt.Run(\"valid\", func(t *testing.T) {\n\t\taddr, err := toWebtransportMultiaddr(&net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1337})\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"/ip4/127.0.0.1/udp/1337/quic-v1/webtransport\", addr.String())\n\t})\n\n\tt.Run(\"invalid\", func(t *testing.T) {\n\t\t_, err := toWebtransportMultiaddr(&net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1337})\n\t\trequire.EqualError(t, err, \"not a UDP address\")\n\t})\n}\n\nfunc TestWebtransportMultiaddrFromString(t *testing.T) {\n\tt.Run(\"valid\", func(t *testing.T) {\n\t\taddr, err := stringToWebtransportMultiaddr(\"1.2.3.4:60042\")\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"/ip4/1.2.3.4/udp/60042/quic-v1/webtransport\", addr.String())\n\t})\n\n\tt.Run(\"invalid\", func(t *testing.T) {\n\t\tfor _, addr := range [...]string{\n\t\t\t\"1.2.3.4\",        // missing port\n\t\t\t\"1.2.3.4:123456\", // invalid port\n\t\t\t\":1234\",          // missing IP\n\t\t\t\"foobar\",\n\t\t} {\n\t\t\t_, err := stringToWebtransportMultiaddr(addr)\n\t\t\trequire.Error(t, err)\n\t\t}\n\t})\n}\n\nfunc encodeCertHash(t *testing.T, b []byte, mh uint64, mb multibase.Encoding) string {\n\tt.Helper()\n\th, err := multihash.Encode(b, mh)\n\trequire.NoError(t, err)\n\tstr, err := multibase.Encode(mb, h)\n\trequire.NoError(t, err)\n\treturn str\n}\n\nfunc TestExtractCertHashes(t *testing.T) {\n\tfooHash := encodeCertHash(t, []byte(\"foo\"), multihash.SHA2_256, multibase.Base58BTC)\n\tbarHash := encodeCertHash(t, []byte(\"bar\"), multihash.BLAKE2B_MAX, multibase.Base32)\n\n\t// valid cases\n\tfor _, tc := range [...]struct {\n\t\taddr   string\n\t\thashes []string\n\t}{\n\t\t{addr: \"/ip4/127.0.0.1/udp/1234/quic-v1/webtransport\"},\n\t\t{addr: fmt.Sprintf(\"/ip4/127.0.0.1/udp/1234/quic-v1/webtransport/certhash/%s\", fooHash), hashes: []string{\"foo\"}},\n\t\t{addr: fmt.Sprintf(\"/ip4/127.0.0.1/udp/1234/quic-v1/webtransport/certhash/%s/certhash/%s\", fooHash, barHash), hashes: []string{\"foo\", \"bar\"}},\n\t} {\n\t\tch, err := extractCertHashes(ma.StringCast(tc.addr))\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, ch, len(tc.hashes))\n\t\tfor i, h := range tc.hashes {\n\t\t\trequire.Equal(t, h, string(ch[i].Digest))\n\t\t}\n\t}\n}\n\nfunc TestWebtransportResolve(t *testing.T) {\n\ttestCases := []string{\n\t\t\"/dns4/example.com/udp/1337/quic-v1/webtransport\",\n\t\t\"/dnsaddr/example.com/udp/1337/quic-v1/webtransport\",\n\t\t\"/ip4/127.0.0.1/udp/1337/quic-v1/sni/example.com/webtransport\",\n\t}\n\n\ttpt := &transport{}\n\tctx := context.Background()\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc, func(t *testing.T) {\n\t\t\toutMa, err := tpt.Resolve(ctx, ma.StringCast(tc))\n\t\t\trequire.NoError(t, err)\n\t\t\tsni, err := outMa[0].ValueForProtocol(ma.P_SNI)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, \"example.com\", sni)\n\t\t})\n\t}\n\n\tt.Run(\"No sni\", func(t *testing.T) {\n\t\toutMa, err := tpt.Resolve(ctx, ma.StringCast(\"/ip4/127.0.0.1/udp/1337/quic-v1/webtransport\"))\n\t\trequire.NoError(t, err)\n\t\t_, err = outMa[0].ValueForProtocol(ma.P_SNI)\n\t\trequire.Error(t, err)\n\t})\n}\n\nfunc TestIsWebtransportMultiaddr(t *testing.T) {\n\tfooHash := encodeCertHash(t, []byte(\"foo\"), multihash.SHA2_256, multibase.Base58BTC)\n\tbarHash := encodeCertHash(t, []byte(\"bar\"), multihash.SHA2_256, multibase.Base58BTC)\n\n\ttestCases := []struct {\n\t\taddr          string\n\t\twant          bool\n\t\tcerthashCount int\n\t}{\n\t\t{addr: \"/ip4/1.2.3.4/udp/60042/quic-v1/webtransport\", want: true},\n\t\t{addr: \"/ip4/1.2.3.4/udp/60042/quic-v1/webtransport/certhash/\" + fooHash, want: true, certhashCount: 1},\n\t\t{addr: \"/ip4/1.2.3.4/udp/60042/quic-v1/webtransport/certhash/\" + fooHash + \"/certhash/\" + barHash, want: true, certhashCount: 2},\n\t\t{addr: \"/dns4/example.com/udp/60042/quic-v1/webtransport/certhash/\" + fooHash, want: true, certhashCount: 1},\n\t\t{addr: \"/dns4/example.com/tcp/60042/quic-v1/webtransport/certhash/\" + fooHash, want: false},\n\t\t{addr: \"/dns4/example.com/udp/60042/webrtc/certhash/\" + fooHash, want: false},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.addr, func(t *testing.T) {\n\t\t\tgot, n := IsWebtransportMultiaddr(ma.StringCast(tc.addr))\n\t\t\trequire.Equal(t, tc.want, got)\n\t\t\trequire.Equal(t, tc.certhashCount, n)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "p2p/transport/webtransport/noise_early_data.go",
    "content": "package libp2pwebtransport\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/p2p/security/noise\"\n\t\"github.com/libp2p/go-libp2p/p2p/security/noise/pb\"\n)\n\ntype earlyDataHandler struct {\n\tearlyData *pb.NoiseExtensions\n\treceive   func(extensions *pb.NoiseExtensions) error\n}\n\nvar _ noise.EarlyDataHandler = &earlyDataHandler{}\n\nfunc newEarlyDataSender(earlyData *pb.NoiseExtensions) noise.EarlyDataHandler {\n\treturn &earlyDataHandler{earlyData: earlyData}\n}\n\nfunc newEarlyDataReceiver(receive func(*pb.NoiseExtensions) error) noise.EarlyDataHandler {\n\treturn &earlyDataHandler{receive: receive}\n}\n\nfunc (e *earlyDataHandler) Send(context.Context, net.Conn, peer.ID) *pb.NoiseExtensions {\n\treturn e.earlyData\n}\n\nfunc (e *earlyDataHandler) Received(_ context.Context, _ net.Conn, ext *pb.NoiseExtensions) error {\n\tif e.receive == nil {\n\t\treturn nil\n\t}\n\treturn e.receive(ext)\n}\n"
  },
  {
    "path": "p2p/transport/webtransport/stream.go",
    "content": "package libp2pwebtransport\n\nimport (\n\t\"errors\"\n\t\"net\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\n\t\"github.com/quic-go/webtransport-go\"\n)\n\nconst (\n\treset webtransport.StreamErrorCode = 0\n)\n\ntype webtransportStream struct {\n\t*webtransport.Stream\n\twsess *webtransport.Session\n}\n\nvar _ net.Conn = webtransportStream{}\n\nfunc (s webtransportStream) LocalAddr() net.Addr {\n\treturn s.wsess.LocalAddr()\n}\n\nfunc (s webtransportStream) RemoteAddr() net.Addr {\n\treturn s.wsess.RemoteAddr()\n}\n\ntype stream struct {\n\t*webtransport.Stream\n}\n\nvar _ network.MuxedStream = stream{}\n\nfunc (s stream) Read(b []byte) (n int, err error) {\n\tn, err = s.Stream.Read(b)\n\tif err != nil {\n\t\tvar streamErr *webtransport.StreamError\n\t\tif errors.As(err, &streamErr) {\n\t\t\terr = &network.StreamError{\n\t\t\t\tErrorCode:      0,\n\t\t\t\tRemote:         streamErr.Remote,\n\t\t\t\tTransportError: err,\n\t\t\t}\n\t\t}\n\t}\n\treturn n, err\n}\n\nfunc (s stream) Write(b []byte) (n int, err error) {\n\tn, err = s.Stream.Write(b)\n\tif err != nil {\n\t\tvar streamErr *webtransport.StreamError\n\t\tif errors.As(err, &streamErr) {\n\t\t\terr = &network.StreamError{\n\t\t\t\tErrorCode:      0,\n\t\t\t\tRemote:         streamErr.Remote,\n\t\t\t\tTransportError: err,\n\t\t\t}\n\t\t}\n\t}\n\treturn n, err\n}\n\nfunc (s stream) Reset() error {\n\ts.Stream.CancelRead(reset)\n\ts.Stream.CancelWrite(reset)\n\treturn nil\n}\n\n// ResetWithError resets the stream ignoring the error code. Error codes aren't\n// specified for WebTransport as the current implementation of WebTransport in\n// browsers(https://www.ietf.org/archive/id/draft-kinnear-webtransport-http2-02.html)\n// only supports 1 byte error codes. For more details, see\n// https://github.com/libp2p/specs/blob/4eca305185c7aef219e936bef76c48b1ab0a8b43/error-codes/README.md?plain=1#L84\nfunc (s stream) ResetWithError(_ network.StreamErrorCode) error {\n\ts.Stream.CancelRead(reset)\n\ts.Stream.CancelWrite(reset)\n\treturn nil\n}\n\nfunc (s stream) Close() error {\n\ts.Stream.CancelRead(reset)\n\treturn s.Stream.Close()\n}\n\nfunc (s stream) CloseRead() error {\n\ts.Stream.CancelRead(reset)\n\treturn nil\n}\n\nfunc (s stream) CloseWrite() error {\n\treturn s.Stream.Close()\n}\n"
  },
  {
    "path": "p2p/transport/webtransport/transport.go",
    "content": "package libp2pwebtransport\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/connmgr\"\n\tic \"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/pnet\"\n\ttpt \"github.com/libp2p/go-libp2p/core/transport\"\n\t\"github.com/libp2p/go-libp2p/p2p/security/noise\"\n\t\"github.com/libp2p/go-libp2p/p2p/security/noise/pb\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/quicreuse\"\n\n\t\"github.com/benbjohnson/clock\"\n\tlogging \"github.com/libp2p/go-libp2p/gologshim\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n\t\"github.com/multiformats/go-multihash\"\n\t\"github.com/quic-go/quic-go\"\n\t\"github.com/quic-go/quic-go/http3\"\n\t\"github.com/quic-go/webtransport-go\"\n)\n\nvar log = logging.Logger(\"webtransport\")\n\nconst webtransportHTTPEndpoint = \"/.well-known/libp2p-webtransport\"\n\nconst errorCodeConnectionGating = 0x47415445 // GATE in ASCII\n\nconst certValidity = 14 * 24 * time.Hour\n\ntype Option func(*transport) error\n\nfunc WithClock(cl clock.Clock) Option {\n\treturn func(t *transport) error {\n\t\tt.clock = cl\n\t\treturn nil\n\t}\n}\n\n// WithTLSClientConfig sets a custom tls.Config used for dialing.\n// This option is most useful for setting a custom tls.Config.RootCAs certificate pool.\n// When dialing a multiaddr that contains a /certhash component, this library will set InsecureSkipVerify and\n// overwrite the VerifyPeerCertificate callback.\nfunc WithTLSClientConfig(c *tls.Config) Option {\n\treturn func(t *transport) error {\n\t\tt.tlsClientConf = c\n\t\treturn nil\n\t}\n}\n\nfunc WithHandshakeTimeout(d time.Duration) Option {\n\treturn func(t *transport) error {\n\t\tt.handshakeTimeout = d\n\t\treturn nil\n\t}\n}\n\ntype transport struct {\n\tprivKey ic.PrivKey\n\tpid     peer.ID\n\tclock   clock.Clock\n\n\tconnManager *quicreuse.ConnManager\n\trcmgr       network.ResourceManager\n\tgater       connmgr.ConnectionGater\n\n\tlistenOnce     sync.Once\n\tlistenOnceErr  error\n\tcertManager    *certManager\n\thasCertManager atomic.Bool // set to true once the certManager is initialized\n\tstaticTLSConf  *tls.Config\n\ttlsClientConf  *tls.Config\n\n\tnoise *noise.Transport\n\n\tconnMx           sync.Mutex\n\tconns            map[*quic.Conn]*conn // quic connection -> *conn\n\thandshakeTimeout time.Duration\n}\n\nvar _ tpt.Transport = &transport{}\nvar _ tpt.Resolver = &transport{}\nvar _ io.Closer = &transport{}\n\nfunc New(key ic.PrivKey, psk pnet.PSK, connManager *quicreuse.ConnManager, gater connmgr.ConnectionGater, rcmgr network.ResourceManager, opts ...Option) (tpt.Transport, error) {\n\tif len(psk) > 0 {\n\t\tlog.Error(\"WebTransport doesn't support private networks yet.\")\n\t\treturn nil, errors.New(\"WebTransport doesn't support private networks yet\")\n\t}\n\tif rcmgr == nil {\n\t\trcmgr = &network.NullResourceManager{}\n\t}\n\tid, err := peer.IDFromPrivateKey(key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tt := &transport{\n\t\tpid:              id,\n\t\tprivKey:          key,\n\t\trcmgr:            rcmgr,\n\t\tgater:            gater,\n\t\tclock:            clock.New(),\n\t\tconnManager:      connManager,\n\t\tconns:            map[*quic.Conn]*conn{},\n\t\thandshakeTimeout: handshakeTimeout,\n\t}\n\tfor _, opt := range opts {\n\t\tif err := opt(t); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tn, err := noise.New(noise.ID, key, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tt.noise = n\n\treturn t, nil\n}\n\nfunc (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tpt.CapableConn, error) {\n\tscope, err := t.rcmgr.OpenConnection(network.DirOutbound, false, raddr)\n\tif err != nil {\n\t\tlog.Debug(\"resource manager blocked outgoing connection\", \"peer\", p, \"addr\", raddr, \"error\", err)\n\t\treturn nil, err\n\t}\n\n\tc, err := t.dialWithScope(ctx, raddr, p, scope)\n\tif err != nil {\n\t\tscope.Done()\n\t\treturn nil, err\n\t}\n\n\treturn c, nil\n}\n\nfunc (t *transport) dialWithScope(ctx context.Context, raddr ma.Multiaddr, p peer.ID, scope network.ConnManagementScope) (tpt.CapableConn, error) {\n\t_, addr, err := manet.DialArgs(raddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\turl := fmt.Sprintf(\"https://%s%s?type=noise\", addr, webtransportHTTPEndpoint)\n\tcertHashes, err := extractCertHashes(raddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(certHashes) == 0 {\n\t\treturn nil, errors.New(\"can't dial webtransport without certhashes\")\n\t}\n\n\tsni, _ := extractSNI(raddr)\n\n\tif err := scope.SetPeer(p); err != nil {\n\t\tlog.Debug(\"resource manager blocked outgoing connection for peer\", \"peer\", p, \"addr\", raddr, \"error\", err)\n\t\treturn nil, err\n\t}\n\n\tmaddr, _ := ma.SplitFunc(raddr, func(c ma.Component) bool { return c.Protocol().Code == ma.P_WEBTRANSPORT })\n\tsess, qconn, err := t.dial(ctx, maddr, url, sni, certHashes)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsconn, err := t.upgrade(ctx, sess, p, certHashes)\n\tif err != nil {\n\t\tsess.CloseWithError(1, \"\")\n\t\tqconn.CloseWithError(1, \"\")\n\t\treturn nil, err\n\t}\n\tif t.gater != nil && !t.gater.InterceptSecured(network.DirOutbound, p, sconn) {\n\t\tsess.CloseWithError(errorCodeConnectionGating, \"\")\n\t\tqconn.CloseWithError(errorCodeConnectionGating, \"\")\n\t\treturn nil, fmt.Errorf(\"secured connection gated\")\n\t}\n\tconn := newConn(t, sess, sconn, scope, qconn)\n\tt.addConn(qconn, conn)\n\treturn conn, nil\n}\n\nfunc (t *transport) dial(ctx context.Context, addr ma.Multiaddr, url, sni string, certHashes []multihash.DecodedMultihash) (*webtransport.Session, *quic.Conn, error) {\n\tvar tlsConf *tls.Config\n\tif t.tlsClientConf != nil {\n\t\ttlsConf = t.tlsClientConf.Clone()\n\t} else {\n\t\ttlsConf = &tls.Config{}\n\t}\n\ttlsConf.NextProtos = append(tlsConf.NextProtos, http3.NextProtoH3)\n\n\tif sni != \"\" {\n\t\ttlsConf.ServerName = sni\n\t}\n\n\tif len(certHashes) > 0 {\n\t\t// This is not insecure. We verify the certificate ourselves.\n\t\t// See https://www.w3.org/TR/webtransport/#certificate-hashes.\n\t\ttlsConf.InsecureSkipVerify = true\n\t\ttlsConf.VerifyPeerCertificate = func(rawCerts [][]byte, _ [][]*x509.Certificate) error {\n\t\t\treturn verifyRawCerts(rawCerts, certHashes)\n\t\t}\n\t}\n\tctx = quicreuse.WithAssociation(ctx, t)\n\tconn, err := t.connManager.DialQUIC(ctx, addr, tlsConf, t.allowWindowIncrease)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tdialer := webtransport.Dialer{\n\t\tDialAddr: func(_ context.Context, _ string, _ *tls.Config, _ *quic.Config) (*quic.Conn, error) {\n\t\t\treturn conn, nil\n\t\t},\n\t\tQUICConfig: t.connManager.ClientConfig().Clone(),\n\t}\n\trsp, sess, err := dialer.Dial(ctx, url, nil)\n\tif err != nil {\n\t\tconn.CloseWithError(1, \"\")\n\t\treturn nil, nil, err\n\t}\n\tif rsp.StatusCode < 200 || rsp.StatusCode > 299 {\n\t\tconn.CloseWithError(1, \"\")\n\t\treturn nil, nil, fmt.Errorf(\"invalid response status code: %d\", rsp.StatusCode)\n\t}\n\treturn sess, conn, err\n}\n\nfunc (t *transport) upgrade(ctx context.Context, sess *webtransport.Session, p peer.ID, certHashes []multihash.DecodedMultihash) (*connSecurityMultiaddrs, error) {\n\tlocal, err := toWebtransportMultiaddr(sess.LocalAddr())\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error determining local addr: %w\", err)\n\t}\n\tremote, err := toWebtransportMultiaddr(sess.RemoteAddr())\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error determining remote addr: %w\", err)\n\t}\n\n\tstr, err := sess.OpenStreamSync(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer str.Close()\n\n\t// Now run a Noise handshake (using early data) and get all the certificate hashes from the server.\n\t// We will verify that the certhashes we used to dial is a subset of the certhashes we received from the server.\n\tvar verified bool\n\tn, err := t.noise.WithSessionOptions(noise.EarlyData(newEarlyDataReceiver(func(b *pb.NoiseExtensions) error {\n\t\tdecodedCertHashes, err := decodeCertHashesFromProtobuf(b.WebtransportCerthashes)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor _, sent := range certHashes {\n\t\t\tvar found bool\n\t\t\tfor _, rcvd := range decodedCertHashes {\n\t\t\t\tif sent.Code == rcvd.Code && bytes.Equal(sent.Digest, rcvd.Digest) {\n\t\t\t\t\tfound = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !found {\n\t\t\t\treturn fmt.Errorf(\"missing cert hash: %v\", sent)\n\t\t\t}\n\t\t}\n\t\tverified = true\n\t\treturn nil\n\t}), nil))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create Noise transport: %w\", err)\n\t}\n\tc, err := n.SecureOutbound(ctx, webtransportStream{Stream: str, wsess: sess}, p)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer c.Close()\n\t// The Noise handshake _should_ guarantee that our verification callback is called.\n\t// Double-check just in case.\n\tif !verified {\n\t\treturn nil, errors.New(\"didn't verify\")\n\t}\n\treturn &connSecurityMultiaddrs{\n\t\tConnSecurity:   c,\n\t\tConnMultiaddrs: &connMultiaddrs{local: local, remote: remote},\n\t}, nil\n}\n\nfunc decodeCertHashesFromProtobuf(b [][]byte) ([]multihash.DecodedMultihash, error) {\n\thashes := make([]multihash.DecodedMultihash, 0, len(b))\n\tfor _, h := range b {\n\t\tdh, err := multihash.Decode(h)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to decode hash: %w\", err)\n\t\t}\n\t\thashes = append(hashes, *dh)\n\t}\n\treturn hashes, nil\n}\n\nfunc (t *transport) CanDial(addr ma.Multiaddr) bool {\n\tok, _ := IsWebtransportMultiaddr(addr)\n\treturn ok\n}\n\nfunc (t *transport) Listen(laddr ma.Multiaddr) (tpt.Listener, error) {\n\tisWebTransport, certhashCount := IsWebtransportMultiaddr(laddr)\n\tif !isWebTransport {\n\t\treturn nil, fmt.Errorf(\"cannot listen on non-WebTransport addr: %s\", laddr)\n\t}\n\tif certhashCount > 0 {\n\t\treturn nil, fmt.Errorf(\"cannot listen on a specific certhash non-WebTransport addr: %s\", laddr)\n\t}\n\tif t.staticTLSConf == nil {\n\t\tt.listenOnce.Do(func() {\n\t\t\tt.certManager, t.listenOnceErr = newCertManager(t.privKey, t.clock)\n\t\t\tt.hasCertManager.Store(true)\n\t\t})\n\t\tif t.listenOnceErr != nil {\n\t\t\treturn nil, t.listenOnceErr\n\t\t}\n\t} else {\n\t\treturn nil, errors.New(\"static TLS config not supported on WebTransport\")\n\t}\n\ttlsConf := t.staticTLSConf.Clone()\n\tif tlsConf == nil {\n\t\ttlsConf = &tls.Config{GetConfigForClient: func(*tls.ClientHelloInfo) (*tls.Config, error) {\n\t\t\treturn t.certManager.GetConfig(), nil\n\t\t}}\n\t}\n\ttlsConf.NextProtos = append(tlsConf.NextProtos, http3.NextProtoH3)\n\n\tln, err := t.connManager.ListenQUICAndAssociate(t, laddr, tlsConf, t.allowWindowIncrease)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn newListener(ln, t, t.staticTLSConf != nil)\n}\n\nfunc (t *transport) Protocols() []int {\n\treturn []int{ma.P_WEBTRANSPORT}\n}\n\nfunc (t *transport) Proxy() bool {\n\treturn false\n}\n\nfunc (t *transport) Close() error {\n\tt.listenOnce.Do(func() {})\n\tif t.certManager != nil {\n\t\treturn t.certManager.Close()\n\t}\n\treturn nil\n}\n\nfunc (t *transport) allowWindowIncrease(conn *quic.Conn, size uint64) bool {\n\tt.connMx.Lock()\n\tdefer t.connMx.Unlock()\n\n\tc, ok := t.conns[conn]\n\tif !ok {\n\t\treturn false\n\t}\n\treturn c.allowWindowIncrease(size)\n}\n\nfunc (t *transport) addConn(conn *quic.Conn, c *conn) {\n\tt.connMx.Lock()\n\tt.conns[conn] = c\n\tt.connMx.Unlock()\n}\n\nfunc (t *transport) removeConn(conn *quic.Conn) {\n\tt.connMx.Lock()\n\tdelete(t.conns, conn)\n\tt.connMx.Unlock()\n}\n\n// extractSNI returns what the SNI should be for the given maddr. If there is an\n// SNI component in the multiaddr, then it will be returned and\n// foundSniComponent will be true. If there's no SNI component, but there is a\n// DNS-like component, then that will be returned for the sni and\n// foundSniComponent will be false (since we didn't find an actual sni component).\nfunc extractSNI(maddr ma.Multiaddr) (sni string, foundSniComponent bool) {\n\tma.ForEach(maddr, func(c ma.Component) bool {\n\t\tswitch c.Protocol().Code {\n\t\tcase ma.P_SNI:\n\t\t\tsni = c.Value()\n\t\t\tfoundSniComponent = true\n\t\t\treturn false\n\t\tcase ma.P_DNS, ma.P_DNS4, ma.P_DNS6, ma.P_DNSADDR:\n\t\t\tsni = c.Value()\n\t\t\t// Keep going in case we find an `sni` component\n\t\t\treturn true\n\t\t}\n\t\treturn true\n\t})\n\treturn sni, foundSniComponent\n}\n\n// Resolve implements transport.Resolver\nfunc (t *transport) Resolve(_ context.Context, maddr ma.Multiaddr) ([]ma.Multiaddr, error) {\n\tsni, foundSniComponent := extractSNI(maddr)\n\n\tif foundSniComponent || sni == \"\" {\n\t\t// The multiaddr already had an sni field, we can keep using it. Or we don't have any sni like thing\n\t\treturn []ma.Multiaddr{maddr}, nil\n\t}\n\n\tbeforeQuicMA, afterIncludingQuicMA := ma.SplitFunc(maddr, func(c ma.Component) bool {\n\t\treturn c.Protocol().Code == ma.P_QUIC_V1\n\t})\n\tif len(afterIncludingQuicMA) == 0 {\n\t\treturn nil, fmt.Errorf(\"no quic component found in %s\", maddr)\n\t}\n\tquicComponent, afterQuicMA := ma.SplitFirst(afterIncludingQuicMA)\n\tif quicComponent == nil {\n\t\t// Should not happen since we split on P_QUIC_V1 already\n\t\treturn nil, fmt.Errorf(\"no quic component found in %s\", maddr)\n\t}\n\tsniComponent, err := ma.NewComponent(ma.ProtocolWithCode(ma.P_SNI).Name, sni)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tresult := beforeQuicMA.AppendComponent(quicComponent, sniComponent)\n\tresult = append(result, afterQuicMA...)\n\treturn []ma.Multiaddr{result}, nil\n}\n\n// AddCertHashes adds the current certificate hashes to a multiaddress.\n// If called before Listen, it's a no-op.\nfunc (t *transport) AddCertHashes(m ma.Multiaddr) (ma.Multiaddr, bool) {\n\tif !t.hasCertManager.Load() {\n\t\treturn m, false\n\t}\n\treturn m.Encapsulate(t.certManager.AddrComponent()), true\n}\n"
  },
  {
    "path": "p2p/transport/webtransport/transport_test.go",
    "content": "package libp2pwebtransport_test\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/rand\"\n\t\"crypto/sha256\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"runtime\"\n\t\"slices\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"testing/quick\"\n\t\"time\"\n\n\tic \"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\tmocknetwork \"github.com/libp2p/go-libp2p/core/network/mocks\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/test\"\n\ttpt \"github.com/libp2p/go-libp2p/core/transport\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/quicreuse\"\n\tlibp2pwebtransport \"github.com/libp2p/go-libp2p/p2p/transport/webtransport\"\n\n\t\"github.com/benbjohnson/clock\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n\t\"github.com/multiformats/go-multibase\"\n\t\"github.com/multiformats/go-multihash\"\n\t\"github.com/quic-go/quic-go\"\n\t\"github.com/quic-go/quic-go/http3\"\n\tquicproxy \"github.com/quic-go/quic-go/integrationtests/tools/proxy\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/mock/gomock\"\n)\n\nconst clockSkewAllowance = time.Hour\nconst certValidity = 14 * 24 * time.Hour\n\nfunc newIdentity(t *testing.T) (peer.ID, ic.PrivKey) {\n\tkey, _, err := ic.GenerateEd25519Key(rand.Reader)\n\trequire.NoError(t, err)\n\tid, err := peer.IDFromPrivateKey(key)\n\trequire.NoError(t, err)\n\treturn id, key\n}\n\nfunc randomMultihash(t *testing.T) string {\n\tt.Helper()\n\tb := make([]byte, 16)\n\trand.Read(b)\n\th, err := multihash.Encode(b, multihash.KECCAK_224)\n\trequire.NoError(t, err)\n\ts, err := multibase.Encode(multibase.Base32hex, h)\n\trequire.NoError(t, err)\n\treturn s\n}\n\nfunc extractCertHashes(addr ma.Multiaddr) []string {\n\tvar certHashesStr []string\n\tma.ForEach(addr, func(c ma.Component) bool {\n\t\tif c.Protocol().Code == ma.P_CERTHASH {\n\t\t\tcertHashesStr = append(certHashesStr, c.Value())\n\t\t}\n\t\treturn true\n\t})\n\treturn certHashesStr\n}\n\nfunc stripCertHashes(addr ma.Multiaddr) ma.Multiaddr {\n\tfor {\n\t\t_, err := addr.ValueForProtocol(ma.P_CERTHASH)\n\t\tif err != nil {\n\t\t\treturn addr\n\t\t}\n\t\taddr, _ = ma.SplitLast(addr)\n\t}\n}\n\n// create a /certhash multiaddr component using the SHA256 of foobar\nfunc getCerthashComponent(t *testing.T, b []byte) *ma.Component {\n\tt.Helper()\n\th := sha256.Sum256(b)\n\tmh, err := multihash.Encode(h[:], multihash.SHA2_256)\n\trequire.NoError(t, err)\n\tcertStr, err := multibase.Encode(multibase.Base58BTC, mh)\n\trequire.NoError(t, err)\n\tha, err := ma.NewComponent(ma.ProtocolWithCode(ma.P_CERTHASH).Name, certStr)\n\trequire.NoError(t, err)\n\treturn ha\n}\n\nfunc newConnManager(t *testing.T, opts ...quicreuse.Option) *quicreuse.ConnManager {\n\tt.Helper()\n\tcm, err := quicreuse.NewConnManager(quic.StatelessResetKey{}, quic.TokenGeneratorKey{}, opts...)\n\trequire.NoError(t, err)\n\tt.Cleanup(func() { cm.Close() })\n\treturn cm\n}\n\nfunc TestTransport(t *testing.T) {\n\tserverID, serverKey := newIdentity(t)\n\ttr, err := libp2pwebtransport.New(serverKey, nil, newConnManager(t), nil, nil)\n\trequire.NoError(t, err)\n\tdefer tr.(io.Closer).Close()\n\tln, err := tr.Listen(ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1/webtransport\"))\n\trequire.NoError(t, err)\n\tdefer ln.Close()\n\n\taddrChan := make(chan ma.Multiaddr)\n\tgo func() {\n\t\t_, clientKey := newIdentity(t)\n\t\ttr2, err := libp2pwebtransport.New(clientKey, nil, newConnManager(t), nil, nil)\n\t\trequire.NoError(t, err)\n\t\tdefer tr2.(io.Closer).Close()\n\n\t\tconn, err := tr2.Dial(context.Background(), ln.Multiaddr(), serverID)\n\t\trequire.NoError(t, err)\n\t\tstr, err := conn.OpenStream(context.Background())\n\t\trequire.NoError(t, err)\n\t\t_, err = str.Write([]byte(\"foobar\"))\n\t\trequire.NoError(t, err)\n\t\trequire.NoError(t, str.Close())\n\n\t\t// check RemoteMultiaddr\n\t\t_, addr, err := manet.DialArgs(ln.Multiaddr())\n\t\trequire.NoError(t, err)\n\t\t_, port, err := net.SplitHostPort(addr)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, fmt.Sprintf(\"/ip4/127.0.0.1/udp/%s/quic-v1/webtransport\", port), conn.RemoteMultiaddr().String())\n\t\taddrChan <- conn.RemoteMultiaddr()\n\t}()\n\n\tconn, err := ln.Accept()\n\trequire.NoError(t, err)\n\trequire.False(t, conn.IsClosed())\n\tstr, err := conn.AcceptStream()\n\trequire.NoError(t, err)\n\tdata, err := io.ReadAll(str)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"foobar\", string(data))\n\trequire.Equal(t, (<-addrChan).String(), conn.LocalMultiaddr().String())\n\trequire.NoError(t, conn.Close())\n\trequire.True(t, conn.IsClosed())\n}\n\nfunc TestHashVerification(t *testing.T) {\n\tserverID, serverKey := newIdentity(t)\n\ttr, err := libp2pwebtransport.New(serverKey, nil, newConnManager(t), nil, &network.NullResourceManager{})\n\trequire.NoError(t, err)\n\tdefer tr.(io.Closer).Close()\n\tln, err := tr.Listen(ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1/webtransport\"))\n\trequire.NoError(t, err)\n\tdone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(done)\n\t\t_, err := ln.Accept()\n\t\trequire.Error(t, err)\n\t}()\n\n\t_, clientKey := newIdentity(t)\n\ttr2, err := libp2pwebtransport.New(clientKey, nil, newConnManager(t), nil, &network.NullResourceManager{})\n\trequire.NoError(t, err)\n\tdefer tr2.(io.Closer).Close()\n\n\tfoobarHash := getCerthashComponent(t, []byte(\"foobar\"))\n\n\tt.Run(\"fails using only a wrong hash\", func(t *testing.T) {\n\t\t// replace the certificate hash in the multiaddr with a fake hash\n\t\taddr := stripCertHashes(ln.Multiaddr()).Encapsulate(foobarHash)\n\t\t_, err := tr2.Dial(context.Background(), addr, serverID)\n\t\trequire.Error(t, err)\n\t\tvar trErr *quic.TransportError\n\t\trequire.ErrorAs(t, err, &trErr)\n\t\trequire.Equal(t, quic.TransportErrorCode(0x12a), trErr.ErrorCode)\n\t\tvar errMismatchHash libp2pwebtransport.ErrCertHashMismatch\n\t\trequire.ErrorAs(t, err, &errMismatchHash)\n\n\t\te := sha256.Sum256([]byte(\"foobar\"))\n\t\trequire.EqualValues(t, e[:], errMismatchHash.Actual[0])\n\t})\n\n\tt.Run(\"fails when adding a wrong hash\", func(t *testing.T) {\n\t\t_, err := tr2.Dial(context.Background(), ln.Multiaddr().Encapsulate(foobarHash), serverID)\n\t\trequire.Error(t, err)\n\t})\n\n\trequire.NoError(t, ln.Close())\n\t<-done\n}\n\nfunc TestCanDial(t *testing.T) {\n\tvalid := []ma.Multiaddr{\n\t\tma.StringCast(\"/ip4/127.0.0.1/udp/1234/quic-v1/webtransport/certhash/\" + randomMultihash(t)),\n\t\tma.StringCast(\"/ip6/b16b:8255:efc6:9cd5:1a54:ee86:2d7a:c2e6/udp/1234/quic-v1/webtransport/certhash/\" + randomMultihash(t)),\n\t\tma.StringCast(fmt.Sprintf(\"/ip4/127.0.0.1/udp/1234/quic-v1/webtransport/certhash/%s/certhash/%s/certhash/%s\", randomMultihash(t), randomMultihash(t), randomMultihash(t))),\n\t\tma.StringCast(\"/ip4/127.0.0.1/udp/1234/quic-v1/webtransport\"), // no certificate hash\n\t}\n\n\tinvalid := []ma.Multiaddr{\n\t\tma.StringCast(\"/ip4/127.0.0.1/udp/1234\"),              // missing webtransport\n\t\tma.StringCast(\"/ip4/127.0.0.1/udp/1234/webtransport\"), // missing quic\n\t\tma.StringCast(\"/ip4/127.0.0.1/tcp/1234/webtransport\"), // WebTransport over TCP? Is this a joke?\n\t}\n\n\t_, key := newIdentity(t)\n\ttr, err := libp2pwebtransport.New(key, nil, newConnManager(t), nil, &network.NullResourceManager{})\n\trequire.NoError(t, err)\n\tdefer tr.(io.Closer).Close()\n\n\tfor _, addr := range valid {\n\t\trequire.Truef(t, tr.CanDial(addr), \"expected to be able to dial %s\", addr)\n\t}\n\tfor _, addr := range invalid {\n\t\trequire.Falsef(t, tr.CanDial(addr), \"expected to not be able to dial %s\", addr)\n\t}\n}\n\nfunc TestListenAddrValidity(t *testing.T) {\n\tvalid := []ma.Multiaddr{\n\t\tma.StringCast(\"/ip6/::/udp/0/quic-v1/webtransport/\"),\n\t}\n\n\tinvalid := []ma.Multiaddr{\n\t\tma.StringCast(\"/ip4/127.0.0.1/udp/0\"),                                                     // missing webtransport\n\t\tma.StringCast(\"/ip4/127.0.0.1/udp/0/webtransport\"),                                        // missing quic\n\t\tma.StringCast(\"/ip4/127.0.0.1/tcp/0/webtransport\"),                                        // WebTransport over TCP? Is this a joke?\n\t\tma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1/webtransport/certhash/\" + randomMultihash(t)), // We can't listen on a specific certhash\n\t}\n\n\t_, key := newIdentity(t)\n\ttr, err := libp2pwebtransport.New(key, nil, newConnManager(t), nil, &network.NullResourceManager{})\n\trequire.NoError(t, err)\n\tdefer tr.(io.Closer).Close()\n\n\tfor _, addr := range valid {\n\t\tln, err := tr.Listen(addr)\n\t\trequire.NoErrorf(t, err, \"expected to be able to listen on %s\", addr)\n\t\tln.Close()\n\t}\n\tfor _, addr := range invalid {\n\t\t_, err := tr.Listen(addr)\n\t\trequire.Errorf(t, err, \"expected to not be able to listen on %s\", addr)\n\t}\n}\n\nfunc TestListenerAddrs(t *testing.T) {\n\t_, key := newIdentity(t)\n\ttr, err := libp2pwebtransport.New(key, nil, newConnManager(t), nil, &network.NullResourceManager{})\n\trequire.NoError(t, err)\n\tdefer tr.(io.Closer).Close()\n\n\tln1, err := tr.Listen(ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1/webtransport\"))\n\trequire.NoError(t, err)\n\tln2, err := tr.Listen(ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1/webtransport\"))\n\trequire.NoError(t, err)\n\thashes1 := extractCertHashes(ln1.Multiaddr())\n\trequire.Len(t, hashes1, 2)\n\thashes2 := extractCertHashes(ln2.Multiaddr())\n\trequire.Equal(t, hashes1, hashes2)\n}\n\nfunc TestResourceManagerDialing(t *testing.T) {\n\tctrl := gomock.NewController(t)\n\tdefer ctrl.Finish()\n\trcmgr := mocknetwork.NewMockResourceManager(ctrl)\n\n\taddr := ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1/webtransport\")\n\tp := peer.ID(\"foobar\")\n\n\t_, key := newIdentity(t)\n\ttr, err := libp2pwebtransport.New(key, nil, newConnManager(t), nil, rcmgr)\n\trequire.NoError(t, err)\n\tdefer tr.(io.Closer).Close()\n\tl, err := tr.Listen(addr)\n\trequire.NoError(t, err)\n\n\taddr = l.Multiaddr()\n\n\tscope := mocknetwork.NewMockConnManagementScope(ctrl)\n\trcmgr.EXPECT().OpenConnection(network.DirOutbound, false, addr).Return(scope, nil)\n\tscope.EXPECT().SetPeer(p).Return(errors.New(\"denied\"))\n\tscope.EXPECT().Done()\n\n\t_, err = tr.Dial(context.Background(), addr, p)\n\trequire.EqualError(t, err, \"denied\")\n}\n\nfunc TestResourceManagerListening(t *testing.T) {\n\tclientID, key := newIdentity(t)\n\tcl, err := libp2pwebtransport.New(key, nil, newConnManager(t), nil, &network.NullResourceManager{})\n\trequire.NoError(t, err)\n\tdefer cl.(io.Closer).Close()\n\n\tt.Run(\"blocking the connection\", func(t *testing.T) {\n\t\tserverID, key := newIdentity(t)\n\t\tctrl := gomock.NewController(t)\n\t\tdefer ctrl.Finish()\n\t\trcmgr := mocknetwork.NewMockResourceManager(ctrl)\n\t\ttr, err := libp2pwebtransport.New(key, nil, newConnManager(t), nil, rcmgr)\n\t\trequire.NoError(t, err)\n\t\tln, err := tr.Listen(ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1/webtransport\"))\n\t\trequire.NoError(t, err)\n\t\tdefer ln.Close()\n\n\t\trcmgr.EXPECT().OpenConnection(network.DirInbound, false, gomock.Any()).DoAndReturn(func(_ network.Direction, _ bool, addr ma.Multiaddr) (network.ConnManagementScope, error) {\n\t\t\t_, err := addr.ValueForProtocol(ma.P_WEBTRANSPORT)\n\t\t\trequire.NoError(t, err, \"expected a WebTransport multiaddr\")\n\t\t\t_, addrStr, err := manet.DialArgs(addr)\n\t\t\trequire.NoError(t, err)\n\t\t\thost, _, err := net.SplitHostPort(addrStr)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, \"127.0.0.1\", host)\n\t\t\treturn nil, errors.New(\"denied\")\n\t\t})\n\n\t\t_, err = cl.Dial(context.Background(), ln.Multiaddr(), serverID)\n\t\trequire.EqualError(t, err, \"received status 503\")\n\t})\n\n\tt.Run(\"blocking the peer\", func(t *testing.T) {\n\t\tserverID, key := newIdentity(t)\n\t\tctrl := gomock.NewController(t)\n\t\tdefer ctrl.Finish()\n\t\trcmgr := mocknetwork.NewMockResourceManager(ctrl)\n\t\ttr, err := libp2pwebtransport.New(key, nil, newConnManager(t), nil, rcmgr)\n\t\trequire.NoError(t, err)\n\t\tln, err := tr.Listen(ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1/webtransport\"))\n\t\trequire.NoError(t, err)\n\t\tdefer ln.Close()\n\n\t\tserverDone := make(chan struct{})\n\t\tscope := mocknetwork.NewMockConnManagementScope(ctrl)\n\t\trcmgr.EXPECT().OpenConnection(network.DirInbound, false, gomock.Any()).Return(scope, nil)\n\t\tscope.EXPECT().SetPeer(clientID).Return(errors.New(\"denied\"))\n\t\tscope.EXPECT().Done().Do(func() { close(serverDone) })\n\n\t\t// The handshake will complete, but the server will immediately close the connection.\n\t\tconn, err := cl.Dial(context.Background(), ln.Multiaddr(), serverID)\n\t\trequire.NoError(t, err)\n\t\tdefer conn.Close()\n\t\tclientDone := make(chan struct{})\n\t\tgo func() {\n\t\t\tdefer close(clientDone)\n\t\t\t_, err = conn.AcceptStream()\n\t\t\trequire.Error(t, err)\n\t\t}()\n\t\tselect {\n\t\tcase <-clientDone:\n\t\tcase <-time.After(5 * time.Second):\n\t\t\tt.Fatal(\"timeout\")\n\t\t}\n\t\tselect {\n\t\tcase <-serverDone:\n\t\tcase <-time.After(5 * time.Second):\n\t\t\tt.Fatal(\"timeout\")\n\t\t}\n\t})\n}\n\n// TODO: unify somehow. We do the same in libp2pquic.\n//go:generate sh -c \"go run go.uber.org/mock/mockgen -package libp2pwebtransport_test -destination mock_connection_gater_test.go github.com/libp2p/go-libp2p/core/connmgr ConnectionGater && go run golang.org/x/tools/cmd/goimports -w mock_connection_gater_test.go\"\n\nfunc TestConnectionGaterDialing(t *testing.T) {\n\tctrl := gomock.NewController(t)\n\tdefer ctrl.Finish()\n\tconnGater := NewMockConnectionGater(ctrl)\n\n\tserverID, serverKey := newIdentity(t)\n\ttr, err := libp2pwebtransport.New(serverKey, nil, newConnManager(t), nil, &network.NullResourceManager{})\n\trequire.NoError(t, err)\n\tdefer tr.(io.Closer).Close()\n\tln, err := tr.Listen(ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1/webtransport\"))\n\trequire.NoError(t, err)\n\tdefer ln.Close()\n\n\tconnGater.EXPECT().InterceptSecured(network.DirOutbound, serverID, gomock.Any()).Do(func(_ network.Direction, _ peer.ID, addrs network.ConnMultiaddrs) {\n\t\trequire.Equal(t, stripCertHashes(ln.Multiaddr()).String(), addrs.RemoteMultiaddr().String())\n\t})\n\t_, key := newIdentity(t)\n\tcl, err := libp2pwebtransport.New(key, nil, newConnManager(t), connGater, &network.NullResourceManager{})\n\trequire.NoError(t, err)\n\tdefer cl.(io.Closer).Close()\n\t_, err = cl.Dial(context.Background(), ln.Multiaddr(), serverID)\n\trequire.EqualError(t, err, \"secured connection gated\")\n}\n\nfunc TestConnectionGaterInterceptAccept(t *testing.T) {\n\tctrl := gomock.NewController(t)\n\tdefer ctrl.Finish()\n\tconnGater := NewMockConnectionGater(ctrl)\n\n\tserverID, serverKey := newIdentity(t)\n\ttr, err := libp2pwebtransport.New(serverKey, nil, newConnManager(t), connGater, &network.NullResourceManager{})\n\trequire.NoError(t, err)\n\tdefer tr.(io.Closer).Close()\n\tln, err := tr.Listen(ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1/webtransport\"))\n\trequire.NoError(t, err)\n\tdefer ln.Close()\n\n\tconnGater.EXPECT().InterceptAccept(gomock.Any()).Do(func(addrs network.ConnMultiaddrs) {\n\t\trequire.Equal(t, stripCertHashes(ln.Multiaddr()).String(), addrs.LocalMultiaddr().String())\n\t\trequire.NotEqual(t, stripCertHashes(ln.Multiaddr()).String(), addrs.RemoteMultiaddr().String())\n\t})\n\n\t_, key := newIdentity(t)\n\tcl, err := libp2pwebtransport.New(key, nil, newConnManager(t), nil, &network.NullResourceManager{})\n\trequire.NoError(t, err)\n\tdefer cl.(io.Closer).Close()\n\t_, err = cl.Dial(context.Background(), ln.Multiaddr(), serverID)\n\trequire.EqualError(t, err, \"received status 403\")\n}\n\nfunc TestConnectionGaterInterceptSecured(t *testing.T) {\n\tctrl := gomock.NewController(t)\n\tdefer ctrl.Finish()\n\tconnGater := NewMockConnectionGater(ctrl)\n\n\tserverID, serverKey := newIdentity(t)\n\ttr, err := libp2pwebtransport.New(serverKey, nil, newConnManager(t), connGater, &network.NullResourceManager{})\n\trequire.NoError(t, err)\n\tdefer tr.(io.Closer).Close()\n\tln, err := tr.Listen(ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1/webtransport\"))\n\trequire.NoError(t, err)\n\tdefer ln.Close()\n\n\tclientID, key := newIdentity(t)\n\tcl, err := libp2pwebtransport.New(key, nil, newConnManager(t), nil, &network.NullResourceManager{})\n\trequire.NoError(t, err)\n\tdefer cl.(io.Closer).Close()\n\n\tconnGater.EXPECT().InterceptAccept(gomock.Any()).Return(true)\n\tconnGater.EXPECT().InterceptSecured(network.DirInbound, clientID, gomock.Any()).Do(func(_ network.Direction, _ peer.ID, addrs network.ConnMultiaddrs) {\n\t\trequire.Equal(t, stripCertHashes(ln.Multiaddr()).String(), addrs.LocalMultiaddr().String())\n\t\trequire.NotEqual(t, stripCertHashes(ln.Multiaddr()).String(), addrs.RemoteMultiaddr().String())\n\t})\n\t// The handshake will complete, but the server will immediately close the connection.\n\tconn, err := cl.Dial(context.Background(), ln.Multiaddr(), serverID)\n\trequire.NoError(t, err)\n\tdefer conn.Close()\n\tdone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(done)\n\t\t_, err = conn.AcceptStream()\n\t\trequire.Error(t, err)\n\t}()\n\tselect {\n\tcase <-done:\n\tcase <-time.After(5 * time.Second):\n\t\tt.Fatal(\"timeout\")\n\t}\n}\n\nfunc TestAcceptQueueFilledUp(t *testing.T) {\n\tserverID, serverKey := newIdentity(t)\n\ttr, err := libp2pwebtransport.New(serverKey, nil, newConnManager(t), nil, &network.NullResourceManager{})\n\trequire.NoError(t, err)\n\tdefer tr.(io.Closer).Close()\n\tln, err := tr.Listen(ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1/webtransport\"))\n\trequire.NoError(t, err)\n\tdefer ln.Close()\n\n\tnewConn := func() (tpt.CapableConn, error) {\n\t\tt.Helper()\n\t\t_, key := newIdentity(t)\n\t\tcl, err := libp2pwebtransport.New(key, nil, newConnManager(t), nil, &network.NullResourceManager{})\n\t\trequire.NoError(t, err)\n\t\tdefer cl.(io.Closer).Close()\n\t\treturn cl.Dial(context.Background(), ln.Multiaddr(), serverID)\n\t}\n\n\tconst num = 16 + 1 // one more than the accept queue capacity\n\t// Dial one more connection than the accept queue can hold.\n\terrChan := make(chan error, num)\n\tfor range num {\n\t\tgo func() {\n\t\t\tconn, err := newConn()\n\t\t\tif err != nil {\n\t\t\t\terrChan <- err\n\t\t\t\treturn\n\t\t\t}\n\t\t\t_, err = conn.AcceptStream()\n\t\t\terrChan <- err\n\t\t}()\n\t}\n\n\t// Since the handshakes complete asynchronously, we won't know _which_ one is rejected,\n\t// so the only thing we can test for is that exactly one connection attempt is rejected.\n\tselect {\n\tcase <-errChan:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"expected one connection to be rejected\")\n\t}\n\tselect {\n\tcase <-errChan:\n\t\tt.Fatal(\"only expected one connection to be rejected\")\n\tcase <-time.After(100 * time.Millisecond):\n\t}\n\n\t// test shutdown\n\trequire.NoError(t, ln.Close())\n\tvar count int\n\ttimer := time.NewTimer(time.Second)\n\tdefer timer.Stop()\n\tfor range 16 {\n\t\tselect {\n\t\tcase <-errChan:\n\t\t\tcount++\n\t\t\tif count == 16 {\n\t\t\t\treturn\n\t\t\t}\n\t\tcase <-timer.C:\n\t\t\tt.Fatal(\"shutdown failed\")\n\t\t}\n\t}\n}\n\ntype reportingRcmgr struct {\n\tnetwork.NullResourceManager\n\treport chan<- int\n}\n\nfunc (m *reportingRcmgr) OpenConnection(_ network.Direction, _ bool, _ ma.Multiaddr) (network.ConnManagementScope, error) {\n\treturn &reportingScope{report: m.report}, nil\n}\n\ntype reportingScope struct {\n\tnetwork.NullScope\n\treport chan<- int\n}\n\nfunc (s *reportingScope) ReserveMemory(size int, _ uint8) error {\n\ts.report <- size\n\treturn nil\n}\n\nfunc newUDPConnLocalhost(t testing.TB) *net.UDPConn {\n\tt.Helper()\n\tconn, err := net.ListenUDP(\"udp\", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})\n\trequire.NoError(t, err)\n\tt.Cleanup(func() { conn.Close() })\n\treturn conn\n}\n\nfunc TestFlowControlWindowIncrease(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"this test is flaky on Windows\")\n\t}\n\n\trtt := 10 * time.Millisecond\n\ttimeout := 5 * time.Second\n\n\tif os.Getenv(\"CI\") != \"\" {\n\t\trtt = 40 * time.Millisecond\n\t\ttimeout = 15 * time.Second\n\t}\n\n\tserverID, serverKey := newIdentity(t)\n\tserverWindowIncreases := make(chan int, 100)\n\tserverRcmgr := &reportingRcmgr{report: serverWindowIncreases}\n\ttr, err := libp2pwebtransport.New(serverKey, nil, newConnManager(t), nil, serverRcmgr)\n\trequire.NoError(t, err)\n\tdefer tr.(io.Closer).Close()\n\tln, err := tr.Listen(ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1/webtransport\"))\n\trequire.NoError(t, err)\n\tdefer ln.Close()\n\n\tgo func() {\n\t\tconn, err := ln.Accept()\n\t\trequire.NoError(t, err)\n\t\tstr, err := conn.AcceptStream()\n\t\trequire.NoError(t, err)\n\t\t_, err = io.CopyBuffer(str, str, make([]byte, 2<<10))\n\t\trequire.NoError(t, err)\n\t\tstr.CloseWrite()\n\t}()\n\n\tproxy := quicproxy.Proxy{\n\t\tConn:        newUDPConnLocalhost(t),\n\t\tServerAddr:  ln.Addr().(*net.UDPAddr),\n\t\tDelayPacket: func(quicproxy.Direction, net.Addr, net.Addr, []byte) time.Duration { return rtt / 2 },\n\t}\n\trequire.NoError(t, proxy.Start())\n\tdefer proxy.Close()\n\n\t_, clientKey := newIdentity(t)\n\tclientWindowIncreases := make(chan int, 100)\n\tclientRcmgr := &reportingRcmgr{report: clientWindowIncreases}\n\ttr2, err := libp2pwebtransport.New(clientKey, nil, newConnManager(t), nil, clientRcmgr)\n\trequire.NoError(t, err)\n\tdefer tr2.(io.Closer).Close()\n\n\tvar addr ma.Multiaddr\n\tfor _, comp := range ln.Multiaddr() {\n\t\tif _, err := comp.ValueForProtocol(ma.P_UDP); err == nil {\n\t\t\taddr = addr.Encapsulate(ma.StringCast(fmt.Sprintf(\"/udp/%d\", proxy.LocalAddr().(*net.UDPAddr).Port)))\n\t\t\tcontinue\n\t\t}\n\t\taddr = append(addr, comp)\n\t}\n\n\tconn, err := tr2.Dial(context.Background(), addr, serverID)\n\trequire.NoError(t, err)\n\tstr, err := conn.OpenStream(context.Background())\n\trequire.NoError(t, err)\n\tvar increasesDone atomic.Bool\n\tgo func() {\n\t\tfor {\n\t\t\t_, err := str.Write(bytes.Repeat([]byte{0x42}, 1<<10))\n\t\t\trequire.NoError(t, err)\n\t\t\tif increasesDone.Load() {\n\t\t\t\tstr.CloseWrite()\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\tdone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(done)\n\t\t_, err := io.ReadAll(str)\n\t\trequire.NoError(t, err)\n\t}()\n\n\tvar numServerIncreases, numClientIncreases int\n\ttimer := time.NewTimer(timeout)\n\tdefer timer.Stop()\n\tfor {\n\t\tselect {\n\t\tcase <-serverWindowIncreases:\n\t\t\tnumServerIncreases++\n\t\tcase <-clientWindowIncreases:\n\t\t\tnumClientIncreases++\n\t\tcase <-timer.C:\n\t\t\tt.Fatalf(\"didn't receive enough window increases (client: %d, server: %d)\", numClientIncreases, numServerIncreases)\n\t\t}\n\t\tif numClientIncreases >= 1 && numServerIncreases >= 1 {\n\t\t\tincreasesDone.Store(true)\n\t\t\tbreak\n\t\t}\n\t}\n\n\tselect {\n\tcase <-done:\n\tcase <-time.After(timeout):\n\t\tt.Fatal(\"timeout\")\n\t}\n}\n\nvar errTimeout = errors.New(\"timeout\")\n\nfunc serverSendsBackValidCert(t *testing.T, timeSinceUnixEpoch time.Duration, keySeed int64, randomClientSkew time.Duration) error {\n\tif timeSinceUnixEpoch < 0 {\n\t\ttimeSinceUnixEpoch = -timeSinceUnixEpoch\n\t}\n\n\t// Bound this to 100 years\n\ttimeSinceUnixEpoch = timeSinceUnixEpoch % (time.Hour * 24 * 365 * 100)\n\t// Start a bit further in the future to avoid edge cases around epoch\n\ttimeSinceUnixEpoch += time.Hour * 24 * 365\n\tstart := time.UnixMilli(timeSinceUnixEpoch.Milliseconds())\n\n\trandomClientSkew = randomClientSkew % clockSkewAllowance\n\n\tcl := clock.NewMock()\n\tcl.Set(start)\n\n\tpriv, _, err := test.SeededTestKeyPair(ic.Ed25519, 256, keySeed)\n\trequire.NoError(t, err)\n\ttr, err := libp2pwebtransport.New(priv, nil, newConnManager(t), nil, &network.NullResourceManager{}, libp2pwebtransport.WithClock(cl))\n\trequire.NoError(t, err)\n\tl, err := tr.Listen(ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1/webtransport\"))\n\trequire.NoError(t, err)\n\tdefer l.Close()\n\n\tconn, err := quic.DialAddr(context.Background(), l.Addr().String(), &tls.Config{\n\t\tNextProtos:         []string{http3.NextProtoH3},\n\t\tInsecureSkipVerify: true,\n\t\tVerifyPeerCertificate: func(rawCerts [][]byte, _ [][]*x509.Certificate) error {\n\t\t\tfor _, c := range rawCerts {\n\t\t\t\tcert, err := x509.ParseCertificate(c)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\tfor _, clientSkew := range []time.Duration{randomClientSkew, -clockSkewAllowance, clockSkewAllowance} {\n\t\t\t\t\tclientTime := cl.Now().Add(clientSkew)\n\t\t\t\t\tif clientTime.After(cert.NotAfter) || clientTime.Before(cert.NotBefore) {\n\t\t\t\t\t\treturn fmt.Errorf(\"Times are not valid: server_now=%v client_now=%v certstart=%v certend=%v\", cl.Now().UTC(), clientTime.UTC(), cert.NotBefore.UTC(), cert.NotAfter.UTC())\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}, &quic.Config{MaxIdleTimeout: time.Second})\n\n\tif err != nil {\n\t\tif _, ok := err.(*quic.IdleTimeoutError); ok {\n\t\t\treturn errTimeout\n\t\t}\n\t\treturn err\n\t}\n\tdefer conn.CloseWithError(0, \"\")\n\n\treturn nil\n}\n\nfunc TestServerSendsBackValidCert(t *testing.T) {\n\tvar maxTimeoutErrors = 10\n\trequire.NoError(t, quick.Check(func(timeSinceUnixEpoch time.Duration, keySeed int64, randomClientSkew time.Duration) bool {\n\t\terr := serverSendsBackValidCert(t, timeSinceUnixEpoch, keySeed, randomClientSkew)\n\t\tif err == errTimeout {\n\t\t\tmaxTimeoutErrors -= 1\n\t\t\tif maxTimeoutErrors <= 0 {\n\t\t\t\tfmt.Println(\"Too many timeout errors\")\n\t\t\t\treturn false\n\t\t\t}\n\t\t\t// Sporadic timeout errors on macOS\n\t\t\treturn true\n\t\t} else if err != nil {\n\t\t\tfmt.Println(\"Err:\", err)\n\t\t\treturn false\n\t\t}\n\n\t\treturn true\n\t}, nil))\n}\n\nfunc TestServerRotatesCertCorrectly(t *testing.T) {\n\trequire.NoError(t, quick.Check(func(timeSinceUnixEpoch time.Duration, keySeed int64) bool {\n\t\tif timeSinceUnixEpoch < 0 {\n\t\t\ttimeSinceUnixEpoch = -timeSinceUnixEpoch\n\t\t}\n\n\t\t// Bound this to 100 years\n\t\ttimeSinceUnixEpoch = timeSinceUnixEpoch % (time.Hour * 24 * 365 * 100)\n\t\t// Start a bit further in the future to avoid edge cases around epoch\n\t\ttimeSinceUnixEpoch += time.Hour * 24 * 365\n\t\tstart := time.UnixMilli(timeSinceUnixEpoch.Milliseconds())\n\n\t\tcl := clock.NewMock()\n\t\tcl.Set(start)\n\n\t\tpriv, _, err := test.SeededTestKeyPair(ic.Ed25519, 256, keySeed)\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\t\ttr, err := libp2pwebtransport.New(priv, nil, newConnManager(t), nil, &network.NullResourceManager{}, libp2pwebtransport.WithClock(cl))\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\n\t\tl, err := tr.Listen(ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1/webtransport\"))\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\t\tcerthashes := extractCertHashes(l.Multiaddr())\n\t\tl.Close()\n\n\t\t// These two certificates together are valid for at most certValidity - (4*clockSkewAllowance)\n\t\tcl.Add(certValidity - (4 * clockSkewAllowance) - time.Second)\n\t\ttr, err = libp2pwebtransport.New(priv, nil, newConnManager(t), nil, &network.NullResourceManager{}, libp2pwebtransport.WithClock(cl))\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\n\t\tl, err = tr.Listen(ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1/webtransport\"))\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\t\tdefer l.Close()\n\n\t\tvar found bool\n\t\tma.ForEach(l.Multiaddr(), func(c ma.Component) bool {\n\t\t\tif c.Protocol().Code == ma.P_CERTHASH {\n\t\t\t\tif slices.Contains(certhashes, c.Value()) {\n\t\t\t\t\tfound = true\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true\n\t\t})\n\n\t\treturn found\n\n\t}, nil))\n}\n\nfunc TestServerRotatesCertCorrectlyAfterSteps(t *testing.T) {\n\tcl := clock.NewMock()\n\t// Move one year ahead to avoid edge cases around epoch\n\tcl.Add(time.Hour * 24 * 365)\n\n\tpriv, _, err := test.RandTestKeyPair(ic.Ed25519, 256)\n\trequire.NoError(t, err)\n\ttr, err := libp2pwebtransport.New(priv, nil, newConnManager(t), nil, &network.NullResourceManager{}, libp2pwebtransport.WithClock(cl))\n\trequire.NoError(t, err)\n\n\tl, err := tr.Listen(ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1/webtransport\"))\n\trequire.NoError(t, err)\n\n\tcerthashes := extractCertHashes(l.Multiaddr())\n\tl.Close()\n\n\t// Traverse various time boundaries and make sure we always keep a common certhash.\n\t// e.g. certhash/A/certhash/B ... -> ... certhash/B/certhash/C ... -> ... certhash/C/certhash/D\n\tfor i := range 200 {\n\t\tcl.Add(24 * time.Hour)\n\t\ttr, err := libp2pwebtransport.New(priv, nil, newConnManager(t), nil, &network.NullResourceManager{}, libp2pwebtransport.WithClock(cl))\n\t\trequire.NoError(t, err)\n\t\tl, err := tr.Listen(ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1/webtransport\"))\n\t\trequire.NoError(t, err)\n\n\t\tvar found bool\n\t\tma.ForEach(l.Multiaddr(), func(c ma.Component) bool {\n\t\t\tif c.Protocol().Code == ma.P_CERTHASH {\n\t\t\t\tif slices.Contains(certhashes, c.Value()) {\n\t\t\t\t\tfound = true\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true\n\t\t})\n\t\tcerthashes = extractCertHashes(l.Multiaddr())\n\t\tl.Close()\n\n\t\trequire.True(t, found, \"Failed after hour: %v\", i)\n\t}\n}\n\nfunc TestH3ConnClosed(t *testing.T) {\n\t_, serverKey := newIdentity(t)\n\ttr, err := libp2pwebtransport.New(serverKey, nil, newConnManager(t), nil, nil, libp2pwebtransport.WithHandshakeTimeout(1*time.Second))\n\trequire.NoError(t, err)\n\tdefer tr.(io.Closer).Close()\n\tln, err := tr.Listen(ma.StringCast(\"/ip4/127.0.0.1/udp/0/quic-v1/webtransport\"))\n\trequire.NoError(t, err)\n\tdefer ln.Close()\n\n\tp, err := net.ListenPacket(\"udp\", \"127.0.0.1:0\")\n\trequire.NoError(t, err)\n\tconn, err := quic.Dial(context.Background(), p, ln.Addr(), &tls.Config{\n\t\tInsecureSkipVerify: true,\n\t\tNextProtos:         []string{http3.NextProtoH3},\n\t}, nil)\n\trequire.NoError(t, err)\n\trt := &http3.Transport{}\n\trt.NewClientConn(conn)\n\trequire.Eventually(t, func() bool {\n\t\tc := http.Client{\n\t\t\tTransport: rt,\n\t\t\tTimeout:   1 * time.Second,\n\t\t}\n\t\tresp, err := c.Get(fmt.Sprintf(\"https://%s\", ln.Addr().String()))\n\t\tif err != nil {\n\t\t\treturn true\n\t\t}\n\t\tresp.Body.Close()\n\t\treturn false\n\t}, 10*time.Second, 1*time.Second)\n}\n"
  },
  {
    "path": "proto_test.go",
    "content": "package libp2p_test\n\nimport (\n\t\"testing\"\n\n\t// Import all protobuf packages to ensure their `init` functions run.\n\t// This may not be strictly necessary if they are imported in the `libp2p` package, but\n\t// we do it here in case the imports in non-test files change.\n\t_ \"github.com/libp2p/go-libp2p/core/crypto/pb\"\n\t_ \"github.com/libp2p/go-libp2p/core/peer/pb\"\n\t_ \"github.com/libp2p/go-libp2p/core/record/pb\"\n\t_ \"github.com/libp2p/go-libp2p/p2p/host/autonat/pb\"\n\t_ \"github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoreds/pb\"\n\t_ \"github.com/libp2p/go-libp2p/p2p/protocol/autonatv2/pb\"\n\t_ \"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/pb\"\n\t_ \"github.com/libp2p/go-libp2p/p2p/protocol/holepunch/pb\"\n\t_ \"github.com/libp2p/go-libp2p/p2p/protocol/identify/pb\"\n\t_ \"github.com/libp2p/go-libp2p/p2p/security/insecure/pb\"\n\t_ \"github.com/libp2p/go-libp2p/p2p/security/noise/pb\"\n\t_ \"github.com/libp2p/go-libp2p/p2p/transport/webrtc/pb\"\n\t\"google.golang.org/protobuf/reflect/protoreflect\"\n\t\"google.golang.org/protobuf/reflect/protoregistry\"\n)\n\n//go:generate scripts/gen-proto.sh .\n\nfunc TestProtoImportsAndPathsAreConsistent(t *testing.T) {\n\tprotoregistry.GlobalFiles.RangeFiles(func(fd protoreflect.FileDescriptor) bool {\n\t\timports := fd.Imports()\n\t\tfor i := 0; i < imports.Len(); i++ {\n\t\t\tpath := imports.Get(i).Path()\n\t\t\tif _, err := protoregistry.GlobalFiles.FindFileByPath(path); err != nil {\n\t\t\t\tt.Fatalf(\"find dependency %s: %v\", path, err)\n\t\t\t}\n\t\t}\n\t\treturn true\n\t})\n}\n"
  },
  {
    "path": "scripts/.gitignore",
    "content": "protobuf-bin/\nprotoc-gen-go\n"
  },
  {
    "path": "scripts/download-protoc.sh",
    "content": "#!/usr/bin/env bash\nset -eou pipefail\n\n# This script requires bash 4+, some macOS installations have bash 3\n\n# Specify the protobuf release version\nPROTOBUF_VERSION=\"29.2\"\n\n# Define SHA-256 hashes for each supported platform\n# Update these hashes by running ./print-protoc-hashes.sh\ndeclare -A SHA256_HASHES=(\n  [\"linux-aarch64\"]=\"0019dfc4b32d63c1392aa264aed2253c1e0c2fb09216f8e2cc269bbfb8bb49b5\"\n  [\"linux-x86_64\"]=\"52e9e7ece55c7e30e7e8bbd254b4b21b408a5309bca826763c7124b696a132e9\"\n  [\"darwin-aarch64\"]=\"0e153a38d6da19594c980e7f7cd3ea0ddd52c9da1068c03c0d8533369fbfeb20\"\n)\n\n# Determine the platform\nOS=\"$(uname -s | tr '[:upper:]' '[:lower:]')\"\nARCH=\"$(uname -m)\"\n[[ \"${ARCH}\" == \"arm64\" ]] && ARCH=\"aarch64\"\n\nPLATFORM=\"${OS}-${ARCH}\"\n\n# Set the download URL based on the platform\ncase \"${PLATFORM}\" in\nlinux-x86_64)\n  PROTOC_ZIP=\"protoc-${PROTOBUF_VERSION}-linux-x86_64.zip\"\n  ;;\nlinux-aarch64)\n  PROTOC_ZIP=\"protoc-${PROTOBUF_VERSION}-linux-aarch64.zip\"\n  ;;\ndarwin-aarch64)\n  PROTOC_ZIP=\"protoc-${PROTOBUF_VERSION}-osx-aarch_64.zip\"\n  ;;\n*)\n  echo \"Unsupported platform: ${PLATFORM}\" >&2\n  exit 1\n  ;;\nesac\n\n# Download the specified version of protobuf\nDOWNLOAD_URL=\"https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOBUF_VERSION}/${PROTOC_ZIP}\"\necho \"Downloading from: ${DOWNLOAD_URL}\" >&2\ncurl -LO \"${DOWNLOAD_URL}\"\n\n# Verify checksum\nEXPECTED_SHA256=\"${SHA256_HASHES[${PLATFORM}]}\"\nif command -v shasum >/dev/null 2>&1; then\n  ACTUAL_SHA256=$(shasum -a 256 \"${PROTOC_ZIP}\" | cut -d' ' -f1)\nelse\n  ACTUAL_SHA256=$(sha256sum \"${PROTOC_ZIP}\" | cut -d' ' -f1)\nfi\n\nif [[ \"${ACTUAL_SHA256}\" != \"${EXPECTED_SHA256}\" ]]; then\n  echo \"Checksum verification failed!\" >&2\n  echo \"Expected: ${EXPECTED_SHA256}\" >&2\n  echo \"Got: ${ACTUAL_SHA256}\" >&2\n  rm \"${PROTOC_ZIP}\"\n  exit 1\nfi\n\necho \"Checksum verified successfully\" >&2\n\n# Create a directory for extraction\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nINSTALL_DIR=\"$SCRIPT_DIR/protobuf-bin/protoc-${PROTOBUF_VERSION}\"\nmkdir -p \"${INSTALL_DIR}\"\n\n# Unzip the downloaded file\nunzip -q -o \"${PROTOC_ZIP}\" -d \"${INSTALL_DIR}\"\n\n# Clean up the zip file\nrm \"${PROTOC_ZIP}\"\n\n# Return a new PATH with the protobuf binary\nPROTOC_BIN=\"${INSTALL_DIR}/bin\"\necho \"Installed protoc ${PROTOBUF_VERSION} to ${INSTALL_DIR}\" >&2\n\n# Return the protoc bin path to stdout\nprintf \"${PROTOC_BIN}\"\n"
  },
  {
    "path": "scripts/gen-proto.sh",
    "content": "#!/usr/bin/env bash\nset -eou pipefail\n\nroot=$1\n\nproto_array=(\n  core/crypto/pb/crypto.proto\n  core/record/pb/envelope.proto\n  core/peer/pb/peer_record.proto\n  p2p/security/insecure/pb/plaintext.proto\n  p2p/host/autonat/pb/autonat.proto\n  p2p/security/noise/pb/payload.proto\n  p2p/transport/webrtc/pb/message.proto\n  p2p/protocol/identify/pb/identify.proto\n  p2p/protocol/circuitv2/pb/circuit.proto\n  p2p/protocol/circuitv2/pb/voucher.proto\n  p2p/protocol/autonatv2/pb/autonatv2.proto\n  p2p/protocol/holepunch/pb/holepunch.proto\n  p2p/host/peerstore/pstoreds/pb/pstore.proto\n)\n\nproto_paths=\"\"\nfor path in \"${proto_array[@]}\"; do\n  proto_paths+=\"$path \"\ndone\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nPROTOC_BIN_PATH=\"$(\"${SCRIPT_DIR}/download-protoc.sh\")\"\nexport PATH=\"$PROTOC_BIN_PATH:$PATH\"\n\necho protoc --version $(protoc --version)\n(cd ${SCRIPT_DIR} && go build -o protoc-gen-go google.golang.org/protobuf/cmd/protoc-gen-go)\n\necho protoc-gen-go --version $(${SCRIPT_DIR}/protoc-gen-go --version)\nprotoc --plugin=\"${SCRIPT_DIR}/protoc-gen-go\" --proto_path=$root --go_out=$root --go_opt=paths=source_relative $proto_paths\n"
  },
  {
    "path": "scripts/mkreleaselog",
    "content": "#!/bin/zsh\n#set -x\nset -euo pipefail\nexport GO111MODULE=on\nexport GOPATH=\"$(go env GOPATH)\"\n\nalias jq=\"jq --unbuffered\"\n\nAUTHORS=(\n    # orgs\n    ipfs\n    ipld\n    libp2p\n    multiformats\n    filecoin-project\n    ipfs-shipyard\n\n    # Authors of personal repos used by go-libp2p that should be mentioned in the\n    # release notes.\n    whyrusleeping\n    Kubuxu\n    jbenet\n    Stebalien\n    marten-seemann\n    hsanjuan\n    lucas-clemente\n    warpfork\n)\n\n[[ -n \"${REPO_FILTER+x}\" ]] || REPO_FILTER=\"github.com/(${$(printf \"|%s\" \"${AUTHORS[@]}\"):1})\"\n\n[[ -n \"${IGNORED_FILES+x}\" ]] || IGNORED_FILES='^\\(\\.gx\\|package\\.json\\|\\.travis\\.yml\\|go.mod\\|go\\.sum|\\.github|\\.circleci|\\.gen\\.go\\)$'\n\nNL=$'\\n'\n\nROOT_DIR=\"$(git rev-parse --show-toplevel)\"\n\nmsg() {\n    echo \"$*\" >&2\n}\n\nstatlog() {\n    local module=\"$1\"\n    local rpath=\"$GOPATH/src/$(strip_version \"$module\")\"\n    local start=\"${2:-}\"\n    local end=\"${3:-HEAD}\"\n    local mailmap_file=\"$rpath/.mailmap\"\n    if ! [[ -e \"$mailmap_file\" ]]; then\n        mailmap_file=\"$ROOT_DIR/.mailmap\"\n    fi\n\n    git -C \"$rpath\" -c mailmap.file=\"$mailmap_file\" log --use-mailmap --shortstat --no-merges --pretty=\"tformat:%H%n%aN%n%aE\" \"$start..$end\" | while \n        read hash\n        read name\n        read email\n        read _ # empty line\n        read changes\n    do\n        if [[ $name == \"web3-bot\" ]]; then\n            continue\n        fi\n        changed=0\n        insertions=0\n        deletions=0\n        while read count event; do\n            if [[ \"$event\" =~ ^file ]]; then\n                changed=$count\n            elif [[ \"$event\" =~ ^insertion ]]; then\n                insertions=$count\n            elif [[ \"$event\" =~ ^deletion ]]; then\n                deletions=$count\n            else\n                echo \"unknown event $event\" >&2\n                exit 1\n            fi\n        done<<<\"${changes//,/$NL}\"\n\n        jq -n \\\n           --arg \"hash\" \"$hash\" \\\n           --arg \"name\" \"$name\" \\\n           --arg \"email\" \"$email\" \\\n           --argjson \"changed\" \"$changed\" \\\n           --argjson \"insertions\" \"$insertions\" \\\n           --argjson \"deletions\" \"$deletions\" \\\n           '{Commit: $hash, Author: $name, Email: $email, Files: $changed, Insertions: $insertions, Deletions: $deletions}'\n    done\n}\n\n# Returns a stream of deps changed between $1 and $2.\ndep_changes() {\n    {\n        <\"$1\"\n        <\"$2\"\n    } | jq -s 'JOIN(INDEX(.[0][]; .Path); .[1][]; .Path; {Path: .[0].Path, Old: (.[1] | del(.Path)), New: (.[0] | del(.Path))}) | select(.New.Version != .Old.Version)'\n}\n\n# resolve_commits resolves a git ref for each version.\nresolve_commits() {\n    jq '. + {Ref: (.Version|capture(\"^((?<ref1>.*)\\\\+incompatible|v.*-(0\\\\.)?[0-9]{14}-(?<ref2>[a-f0-9]{12})|(?<ref3>v.*))$\") | .ref1 // .ref2 // .ref3)}'\n}\n\npr_link() {\n    local repo=\"$1\"\n    local prnum=\"$2\"\n    local ghname=\"${repo##github.com/}\"\n    printf -- \"[%s#%s](https://%s/pull/%s)\" \"$ghname\" \"$prnum\" \"$repo\" \"$prnum\"\n}\n\n# Generate a release log for a range of commits in a single repo.\nrelease_log() {\n    setopt local_options BASH_REMATCH\n\n    local module=\"$1\"\n    local start=\"$2\"\n    local end=\"${3:-HEAD}\"\n    local repo=\"$(strip_version \"$1\")\"\n    local dir=\"$GOPATH/src/$repo\"\n\n    local commit pr\n    git -C \"$dir\" log \\\n        --format='tformat:%H %s' \\\n        --first-parent \\\n        \"$start..$end\" |\n        while read commit subject; do\n            # Skip gx-only PRs.\n            if git rev-parse '$commit^' >/dev/null 2>&1 &&\n                   ! git -C \"$dir\" diff-tree --no-commit-id --name-only \"$commit^\" \"$commit\" | grep -v \"${IGNORED_FILES}\" >/dev/null; then\n                continue\n            fi\n\n            if [[ \"$subject\" =~ \"^sync: update CI config files\" ]]; then\n                continue\n            fi\n\n            if [[ \"$subject\" =~ '^Merge pull request #([0-9]+) from' ]]; then\n                local prnum=\"${BASH_REMATCH[2]}\"\n                local desc=\"$(git -C \"$dir\" show --summary --format='tformat:%b' \"$commit\" | head -1)\"\n                if [[ \"$desc\" =~ \"^sync: update CI config files\" ]]; then\n                    continue\n                fi\n                printf -- \"- %s (%s)\\n\" \"$desc\" \"$(pr_link \"$repo\" \"$prnum\")\"\n            elif [[ \"$subject\" =~ '\\(#([0-9]+)\\)$' ]]; then\n                local prnum=\"${BASH_REMATCH[2]}\"\n                printf -- \"- %s (%s)\\n\" \"$subject\" \"$(pr_link \"$repo\" \"$prnum\")\"\n            else\n                printf -- \"- %s\\n\" \"$subject\"\n            fi\n        done\n}\n\nindent() {\n    sed -e 's/^/  /'\n}\n\nmod_deps() {\n    go list -mod=mod -json -m all | jq 'select(.Version != null)'\n}\n\nensure() {\n    local repo=\"$(strip_version \"$1\")\"\n    local commit=\"$2\"\n    local rpath=\"$GOPATH/src/$repo\"\n    if [[ ! -d \"$rpath\" ]]; then\n        msg \"Cloning $repo...\"\n        git clone \"http://$repo\" \"$rpath\" >&2\n    fi\n\n    if ! git -C \"$rpath\" rev-parse --verify \"$commit\" >/dev/null; then\n        msg \"Fetching $repo...\"\n        git -C \"$rpath\" fetch --all >&2\n    fi\n\n    git -C \"$rpath\" rev-parse --verify \"$commit\" >/dev/null || return 1\n}\n\nstatsummary() {\n    jq -s 'group_by(.Author)[] | {Author: .[0].Author, Commits: (. | length), Insertions: (map(.Insertions) | add), Deletions: (map(.Deletions) | add), Files: (map(.Files) | add)}' |\n        jq '. + {Lines: (.Deletions + .Insertions)}'\n}\n\nstrip_version() {\n    local repo=\"$1\"\n    if [[ \"$repo\" =~ '.*/v[0-9]+$' ]]; then\n        repo=\"$(dirname \"$repo\")\"\n    fi\n    echo \"$repo\"\n}\n\nrecursive_release_log() {\n    local start=\"${1:-$(git tag -l | sort -V | grep -v -- '-rc' | grep 'v'| tail -n1)}\"\n    local end=\"${2:-$(git rev-parse HEAD)}\"\n    local repo_root=\"$(git rev-parse --show-toplevel)\"\n    local module=\"$(go list -m)\"\n    local dir=\"$(go list -m -f '{{.Dir}}')\"\n\n    if [[ \"${GOPATH}/${module}\" -ef \"${dir}\" ]]; then\n        echo \"This script requires the target module and all dependencies to live in a GOPATH.\"\n        return 1\n    fi\n\n    (\n        local result=0\n        local workspace=\"$(mktemp -d)\"\n        trap \"$(printf 'rm -rf \"%q\"' \"$workspace\")\" INT TERM EXIT\n        cd \"$workspace\"\n\n        echo \"Computing old deps...\" >&2\n        git -C \"$repo_root\" show \"$start:go.mod\" >go.mod\n        mod_deps | resolve_commits | jq -s > old_deps.json\n\n        echo \"Computing new deps...\" >&2\n        git -C \"$repo_root\" show \"$end:go.mod\" >go.mod\n        mod_deps | resolve_commits | jq -s > new_deps.json\n\n        rm -f go.mod go.sum\n\n        printf -- \"Generating Changelog for %s %s..%s\\n\" \"$module\" \"$start\" \"$end\" >&2\n\n        printf -- \"- %s:\\n\" \"$module\"\n        release_log \"$module\" \"$start\" \"$end\" | indent\n\n\n        statlog \"$module\" \"$start\" \"$end\" > statlog.json\n\n        dep_changes old_deps.json new_deps.json |\n            jq --arg filter \"$REPO_FILTER\" 'select(.Path | match($filter))' |\n            # Compute changelogs\n            jq -r '\"\\(.Path) \\(.New.Version) \\(.New.Ref) \\(.Old.Version) \\(.Old.Ref // \"\")\"' |\n            while read module new new_ref old old_ref; do\n                if ! ensure \"$module\" \"$new_ref\"; then\n                    result=1\n                    local changelog=\"failed to fetch repo\"\n                else\n                    statlog \"$module\" \"$old_ref\" \"$new_ref\" >> statlog.json\n                    local changelog=\"$(release_log \"$module\" \"$old_ref\" \"$new_ref\")\"\n                fi\n                if [[ -n \"$changelog\" ]]; then\n                    printf -- \"- %s (%s -> %s):\\n\" \"$module\" \"$old\" \"$new\"\n                    echo \"$changelog\" | indent\n                fi\n            done\n\n        echo\n        echo \"Contributors\"\n        echo\n\n        echo \"| Contributor | Commits | Lines ± | Files Changed |\"\n        echo \"|-------------|---------|---------|---------------|\"\n        statsummary <statlog.json |\n            jq -s 'sort_by(.Lines) | reverse | .[]' |\n            jq -r '\"| \\(.Author) | \\(.Commits) | +\\(.Insertions)/-\\(.Deletions) | \\(.Files) |\"'\n        return \"$status\"\n    )\n}\n\nrecursive_release_log \"$@\"\n"
  },
  {
    "path": "scripts/print-protoc-hashes.sh",
    "content": "#!/usr/bin/env bash\nset -eou pipefail\n\n# Specify the protobuf release version\nPROTOBUF_VERSION=\"29.2\"\n\n# Define the platforms\nPLATFORMS=(\"linux-x86_64\" \"linux-aarch64\" \"darwin-aarch64\")\n\n# Array to store the hashes\ndeclare -A HASHES\n\n# Function to download and calculate the SHA-256 hash\ncalculate_hash() {\n  local platform=$1\n  local protoc_zip\n\n  case \"${platform}\" in\n  linux-x86_64)\n    protoc_zip=\"protoc-${PROTOBUF_VERSION}-linux-x86_64.zip\"\n    ;;\n  linux-aarch64)\n    protoc_zip=\"protoc-${PROTOBUF_VERSION}-linux-aarch64.zip\"\n    ;;\n  darwin-aarch64)\n    protoc_zip=\"protoc-${PROTOBUF_VERSION}-osx-aarch_64.zip\"\n    ;;\n  *)\n    echo \"Unsupported platform: ${platform}\"\n    exit 1\n    ;;\n  esac\n\n  # Download the specified version of protobuf\n  download_url=\"https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOBUF_VERSION}/${protoc_zip}\"\n  echo \"Downloading from: ${download_url}\"\n  curl -LO \"${download_url}\"\n\n  # Calculate the SHA-256 hash\n  if command -v shasum >/dev/null 2>&1; then\n    sha256_hash=$(shasum -a 256 \"${protoc_zip}\" | cut -d' ' -f1)\n  else\n    sha256_hash=$(sha256sum \"${protoc_zip}\" | cut -d' ' -f1)\n  fi\n\n  # Store the hash in the array\n  HASHES[\"${platform}\"]=\"${sha256_hash}\"\n\n  # Clean up the zip file\n  rm \"${protoc_zip}\"\n}\n\n# Iterate over the platforms and calculate the hashes\nfor platform in \"${PLATFORMS[@]}\"; do\n  calculate_hash \"${platform}\"\ndone\n\n# Print all the hashes together at the end\necho \"Expected SHA-256 hashes for protobuf ${PROTOBUF_VERSION}:\"\nfor platform in \"${!HASHES[@]}\"; do\n  echo \"[\\\"${platform}\\\"]=\\\"${HASHES[${platform}]}\\\"\"\ndone\n"
  },
  {
    "path": "scripts/test_analysis/cmd/gotest2sql/main.go",
    "content": "// gotest2sql inserts the output of go test -json ./... into a sqlite database\npackage main\n\nimport (\n\t\"bufio\"\n\t\"database/sql\"\n\t\"encoding/json\"\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"time\"\n\n\t_ \"modernc.org/sqlite\"\n)\n\ntype TestEvent struct {\n\tTime    time.Time // encodes as an RFC3339-format string\n\tAction  string\n\tPackage string\n\tTest    string\n\tElapsed float64 // seconds\n\tOutput  string\n}\n\nfunc main() {\n\toutputPath := flag.String(\"output\", \"\", \"output db file\")\n\tverbose := flag.Bool(\"v\", false, \"Print test output to stdout\")\n\tflag.Parse()\n\n\tif *outputPath == \"\" {\n\t\tlog.Fatal(\"-output path is required\")\n\t}\n\n\tdb, err := sql.Open(\"sqlite\", *outputPath)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Create a table to store test results.\n\t_, err = db.Exec(`\n\t\tCREATE TABLE IF NOT EXISTS test_results (\n\t\t\tTime TEXT,\n\t\t\tAction TEXT,\n\t\t\tPackage TEXT,\n\t\t\tTest TEXT,\n\t\t\tElapsed REAL,\n\t\t\tOutput TEXT,\n\t\t\tBatchInsertTime TEXT\n\t)`)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\ttx, err := db.Begin()\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Prepare the insert statement once\n\tinsertTime := time.Now().Format(time.RFC3339Nano)\n\tstmt, err := tx.Prepare(`\n    INSERT INTO test_results (Time, Action, Package, Test, Elapsed, Output, BatchInsertTime)\n    VALUES (?, ?, ?, ?, ?, ?, ?)`)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tdefer stmt.Close() // Ensure the statement is closed after use\n\n\ts := bufio.NewScanner(os.Stdin)\n\tfor s.Scan() {\n\t\tline := s.Bytes()\n\t\tvar ev TestEvent\n\t\terr = json.Unmarshal(line, &ev)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t\tif *verbose && ev.Action == \"output\" {\n\t\t\tfmt.Print(ev.Output)\n\t\t}\n\n\t\t_, err = stmt.Exec(\n\t\t\tev.Time.Format(time.RFC3339Nano),\n\t\t\tev.Action,\n\t\t\tev.Package,\n\t\t\tev.Test,\n\t\t\tev.Elapsed,\n\t\t\tev.Output,\n\t\t\tinsertTime,\n\t\t)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t}\n\n\t// Commit the transaction\n\tif err := tx.Commit(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n"
  },
  {
    "path": "scripts/test_analysis/go.mod",
    "content": "module github.com/libp2p/go-libp2p/scripts/test_analysis\n\ngo 1.24\n\nrequire modernc.org/sqlite v1.36.0\n\nrequire (\n\tgithub.com/dustin/go-humanize v1.0.1 // indirect\n\tgithub.com/google/uuid v1.6.0 // indirect\n\tgithub.com/mattn/go-isatty v0.0.20 // indirect\n\tgithub.com/ncruces/go-strftime v0.1.9 // indirect\n\tgithub.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect\n\tgolang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 // indirect\n\tgolang.org/x/sys v0.30.0 // indirect\n\tmodernc.org/libc v1.61.13 // indirect\n\tmodernc.org/mathutil v1.7.1 // indirect\n\tmodernc.org/memory v1.8.2 // indirect\n)\n"
  },
  {
    "path": "scripts/test_analysis/go.sum",
    "content": "github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=\ngithub.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=\ngithub.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=\ngithub.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=\ngithub.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=\ngithub.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=\ngithub.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=\ngolang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 h1:pVgRXcIictcr+lBQIFeiwuwtDIs4eL21OuM9nyAADmo=\ngolang.org/x/exp v0.0.0-20230315142452-642cacee5cc0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=\ngolang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=\ngolang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=\ngolang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=\ngolang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=\ngolang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=\ngolang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=\nmodernc.org/cc/v4 v4.24.4 h1:TFkx1s6dCkQpd6dKurBNmpo+G8Zl4Sq/ztJ+2+DEsh0=\nmodernc.org/cc/v4 v4.24.4/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=\nmodernc.org/ccgo/v4 v4.23.16 h1:Z2N+kk38b7SfySC1ZkpGLN2vthNJP1+ZzGZIlH7uBxo=\nmodernc.org/ccgo/v4 v4.23.16/go.mod h1:nNma8goMTY7aQZQNTyN9AIoJfxav4nvTnvKThAeMDdo=\nmodernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=\nmodernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=\nmodernc.org/gc/v2 v2.6.3 h1:aJVhcqAte49LF+mGveZ5KPlsp4tdGdAOT4sipJXADjw=\nmodernc.org/gc/v2 v2.6.3/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=\nmodernc.org/libc v1.61.13 h1:3LRd6ZO1ezsFiX1y+bHd1ipyEHIJKvuprv0sLTBwLW8=\nmodernc.org/libc v1.61.13/go.mod h1:8F/uJWL/3nNil0Lgt1Dpz+GgkApWh04N3el3hxJcA6E=\nmodernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=\nmodernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=\nmodernc.org/memory v1.8.2 h1:cL9L4bcoAObu4NkxOlKWBWtNHIsnnACGF/TbqQ6sbcI=\nmodernc.org/memory v1.8.2/go.mod h1:ZbjSvMO5NQ1A2i3bWeDiVMxIorXwdClKE/0SZ+BMotU=\nmodernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=\nmodernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=\nmodernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=\nmodernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=\nmodernc.org/sqlite v1.36.0 h1:EQXNRn4nIS+gfsKeUTymHIz1waxuv5BzU7558dHSfH8=\nmodernc.org/sqlite v1.36.0/go.mod h1:7MPwH7Z6bREicF9ZVUR78P1IKuxfZ8mRIDHD0iD+8TU=\nmodernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=\nmodernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=\nmodernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=\nmodernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=\n"
  },
  {
    "path": "scripts/test_analysis/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"os/exec\"\n\t\"regexp\"\n\t\"strings\"\n\n\t_ \"modernc.org/sqlite\"\n)\n\nconst dbPath = \"./test_results.db\"\nconst retryCount = 4 // For a total of 5 runs\n\nvar coverRegex = regexp.MustCompile(`-cover`)\n\nfunc main() {\n\tvar t tester\n\tif len(os.Args) >= 2 {\n\t\tif os.Args[1] == \"summarize\" {\n\t\t\tmd, err := t.summarize()\n\t\t\tif err != nil {\n\t\t\t\tlog.Fatal(err)\n\t\t\t}\n\t\t\tfmt.Print(md)\n\t\t\treturn\n\t\t}\n\t}\n\n\tpassThruFlags := os.Args[1:]\n\terr := t.runTests(passThruFlags)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\ntype tester struct {\n\tDir string\n}\n\nfunc (t *tester) runTests(passThruFlags []string) error {\n\terr := t.goTestAll(passThruFlags)\n\tif err == nil {\n\t\t// No failed tests, nothing to do\n\t\treturn nil\n\t}\n\tlog.Printf(\"Not all tests passed: %v\", err)\n\n\ttimedOutPackages, err := t.findTimedoutTests(context.Background())\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(timedOutPackages) > 0 {\n\t\t// Fail immediately if we find any timeouts. We'd have to run all tests\n\t\t// in the package, and this could take a long time.\n\t\tlog.Printf(\"Found %d timed out packages. Failing\", len(timedOutPackages))\n\t\treturn errors.New(\"one or more tests timed out\")\n\t}\n\n\tfailedTests, err := t.findFailedTests(context.Background())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlog.Printf(\"Found %d failed tests. Retrying them %d times\", len(failedTests), retryCount)\n\thasOneNonFlakyFailure := false\n\tloggedFlaky := map[string]struct{}{}\n\n\tfor _, ft := range failedTests {\n\t\tisFlaky := false\n\t\tfor i := 0; i < retryCount; i++ {\n\t\t\tlog.Printf(\"Retrying %s.%s\", ft.Package, ft.Test)\n\t\t\tif err := t.goTestPkgTest(ft.Package, ft.Test, filterOutFlags(passThruFlags, coverRegex)); err != nil {\n\t\t\t\tlog.Printf(\"Failed to run %s.%s: %v\", ft.Package, ft.Test, err)\n\t\t\t} else {\n\t\t\t\tisFlaky = true\n\t\t\t\tflakyName := ft.Package + \".\" + ft.Test\n\t\t\t\tif _, ok := loggedFlaky[flakyName]; !ok {\n\t\t\t\t\tloggedFlaky[flakyName] = struct{}{}\n\t\t\t\t\tlog.Printf(\"Test %s.%s is flaky.\", ft.Package, ft.Test)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif !isFlaky {\n\t\t\thasOneNonFlakyFailure = true\n\t\t}\n\t}\n\n\t// A test consistently failed, so we should exit with a non-zero exit code.\n\tif hasOneNonFlakyFailure {\n\t\treturn errors.New(\"one or more tests consistently failed\")\n\t}\n\treturn nil\n}\n\nfunc (t *tester) goTestAll(extraFlags []string) error {\n\tflags := []string{\"./...\"}\n\tflags = append(flags, extraFlags...)\n\treturn t.goTest(flags)\n}\n\nfunc (t *tester) goTestPkgTest(pkg, testname string, extraFlags []string) error {\n\tflags := []string{\n\t\tpkg, \"-run\", \"^\" + testname + \"$\", \"-count\", \"1\",\n\t}\n\tflags = append(flags, extraFlags...)\n\treturn t.goTest(flags)\n}\n\nfunc (t *tester) goTest(extraFlags []string) error {\n\tflags := []string{\n\t\t\"test\", \"-json\",\n\t}\n\tflags = append(flags, extraFlags...)\n\tcmd := exec.Command(\"go\", flags...)\n\tcmd.Dir = t.Dir\n\tcmd.Stderr = os.Stderr\n\n\tgotest2sql := exec.Command(\"gotest2sql\", \"-v\", \"-output\", dbPath)\n\tgotest2sql.Dir = t.Dir\n\tgotest2sql.Stdin, _ = cmd.StdoutPipe()\n\tgotest2sql.Stdout = os.Stdout\n\tgotest2sql.Stderr = os.Stderr\n\terr := gotest2sql.Start()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = cmd.Run()\n\treturn errors.Join(err, gotest2sql.Wait())\n}\n\ntype failedTest struct {\n\tPackage string\n\tTest    string\n}\n\ntype timedOutPackage struct {\n\tPackage string\n\tOutputs string\n}\n\nfunc (t *tester) findFailedTests(ctx context.Context) ([]failedTest, error) {\n\tdb, err := sql.Open(\"sqlite\", t.Dir+dbPath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.Close()\n\n\trows, err := db.QueryContext(ctx, \"SELECT DISTINCT Package, Test FROM test_results where Action='fail' and Test != ''\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar out []failedTest\n\tfor rows.Next() {\n\t\tvar pkg, test string\n\t\tif err := rows.Scan(&pkg, &test); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tout = append(out, failedTest{pkg, test})\n\t}\n\treturn out, nil\n}\n\nfunc (t *tester) findTimedoutTests(ctx context.Context) ([]timedOutPackage, error) {\n\tdb, err := sql.Open(\"sqlite\", t.Dir+dbPath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer db.Close()\n\n\trows, err := db.QueryContext(ctx, `WITH failed_packages AS (\n    SELECT\n        Package\n    FROM\n        test_results\n    WHERE\n        Action = 'fail'\n        AND Elapsed > 300\n)\nSELECT\n\ttest_results.Package, GROUP_CONCAT(Output, \"\") as Outputs\nFROM\n    test_results\nINNER JOIN\n    failed_packages\nON\n    test_results.Package = failed_packages.Package\nGROUP BY\n    test_results.Package\nHAVING\n    Outputs LIKE '%timed out%'\nORDER BY Time;`)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar out []timedOutPackage\n\tfor rows.Next() {\n\t\tvar pkg, outputs string\n\t\tif err := rows.Scan(&pkg, &outputs); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tout = append(out, timedOutPackage{pkg, outputs})\n\t}\n\treturn out, nil\n}\n\nfunc filterOutFlags(flags []string, exclude *regexp.Regexp) []string {\n\tout := make([]string, 0, len(flags))\n\tfor _, f := range flags {\n\t\tif !exclude.MatchString(f) {\n\t\t\tout = append(out, f)\n\t\t}\n\t}\n\treturn out\n}\n\n// summarize returns a markdown string of the test results.\nfunc (t *tester) summarize() (string, error) {\n\tctx := context.Background()\n\tvar out strings.Builder\n\n\ttestFailures, err := t.findFailedTests(ctx)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\ttimeouts, err := t.findTimedoutTests(ctx)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\ttestFailureCount := len(testFailures) + len(timeouts)\n\n\tplural := \"s\"\n\tif testFailureCount == 1 {\n\t\tplural = \"\"\n\t}\n\tout.WriteString(fmt.Sprintf(\"## %d Test Failure%s\\n\\n\", testFailureCount, plural))\n\n\tif len(timeouts) > 0 {\n\t\tout.WriteString(\"### Timed Out Tests\\n\\n\")\n\t\tfor _, timeout := range timeouts {\n\t\t\t_, err = out.WriteString(fmt.Sprintf(`<details>\n<summary>%s</summary>\n<pre>\n%s\n</pre>\n</details>`, timeout.Package, timeout.Outputs))\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", err\n\t\t\t}\n\t\t}\n\t\tout.WriteString(\"\\n\")\n\t}\n\n\tif len(testFailures) > 0 {\n\t\tout.WriteString(\"### Failed Tests\\n\\n\")\n\n\t\tdb, err := sql.Open(\"sqlite\", t.Dir+dbPath)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tdefer db.Close()\n\n\t\trows, err := db.QueryContext(ctx, `SELECT\n    tr_output.Package,\n    tr_output.Test,\n    GROUP_CONCAT(tr_output.Output,  \"\") AS Outputs\nFROM\n    test_results tr_fail\nJOIN\n    test_results tr_output\nON\n    tr_fail.Test = tr_output.Test\n    AND tr_fail.BatchInsertTime = tr_output.BatchInsertTime\n    AND tr_fail.Package = tr_output.Package\nWHERE\n    tr_fail.Action = 'fail'\n    AND tr_output.Test != ''\nGROUP BY\n    tr_output.BatchInsertTime,\n    tr_output.Package,\n    tr_output.Test\nORDER BY\n    MIN(tr_output.Time);`)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tfor rows.Next() {\n\t\t\tvar pkg, test, outputs string\n\t\t\tif err := rows.Scan(&pkg, &test, &outputs); err != nil {\n\t\t\t\treturn \"\", err\n\t\t\t}\n\t\t\t_, err = out.WriteString(fmt.Sprintf(`<details>\n<summary>%s.%s</summary>\n<pre>\n%s\n</pre>\n</details>`, pkg, test, outputs))\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", err\n\t\t\t}\n\t\t}\n\t}\n\treturn out.String(), nil\n}\n"
  },
  {
    "path": "scripts/test_analysis/main_test.go",
    "content": "package main\n\nimport (\n\t\"os\"\n\t\"testing\"\n)\n\nfunc TestFailsOnConsistentFailure(t *testing.T) {\n\ttmpDir := t.TempDir() + \"/\"\n\tos.WriteFile(tmpDir+\"/main.go\", []byte(`package main\nfunc main() {}`), 0644)\n\t// Add a test that fails consistently.\n\tos.WriteFile(tmpDir+\"/main_test.go\", []byte(`package main\n\nimport (\n\t\"testing\"\n)\nfunc TestConsistentFailure(t *testing.T) {\n\tt.Fatal(\"consistent failure\")\n}`), 0644)\n\tos.WriteFile(tmpDir+\"/go.mod\", []byte(`module example.com/test`), 0644)\n\n\ttstr := tester{Dir: tmpDir}\n\terr := tstr.runTests(nil)\n\tif err == nil {\n\t\tt.Fatal(\"Should have failed with a consistent failure\")\n\t}\n}\n\nfunc TestPassesOnFlakyFailure(t *testing.T) {\n\ttmpDir := t.TempDir() + \"/\"\n\tos.WriteFile(tmpDir+\"/main.go\", []byte(`package main\nfunc main() {\n}`), 0644)\n\t// Add a test that fails the first time.\n\tos.WriteFile(tmpDir+\"/main_test.go\", []byte(`package main\nimport (\n\t\"os\"\n\t\"testing\"\n)\nfunc TestFlakyFailure(t *testing.T) {\n\t_, err := os.Stat(\"foo\")\n\tif err != nil {\n\t\tos.WriteFile(\"foo\", []byte(\"hello\"), 0644)\n\t\tt.Fatal(\"flaky failure\")\n\t}\n}`), 0644)\n\tos.WriteFile(tmpDir+\"/go.mod\", []byte(`module example.com/test`), 0644)\n\n\t// Run the test.\n\ttstr := tester{Dir: tmpDir}\n\terr := tstr.runTests(nil)\n\tif err != nil {\n\t\tt.Fatal(\"Should have passed with a flaky test\")\n\t}\n}\n"
  },
  {
    "path": "test-plans/.gitignore",
    "content": "ping-image.tar\nping-image.json\n"
  },
  {
    "path": "test-plans/PingDockerfile",
    "content": "# syntax=docker/dockerfile:1\n# This is run from the parent directory to copy the whole go-libp2p codebase\n\nFROM golang:1.25-alpine AS builder\n\nWORKDIR /app/\n\nCOPY ./ .\nWORKDIR /app/test-plans\nRUN go mod download\nRUN go build -o /testplan ./cmd/ping\n\nFROM alpine\nWORKDIR /app\n\nCOPY --from=builder /testplan /testplan\nENTRYPOINT [ \"/testplan\"]\n"
  },
  {
    "path": "test-plans/README.md",
    "content": "# test-plans test implementation\n\nThis folder defines the implementation for the test-plans interop tests.\n\n# Running this test locally\n\nYou can run this test locally by having a local Redis instance and by having\nanother peer that this test can dial or listen for. For example to test that we\ncan dial/listen for ourselves we can do the following:\n\n1. Start redis (needed by the tests): `docker run --rm -it -p 6379:6379\n   redis/redis-stack`.\n2. In one terminal run the dialer: `redis_addr=localhost:6379 ip=\"0.0.0.0\"\n   transport=quic-v1 security=quic muxer=quic is_dialer=\"true\" go run\n   ./cmd/ping`\n3. In another terminal, run the listener: `redis_addr=localhost:6379\n   ip=\"0.0.0.0\" transport=quic-v1 security=quic muxer=quic is_dialer=\"false\" go\n   run ./cmd/ping`\n\n\nTo test the interop with other versions do something similar, except replace one\nof these nodes with the other version's interop test.\n\n# Running all interop tests locally with Compose\n\nTo run this test against all released libp2p versions you'll need to have the\n[libp2p/test-plans](https://github.com/libp2p/test-plans) checked out. Then do\nthe following (from the root directory of this repository):\n\n1. Build the image: `docker build -t go-libp2p-head -f test-plans/PingDockerfile .`.\n2. Build the images for all released versions in `libp2p/test-plans`: `(cd <path\n   to >/libp2p/test-plans/transport-interop/ && make)`.\n3. Run the test:\n```\nGO_LIBP2P=\"$PWD\"; (cd <path to >/libp2p/test-plans/transport-interop/ && npm run test -- --extra-version=$GO_LIBP2P/test-plans/ping-version.json --name-filter=\"go-libp2p-head\")\n\n```\n"
  },
  {
    "path": "test-plans/cmd/ping/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"crypto/x509/pkix\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log\"\n\t\"math/big\"\n\t\"os\"\n\t\"strconv\"\n\t\"time\"\n\n\tlibp2pwebrtc \"github.com/libp2p/go-libp2p/p2p/transport/webrtc\"\n\n\t\"github.com/go-redis/redis/v8\"\n\t\"github.com/libp2p/go-libp2p\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/p2p/muxer/yamux\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/ping\"\n\t\"github.com/libp2p/go-libp2p/p2p/security/noise\"\n\tlibp2ptls \"github.com/libp2p/go-libp2p/p2p/security/tls\"\n\tlibp2pquic \"github.com/libp2p/go-libp2p/p2p/transport/quic\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/tcp\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/websocket\"\n\tlibp2pwebtransport \"github.com/libp2p/go-libp2p/p2p/transport/webtransport\"\n\tma \"github.com/multiformats/go-multiaddr\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n)\n\nfunc main() {\n\tvar (\n\t\ttransport      = os.Getenv(\"transport\")\n\t\tmuxer          = os.Getenv(\"muxer\")\n\t\tsecureChannel  = os.Getenv(\"security\")\n\t\tisDialerStr    = os.Getenv(\"is_dialer\")\n\t\tip             = os.Getenv(\"ip\")\n\t\tredisAddr      = os.Getenv(\"redis_addr\")\n\t\ttestTimeoutStr = os.Getenv(\"test_timeout_seconds\")\n\t)\n\n\ttestTimeout := 3 * time.Minute\n\tif testTimeoutStr != \"\" {\n\t\tsecs, err := strconv.ParseInt(testTimeoutStr, 10, 32)\n\t\tif err == nil {\n\t\t\ttestTimeout = time.Duration(secs) * time.Second\n\t\t}\n\t}\n\n\tif ip == \"\" {\n\t\tip = \"0.0.0.0\"\n\t}\n\n\tif redisAddr == \"\" {\n\t\tredisAddr = \"redis:6379\"\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), testTimeout)\n\tdefer cancel()\n\n\t// Get peer information via redis\n\trClient := redis.NewClient(&redis.Options{\n\t\tDialTimeout: testTimeout,\n\t\tAddr:        redisAddr,\n\t\tPassword:    \"\",\n\t\tDB:          0,\n\t})\n\tdefer rClient.Close()\n\n\tfor {\n\t\tif ctx.Err() != nil {\n\t\t\tlog.Fatal(\"timeout waiting for redis\")\n\t\t}\n\n\t\t// Wait for redis to be ready\n\t\t_, err := rClient.Ping(ctx).Result()\n\t\tif err == nil {\n\t\t\tbreak\n\t\t}\n\t\ttime.Sleep(100 * time.Millisecond)\n\t}\n\n\tisDialer := isDialerStr == \"true\"\n\n\toptions := []libp2p.Option{}\n\n\tvar listenAddr string\n\tswitch transport {\n\tcase \"ws\":\n\t\toptions = append(options, libp2p.Transport(websocket.New))\n\t\tlistenAddr = fmt.Sprintf(\"/ip4/%s/tcp/0/ws\", ip)\n\tcase \"wss\":\n\t\toptions = append(options, libp2p.Transport(websocket.New, websocket.WithTLSConfig(generateTLSConfig()), websocket.WithTLSClientConfig(&tls.Config{InsecureSkipVerify: true})))\n\t\tlistenAddr = fmt.Sprintf(\"/ip4/%s/tcp/0/wss\", ip)\n\tcase \"tcp\":\n\t\toptions = append(options, libp2p.Transport(tcp.NewTCPTransport))\n\t\tlistenAddr = fmt.Sprintf(\"/ip4/%s/tcp/0\", ip)\n\tcase \"quic-v1\":\n\t\toptions = append(options, libp2p.Transport(libp2pquic.NewTransport))\n\t\tlistenAddr = fmt.Sprintf(\"/ip4/%s/udp/0/quic-v1\", ip)\n\tcase \"webtransport\":\n\t\toptions = append(options, libp2p.Transport(libp2pwebtransport.New))\n\t\tlistenAddr = fmt.Sprintf(\"/ip4/%s/udp/0/quic-v1/webtransport\", ip)\n\tcase \"webrtc-direct\":\n\t\toptions = append(options, libp2p.Transport(libp2pwebrtc.New))\n\t\tlistenAddr = fmt.Sprintf(\"/ip4/%s/udp/0/webrtc-direct\", ip)\n\tdefault:\n\t\tlog.Fatalf(\"Unsupported transport: %s\", transport)\n\t}\n\toptions = append(options, libp2p.ListenAddrStrings(listenAddr))\n\n\t// Skipped for certain transports\n\tvar skipMuxer bool\n\tvar skipSecureChannel bool\n\tswitch transport {\n\tcase \"quic-v1\":\n\t\tfallthrough\n\tcase \"webtransport\":\n\t\tfallthrough\n\tcase \"webrtc-direct\":\n\t\tskipMuxer = true\n\t\tskipSecureChannel = true\n\t}\n\n\tif !skipSecureChannel {\n\t\tswitch secureChannel {\n\t\tcase \"tls\":\n\t\t\toptions = append(options, libp2p.Security(libp2ptls.ID, libp2ptls.New))\n\t\tcase \"noise\":\n\t\t\toptions = append(options, libp2p.Security(noise.ID, noise.New))\n\t\tdefault:\n\t\t\tlog.Fatalf(\"Unsupported secure channel: %s\", secureChannel)\n\t\t}\n\t}\n\n\tif !skipMuxer {\n\t\tswitch muxer {\n\t\tcase \"yamux\":\n\t\t\toptions = append(options, libp2p.Muxer(\"/yamux/1.0.0\", yamux.DefaultTransport))\n\t\tdefault:\n\t\t\tlog.Fatalf(\"Unsupported muxer: %s\", muxer)\n\t\t}\n\t}\n\n\thost, err := libp2p.New(options...)\n\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to instantiate libp2p instance: %s\", err)\n\t}\n\tdefer host.Close()\n\n\tlog.Println(\"My multiaddr is: \", host.Addrs())\n\n\tif isDialer {\n\t\tval, err := rClient.BLPop(ctx, testTimeout, \"listenerAddr\").Result()\n\t\tif err != nil {\n\t\t\tlog.Fatal(\"Failed to wait for listener to be ready\")\n\t\t}\n\t\totherMa := ma.StringCast(val[1])\n\t\tlog.Println(\"Other peer multiaddr is: \", otherMa)\n\t\totherMa, p2pComponent := ma.SplitLast(otherMa)\n\t\totherPeerId, err := peer.Decode(p2pComponent.Value())\n\t\tif err != nil {\n\t\t\tlog.Fatal(\"Failed to get peer id from multiaddr\")\n\t\t}\n\n\t\thandshakeStartInstant := time.Now()\n\t\terr = host.Connect(ctx, peer.AddrInfo{\n\t\t\tID:    otherPeerId,\n\t\t\tAddrs: []ma.Multiaddr{otherMa},\n\t\t})\n\t\tif err != nil {\n\t\t\tlog.Fatal(\"Failed to connect to other peer\")\n\t\t}\n\n\t\tping := ping.NewPingService(host)\n\n\t\tres := <-ping.Ping(ctx, otherPeerId)\n\t\tif res.Error != nil {\n\t\t\tlog.Fatal(res.Error)\n\t\t}\n\t\thandshakePlusOneRTT := time.Since(handshakeStartInstant)\n\n\t\ttestResult := struct {\n\t\t\tHandshakePlusOneRTTMillis float32 `json:\"handshakePlusOneRTTMillis\"`\n\t\t\tPingRTTMilllis            float32 `json:\"pingRTTMilllis\"`\n\t\t}{\n\t\t\tHandshakePlusOneRTTMillis: float32(handshakePlusOneRTT.Microseconds()) / 1000,\n\t\t\tPingRTTMilllis:            float32(res.RTT.Microseconds()) / 1000,\n\t\t}\n\n\t\ttestResultJSON, err := json.Marshal(testResult)\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"Failed to marshal test result: %v\", err)\n\t\t}\n\t\tfmt.Println(string(testResultJSON))\n\t} else {\n\t\tvar listenAddr ma.Multiaddr\n\t\tfor _, addr := range host.Addrs() {\n\t\t\tif !manet.IsIPLoopback(addr) {\n\t\t\t\tlistenAddr = addr\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\t_, err := rClient.RPush(ctx, \"listenerAddr\", listenAddr.Encapsulate(ma.StringCast(\"/p2p/\"+host.ID().String())).String()).Result()\n\t\tif err != nil {\n\t\t\tlog.Fatal(\"Failed to send listener address\")\n\t\t}\n\t\ttime.Sleep(testTimeout)\n\t\tos.Exit(1)\n\t}\n}\n\nfunc generateTLSConfig() *tls.Config {\n\tpriv, err := rsa.GenerateKey(rand.Reader, 2048)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\ttmpl := &x509.Certificate{\n\t\tSerialNumber:          big.NewInt(1),\n\t\tSubject:               pkix.Name{},\n\t\tSignatureAlgorithm:    x509.SHA256WithRSA,\n\t\tNotBefore:             time.Now(),\n\t\tNotAfter:              time.Now().Add(time.Hour), // valid for an hour\n\t\tBasicConstraintsValid: true,\n\t}\n\tcertDER, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, priv.Public(), priv)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\treturn &tls.Config{\n\t\tCertificates: []tls.Certificate{{\n\t\t\tPrivateKey:  priv,\n\t\t\tCertificate: [][]byte{certDER},\n\t\t}},\n\t}\n}\n"
  },
  {
    "path": "test-plans/go.mod",
    "content": "module github.com/libp2p/go-libp2p/test-plans/m/v2\n\ngo 1.25.7\n\nrequire (\n\tgithub.com/go-redis/redis/v8 v8.11.5\n\tgithub.com/libp2p/go-libp2p v0.0.0\n\tgithub.com/multiformats/go-multiaddr v0.16.0\n)\n\nrequire (\n\tfilippo.io/bigmod v0.1.1-0.20260103110540-f8a47775ebe5 // indirect\n\tfilippo.io/keygen v0.0.0-20260114151900-8e2790ea4c5b // indirect\n\tgithub.com/benbjohnson/clock v1.3.5 // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect\n\tgithub.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect\n\tgithub.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect\n\tgithub.com/dunglas/httpsfv v1.1.0 // indirect\n\tgithub.com/flynn/noise v1.1.0 // indirect\n\tgithub.com/google/uuid v1.6.0 // indirect\n\tgithub.com/gorilla/websocket v1.5.3 // indirect\n\tgithub.com/huin/goupnp v1.3.0 // indirect\n\tgithub.com/ipfs/go-cid v0.5.0 // indirect\n\tgithub.com/jackpal/go-nat-pmp v1.0.2 // indirect\n\tgithub.com/jbenet/go-temp-err-catcher v0.1.0 // indirect\n\tgithub.com/klauspost/cpuid/v2 v2.2.10 // indirect\n\tgithub.com/koron/go-ssdp v0.0.6 // indirect\n\tgithub.com/libp2p/go-buffer-pool v0.1.0 // indirect\n\tgithub.com/libp2p/go-flow-metrics v0.2.0 // indirect\n\tgithub.com/libp2p/go-libp2p-asn-util v0.4.1 // indirect\n\tgithub.com/libp2p/go-msgio v0.3.0 // indirect\n\tgithub.com/libp2p/go-netroute v0.4.0 // indirect\n\tgithub.com/libp2p/go-reuseport v0.4.0 // indirect\n\tgithub.com/libp2p/go-yamux/v5 v5.0.1 // indirect\n\tgithub.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect\n\tgithub.com/miekg/dns v1.1.66 // indirect\n\tgithub.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect\n\tgithub.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect\n\tgithub.com/minio/sha256-simd v1.0.1 // indirect\n\tgithub.com/mr-tron/base58 v1.2.0 // indirect\n\tgithub.com/multiformats/go-base32 v0.1.0 // indirect\n\tgithub.com/multiformats/go-base36 v0.2.0 // indirect\n\tgithub.com/multiformats/go-multiaddr-dns v0.4.1 // indirect\n\tgithub.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect\n\tgithub.com/multiformats/go-multibase v0.2.0 // indirect\n\tgithub.com/multiformats/go-multicodec v0.9.1 // indirect\n\tgithub.com/multiformats/go-multihash v0.2.3 // indirect\n\tgithub.com/multiformats/go-multistream v0.6.1 // indirect\n\tgithub.com/multiformats/go-varint v0.0.7 // indirect\n\tgithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect\n\tgithub.com/onsi/gomega v1.36.3 // indirect\n\tgithub.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect\n\tgithub.com/pion/datachannel v1.6.0 // indirect\n\tgithub.com/pion/dtls/v3 v3.1.2 // indirect\n\tgithub.com/pion/ice/v4 v4.2.1 // indirect\n\tgithub.com/pion/interceptor v0.1.44 // indirect\n\tgithub.com/pion/logging v0.2.4 // indirect\n\tgithub.com/pion/mdns/v2 v2.1.0 // indirect\n\tgithub.com/pion/randutil v0.1.0 // indirect\n\tgithub.com/pion/rtcp v1.2.16 // indirect\n\tgithub.com/pion/rtp v1.10.1 // indirect\n\tgithub.com/pion/sctp v1.9.2 // indirect\n\tgithub.com/pion/sdp/v3 v3.0.18 // indirect\n\tgithub.com/pion/srtp/v3 v3.0.10 // indirect\n\tgithub.com/pion/stun/v3 v3.1.1 // indirect\n\tgithub.com/pion/transport/v4 v4.0.1 // indirect\n\tgithub.com/pion/turn/v4 v4.1.4 // indirect\n\tgithub.com/pion/webrtc/v4 v4.2.9 // indirect\n\tgithub.com/prometheus/client_golang v1.22.0 // indirect\n\tgithub.com/prometheus/client_model v0.6.2 // indirect\n\tgithub.com/prometheus/common v0.64.0 // indirect\n\tgithub.com/prometheus/procfs v0.16.1 // indirect\n\tgithub.com/quic-go/qpack v0.6.0 // indirect\n\tgithub.com/quic-go/quic-go v0.59.0 // indirect\n\tgithub.com/quic-go/webtransport-go v0.10.0 // indirect\n\tgithub.com/spaolacci/murmur3 v1.1.0 // indirect\n\tgithub.com/wlynxg/anet v0.0.5 // indirect\n\tgo.uber.org/dig v1.19.0 // indirect\n\tgo.uber.org/fx v1.24.0 // indirect\n\tgo.uber.org/mock v0.5.2 // indirect\n\tgo.uber.org/multierr v1.11.0 // indirect\n\tgo.uber.org/zap v1.27.0 // indirect\n\tgolang.org/x/crypto v0.48.0 // indirect\n\tgolang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476 // indirect\n\tgolang.org/x/mod v0.32.0 // indirect\n\tgolang.org/x/net v0.50.0 // indirect\n\tgolang.org/x/sync v0.19.0 // indirect\n\tgolang.org/x/sys v0.41.0 // indirect\n\tgolang.org/x/telemetry v0.0.0-20260109210033-bd525da824e2 // indirect\n\tgolang.org/x/text v0.34.0 // indirect\n\tgolang.org/x/time v0.12.0 // indirect\n\tgolang.org/x/tools v0.41.0 // indirect\n\tgoogle.golang.org/protobuf v1.36.6 // indirect\n\tlukechampine.com/blake3 v1.4.1 // indirect\n)\n\nreplace github.com/libp2p/go-libp2p => ../\n"
  },
  {
    "path": "test-plans/go.sum",
    "content": "filippo.io/bigmod v0.1.1-0.20260103110540-f8a47775ebe5 h1:JA0fFr+kxpqTdxR9LOBiTWpGNchqmkcsgmdeJZRclZ0=\nfilippo.io/bigmod v0.1.1-0.20260103110540-f8a47775ebe5/go.mod h1:OjOXDNlClLblvXdwgFFOQFJEocLhhtai8vGLy0JCZlI=\nfilippo.io/keygen v0.0.0-20260114151900-8e2790ea4c5b h1:REI1FbdW71yO56Are4XAxD+OS/e+BQsB3gE4mZRQEXY=\nfilippo.io/keygen v0.0.0-20260114151900-8e2790ea4c5b/go.mod h1:9nnw1SlYHYuPSo/3wjQzNjSbeHlq2NsKo5iEtfJPWP0=\ngithub.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=\ngithub.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=\ngithub.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=\ngithub.com/canonical/go-sp800.90a-drbg v0.0.0-20210314144037-6eeb1040d6c3 h1:oe6fCvaEpkhyW3qAicT0TnGtyht/UrgvOwMcEgLb7Aw=\ngithub.com/canonical/go-sp800.90a-drbg v0.0.0-20210314144037-6eeb1040d6c3/go.mod h1:qdP0gaj0QtgX2RUZhnlVrceJ+Qln8aSlDyJwelLLFeM=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU=\ngithub.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U=\ngithub.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8=\ngithub.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=\ngithub.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=\ngithub.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=\ngithub.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=\ngithub.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=\ngithub.com/dunglas/httpsfv v1.1.0 h1:Jw76nAyKWKZKFrpMMcL76y35tOpYHqQPzHQiwDvpe54=\ngithub.com/dunglas/httpsfv v1.1.0/go.mod h1:zID2mqw9mFsnt7YC3vYQ9/cjq30q41W+1AnDwH8TiMg=\ngithub.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg=\ngithub.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag=\ngithub.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=\ngithub.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=\ngithub.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=\ngithub.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=\ngithub.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=\ngithub.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=\ngithub.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=\ngithub.com/ipfs/go-cid v0.5.0 h1:goEKKhaGm0ul11IHA7I6p1GmKz8kEYniqFopaB5Otwg=\ngithub.com/ipfs/go-cid v0.5.0/go.mod h1:0L7vmeNXpQpUS9vt+yEARkJ8rOg43DF3iPgn4GIN0mk=\ngithub.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=\ngithub.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=\ngithub.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk=\ngithub.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk=\ngithub.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=\ngithub.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=\ngithub.com/koron/go-ssdp v0.0.6 h1:Jb0h04599eq/CY7rB5YEqPS83HmRfHP2azkxMN2rFtU=\ngithub.com/koron/go-ssdp v0.0.6/go.mod h1:0R9LfRJGek1zWTjN3JUNlm5INCDYGpRDfAptnct63fI=\ngithub.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8=\ngithub.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=\ngithub.com/libp2p/go-flow-metrics v0.2.0 h1:EIZzjmeOE6c8Dav0sNv35vhZxATIXWZg6j/C08XmmDw=\ngithub.com/libp2p/go-flow-metrics v0.2.0/go.mod h1:st3qqfu8+pMfh+9Mzqb2GTiwrAGjIPszEjZmtksN8Jc=\ngithub.com/libp2p/go-libp2p-asn-util v0.4.1 h1:xqL7++IKD9TBFMgnLPZR6/6iYhawHKHl950SO9L6n94=\ngithub.com/libp2p/go-libp2p-asn-util v0.4.1/go.mod h1:d/NI6XZ9qxw67b4e+NgpQexCIiFYJjErASrYW4PFDN8=\ngithub.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA=\ngithub.com/libp2p/go-libp2p-testing v0.12.0/go.mod h1:KcGDRXyN7sQCllucn1cOOS+Dmm7ujhfEyXQL5lvkcPg=\ngithub.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0=\ngithub.com/libp2p/go-msgio v0.3.0/go.mod h1:nyRM819GmVaF9LX3l03RMh10QdOroF++NBbxAb0mmDM=\ngithub.com/libp2p/go-netroute v0.4.0 h1:sZZx9hyANYUx9PZyqcgE/E1GUG3iEtTZHUEvdtXT7/Q=\ngithub.com/libp2p/go-netroute v0.4.0/go.mod h1:Nkd5ShYgSMS5MUKy/MU2T57xFoOKvvLR92Lic48LEyA=\ngithub.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s=\ngithub.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU=\ngithub.com/libp2p/go-yamux/v5 v5.0.1 h1:f0WoX/bEF2E8SbE4c/k1Mo+/9z0O4oC/hWEA+nfYRSg=\ngithub.com/libp2p/go-yamux/v5 v5.0.1/go.mod h1:en+3cdX51U0ZslwRdRLrvQsdayFt3TSUKvBGErzpWbU=\ngithub.com/marcopolo/simnet v0.0.4 h1:50Kx4hS9kFGSRIbrt9xUS3NJX33EyPqHVmpXvaKLqrY=\ngithub.com/marcopolo/simnet v0.0.4/go.mod h1:tfQF1u2DmaB6WHODMtQaLtClEf3a296CKQLq5gAsIS0=\ngithub.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk=\ngithub.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU=\ngithub.com/miekg/dns v1.1.66 h1:FeZXOS3VCVsKnEAd+wBkjMC3D2K+ww66Cq3VnCINuJE=\ngithub.com/miekg/dns v1.1.66/go.mod h1:jGFzBsSNbJw6z1HYut1RKBKHA9PBdxeHrZG8J+gC2WE=\ngithub.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c h1:bzE/A84HN25pxAuk9Eej1Kz9OUelF97nAc82bDquQI8=\ngithub.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c/go.mod h1:0SQS9kMwD2VsyFEB++InYyBJroV/FRmBgcydeSUcJms=\ngithub.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b h1:z78hV3sbSMAUoyUMM0I83AUIT6Hu17AWfgjzIbtrYFc=\ngithub.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b/go.mod h1:lxPUiZwKoFL8DUUmalo2yJJUCxbPKtm8OKfqr2/FTNU=\ngithub.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc h1:PTfri+PuQmWDqERdnNMiD9ZejrlswWrCpBEZgWOiTrc=\ngithub.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc/go.mod h1:cGKTAVKx4SxOuR/czcZ/E2RSJ3sfHs8FpHhQ5CWMf9s=\ngithub.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ=\ngithub.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=\ngithub.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=\ngithub.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=\ngithub.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=\ngithub.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=\ngithub.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=\ngithub.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE=\ngithub.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI=\ngithub.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0=\ngithub.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4=\ngithub.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo=\ngithub.com/multiformats/go-multiaddr v0.16.0 h1:oGWEVKioVQcdIOBlYM8BH1rZDWOGJSqr9/BKl6zQ4qc=\ngithub.com/multiformats/go-multiaddr v0.16.0/go.mod h1:JSVUmXDjsVFiW7RjIFMP7+Ev+h1DTbiJgVeTV/tcmP0=\ngithub.com/multiformats/go-multiaddr-dns v0.4.1 h1:whi/uCLbDS3mSEUMb1MsoT4uzUeZB0N32yzufqS0i5M=\ngithub.com/multiformats/go-multiaddr-dns v0.4.1/go.mod h1:7hfthtB4E4pQwirrz+J0CcDUfbWzTqEzVyYKKIKpgkc=\ngithub.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E=\ngithub.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo=\ngithub.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g=\ngithub.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk=\ngithub.com/multiformats/go-multicodec v0.9.1 h1:x/Fuxr7ZuR4jJV4Os5g444F7xC4XmyUaT/FWtE+9Zjo=\ngithub.com/multiformats/go-multicodec v0.9.1/go.mod h1:LLWNMtyV5ithSBUo3vFIMaeDy+h3EbkMTek1m+Fybbo=\ngithub.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew=\ngithub.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U=\ngithub.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM=\ngithub.com/multiformats/go-multistream v0.6.1 h1:4aoX5v6T+yWmc2raBHsTvzmFhOI8WVOer28DeBBEYdQ=\ngithub.com/multiformats/go-multistream v0.6.1/go.mod h1:ksQf6kqHAb6zIsyw7Zm+gAuVo57Qbq84E27YlYqavqw=\ngithub.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8=\ngithub.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=\ngithub.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=\ngithub.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=\ngithub.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=\ngithub.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=\ngithub.com/onsi/gomega v1.36.3 h1:hID7cr8t3Wp26+cYnfcjR6HpJ00fdogN6dqZ1t6IylU=\ngithub.com/onsi/gomega v1.36.3/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=\ngithub.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=\ngithub.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=\ngithub.com/pion/datachannel v1.6.0 h1:XecBlj+cvsxhAMZWFfFcPyUaDZtd7IJvrXqlXD/53i0=\ngithub.com/pion/datachannel v1.6.0/go.mod h1:ur+wzYF8mWdC+Mkis5Thosk+u/VOL287apDNEbFpsIk=\ngithub.com/pion/dtls/v3 v3.1.2 h1:gqEdOUXLtCGW+afsBLO0LtDD8GnuBBjEy6HRtyofZTc=\ngithub.com/pion/dtls/v3 v3.1.2/go.mod h1:Hw/igcX4pdY69z1Hgv5x7wJFrUkdgHwAn/Q/uo7YHRo=\ngithub.com/pion/ice/v4 v4.2.1 h1:XPRYXaLiFq3LFDG7a7bMrmr3mFr27G/gtXN3v/TVfxY=\ngithub.com/pion/ice/v4 v4.2.1/go.mod h1:2quLV1S5v1tAx3VvAJaH//KGitRXvo4RKlX6D3tnN+c=\ngithub.com/pion/interceptor v0.1.44 h1:sNlZwM8dWXU9JQAkJh8xrarC0Etn8Oolcniukmuy0/I=\ngithub.com/pion/interceptor v0.1.44/go.mod h1:4atVlBkcgXuUP+ykQF0qOCGU2j7pQzX2ofvPRFsY5RY=\ngithub.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=\ngithub.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=\ngithub.com/pion/mdns/v2 v2.1.0 h1:3IJ9+Xio6tWYjhN6WwuY142P/1jA0D5ERaIqawg/fOY=\ngithub.com/pion/mdns/v2 v2.1.0/go.mod h1:pcez23GdynwcfRU1977qKU0mDxSeucttSHbCSfFOd9A=\ngithub.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=\ngithub.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=\ngithub.com/pion/rtcp v1.2.16 h1:fk1B1dNW4hsI78XUCljZJlC4kZOPk67mNRuQ0fcEkSo=\ngithub.com/pion/rtcp v1.2.16/go.mod h1:/as7VKfYbs5NIb4h6muQ35kQF/J0ZVNz2Z3xKoCBYOo=\ngithub.com/pion/rtp v1.10.1 h1:xP1prZcCTUuhO2c83XtxyOHJteISg6o8iPsE2acaMtA=\ngithub.com/pion/rtp v1.10.1/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM=\ngithub.com/pion/sctp v1.9.2 h1:HxsOzEV9pWoeggv7T5kewVkstFNcGvhMPx0GvUOUQXo=\ngithub.com/pion/sctp v1.9.2/go.mod h1:OTOlsQ5EDQ6mQ0z4MUGXt2CgQmKyafBEXhUVqLRB6G8=\ngithub.com/pion/sdp/v3 v3.0.18 h1:l0bAXazKHpepazVdp+tPYnrsy9dfh7ZbT8DxesH5ZnI=\ngithub.com/pion/sdp/v3 v3.0.18/go.mod h1:ZREGo6A9ZygQ9XkqAj5xYCQtQpif0i6Pa81HOiAdqQ8=\ngithub.com/pion/srtp/v3 v3.0.10 h1:tFirkpBb3XccP5VEXLi50GqXhv5SKPxqrdlhDCJlZrQ=\ngithub.com/pion/srtp/v3 v3.0.10/go.mod h1:3mOTIB0cq9qlbn59V4ozvv9ClW/BSEbRp4cY0VtaR7M=\ngithub.com/pion/stun/v3 v3.1.1 h1:CkQxveJ4xGQjulGSROXbXq94TAWu8gIX2dT+ePhUkqw=\ngithub.com/pion/stun/v3 v3.1.1/go.mod h1:qC1DfmcCTQjl9PBaMa5wSn3x9IPmKxSdcCsxBcDBndM=\ngithub.com/pion/transport/v3 v3.1.1 h1:Tr684+fnnKlhPceU+ICdrw6KKkTms+5qHMgw6bIkYOM=\ngithub.com/pion/transport/v3 v3.1.1/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ=\ngithub.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o=\ngithub.com/pion/transport/v4 v4.0.1/go.mod h1:nEuEA4AD5lPdcIegQDpVLgNoDGreqM/YqmEx3ovP4jM=\ngithub.com/pion/turn/v4 v4.1.4 h1:EU11yMXKIsK43FhcUnjLlrhE4nboHZq+TXBIi3QpcxQ=\ngithub.com/pion/turn/v4 v4.1.4/go.mod h1:ES1DXVFKnOhuDkqn9hn5VJlSWmZPaRJLyBXoOeO/BmQ=\ngithub.com/pion/webrtc/v4 v4.2.9 h1:DZIh1HAhPIL3RvwEDFsmL5hfPSLEpxsQk9/Jir2vkJE=\ngithub.com/pion/webrtc/v4 v4.2.9/go.mod h1:9EmLZve0H76eTzf8v2FmchZ6tcBXtDgpfTEu+drW6SY=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=\ngithub.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=\ngithub.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=\ngithub.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=\ngithub.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4=\ngithub.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=\ngithub.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=\ngithub.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=\ngithub.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=\ngithub.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=\ngithub.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=\ngithub.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=\ngithub.com/quic-go/webtransport-go v0.10.0 h1:LqXXPOXuETY5Xe8ITdGisBzTYmUOy5eSj+9n4hLTjHI=\ngithub.com/quic-go/webtransport-go v0.10.0/go.mod h1:LeGIXr5BQKE3UsynwVBeQrU1TPrbh73MGoC6jd+V7ow=\ngithub.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=\ngithub.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=\ngithub.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=\ngithub.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=\ngithub.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=\ngo.uber.org/dig v1.19.0 h1:BACLhebsYdpQ7IROQ1AGPjrXcP5dF80U3gKoFzbaq/4=\ngo.uber.org/dig v1.19.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=\ngo.uber.org/fx v1.24.0 h1:wE8mruvpg2kiiL1Vqd0CC+tr0/24XIB10Iwp2lLWzkg=\ngo.uber.org/fx v1.24.0/go.mod h1:AmDeGyS+ZARGKM4tlH4FY2Jr63VjbEDJHtqXTGP5hbo=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngo.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=\ngo.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=\ngo.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=\ngo.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=\ngo.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=\ngo.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=\ngolang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=\ngolang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=\ngolang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476 h1:bsqhLWFR6G6xiQcb+JoGqdKdRU6WzPWmK8E0jxTjzo4=\ngolang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=\ngolang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=\ngolang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=\ngolang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=\ngolang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=\ngolang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=\ngolang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/telemetry v0.0.0-20260109210033-bd525da824e2 h1:O1cMQHRfwNpDfDJerqRoE2oD+AFlyid87D40L/OkkJo=\ngolang.org/x/telemetry v0.0.0-20260109210033-bd525da824e2/go.mod h1:b7fPSJ0pKZ3ccUh8gnTONJxhn3c/PS6tyzQvyqw4iA8=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=\ngolang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=\ngolang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=\ngolang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=\ngolang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=\ngoogle.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=\ngopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nlukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=\nlukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=\n"
  },
  {
    "path": "test-plans/ping-version.json",
    "content": "{\n    \"id\": \"go-libp2p-head\",\n    \"containerImageID\": \"go-libp2p-head\",\n    \"transports\": [\n        \"tcp\",\n        \"ws\",\n        \"wss\",\n        \"quic-v1\",\n        \"webtransport\",\n        \"webrtc-direct\"\n    ],\n    \"secureChannels\": [\n        \"tls\",\n        \"noise\"\n    ],\n    \"muxers\": [\n        \"yamux\"\n    ]\n}\n"
  },
  {
    "path": "tools.go",
    "content": "//go:build tools\n\npackage libp2p\n\nimport (\n\t_ \"go.uber.org/mock/mockgen\"\n\t_ \"golang.org/x/tools/cmd/goimports\"\n\t_ \"google.golang.org/protobuf/cmd/protoc-gen-go\"\n)\n"
  },
  {
    "path": "version.json",
    "content": "{\n  \"version\": \"v0.48.0\"\n}\n"
  },
  {
    "path": "x/rate/limiter.go",
    "content": "// Package rate provides rate limiting functionality at a global, network, and subnet level.\npackage rate\n\nimport (\n\t\"container/heap\"\n\t\"net/netip\"\n\t\"slices\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\tmanet \"github.com/multiformats/go-multiaddr/net\"\n\t\"golang.org/x/time/rate\"\n)\n\n// Limit is the configuration for a token bucket rate limiter.\n// The bucket has a capacity of Burst, and is refilled at a rate of RPS tokens per second.\n// Initially, buckets are completley full, i.e. tokens in the bucket is equal to `Burst`.\n// In any given time interval T seconds, maximum events allowed will be `T*RPS + Burst`.\ntype Limit struct {\n\t// RPS is the rate of requests per second in steady state.\n\tRPS float64\n\t// Burst is the number of requests allowed over the RPS.\n\tBurst int\n}\n\n// PrefixLimit is a rate limit configuration that applies to a specific network prefix.\ntype PrefixLimit struct {\n\tPrefix netip.Prefix\n\tLimit\n}\n\n// SubnetLimit is a rate limit configuration that applies to a specific subnet.\ntype SubnetLimit struct {\n\tPrefixLength int\n\tLimit\n}\n\n// Limiter rate limits new streams for a service. It allows setting NetworkPrefix specific,\n// global, and subnet specific limits. Use 0 for no rate limiting.\n// The limiter maintains state that must be periodically cleaned up using Cleanup\ntype Limiter struct {\n\t// NetworkPrefixLimits are limits for streams with peer IPs belonging to specific subnets.\n\t// It can be used to increase the limit for trusted networks and decrease the limit for specific networks.\n\tNetworkPrefixLimits []PrefixLimit\n\t// GlobalLimit is the limit for all streams where the peer IP doesn't fall within any\n\t// of the `NetworkPrefixLimits`\n\tGlobalLimit Limit\n\t// SubnetRateLimiter is a rate limiter for subnets.\n\tSubnetRateLimiter SubnetLimiter\n\n\tinitOnce             sync.Once\n\tglobalBucket         *rate.Limiter\n\tnetworkPrefixBuckets []*rate.Limiter // ith element ratelimits ith NetworkPrefixLimits\n}\n\nfunc (r *Limiter) init() {\n\tr.initOnce.Do(func() {\n\t\tif r.GlobalLimit.RPS == 0 {\n\t\t\tr.globalBucket = rate.NewLimiter(rate.Inf, 0)\n\t\t} else {\n\t\t\tr.globalBucket = rate.NewLimiter(rate.Limit(r.GlobalLimit.RPS), r.GlobalLimit.Burst)\n\t\t}\n\t\t// clone the slice in case it's shared with other limiters\n\t\tr.NetworkPrefixLimits = slices.Clone(r.NetworkPrefixLimits)\n\t\t// sort such that the widest prefix (smallest bit count) is last.\n\t\tslices.SortFunc(r.NetworkPrefixLimits, func(a, b PrefixLimit) int { return b.Prefix.Bits() - a.Prefix.Bits() })\n\t\tr.networkPrefixBuckets = make([]*rate.Limiter, 0, len(r.NetworkPrefixLimits))\n\t\tfor _, limit := range r.NetworkPrefixLimits {\n\t\t\tif limit.RPS == 0 {\n\t\t\t\tr.networkPrefixBuckets = append(r.networkPrefixBuckets, rate.NewLimiter(rate.Inf, 0))\n\t\t\t} else {\n\t\t\t\tr.networkPrefixBuckets = append(r.networkPrefixBuckets, rate.NewLimiter(rate.Limit(limit.RPS), limit.Burst))\n\t\t\t}\n\t\t}\n\t})\n}\n\n// Limit rate limits a StreamHandler function.\nfunc (r *Limiter) Limit(f func(s network.Stream)) func(s network.Stream) {\n\tr.init()\n\treturn func(s network.Stream) {\n\t\taddr := s.Conn().RemoteMultiaddr()\n\t\tip, err := manet.ToIP(addr)\n\t\tif err != nil {\n\t\t\tip = nil\n\t\t}\n\t\tipAddr, ok := netip.AddrFromSlice(ip)\n\t\tif !ok {\n\t\t\tipAddr = netip.Addr{}\n\t\t}\n\t\tif !r.Allow(ipAddr) {\n\t\t\t_ = s.ResetWithError(network.StreamRateLimited)\n\t\t\treturn\n\t\t}\n\t\tf(s)\n\t}\n}\n\n// Allow returns true if requests for `ipAddr` are within specified rate limits\nfunc (r *Limiter) Allow(ipAddr netip.Addr) bool {\n\tr.init()\n\t// Check buckets from the most specific to the least.\n\t//\n\t// This ensures that a single peer cannot take up all the tokens in the global\n\t// rate limiting bucket. We *MUST* follow this order because the rate limiter\n\t// implementation doesn't have a `ReturnToken` method. If we checked the global\n\t// bucket before the specific bucket, and the specific bucket rejected the\n\t// request, there's no way to return the token to the global bucket. So all\n\t// rejected requests from the specific bucket would take up tokens from the global bucket.\n\n\t// prefixs have been sorted from most to least specific so rejected requests for more\n\t// specific prefixes don't take up tokens from the less specific prefixes.\n\tisWithinNetworkPrefix := false\n\tfor i, limit := range r.NetworkPrefixLimits {\n\t\tif limit.Prefix.Contains(ipAddr) {\n\t\t\tif !r.networkPrefixBuckets[i].Allow() {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tisWithinNetworkPrefix = true\n\t\t}\n\t}\n\tif isWithinNetworkPrefix {\n\t\treturn true\n\t}\n\n\tif !r.SubnetRateLimiter.Allow(ipAddr, time.Now()) {\n\t\treturn false\n\t}\n\treturn r.globalBucket.Allow()\n}\n\n// SubnetLimiter rate limits requests per ip subnet.\ntype SubnetLimiter struct {\n\t// IPv4SubnetLimits are the per subnet limits for streams with IPv4 Peers.\n\tIPv4SubnetLimits []SubnetLimit\n\t// IPv6SubnetLimits are the per subnet limits for streams with IPv6 Peers.\n\tIPv6SubnetLimits []SubnetLimit\n\t// GracePeriod is the time to wait to remove a full capacity bucket.\n\t// Keeping a bucket around helps prevent allocations\n\tGracePeriod time.Duration\n\n\tinitOnce  sync.Once\n\tmx        sync.Mutex\n\tipv4Heaps []*bucketHeap\n\tipv6Heaps []*bucketHeap\n}\n\nfunc (s *SubnetLimiter) init() {\n\ts.initOnce.Do(func() {\n\t\t// smaller prefix length, i.e. largest subnet, last\n\t\tslices.SortFunc(s.IPv4SubnetLimits, func(a, b SubnetLimit) int { return b.PrefixLength - a.PrefixLength })\n\t\tslices.SortFunc(s.IPv6SubnetLimits, func(a, b SubnetLimit) int { return b.PrefixLength - a.PrefixLength })\n\n\t\ts.ipv4Heaps = make([]*bucketHeap, len(s.IPv4SubnetLimits))\n\t\tfor i := range s.IPv4SubnetLimits {\n\t\t\ts.ipv4Heaps[i] = &bucketHeap{\n\t\t\t\tprefixBucket:  make([]prefixBucketWithExpiry, 0),\n\t\t\t\tprefixToIndex: make(map[netip.Prefix]int),\n\t\t\t}\n\t\t\theap.Init(s.ipv4Heaps[i])\n\t\t}\n\n\t\ts.ipv6Heaps = make([]*bucketHeap, len(s.IPv6SubnetLimits))\n\t\tfor i := range s.IPv6SubnetLimits {\n\t\t\ts.ipv6Heaps[i] = &bucketHeap{\n\t\t\t\tprefixBucket:  make([]prefixBucketWithExpiry, 0),\n\t\t\t\tprefixToIndex: make(map[netip.Prefix]int),\n\t\t\t}\n\t\t\theap.Init(s.ipv6Heaps[i])\n\t\t}\n\t})\n}\n\n// Allow returns true if requests for `ipAddr` are within specified rate limits\nfunc (s *SubnetLimiter) Allow(ipAddr netip.Addr, now time.Time) bool {\n\ts.init()\n\ts.mx.Lock()\n\tdefer s.mx.Unlock()\n\n\ts.cleanUp(now)\n\n\tvar subNetLimits []SubnetLimit\n\tvar heaps []*bucketHeap\n\tif ipAddr.Is4() {\n\t\tsubNetLimits = s.IPv4SubnetLimits\n\t\theaps = s.ipv4Heaps\n\t} else {\n\t\tsubNetLimits = s.IPv6SubnetLimits\n\t\theaps = s.ipv6Heaps\n\t}\n\n\tfor i, limit := range subNetLimits {\n\t\tprefix, err := ipAddr.Prefix(limit.PrefixLength)\n\t\tif err != nil {\n\t\t\treturn false // we have a ipaddr this shouldn't happen\n\t\t}\n\n\t\tbucket := heaps[i].Get(prefix)\n\t\tif bucket == (prefixBucketWithExpiry{}) {\n\t\t\tbucket = prefixBucketWithExpiry{\n\t\t\t\tPrefix:      prefix,\n\t\t\t\ttokenBucket: tokenBucket{rate.NewLimiter(rate.Limit(limit.RPS), limit.Burst)},\n\t\t\t\tExpiry:      now,\n\t\t\t}\n\t\t}\n\n\t\tif !bucket.Allow() {\n\t\t\t// bucket is empty, its expiry would have been set correctly the last time\n\t\t\t// it allowed a request.\n\t\t\treturn false\n\t\t}\n\t\tbucket.Expiry = bucket.FullAt(now).Add(s.GracePeriod)\n\t\theaps[i].Upsert(bucket)\n\t}\n\treturn true\n}\n\n// cleanUp removes limiters that have expired by now.\nfunc (s *SubnetLimiter) cleanUp(now time.Time) {\n\tfor _, h := range s.ipv4Heaps {\n\t\th.Expire(now)\n\t}\n\tfor _, h := range s.ipv6Heaps {\n\t\th.Expire(now)\n\t}\n}\n\n// tokenBucket is a *rate.Limiter with a `FullAt` method.\ntype tokenBucket struct {\n\t*rate.Limiter\n}\n\n// FullAt returns the instant at which the bucket will be full.\nfunc (b *tokenBucket) FullAt(now time.Time) time.Time {\n\ttokensNeeded := float64(b.Burst()) - b.TokensAt(now)\n\trefillRate := float64(b.Limit())\n\teta := time.Duration((tokensNeeded / refillRate) * float64(time.Second))\n\treturn now.Add(eta)\n}\n\n// prefixBucketWithExpiry is a token bucket with a prefix and Expiry. The expiry is when the bucket\n// will be full with tokens.\ntype prefixBucketWithExpiry struct {\n\ttokenBucket\n\tPrefix netip.Prefix\n\tExpiry time.Time\n}\n\n// bucketHeap is a heap of buckets ordered by their Expiry. At expiry, the bucket\n// is removed from the heap as a full bucket is indistinguishable from a new bucket.\ntype bucketHeap struct {\n\tprefixBucket  []prefixBucketWithExpiry\n\tprefixToIndex map[netip.Prefix]int\n}\n\nvar _ heap.Interface = (*bucketHeap)(nil)\n\n// Upsert replaces the bucket with prefix `b.Prefix` with the provided bucket, `b`, or\n// inserts `b` if no bucket with prefix `b.Prefix` exists.\nfunc (h *bucketHeap) Upsert(b prefixBucketWithExpiry) {\n\tif i, ok := h.prefixToIndex[b.Prefix]; ok {\n\t\th.prefixBucket[i] = b\n\t\theap.Fix(h, i)\n\t\treturn\n\t}\n\theap.Push(h, b)\n}\n\n// Get returns the limiter for a prefix\nfunc (h *bucketHeap) Get(prefix netip.Prefix) prefixBucketWithExpiry {\n\tif i, ok := h.prefixToIndex[prefix]; ok {\n\t\treturn h.prefixBucket[i]\n\t}\n\treturn prefixBucketWithExpiry{}\n}\n\n// Expire removes elements with expiry before `expiry`\nfunc (h *bucketHeap) Expire(expiry time.Time) {\n\tfor h.Len() > 0 {\n\t\toldest := h.prefixBucket[0]\n\t\tif oldest.Expiry.After(expiry) {\n\t\t\tbreak\n\t\t}\n\t\theap.Pop(h)\n\t}\n}\n\n// Methods for the heap interface\n\n// Len returns the length of the heap\nfunc (h *bucketHeap) Len() int {\n\treturn len(h.prefixBucket)\n}\n\n// Less compares two elements in the heap\nfunc (h *bucketHeap) Less(i, j int) bool {\n\treturn h.prefixBucket[i].Expiry.Before(h.prefixBucket[j].Expiry)\n}\n\n// Swap swaps two elements in the heap\nfunc (h *bucketHeap) Swap(i, j int) {\n\th.prefixBucket[i], h.prefixBucket[j] = h.prefixBucket[j], h.prefixBucket[i]\n\th.prefixToIndex[h.prefixBucket[i].Prefix] = i\n\th.prefixToIndex[h.prefixBucket[j].Prefix] = j\n}\n\n// Push adds a new element to the heap\nfunc (h *bucketHeap) Push(x any) {\n\titem := x.(prefixBucketWithExpiry)\n\th.prefixBucket = append(h.prefixBucket, item)\n\th.prefixToIndex[item.Prefix] = len(h.prefixBucket) - 1\n}\n\n// Pop removes and returns the top element from the heap\nfunc (h *bucketHeap) Pop() any {\n\tn := len(h.prefixBucket)\n\titem := h.prefixBucket[n-1]\n\th.prefixBucket = h.prefixBucket[0 : n-1]\n\tdelete(h.prefixToIndex, item.Prefix)\n\treturn item\n}\n"
  },
  {
    "path": "x/rate/limiter_test.go",
    "content": "package rate\n\nimport (\n\t\"fmt\"\n\t\"net/netip\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"golang.org/x/time/rate\"\n)\n\nconst rateLimitErrorTolerance = 0.05\n\nfunc getSleepDurationAndRequestCount(rps float64) (time.Duration, int) {\n\tsleepDuration := 100 * time.Millisecond\n\trequestCount := int(sleepDuration.Seconds() * float64(rps))\n\tif requestCount < 1 {\n\t\t// Adding 1ms to ensure we do get 1 request. If the rate is low enough that\n\t\t// 100ms won't have a single request adding 1ms won't error here.\n\t\tsleepDuration = time.Duration((1/rps)*float64(time.Second)) + 1*time.Millisecond\n\t\trequestCount = 1\n\t}\n\treturn sleepDuration, requestCount\n}\n\nfunc assertLimiter(t *testing.T, rl *Limiter, ipAddr netip.Addr, allowed, errorMargin int) {\n\tt.Helper()\n\tfor range allowed {\n\t\trequire.True(t, rl.Allow(ipAddr))\n\t}\n\tfor range errorMargin {\n\t\trl.Allow(ipAddr)\n\t}\n\trequire.False(t, rl.Allow(ipAddr))\n}\n\nfunc TestLimiterGlobal(t *testing.T) {\n\taddr := netip.MustParseAddr(\"127.0.0.1\")\n\tlimits := []Limit{\n\t\t{RPS: 0.0, Burst: 1},\n\t\t{RPS: 0.8, Burst: 1},\n\t\t{RPS: 10, Burst: 20},\n\t\t{RPS: 100, Burst: 200},\n\t\t{RPS: 1000, Burst: 2000},\n\t}\n\tfor _, limit := range limits {\n\t\tt.Run(fmt.Sprintf(\"limit %0.1f\", limit.RPS), func(t *testing.T) {\n\t\t\trl := &Limiter{\n\t\t\t\tGlobalLimit: limit,\n\t\t\t}\n\t\t\tif limit.RPS == 0 {\n\t\t\t\t// 0 implies no rate limiting, any large number would do\n\t\t\t\tfor range 1000 {\n\t\t\t\t\trequire.True(t, rl.Allow(addr))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tassertLimiter(t, rl, addr, limit.Burst, int(limit.RPS*rateLimitErrorTolerance))\n\t\t\tsleepDuration, requestCount := getSleepDurationAndRequestCount(limit.RPS)\n\t\t\ttime.Sleep(sleepDuration)\n\t\t\tassertLimiter(t, rl, addr, requestCount, int(float64(requestCount)*rateLimitErrorTolerance))\n\t\t})\n\t}\n}\n\nfunc TestLimiterNetworkPrefix(t *testing.T) {\n\tlocal := netip.MustParseAddr(\"127.0.0.1\")\n\tpublic := netip.MustParseAddr(\"1.1.1.1\")\n\trl := &Limiter{\n\t\tNetworkPrefixLimits: []PrefixLimit{\n\t\t\t{Prefix: netip.MustParsePrefix(\"127.0.0.0/24\"), Limit: Limit{}},\n\t\t},\n\t\tGlobalLimit: Limit{RPS: 10, Burst: 10},\n\t}\n\t// element within prefix is allowed even over the limit\n\tfor range rl.GlobalLimit.Burst + 100 {\n\t\trequire.True(t, rl.Allow(local))\n\t}\n\t// rate limit public ips\n\tassertLimiter(t, rl, public, rl.GlobalLimit.Burst, int(rl.GlobalLimit.RPS*rateLimitErrorTolerance))\n\n\t// public ip rejected\n\trequire.False(t, rl.Allow(public))\n\t// local ip accepted\n\tfor range 100 {\n\t\trequire.True(t, rl.Allow(local))\n\t}\n}\n\nfunc TestLimiterNetworkPrefixWidth(t *testing.T) {\n\ta1 := netip.MustParseAddr(\"1.1.1.1\")\n\ta2 := netip.MustParseAddr(\"1.1.0.1\")\n\n\twideLimit := 20\n\tnarrowLimit := 10\n\trl := &Limiter{\n\t\tNetworkPrefixLimits: []PrefixLimit{\n\t\t\t{Prefix: netip.MustParsePrefix(\"1.1.0.0/16\"), Limit: Limit{RPS: 0.01, Burst: wideLimit}},\n\t\t\t{Prefix: netip.MustParsePrefix(\"1.1.1.0/24\"), Limit: Limit{RPS: 0.01, Burst: narrowLimit}},\n\t\t},\n\t}\n\tfor range 2 * wideLimit {\n\t\trl.Allow(a1)\n\t}\n\t// a1 rejected\n\trequire.False(t, rl.Allow(a1))\n\t// a2 accepted\n\tfor range wideLimit - narrowLimit {\n\t\trequire.True(t, rl.Allow(a2))\n\t}\n}\n\nfunc subnetAddrs(prefix netip.Prefix) func() netip.Addr {\n\tnext := prefix.Addr()\n\treturn func() netip.Addr {\n\t\taddr := next\n\t\tnext = addr.Next()\n\t\tif !prefix.Contains(addr) {\n\t\t\tnext = prefix.Addr()\n\t\t\taddr = next\n\t\t}\n\t\treturn addr\n\t}\n}\n\nfunc TestSubnetLimiter(t *testing.T) {\n\tassertOutput := func(outcome bool, rl *SubnetLimiter, subnetAddrs func() netip.Addr, n int) {\n\t\tt.Helper()\n\t\tfor range n {\n\t\t\trequire.Equal(t, outcome, rl.Allow(subnetAddrs(), time.Now()), \"%d\", n)\n\t\t}\n\t}\n\n\tt.Run(\"Simple\", func(*testing.T) {\n\t\t// Keep the refil rate low\n\t\tv4Small := SubnetLimit{PrefixLength: 24, Limit: Limit{RPS: 0.0001, Burst: 10}}\n\t\tv4Large := SubnetLimit{PrefixLength: 16, Limit: Limit{RPS: 0.0001, Burst: 19}}\n\n\t\tv6Small := SubnetLimit{PrefixLength: 64, Limit: Limit{RPS: 0.0001, Burst: 10}}\n\t\tv6Large := SubnetLimit{PrefixLength: 48, Limit: Limit{RPS: 0.0001, Burst: 17}}\n\t\trl := &SubnetLimiter{\n\t\t\tIPv4SubnetLimits: []SubnetLimit{v4Large, v4Small},\n\t\t\tIPv6SubnetLimits: []SubnetLimit{v6Large, v6Small},\n\t\t}\n\n\t\tv4SubnetAddr1 := subnetAddrs(netip.MustParsePrefix(\"192.168.1.1/24\"))\n\t\tv4SubnetAddr2 := subnetAddrs(netip.MustParsePrefix(\"192.168.2.1/24\"))\n\t\tv6SubnetAddr1 := subnetAddrs(netip.MustParsePrefix(\"2001:0:0:1::/64\"))\n\t\tv6SubnetAddr2 := subnetAddrs(netip.MustParsePrefix(\"2001:0:0:2::/64\"))\n\n\t\tassertOutput(true, rl, v4SubnetAddr1, v4Small.Burst)\n\t\tassertOutput(false, rl, v4SubnetAddr1, v4Large.Burst)\n\n\t\tassertOutput(true, rl, v4SubnetAddr2, v4Large.Burst-v4Small.Burst)\n\t\tassertOutput(false, rl, v4SubnetAddr2, v4Large.Burst)\n\n\t\tassertOutput(true, rl, v6SubnetAddr1, v6Small.Burst)\n\t\tassertOutput(false, rl, v6SubnetAddr1, v6Large.Burst)\n\n\t\tassertOutput(true, rl, v6SubnetAddr2, v6Large.Burst-v6Small.Burst)\n\t\tassertOutput(false, rl, v6SubnetAddr2, v6Large.Burst)\n\t})\n\n\tt.Run(\"Complex\", func(*testing.T) {\n\t\tlimits := []SubnetLimit{\n\t\t\t{PrefixLength: 32, Limit: Limit{RPS: 0.01, Burst: 10}},\n\t\t\t{PrefixLength: 24, Limit: Limit{RPS: 0.01, Burst: 20}},\n\t\t\t{PrefixLength: 16, Limit: Limit{RPS: 0.01, Burst: 30}},\n\t\t\t{PrefixLength: 8, Limit: Limit{RPS: 0.01, Burst: 40}},\n\t\t}\n\t\trl := &SubnetLimiter{\n\t\t\tIPv4SubnetLimits: limits,\n\t\t}\n\n\t\tsnAddrs := []func() netip.Addr{\n\t\t\tsubnetAddrs(netip.MustParsePrefix(\"192.168.1.1/32\")),\n\t\t\tsubnetAddrs(netip.MustParsePrefix(\"192.168.1.2/24\")),\n\t\t\tsubnetAddrs(netip.MustParsePrefix(\"192.168.2.1/16\")),\n\t\t\tsubnetAddrs(netip.MustParsePrefix(\"192.0.1.1/8\")),\n\t\t}\n\t\tfor i, addrsFunc := range snAddrs {\n\t\t\tprev := 0\n\t\t\tif i > 0 {\n\t\t\t\tprev = limits[i-1].Burst\n\t\t\t}\n\t\t\tassertOutput(true, rl, addrsFunc, limits[i].Burst-prev)\n\t\t\tassertOutput(false, rl, addrsFunc, limits[i].Burst)\n\t\t}\n\t})\n\n\tt.Run(\"Zero\", func(t *testing.T) {\n\t\tsl := SubnetLimiter{}\n\t\tfor range 10000 {\n\t\t\trequire.True(t, sl.Allow(netip.IPv6Loopback(), time.Now()))\n\t\t}\n\t})\n}\n\nfunc TestSubnetLimiterCleanup(t *testing.T) {\n\ttc := []struct {\n\t\tLimit\n\t\tTTL time.Duration\n\t}{\n\t\t{Limit: Limit{RPS: 1, Burst: 10}, TTL: 10 * time.Second},\n\t\t{Limit: Limit{RPS: 0.1, Burst: 2}, TTL: 20 * time.Second},\n\t\t{Limit: Limit{RPS: 1, Burst: 100}, TTL: 100 * time.Second},\n\t\t{Limit: Limit{RPS: 3, Burst: 6}, TTL: 2 * time.Second},\n\t}\n\tfor i, tt := range tc {\n\t\tt.Run(fmt.Sprintf(\"%d\", i), func(t *testing.T) {\n\t\t\tip1, ip2 := netip.IPv6Loopback(), netip.MustParseAddr(\"2001::\")\n\t\t\tsl := SubnetLimiter{IPv6SubnetLimits: []SubnetLimit{{PrefixLength: 64, Limit: tt.Limit}}}\n\t\t\tnow := time.Now()\n\t\t\t// Empty the ip1 bucket\n\t\t\tfor range tt.Burst {\n\t\t\t\trequire.True(t, sl.Allow(ip1, now))\n\t\t\t}\n\t\t\tfor range tt.Burst / 2 {\n\t\t\t\trequire.True(t, sl.Allow(ip2, now))\n\t\t\t}\n\t\t\tepsilon := 100 * time.Millisecond\n\t\t\t// just before ip1 expiry\n\t\t\tnow = now.Add(tt.TTL).Add(-epsilon)\n\t\t\tsl.cleanUp(now) // ip2 will be removed\n\t\t\trequire.Equal(t, 1, sl.ipv6Heaps[0].Len())\n\t\t\t// just after ip1 expiry\n\t\t\tnow = now.Add(2 * epsilon)\n\t\t\trequire.True(t, sl.Allow(ip2, now))        // remove the ip1 bucket\n\t\t\trequire.Equal(t, 1, sl.ipv6Heaps[0].Len()) // ip2 added in the previous call\n\t\t})\n\t}\n}\n\nfunc TestTokenBucketFullAfter(t *testing.T) {\n\ttc := []struct {\n\t\t*rate.Limiter\n\t\tFullAfter time.Duration\n\t}{\n\t\t{Limiter: rate.NewLimiter(1, 10), FullAfter: 10 * time.Second},\n\t\t{Limiter: rate.NewLimiter(0.01, 10), FullAfter: 1000 * time.Second},\n\t\t{Limiter: rate.NewLimiter(0.01, 1), FullAfter: 100 * time.Second},\n\t}\n\tfor i, tt := range tc {\n\t\tt.Run(fmt.Sprintf(\"%d\", i), func(t *testing.T) {\n\t\t\tb := tokenBucket{tt.Limiter}\n\t\t\tnow := time.Now()\n\t\t\tfor range b.Burst() {\n\t\t\t\ttt.Allow()\n\t\t\t}\n\t\t\tepsilon := 10 * time.Millisecond\n\t\t\trequire.GreaterOrEqual(t, tt.FullAfter+epsilon, b.FullAt(now).Sub(now))\n\t\t\trequire.LessOrEqual(t, tt.FullAfter-epsilon, b.FullAt(now).Sub(now))\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "x/simlibp2p/libp2p.go",
    "content": "package simlibp2p\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"fmt\"\n\t\"net\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/libp2p/go-libp2p\"\n\t\"github.com/libp2p/go-libp2p/config\"\n\t\"github.com/libp2p/go-libp2p/core/crypto\"\n\t\"github.com/libp2p/go-libp2p/core/host\"\n\t\"github.com/libp2p/go-libp2p/core/network\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/core/peerstore\"\n\tblankhost \"github.com/libp2p/go-libp2p/p2p/host/blank\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/eventbus\"\n\t\"github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoremem\"\n\t\"github.com/libp2p/go-libp2p/p2p/net/connmgr\"\n\t\"github.com/libp2p/go-libp2p/p2p/net/swarm\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/identify\"\n\tlibp2pquic \"github.com/libp2p/go-libp2p/p2p/transport/quic\"\n\t\"github.com/libp2p/go-libp2p/p2p/transport/quicreuse\"\n\t\"github.com/marcopolo/simnet\"\n\t\"github.com/multiformats/go-multiaddr\"\n\t\"github.com/quic-go/quic-go\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/fx\"\n)\n\nfunc MustNewHost(t *testing.T, opts ...libp2p.Option) host.Host {\n\tt.Helper()\n\th, err := libp2p.New(opts...)\n\trequire.NoError(t, err)\n\treturn h\n}\n\ntype MockSourceIPSelector struct {\n\tip atomic.Pointer[net.IP]\n}\n\nfunc (m *MockSourceIPSelector) PreferredSourceIPForDestination(_ *net.UDPAddr) (net.IP, error) {\n\treturn *m.ip.Load(), nil\n}\n\nconst OneMbps = 1_000_000\n\nfunc QUICSimnet(simnet *simnet.Simnet, linkSettings simnet.NodeBiDiLinkSettings, quicReuseOpts ...quicreuse.Option) libp2p.Option {\n\tm := &MockSourceIPSelector{}\n\tquicReuseOpts = append(quicReuseOpts,\n\t\tquicreuse.OverrideSourceIPSelector(func() (quicreuse.SourceIPSelector, error) {\n\t\t\treturn m, nil\n\t\t}),\n\t\tquicreuse.OverrideListenUDP(func(_ string, address *net.UDPAddr) (net.PacketConn, error) {\n\t\t\tm.ip.Store(&address.IP)\n\t\t\tc := simnet.NewEndpoint(address, linkSettings)\n\t\t\treturn c, nil\n\t\t}))\n\treturn libp2p.QUICReuse(\n\t\tfunc(l fx.Lifecycle, statelessResetKey quic.StatelessResetKey, tokenKey quic.TokenGeneratorKey, opts ...quicreuse.Option) (*quicreuse.ConnManager, error) {\n\t\t\tcm, err := quicreuse.NewConnManager(statelessResetKey, tokenKey, opts...)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tl.Append(fx.StopHook(func() error {\n\t\t\t\t// When we pass in our own conn manager, we need to close it manually (??)\n\t\t\t\t// TODO: this seems like a bug\n\t\t\t\treturn cm.Close()\n\t\t\t}))\n\t\t\treturn cm, nil\n\t\t}, quicReuseOpts...)\n}\n\ntype wrappedHost struct {\n\tblankhost.BlankHost\n\tps        peerstore.Peerstore\n\tquicCM    *quicreuse.ConnManager\n\tidService identify.IDService\n\tconnMgr   *connmgr.BasicConnMgr\n}\n\nfunc (h *wrappedHost) Close() error {\n\th.BlankHost.Close()\n\th.ps.Close()\n\th.quicCM.Close()\n\th.idService.Close()\n\th.connMgr.Close()\n\treturn nil\n}\n\ntype BlankHostOpts struct {\n\tConnMgr         *connmgr.BasicConnMgr\n\tlistenMultiaddr multiaddr.Multiaddr\n\tsimnet          *simnet.Simnet\n\tlinkSettings    simnet.NodeBiDiLinkSettings\n\tquicReuseOpts   []quicreuse.Option\n}\n\nfunc newBlankHost(opts BlankHostOpts) (*wrappedHost, error) {\n\tpriv, _, err := crypto.GenerateEd25519Key(rand.Reader)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tid, err := peer.IDFromPrivateKey(priv)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tps, err := pstoremem.NewPeerstore()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tps.AddPrivKey(id, priv)\n\n\teb := eventbus.NewBus()\n\n\tswarm, err := swarm.NewSwarm(id, ps, eb)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tstatelessResetKey, err := config.PrivKeyToStatelessResetKey(priv)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ttokenGeneratorKey, err := config.PrivKeyToTokenGeneratorKey(priv)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tm := &MockSourceIPSelector{}\n\tquicReuseOpts := append(opts.quicReuseOpts,\n\t\tquicreuse.OverrideSourceIPSelector(func() (quicreuse.SourceIPSelector, error) {\n\t\t\treturn m, nil\n\t\t}),\n\t\tquicreuse.OverrideListenUDP(func(_ string, address *net.UDPAddr) (net.PacketConn, error) {\n\t\t\tm.ip.Store(&address.IP)\n\t\t\tc := opts.simnet.NewEndpoint(address, opts.linkSettings)\n\t\t\treturn c, nil\n\t\t}),\n\t)\n\n\tquicCM, err := quicreuse.NewConnManager(statelessResetKey, tokenGeneratorKey, quicReuseOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tquicTr, err := libp2pquic.NewTransport(priv, quicCM, nil, nil, &network.NullResourceManager{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = swarm.AddTransport(quicTr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = swarm.Listen(opts.listenMultiaddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar cm *connmgr.BasicConnMgr\n\tif opts.ConnMgr == nil {\n\t\tcm, err = connmgr.NewConnManager(100, 200, connmgr.WithGracePeriod(time.Second*10))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t} else {\n\t\tcm = opts.ConnMgr\n\t}\n\n\thost := blankhost.NewBlankHost(swarm, blankhost.WithEventBus(eb), blankhost.WithConnectionManager(cm))\n\n\tidService, err := identify.NewIDService(host)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tidService.Start()\n\n\treturn &wrappedHost{\n\t\tBlankHost: *host,\n\t\tps:        ps,\n\t\tquicCM:    quicCM,\n\t\tidService: idService,\n\t\tconnMgr:   cm,\n\t}, nil\n}\n\ntype NodeLinkSettingsAndCount struct {\n\tLinkSettings simnet.NodeBiDiLinkSettings\n\tCount        int\n}\n\ntype HostAndIdx struct {\n\tHost host.Host\n\tIdx  int\n}\n\ntype SimpleLibp2pNetworkMeta struct {\n\tNodes      []host.Host\n\tAddrToNode map[string]HostAndIdx\n}\n\ntype NetworkSettings struct {\n\tUseBlankHost            bool\n\tQUICReuseOptsForHostIdx func(idx int) []quicreuse.Option\n\tBlankHostOptsForHostIdx func(idx int) BlankHostOpts\n}\n\ntype LatencyFunc func(*simnet.Packet) time.Duration\n\nfunc SimpleLibp2pNetwork(linkSettings []NodeLinkSettingsAndCount, latencyFunc LatencyFunc, networkSettings NetworkSettings) (*simnet.Simnet, *SimpleLibp2pNetworkMeta, error) {\n\tnw := &simnet.Simnet{\n\t\tLatencyFunc: latencyFunc,\n\t}\n\tmeta := &SimpleLibp2pNetworkMeta{\n\t\tAddrToNode: make(map[string]HostAndIdx),\n\t}\n\n\tfor _, l := range linkSettings {\n\t\tfor i := 0; i < l.Count; i++ {\n\t\t\tidx := len(meta.Nodes)\n\t\t\tip := simnet.IntToPublicIPv4(idx)\n\t\t\taddr := fmt.Sprintf(\"/ip4/%s/udp/8000/quic-v1\", ip)\n\t\t\tvar h host.Host\n\t\t\tvar err error\n\t\t\tvar quicReuseOpts []quicreuse.Option\n\t\t\tif networkSettings.QUICReuseOptsForHostIdx != nil {\n\t\t\t\tquicReuseOpts = networkSettings.QUICReuseOptsForHostIdx(idx)\n\t\t\t}\n\t\t\tif networkSettings.UseBlankHost {\n\t\t\t\tvar opts BlankHostOpts\n\t\t\t\tif networkSettings.BlankHostOptsForHostIdx != nil {\n\t\t\t\t\topts = networkSettings.BlankHostOptsForHostIdx(idx)\n\t\t\t\t}\n\n\t\t\t\th, err = newBlankHost(BlankHostOpts{\n\t\t\t\t\tlistenMultiaddr: multiaddr.StringCast(addr),\n\t\t\t\t\tsimnet:          nw,\n\t\t\t\t\tlinkSettings:    l.LinkSettings,\n\t\t\t\t\tquicReuseOpts:   quicReuseOpts,\n\t\t\t\t\tConnMgr:         opts.ConnMgr,\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\th, err = libp2p.New(\n\t\t\t\t\tlibp2p.ListenAddrStrings(addr),\n\t\t\t\t\tQUICSimnet(nw, l.LinkSettings, quicReuseOpts...),\n\t\t\t\t\t// TODO: Currently using identify address discovery stalls\n\t\t\t\t\t// synctest\n\t\t\t\t\tlibp2p.DisableIdentifyAddressDiscovery(),\n\t\t\t\t\tlibp2p.ResourceManager(&network.NullResourceManager{}),\n\t\t\t\t)\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\treturn nil, nil, err\n\t\t\t}\n\t\t\tmeta.Nodes = append(meta.Nodes, h)\n\t\t\tmeta.AddrToNode[addr] = HostAndIdx{Host: h, Idx: idx}\n\t\t}\n\t}\n\n\treturn nw, meta, nil\n}\n\n// GetBasicHostPair gets a new pair of hosts.\n// The first host initiates the connection to the second host.\nfunc GetBasicHostPair(t *testing.T) (host.Host, host.Host) {\n\tnetwork, meta, err := SimpleLibp2pNetwork([]NodeLinkSettingsAndCount{{\n\t\tLinkSettings: simnet.NodeBiDiLinkSettings{\n\t\t\tDownlink: simnet.LinkSettings{BitsPerSecond: 20 * OneMbps},\n\t\t\tUplink:   simnet.LinkSettings{BitsPerSecond: 20 * OneMbps},\n\t\t}, Count: 2},\n\t}, simnet.StaticLatency(100/2*time.Millisecond), NetworkSettings{})\n\trequire.NoError(t, err)\n\tnetwork.Start()\n\tt.Cleanup(func() {\n\t\tnetwork.Close()\n\t})\n\n\th1 := meta.Nodes[0]\n\th2 := meta.Nodes[1]\n\n\tctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)\n\tdefer cancel()\n\th2pi := h2.Peerstore().PeerInfo(h2.ID())\n\trequire.NoError(t, h1.Connect(ctx, h2pi))\n\treturn h1, h2\n}\n"
  },
  {
    "path": "x/simlibp2p/synctest_test.go",
    "content": "//go:build go1.25\n\npackage simlibp2p_test\n\nimport (\n\t\"context\"\n\t\"math/rand\"\n\t\"testing\"\n\t\"time\"\n\n\t\"testing/synctest\"\n\n\t\"github.com/libp2p/go-libp2p\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\t\"github.com/libp2p/go-libp2p/p2p/protocol/ping\"\n\t\"github.com/libp2p/go-libp2p/x/simlibp2p\"\n\t\"github.com/marcopolo/simnet\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSimpleLibp2pNetwork_synctest(t *testing.T) {\n\tsynctest.Test(t, func(t *testing.T) {\n\t\tlatency := 10 * time.Millisecond\n\t\tnetwork, meta, err := simlibp2p.SimpleLibp2pNetwork([]simlibp2p.NodeLinkSettingsAndCount{\n\t\t\t{LinkSettings: simnet.NodeBiDiLinkSettings{\n\t\t\t\tDownlink: simnet.LinkSettings{BitsPerSecond: 20 * simlibp2p.OneMbps}, // Divide by two since this is latency for each direction\n\t\t\t\tUplink:   simnet.LinkSettings{BitsPerSecond: 20 * simlibp2p.OneMbps},\n\t\t\t}, Count: 100},\n\t\t}, simnet.StaticLatency(latency/2), simlibp2p.NetworkSettings{})\n\t\trequire.NoError(t, err)\n\t\tnetwork.Start()\n\t\tdefer network.Close()\n\n\t\tdefer func() {\n\t\t\tfor _, node := range meta.Nodes {\n\t\t\t\tnode.Close()\n\t\t\t}\n\t\t}()\n\n\t\t// Test random nodes can ping each other\n\t\tconst numQueries = 100\n\t\tfor range numQueries {\n\t\t\ti := rand.Intn(len(meta.Nodes))\n\t\t\tj := rand.Intn(len(meta.Nodes))\n\t\t\tfor i == j {\n\t\t\t\tj = rand.Intn(len(meta.Nodes))\n\t\t\t}\n\t\t\th1 := meta.Nodes[i]\n\t\t\th2 := meta.Nodes[j]\n\t\t\tt.Logf(\"connecting %s <-> %s\", h1.ID(), h2.ID())\n\t\t\terr := h1.Connect(context.Background(), peer.AddrInfo{\n\t\t\t\tID:    h2.ID(),\n\t\t\t\tAddrs: h2.Addrs(),\n\t\t\t})\n\t\t\trequire.NoError(t, err)\n\t\t\tpingA := ping.NewPingService(h1)\n\t\t\tping.NewPingService(h2)\n\t\t\ttime.Sleep(1 * time.Second)\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)\n\t\t\tdefer cancel()\n\t\t\tt.Logf(\"pinging %s <-> %s\", h1.ID(), h2.ID())\n\t\t\tres := pingA.Ping(ctx, meta.Nodes[j].ID())\n\t\t\tresult := <-res\n\t\t\tt.Logf(\"pinged %s <-> %s\", h1.ID(), h2.ID())\n\t\t\trequire.NoError(t, result.Error)\n\t\t\tt.Logf(\"ping: (%d) <-> (%d): %v\", i, j, result.RTT)\n\t\t\texpectedLatency := 20 * time.Millisecond // RTT is the sum of the latency of the two links\n\t\t\tpercentDiff := float64(result.RTT-expectedLatency) / float64(expectedLatency)\n\t\t\tif percentDiff > 0.20 {\n\t\t\t\tt.Fatalf(\"latency is wrong: %v. percent off: %v\", result.RTT, percentDiff)\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc TestSimpleSimNetPing_synctest(t *testing.T) {\n\tsynctest.Test(t, func(t *testing.T) {\n\t\tconst latency = 10 * time.Millisecond\n\t\trouter := &simnet.Simnet{LatencyFunc: simnet.StaticLatency(latency / 2)}\n\n\t\tconst bandwidth = 10 * simlibp2p.OneMbps\n\t\tlinkSettings := simnet.NodeBiDiLinkSettings{\n\t\t\tDownlink: simnet.LinkSettings{\n\t\t\t\tBitsPerSecond: bandwidth,\n\t\t\t},\n\t\t\tUplink: simnet.LinkSettings{\n\t\t\t\tBitsPerSecond: bandwidth,\n\t\t\t},\n\t\t}\n\n\t\thostA := simlibp2p.MustNewHost(t,\n\t\t\tlibp2p.ListenAddrStrings(\"/ip4/1.0.0.1/udp/8000/quic-v1\"),\n\t\t\tlibp2p.DisableIdentifyAddressDiscovery(),\n\t\t\tsimlibp2p.QUICSimnet(router, linkSettings),\n\t\t)\n\t\thostB := simlibp2p.MustNewHost(t,\n\t\t\tlibp2p.ListenAddrStrings(\"/ip4/1.0.0.2/udp/8000/quic-v1\"),\n\t\t\tlibp2p.DisableIdentifyAddressDiscovery(),\n\t\t\tsimlibp2p.QUICSimnet(router, linkSettings),\n\t\t)\n\n\t\trouter.Start()\n\t\tdefer router.Close()\n\n\t\tdefer hostA.Close()\n\t\tdefer hostB.Close()\n\n\t\terr := hostA.Connect(context.Background(), peer.AddrInfo{\n\t\t\tID:    hostB.ID(),\n\t\t\tAddrs: hostB.Addrs(),\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tpingA := ping.NewPingService(hostA)\n\n\t\tctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)\n\t\tdefer cancel()\n\n\t\tres := pingA.Ping(ctx, hostB.ID())\n\t\tresult := <-res\n\t\trequire.NoError(t, result.Error)\n\t\tt.Logf(\"pingA -> pingB: %v\", result.RTT)\n\n\t\texpectedLatency := latency * 2 // RTT is the sum of the latency of the two links\n\t\tpercentDiff := float64(result.RTT-expectedLatency) / float64(expectedLatency)\n\t\tif percentDiff > 0.20 {\n\t\t\tt.Fatalf(\"latency is wrong: %v. percent off: %v\", result.RTT, percentDiff)\n\t\t}\n\t})\n}\n"
  }
]