Repository: imsnif/bandwhich Branch: main Commit: cbcffe8816a8 Files: 124 Total size: 814.6 KB Directory structure: gitextract_5glekt08/ ├── .github/ │ ├── FUNDING.yml │ ├── dependabot.yml │ └── workflows/ │ ├── ci.yaml │ ├── publish-crate.yaml │ ├── release.yaml │ └── require-changelog-for-PRs.yml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Cargo.toml ├── Cross.toml ├── INSTALL.md ├── LICENSE.md ├── README.md ├── build.rs ├── rustfmt.toml └── src/ ├── cli.rs ├── display/ │ ├── components/ │ │ ├── display_bandwidth.rs │ │ ├── header_details.rs │ │ ├── help_text.rs │ │ ├── layout.rs │ │ ├── mod.rs │ │ ├── snapshots/ │ │ │ └── bandwhich__display__components__display_bandwidth__tests__bandwidth_formatting.snap │ │ └── table.rs │ ├── mod.rs │ ├── raw_terminal_backend.rs │ ├── ui.rs │ └── ui_state.rs ├── main.rs ├── network/ │ ├── connection.rs │ ├── dns/ │ │ ├── client.rs │ │ ├── mod.rs │ │ └── resolver.rs │ ├── mod.rs │ ├── sniffer.rs │ └── utilization.rs ├── os/ │ ├── errors.rs │ ├── linux.rs │ ├── lsof.rs │ ├── lsof_utils.rs │ ├── mod.rs │ ├── shared.rs │ └── windows.rs └── tests/ ├── cases/ │ ├── mod.rs │ ├── raw_mode.rs │ ├── snapshots/ │ │ ├── bandwhich__tests__cases__raw_mode__bi_directional_traffic.snap │ │ ├── bandwhich__tests__cases__raw_mode__multiple_connections_from_remote_address.snap │ │ ├── bandwhich__tests__cases__raw_mode__multiple_packets_of_traffic_from_different_connections.snap │ │ ├── bandwhich__tests__cases__raw_mode__multiple_packets_of_traffic_from_single_connection.snap │ │ ├── bandwhich__tests__cases__raw_mode__multiple_processes_with_multiple_connections.snap │ │ ├── bandwhich__tests__cases__raw_mode__no_resolve_mode.snap │ │ ├── bandwhich__tests__cases__raw_mode__one_ip_packet_of_traffic.snap │ │ ├── bandwhich__tests__cases__raw_mode__one_packet_of_traffic.snap │ │ ├── bandwhich__tests__cases__raw_mode__one_process_with_multiple_connections.snap │ │ ├── bandwhich__tests__cases__raw_mode__sustained_traffic_from_multiple_processes.snap │ │ ├── bandwhich__tests__cases__raw_mode__sustained_traffic_from_multiple_processes_bi_directional.snap │ │ ├── bandwhich__tests__cases__raw_mode__sustained_traffic_from_one_process.snap │ │ ├── bandwhich__tests__cases__raw_mode__traffic_with_host_names.snap │ │ ├── bandwhich__tests__cases__ui__basic_only_addresses.snap │ │ ├── bandwhich__tests__cases__ui__basic_only_connections.snap │ │ ├── bandwhich__tests__cases__ui__basic_only_processes.snap │ │ ├── bandwhich__tests__cases__ui__basic_processes_with_dns_queries.snap │ │ ├── bandwhich__tests__cases__ui__basic_startup-2.snap │ │ ├── bandwhich__tests__cases__ui__basic_startup.snap │ │ ├── bandwhich__tests__cases__ui__bi_directional_traffic-2.snap │ │ ├── bandwhich__tests__cases__ui__bi_directional_traffic.snap │ │ ├── bandwhich__tests__cases__ui__layout-full-width-under-30-height-draw_events.snap │ │ ├── bandwhich__tests__cases__ui__layout-full-width-under-30-height-events.snap │ │ ├── bandwhich__tests__cases__ui__layout-under-120-width-full-height-draw_events.snap │ │ ├── bandwhich__tests__cases__ui__layout-under-120-width-full-height-events.snap │ │ ├── bandwhich__tests__cases__ui__layout-under-120-width-under-30-height-draw_events.snap │ │ ├── bandwhich__tests__cases__ui__layout-under-120-width-under-30-height-events.snap │ │ ├── bandwhich__tests__cases__ui__layout-under-50-width-under-50-height-draw_events.snap │ │ ├── bandwhich__tests__cases__ui__layout-under-50-width-under-50-height-events.snap │ │ ├── bandwhich__tests__cases__ui__layout-under-70-width-under-30-height-draw_events.snap │ │ ├── bandwhich__tests__cases__ui__layout-under-70-width-under-30-height-events.snap │ │ ├── bandwhich__tests__cases__ui__multiple_connections_from_remote_address-2.snap │ │ ├── bandwhich__tests__cases__ui__multiple_connections_from_remote_address.snap │ │ ├── bandwhich__tests__cases__ui__multiple_packets_of_traffic_from_different_connections-2.snap │ │ ├── bandwhich__tests__cases__ui__multiple_packets_of_traffic_from_different_connections.snap │ │ ├── bandwhich__tests__cases__ui__multiple_packets_of_traffic_from_single_connection-2.snap │ │ ├── bandwhich__tests__cases__ui__multiple_packets_of_traffic_from_single_connection.snap │ │ ├── bandwhich__tests__cases__ui__multiple_processes_with_multiple_connections-2.snap │ │ ├── bandwhich__tests__cases__ui__multiple_processes_with_multiple_connections.snap │ │ ├── bandwhich__tests__cases__ui__no_resolve_mode-2.snap │ │ ├── bandwhich__tests__cases__ui__no_resolve_mode.snap │ │ ├── bandwhich__tests__cases__ui__one_packet_of_traffic-2.snap │ │ ├── bandwhich__tests__cases__ui__one_packet_of_traffic.snap │ │ ├── bandwhich__tests__cases__ui__one_process_with_multiple_connections-2.snap │ │ ├── bandwhich__tests__cases__ui__one_process_with_multiple_connections.snap │ │ ├── bandwhich__tests__cases__ui__pause_by_space-2.snap │ │ ├── bandwhich__tests__cases__ui__pause_by_space.snap │ │ ├── bandwhich__tests__cases__ui__rearranged_by_tab-2.snap │ │ ├── bandwhich__tests__cases__ui__rearranged_by_tab.snap │ │ ├── bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes-2.snap │ │ ├── bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes.snap │ │ ├── bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes_bi_directional-2.snap │ │ ├── bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes_bi_directional.snap │ │ ├── bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes_bi_directional_total-2.snap │ │ ├── bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes_bi_directional_total.snap │ │ ├── bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes_total-2.snap │ │ ├── bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes_total.snap │ │ ├── bandwhich__tests__cases__ui__sustained_traffic_from_one_process-2.snap │ │ ├── bandwhich__tests__cases__ui__sustained_traffic_from_one_process.snap │ │ ├── bandwhich__tests__cases__ui__sustained_traffic_from_one_process_total-2.snap │ │ ├── bandwhich__tests__cases__ui__sustained_traffic_from_one_process_total.snap │ │ ├── bandwhich__tests__cases__ui__traffic_with_host_names-2.snap │ │ ├── bandwhich__tests__cases__ui__traffic_with_host_names.snap │ │ ├── bandwhich__tests__cases__ui__traffic_with_winch_event-2.snap │ │ ├── bandwhich__tests__cases__ui__traffic_with_winch_event.snap │ │ ├── bandwhich__tests__cases__ui__truncate_long_hostnames-2.snap │ │ ├── bandwhich__tests__cases__ui__truncate_long_hostnames.snap │ │ ├── bandwhich__tests__cases__ui__two_packets_only_addresses.snap │ │ ├── bandwhich__tests__cases__ui__two_packets_only_connections.snap │ │ ├── bandwhich__tests__cases__ui__two_packets_only_processes.snap │ │ ├── bandwhich__tests__cases__ui__two_windows_split_horizontally.snap │ │ └── bandwhich__tests__cases__ui__two_windows_split_vertically.snap │ ├── test_utils.rs │ └── ui.rs ├── fakes/ │ ├── fake_input.rs │ ├── fake_output.rs │ └── mod.rs └── mod.rs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: [imsnif] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: cargo directory: "/" schedule: interval: monthly open-pull-requests-limit: 30 groups: dependencies: patterns: - "*" - package-ecosystem: github-actions directory: "/" schedule: interval: monthly open-pull-requests-limit: 30 groups: github-actions: patterns: - "*" ================================================ FILE: .github/workflows/ci.yaml ================================================ name: ci on: pull_request: push: branches: - main workflow_dispatch: jobs: get-msrv: name: Get declared MSRV from Cargo.toml runs-on: ubuntu-latest outputs: msrv: ${{ steps.get_msrv.outputs.msrv }} steps: - name: Install ripgrep run: sudo apt-get install -y ripgrep - name: Checkout repository uses: actions/checkout@v6 - name: Get MSRV id: get_msrv run: rg '^\s*rust-version\s*=\s*"(\d+(\.\d+){0,2})"' --replace 'msrv=$1' Cargo.toml >> "$GITHUB_OUTPUT" check-fmt: name: Check code formatting runs-on: ubuntu-latest needs: get-msrv strategy: fail-fast: false matrix: rust: - ${{ needs.get-msrv.outputs.msrv }} - stable - nightly steps: - name: Checkout repository uses: actions/checkout@v6 - name: Install Rust uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} components: rustfmt - name: Check formatting run: cargo fmt --all -- --check test: name: Test on each target needs: get-msrv env: # use sccache # It's too much of a hassle to set up sccache in cross. # See https://github.com/cross-rs/cross/wiki/Recipes#sccache. SCCACHE_GHA_ENABLED: ${{ matrix.cargo == 'cargo' && 'true' || 'false'}} RUSTC_WRAPPER: ${{ matrix.cargo == 'cargo' && 'sccache' || '' }} # Emit backtraces on panics. RUST_BACKTRACE: 1 runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: build: - android-aarch64 - linux-aarch64-gnu - linux-aarch64-musl - linux-armv7-gnueabihf - linux-armv7-musleabihf - linux-x64-gnu - linux-x64-musl - macos-aarch64 - macos-x64 - windows-x64-msvc rust: - ${{ needs.get-msrv.outputs.msrv }} - stable - nightly include: - os: ubuntu-latest # default - cargo: cargo # default; overwrite with `cross` if necessary - build: android-aarch64 target: aarch64-linux-android cargo: cross - build: linux-aarch64-gnu target: aarch64-unknown-linux-gnu cargo: cross - build: linux-aarch64-musl target: aarch64-unknown-linux-musl cargo: cross - build: linux-armv7-gnueabihf target: armv7-unknown-linux-gnueabihf cargo: cross - build: linux-armv7-musleabihf target: armv7-unknown-linux-musleabihf cargo: cross - build: linux-x64-gnu target: x86_64-unknown-linux-gnu - build: linux-x64-musl target: x86_64-unknown-linux-musl - build: macos-aarch64 # Go back ot `macos-latest` after migration is complete # See https://github.blog/changelog/2024-04-01-macos-14-sonoma-is-generally-available-and-the-latest-macos-runner-image/. os: macos-14 target: aarch64-apple-darwin - build: macos-x64 os: macos-14 target: x86_64-apple-darwin - build: windows-x64-msvc os: windows-latest target: x86_64-pc-windows-msvc steps: - name: Checkout repository uses: actions/checkout@v6 - name: Install Rust uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} targets: ${{ matrix.target }} components: clippy - name: Set up sccache # It's too much of a hassle to set up sccache in cross. # See https://github.com/cross-rs/cross/wiki/Recipes#sccache. if: matrix.cargo == 'cargo' uses: mozilla-actions/sccache-action@v0.0.9 - name: Install cross if: matrix.cargo == 'cross' # The latest release of `cross` is not able to build/link for `aarch64-linux-android` # See: https://github.com/cross-rs/cross/issues/1222 # This is fixed on `main` but not yet released. To avoid a breakage somewhen in the future # pin the cross revision used to the latest HEAD at 04/2024. # Go back to taiki-e/install-action once cross 0.3 is released. uses: taiki-e/cache-cargo-install-action@v3 with: tool: cross git: https://github.com/cross-rs/cross.git rev: 085092c - name: Build id: build run: ${{ matrix.cargo }} build --verbose --target ${{ matrix.target }} # This is useful for debugging problems when the expected build artifacts # (like shell completions and man pages) aren't generated. - name: Show build.rs stderr shell: bash run: | # it's probably okay to assume no spaces? STDERR_FILES=$(find "./target/debug" -name stderr | grep bandwhich || true) for FILE in $STDERR_FILES; do echo "::group::$FILE" cat "$FILE" echo "::endgroup::" done - name: Run clippy run: ${{ matrix.cargo }} clippy --all-targets --target ${{ matrix.target }} -- -D warnings - name: Install npcap on Windows # PRs from other repositories cannot be trusted with repository secrets if: matrix.os == 'windows-latest' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) env: NPCAP_OEM_URL: ${{ secrets.NPCAP_OEM_URL }} run: | Invoke-WebRequest -Uri "$env:NPCAP_OEM_URL" -OutFile "$env:TEMP/npcap-oem.exe" # for this ridiculous `&` syntax alone, I'd rather use COBOL than Powershell # see https://stackoverflow.com/a/1674950/5637701 & "$env:TEMP/npcap-oem.exe" /S - name: Run tests id: run_tests # npcap is needed to run tests on Windows, so unfortunately we cannot run tests # on PRs from other repositories if: matrix.os != 'windows-latest' || github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository env: # make insta generate new snapshots in CI INSTA_UPDATE: new run: ${{ matrix.cargo }} test --all-targets --target ${{ matrix.target }} - name: Upload snapshots of failed tests if: ${{ failure() && steps.run_tests.outcome == 'failure' }} uses: actions/upload-artifact@v6 with: name: ${{ matrix.os }}-${{ matrix.rust }}-failed_snapshots path: '**/*.snap.new' - name: Upload binaries if: ${{ success() || steps.build.outcome == 'success' }} uses: actions/upload-artifact@v6 with: name: ${{ matrix.target }}-${{ matrix.rust }} path: | target/${{ matrix.target }}/debug/bandwhich target/${{ matrix.target }}/debug/bandwhich.exe target/${{ matrix.target }}/debug/bandwhich.pdb ================================================ FILE: .github/workflows/publish-crate.yaml ================================================ # This workflow triggers when a stable release is published on GitHub. # # The crates.io token used for publishing was created under the account of # cyqsimon <28627918+cyqsimon@users.noreply.github.com> and was added to this # repository's secrets by Aram Drevekenin . name: publish-crate on: release: types: - released workflow_dispatch: jobs: publish-to-crates-io: name: Publish to crates.io runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v6 - name: Install Rust uses: dtolnay/rust-toolchain@master with: toolchain: stable - name: Run cargo publish uses: katyo/publish-crates@v2 with: registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }} ================================================ FILE: .github/workflows/release.yaml ================================================ # The way this works is the following: # # - create-release job runs purely to initialize the GitHub release itself # and to output upload_url for the following job. # # - build-release job runs only once create-release is finished. It gets # the release upload URL from create-release job outputs, then builds # the release executables for each supported platform and attaches them # as release assets to the previously created release. # # Reference: # - https://eugene-babichenko.github.io/blog/2020/05/09/github-actions-cross-platform-auto-releases/ # # Currently this workflow only ever creates drafts; the draft should be checked # and then released manually. name: release on: push: tags: - "v[0-9]+.[0-9]+.[0-9]+" workflow_dispatch: jobs: create-release: name: create-release runs-on: ubuntu-latest outputs: upload_url: ${{ steps.create_release.outputs.upload_url }} steps: - name: create_release id: create_release uses: actions/create-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: ${{ github.ref_name }} release_name: Release ${{ github.ref_name }} # draft: ${{ github.event_name == 'workflow_dispatch' }} draft: true prerelease: false build-release: name: build-release needs: create-release runs-on: ${{ matrix.os }} env: # Emit backtraces on panics. RUST_BACKTRACE: 1 BANDWHICH_GEN_DIR: assets PKGDIR: github-actions-pkg strategy: matrix: build: - android-aarch64 - linux-aarch64-gnu - linux-aarch64-musl - linux-armv7-gnueabihf - linux-armv7-musleabihf - linux-x64-gnu - linux-x64-musl - macos-aarch64 - macos-x64 - windows-x64-msvc include: - os: ubuntu-latest # default - cargo: cargo # default; overwrite with `cross` if necessary - build: android-aarch64 target: aarch64-linux-android cargo: cross - build: linux-aarch64-gnu target: aarch64-unknown-linux-gnu cargo: cross - build: linux-aarch64-musl target: aarch64-unknown-linux-musl cargo: cross - build: linux-armv7-gnueabihf target: armv7-unknown-linux-gnueabihf cargo: cross - build: linux-armv7-musleabihf target: armv7-unknown-linux-musleabihf cargo: cross - build: linux-x64-gnu target: x86_64-unknown-linux-gnu - build: linux-x64-musl target: x86_64-unknown-linux-musl - build: macos-aarch64 # Go back ot `macos-latest` after migration is complete # See https://github.blog/changelog/2024-04-01-macos-14-sonoma-is-generally-available-and-the-latest-macos-runner-image/. os: macos-14 target: aarch64-apple-darwin - build: macos-x64 os: macos-14 target: x86_64-apple-darwin - build: windows-x64-msvc os: windows-latest target: x86_64-pc-windows-msvc steps: - name: Checkout repository uses: actions/checkout@v6 - name: Install Rust uses: dtolnay/rust-toolchain@master with: toolchain: stable targets: ${{ matrix.target }} - name: Install cross if: matrix.cargo == 'cross' # The latest release of `cross` is not able to build/link for `aarch64-linux-android` # See: https://github.com/cross-rs/cross/issues/1222 # This is fixed on `main` but not yet released. To avoid a breakage somewhen in the future # pin the cross revision used to the latest HEAD at 04/2024. # Go back to taiki-e/install-action once cross 0.3 is released. uses: taiki-e/cache-cargo-install-action@v3 with: tool: cross git: https://github.com/cross-rs/cross.git rev: 085092c - name: Build release binary shell: bash env: RUSTFLAGS: "-C strip=symbols" run: | mkdir -p "$BANDWHICH_GEN_DIR" ${{ matrix.cargo }} build --verbose --release --target ${{ matrix.target }} - name: Collect build artifacts shell: bash env: BANDWHICH_BIN: ${{ contains(matrix.os, 'windows') && 'bandwhich.exe' || 'bandwhich' }} run: | mkdir "$PKGDIR" mv "target/${{ matrix.target }}/release/$BANDWHICH_BIN" "$PKGDIR" mv "$BANDWHICH_GEN_DIR" "$PKGDIR" - name: Tar release (Unix) if: ${{ !contains(matrix.os, 'windows') }} working-directory: ${{ env.PKGDIR }} run: tar cvfz bandwhich-${{ github.ref_name }}-${{ matrix.target }}.tar.gz * - name: Zip release (Windows) if: contains(matrix.os, 'windows') working-directory: ${{ env.PKGDIR }} run: Compress-Archive -Path * -DestinationPath bandwhich-${{ github.ref_name }}-${{ matrix.target }}.zip - name: Upload release archive uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ARCHIVE_EXT: ${{ contains(matrix.os, 'windows') && 'zip' || 'tar.gz' }} with: upload_url: ${{ needs.create-release.outputs.upload_url }} asset_path: ${{ env.PKGDIR }}/bandwhich-${{ github.ref_name }}-${{ matrix.target }}.${{ env.ARCHIVE_EXT }} asset_name: bandwhich-${{ github.ref_name }}-${{ matrix.target }}.${{ env.ARCHIVE_EXT }} asset_content_type: application/octet-stream ================================================ FILE: .github/workflows/require-changelog-for-PRs.yml ================================================ name: Changelog on: pull_request: env: PR_NUMBER: ${{ github.event.number }} PR_BASE: ${{ github.base_ref }} jobs: get-submitter: name: Get the username of the PR submitter runs-on: ubuntu-latest outputs: submitter: ${{ steps.get-submitter.outputs.submitter }} steps: # cannot use `github.actor`: the triggering commit may be authored by a maintainer - name: Get PR submitter id: get-submitter run: curl -sSfL https://api.github.com/repos/imsnif/bandwhich/pulls/${PR_NUMBER} | jq -r '"submitter=" + .user.login' | tee -a $GITHUB_OUTPUT check-changelog: name: Check for changelog entry needs: get-submitter env: PR_SUBMITTER: ${{ needs.get-submitter.outputs.submitter }} runs-on: ubuntu-latest # allow dependabot PRs to have no changelog if: ${{ needs.get-submitter.outputs.submitter != 'dependabot[bot]' }} steps: - uses: actions/checkout@v6 - name: Fetch PR base run: git fetch --no-tags --prune --depth=1 origin - name: Search for added line in changelog run: | ADDED=$(git diff -U0 "origin/${PR_BASE}" HEAD -- CHANGELOG.md | grep -P '^\+[^\+].+$') echo "Added lines in CHANGELOG.md:" echo "$ADDED" echo "Grepping for PR info:" grep -P "(#|pull/)${PR_NUMBER}\\b.*@${PR_SUBMITTER}\\b" <<< "$ADDED" ================================================ FILE: .gitignore ================================================ target/ ================================================ FILE: CHANGELOG.md ================================================ # Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## [Unreleased] ### Fixed * Fix Ctrl+C handling to use SIGINT signal instead of keypress #491 - @chiranjeevi-max * Update CONTRIBUTING information #438 - @YJDoc2 @cyqsimon * Fix new clippy lint #457 - @cyqsimon * Apply new clippy lints #468 - @cyqsimon ### Changed * Bump msrv to 1.75.0 #439 - @YJDoc2 * Replace `derivative` with `derive_more` #439 - @YJDoc2 * Add build optimizations for release binary #434 - @pando85 * Minor cleanup and optimisations #435 - @cyqsimon * Bump `pnet` & `packet-builder` #444 - @cyqsimon * Switch from anyhow to eyre #450 - @cyqsimon * Manually bump all dependencies #456 - @cyqsimon * Bump MSRV to 1.82.0 - @cyqsimon ## [0.23.1] - 2024-10-09 ### Fixed * CI: Use Powershell Compress-Archive to create Windows binary zip #424 - @cyqsimon * Exit gracefully when there is a broken pipe error #429 - @sigmaSd * Fix breaking changes of sysinfo crate #431 - @cyqsimon * Fix `clippy::needless_lifetimes` warnings on nightly #432 - @cyqsimon ## [0.23.0] - 2024-08-17 ### Fixed * Remove redundant imports #377 - @cyqsimon * CI: use GitHub API to exempt dependabot from changelog requirement #378 - @cyqsimon * Remove unnecessary logging synchronisation #381 - @cyqsimon * Apply suggestions from new clippy lint clippy::assigning_clones #382 - @cyqsimon * Fix IPv6 socket detect logic #383 - @cyqsimon * Support build for `target_os` `android` #384 - @flxo * Fix Windows FP discrepancy issue in test #400 - @cyqsimon ### Added * CI: include generated assets in release archive #359 - @cyqsimon * Add PID column to the process table #379 - @notjedi * CI: add builds for target `aarch64-linux-android` #384 - @flxo * CI: Keep GitHub Actions up to date with GitHub's Dependabot #403 - @cclauss * CI: Enable more cross-compiled builds #401 - @cyqsimon * CI: use sccache to speed up CI #408 - @cyqsimon ### Changed * CI: strip release binaries for all targets #358 - @cyqsimon * Bump MSRV to 1.74 (required by clap 4.5; see #373) * CI: Configure dependabot grouping #395 - @cyqsimon * CI refactor #399 - @cyqsimon * CI: Temporarily disable UI tests #406 - @cyqsimon * Update README #407 - @cyqsimon * Update usage in README #409 - @cyqsimon ### Removed * CI: Remove musl-tools install step #402 - @cyqsimon ## [0.22.2] - 2024-01-28 ### Added * Generate completion & manpage #357 - @cyqsimon ## [0.22.1] - 2024-01-28 ### Fixed * Hot fix a Windows compile issue #356 - @cyqsimon ## [0.22.0] - 2024-01-28 ### Added * Log unresolved processes in more detail + general refactor #318 - @cyqsimon * Display bandwidth in different unit families #328 - @cyqsimon * CI: ensure a changelog entry exists for each PR #331 - @cyqsimon * Show interface names #340 - @ilyes-ced ### Changed * Table formatting logic overhaul #305 - @cyqsimon * Refactor OsInputOutput (combine interfaces & frames into single Vec) #310 - @cyqsimon ### Removed * Reorganise & cleanup packaging code/resources #329 - @cyqsimon ### Fixed * Make logging race-free using a global lock & macro #309 - @cyqsimon * Use once_cell::sync::Lazy to make regex usage more ergonomic #313 - @cyqsimon * Fix vague CLI option documentation; closes #314 #316 - @cyqsimon ## [0.21.1] - 2023-10-16 ### Fixed * Ignore connections that fail parsing instead of panicking on BSD (https://github.com/imsnif/bandwhich/pull/288) - [@cyqsimon](https://github.com/cyqsimon) * Add missing version flag to CLI (https://github.com/imsnif/bandwhich/pull/290) - [@tranzystorek-io](https://github.com/tranzystorek-io) * Various minor codestyle changes - [@cyqsimon](https://github.com/cyqsimon) * Handle IPv4-mapped IPv6 addresses when resolving connection owner (https://github.com/imsnif/bandwhich/commit/76956cf) - [@cyqsimon](https://github.com/cyqsimon) * Bump `rustix` dependencies to fix a memory leak (https://github.com/imsnif/bandwhich/commit/bc10c07) - [@cyqsimon](https://github.com/cyqsimon) ### Added * Logging infrastrure (https://github.com/imsnif/bandwhich/pull/302) - [@cyqsimon](https://github.com/cyqsimon) ## [0.21.0] - 2023-09-19 ### Fixed * Fixed resolv.conf errors on systems with trust-ad (https://github.com/imsnif/bandwhich/pull/201) - [@JoshLambda](https://github.com/JoshLambda) * Fixed build issues by updating various dependencies * migrate out-of-date dependency `structopt` to `clap` (https://github.com/imsnif/bandwhich/pull/285) - [@Liyixin95](https://github.com/Liyixin95) ## [0.20.0] - 2020-10-15 ### Added * New command line argument to explicitly specify a DNS server to use (https://github.com/imsnif/bandwhich/pull/193) - [@imsnif](https://github.com/imsnif) ## [0.19.0] - 2020-09-29 ### Fixed * Fixed resolv.conf parsing for rDNS in some cases (https://github.com/imsnif/bandwhich/pull/184) - [@Ma27](https://github.com/Ma27) * Cross platform window resizing (fixes momentary UI break when resizing window on Windows) (https://github.com/imsnif/bandwhich/pull/186) - [@remgodow](https://github.com/remgodow) * CI: build binaries using github actions (https://github.com/imsnif/bandwhich/pull/181) - [@remgodow](https://github.com/remgodow) * Fix build on FreeBSD (https://github.com/imsnif/bandwhich/pull/189) - [@imsnif](https://github.com/imsnif) * Upgrade TUI to latest version (https://github.com/imsnif/bandwhich/pull/190) - [@imsnif](https://github.com/imsnif) * Try to reconnect to disconnected interfaces (https://github.com/imsnif/bandwhich/pull/191) - [@thepacketgeek](https://github.com/thepacketgeek) ## [0.18.1] - 2020-09-11 * HOTFIX: do not build windows build-dependencies on other platforms ## [0.18.0] - 2020-09-11 ### Added * Future windows infrastructure support (should not have any user facing effect) (https://github.com/imsnif/bandwhich/pull/179) - [@remgodow](https://github.com/remgodow) * Windows build and run support (https://github.com/imsnif/bandwhich/pull/180) - [@remgodow](https://github.com/remgodow) ### Fixed * Update and improve MAN page (https://github.com/imsnif/bandwhich/pull/182) - [@Nudin](https://github.com/Nudin) ## [0.17.0] - 2020-09-02 ### Added * Add delimiters between refreshes in raw mode for easier parsing (https://github.com/imsnif/bandwhich/pull/175) - [@sigmaSd](https://github.com/sigmaSd) ### Fixed * Truncate Chinese characters properly (https://github.com/imsnif/bandwhich/pull/177) - [@zxlzy](https://github.com/zxlzy) * Moved to mebi/gibi/tibi bytes to improve bandwidth accuracy and reduce ambiguity (https://github.com/imsnif/bandwhich/pull/178) - [@imsnif](https://github.com/imsnif) ## [0.16.0] - 2020-07-13 ### Fixed * Allow filtering by processes/connections/remote-ips in raw-mode (https://github.com/imsnif/bandwhich/pull/174) - [@sigmaSd](https://github.com/sigmaSd) * Changed repository trunk branch to "main" instead of "master". ## [0.15.0] - 2020-05-23 ### Added * Ability to change the window layout with (https://github.com/imsnif/bandwhich/pull/118) - [@Louis-Lesage](https://github.com/Louis-Lesage) * Show duration of current capture when running in "total utilization" mode. - [@Eosis](https://github.com/Eosis) ### Fixed * Add terabytes as a display unit (for cumulative mode) (https://github.com/imsnif/bandwhich/pull/168) - [@TheLostLambda](https://github.com/TheLostLambda) ## [0.14.0] - 2020-05-03 ### Fixed * HOTFIX: remove pnet_bandwhich_fork dependency and upgrade to working version of pnet + packet_builder instead (this should hopefully not change anything) ## [0.13.0] - 2020-04-05 ### Added * Hide DNS queries by default. This can be overridden with `-s, --show-dns` (https://github.com/imsnif/bandwhich/pull/161) - [@olesh0](https://github.com/olehs0) * Show cumulative utilization in "total utilization" mode. Trigger with `-t, --total-utilization` (https://github.com/imsnif/bandwhich/pull/155) - [@TheLostLambda](https://github.com/TheLostLambda) ### Fixed * Fix the loss of large, merged packets (https://github.com/imsnif/bandwhich/pull/158) - [@TheLostLambda](https://github.com/TheLostLambda) ## [0.12.0] - 2020-03-01 ### Added * Add custom error handling (https://github.com/imsnif/bandwhich/pull/104) - [@captain-yossarian](https://github.com/captain-yossarian) ## [0.11.0] - 2020-01-25 ### Added * List unknown processes in processes table as well (https://github.com/imsnif/bandwhich/pull/132) - [@jcfvalente](https://github.com/jcfvalente) * New layout (https://github.com/imsnif/bandwhich/pull/139) - [@imsnif](https://github.com/imsnif) ## [0.10.0] - 2020-01-18 ### Added * Support Ipv6 (https://github.com/imsnif/bandwhich/pull/70) - [@zhangxp1998](https://github.com/zhangxp1998) * Select tables to render from the CLI (https://github.com/imsnif/bandwhich/pull/107) - [@chobeat](https://github.com/chobeat) ### Fixed * VPN traffic sniffing on mac (https://github.com/imsnif/bandwhich/pull/129) - [@zhangxp1998](https://github.com/zhangxp1998) ## [0.9.0] - 2020-01-14 ### Added * Paused UI by pressing key. Does not affect raw mode. (https://github.com/imsnif/bandwhich/pull/106) - [@zhangxp1998](https://github.com/zhangxp1998) * Mention setcap option in linux permission error. (https://github.com/imsnif/bandwhich/pull/108) - [@Ma27](https://github.com/Ma27) * Display weighted average bandwidth for the past 5 seconds. (https://github.com/imsnif/bandwhich/pull/77) - [@zhangxp1998](https://github.com/zhangxp1998) + [@imsnif](https://github.com/imsnif) * FreeBSD support. (https://github.com/imsnif/bandwhich/pull/110) - [@Erk-](https://github.com/Erk-) * Pause help text. (https://github.com/imsnif/bandwhich/pull/111) - [@imsnif](https://github.com/imsnif) ### Fixed * Upgrade trust-dns-resolver. (https://github.com/imsnif/bandwhich/pull/105) - [@bigtoast](https://github.com/bigtoast) * Do not listen on inactive interfaces. (https://github.com/imsnif/bandwhich/pull/116) - [@zhangxp1998](https://github.com/zhangxp1998) ## [0.8.0] - 2020-01-09 ### Added - Brew formula and installation instructions for macOS (https://github.com/imsnif/bandwhich/pull/75) - [@imbsky](https://github.com/imbsky) - UI change: add spacing between up and down rates for readability (https://github.com/imsnif/bandwhich/pull/58) - [@Calinou](https://github.com/Calinou) - Support for wireguard interfaces (eg. for VPNs) (https://github.com/imsnif/bandwhich/pull/98) - [@Ma27](https://github.com/Ma27) - Void linux installation instructions (https://github.com/imsnif/bandwhich/pull/102) - [@jcgruenhage](https://github.com/jcgruenhage) - Arch installation with pacman (https://github.com/imsnif/bandwhich/pull/103) - [@kpcyrd](https://github.com/kpcyrd) ### Fixed - Fix string conversion error on macOS (https://github.com/imsnif/bandwhich/pull/79) - [@zhangxp1998](https://github.com/zhangxp1998) - Proper fix for macos no-screen-of-death (https://github.com/imsnif/bandwhich/pull/83) - [@zhangxp1998](https://github.com/zhangxp1998) + [@imsnif](https://github.com/imsnif) - Fix UDP traffic not displayed issue #81 with (https://github.com/imsnif/bandwhich/pull/82) - [@zhangxp1998](https://github.com/zhangxp1998) - Fix mac build (https://github.com/imsnif/bandwhich/pull/93) - [@imsnif](https://github.com/imsnif) - Better procfs error handling (https://github.com/imsnif/bandwhich/pull/88) - [@zhangxp1998](https://github.com/zhangxp1998) ## [0.7.0] - 2020-01-05 ### Added - Running instructions (sudo) for a cargo installation (https://github.com/imsnif/bandwhich/pull/42) - [@LoyVanBeek](https://github.com/LoyVanBeek) + [@filalex77](https://github.com/filalex77) - `setcap` instructions for linux instead of using sudo (https://github.com/imsnif/bandwhich/pull/57) - [@Calinou](https://github.com/Calinou) - Installation instructions for Nix/NixOS (https://github.com/imsnif/bandwhich/pull/32) - [@filalex77](https://github.com/filalex77) - MSRV and cargo installation instructions (https://github.com/imsnif/bandwhich/pull/66) - [@ebroto](https://github.com/ebroto) ### Fixed - Repository URLs in Cargo.toml (https://github.com/imsnif/bandwhich/pull/43) - [@MatthieuBizien](https://github.com/MatthieuBizien) - Skip interfaces with error (https://github.com/imsnif/bandwhich/pull/49) - [@Grishy](https://github.com/Grishy) - MacOS no-screen-of-death workaround (https://github.com/imsnif/bandwhich/pull/56) - [@zhangxp1998](https://github.com/zhangxp1998) - Reduce CPU utilization on linux (https://github.com/imsnif/bandwhich/pull/68) - [@ebroto](https://github.com/ebroto) - Informative sudo error message (https://github.com/imsnif/bandwhich/pull/67) - [@Tobbeman](https://github.com/Tobbeman) - Foreground text color for non-black terminals (https://github.com/imsnif/bandwhich/pull/65) - [@niiiil](https://github.com/niiiil) - Do not truncate process names on MacOS (https://github.com/imsnif/bandwhich/pull/63) - [@zhangxp1998](https://github.com/zhangxp1998) - Refactor tests into shared functionality (https://github.com/imsnif/bandwhich/pull/55) - [@chobeat](https://github.com/chobeat) - Error on 0 interfaces found (https://github.com/imsnif/bandwhich/pull/69) - [@imsnif](https://github.com/imsnif) ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at aram@poor.dev. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq ================================================ FILE: CONTRIBUTING.md ================================================ Contributions of any kind are very welcome. If you'd like a new feature (or found a bug), please open an issue or a PR. To set up your development environment: 1. Clone the project 2. `cargo run`, or if you prefer `cargo run -- -i ` (you can often find out the name with `ifconfig` or `iwconfig`). You might need root privileges to run this application, so be sure to use (for example) sudo. To run tests: `cargo test` After tests, check the formatting: `cargo fmt -- --check` Note that at the moment the tests do not test the os layer (anything in the `os` folder). If you are stuck, unsure about how to approach an issue or would like some guidance, you are welcome to [open an issue](https://github.com/imsnif/bandwhich/issues/new); ================================================ FILE: Cargo.toml ================================================ [package] name = "bandwhich" version = "0.23.1" authors = [ "Aram Drevekenin ", "Eduardo Toledo ", "Eduardo Broto ", "Kelvin Zhang ", "Brooks Rady ", "cyqsimon <28627918+cyqsimon@users.noreply.github.com>", ] categories = ["network-programming", "command-line-utilities"] edition = "2021" exclude = ["src/tests/*", "demo.gif"] homepage = "https://github.com/imsnif/bandwhich" keywords = ["networking", "utilization", "cli"] license = "MIT" readme = "README.md" repository = "https://github.com/imsnif/bandwhich" rust-version = "1.82.0" description = "Display current network utilization by process, connection and remote IP/hostname" [features] default = [] # UI tests temporarily disabled by default, until big refactor is done ui_test = [] [dependencies] async-trait = "0.1.88" chrono = "0.4" clap-verbosity-flag = "3.0.3" clap = { version = "4.5.41", features = ["derive"] } crossterm = "0.29.0" ctrlc = "3.4" derive_more = { version = "2.0.1", features = ["debug"] } eyre = "0.6.12" itertools = "0.14.0" log = "0.4.27" once_cell = "1.21.3" pnet = "0.35.0" pnet_macros_support = "0.35.0" ratatui = "0.29.0" resolv-conf = "0.7.4" simplelog = "0.12.2" thiserror = "2.0.12" tokio = { version = "1.46", features = ["rt", "sync"] } trust-dns-resolver = "0.23.2" unicode-width = "0.2.0" strum = { version = "0.27.1", features = ["derive"] } [target.'cfg(any(target_os = "android", target_os = "linux"))'.dependencies] procfs = "0.17.0" [target.'cfg(any(target_os = "macos", target_os = "freebsd"))'.dependencies] regex = "1.11.1" [target.'cfg(target_os = "windows")'.dependencies] netstat2 = "0.11.1" sysinfo = "0.35.2" [dev-dependencies] insta = "1.43.1" packet-builder = { version = "0.7.0", git = "https://github.com/cyqsimon/packet_builder.git", branch = "patch-pnet-0.35" } pnet_base = "0.35.0" regex = "1.11.1" rstest = "0.25.0" [build-dependencies] clap = { version = "4.5.41", features = ["derive"] } clap-verbosity-flag = "3.0.3" clap_complete = "4.5.55" clap_mangen = "0.2.28" derive_more = { version = "2.0.1", features = ["debug"] } eyre = "0.6.12" strum = { version = "0.27.1", features = ["derive"] } [target.'cfg(target_os = "windows")'.build-dependencies] http_req = "0.14.0" zip = "2.4.2" [profile.release] codegen-units = 1 opt-level = 3 lto = "fat" panic = "abort" strip = "symbols" ================================================ FILE: Cross.toml ================================================ [build.env] passthrough = ["BANDWHICH_GEN_DIR"] ================================================ FILE: INSTALL.md ================================================ # Installation - [Installation](#installation) - [Arch Linux](#arch-linux) - [Exherbo Linux](#exherbo-linux) - [Nix/NixOS](#nixnixos) - [Void Linux](#void-linux) - [Fedora](#fedora) - [macOS/Linux (using Homebrew)](#macoslinux-using-homebrew) - [macOS (using MacPorts)](#macos-using-macports) - [FreeBSD](#freebsd) - [Cargo](#cargo) ## Arch Linux ``` pacman -S bandwhich ``` ## Exherbo Linux `bandwhich` is available in [rust repository](https://gitlab.exherbo.org/exherbo/rust/-/tree/master/packages/sys-apps/bandwhich), and can be installed via `cave`: ``` cave resolve -x repository/rust cave resolve -x bandwhich ``` ## Nix/NixOS `bandwhich` is available in [`nixpkgs`](https://github.com/nixos/nixpkgs/blob/master/pkgs/tools/networking/bandwhich/default.nix), and can be installed, for example, with `nix-env`: ``` nix-env -iA nixpkgs.bandwhich ``` ## Void Linux ``` xbps-install -S bandwhich ``` ## Fedora `bandwhich` is available in [COPR](https://copr.fedorainfracloud.org/coprs/atim/bandwhich/), and can be installed via DNF: ``` sudo dnf copr enable atim/bandwhich -y && sudo dnf install bandwhich ``` ## macOS/Linux (using Homebrew) ``` brew install bandwhich ``` ## macOS (using MacPorts) ``` sudo port selfupdate sudo port install bandwhich ``` ## FreeBSD ``` pkg install bandwhich ``` or ``` cd /usr/ports/net-mgmt/bandwhich && make install clean ``` ## Cargo Regardless of OS, you can always fallback to the Rust package manager, `cargo`. For installation instructions of the Rust toolchain, see [here](https://www.rust-lang.org/tools/install). ``` cargo install bandwhich ``` ================================================ FILE: LICENSE.md ================================================ MIT License Copyright (c) 2019 Aram Drevekenin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # bandwhich ![demo](res/demo.gif) This is a CLI utility for displaying current network utilization by process, connection and remote IP/hostname ## Table of contents - [bandwhich](#bandwhich) - [Table of contents](#table-of-contents) - [Project status](#project-status) - [How does it work?](#how-does-it-work) - [Installation](#installation) - [Downstream packaging status](#downstream-packaging-status) - [Download a prebuilt binary](#download-a-prebuilt-binary) - [Building from source](#building-from-source) - [Cross-compiling](#cross-compiling) - [Android](#android) - [Post install (Linux)](#post-install-linux) - [1. `setcap`](#1-setcap) - [Capabilities explained](#capabilities-explained) - [2. `sudo` (or alternative)](#2-sudo-or-alternative) - [Post install (Windows)](#post-install-windows) - [Usage](#usage) - [Contributing](#contributing) - [License](#license) ## Project status This project is in passive maintenance. Critical issues will be addressed, but no new features are being worked on. However, this is due to a lack of funding and/or manpower more than anything else, so pull requests are more than welcome. In addition, if you are able and willing to contribute to this project long-term, we would like to invite you to apply for co-maintainership. For more details, see [The Future of Bandwhich #275](https://github.com/imsnif/bandwhich/issues/275). ## How does it work? `bandwhich` sniffs a given network interface and records IP packet size, cross referencing it with the `/proc` filesystem on linux, `lsof` on macOS, or using WinApi on windows. It is responsive to the terminal window size, displaying less info if there is no room for it. It will also attempt to resolve ips to their host name in the background using reverse DNS on a best effort basis. ## Installation ### Downstream packaging status For detailed instructions for each platform, see [INSTALL.md](INSTALL.md). Packaging status ### Download a prebuilt binary We offer several generic binaries in [releases](https://github.com/imsnif/bandwhich/releases) for various OSes.
OSArchitectureSupportUsage
Androidaarch64Best effort

All modern Android devices.

Note that this is a pure binary file, not an APK suitable for general usage.

Linuxaarch64Full 64-bit ARMv8+ (servers, some modern routers, RPi-4+).
armv7hfBest effort32-bit ARMv7 (older routers, pre-RPi-4).
x64Full Most Linux desktops & servers.
MacOSaarch64Full Apple silicon Macs (2021+).
x64 Intel Macs (pre-2021).
Windowsx64Full Most Windows desktops & servers.
## Building from source ```sh git clone https://github.com/imsnif/bandwhich.git cd bandwhich cargo build --release ``` For the up-to-date minimum supported Rust version, please refer to the `rust-version` field in [Cargo.toml](Cargo.toml). ### Cross-compiling Cross-compiling for alternate targets is supported via [cross](https://github.com/cross-rs/cross). Here's the rough procedure: 1. Check the target architecture. If on Linux, you can use `uname -m`. 2. Lookup [rustc platform support](https://doc.rust-lang.org/rustc/platform-support.html) for the corresponding target triple. 3. [Install `cross`](https://github.com/cross-rs/cross#installation). 4. Run `cross build --release --target `. #### Android Until [cross-rs/cross#1222](https://github.com/cross-rs/cross/issues/1222) is solved, use the latest HEAD: ```sh cargo install --git https://github.com/cross-rs/cross.git cross cross build --release --target aarch64-linux-android ``` ## Post install (Linux) Since `bandwhich` sniffs network packets, it requires elevated privileges. On Linux, there are two main ways to accomplish this: ### 1. `setcap` - Permanently allow the `bandwhich` binary its required privileges (called "capabilities" in Linux). - Do this if you want to give all unprivileged users full access to bandwhich's monitoring capabilities. - This is the **recommended** setup **for single user machines**, or **if all users are trusted**. - This is **not recommended** if you want to **ensure users cannot see others' traffic**. ```sh # assign capabilities sudo setcap cap_sys_ptrace,cap_dac_read_search,cap_net_raw,cap_net_admin+ep $(command -v bandwhich) # run as unprivileged user bandwhich ``` #### Capabilities explained - `cap_sys_ptrace,cap_dac_read_search`: allow access to `/proc//fd/`, so that `bandwhich` can determine which open port belongs to which process. - `cap_net_raw,cap_net_admin`: allow capturing packets on your system. ### 2. `sudo` (or alternative) - Require privilege escalation every time. - Do this if you are an administrator of a multi-user environment. ```sh sudo bandwhich ``` Note that if your installation method installed `bandwhich` to somewhere in your home directory (you can check with `command -v bandwhich`), you may get a `command not found` error. This is because in many distributions, `sudo` by default does not keep your user's `$PATH` for safety concerns. To overcome this, you can do any one of the following: 1. [make `sudo` preserve your `$PATH` environment variable](https://unix.stackexchange.com/q/83191/375550); 2. explicitly set `$PATH` while running `bandwhich`: `sudo env "PATH=$PATH" bandwhich`; 3. pass the full path to `sudo`: `sudo $(command -v bandwhich)`. ## Post install (Windows) You might need to first install [npcap](https://npcap.com/#download) for capturing packets on Windows. ## Usage ``` Usage: bandwhich [OPTIONS] Options: -i, --interface The network interface to listen on, eg. eth0 -r, --raw Machine friendlier output -n, --no-resolve Do not attempt to resolve IPs to their hostnames -s, --show-dns Show DNS queries -d, --dns-server A dns server ip to use instead of the system default --log-to Enable debug logging to a file -v, --verbose... Increase logging verbosity -q, --quiet... Decrease logging verbosity -p, --processes Show processes table only -c, --connections Show connections table only -a, --addresses Show remote addresses table only -u, --unit-family Choose a specific family of units [default: bin-bytes] [possible values: bin-bytes, bin-bits, si-bytes, si-bits] -t, --total-utilization Show total (cumulative) usages -h, --help Print help (see more with '--help') -V, --version Print version ``` ## Contributing See [CONTRIBUTING.md](CONTRIBUTING.md). ## License MIT ================================================ FILE: build.rs ================================================ use std::{env, fs::File}; use clap::CommandFactory; use clap_complete::Shell; use clap_mangen::Man; use eyre::eyre; fn main() { build_completion_manpage().unwrap(); #[cfg(target_os = "windows")] download_windows_npcap_sdk().unwrap(); } include!("src/cli.rs"); fn build_completion_manpage() -> eyre::Result<()> { let mut cmd = Opt::command(); // build into `BANDWHICH_GEN_DIR` with a fallback to `OUT_DIR` let gen_dir: PathBuf = env::var_os("BANDWHICH_GEN_DIR") .or_else(|| env::var_os("OUT_DIR")) .ok_or(eyre!("OUT_DIR is unset"))? .into(); // completion for &shell in Shell::value_variants() { clap_complete::generate_to(shell, &mut cmd, "bandwhich", &gen_dir)?; } // manpage let mut manpage_out = File::create(gen_dir.join("bandwhich.1"))?; let manpage = Man::new(cmd); manpage.render(&mut manpage_out)?; Ok(()) } #[cfg(target_os = "windows")] fn download_windows_npcap_sdk() -> eyre::Result<()> { use std::{ fs, io::{self, Write}, }; use http_req::request; use zip::ZipArchive; println!("cargo:rerun-if-changed=build.rs"); // get npcap SDK const NPCAP_SDK: &str = "npcap-sdk-1.13.zip"; let npcap_sdk_download_url = format!("https://npcap.com/dist/{NPCAP_SDK}"); let cache_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR")?).join("target"); let npcap_sdk_cache_path = cache_dir.join(NPCAP_SDK); let npcap_zip = match fs::read(&npcap_sdk_cache_path) { // use cached Ok(zip_data) => { eprintln!("Found cached npcap SDK"); zip_data } // download SDK Err(_) => { eprintln!("Downloading npcap SDK"); // download let mut zip_data = vec![]; let _res = request::get(npcap_sdk_download_url, &mut zip_data)?; // write cache fs::create_dir_all(cache_dir)?; let mut cache = fs::File::create(npcap_sdk_cache_path)?; cache.write_all(&zip_data)?; zip_data } }; // extract DLL let lib_path = if cfg!(target_arch = "aarch64") { "Lib/ARM64/Packet.lib" } else if cfg!(target_arch = "x86_64") { "Lib/x64/Packet.lib" } else if cfg!(target_arch = "x86") { "Lib/Packet.lib" } else { panic!("Unsupported target!") }; let mut archive = ZipArchive::new(io::Cursor::new(npcap_zip))?; let mut npcap_lib = archive.by_name(lib_path)?; // write DLL let lib_dir = PathBuf::from(env::var("OUT_DIR")?).join("npcap_sdk"); let lib_path = lib_dir.join("Packet.lib"); fs::create_dir_all(&lib_dir)?; let mut lib_file = fs::File::create(lib_path)?; io::copy(&mut npcap_lib, &mut lib_file)?; println!( "cargo:rustc-link-search=native={}", lib_dir .to_str() .ok_or(eyre!("{lib_dir:?} is not valid UTF-8"))? ); Ok(()) } ================================================ FILE: rustfmt.toml ================================================ ================================================ FILE: src/cli.rs ================================================ use std::{net::Ipv4Addr, path::PathBuf}; use clap::{Args, Parser, ValueEnum, ValueHint}; use clap_verbosity_flag::{InfoLevel, Verbosity}; use derive_more::Debug; use strum::EnumIter; #[derive(Clone, Debug, Parser, Default)] #[command(name = "bandwhich", version)] pub struct Opt { #[arg(short, long)] /// The network interface to listen on, eg. eth0 pub interface: Option, #[arg(short, long)] /// Machine friendlier output pub raw: bool, #[arg(short, long)] /// Do not attempt to resolve IPs to their hostnames pub no_resolve: bool, #[arg(short, long)] /// Show DNS queries pub show_dns: bool, #[arg(short, long)] /// A dns server ip to use instead of the system default pub dns_server: Option, #[arg(long, value_hint = ValueHint::FilePath)] /// Enable debug logging to a file pub log_to: Option, #[command(flatten)] pub verbosity: Verbosity, #[command(flatten)] pub render_opts: RenderOpts, } #[derive(Copy, Clone, Debug, Default, Args)] pub struct RenderOpts { #[arg(short, long)] /// Show processes table only pub processes: bool, #[arg(short, long)] /// Show connections table only pub connections: bool, #[arg(short, long)] /// Show remote addresses table only pub addresses: bool, #[arg(short, long, value_enum, default_value_t)] /// Choose a specific family of units pub unit_family: UnitFamily, #[arg(short, long)] /// Show total (cumulative) usages pub total_utilization: bool, } // IMPRV: it would be nice if we can `#[cfg_attr(not(build), derive(strum::EnumIter))]` this // unfortunately there is no configuration option for build script detection #[derive(Copy, Clone, Debug, Default, Eq, PartialEq, ValueEnum, EnumIter)] pub enum UnitFamily { #[default] /// bytes, in powers of 2^10 BinBytes, /// bits, in powers of 2^10 BinBits, /// bytes, in powers of 10^3 SiBytes, /// bits, in powers of 10^3 SiBits, } ================================================ FILE: src/display/components/display_bandwidth.rs ================================================ use std::fmt; use derive_more::Debug; use crate::cli::UnitFamily; #[derive(Copy, Clone, Debug)] pub struct DisplayBandwidth { // Custom format for reduced precision. // Workaround for FP calculation discrepancy between Unix and Windows. // See https://github.com/rust-lang/rust/issues/111405#issuecomment-2055964223. #[debug("{bandwidth:.10e}")] pub bandwidth: f64, pub unit_family: BandwidthUnitFamily, } impl fmt::Display for DisplayBandwidth { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let (div, suffix) = self.unit_family.get_unit_for(self.bandwidth); write!(f, "{:.2}{suffix}", self.bandwidth / div) } } /// Type wrapper around [`UnitFamily`] to provide extra functionality. #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] #[debug("{_0:?}")] pub struct BandwidthUnitFamily(UnitFamily); impl From for BandwidthUnitFamily { fn from(value: UnitFamily) -> Self { Self(value) } } impl BandwidthUnitFamily { #[inline] /// Returns an array of tuples, corresponding to the steps of this unit family. /// /// Each step contains a divisor, an upper bound, and a unit suffix. fn steps(&self) -> [(f64, f64, &'static str); 6] { /// The fraction of the next unit the value has to meet to step up. const STEP_UP_FRAC: f64 = 0.95; /// Binary base: 2^10. const BB: f64 = 1024.0; use UnitFamily as F; // probably could macro this stuff, but I'm too lazy match self.0 { F::BinBytes => [ (1.0, BB * STEP_UP_FRAC, "B"), (BB, BB.powi(2) * STEP_UP_FRAC, "KiB"), (BB.powi(2), BB.powi(3) * STEP_UP_FRAC, "MiB"), (BB.powi(3), BB.powi(4) * STEP_UP_FRAC, "GiB"), (BB.powi(4), BB.powi(5) * STEP_UP_FRAC, "TiB"), (BB.powi(5), f64::MAX, "PiB"), ], F::BinBits => [ (1.0 / 8.0, BB / 8.0 * STEP_UP_FRAC, "b"), (BB / 8.0, BB.powi(2) / 8.0 * STEP_UP_FRAC, "Kib"), (BB.powi(2) / 8.0, BB.powi(3) / 8.0 * STEP_UP_FRAC, "Mib"), (BB.powi(3) / 8.0, BB.powi(4) / 8.0 * STEP_UP_FRAC, "Gib"), (BB.powi(4) / 8.0, BB.powi(5) / 8.0 * STEP_UP_FRAC, "Tib"), (BB.powi(5) / 8.0, f64::MAX, "Pib"), ], F::SiBytes => [ (1.0, 1e3 * STEP_UP_FRAC, "B"), (1e3, 1e6 * STEP_UP_FRAC, "kB"), (1e6, 1e9 * STEP_UP_FRAC, "MB"), (1e9, 1e12 * STEP_UP_FRAC, "GB"), (1e12, 1e15 * STEP_UP_FRAC, "TB"), (1e15, f64::MAX, "PB"), ], F::SiBits => [ (1.0 / 8.0, 1e3 / 8.0 * STEP_UP_FRAC, "b"), (1e3 / 8.0, 1e6 / 8.0 * STEP_UP_FRAC, "kb"), (1e6 / 8.0, 1e9 / 8.0 * STEP_UP_FRAC, "Mb"), (1e9 / 8.0, 1e12 / 8.0 * STEP_UP_FRAC, "Gb"), (1e12 / 8.0, 1e15 / 8.0 * STEP_UP_FRAC, "Tb"), (1e15 / 8.0, f64::MAX, "Pb"), ], } } /// Select a unit for a given value, returning its divisor and suffix. fn get_unit_for(&self, bytes: f64) -> (f64, &'static str) { let Some((div, _, suffix)) = self .steps() .into_iter() .find(|&(_, bound, _)| bound >= bytes) else { panic!("Cannot select an appropriate unit for {bytes:.2}B.") }; (div, suffix) } } #[cfg(test)] mod tests { use std::fmt::Write; use insta::assert_snapshot; use itertools::Itertools; use strum::IntoEnumIterator; use crate::{cli::UnitFamily, display::DisplayBandwidth}; #[test] fn bandwidth_formatting() { let test_bandwidths_formatted = UnitFamily::iter() .map_into() .cartesian_product( // I feel like this is a decent selection of values (-6..60) .map(|exp| 2f64.powi(exp)) .chain((-5..45).map(|exp| 2.5f64.powi(exp))) .chain((-4..38).map(|exp| 3f64.powi(exp))) .chain((-3..26).map(|exp| 5f64.powi(exp))), ) .map(|(unit_family, bandwidth)| DisplayBandwidth { bandwidth, unit_family, }) .fold(String::new(), |mut buf, b| { let _ = writeln!(buf, "{b:?}: {b}"); buf }); assert_snapshot!(test_bandwidths_formatted); } } ================================================ FILE: src/display/components/header_details.rs ================================================ use std::time::{Duration, Instant}; use ratatui::{ layout::{Alignment, Rect}, style::{Color, Modifier, Style}, text::Span, widgets::Paragraph, Frame, }; use unicode_width::UnicodeWidthStr; use crate::display::{DisplayBandwidth, UIState}; pub fn elapsed_time(last_start_time: Instant, cumulative_time: Duration, paused: bool) -> Duration { if paused { cumulative_time } else { cumulative_time + last_start_time.elapsed() } } fn format_duration(d: Duration) -> String { let s = d.as_secs(); let days = match s / 86400 { 0 => "".to_string(), 1 => "1 day, ".to_string(), n => format!("{n} days, "), }; format!( "{days}{:02}:{:02}:{:02}", (s / 3600) % 24, (s / 60) % 60, s % 60, ) } pub struct HeaderDetails<'a> { pub state: &'a UIState, pub elapsed_time: Duration, pub paused: bool, } impl HeaderDetails<'_> { pub fn render(&self, frame: &mut Frame, rect: Rect) { let bandwidth = self.bandwidth_string(); let color = if self.paused { Color::Yellow } else { Color::Green }; // do not render time in tests, otherwise the output becomes non-deterministic // see: https://github.com/imsnif/bandwhich/issues/303 if cfg!(not(test)) && self.state.cumulative_mode { let elapsed_time = format_duration(self.elapsed_time); // only render if there is enough width if bandwidth.width() + 1 + elapsed_time.width() <= rect.width as usize { self.render_elapsed_time(frame, rect, &elapsed_time, color); } } self.render_bandwidth(frame, rect, &bandwidth, color); } fn render_bandwidth(&self, frame: &mut Frame, rect: Rect, bandwidth: &str, color: Color) { let bandwidth_text = Span::styled( bandwidth, Style::default().fg(color).add_modifier(Modifier::BOLD), ); let paragraph = Paragraph::new(bandwidth_text).alignment(Alignment::Left); frame.render_widget(paragraph, rect); } fn bandwidth_string(&self) -> String { let intrf = self.state.interface_name.as_deref().unwrap_or("all"); let t = if self.state.cumulative_mode { "Data" } else { "Rate" }; let unit_family = self.state.unit_family; let up = DisplayBandwidth { bandwidth: self.state.total_bytes_uploaded as f64, unit_family, }; let down = DisplayBandwidth { bandwidth: self.state.total_bytes_downloaded as f64, unit_family, }; let paused = if self.paused { " [PAUSED]" } else { "" }; format!("IF: {intrf} | Total {t} (Up / Down): {up} / {down}{paused}") } fn render_elapsed_time(&self, frame: &mut Frame, rect: Rect, elapsed_time: &str, color: Color) { let elapsed_time_text = Span::styled( elapsed_time, Style::default().fg(color).add_modifier(Modifier::BOLD), ); let paragraph = Paragraph::new(elapsed_time_text).alignment(Alignment::Right); frame.render_widget(paragraph, rect); } } ================================================ FILE: src/display/components/help_text.rs ================================================ use ratatui::{ layout::{Alignment, Rect}, style::{Modifier, Style}, text::Span, widgets::Paragraph, Frame, }; pub struct HelpText { pub paused: bool, pub show_dns: bool, } const FIRST_WIDTH_BREAKPOINT: u16 = 76; const SECOND_WIDTH_BREAKPOINT: u16 = 54; const TEXT_WHEN_PAUSED: &str = " Press to resume."; const TEXT_WHEN_NOT_PAUSED: &str = " Press to pause."; const TEXT_WHEN_DNS_NOT_SHOWN: &str = " (DNS queries hidden)."; const TEXT_WHEN_DNS_SHOWN: &str = " (DNS queries shown)."; const TEXT_TAB_TIP: &str = " Use to rearrange tables."; impl HelpText { pub fn render(&self, frame: &mut Frame, rect: Rect) { let pause_content = if self.paused { TEXT_WHEN_PAUSED } else { TEXT_WHEN_NOT_PAUSED }; let dns_content = if rect.width <= FIRST_WIDTH_BREAKPOINT { "" } else if self.show_dns { TEXT_WHEN_DNS_SHOWN } else { TEXT_WHEN_DNS_NOT_SHOWN }; let tab_text = if rect.width <= SECOND_WIDTH_BREAKPOINT { "" } else { TEXT_TAB_TIP }; let text = Span::styled( [pause_content, tab_text, dns_content].concat(), Style::default().add_modifier(Modifier::BOLD), ); let paragraph = Paragraph::new(text).alignment(Alignment::Left); frame.render_widget(paragraph, rect); } } ================================================ FILE: src/display/components/layout.rs ================================================ use ratatui::{ layout::{Constraint, Direction, Rect}, Frame, }; use crate::display::{HeaderDetails, HelpText, Table}; const FIRST_HEIGHT_BREAKPOINT: u16 = 30; const FIRST_WIDTH_BREAKPOINT: u16 = 120; fn top_app_and_bottom_split(rect: Rect) -> (Rect, Rect, Rect) { let parts = ratatui::layout::Layout::default() .direction(Direction::Vertical) .margin(0) .constraints( [ Constraint::Length(1), Constraint::Length(rect.height - 2), Constraint::Length(1), ] .as_ref(), ) .split(rect); (parts[0], parts[1], parts[2]) } pub struct Layout<'a> { pub header: HeaderDetails<'a>, pub children: Vec, pub footer: HelpText, } impl Layout<'_> { fn progressive_split(&self, rect: Rect, splits: Vec) -> Vec { splits .into_iter() .fold(vec![rect], |mut layout, direction| { let last_rect = layout.pop().unwrap(); let halves = ratatui::layout::Layout::default() .direction(direction) .margin(0) .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) .split(last_rect); layout.append(&mut halves.to_vec()); layout }) } fn build_two_children_layout(&self, rect: Rect) -> Vec { // if there are two elements if rect.height < FIRST_HEIGHT_BREAKPOINT && rect.width < FIRST_WIDTH_BREAKPOINT { // if the space is not enough, we drop one element vec![rect] } else if rect.width < FIRST_WIDTH_BREAKPOINT { // if the horizontal space is not enough, we drop one element and we split horizontally self.progressive_split(rect, vec![Direction::Vertical]) } else { // by default we display two elements splitting vertically self.progressive_split(rect, vec![Direction::Horizontal]) } } fn build_three_children_layout(&self, rect: Rect) -> Vec { // if there are three elements if rect.height < FIRST_HEIGHT_BREAKPOINT && rect.width < FIRST_WIDTH_BREAKPOINT { //if the space is not enough, we drop two elements vec![rect] } else if rect.height < FIRST_HEIGHT_BREAKPOINT { // if the vertical space is not enough, we drop one element and we split vertically self.progressive_split(rect, vec![Direction::Horizontal]) } else if rect.width < FIRST_WIDTH_BREAKPOINT { // if the horizontal space is not enough, we drop one element and we split horizontally self.progressive_split(rect, vec![Direction::Vertical]) } else { // default layout let halves = ratatui::layout::Layout::default() .direction(Direction::Vertical) .margin(0) .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) .split(rect); let top_quarters = ratatui::layout::Layout::default() .direction(Direction::Horizontal) .margin(0) .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) .split(halves[0]); vec![top_quarters[0], top_quarters[1], halves[1]] } } fn build_layout(&self, rect: Rect) -> Vec { if self.children.len() == 1 { // if there's only one element to render, it can take the whole frame vec![rect] } else if self.children.len() == 2 { self.build_two_children_layout(rect) } else { self.build_three_children_layout(rect) } } pub fn render(&self, frame: &mut Frame, rect: Rect, table_cycle_offset: usize) { let (top, app, bottom) = top_app_and_bottom_split(rect); let layout_slots = self.build_layout(app); for i in 0..layout_slots.len() { if let Some(rect) = layout_slots.get(i) { if let Some(child) = self .children .get((i + table_cycle_offset) % self.children.len()) { child.render(frame, *rect); } } } self.header.render(frame, top); self.footer.render(frame, bottom); } } ================================================ FILE: src/display/components/mod.rs ================================================ mod display_bandwidth; mod header_details; mod help_text; mod layout; mod table; pub use display_bandwidth::*; pub use header_details::*; pub use help_text::*; pub use layout::*; pub use table::*; ================================================ FILE: src/display/components/snapshots/bandwhich__display__components__display_bandwidth__tests__bandwidth_formatting.snap ================================================ --- source: src/display/components/display_bandwidth.rs expression: test_bandwidths_formatted --- DisplayBandwidth { bandwidth: 1.5625000000e-2, unit_family: BinBytes }: 0.02B DisplayBandwidth { bandwidth: 3.1250000000e-2, unit_family: BinBytes }: 0.03B DisplayBandwidth { bandwidth: 6.2500000000e-2, unit_family: BinBytes }: 0.06B DisplayBandwidth { bandwidth: 1.2500000000e-1, unit_family: BinBytes }: 0.12B DisplayBandwidth { bandwidth: 2.5000000000e-1, unit_family: BinBytes }: 0.25B DisplayBandwidth { bandwidth: 5.0000000000e-1, unit_family: BinBytes }: 0.50B DisplayBandwidth { bandwidth: 1.0000000000e0, unit_family: BinBytes }: 1.00B DisplayBandwidth { bandwidth: 2.0000000000e0, unit_family: BinBytes }: 2.00B DisplayBandwidth { bandwidth: 4.0000000000e0, unit_family: BinBytes }: 4.00B DisplayBandwidth { bandwidth: 8.0000000000e0, unit_family: BinBytes }: 8.00B DisplayBandwidth { bandwidth: 1.6000000000e1, unit_family: BinBytes }: 16.00B DisplayBandwidth { bandwidth: 3.2000000000e1, unit_family: BinBytes }: 32.00B DisplayBandwidth { bandwidth: 6.4000000000e1, unit_family: BinBytes }: 64.00B DisplayBandwidth { bandwidth: 1.2800000000e2, unit_family: BinBytes }: 128.00B DisplayBandwidth { bandwidth: 2.5600000000e2, unit_family: BinBytes }: 256.00B DisplayBandwidth { bandwidth: 5.1200000000e2, unit_family: BinBytes }: 512.00B DisplayBandwidth { bandwidth: 1.0240000000e3, unit_family: BinBytes }: 1.00KiB DisplayBandwidth { bandwidth: 2.0480000000e3, unit_family: BinBytes }: 2.00KiB DisplayBandwidth { bandwidth: 4.0960000000e3, unit_family: BinBytes }: 4.00KiB DisplayBandwidth { bandwidth: 8.1920000000e3, unit_family: BinBytes }: 8.00KiB DisplayBandwidth { bandwidth: 1.6384000000e4, unit_family: BinBytes }: 16.00KiB DisplayBandwidth { bandwidth: 3.2768000000e4, unit_family: BinBytes }: 32.00KiB DisplayBandwidth { bandwidth: 6.5536000000e4, unit_family: BinBytes }: 64.00KiB DisplayBandwidth { bandwidth: 1.3107200000e5, unit_family: BinBytes }: 128.00KiB DisplayBandwidth { bandwidth: 2.6214400000e5, unit_family: BinBytes }: 256.00KiB DisplayBandwidth { bandwidth: 5.2428800000e5, unit_family: BinBytes }: 512.00KiB DisplayBandwidth { bandwidth: 1.0485760000e6, unit_family: BinBytes }: 1.00MiB DisplayBandwidth { bandwidth: 2.0971520000e6, unit_family: BinBytes }: 2.00MiB DisplayBandwidth { bandwidth: 4.1943040000e6, unit_family: BinBytes }: 4.00MiB DisplayBandwidth { bandwidth: 8.3886080000e6, unit_family: BinBytes }: 8.00MiB DisplayBandwidth { bandwidth: 1.6777216000e7, unit_family: BinBytes }: 16.00MiB DisplayBandwidth { bandwidth: 3.3554432000e7, unit_family: BinBytes }: 32.00MiB DisplayBandwidth { bandwidth: 6.7108864000e7, unit_family: BinBytes }: 64.00MiB DisplayBandwidth { bandwidth: 1.3421772800e8, unit_family: BinBytes }: 128.00MiB DisplayBandwidth { bandwidth: 2.6843545600e8, unit_family: BinBytes }: 256.00MiB DisplayBandwidth { bandwidth: 5.3687091200e8, unit_family: BinBytes }: 512.00MiB DisplayBandwidth { bandwidth: 1.0737418240e9, unit_family: BinBytes }: 1.00GiB DisplayBandwidth { bandwidth: 2.1474836480e9, unit_family: BinBytes }: 2.00GiB DisplayBandwidth { bandwidth: 4.2949672960e9, unit_family: BinBytes }: 4.00GiB DisplayBandwidth { bandwidth: 8.5899345920e9, unit_family: BinBytes }: 8.00GiB DisplayBandwidth { bandwidth: 1.7179869184e10, unit_family: BinBytes }: 16.00GiB DisplayBandwidth { bandwidth: 3.4359738368e10, unit_family: BinBytes }: 32.00GiB DisplayBandwidth { bandwidth: 6.8719476736e10, unit_family: BinBytes }: 64.00GiB DisplayBandwidth { bandwidth: 1.3743895347e11, unit_family: BinBytes }: 128.00GiB DisplayBandwidth { bandwidth: 2.7487790694e11, unit_family: BinBytes }: 256.00GiB DisplayBandwidth { bandwidth: 5.4975581389e11, unit_family: BinBytes }: 512.00GiB DisplayBandwidth { bandwidth: 1.0995116278e12, unit_family: BinBytes }: 1.00TiB DisplayBandwidth { bandwidth: 2.1990232556e12, unit_family: BinBytes }: 2.00TiB DisplayBandwidth { bandwidth: 4.3980465111e12, unit_family: BinBytes }: 4.00TiB DisplayBandwidth { bandwidth: 8.7960930222e12, unit_family: BinBytes }: 8.00TiB DisplayBandwidth { bandwidth: 1.7592186044e13, unit_family: BinBytes }: 16.00TiB DisplayBandwidth { bandwidth: 3.5184372089e13, unit_family: BinBytes }: 32.00TiB DisplayBandwidth { bandwidth: 7.0368744178e13, unit_family: BinBytes }: 64.00TiB DisplayBandwidth { bandwidth: 1.4073748836e14, unit_family: BinBytes }: 128.00TiB DisplayBandwidth { bandwidth: 2.8147497671e14, unit_family: BinBytes }: 256.00TiB DisplayBandwidth { bandwidth: 5.6294995342e14, unit_family: BinBytes }: 512.00TiB DisplayBandwidth { bandwidth: 1.1258999068e15, unit_family: BinBytes }: 1.00PiB DisplayBandwidth { bandwidth: 2.2517998137e15, unit_family: BinBytes }: 2.00PiB DisplayBandwidth { bandwidth: 4.5035996274e15, unit_family: BinBytes }: 4.00PiB DisplayBandwidth { bandwidth: 9.0071992547e15, unit_family: BinBytes }: 8.00PiB DisplayBandwidth { bandwidth: 1.8014398509e16, unit_family: BinBytes }: 16.00PiB DisplayBandwidth { bandwidth: 3.6028797019e16, unit_family: BinBytes }: 32.00PiB DisplayBandwidth { bandwidth: 7.2057594038e16, unit_family: BinBytes }: 64.00PiB DisplayBandwidth { bandwidth: 1.4411518808e17, unit_family: BinBytes }: 128.00PiB DisplayBandwidth { bandwidth: 2.8823037615e17, unit_family: BinBytes }: 256.00PiB DisplayBandwidth { bandwidth: 5.7646075230e17, unit_family: BinBytes }: 512.00PiB DisplayBandwidth { bandwidth: 1.0240000000e-2, unit_family: BinBytes }: 0.01B DisplayBandwidth { bandwidth: 2.5600000000e-2, unit_family: BinBytes }: 0.03B DisplayBandwidth { bandwidth: 6.4000000000e-2, unit_family: BinBytes }: 0.06B DisplayBandwidth { bandwidth: 1.6000000000e-1, unit_family: BinBytes }: 0.16B DisplayBandwidth { bandwidth: 4.0000000000e-1, unit_family: BinBytes }: 0.40B DisplayBandwidth { bandwidth: 1.0000000000e0, unit_family: BinBytes }: 1.00B DisplayBandwidth { bandwidth: 2.5000000000e0, unit_family: BinBytes }: 2.50B DisplayBandwidth { bandwidth: 6.2500000000e0, unit_family: BinBytes }: 6.25B DisplayBandwidth { bandwidth: 1.5625000000e1, unit_family: BinBytes }: 15.62B DisplayBandwidth { bandwidth: 3.9062500000e1, unit_family: BinBytes }: 39.06B DisplayBandwidth { bandwidth: 9.7656250000e1, unit_family: BinBytes }: 97.66B DisplayBandwidth { bandwidth: 2.4414062500e2, unit_family: BinBytes }: 244.14B DisplayBandwidth { bandwidth: 6.1035156250e2, unit_family: BinBytes }: 610.35B DisplayBandwidth { bandwidth: 1.5258789062e3, unit_family: BinBytes }: 1.49KiB DisplayBandwidth { bandwidth: 3.8146972656e3, unit_family: BinBytes }: 3.73KiB DisplayBandwidth { bandwidth: 9.5367431641e3, unit_family: BinBytes }: 9.31KiB DisplayBandwidth { bandwidth: 2.3841857910e4, unit_family: BinBytes }: 23.28KiB DisplayBandwidth { bandwidth: 5.9604644775e4, unit_family: BinBytes }: 58.21KiB DisplayBandwidth { bandwidth: 1.4901161194e5, unit_family: BinBytes }: 145.52KiB DisplayBandwidth { bandwidth: 3.7252902985e5, unit_family: BinBytes }: 363.80KiB DisplayBandwidth { bandwidth: 9.3132257462e5, unit_family: BinBytes }: 909.49KiB DisplayBandwidth { bandwidth: 2.3283064365e6, unit_family: BinBytes }: 2.22MiB DisplayBandwidth { bandwidth: 5.8207660913e6, unit_family: BinBytes }: 5.55MiB DisplayBandwidth { bandwidth: 1.4551915228e7, unit_family: BinBytes }: 13.88MiB DisplayBandwidth { bandwidth: 3.6379788071e7, unit_family: BinBytes }: 34.69MiB DisplayBandwidth { bandwidth: 9.0949470177e7, unit_family: BinBytes }: 86.74MiB DisplayBandwidth { bandwidth: 2.2737367544e8, unit_family: BinBytes }: 216.84MiB DisplayBandwidth { bandwidth: 5.6843418861e8, unit_family: BinBytes }: 542.10MiB DisplayBandwidth { bandwidth: 1.4210854715e9, unit_family: BinBytes }: 1.32GiB DisplayBandwidth { bandwidth: 3.5527136788e9, unit_family: BinBytes }: 3.31GiB DisplayBandwidth { bandwidth: 8.8817841970e9, unit_family: BinBytes }: 8.27GiB DisplayBandwidth { bandwidth: 2.2204460493e10, unit_family: BinBytes }: 20.68GiB DisplayBandwidth { bandwidth: 5.5511151231e10, unit_family: BinBytes }: 51.70GiB DisplayBandwidth { bandwidth: 1.3877787808e11, unit_family: BinBytes }: 129.25GiB DisplayBandwidth { bandwidth: 3.4694469520e11, unit_family: BinBytes }: 323.12GiB DisplayBandwidth { bandwidth: 8.6736173799e11, unit_family: BinBytes }: 807.79GiB DisplayBandwidth { bandwidth: 2.1684043450e12, unit_family: BinBytes }: 1.97TiB DisplayBandwidth { bandwidth: 5.4210108624e12, unit_family: BinBytes }: 4.93TiB DisplayBandwidth { bandwidth: 1.3552527156e13, unit_family: BinBytes }: 12.33TiB DisplayBandwidth { bandwidth: 3.3881317890e13, unit_family: BinBytes }: 30.81TiB DisplayBandwidth { bandwidth: 8.4703294725e13, unit_family: BinBytes }: 77.04TiB DisplayBandwidth { bandwidth: 2.1175823681e14, unit_family: BinBytes }: 192.59TiB DisplayBandwidth { bandwidth: 5.2939559203e14, unit_family: BinBytes }: 481.48TiB DisplayBandwidth { bandwidth: 1.3234889801e15, unit_family: BinBytes }: 1.18PiB DisplayBandwidth { bandwidth: 3.3087224502e15, unit_family: BinBytes }: 2.94PiB DisplayBandwidth { bandwidth: 8.2718061255e15, unit_family: BinBytes }: 7.35PiB DisplayBandwidth { bandwidth: 2.0679515314e16, unit_family: BinBytes }: 18.37PiB DisplayBandwidth { bandwidth: 5.1698788285e16, unit_family: BinBytes }: 45.92PiB DisplayBandwidth { bandwidth: 1.2924697071e17, unit_family: BinBytes }: 114.79PiB DisplayBandwidth { bandwidth: 3.2311742678e17, unit_family: BinBytes }: 286.99PiB DisplayBandwidth { bandwidth: 1.2345679012e-2, unit_family: BinBytes }: 0.01B DisplayBandwidth { bandwidth: 3.7037037037e-2, unit_family: BinBytes }: 0.04B DisplayBandwidth { bandwidth: 1.1111111111e-1, unit_family: BinBytes }: 0.11B DisplayBandwidth { bandwidth: 3.3333333333e-1, unit_family: BinBytes }: 0.33B DisplayBandwidth { bandwidth: 1.0000000000e0, unit_family: BinBytes }: 1.00B DisplayBandwidth { bandwidth: 3.0000000000e0, unit_family: BinBytes }: 3.00B DisplayBandwidth { bandwidth: 9.0000000000e0, unit_family: BinBytes }: 9.00B DisplayBandwidth { bandwidth: 2.7000000000e1, unit_family: BinBytes }: 27.00B DisplayBandwidth { bandwidth: 8.1000000000e1, unit_family: BinBytes }: 81.00B DisplayBandwidth { bandwidth: 2.4300000000e2, unit_family: BinBytes }: 243.00B DisplayBandwidth { bandwidth: 7.2900000000e2, unit_family: BinBytes }: 729.00B DisplayBandwidth { bandwidth: 2.1870000000e3, unit_family: BinBytes }: 2.14KiB DisplayBandwidth { bandwidth: 6.5610000000e3, unit_family: BinBytes }: 6.41KiB DisplayBandwidth { bandwidth: 1.9683000000e4, unit_family: BinBytes }: 19.22KiB DisplayBandwidth { bandwidth: 5.9049000000e4, unit_family: BinBytes }: 57.67KiB DisplayBandwidth { bandwidth: 1.7714700000e5, unit_family: BinBytes }: 173.00KiB DisplayBandwidth { bandwidth: 5.3144100000e5, unit_family: BinBytes }: 518.99KiB DisplayBandwidth { bandwidth: 1.5943230000e6, unit_family: BinBytes }: 1.52MiB DisplayBandwidth { bandwidth: 4.7829690000e6, unit_family: BinBytes }: 4.56MiB DisplayBandwidth { bandwidth: 1.4348907000e7, unit_family: BinBytes }: 13.68MiB DisplayBandwidth { bandwidth: 4.3046721000e7, unit_family: BinBytes }: 41.05MiB DisplayBandwidth { bandwidth: 1.2914016300e8, unit_family: BinBytes }: 123.16MiB DisplayBandwidth { bandwidth: 3.8742048900e8, unit_family: BinBytes }: 369.47MiB DisplayBandwidth { bandwidth: 1.1622614670e9, unit_family: BinBytes }: 1.08GiB DisplayBandwidth { bandwidth: 3.4867844010e9, unit_family: BinBytes }: 3.25GiB DisplayBandwidth { bandwidth: 1.0460353203e10, unit_family: BinBytes }: 9.74GiB DisplayBandwidth { bandwidth: 3.1381059609e10, unit_family: BinBytes }: 29.23GiB DisplayBandwidth { bandwidth: 9.4143178827e10, unit_family: BinBytes }: 87.68GiB DisplayBandwidth { bandwidth: 2.8242953648e11, unit_family: BinBytes }: 263.03GiB DisplayBandwidth { bandwidth: 8.4728860944e11, unit_family: BinBytes }: 789.10GiB DisplayBandwidth { bandwidth: 2.5418658283e12, unit_family: BinBytes }: 2.31TiB DisplayBandwidth { bandwidth: 7.6255974850e12, unit_family: BinBytes }: 6.94TiB DisplayBandwidth { bandwidth: 2.2876792455e13, unit_family: BinBytes }: 20.81TiB DisplayBandwidth { bandwidth: 6.8630377365e13, unit_family: BinBytes }: 62.42TiB DisplayBandwidth { bandwidth: 2.0589113209e14, unit_family: BinBytes }: 187.26TiB DisplayBandwidth { bandwidth: 6.1767339628e14, unit_family: BinBytes }: 561.77TiB DisplayBandwidth { bandwidth: 1.8530201889e15, unit_family: BinBytes }: 1.65PiB DisplayBandwidth { bandwidth: 5.5590605666e15, unit_family: BinBytes }: 4.94PiB DisplayBandwidth { bandwidth: 1.6677181700e16, unit_family: BinBytes }: 14.81PiB DisplayBandwidth { bandwidth: 5.0031545099e16, unit_family: BinBytes }: 44.44PiB DisplayBandwidth { bandwidth: 1.5009463530e17, unit_family: BinBytes }: 133.31PiB DisplayBandwidth { bandwidth: 4.5028390589e17, unit_family: BinBytes }: 399.93PiB DisplayBandwidth { bandwidth: 8.0000000000e-3, unit_family: BinBytes }: 0.01B DisplayBandwidth { bandwidth: 4.0000000000e-2, unit_family: BinBytes }: 0.04B DisplayBandwidth { bandwidth: 2.0000000000e-1, unit_family: BinBytes }: 0.20B DisplayBandwidth { bandwidth: 1.0000000000e0, unit_family: BinBytes }: 1.00B DisplayBandwidth { bandwidth: 5.0000000000e0, unit_family: BinBytes }: 5.00B DisplayBandwidth { bandwidth: 2.5000000000e1, unit_family: BinBytes }: 25.00B DisplayBandwidth { bandwidth: 1.2500000000e2, unit_family: BinBytes }: 125.00B DisplayBandwidth { bandwidth: 6.2500000000e2, unit_family: BinBytes }: 625.00B DisplayBandwidth { bandwidth: 3.1250000000e3, unit_family: BinBytes }: 3.05KiB DisplayBandwidth { bandwidth: 1.5625000000e4, unit_family: BinBytes }: 15.26KiB DisplayBandwidth { bandwidth: 7.8125000000e4, unit_family: BinBytes }: 76.29KiB DisplayBandwidth { bandwidth: 3.9062500000e5, unit_family: BinBytes }: 381.47KiB DisplayBandwidth { bandwidth: 1.9531250000e6, unit_family: BinBytes }: 1.86MiB DisplayBandwidth { bandwidth: 9.7656250000e6, unit_family: BinBytes }: 9.31MiB DisplayBandwidth { bandwidth: 4.8828125000e7, unit_family: BinBytes }: 46.57MiB DisplayBandwidth { bandwidth: 2.4414062500e8, unit_family: BinBytes }: 232.83MiB DisplayBandwidth { bandwidth: 1.2207031250e9, unit_family: BinBytes }: 1.14GiB DisplayBandwidth { bandwidth: 6.1035156250e9, unit_family: BinBytes }: 5.68GiB DisplayBandwidth { bandwidth: 3.0517578125e10, unit_family: BinBytes }: 28.42GiB DisplayBandwidth { bandwidth: 1.5258789062e11, unit_family: BinBytes }: 142.11GiB DisplayBandwidth { bandwidth: 7.6293945312e11, unit_family: BinBytes }: 710.54GiB DisplayBandwidth { bandwidth: 3.8146972656e12, unit_family: BinBytes }: 3.47TiB DisplayBandwidth { bandwidth: 1.9073486328e13, unit_family: BinBytes }: 17.35TiB DisplayBandwidth { bandwidth: 9.5367431641e13, unit_family: BinBytes }: 86.74TiB DisplayBandwidth { bandwidth: 4.7683715820e14, unit_family: BinBytes }: 433.68TiB DisplayBandwidth { bandwidth: 2.3841857910e15, unit_family: BinBytes }: 2.12PiB DisplayBandwidth { bandwidth: 1.1920928955e16, unit_family: BinBytes }: 10.59PiB DisplayBandwidth { bandwidth: 5.9604644775e16, unit_family: BinBytes }: 52.94PiB DisplayBandwidth { bandwidth: 2.9802322388e17, unit_family: BinBytes }: 264.70PiB DisplayBandwidth { bandwidth: 1.5625000000e-2, unit_family: BinBits }: 0.12b DisplayBandwidth { bandwidth: 3.1250000000e-2, unit_family: BinBits }: 0.25b DisplayBandwidth { bandwidth: 6.2500000000e-2, unit_family: BinBits }: 0.50b DisplayBandwidth { bandwidth: 1.2500000000e-1, unit_family: BinBits }: 1.00b DisplayBandwidth { bandwidth: 2.5000000000e-1, unit_family: BinBits }: 2.00b DisplayBandwidth { bandwidth: 5.0000000000e-1, unit_family: BinBits }: 4.00b DisplayBandwidth { bandwidth: 1.0000000000e0, unit_family: BinBits }: 8.00b DisplayBandwidth { bandwidth: 2.0000000000e0, unit_family: BinBits }: 16.00b DisplayBandwidth { bandwidth: 4.0000000000e0, unit_family: BinBits }: 32.00b DisplayBandwidth { bandwidth: 8.0000000000e0, unit_family: BinBits }: 64.00b DisplayBandwidth { bandwidth: 1.6000000000e1, unit_family: BinBits }: 128.00b DisplayBandwidth { bandwidth: 3.2000000000e1, unit_family: BinBits }: 256.00b DisplayBandwidth { bandwidth: 6.4000000000e1, unit_family: BinBits }: 512.00b DisplayBandwidth { bandwidth: 1.2800000000e2, unit_family: BinBits }: 1.00Kib DisplayBandwidth { bandwidth: 2.5600000000e2, unit_family: BinBits }: 2.00Kib DisplayBandwidth { bandwidth: 5.1200000000e2, unit_family: BinBits }: 4.00Kib DisplayBandwidth { bandwidth: 1.0240000000e3, unit_family: BinBits }: 8.00Kib DisplayBandwidth { bandwidth: 2.0480000000e3, unit_family: BinBits }: 16.00Kib DisplayBandwidth { bandwidth: 4.0960000000e3, unit_family: BinBits }: 32.00Kib DisplayBandwidth { bandwidth: 8.1920000000e3, unit_family: BinBits }: 64.00Kib DisplayBandwidth { bandwidth: 1.6384000000e4, unit_family: BinBits }: 128.00Kib DisplayBandwidth { bandwidth: 3.2768000000e4, unit_family: BinBits }: 256.00Kib DisplayBandwidth { bandwidth: 6.5536000000e4, unit_family: BinBits }: 512.00Kib DisplayBandwidth { bandwidth: 1.3107200000e5, unit_family: BinBits }: 1.00Mib DisplayBandwidth { bandwidth: 2.6214400000e5, unit_family: BinBits }: 2.00Mib DisplayBandwidth { bandwidth: 5.2428800000e5, unit_family: BinBits }: 4.00Mib DisplayBandwidth { bandwidth: 1.0485760000e6, unit_family: BinBits }: 8.00Mib DisplayBandwidth { bandwidth: 2.0971520000e6, unit_family: BinBits }: 16.00Mib DisplayBandwidth { bandwidth: 4.1943040000e6, unit_family: BinBits }: 32.00Mib DisplayBandwidth { bandwidth: 8.3886080000e6, unit_family: BinBits }: 64.00Mib DisplayBandwidth { bandwidth: 1.6777216000e7, unit_family: BinBits }: 128.00Mib DisplayBandwidth { bandwidth: 3.3554432000e7, unit_family: BinBits }: 256.00Mib DisplayBandwidth { bandwidth: 6.7108864000e7, unit_family: BinBits }: 512.00Mib DisplayBandwidth { bandwidth: 1.3421772800e8, unit_family: BinBits }: 1.00Gib DisplayBandwidth { bandwidth: 2.6843545600e8, unit_family: BinBits }: 2.00Gib DisplayBandwidth { bandwidth: 5.3687091200e8, unit_family: BinBits }: 4.00Gib DisplayBandwidth { bandwidth: 1.0737418240e9, unit_family: BinBits }: 8.00Gib DisplayBandwidth { bandwidth: 2.1474836480e9, unit_family: BinBits }: 16.00Gib DisplayBandwidth { bandwidth: 4.2949672960e9, unit_family: BinBits }: 32.00Gib DisplayBandwidth { bandwidth: 8.5899345920e9, unit_family: BinBits }: 64.00Gib DisplayBandwidth { bandwidth: 1.7179869184e10, unit_family: BinBits }: 128.00Gib DisplayBandwidth { bandwidth: 3.4359738368e10, unit_family: BinBits }: 256.00Gib DisplayBandwidth { bandwidth: 6.8719476736e10, unit_family: BinBits }: 512.00Gib DisplayBandwidth { bandwidth: 1.3743895347e11, unit_family: BinBits }: 1.00Tib DisplayBandwidth { bandwidth: 2.7487790694e11, unit_family: BinBits }: 2.00Tib DisplayBandwidth { bandwidth: 5.4975581389e11, unit_family: BinBits }: 4.00Tib DisplayBandwidth { bandwidth: 1.0995116278e12, unit_family: BinBits }: 8.00Tib DisplayBandwidth { bandwidth: 2.1990232556e12, unit_family: BinBits }: 16.00Tib DisplayBandwidth { bandwidth: 4.3980465111e12, unit_family: BinBits }: 32.00Tib DisplayBandwidth { bandwidth: 8.7960930222e12, unit_family: BinBits }: 64.00Tib DisplayBandwidth { bandwidth: 1.7592186044e13, unit_family: BinBits }: 128.00Tib DisplayBandwidth { bandwidth: 3.5184372089e13, unit_family: BinBits }: 256.00Tib DisplayBandwidth { bandwidth: 7.0368744178e13, unit_family: BinBits }: 512.00Tib DisplayBandwidth { bandwidth: 1.4073748836e14, unit_family: BinBits }: 1.00Pib DisplayBandwidth { bandwidth: 2.8147497671e14, unit_family: BinBits }: 2.00Pib DisplayBandwidth { bandwidth: 5.6294995342e14, unit_family: BinBits }: 4.00Pib DisplayBandwidth { bandwidth: 1.1258999068e15, unit_family: BinBits }: 8.00Pib DisplayBandwidth { bandwidth: 2.2517998137e15, unit_family: BinBits }: 16.00Pib DisplayBandwidth { bandwidth: 4.5035996274e15, unit_family: BinBits }: 32.00Pib DisplayBandwidth { bandwidth: 9.0071992547e15, unit_family: BinBits }: 64.00Pib DisplayBandwidth { bandwidth: 1.8014398509e16, unit_family: BinBits }: 128.00Pib DisplayBandwidth { bandwidth: 3.6028797019e16, unit_family: BinBits }: 256.00Pib DisplayBandwidth { bandwidth: 7.2057594038e16, unit_family: BinBits }: 512.00Pib DisplayBandwidth { bandwidth: 1.4411518808e17, unit_family: BinBits }: 1024.00Pib DisplayBandwidth { bandwidth: 2.8823037615e17, unit_family: BinBits }: 2048.00Pib DisplayBandwidth { bandwidth: 5.7646075230e17, unit_family: BinBits }: 4096.00Pib DisplayBandwidth { bandwidth: 1.0240000000e-2, unit_family: BinBits }: 0.08b DisplayBandwidth { bandwidth: 2.5600000000e-2, unit_family: BinBits }: 0.20b DisplayBandwidth { bandwidth: 6.4000000000e-2, unit_family: BinBits }: 0.51b DisplayBandwidth { bandwidth: 1.6000000000e-1, unit_family: BinBits }: 1.28b DisplayBandwidth { bandwidth: 4.0000000000e-1, unit_family: BinBits }: 3.20b DisplayBandwidth { bandwidth: 1.0000000000e0, unit_family: BinBits }: 8.00b DisplayBandwidth { bandwidth: 2.5000000000e0, unit_family: BinBits }: 20.00b DisplayBandwidth { bandwidth: 6.2500000000e0, unit_family: BinBits }: 50.00b DisplayBandwidth { bandwidth: 1.5625000000e1, unit_family: BinBits }: 125.00b DisplayBandwidth { bandwidth: 3.9062500000e1, unit_family: BinBits }: 312.50b DisplayBandwidth { bandwidth: 9.7656250000e1, unit_family: BinBits }: 781.25b DisplayBandwidth { bandwidth: 2.4414062500e2, unit_family: BinBits }: 1.91Kib DisplayBandwidth { bandwidth: 6.1035156250e2, unit_family: BinBits }: 4.77Kib DisplayBandwidth { bandwidth: 1.5258789062e3, unit_family: BinBits }: 11.92Kib DisplayBandwidth { bandwidth: 3.8146972656e3, unit_family: BinBits }: 29.80Kib DisplayBandwidth { bandwidth: 9.5367431641e3, unit_family: BinBits }: 74.51Kib DisplayBandwidth { bandwidth: 2.3841857910e4, unit_family: BinBits }: 186.26Kib DisplayBandwidth { bandwidth: 5.9604644775e4, unit_family: BinBits }: 465.66Kib DisplayBandwidth { bandwidth: 1.4901161194e5, unit_family: BinBits }: 1.14Mib DisplayBandwidth { bandwidth: 3.7252902985e5, unit_family: BinBits }: 2.84Mib DisplayBandwidth { bandwidth: 9.3132257462e5, unit_family: BinBits }: 7.11Mib DisplayBandwidth { bandwidth: 2.3283064365e6, unit_family: BinBits }: 17.76Mib DisplayBandwidth { bandwidth: 5.8207660913e6, unit_family: BinBits }: 44.41Mib DisplayBandwidth { bandwidth: 1.4551915228e7, unit_family: BinBits }: 111.02Mib DisplayBandwidth { bandwidth: 3.6379788071e7, unit_family: BinBits }: 277.56Mib DisplayBandwidth { bandwidth: 9.0949470177e7, unit_family: BinBits }: 693.89Mib DisplayBandwidth { bandwidth: 2.2737367544e8, unit_family: BinBits }: 1.69Gib DisplayBandwidth { bandwidth: 5.6843418861e8, unit_family: BinBits }: 4.24Gib DisplayBandwidth { bandwidth: 1.4210854715e9, unit_family: BinBits }: 10.59Gib DisplayBandwidth { bandwidth: 3.5527136788e9, unit_family: BinBits }: 26.47Gib DisplayBandwidth { bandwidth: 8.8817841970e9, unit_family: BinBits }: 66.17Gib DisplayBandwidth { bandwidth: 2.2204460493e10, unit_family: BinBits }: 165.44Gib DisplayBandwidth { bandwidth: 5.5511151231e10, unit_family: BinBits }: 413.59Gib DisplayBandwidth { bandwidth: 1.3877787808e11, unit_family: BinBits }: 1.01Tib DisplayBandwidth { bandwidth: 3.4694469520e11, unit_family: BinBits }: 2.52Tib DisplayBandwidth { bandwidth: 8.6736173799e11, unit_family: BinBits }: 6.31Tib DisplayBandwidth { bandwidth: 2.1684043450e12, unit_family: BinBits }: 15.78Tib DisplayBandwidth { bandwidth: 5.4210108624e12, unit_family: BinBits }: 39.44Tib DisplayBandwidth { bandwidth: 1.3552527156e13, unit_family: BinBits }: 98.61Tib DisplayBandwidth { bandwidth: 3.3881317890e13, unit_family: BinBits }: 246.52Tib DisplayBandwidth { bandwidth: 8.4703294725e13, unit_family: BinBits }: 616.30Tib DisplayBandwidth { bandwidth: 2.1175823681e14, unit_family: BinBits }: 1.50Pib DisplayBandwidth { bandwidth: 5.2939559203e14, unit_family: BinBits }: 3.76Pib DisplayBandwidth { bandwidth: 1.3234889801e15, unit_family: BinBits }: 9.40Pib DisplayBandwidth { bandwidth: 3.3087224502e15, unit_family: BinBits }: 23.51Pib DisplayBandwidth { bandwidth: 8.2718061255e15, unit_family: BinBits }: 58.77Pib DisplayBandwidth { bandwidth: 2.0679515314e16, unit_family: BinBits }: 146.94Pib DisplayBandwidth { bandwidth: 5.1698788285e16, unit_family: BinBits }: 367.34Pib DisplayBandwidth { bandwidth: 1.2924697071e17, unit_family: BinBits }: 918.35Pib DisplayBandwidth { bandwidth: 3.2311742678e17, unit_family: BinBits }: 2295.89Pib DisplayBandwidth { bandwidth: 1.2345679012e-2, unit_family: BinBits }: 0.10b DisplayBandwidth { bandwidth: 3.7037037037e-2, unit_family: BinBits }: 0.30b DisplayBandwidth { bandwidth: 1.1111111111e-1, unit_family: BinBits }: 0.89b DisplayBandwidth { bandwidth: 3.3333333333e-1, unit_family: BinBits }: 2.67b DisplayBandwidth { bandwidth: 1.0000000000e0, unit_family: BinBits }: 8.00b DisplayBandwidth { bandwidth: 3.0000000000e0, unit_family: BinBits }: 24.00b DisplayBandwidth { bandwidth: 9.0000000000e0, unit_family: BinBits }: 72.00b DisplayBandwidth { bandwidth: 2.7000000000e1, unit_family: BinBits }: 216.00b DisplayBandwidth { bandwidth: 8.1000000000e1, unit_family: BinBits }: 648.00b DisplayBandwidth { bandwidth: 2.4300000000e2, unit_family: BinBits }: 1.90Kib DisplayBandwidth { bandwidth: 7.2900000000e2, unit_family: BinBits }: 5.70Kib DisplayBandwidth { bandwidth: 2.1870000000e3, unit_family: BinBits }: 17.09Kib DisplayBandwidth { bandwidth: 6.5610000000e3, unit_family: BinBits }: 51.26Kib DisplayBandwidth { bandwidth: 1.9683000000e4, unit_family: BinBits }: 153.77Kib DisplayBandwidth { bandwidth: 5.9049000000e4, unit_family: BinBits }: 461.32Kib DisplayBandwidth { bandwidth: 1.7714700000e5, unit_family: BinBits }: 1.35Mib DisplayBandwidth { bandwidth: 5.3144100000e5, unit_family: BinBits }: 4.05Mib DisplayBandwidth { bandwidth: 1.5943230000e6, unit_family: BinBits }: 12.16Mib DisplayBandwidth { bandwidth: 4.7829690000e6, unit_family: BinBits }: 36.49Mib DisplayBandwidth { bandwidth: 1.4348907000e7, unit_family: BinBits }: 109.47Mib DisplayBandwidth { bandwidth: 4.3046721000e7, unit_family: BinBits }: 328.42Mib DisplayBandwidth { bandwidth: 1.2914016300e8, unit_family: BinBits }: 0.96Gib DisplayBandwidth { bandwidth: 3.8742048900e8, unit_family: BinBits }: 2.89Gib DisplayBandwidth { bandwidth: 1.1622614670e9, unit_family: BinBits }: 8.66Gib DisplayBandwidth { bandwidth: 3.4867844010e9, unit_family: BinBits }: 25.98Gib DisplayBandwidth { bandwidth: 1.0460353203e10, unit_family: BinBits }: 77.94Gib DisplayBandwidth { bandwidth: 3.1381059609e10, unit_family: BinBits }: 233.81Gib DisplayBandwidth { bandwidth: 9.4143178827e10, unit_family: BinBits }: 701.42Gib DisplayBandwidth { bandwidth: 2.8242953648e11, unit_family: BinBits }: 2.05Tib DisplayBandwidth { bandwidth: 8.4728860944e11, unit_family: BinBits }: 6.16Tib DisplayBandwidth { bandwidth: 2.5418658283e12, unit_family: BinBits }: 18.49Tib DisplayBandwidth { bandwidth: 7.6255974850e12, unit_family: BinBits }: 55.48Tib DisplayBandwidth { bandwidth: 2.2876792455e13, unit_family: BinBits }: 166.45Tib DisplayBandwidth { bandwidth: 6.8630377365e13, unit_family: BinBits }: 499.35Tib DisplayBandwidth { bandwidth: 2.0589113209e14, unit_family: BinBits }: 1.46Pib DisplayBandwidth { bandwidth: 6.1767339628e14, unit_family: BinBits }: 4.39Pib DisplayBandwidth { bandwidth: 1.8530201889e15, unit_family: BinBits }: 13.17Pib DisplayBandwidth { bandwidth: 5.5590605666e15, unit_family: BinBits }: 39.50Pib DisplayBandwidth { bandwidth: 1.6677181700e16, unit_family: BinBits }: 118.50Pib DisplayBandwidth { bandwidth: 5.0031545099e16, unit_family: BinBits }: 355.50Pib DisplayBandwidth { bandwidth: 1.5009463530e17, unit_family: BinBits }: 1066.49Pib DisplayBandwidth { bandwidth: 4.5028390589e17, unit_family: BinBits }: 3199.46Pib DisplayBandwidth { bandwidth: 8.0000000000e-3, unit_family: BinBits }: 0.06b DisplayBandwidth { bandwidth: 4.0000000000e-2, unit_family: BinBits }: 0.32b DisplayBandwidth { bandwidth: 2.0000000000e-1, unit_family: BinBits }: 1.60b DisplayBandwidth { bandwidth: 1.0000000000e0, unit_family: BinBits }: 8.00b DisplayBandwidth { bandwidth: 5.0000000000e0, unit_family: BinBits }: 40.00b DisplayBandwidth { bandwidth: 2.5000000000e1, unit_family: BinBits }: 200.00b DisplayBandwidth { bandwidth: 1.2500000000e2, unit_family: BinBits }: 0.98Kib DisplayBandwidth { bandwidth: 6.2500000000e2, unit_family: BinBits }: 4.88Kib DisplayBandwidth { bandwidth: 3.1250000000e3, unit_family: BinBits }: 24.41Kib DisplayBandwidth { bandwidth: 1.5625000000e4, unit_family: BinBits }: 122.07Kib DisplayBandwidth { bandwidth: 7.8125000000e4, unit_family: BinBits }: 610.35Kib DisplayBandwidth { bandwidth: 3.9062500000e5, unit_family: BinBits }: 2.98Mib DisplayBandwidth { bandwidth: 1.9531250000e6, unit_family: BinBits }: 14.90Mib DisplayBandwidth { bandwidth: 9.7656250000e6, unit_family: BinBits }: 74.51Mib DisplayBandwidth { bandwidth: 4.8828125000e7, unit_family: BinBits }: 372.53Mib DisplayBandwidth { bandwidth: 2.4414062500e8, unit_family: BinBits }: 1.82Gib DisplayBandwidth { bandwidth: 1.2207031250e9, unit_family: BinBits }: 9.09Gib DisplayBandwidth { bandwidth: 6.1035156250e9, unit_family: BinBits }: 45.47Gib DisplayBandwidth { bandwidth: 3.0517578125e10, unit_family: BinBits }: 227.37Gib DisplayBandwidth { bandwidth: 1.5258789062e11, unit_family: BinBits }: 1.11Tib DisplayBandwidth { bandwidth: 7.6293945312e11, unit_family: BinBits }: 5.55Tib DisplayBandwidth { bandwidth: 3.8146972656e12, unit_family: BinBits }: 27.76Tib DisplayBandwidth { bandwidth: 1.9073486328e13, unit_family: BinBits }: 138.78Tib DisplayBandwidth { bandwidth: 9.5367431641e13, unit_family: BinBits }: 693.89Tib DisplayBandwidth { bandwidth: 4.7683715820e14, unit_family: BinBits }: 3.39Pib DisplayBandwidth { bandwidth: 2.3841857910e15, unit_family: BinBits }: 16.94Pib DisplayBandwidth { bandwidth: 1.1920928955e16, unit_family: BinBits }: 84.70Pib DisplayBandwidth { bandwidth: 5.9604644775e16, unit_family: BinBits }: 423.52Pib DisplayBandwidth { bandwidth: 2.9802322388e17, unit_family: BinBits }: 2117.58Pib DisplayBandwidth { bandwidth: 1.5625000000e-2, unit_family: SiBytes }: 0.02B DisplayBandwidth { bandwidth: 3.1250000000e-2, unit_family: SiBytes }: 0.03B DisplayBandwidth { bandwidth: 6.2500000000e-2, unit_family: SiBytes }: 0.06B DisplayBandwidth { bandwidth: 1.2500000000e-1, unit_family: SiBytes }: 0.12B DisplayBandwidth { bandwidth: 2.5000000000e-1, unit_family: SiBytes }: 0.25B DisplayBandwidth { bandwidth: 5.0000000000e-1, unit_family: SiBytes }: 0.50B DisplayBandwidth { bandwidth: 1.0000000000e0, unit_family: SiBytes }: 1.00B DisplayBandwidth { bandwidth: 2.0000000000e0, unit_family: SiBytes }: 2.00B DisplayBandwidth { bandwidth: 4.0000000000e0, unit_family: SiBytes }: 4.00B DisplayBandwidth { bandwidth: 8.0000000000e0, unit_family: SiBytes }: 8.00B DisplayBandwidth { bandwidth: 1.6000000000e1, unit_family: SiBytes }: 16.00B DisplayBandwidth { bandwidth: 3.2000000000e1, unit_family: SiBytes }: 32.00B DisplayBandwidth { bandwidth: 6.4000000000e1, unit_family: SiBytes }: 64.00B DisplayBandwidth { bandwidth: 1.2800000000e2, unit_family: SiBytes }: 128.00B DisplayBandwidth { bandwidth: 2.5600000000e2, unit_family: SiBytes }: 256.00B DisplayBandwidth { bandwidth: 5.1200000000e2, unit_family: SiBytes }: 512.00B DisplayBandwidth { bandwidth: 1.0240000000e3, unit_family: SiBytes }: 1.02kB DisplayBandwidth { bandwidth: 2.0480000000e3, unit_family: SiBytes }: 2.05kB DisplayBandwidth { bandwidth: 4.0960000000e3, unit_family: SiBytes }: 4.10kB DisplayBandwidth { bandwidth: 8.1920000000e3, unit_family: SiBytes }: 8.19kB DisplayBandwidth { bandwidth: 1.6384000000e4, unit_family: SiBytes }: 16.38kB DisplayBandwidth { bandwidth: 3.2768000000e4, unit_family: SiBytes }: 32.77kB DisplayBandwidth { bandwidth: 6.5536000000e4, unit_family: SiBytes }: 65.54kB DisplayBandwidth { bandwidth: 1.3107200000e5, unit_family: SiBytes }: 131.07kB DisplayBandwidth { bandwidth: 2.6214400000e5, unit_family: SiBytes }: 262.14kB DisplayBandwidth { bandwidth: 5.2428800000e5, unit_family: SiBytes }: 524.29kB DisplayBandwidth { bandwidth: 1.0485760000e6, unit_family: SiBytes }: 1.05MB DisplayBandwidth { bandwidth: 2.0971520000e6, unit_family: SiBytes }: 2.10MB DisplayBandwidth { bandwidth: 4.1943040000e6, unit_family: SiBytes }: 4.19MB DisplayBandwidth { bandwidth: 8.3886080000e6, unit_family: SiBytes }: 8.39MB DisplayBandwidth { bandwidth: 1.6777216000e7, unit_family: SiBytes }: 16.78MB DisplayBandwidth { bandwidth: 3.3554432000e7, unit_family: SiBytes }: 33.55MB DisplayBandwidth { bandwidth: 6.7108864000e7, unit_family: SiBytes }: 67.11MB DisplayBandwidth { bandwidth: 1.3421772800e8, unit_family: SiBytes }: 134.22MB DisplayBandwidth { bandwidth: 2.6843545600e8, unit_family: SiBytes }: 268.44MB DisplayBandwidth { bandwidth: 5.3687091200e8, unit_family: SiBytes }: 536.87MB DisplayBandwidth { bandwidth: 1.0737418240e9, unit_family: SiBytes }: 1.07GB DisplayBandwidth { bandwidth: 2.1474836480e9, unit_family: SiBytes }: 2.15GB DisplayBandwidth { bandwidth: 4.2949672960e9, unit_family: SiBytes }: 4.29GB DisplayBandwidth { bandwidth: 8.5899345920e9, unit_family: SiBytes }: 8.59GB DisplayBandwidth { bandwidth: 1.7179869184e10, unit_family: SiBytes }: 17.18GB DisplayBandwidth { bandwidth: 3.4359738368e10, unit_family: SiBytes }: 34.36GB DisplayBandwidth { bandwidth: 6.8719476736e10, unit_family: SiBytes }: 68.72GB DisplayBandwidth { bandwidth: 1.3743895347e11, unit_family: SiBytes }: 137.44GB DisplayBandwidth { bandwidth: 2.7487790694e11, unit_family: SiBytes }: 274.88GB DisplayBandwidth { bandwidth: 5.4975581389e11, unit_family: SiBytes }: 549.76GB DisplayBandwidth { bandwidth: 1.0995116278e12, unit_family: SiBytes }: 1.10TB DisplayBandwidth { bandwidth: 2.1990232556e12, unit_family: SiBytes }: 2.20TB DisplayBandwidth { bandwidth: 4.3980465111e12, unit_family: SiBytes }: 4.40TB DisplayBandwidth { bandwidth: 8.7960930222e12, unit_family: SiBytes }: 8.80TB DisplayBandwidth { bandwidth: 1.7592186044e13, unit_family: SiBytes }: 17.59TB DisplayBandwidth { bandwidth: 3.5184372089e13, unit_family: SiBytes }: 35.18TB DisplayBandwidth { bandwidth: 7.0368744178e13, unit_family: SiBytes }: 70.37TB DisplayBandwidth { bandwidth: 1.4073748836e14, unit_family: SiBytes }: 140.74TB DisplayBandwidth { bandwidth: 2.8147497671e14, unit_family: SiBytes }: 281.47TB DisplayBandwidth { bandwidth: 5.6294995342e14, unit_family: SiBytes }: 562.95TB DisplayBandwidth { bandwidth: 1.1258999068e15, unit_family: SiBytes }: 1.13PB DisplayBandwidth { bandwidth: 2.2517998137e15, unit_family: SiBytes }: 2.25PB DisplayBandwidth { bandwidth: 4.5035996274e15, unit_family: SiBytes }: 4.50PB DisplayBandwidth { bandwidth: 9.0071992547e15, unit_family: SiBytes }: 9.01PB DisplayBandwidth { bandwidth: 1.8014398509e16, unit_family: SiBytes }: 18.01PB DisplayBandwidth { bandwidth: 3.6028797019e16, unit_family: SiBytes }: 36.03PB DisplayBandwidth { bandwidth: 7.2057594038e16, unit_family: SiBytes }: 72.06PB DisplayBandwidth { bandwidth: 1.4411518808e17, unit_family: SiBytes }: 144.12PB DisplayBandwidth { bandwidth: 2.8823037615e17, unit_family: SiBytes }: 288.23PB DisplayBandwidth { bandwidth: 5.7646075230e17, unit_family: SiBytes }: 576.46PB DisplayBandwidth { bandwidth: 1.0240000000e-2, unit_family: SiBytes }: 0.01B DisplayBandwidth { bandwidth: 2.5600000000e-2, unit_family: SiBytes }: 0.03B DisplayBandwidth { bandwidth: 6.4000000000e-2, unit_family: SiBytes }: 0.06B DisplayBandwidth { bandwidth: 1.6000000000e-1, unit_family: SiBytes }: 0.16B DisplayBandwidth { bandwidth: 4.0000000000e-1, unit_family: SiBytes }: 0.40B DisplayBandwidth { bandwidth: 1.0000000000e0, unit_family: SiBytes }: 1.00B DisplayBandwidth { bandwidth: 2.5000000000e0, unit_family: SiBytes }: 2.50B DisplayBandwidth { bandwidth: 6.2500000000e0, unit_family: SiBytes }: 6.25B DisplayBandwidth { bandwidth: 1.5625000000e1, unit_family: SiBytes }: 15.62B DisplayBandwidth { bandwidth: 3.9062500000e1, unit_family: SiBytes }: 39.06B DisplayBandwidth { bandwidth: 9.7656250000e1, unit_family: SiBytes }: 97.66B DisplayBandwidth { bandwidth: 2.4414062500e2, unit_family: SiBytes }: 244.14B DisplayBandwidth { bandwidth: 6.1035156250e2, unit_family: SiBytes }: 610.35B DisplayBandwidth { bandwidth: 1.5258789062e3, unit_family: SiBytes }: 1.53kB DisplayBandwidth { bandwidth: 3.8146972656e3, unit_family: SiBytes }: 3.81kB DisplayBandwidth { bandwidth: 9.5367431641e3, unit_family: SiBytes }: 9.54kB DisplayBandwidth { bandwidth: 2.3841857910e4, unit_family: SiBytes }: 23.84kB DisplayBandwidth { bandwidth: 5.9604644775e4, unit_family: SiBytes }: 59.60kB DisplayBandwidth { bandwidth: 1.4901161194e5, unit_family: SiBytes }: 149.01kB DisplayBandwidth { bandwidth: 3.7252902985e5, unit_family: SiBytes }: 372.53kB DisplayBandwidth { bandwidth: 9.3132257462e5, unit_family: SiBytes }: 931.32kB DisplayBandwidth { bandwidth: 2.3283064365e6, unit_family: SiBytes }: 2.33MB DisplayBandwidth { bandwidth: 5.8207660913e6, unit_family: SiBytes }: 5.82MB DisplayBandwidth { bandwidth: 1.4551915228e7, unit_family: SiBytes }: 14.55MB DisplayBandwidth { bandwidth: 3.6379788071e7, unit_family: SiBytes }: 36.38MB DisplayBandwidth { bandwidth: 9.0949470177e7, unit_family: SiBytes }: 90.95MB DisplayBandwidth { bandwidth: 2.2737367544e8, unit_family: SiBytes }: 227.37MB DisplayBandwidth { bandwidth: 5.6843418861e8, unit_family: SiBytes }: 568.43MB DisplayBandwidth { bandwidth: 1.4210854715e9, unit_family: SiBytes }: 1.42GB DisplayBandwidth { bandwidth: 3.5527136788e9, unit_family: SiBytes }: 3.55GB DisplayBandwidth { bandwidth: 8.8817841970e9, unit_family: SiBytes }: 8.88GB DisplayBandwidth { bandwidth: 2.2204460493e10, unit_family: SiBytes }: 22.20GB DisplayBandwidth { bandwidth: 5.5511151231e10, unit_family: SiBytes }: 55.51GB DisplayBandwidth { bandwidth: 1.3877787808e11, unit_family: SiBytes }: 138.78GB DisplayBandwidth { bandwidth: 3.4694469520e11, unit_family: SiBytes }: 346.94GB DisplayBandwidth { bandwidth: 8.6736173799e11, unit_family: SiBytes }: 867.36GB DisplayBandwidth { bandwidth: 2.1684043450e12, unit_family: SiBytes }: 2.17TB DisplayBandwidth { bandwidth: 5.4210108624e12, unit_family: SiBytes }: 5.42TB DisplayBandwidth { bandwidth: 1.3552527156e13, unit_family: SiBytes }: 13.55TB DisplayBandwidth { bandwidth: 3.3881317890e13, unit_family: SiBytes }: 33.88TB DisplayBandwidth { bandwidth: 8.4703294725e13, unit_family: SiBytes }: 84.70TB DisplayBandwidth { bandwidth: 2.1175823681e14, unit_family: SiBytes }: 211.76TB DisplayBandwidth { bandwidth: 5.2939559203e14, unit_family: SiBytes }: 529.40TB DisplayBandwidth { bandwidth: 1.3234889801e15, unit_family: SiBytes }: 1.32PB DisplayBandwidth { bandwidth: 3.3087224502e15, unit_family: SiBytes }: 3.31PB DisplayBandwidth { bandwidth: 8.2718061255e15, unit_family: SiBytes }: 8.27PB DisplayBandwidth { bandwidth: 2.0679515314e16, unit_family: SiBytes }: 20.68PB DisplayBandwidth { bandwidth: 5.1698788285e16, unit_family: SiBytes }: 51.70PB DisplayBandwidth { bandwidth: 1.2924697071e17, unit_family: SiBytes }: 129.25PB DisplayBandwidth { bandwidth: 3.2311742678e17, unit_family: SiBytes }: 323.12PB DisplayBandwidth { bandwidth: 1.2345679012e-2, unit_family: SiBytes }: 0.01B DisplayBandwidth { bandwidth: 3.7037037037e-2, unit_family: SiBytes }: 0.04B DisplayBandwidth { bandwidth: 1.1111111111e-1, unit_family: SiBytes }: 0.11B DisplayBandwidth { bandwidth: 3.3333333333e-1, unit_family: SiBytes }: 0.33B DisplayBandwidth { bandwidth: 1.0000000000e0, unit_family: SiBytes }: 1.00B DisplayBandwidth { bandwidth: 3.0000000000e0, unit_family: SiBytes }: 3.00B DisplayBandwidth { bandwidth: 9.0000000000e0, unit_family: SiBytes }: 9.00B DisplayBandwidth { bandwidth: 2.7000000000e1, unit_family: SiBytes }: 27.00B DisplayBandwidth { bandwidth: 8.1000000000e1, unit_family: SiBytes }: 81.00B DisplayBandwidth { bandwidth: 2.4300000000e2, unit_family: SiBytes }: 243.00B DisplayBandwidth { bandwidth: 7.2900000000e2, unit_family: SiBytes }: 729.00B DisplayBandwidth { bandwidth: 2.1870000000e3, unit_family: SiBytes }: 2.19kB DisplayBandwidth { bandwidth: 6.5610000000e3, unit_family: SiBytes }: 6.56kB DisplayBandwidth { bandwidth: 1.9683000000e4, unit_family: SiBytes }: 19.68kB DisplayBandwidth { bandwidth: 5.9049000000e4, unit_family: SiBytes }: 59.05kB DisplayBandwidth { bandwidth: 1.7714700000e5, unit_family: SiBytes }: 177.15kB DisplayBandwidth { bandwidth: 5.3144100000e5, unit_family: SiBytes }: 531.44kB DisplayBandwidth { bandwidth: 1.5943230000e6, unit_family: SiBytes }: 1.59MB DisplayBandwidth { bandwidth: 4.7829690000e6, unit_family: SiBytes }: 4.78MB DisplayBandwidth { bandwidth: 1.4348907000e7, unit_family: SiBytes }: 14.35MB DisplayBandwidth { bandwidth: 4.3046721000e7, unit_family: SiBytes }: 43.05MB DisplayBandwidth { bandwidth: 1.2914016300e8, unit_family: SiBytes }: 129.14MB DisplayBandwidth { bandwidth: 3.8742048900e8, unit_family: SiBytes }: 387.42MB DisplayBandwidth { bandwidth: 1.1622614670e9, unit_family: SiBytes }: 1.16GB DisplayBandwidth { bandwidth: 3.4867844010e9, unit_family: SiBytes }: 3.49GB DisplayBandwidth { bandwidth: 1.0460353203e10, unit_family: SiBytes }: 10.46GB DisplayBandwidth { bandwidth: 3.1381059609e10, unit_family: SiBytes }: 31.38GB DisplayBandwidth { bandwidth: 9.4143178827e10, unit_family: SiBytes }: 94.14GB DisplayBandwidth { bandwidth: 2.8242953648e11, unit_family: SiBytes }: 282.43GB DisplayBandwidth { bandwidth: 8.4728860944e11, unit_family: SiBytes }: 847.29GB DisplayBandwidth { bandwidth: 2.5418658283e12, unit_family: SiBytes }: 2.54TB DisplayBandwidth { bandwidth: 7.6255974850e12, unit_family: SiBytes }: 7.63TB DisplayBandwidth { bandwidth: 2.2876792455e13, unit_family: SiBytes }: 22.88TB DisplayBandwidth { bandwidth: 6.8630377365e13, unit_family: SiBytes }: 68.63TB DisplayBandwidth { bandwidth: 2.0589113209e14, unit_family: SiBytes }: 205.89TB DisplayBandwidth { bandwidth: 6.1767339628e14, unit_family: SiBytes }: 617.67TB DisplayBandwidth { bandwidth: 1.8530201889e15, unit_family: SiBytes }: 1.85PB DisplayBandwidth { bandwidth: 5.5590605666e15, unit_family: SiBytes }: 5.56PB DisplayBandwidth { bandwidth: 1.6677181700e16, unit_family: SiBytes }: 16.68PB DisplayBandwidth { bandwidth: 5.0031545099e16, unit_family: SiBytes }: 50.03PB DisplayBandwidth { bandwidth: 1.5009463530e17, unit_family: SiBytes }: 150.09PB DisplayBandwidth { bandwidth: 4.5028390589e17, unit_family: SiBytes }: 450.28PB DisplayBandwidth { bandwidth: 8.0000000000e-3, unit_family: SiBytes }: 0.01B DisplayBandwidth { bandwidth: 4.0000000000e-2, unit_family: SiBytes }: 0.04B DisplayBandwidth { bandwidth: 2.0000000000e-1, unit_family: SiBytes }: 0.20B DisplayBandwidth { bandwidth: 1.0000000000e0, unit_family: SiBytes }: 1.00B DisplayBandwidth { bandwidth: 5.0000000000e0, unit_family: SiBytes }: 5.00B DisplayBandwidth { bandwidth: 2.5000000000e1, unit_family: SiBytes }: 25.00B DisplayBandwidth { bandwidth: 1.2500000000e2, unit_family: SiBytes }: 125.00B DisplayBandwidth { bandwidth: 6.2500000000e2, unit_family: SiBytes }: 625.00B DisplayBandwidth { bandwidth: 3.1250000000e3, unit_family: SiBytes }: 3.12kB DisplayBandwidth { bandwidth: 1.5625000000e4, unit_family: SiBytes }: 15.62kB DisplayBandwidth { bandwidth: 7.8125000000e4, unit_family: SiBytes }: 78.12kB DisplayBandwidth { bandwidth: 3.9062500000e5, unit_family: SiBytes }: 390.62kB DisplayBandwidth { bandwidth: 1.9531250000e6, unit_family: SiBytes }: 1.95MB DisplayBandwidth { bandwidth: 9.7656250000e6, unit_family: SiBytes }: 9.77MB DisplayBandwidth { bandwidth: 4.8828125000e7, unit_family: SiBytes }: 48.83MB DisplayBandwidth { bandwidth: 2.4414062500e8, unit_family: SiBytes }: 244.14MB DisplayBandwidth { bandwidth: 1.2207031250e9, unit_family: SiBytes }: 1.22GB DisplayBandwidth { bandwidth: 6.1035156250e9, unit_family: SiBytes }: 6.10GB DisplayBandwidth { bandwidth: 3.0517578125e10, unit_family: SiBytes }: 30.52GB DisplayBandwidth { bandwidth: 1.5258789062e11, unit_family: SiBytes }: 152.59GB DisplayBandwidth { bandwidth: 7.6293945312e11, unit_family: SiBytes }: 762.94GB DisplayBandwidth { bandwidth: 3.8146972656e12, unit_family: SiBytes }: 3.81TB DisplayBandwidth { bandwidth: 1.9073486328e13, unit_family: SiBytes }: 19.07TB DisplayBandwidth { bandwidth: 9.5367431641e13, unit_family: SiBytes }: 95.37TB DisplayBandwidth { bandwidth: 4.7683715820e14, unit_family: SiBytes }: 476.84TB DisplayBandwidth { bandwidth: 2.3841857910e15, unit_family: SiBytes }: 2.38PB DisplayBandwidth { bandwidth: 1.1920928955e16, unit_family: SiBytes }: 11.92PB DisplayBandwidth { bandwidth: 5.9604644775e16, unit_family: SiBytes }: 59.60PB DisplayBandwidth { bandwidth: 2.9802322388e17, unit_family: SiBytes }: 298.02PB DisplayBandwidth { bandwidth: 1.5625000000e-2, unit_family: SiBits }: 0.12b DisplayBandwidth { bandwidth: 3.1250000000e-2, unit_family: SiBits }: 0.25b DisplayBandwidth { bandwidth: 6.2500000000e-2, unit_family: SiBits }: 0.50b DisplayBandwidth { bandwidth: 1.2500000000e-1, unit_family: SiBits }: 1.00b DisplayBandwidth { bandwidth: 2.5000000000e-1, unit_family: SiBits }: 2.00b DisplayBandwidth { bandwidth: 5.0000000000e-1, unit_family: SiBits }: 4.00b DisplayBandwidth { bandwidth: 1.0000000000e0, unit_family: SiBits }: 8.00b DisplayBandwidth { bandwidth: 2.0000000000e0, unit_family: SiBits }: 16.00b DisplayBandwidth { bandwidth: 4.0000000000e0, unit_family: SiBits }: 32.00b DisplayBandwidth { bandwidth: 8.0000000000e0, unit_family: SiBits }: 64.00b DisplayBandwidth { bandwidth: 1.6000000000e1, unit_family: SiBits }: 128.00b DisplayBandwidth { bandwidth: 3.2000000000e1, unit_family: SiBits }: 256.00b DisplayBandwidth { bandwidth: 6.4000000000e1, unit_family: SiBits }: 512.00b DisplayBandwidth { bandwidth: 1.2800000000e2, unit_family: SiBits }: 1.02kb DisplayBandwidth { bandwidth: 2.5600000000e2, unit_family: SiBits }: 2.05kb DisplayBandwidth { bandwidth: 5.1200000000e2, unit_family: SiBits }: 4.10kb DisplayBandwidth { bandwidth: 1.0240000000e3, unit_family: SiBits }: 8.19kb DisplayBandwidth { bandwidth: 2.0480000000e3, unit_family: SiBits }: 16.38kb DisplayBandwidth { bandwidth: 4.0960000000e3, unit_family: SiBits }: 32.77kb DisplayBandwidth { bandwidth: 8.1920000000e3, unit_family: SiBits }: 65.54kb DisplayBandwidth { bandwidth: 1.6384000000e4, unit_family: SiBits }: 131.07kb DisplayBandwidth { bandwidth: 3.2768000000e4, unit_family: SiBits }: 262.14kb DisplayBandwidth { bandwidth: 6.5536000000e4, unit_family: SiBits }: 524.29kb DisplayBandwidth { bandwidth: 1.3107200000e5, unit_family: SiBits }: 1.05Mb DisplayBandwidth { bandwidth: 2.6214400000e5, unit_family: SiBits }: 2.10Mb DisplayBandwidth { bandwidth: 5.2428800000e5, unit_family: SiBits }: 4.19Mb DisplayBandwidth { bandwidth: 1.0485760000e6, unit_family: SiBits }: 8.39Mb DisplayBandwidth { bandwidth: 2.0971520000e6, unit_family: SiBits }: 16.78Mb DisplayBandwidth { bandwidth: 4.1943040000e6, unit_family: SiBits }: 33.55Mb DisplayBandwidth { bandwidth: 8.3886080000e6, unit_family: SiBits }: 67.11Mb DisplayBandwidth { bandwidth: 1.6777216000e7, unit_family: SiBits }: 134.22Mb DisplayBandwidth { bandwidth: 3.3554432000e7, unit_family: SiBits }: 268.44Mb DisplayBandwidth { bandwidth: 6.7108864000e7, unit_family: SiBits }: 536.87Mb DisplayBandwidth { bandwidth: 1.3421772800e8, unit_family: SiBits }: 1.07Gb DisplayBandwidth { bandwidth: 2.6843545600e8, unit_family: SiBits }: 2.15Gb DisplayBandwidth { bandwidth: 5.3687091200e8, unit_family: SiBits }: 4.29Gb DisplayBandwidth { bandwidth: 1.0737418240e9, unit_family: SiBits }: 8.59Gb DisplayBandwidth { bandwidth: 2.1474836480e9, unit_family: SiBits }: 17.18Gb DisplayBandwidth { bandwidth: 4.2949672960e9, unit_family: SiBits }: 34.36Gb DisplayBandwidth { bandwidth: 8.5899345920e9, unit_family: SiBits }: 68.72Gb DisplayBandwidth { bandwidth: 1.7179869184e10, unit_family: SiBits }: 137.44Gb DisplayBandwidth { bandwidth: 3.4359738368e10, unit_family: SiBits }: 274.88Gb DisplayBandwidth { bandwidth: 6.8719476736e10, unit_family: SiBits }: 549.76Gb DisplayBandwidth { bandwidth: 1.3743895347e11, unit_family: SiBits }: 1.10Tb DisplayBandwidth { bandwidth: 2.7487790694e11, unit_family: SiBits }: 2.20Tb DisplayBandwidth { bandwidth: 5.4975581389e11, unit_family: SiBits }: 4.40Tb DisplayBandwidth { bandwidth: 1.0995116278e12, unit_family: SiBits }: 8.80Tb DisplayBandwidth { bandwidth: 2.1990232556e12, unit_family: SiBits }: 17.59Tb DisplayBandwidth { bandwidth: 4.3980465111e12, unit_family: SiBits }: 35.18Tb DisplayBandwidth { bandwidth: 8.7960930222e12, unit_family: SiBits }: 70.37Tb DisplayBandwidth { bandwidth: 1.7592186044e13, unit_family: SiBits }: 140.74Tb DisplayBandwidth { bandwidth: 3.5184372089e13, unit_family: SiBits }: 281.47Tb DisplayBandwidth { bandwidth: 7.0368744178e13, unit_family: SiBits }: 562.95Tb DisplayBandwidth { bandwidth: 1.4073748836e14, unit_family: SiBits }: 1.13Pb DisplayBandwidth { bandwidth: 2.8147497671e14, unit_family: SiBits }: 2.25Pb DisplayBandwidth { bandwidth: 5.6294995342e14, unit_family: SiBits }: 4.50Pb DisplayBandwidth { bandwidth: 1.1258999068e15, unit_family: SiBits }: 9.01Pb DisplayBandwidth { bandwidth: 2.2517998137e15, unit_family: SiBits }: 18.01Pb DisplayBandwidth { bandwidth: 4.5035996274e15, unit_family: SiBits }: 36.03Pb DisplayBandwidth { bandwidth: 9.0071992547e15, unit_family: SiBits }: 72.06Pb DisplayBandwidth { bandwidth: 1.8014398509e16, unit_family: SiBits }: 144.12Pb DisplayBandwidth { bandwidth: 3.6028797019e16, unit_family: SiBits }: 288.23Pb DisplayBandwidth { bandwidth: 7.2057594038e16, unit_family: SiBits }: 576.46Pb DisplayBandwidth { bandwidth: 1.4411518808e17, unit_family: SiBits }: 1152.92Pb DisplayBandwidth { bandwidth: 2.8823037615e17, unit_family: SiBits }: 2305.84Pb DisplayBandwidth { bandwidth: 5.7646075230e17, unit_family: SiBits }: 4611.69Pb DisplayBandwidth { bandwidth: 1.0240000000e-2, unit_family: SiBits }: 0.08b DisplayBandwidth { bandwidth: 2.5600000000e-2, unit_family: SiBits }: 0.20b DisplayBandwidth { bandwidth: 6.4000000000e-2, unit_family: SiBits }: 0.51b DisplayBandwidth { bandwidth: 1.6000000000e-1, unit_family: SiBits }: 1.28b DisplayBandwidth { bandwidth: 4.0000000000e-1, unit_family: SiBits }: 3.20b DisplayBandwidth { bandwidth: 1.0000000000e0, unit_family: SiBits }: 8.00b DisplayBandwidth { bandwidth: 2.5000000000e0, unit_family: SiBits }: 20.00b DisplayBandwidth { bandwidth: 6.2500000000e0, unit_family: SiBits }: 50.00b DisplayBandwidth { bandwidth: 1.5625000000e1, unit_family: SiBits }: 125.00b DisplayBandwidth { bandwidth: 3.9062500000e1, unit_family: SiBits }: 312.50b DisplayBandwidth { bandwidth: 9.7656250000e1, unit_family: SiBits }: 781.25b DisplayBandwidth { bandwidth: 2.4414062500e2, unit_family: SiBits }: 1.95kb DisplayBandwidth { bandwidth: 6.1035156250e2, unit_family: SiBits }: 4.88kb DisplayBandwidth { bandwidth: 1.5258789062e3, unit_family: SiBits }: 12.21kb DisplayBandwidth { bandwidth: 3.8146972656e3, unit_family: SiBits }: 30.52kb DisplayBandwidth { bandwidth: 9.5367431641e3, unit_family: SiBits }: 76.29kb DisplayBandwidth { bandwidth: 2.3841857910e4, unit_family: SiBits }: 190.73kb DisplayBandwidth { bandwidth: 5.9604644775e4, unit_family: SiBits }: 476.84kb DisplayBandwidth { bandwidth: 1.4901161194e5, unit_family: SiBits }: 1.19Mb DisplayBandwidth { bandwidth: 3.7252902985e5, unit_family: SiBits }: 2.98Mb DisplayBandwidth { bandwidth: 9.3132257462e5, unit_family: SiBits }: 7.45Mb DisplayBandwidth { bandwidth: 2.3283064365e6, unit_family: SiBits }: 18.63Mb DisplayBandwidth { bandwidth: 5.8207660913e6, unit_family: SiBits }: 46.57Mb DisplayBandwidth { bandwidth: 1.4551915228e7, unit_family: SiBits }: 116.42Mb DisplayBandwidth { bandwidth: 3.6379788071e7, unit_family: SiBits }: 291.04Mb DisplayBandwidth { bandwidth: 9.0949470177e7, unit_family: SiBits }: 727.60Mb DisplayBandwidth { bandwidth: 2.2737367544e8, unit_family: SiBits }: 1.82Gb DisplayBandwidth { bandwidth: 5.6843418861e8, unit_family: SiBits }: 4.55Gb DisplayBandwidth { bandwidth: 1.4210854715e9, unit_family: SiBits }: 11.37Gb DisplayBandwidth { bandwidth: 3.5527136788e9, unit_family: SiBits }: 28.42Gb DisplayBandwidth { bandwidth: 8.8817841970e9, unit_family: SiBits }: 71.05Gb DisplayBandwidth { bandwidth: 2.2204460493e10, unit_family: SiBits }: 177.64Gb DisplayBandwidth { bandwidth: 5.5511151231e10, unit_family: SiBits }: 444.09Gb DisplayBandwidth { bandwidth: 1.3877787808e11, unit_family: SiBits }: 1.11Tb DisplayBandwidth { bandwidth: 3.4694469520e11, unit_family: SiBits }: 2.78Tb DisplayBandwidth { bandwidth: 8.6736173799e11, unit_family: SiBits }: 6.94Tb DisplayBandwidth { bandwidth: 2.1684043450e12, unit_family: SiBits }: 17.35Tb DisplayBandwidth { bandwidth: 5.4210108624e12, unit_family: SiBits }: 43.37Tb DisplayBandwidth { bandwidth: 1.3552527156e13, unit_family: SiBits }: 108.42Tb DisplayBandwidth { bandwidth: 3.3881317890e13, unit_family: SiBits }: 271.05Tb DisplayBandwidth { bandwidth: 8.4703294725e13, unit_family: SiBits }: 677.63Tb DisplayBandwidth { bandwidth: 2.1175823681e14, unit_family: SiBits }: 1.69Pb DisplayBandwidth { bandwidth: 5.2939559203e14, unit_family: SiBits }: 4.24Pb DisplayBandwidth { bandwidth: 1.3234889801e15, unit_family: SiBits }: 10.59Pb DisplayBandwidth { bandwidth: 3.3087224502e15, unit_family: SiBits }: 26.47Pb DisplayBandwidth { bandwidth: 8.2718061255e15, unit_family: SiBits }: 66.17Pb DisplayBandwidth { bandwidth: 2.0679515314e16, unit_family: SiBits }: 165.44Pb DisplayBandwidth { bandwidth: 5.1698788285e16, unit_family: SiBits }: 413.59Pb DisplayBandwidth { bandwidth: 1.2924697071e17, unit_family: SiBits }: 1033.98Pb DisplayBandwidth { bandwidth: 3.2311742678e17, unit_family: SiBits }: 2584.94Pb DisplayBandwidth { bandwidth: 1.2345679012e-2, unit_family: SiBits }: 0.10b DisplayBandwidth { bandwidth: 3.7037037037e-2, unit_family: SiBits }: 0.30b DisplayBandwidth { bandwidth: 1.1111111111e-1, unit_family: SiBits }: 0.89b DisplayBandwidth { bandwidth: 3.3333333333e-1, unit_family: SiBits }: 2.67b DisplayBandwidth { bandwidth: 1.0000000000e0, unit_family: SiBits }: 8.00b DisplayBandwidth { bandwidth: 3.0000000000e0, unit_family: SiBits }: 24.00b DisplayBandwidth { bandwidth: 9.0000000000e0, unit_family: SiBits }: 72.00b DisplayBandwidth { bandwidth: 2.7000000000e1, unit_family: SiBits }: 216.00b DisplayBandwidth { bandwidth: 8.1000000000e1, unit_family: SiBits }: 648.00b DisplayBandwidth { bandwidth: 2.4300000000e2, unit_family: SiBits }: 1.94kb DisplayBandwidth { bandwidth: 7.2900000000e2, unit_family: SiBits }: 5.83kb DisplayBandwidth { bandwidth: 2.1870000000e3, unit_family: SiBits }: 17.50kb DisplayBandwidth { bandwidth: 6.5610000000e3, unit_family: SiBits }: 52.49kb DisplayBandwidth { bandwidth: 1.9683000000e4, unit_family: SiBits }: 157.46kb DisplayBandwidth { bandwidth: 5.9049000000e4, unit_family: SiBits }: 472.39kb DisplayBandwidth { bandwidth: 1.7714700000e5, unit_family: SiBits }: 1.42Mb DisplayBandwidth { bandwidth: 5.3144100000e5, unit_family: SiBits }: 4.25Mb DisplayBandwidth { bandwidth: 1.5943230000e6, unit_family: SiBits }: 12.75Mb DisplayBandwidth { bandwidth: 4.7829690000e6, unit_family: SiBits }: 38.26Mb DisplayBandwidth { bandwidth: 1.4348907000e7, unit_family: SiBits }: 114.79Mb DisplayBandwidth { bandwidth: 4.3046721000e7, unit_family: SiBits }: 344.37Mb DisplayBandwidth { bandwidth: 1.2914016300e8, unit_family: SiBits }: 1.03Gb DisplayBandwidth { bandwidth: 3.8742048900e8, unit_family: SiBits }: 3.10Gb DisplayBandwidth { bandwidth: 1.1622614670e9, unit_family: SiBits }: 9.30Gb DisplayBandwidth { bandwidth: 3.4867844010e9, unit_family: SiBits }: 27.89Gb DisplayBandwidth { bandwidth: 1.0460353203e10, unit_family: SiBits }: 83.68Gb DisplayBandwidth { bandwidth: 3.1381059609e10, unit_family: SiBits }: 251.05Gb DisplayBandwidth { bandwidth: 9.4143178827e10, unit_family: SiBits }: 753.15Gb DisplayBandwidth { bandwidth: 2.8242953648e11, unit_family: SiBits }: 2.26Tb DisplayBandwidth { bandwidth: 8.4728860944e11, unit_family: SiBits }: 6.78Tb DisplayBandwidth { bandwidth: 2.5418658283e12, unit_family: SiBits }: 20.33Tb DisplayBandwidth { bandwidth: 7.6255974850e12, unit_family: SiBits }: 61.00Tb DisplayBandwidth { bandwidth: 2.2876792455e13, unit_family: SiBits }: 183.01Tb DisplayBandwidth { bandwidth: 6.8630377365e13, unit_family: SiBits }: 549.04Tb DisplayBandwidth { bandwidth: 2.0589113209e14, unit_family: SiBits }: 1.65Pb DisplayBandwidth { bandwidth: 6.1767339628e14, unit_family: SiBits }: 4.94Pb DisplayBandwidth { bandwidth: 1.8530201889e15, unit_family: SiBits }: 14.82Pb DisplayBandwidth { bandwidth: 5.5590605666e15, unit_family: SiBits }: 44.47Pb DisplayBandwidth { bandwidth: 1.6677181700e16, unit_family: SiBits }: 133.42Pb DisplayBandwidth { bandwidth: 5.0031545099e16, unit_family: SiBits }: 400.25Pb DisplayBandwidth { bandwidth: 1.5009463530e17, unit_family: SiBits }: 1200.76Pb DisplayBandwidth { bandwidth: 4.5028390589e17, unit_family: SiBits }: 3602.27Pb DisplayBandwidth { bandwidth: 8.0000000000e-3, unit_family: SiBits }: 0.06b DisplayBandwidth { bandwidth: 4.0000000000e-2, unit_family: SiBits }: 0.32b DisplayBandwidth { bandwidth: 2.0000000000e-1, unit_family: SiBits }: 1.60b DisplayBandwidth { bandwidth: 1.0000000000e0, unit_family: SiBits }: 8.00b DisplayBandwidth { bandwidth: 5.0000000000e0, unit_family: SiBits }: 40.00b DisplayBandwidth { bandwidth: 2.5000000000e1, unit_family: SiBits }: 200.00b DisplayBandwidth { bandwidth: 1.2500000000e2, unit_family: SiBits }: 1.00kb DisplayBandwidth { bandwidth: 6.2500000000e2, unit_family: SiBits }: 5.00kb DisplayBandwidth { bandwidth: 3.1250000000e3, unit_family: SiBits }: 25.00kb DisplayBandwidth { bandwidth: 1.5625000000e4, unit_family: SiBits }: 125.00kb DisplayBandwidth { bandwidth: 7.8125000000e4, unit_family: SiBits }: 625.00kb DisplayBandwidth { bandwidth: 3.9062500000e5, unit_family: SiBits }: 3.12Mb DisplayBandwidth { bandwidth: 1.9531250000e6, unit_family: SiBits }: 15.62Mb DisplayBandwidth { bandwidth: 9.7656250000e6, unit_family: SiBits }: 78.12Mb DisplayBandwidth { bandwidth: 4.8828125000e7, unit_family: SiBits }: 390.62Mb DisplayBandwidth { bandwidth: 2.4414062500e8, unit_family: SiBits }: 1.95Gb DisplayBandwidth { bandwidth: 1.2207031250e9, unit_family: SiBits }: 9.77Gb DisplayBandwidth { bandwidth: 6.1035156250e9, unit_family: SiBits }: 48.83Gb DisplayBandwidth { bandwidth: 3.0517578125e10, unit_family: SiBits }: 244.14Gb DisplayBandwidth { bandwidth: 1.5258789062e11, unit_family: SiBits }: 1.22Tb DisplayBandwidth { bandwidth: 7.6293945312e11, unit_family: SiBits }: 6.10Tb DisplayBandwidth { bandwidth: 3.8146972656e12, unit_family: SiBits }: 30.52Tb DisplayBandwidth { bandwidth: 1.9073486328e13, unit_family: SiBits }: 152.59Tb DisplayBandwidth { bandwidth: 9.5367431641e13, unit_family: SiBits }: 762.94Tb DisplayBandwidth { bandwidth: 4.7683715820e14, unit_family: SiBits }: 3.81Pb DisplayBandwidth { bandwidth: 2.3841857910e15, unit_family: SiBits }: 19.07Pb DisplayBandwidth { bandwidth: 1.1920928955e16, unit_family: SiBits }: 95.37Pb DisplayBandwidth { bandwidth: 5.9604644775e16, unit_family: SiBits }: 476.84Pb DisplayBandwidth { bandwidth: 2.9802322388e17, unit_family: SiBits }: 2384.19Pb ================================================ FILE: src/display/components/table.rs ================================================ use std::{collections::HashMap, net::IpAddr, ops::Index, rc::Rc}; use derive_more::Debug; use itertools::Itertools; use ratatui::{ layout::{Constraint, Rect}, style::{Color, Style}, widgets::{Block, Borders, Row}, Frame, }; use unicode_width::{UnicodeWidthChar, UnicodeWidthStr}; use crate::{ display::{Bandwidth, BandwidthUnitFamily, DisplayBandwidth, UIState}, network::{display_connection_string, display_ip_or_host}, }; /// The displayed layout choice of a table. /// Each value in the array is the width of each column. /// /// Note that this only determines how a table is displayed, not what data it contains. /// /// If we intend to display different number of columns in the future, /// then new variants should be added. #[derive(Copy, Clone, Debug)] pub enum DisplayLayout { /// Show 2 columns. C2([u16; 2]), /// Show 3 columns. C3([u16; 3]), /// Show 4 columns. C4([u16; 4]), } impl Index for DisplayLayout { type Output = u16; fn index(&self, i: usize) -> &Self::Output { match self { Self::C2(arr) => &arr[i], Self::C3(arr) => &arr[i], Self::C4(arr) => &arr[i], } } } impl DisplayLayout { #[inline] fn columns_count(&self) -> usize { match self { Self::C2(_) => 2, Self::C3(_) => 3, Self::C4(_) => 4, } } #[inline] fn iter(&self) -> impl Iterator { match self { Self::C2(ws) => ws.iter(), Self::C3(ws) => ws.iter(), Self::C4(ws) => ws.iter(), } } #[inline] fn widths_sum(&self) -> u16 { self.iter().sum() } /// Returns the computed actual width and the spacer width. /// /// See [`Table`] for layout rules. fn compute_actual_widths(&self, available: u16) -> (Self, u16) { let columns_count = self.columns_count() as u16; let desired_min = self.widths_sum(); // spacer max width is 2 let spacer = if available > desired_min { ((available - desired_min) / (columns_count - 1)).min(2) } else { 0 }; let available_without_spacers = available - spacer * (columns_count - 1); // multiplier let m = available_without_spacers as f64 / desired_min as f64; // remainder width is arbitrarily given to column 0 let computed = match *self { Self::C2([_w0, w1]) => { let w1_new = (w1 as f64 * m).trunc() as u16; Self::C2([available_without_spacers - w1_new, w1_new]) } Self::C3([_w0, w1, w2]) => { let w1_new = (w1 as f64 * m).trunc() as u16; let w2_new = (w2 as f64 * m).trunc() as u16; Self::C3([available_without_spacers - w1_new - w2_new, w1_new, w2_new]) } Self::C4([_w0, w1, w2, w3]) => { let w1_new = (w1 as f64 * m).trunc() as u16; let w2_new = (w2 as f64 * m).trunc() as u16; let w3_new = (w3 as f64 * m).trunc() as u16; Self::C4([ available_without_spacers - w1_new - w2_new - w3_new, w1_new, w2_new, w3_new, ]) } }; (computed, spacer) } } /// All data of a table. /// /// If tables with different number of columns are added in the future, /// then new variants should be added. #[derive(Clone, Debug)] enum TableData { /// A table with 3 columns. C3(NColsTableData<3>), /// A table with 4 columns. C4(NColsTableData<4>), } impl From> for TableData { fn from(data: NColsTableData<3>) -> Self { Self::C3(data) } } impl From> for TableData { fn from(data: NColsTableData<4>) -> Self { Self::C4(data) } } impl TableData { fn column_names(&self) -> &[&str] { match self { Self::C3(inner) => &inner.column_names, Self::C4(inner) => &inner.column_names, } } fn rows(&self) -> Vec<&[String]> { match self { Self::C3(inner) => inner.rows.iter().map(|r| r.as_slice()).collect(), Self::C4(inner) => inner.rows.iter().map(|r| r.as_slice()).collect(), } } fn column_selector(&self) -> &dyn Fn(&DisplayLayout) -> Vec { match self { Self::C3(inner) => inner.column_selector.as_ref(), Self::C4(inner) => inner.column_selector.as_ref(), } } } /// All data of a table with `C` columns. /// /// Note that the number of columns here is independent of the number of columns /// being actually shown. If width-constrained, we might only show some of the columns. #[derive(Clone, Debug)] struct NColsTableData { /// The name of each column. column_names: [&'static str; C], /// All rows of data. rows: Vec<[String; C]>, /// Function to determine which columns to show for a given layout. /// /// This function should return a vector of column indices. /// The indices should be less than `C`; otherwise this will cause a runtime panic. #[debug("Rc")] column_selector: Rc, } /// Clippy wanted me to write this. 💢 type ColumnSelectorFn = dyn Fn(&DisplayLayout) -> Vec; /// A table displayed by bandwhich. #[derive(Clone, Debug)] pub struct Table { title: &'static str, /// A layout mapping between minimum available width and the width of each column. /// /// Note that the width of each column here is the "desired minimum width". /// /// - Wt = available width of table /// - Wd = sum of desired minimum width of each column /// /// - If `Wt >= Wd`, spacers with a maximum width of `2` will be inserted /// between columns; and then the columns will proportionally expand. /// - If `Wt < Wd`, columns will proportionally shrink. width_cutoffs: Vec<(u16, DisplayLayout)>, data: TableData, } impl Table { pub fn create_connections_table(state: &UIState, ip_to_host: &HashMap) -> Self { use DisplayLayout as D; let title = "Utilization by connection"; let width_cutoffs = vec![ (0, D::C2([32, 18])), (80, D::C3([36, 12, 18])), (100, D::C3([54, 18, 22])), (120, D::C3([72, 24, 22])), ]; let column_names = [ "Connection", "Process", if state.cumulative_mode { "Data (Up / Down)" } else { "Rate (Up / Down)" }, ]; let rows = state .connections .iter() .map(|(connection, connection_data)| { [ display_connection_string( connection, ip_to_host, &connection_data.interface_name, ), connection_data.process_name.to_string(), display_upload_and_download( connection_data, state.unit_family, state.cumulative_mode, ), ] }) .collect(); let column_selector = Rc::new(|layout: &D| match layout { D::C2(_) => vec![0, 2], D::C3(_) => vec![0, 1, 2], D::C4(_) => unreachable!(), }); Table { title, width_cutoffs, data: NColsTableData { column_names, rows, column_selector, } .into(), } } pub fn create_processes_table(state: &UIState) -> Self { use DisplayLayout as D; let title = "Utilization by process name"; let width_cutoffs = vec![ (0, D::C2([16, 18])), (50, D::C3([16, 12, 20])), (60, D::C3([24, 12, 20])), (80, D::C4([28, 12, 12, 24])), ]; let column_names = [ "Process", "PID", "Connections", if state.cumulative_mode { "Data (Up / Down)" } else { "Rate (Up / Down)" }, ]; let rows = state .processes .iter() .map(|(proc_info, data_for_process)| { [ proc_info.name.to_string(), proc_info.pid.to_string(), data_for_process.connection_count.to_string(), display_upload_and_download( data_for_process, state.unit_family, state.cumulative_mode, ), ] }) .collect(); let column_selector = Rc::new(|layout: &D| match layout { D::C2(_) => vec![0, 3], D::C3(_) => vec![0, 2, 3], D::C4(_) => vec![0, 1, 2, 3], }); Table { title, width_cutoffs, data: NColsTableData { column_names, rows, column_selector, } .into(), } } pub fn create_remote_addresses_table( state: &UIState, ip_to_host: &HashMap, ) -> Self { use DisplayLayout as D; let title = "Utilization by remote address"; let width_cutoffs = vec![ (0, D::C2([16, 16])), (40, D::C2([20, 16])), (60, D::C3([24, 10, 20])), (100, D::C3([54, 16, 24])), ]; let column_names = [ "Remote Address", "Connections", if state.cumulative_mode { "Data (Up / Down)" } else { "Rate (Up / Down)" }, ]; let rows = state .remote_addresses .iter() .map(|(remote_address, data_for_remote_address)| { let remote_address = display_ip_or_host(*remote_address, ip_to_host); [ remote_address, data_for_remote_address.connection_count.to_string(), display_upload_and_download( data_for_remote_address, state.unit_family, state.cumulative_mode, ), ] }) .collect(); let column_selector = Rc::new(|layout: &D| match layout { D::C2(_) => vec![0, 2], D::C3(_) => vec![0, 1, 2], D::C4(_) => unreachable!(), }); Table { title, width_cutoffs, data: NColsTableData { column_names, rows, column_selector, } .into(), } } /// See [`Table`] for layout rules. pub fn render(&self, frame: &mut Frame, rect: Rect) { let (computed_layout, spacer_width) = { // pick the largest possible layout, constrained by the available width let &(_, layout) = self .width_cutoffs .iter() .rev() .find(|(cutoff, _)| rect.width > *cutoff) .unwrap(); // all cutoff tables have a 0-width entry layout.compute_actual_widths(rect.width) }; let columns_to_show = self.data.column_selector()(&computed_layout); let column_names: Vec<_> = columns_to_show .iter() .copied() .map(|i| self.data.column_names()[i]) .collect(); // text needs to react to column widths let tui_rows_iter = self .data .rows() .into_iter() .map(|row_data| { let shown_columns_data = columns_to_show.iter().copied().map(|i| &row_data[i]); let column_widths = computed_layout.iter().copied(); shown_columns_data .zip_eq(column_widths) .map(|(text, width)| truncate_middle(text, width)) .collect::>() }) .map(Row::new); let widths_constraints: Vec<_> = computed_layout .iter() .copied() .map(Constraint::Length) .collect(); let table = ratatui::widgets::Table::new(tui_rows_iter, widths_constraints) .block(Block::default().title(self.title).borders(Borders::ALL)) .header(Row::new(column_names).style(Style::default().fg(Color::Yellow))) .flex(ratatui::layout::Flex::Legacy) .column_spacing(spacer_width); frame.render_widget(table, rect); } } fn display_upload_and_download( bandwidth: &impl Bandwidth, unit_family: BandwidthUnitFamily, _cumulative: bool, ) -> String { let up = DisplayBandwidth { bandwidth: bandwidth.get_total_bytes_uploaded() as f64, unit_family, }; let down = DisplayBandwidth { bandwidth: bandwidth.get_total_bytes_downloaded() as f64, unit_family, }; format!("{up} / {down}") } fn collect_to_unicode_width(iter: impl Iterator, width: usize) -> T where T: FromIterator, { let mut chunk_width = 0; iter.take_while(|ch| { chunk_width += ch.width().unwrap_or(0); chunk_width <= width }) .collect() } fn truncate_middle(row: &str, max_len: u16) -> String { const ELLIPSIS: &str = ".."; if max_len < 6 { collect_to_unicode_width(row.chars(), max_len as usize) } else if row.width() as u16 > max_len { let suffix_len = (max_len as usize - ELLIPSIS.len()) / 2; // remainder length arbitrarily given to prefix let prefix_len = max_len as usize - ELLIPSIS.len() - suffix_len; let prefix: String = collect_to_unicode_width(row.chars(), prefix_len); let suffix: String = collect_to_unicode_width::>(row.chars().rev(), suffix_len) .into_iter() .rev() .collect(); format!("{prefix}{ELLIPSIS}{suffix}") } else { row.to_string() } } ================================================ FILE: src/display/mod.rs ================================================ mod components; mod raw_terminal_backend; mod ui; mod ui_state; pub use components::*; pub use raw_terminal_backend::*; pub use ui::*; pub use ui_state::*; ================================================ FILE: src/display/raw_terminal_backend.rs ================================================ // this is a bit of a hack: // the TUI backend used by this app changes stdout to raw byte mode. // this is not desired when we do not use it (in our --raw mode), // since it makes writing to stdout overly complex // // so what we do here is provide a fake backend (RawTerminalBackend) // that implements the Backend TUI trait, but does nothing // this way, we don't need to create the TermionBackend // and thus skew our stdout when we don't need it use std::io; use ratatui::{ backend::{Backend, WindowSize}, buffer::Cell, layout::{Position, Size}, }; pub struct RawTerminalBackend {} impl Backend for RawTerminalBackend { fn clear(&mut self) -> io::Result<()> { Ok(()) } fn hide_cursor(&mut self) -> io::Result<()> { Ok(()) } fn show_cursor(&mut self) -> io::Result<()> { Ok(()) } fn get_cursor_position(&mut self) -> io::Result { Ok(Position::new(0, 0)) } fn set_cursor_position>(&mut self, _position: P) -> io::Result<()> { Ok(()) } fn draw<'a, I>(&mut self, _content: I) -> io::Result<()> where I: Iterator, { Ok(()) } fn size(&self) -> io::Result { Ok(Size::new(0, 0)) } fn window_size(&mut self) -> io::Result { Ok(WindowSize { columns_rows: Size::default(), pixels: Size::default(), }) } fn flush(&mut self) -> io::Result<()> { Ok(()) } } ================================================ FILE: src/display/ui.rs ================================================ use std::{collections::HashMap, net::IpAddr, time::Duration}; use chrono::prelude::*; use ratatui::{backend::Backend, Terminal}; use crate::{ cli::{Opt, RenderOpts}, display::{ components::{HeaderDetails, HelpText, Layout, Table}, UIState, }, network::{display_connection_string, display_ip_or_host, LocalSocket, Utilization}, os::ProcessInfo, }; pub struct Ui where B: Backend, { terminal: Terminal, state: UIState, ip_to_host: HashMap, opts: RenderOpts, } impl Ui where B: Backend, { pub fn new(terminal_backend: B, opts: &Opt) -> Self { let mut terminal = Terminal::new(terminal_backend).unwrap(); terminal.clear().unwrap(); terminal.hide_cursor().unwrap(); let state = { let mut state = UIState::default(); state.interface_name.clone_from(&opts.interface); state.unit_family = opts.render_opts.unit_family.into(); state.cumulative_mode = opts.render_opts.total_utilization; state.show_dns = opts.show_dns; state }; Ui { terminal, state, ip_to_host: Default::default(), opts: opts.render_opts, } } pub fn output_text(&mut self, write_to_stdout: &mut (dyn FnMut(&str) + Send)) { let state = &self.state; let ip_to_host = &self.ip_to_host; let local_time: DateTime = Local::now(); let timestamp = local_time.timestamp(); let mut no_traffic = true; let output_process_data = |write_to_stdout: &mut (dyn FnMut(&str) + Send), no_traffic: &mut bool| { for (proc_info, process_network_data) in &state.processes { write_to_stdout(&format!( "process: <{timestamp}> \"{}\" up/down Bps: {}/{} connections: {}", proc_info.name, process_network_data.total_bytes_uploaded, process_network_data.total_bytes_downloaded, process_network_data.connection_count )); *no_traffic = false; } }; let output_connections_data = |write_to_stdout: &mut (dyn FnMut(&str) + Send), no_traffic: &mut bool| { for (connection, connection_network_data) in &state.connections { write_to_stdout(&format!( "connection: <{timestamp}> {} up/down Bps: {}/{} process: \"{}\"", display_connection_string( connection, ip_to_host, &connection_network_data.interface_name, ), connection_network_data.total_bytes_uploaded, connection_network_data.total_bytes_downloaded, connection_network_data.process_name )); *no_traffic = false; } }; let output_adressess_data = |write_to_stdout: &mut (dyn FnMut(&str) + Send), no_traffic: &mut bool| { for (remote_address, remote_address_network_data) in &state.remote_addresses { write_to_stdout(&format!( "remote_address: <{timestamp}> {} up/down Bps: {}/{} connections: {}", display_ip_or_host(*remote_address, ip_to_host), remote_address_network_data.total_bytes_uploaded, remote_address_network_data.total_bytes_downloaded, remote_address_network_data.connection_count )); *no_traffic = false; } }; // header write_to_stdout("Refreshing:"); // body1 if self.opts.processes { output_process_data(write_to_stdout, &mut no_traffic); } if self.opts.connections { output_connections_data(write_to_stdout, &mut no_traffic); } if self.opts.addresses { output_adressess_data(write_to_stdout, &mut no_traffic); } if !(self.opts.processes || self.opts.connections || self.opts.addresses) { output_process_data(write_to_stdout, &mut no_traffic); output_connections_data(write_to_stdout, &mut no_traffic); output_adressess_data(write_to_stdout, &mut no_traffic); } // body2: In case no traffic is detected if no_traffic { write_to_stdout(""); } // footer write_to_stdout(""); } pub fn draw(&mut self, paused: bool, elapsed_time: Duration, table_cycle_offset: usize) { let layout = Layout { header: HeaderDetails { state: &self.state, elapsed_time, paused, }, children: self.get_tables_to_display(), footer: HelpText { paused, show_dns: self.state.show_dns, }, }; self.terminal .draw(|frame| layout.render(frame, frame.area(), table_cycle_offset)) .unwrap(); } fn get_tables_to_display(&self) -> Vec
{ let opts = &self.opts; let mut children: Vec
= Vec::new(); if opts.processes { children.push(Table::create_processes_table(&self.state)); } if opts.addresses { children.push(Table::create_remote_addresses_table( &self.state, &self.ip_to_host, )); } if opts.connections { children.push(Table::create_connections_table( &self.state, &self.ip_to_host, )); } if !(opts.processes || opts.addresses || opts.connections) { children = vec![ Table::create_processes_table(&self.state), Table::create_remote_addresses_table(&self.state, &self.ip_to_host), Table::create_connections_table(&self.state, &self.ip_to_host), ]; } children } pub fn get_table_count(&self) -> usize { self.get_tables_to_display().len() } pub fn update_state( &mut self, connections_to_procs: HashMap, utilization: Utilization, ip_to_host: HashMap, ) { self.state.update(connections_to_procs, utilization); self.ip_to_host.extend(ip_to_host); } pub fn end(&mut self) { self.terminal.show_cursor().unwrap(); } } ================================================ FILE: src/display/ui_state.rs ================================================ use std::{ cmp, collections::{HashMap, HashSet, VecDeque}, hash::Hash, net::{IpAddr, Ipv4Addr, Ipv6Addr}, }; use log::warn; use crate::{ display::BandwidthUnitFamily, network::{Connection, LocalSocket, Utilization}, os::ProcessInfo, }; static RECALL_LENGTH: usize = 5; static MAX_BANDWIDTH_ITEMS: usize = 1000; pub trait Bandwidth { fn get_total_bytes_downloaded(&self) -> u128; fn get_total_bytes_uploaded(&self) -> u128; fn combine_bandwidth(&mut self, other: &Self); fn divide_by(&mut self, amount: u128); } #[derive(Clone, Default)] pub struct NetworkData { pub total_bytes_downloaded: u128, pub total_bytes_uploaded: u128, pub connection_count: u128, } #[derive(Clone, Default)] pub struct ConnectionData { pub total_bytes_downloaded: u128, pub total_bytes_uploaded: u128, pub process_name: String, pub interface_name: String, } impl Bandwidth for NetworkData { fn get_total_bytes_downloaded(&self) -> u128 { self.total_bytes_downloaded } fn get_total_bytes_uploaded(&self) -> u128 { self.total_bytes_uploaded } fn combine_bandwidth(&mut self, other: &NetworkData) { self.total_bytes_downloaded += other.get_total_bytes_downloaded(); self.total_bytes_uploaded += other.get_total_bytes_uploaded(); self.connection_count = other.connection_count; } fn divide_by(&mut self, amount: u128) { self.total_bytes_downloaded /= amount; self.total_bytes_uploaded /= amount; } } impl Bandwidth for ConnectionData { fn get_total_bytes_downloaded(&self) -> u128 { self.total_bytes_downloaded } fn get_total_bytes_uploaded(&self) -> u128 { self.total_bytes_uploaded } fn combine_bandwidth(&mut self, other: &ConnectionData) { self.total_bytes_downloaded += other.get_total_bytes_downloaded(); self.total_bytes_uploaded += other.get_total_bytes_uploaded(); } fn divide_by(&mut self, amount: u128) { self.total_bytes_downloaded /= amount; self.total_bytes_uploaded /= amount; } } pub struct UtilizationData { connections_to_procs: HashMap, network_utilization: Utilization, } #[derive(Default)] pub struct UIState { /// The interface name in single-interface mode. `None` means all interfaces. pub interface_name: Option, pub processes: Vec<(ProcessInfo, NetworkData)>, pub remote_addresses: Vec<(IpAddr, NetworkData)>, pub connections: Vec<(Connection, ConnectionData)>, pub total_bytes_downloaded: u128, pub total_bytes_uploaded: u128, pub cumulative_mode: bool, pub show_dns: bool, pub unit_family: BandwidthUnitFamily, pub utilization_data: VecDeque, pub processes_map: HashMap, pub remote_addresses_map: HashMap, pub connections_map: HashMap, /// Used for reducing logging noise. known_orphan_sockets: VecDeque, } impl UIState { pub fn update( &mut self, connections_to_procs: HashMap, network_utilization: Utilization, ) { self.utilization_data.push_back(UtilizationData { connections_to_procs, network_utilization, }); if self.utilization_data.len() > RECALL_LENGTH { self.utilization_data.pop_front(); } let mut processes: HashMap = HashMap::new(); let mut remote_addresses: HashMap = HashMap::new(); let mut connections: HashMap = HashMap::new(); let mut total_bytes_downloaded: u128 = 0; let mut total_bytes_uploaded: u128 = 0; let mut seen_connections = HashSet::new(); for state in self.utilization_data.iter().rev() { let connections_to_procs = &state.connections_to_procs; let network_utilization = &state.network_utilization; for (connection, connection_info) in &network_utilization.connections { let connection_previously_seen = !seen_connections.insert(connection); let connection_data = connections.entry(*connection).or_default(); let data_for_remote_address = remote_addresses .entry(connection.remote_socket.ip) .or_default(); connection_data.total_bytes_downloaded += connection_info.total_bytes_downloaded; connection_data.total_bytes_uploaded += connection_info.total_bytes_uploaded; connection_data .interface_name .clone_from(&connection_info.interface_name); data_for_remote_address.total_bytes_downloaded += connection_info.total_bytes_downloaded; data_for_remote_address.total_bytes_uploaded += connection_info.total_bytes_uploaded; if !connection_previously_seen { data_for_remote_address.connection_count += 1; } total_bytes_downloaded += connection_info.total_bytes_downloaded; total_bytes_uploaded += connection_info.total_bytes_uploaded; let data_for_process = { let local_socket = connection.local_socket; let proc_info = get_proc_info(connections_to_procs, &local_socket); // only log each orphan connection once if proc_info.is_none() && !self.known_orphan_sockets.contains(&local_socket) { // newer connections go in the front so that searches are faster // basically recency bias self.known_orphan_sockets.push_front(local_socket); self.known_orphan_sockets.truncate(10_000); // arbitrary maximum backlog match connections_to_procs .iter() .find(|(&LocalSocket { port, protocol, .. }, _)| { port == local_socket.port && protocol == local_socket.protocol }) .and_then(|(local_conn_lookalike, info)| { network_utilization .connections .keys() .find(|conn| &conn.local_socket == local_conn_lookalike) .map(|conn| (conn, info)) }) { Some((lookalike, proc_info)) => { warn!( r#""{0}" owns a similar looking connection, but its local ip doesn't match."#, proc_info.name ); warn!("Looking for: {connection:?}; found: {lookalike:?}"); } None => { warn!("Cannot determine which process owns {connection:?}"); } }; } let proc_info = proc_info .cloned() .unwrap_or_else(|| ProcessInfo::new("", 0)); connection_data.process_name.clone_from(&proc_info.name); processes.entry(proc_info).or_default() }; data_for_process.total_bytes_downloaded += connection_info.total_bytes_downloaded; data_for_process.total_bytes_uploaded += connection_info.total_bytes_uploaded; if !connection_previously_seen { data_for_process.connection_count += 1; } } } let divide_by = if self.utilization_data.is_empty() { 1_u128 } else { self.utilization_data.len() as u128 }; for (_, network_data) in processes.iter_mut() { network_data.divide_by(divide_by) } for (_, network_data) in remote_addresses.iter_mut() { network_data.divide_by(divide_by) } for (_, connection_data) in connections.iter_mut() { connection_data.divide_by(divide_by) } if self.cumulative_mode { merge_bandwidth(&mut self.processes_map, processes); merge_bandwidth(&mut self.remote_addresses_map, remote_addresses); merge_bandwidth(&mut self.connections_map, connections); self.total_bytes_downloaded += total_bytes_downloaded / divide_by; self.total_bytes_uploaded += total_bytes_uploaded / divide_by; } else { self.processes_map = processes; self.remote_addresses_map = remote_addresses; self.connections_map = connections; self.total_bytes_downloaded = total_bytes_downloaded / divide_by; self.total_bytes_uploaded = total_bytes_uploaded / divide_by; } self.processes = sort_and_prune(&mut self.processes_map); self.remote_addresses = sort_and_prune(&mut self.remote_addresses_map); self.connections = sort_and_prune(&mut self.connections_map); } } fn get_proc_info<'a>( connections_to_procs: &'a HashMap, local_socket: &LocalSocket, ) -> Option<&'a ProcessInfo> { connections_to_procs // direct match .get(local_socket) // IPv4-mapped IPv6 addresses .or_else(|| { let swapped: IpAddr = match local_socket.ip { IpAddr::V4(v4) => v4.to_ipv6_mapped().into(), IpAddr::V6(v6) => v6.to_ipv4_mapped()?.into(), }; connections_to_procs.get(&LocalSocket { ip: swapped, ..*local_socket }) }) // address unspecified .or_else(|| { connections_to_procs.get(&LocalSocket { ip: Ipv4Addr::UNSPECIFIED.into(), ..*local_socket }) }) .or_else(|| { connections_to_procs.get(&LocalSocket { ip: Ipv6Addr::UNSPECIFIED.into(), ..*local_socket }) }) } fn merge_bandwidth(self_map: &mut HashMap, other_map: HashMap) where K: Eq + Hash, V: Bandwidth, { for (key, b_other) in other_map { self_map .entry(key) .and_modify(|b_self| b_self.combine_bandwidth(&b_other)) .or_insert(b_other); } } fn sort_and_prune(map: &mut HashMap) -> Vec<(K, V)> where K: Eq + Hash + Clone, V: Bandwidth + Clone, { let mut bandwidth_list = Vec::from_iter(map.clone()); bandwidth_list.sort_by_key(|(_, b)| { cmp::Reverse(b.get_total_bytes_downloaded() + b.get_total_bytes_uploaded()) }); if bandwidth_list.len() > MAX_BANDWIDTH_ITEMS { for (key, _) in &bandwidth_list[MAX_BANDWIDTH_ITEMS..] { map.remove(key); } } bandwidth_list } ================================================ FILE: src/main.rs ================================================ #![deny(clippy::enum_glob_use)] mod cli; mod display; mod network; mod os; #[cfg(test)] mod tests; use std::{ collections::HashMap, fs::File, sync::{ atomic::{AtomicBool, AtomicUsize, Ordering}, Arc, Mutex, RwLock, }, thread::{self, park_timeout}, time::{Duration, Instant}, }; use clap::Parser; use crossterm::{ event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers}, terminal, }; use display::{elapsed_time, RawTerminalBackend, Ui}; use eyre::bail; use network::{ dns::{self, IpTable}, LocalSocket, Sniffer, Utilization, }; use pnet::datalink::{DataLinkReceiver, NetworkInterface}; use ratatui::backend::{Backend, CrosstermBackend}; use simplelog::WriteLogger; use crate::cli::Opt; use crate::os::ProcessInfo; const DISPLAY_DELTA: Duration = Duration::from_millis(1000); fn main() -> eyre::Result<()> { let opts = Opt::parse(); // init logging if let Some(ref log_path) = opts.log_to { let log_file = File::options() .write(true) .create_new(true) .open(log_path)?; WriteLogger::init( opts.verbosity.log_level_filter(), Default::default(), log_file, )?; } let os_input = os::get_input(opts.interface.as_deref(), !opts.no_resolve, opts.dns_server)?; if opts.raw { let terminal_backend = RawTerminalBackend {}; start(terminal_backend, os_input, opts); } else { let Ok(()) = terminal::enable_raw_mode() else { bail!( "Failed to get stdout: if you are trying to pipe 'bandwhich' you should use the --raw flag" ) }; let mut stdout = std::io::stdout(); // Ignore enteralternatescreen error let _ = crossterm::execute!(&mut stdout, terminal::EnterAlternateScreen); let terminal_backend = CrosstermBackend::new(stdout); start(terminal_backend, os_input, opts); // Ensure terminal is restored after exit (handles SIGINT case). // These operations are idempotent, so safe to call even if 'q' already cleaned up. let _ = terminal::disable_raw_mode(); let _ = crossterm::execute!(std::io::stdout(), terminal::LeaveAlternateScreen); } Ok(()) } pub struct OpenSockets { sockets_to_procs: HashMap, } pub struct OsInputOutput { pub interfaces_with_frames: Vec<(NetworkInterface, Box)>, pub get_open_sockets: fn() -> OpenSockets, pub terminal_events: Box + Send>, pub dns_client: Option, pub write_to_stdout: Box, } pub fn start(terminal_backend: B, os_input: OsInputOutput, opts: Opt) where B: Backend + Send + 'static, { let running = Arc::new(AtomicBool::new(true)); let paused = Arc::new(AtomicBool::new(false)); let last_start_time = Arc::new(RwLock::new(Instant::now())); let cumulative_time = Arc::new(RwLock::new(Duration::new(0, 0))); let table_cycle_offset = Arc::new(AtomicUsize::new(0)); // handle SIGINT properly instead of as a keypress // see https://github.com/imsnif/bandwhich/issues/487 #[cfg(not(test))] { let running = running.clone(); ctrlc::set_handler(move || { running.store(false, Ordering::Release); }) .expect("failed to set SIGINT handler"); } let mut active_threads = vec![]; let terminal_events = os_input.terminal_events; let get_open_sockets = os_input.get_open_sockets; let mut write_to_stdout = os_input.write_to_stdout; let mut dns_client = os_input.dns_client; let raw_mode = opts.raw; let network_utilization = Arc::new(Mutex::new(Utilization::new())); let ui = Arc::new(Mutex::new(Ui::new(terminal_backend, &opts))); let display_handler = thread::Builder::new() .name("display_handler".to_string()) .spawn({ let running = running.clone(); let paused = paused.clone(); let table_cycle_offset = table_cycle_offset.clone(); let network_utilization = network_utilization.clone(); let last_start_time = last_start_time.clone(); let cumulative_time = cumulative_time.clone(); let ui = ui.clone(); move || { while running.load(Ordering::Acquire) { let render_start_time = Instant::now(); let utilization = network_utilization.lock().unwrap().clone_and_reset(); let OpenSockets { sockets_to_procs } = get_open_sockets(); let mut ip_to_host = IpTable::new(); if let Some(dns_client) = dns_client.as_mut() { ip_to_host = dns_client.cache(); let unresolved_ips = utilization .connections .keys() .filter(|conn| !ip_to_host.contains_key(&conn.remote_socket.ip)) .map(|conn| conn.remote_socket.ip) .collect::>(); dns_client.resolve(unresolved_ips); } { let mut ui = ui.lock().unwrap(); let paused = paused.load(Ordering::SeqCst); let table_cycle_offset = table_cycle_offset.load(Ordering::SeqCst); if !paused { ui.update_state(sockets_to_procs, utilization, ip_to_host); } let elapsed_time = elapsed_time( *last_start_time.read().unwrap(), *cumulative_time.read().unwrap(), paused, ); if raw_mode { ui.output_text(&mut write_to_stdout); } else { ui.draw(paused, elapsed_time, table_cycle_offset); } } let render_duration = render_start_time.elapsed(); if render_duration < DISPLAY_DELTA { park_timeout(DISPLAY_DELTA - render_duration); } } if !raw_mode { let mut ui = ui.lock().unwrap(); ui.end(); } } }) .unwrap(); let terminal_event_handler = thread::Builder::new() .name("terminal_events_handler".to_string()) .spawn({ let running = running.clone(); let display_handler = display_handler.thread().clone(); move || { let mut terminal_events = terminal_events; while running.load(Ordering::Acquire) { let Some(evt) = terminal_events.next() else { continue; }; let mut ui = ui.lock().unwrap(); match evt { Event::Resize(_x, _y) if !raw_mode => { let paused = paused.load(Ordering::SeqCst); ui.draw( paused, elapsed_time( *last_start_time.read().unwrap(), *cumulative_time.read().unwrap(), paused, ), table_cycle_offset.load(Ordering::SeqCst), ); } Event::Key(KeyEvent { modifiers: KeyModifiers::NONE, code: KeyCode::Char('q'), kind: KeyEventKind::Press, .. }) => { running.store(false, Ordering::Release); display_handler.unpark(); match terminal::disable_raw_mode() { Ok(_) => {} Err(_) => println!("Error could not disable raw input"), } let mut stdout = std::io::stdout(); if crossterm::execute!(&mut stdout, terminal::LeaveAlternateScreen) .is_err() { println!("Error could not leave alternte screen"); }; break; } Event::Key(KeyEvent { modifiers: KeyModifiers::NONE, code: KeyCode::Char(' '), kind: KeyEventKind::Press, .. }) => { let restarting = paused.fetch_xor(true, Ordering::SeqCst); if restarting { *last_start_time.write().unwrap() = Instant::now(); } else { let last_start_time_copy = *last_start_time.read().unwrap(); let current_cumulative_time_copy = *cumulative_time.read().unwrap(); let new_cumulative_time = current_cumulative_time_copy + last_start_time_copy.elapsed(); *cumulative_time.write().unwrap() = new_cumulative_time; } display_handler.unpark(); } Event::Key(KeyEvent { modifiers: KeyModifiers::NONE, code: KeyCode::Tab, kind: KeyEventKind::Press, .. }) => { let paused = paused.load(Ordering::SeqCst); let elapsed_time = elapsed_time( *last_start_time.read().unwrap(), *cumulative_time.read().unwrap(), paused, ); let table_count = ui.get_table_count(); let new = table_cycle_offset.load(Ordering::SeqCst) + 1 % table_count; table_cycle_offset.store(new, Ordering::SeqCst); ui.draw(paused, elapsed_time, new); } _ => (), }; } } }) .unwrap(); active_threads.push(display_handler); active_threads.push(terminal_event_handler); let sniffer_threads = os_input .interfaces_with_frames .into_iter() .map(|(iface, frames)| { let name = format!("sniffing_handler_{}", iface.name); let running = running.clone(); let show_dns = opts.show_dns; let network_utilization = network_utilization.clone(); thread::Builder::new() .name(name) .spawn(move || { let mut sniffer = Sniffer::new(iface, frames, show_dns); while running.load(Ordering::Acquire) { if let Some(segment) = sniffer.next() { network_utilization.lock().unwrap().ingest(segment); } } }) .unwrap() }) .collect::>(); active_threads.extend(sniffer_threads); for thread_handler in active_threads { thread_handler.join().unwrap() } } ================================================ FILE: src/network/connection.rs ================================================ use std::{ collections::HashMap, fmt, net::{IpAddr, SocketAddr}, }; #[derive(PartialEq, Hash, Eq, Clone, PartialOrd, Ord, Debug, Copy)] pub enum Protocol { Tcp, Udp, } impl Protocol { #[allow(dead_code)] pub fn from_str(string: &str) -> Option { match string { "TCP" => Some(Protocol::Tcp), "UDP" => Some(Protocol::Udp), _ => None, } } } impl fmt::Display for Protocol { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Protocol::Tcp => write!(f, "tcp"), Protocol::Udp => write!(f, "udp"), } } } #[derive(Clone, Ord, PartialOrd, PartialEq, Eq, Hash, Copy)] pub struct Socket { pub ip: IpAddr, pub port: u16, } impl fmt::Debug for Socket { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Socket { ip, port } = self; match ip { IpAddr::V4(v4) => write!(f, "{v4}:{port}"), IpAddr::V6(v6) => write!(f, "[{v6}]:{port}"), } } } #[derive(PartialEq, Hash, Eq, Clone, PartialOrd, Ord, Copy)] pub struct LocalSocket { pub ip: IpAddr, pub port: u16, pub protocol: Protocol, } impl fmt::Debug for LocalSocket { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let LocalSocket { ip, port, protocol } = self; match ip { IpAddr::V4(v4) => write!(f, "{protocol}://{v4}:{port}"), IpAddr::V6(v6) => write!(f, "{protocol}://[{v6}]:{port}"), } } } #[derive(PartialEq, Hash, Eq, Clone, PartialOrd, Ord, Copy)] pub struct Connection { pub remote_socket: Socket, pub local_socket: LocalSocket, } impl fmt::Debug for Connection { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Connection { remote_socket, local_socket, } = self; write!(f, "{local_socket:?} => {remote_socket:?}") } } pub fn display_ip_or_host(ip: IpAddr, ip_to_host: &HashMap) -> String { match ip_to_host.get(&ip) { Some(host) => host.clone(), None => ip.to_string(), } } pub fn display_connection_string( connection: &Connection, ip_to_host: &HashMap, interface_name: &str, ) -> String { format!( "<{interface_name}>:{} => {}:{} ({})", connection.local_socket.port, display_ip_or_host(connection.remote_socket.ip, ip_to_host), connection.remote_socket.port, connection.local_socket.protocol, ) } impl Connection { pub fn new( remote_socket: SocketAddr, local_ip: IpAddr, local_port: u16, protocol: Protocol, ) -> Self { Connection { remote_socket: Socket { ip: remote_socket.ip(), port: remote_socket.port(), }, local_socket: LocalSocket { ip: local_ip, port: local_port, protocol, }, } } } ================================================ FILE: src/network/dns/client.rs ================================================ use std::{ collections::HashSet, net::IpAddr, sync::{Arc, Mutex}, thread::{Builder, JoinHandle}, }; use tokio::{ runtime::Runtime, sync::mpsc::{self, Sender}, }; use crate::network::dns::{resolver::Lookup, IpTable}; type PendingAddrs = HashSet; const CHANNEL_SIZE: usize = 1_000; pub struct Client { cache: Arc>, pending: Arc>, tx: Option>>, handle: Option>, } impl Client { pub fn new(resolver: R, runtime: Runtime) -> eyre::Result where R: Lookup + Send + Sync + 'static, { let cache = Arc::new(Mutex::new(IpTable::new())); let pending = Arc::new(Mutex::new(PendingAddrs::new())); let (tx, mut rx) = mpsc::channel::>(CHANNEL_SIZE); let handle = Builder::new().name("resolver".into()).spawn({ let cache = cache.clone(); let pending = pending.clone(); move || { runtime.block_on(async { let resolver = Arc::new(resolver); while let Some(ips) = rx.recv().await { for ip in ips { tokio::spawn({ let resolver = resolver.clone(); let cache = cache.clone(); let pending = pending.clone(); async move { if let Some(name) = resolver.lookup(ip).await { cache.lock().unwrap().insert(ip, name); } pending.lock().unwrap().remove(&ip); } }); } } }); } })?; Ok(Self { cache, pending, tx: Some(tx), handle: Some(handle), }) } pub fn resolve(&mut self, ips: Vec) { // Remove ips that are already being resolved let ips = ips .into_iter() .filter(|ip| self.pending.lock().unwrap().insert(*ip)) .collect::>(); if !ips.is_empty() { // Discard the message if the channel is full; it will be retried eventually let _ = self.tx.as_mut().unwrap().try_send(ips); } } pub fn cache(&mut self) -> IpTable { let cache = self.cache.lock().unwrap(); cache.clone() } } impl Drop for Client { fn drop(&mut self) { // Do the Option dance to be able to drop the sender so that the receiver finishes and the thread can be joined drop(self.tx.take().unwrap()); self.handle.take().unwrap().join().unwrap(); } } ================================================ FILE: src/network/dns/mod.rs ================================================ use std::{collections::HashMap, net::IpAddr}; mod client; mod resolver; pub use client::*; pub use resolver::*; pub type IpTable = HashMap; ================================================ FILE: src/network/dns/resolver.rs ================================================ use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4}; use async_trait::async_trait; use trust_dns_resolver::{ config::{NameServerConfig, Protocol, ResolverConfig, ResolverOpts}, error::ResolveErrorKind, TokioAsyncResolver, }; #[async_trait] pub trait Lookup { async fn lookup(&self, ip: IpAddr) -> Option; } pub struct Resolver(TokioAsyncResolver); impl Resolver { pub async fn new(dns_server: Option) -> eyre::Result { let resolver = match dns_server { Some(dns_server_address) => { let mut config = ResolverConfig::new(); let options = ResolverOpts::default(); let socket = SocketAddr::V4(SocketAddrV4::new(dns_server_address, 53)); let nameserver_config = NameServerConfig { socket_addr: socket, protocol: Protocol::Udp, tls_dns_name: None, trust_negative_responses: false, bind_addr: None, }; config.add_name_server(nameserver_config); TokioAsyncResolver::tokio(config, options) } None => TokioAsyncResolver::tokio_from_system_conf()?, }; Ok(Self(resolver)) } } #[async_trait] impl Lookup for Resolver { async fn lookup(&self, ip: IpAddr) -> Option { let lookup_future = self.0.reverse_lookup(ip); match lookup_future.await { Ok(names) => { // Take the first result and convert it to a string names.into_iter().next().map(|name| name.to_string()) } Err(e) => match e.kind() { // If the IP is not associated with a hostname, store the IP // so that we don't retry indefinitely ResolveErrorKind::NoRecordsFound { .. } => Some(ip.to_string()), _ => None, }, } } } ================================================ FILE: src/network/mod.rs ================================================ mod connection; pub mod dns; mod sniffer; mod utilization; pub use connection::*; pub use sniffer::*; pub use utilization::*; ================================================ FILE: src/network/sniffer.rs ================================================ use std::{ io::{self, Result}, net::{IpAddr, SocketAddr}, thread::park_timeout, time::Duration, }; use pnet::{ datalink::{DataLinkReceiver, NetworkInterface}, ipnetwork::IpNetwork, packet::{ ethernet::{EtherTypes, EthernetPacket}, ip::{IpNextHeaderProtocol, IpNextHeaderProtocols}, ipv4::Ipv4Packet, ipv6::Ipv6Packet, tcp::TcpPacket, udp::UdpPacket, Packet, }, }; use crate::{ network::{Connection, Protocol}, os::shared::get_datalink_channel, }; const PACKET_WAIT_TIMEOUT: Duration = Duration::from_millis(10); const CHANNEL_RESET_DELAY: Duration = Duration::from_millis(1000); #[derive(Debug)] pub struct Segment { pub interface_name: String, pub connection: Connection, pub direction: Direction, pub data_length: u128, } #[derive(PartialEq, Hash, Eq, Debug, Clone, PartialOrd)] pub enum Direction { Download, Upload, } impl Direction { pub fn new(network_interface_ips: &[IpNetwork], source: IpAddr) -> Self { if network_interface_ips .iter() .any(|ip_network| ip_network.ip() == source) { Direction::Upload } else { Direction::Download } } } trait NextLevelProtocol { fn get_next_level_protocol(&self) -> IpNextHeaderProtocol; } impl NextLevelProtocol for Ipv6Packet<'_> { fn get_next_level_protocol(&self) -> IpNextHeaderProtocol { self.get_next_header() } } macro_rules! extract_transport_protocol { ( $ip_packet: ident ) => {{ match $ip_packet.get_next_level_protocol() { IpNextHeaderProtocols::Tcp => { let message = TcpPacket::new($ip_packet.payload())?; ( Protocol::Tcp, message.get_source(), message.get_destination(), $ip_packet.payload().len() as u128, ) } IpNextHeaderProtocols::Udp => { let datagram = UdpPacket::new($ip_packet.payload())?; ( Protocol::Udp, datagram.get_source(), datagram.get_destination(), $ip_packet.payload().len() as u128, ) } _ => return None, } }}; } pub struct Sniffer { network_interface: NetworkInterface, network_frames: Box, show_dns: bool, } impl Sniffer { pub fn new( network_interface: NetworkInterface, network_frames: Box, show_dns: bool, ) -> Self { Sniffer { network_interface, network_frames, show_dns, } } pub fn next(&mut self) -> Option { let bytes = match self.network_frames.next() { Ok(bytes) => bytes, Err(err) => match err.kind() { std::io::ErrorKind::TimedOut => { park_timeout(PACKET_WAIT_TIMEOUT); return None; } _ => { park_timeout(CHANNEL_RESET_DELAY); self.reset_channel().ok(); return None; } }, }; // See https://github.com/libpnet/libpnet/blob/master/examples/packetdump.rs // VPN interfaces (such as utun0, utun1, etc) have POINT_TO_POINT bit set to 1 let payload_offset = if (self.network_interface.is_loopback() || self.network_interface.is_point_to_point()) && cfg!(target_os = "macos") { // The pnet code for BPF loopback adds a zero'd out Ethernet header 14 } else { 0 }; let ip_packet = Ipv4Packet::new(&bytes[payload_offset..])?; let version = ip_packet.get_version(); match version { 4 => Self::handle_v4(ip_packet, &self.network_interface, self.show_dns), 6 => Self::handle_v6( Ipv6Packet::new(&bytes[payload_offset..])?, &self.network_interface, ), _ => { let pkg = EthernetPacket::new(bytes)?; match pkg.get_ethertype() { EtherTypes::Ipv4 => Self::handle_v4( Ipv4Packet::new(pkg.payload())?, &self.network_interface, self.show_dns, ), EtherTypes::Ipv6 => { Self::handle_v6(Ipv6Packet::new(pkg.payload())?, &self.network_interface) } _ => None, } } } } pub fn reset_channel(&mut self) -> Result<()> { self.network_frames = get_datalink_channel(&self.network_interface) .map_err(|_| io::Error::other("Interface not available"))?; Ok(()) } fn handle_v6(ip_packet: Ipv6Packet, network_interface: &NetworkInterface) -> Option { let (protocol, source_port, destination_port, data_length) = extract_transport_protocol!(ip_packet); let interface_name = network_interface.name.clone(); let direction = Direction::new(&network_interface.ips, ip_packet.get_source().into()); let from = SocketAddr::new(ip_packet.get_source().into(), source_port); let to = SocketAddr::new(ip_packet.get_destination().into(), destination_port); let connection = match direction { Direction::Download => Connection::new(from, to.ip(), destination_port, protocol), Direction::Upload => Connection::new(to, from.ip(), source_port, protocol), }; Some(Segment { interface_name, connection, data_length, direction, }) } fn handle_v4( ip_packet: Ipv4Packet, network_interface: &NetworkInterface, show_dns: bool, ) -> Option { let (protocol, source_port, destination_port, data_length) = extract_transport_protocol!(ip_packet); let interface_name = network_interface.name.clone(); let direction = Direction::new(&network_interface.ips, ip_packet.get_source().into()); let from = SocketAddr::new(ip_packet.get_source().into(), source_port); let to = SocketAddr::new(ip_packet.get_destination().into(), destination_port); let connection = match direction { Direction::Download => Connection::new(from, to.ip(), destination_port, protocol), Direction::Upload => Connection::new(to, from.ip(), source_port, protocol), }; if !show_dns && connection.remote_socket.port == 53 { return None; } Some(Segment { interface_name, connection, data_length, direction, }) } } ================================================ FILE: src/network/utilization.rs ================================================ use std::collections::HashMap; use crate::network::{Connection, Direction, Segment}; #[derive(Clone)] pub struct ConnectionInfo { pub interface_name: String, pub total_bytes_downloaded: u128, pub total_bytes_uploaded: u128, } #[derive(Clone)] pub struct Utilization { pub connections: HashMap, } impl Utilization { pub fn new() -> Self { let connections = HashMap::new(); Utilization { connections } } pub fn clone_and_reset(&mut self) -> Self { let clone = self.clone(); self.connections.clear(); clone } pub fn ingest(&mut self, seg: Segment) { let total_bandwidth = self .connections .entry(seg.connection) .or_insert(ConnectionInfo { interface_name: seg.interface_name, total_bytes_downloaded: 0, total_bytes_uploaded: 0, }); match seg.direction { Direction::Download => { total_bandwidth.total_bytes_downloaded += seg.data_length; } Direction::Upload => { total_bandwidth.total_bytes_uploaded += seg.data_length; } } } } ================================================ FILE: src/os/errors.rs ================================================ #[derive(Clone, Eq, PartialEq, Debug, thiserror::Error)] pub enum GetInterfaceError { #[error("Permission error: {0}")] PermissionError(String), #[error("Other error: {0}")] OtherError(String), } ================================================ FILE: src/os/linux.rs ================================================ use std::collections::HashMap; use procfs::process::FDTarget; use crate::{ network::{LocalSocket, Protocol}, os::ProcessInfo, OpenSockets, }; pub(crate) fn get_open_sockets() -> OpenSockets { let mut open_sockets = HashMap::new(); let mut inode_to_proc = HashMap::new(); if let Ok(all_procs) = procfs::process::all_processes() { for process in all_procs.filter_map(|res| res.ok()) { let Ok(fds) = process.fd() else { continue }; let Ok(stat) = process.stat() else { continue }; let proc_name = stat.comm; let proc_info = ProcessInfo::new(&proc_name, stat.pid as u32); for fd in fds.filter_map(|res| res.ok()) { if let FDTarget::Socket(inode) = fd.target { inode_to_proc.insert(inode, proc_info.clone()); } } } } macro_rules! insert_proto { ($source: expr, $proto: expr) => { let entries = $source.into_iter().filter_map(|res| res.ok()).flatten(); for entry in entries { if let Some(proc_info) = inode_to_proc.get(&entry.inode) { let socket = LocalSocket { ip: entry.local_address.ip(), port: entry.local_address.port(), protocol: $proto, }; open_sockets.insert(socket, proc_info.clone()); } } }; } insert_proto!([procfs::net::tcp(), procfs::net::tcp6()], Protocol::Tcp); insert_proto!([procfs::net::udp(), procfs::net::udp6()], Protocol::Udp); OpenSockets { sockets_to_procs: open_sockets, } } ================================================ FILE: src/os/lsof.rs ================================================ use crate::{os::lsof_utils::get_connections, OpenSockets}; pub(crate) fn get_open_sockets() -> OpenSockets { let sockets_to_procs = get_connections() .filter_map(|raw| raw.as_local_socket().map(|s| (s, raw.proc_info))) .collect(); OpenSockets { sockets_to_procs } } ================================================ FILE: src/os/lsof_utils.rs ================================================ use std::{ffi::OsStr, net::IpAddr, process::Command}; use log::warn; use once_cell::sync::Lazy; use regex::Regex; use crate::{ network::{LocalSocket, Protocol}, os::ProcessInfo, }; #[allow(dead_code)] #[derive(Debug, Clone)] pub struct RawConnection { remote_ip: String, local_ip: String, local_port: String, remote_port: String, protocol: String, pub proc_info: ProcessInfo, } fn get_null_addr(ip_type: &str) -> &str { if ip_type.contains('4') { "0.0.0.0" } else { "::0" } } impl RawConnection { pub fn new(raw_line: &str) -> Option { // Example row // com.apple 664 user 198u IPv4 0xeb179a6650592b8d 0t0 TCP 192.168.1.187:58535->1.2.3.4:443 (ESTABLISHED) let columns: Vec<&str> = raw_line.split_ascii_whitespace().collect(); if columns.len() < 9 { return None; } let process_name = columns[0].replace("\\x20", " "); let pid = columns[1].parse().ok()?; let proc_info = ProcessInfo::new(&process_name, pid); // Unneeded // let username = columns[2]; // let fd = columns[3]; // IPv4 or IPv6 let ip_type = columns[4]; // let device = columns[5]; // let size = columns[6]; // UDP/TCP let protocol = columns[7].to_ascii_uppercase(); if protocol != "TCP" && protocol != "UDP" { return None; } let connection_str = columns[8]; // "(LISTEN)" or "(ESTABLISHED)", this column may or may not be present // let connection_state = columns[9]; static CONNECTION_REGEX: Lazy = Lazy::new(|| Regex::new(r"\[?([^\s\]]*)\]?:(\d+)->\[?([^\s\]]*)\]?:(\d+)").unwrap()); static LISTEN_REGEX: Lazy = Lazy::new(|| Regex::new(r"\[?([^\s\[\]]*)\]?:(.*)").unwrap()); // If this socket is in a "connected" state if let Some(caps) = CONNECTION_REGEX.captures(connection_str) { // Example // 192.168.1.187:64230->0.1.2.3:5228 // *:* // *:4567 let local_ip = String::from(caps.get(1).unwrap().as_str()); let local_port = String::from(caps.get(2).unwrap().as_str()); let remote_ip = String::from(caps.get(3).unwrap().as_str()); let remote_port = String::from(caps.get(4).unwrap().as_str()); let connection = RawConnection { local_ip, local_port, remote_ip, remote_port, protocol, proc_info, }; Some(connection) } else if let Some(caps) = LISTEN_REGEX.captures(connection_str) { let local_ip = if caps.get(1).unwrap().as_str() == "*" { get_null_addr(ip_type) } else { caps.get(1).unwrap().as_str() }; let local_ip = String::from(local_ip); let local_port = String::from(if caps.get(2).unwrap().as_str() == "*" { "0" } else { caps.get(2).unwrap().as_str() }); let remote_ip = String::from(get_null_addr(ip_type)); let remote_port = String::from("0"); let connection = RawConnection { local_ip, local_port, remote_ip, remote_port, protocol, proc_info, }; Some(connection) } else { None } } pub fn get_protocol(&self) -> Option { Protocol::from_str(&self.protocol) } pub fn get_local_ip(&self) -> Option { self.local_ip.parse().ok() } pub fn get_local_port(&self) -> Option { self.local_port.parse::().ok() } pub fn as_local_socket(&self) -> Option { let process = &self.proc_info.name; let Some(ip) = self.get_local_ip() else { warn!(r#"Failed to get the local IP of a connection belonging to "{process}"."#); return None; }; let Some(port) = self.get_local_port() else { warn!(r#"Failed to get the local port of a connection belonging to "{process}"."#); return None; }; let Some(protocol) = self.get_protocol() else { warn!(r#"Failed to get the protocol of a connection belonging to "{process}"."#); return None; }; Some(LocalSocket { ip, port, protocol }) } } pub fn get_connections() -> RawConnections { let content = run(["-n", "-P", "-i4", "-i6", "+c", "0"]); RawConnections::new(content) } fn run(args: I) -> String where I: IntoIterator, S: AsRef, { let output = Command::new("lsof") .args(args) .output() .expect("failed to execute process"); String::from_utf8_lossy(&output.stdout).into_owned() } pub struct RawConnections { content: Vec, } impl RawConnections { pub fn new(content: String) -> RawConnections { let lines: Vec = content.lines().flat_map(RawConnection::new).collect(); RawConnections { content: lines } } } impl Iterator for RawConnections { type Item = RawConnection; fn next(&mut self) -> Option { self.content.pop() } } #[cfg(test)] mod tests { use super::*; const IPV6_LINE_RAW_OUTPUT: &str = "ProcessName 29266 user 9u IPv6 0x5d53dfe5445cee01 0t0 UDP [fe80:4::aede:48ff:fe00:1122]:1111->[fe80:4::aede:48ff:fe33:4455]:2222"; const LINE_RAW_OUTPUT: &str = "ProcessName 29266 user 39u IPv4 0x28ffb9c0021196bf 0t0 UDP 192.168.0.1:1111->198.252.206.25:2222"; const FULL_RAW_OUTPUT: &str = r#" com.apple 590 etoledom 193u IPv4 0x28ffb9c041115627 0t0 TCP 192.168.1.37:60298->31.13.83.36:443 (ESTABLISHED) com.apple 590 etoledom 198u IPv4 0x28ffb9c04110ea8f 0t0 TCP 192.168.1.37:60299->31.13.83.8:443 (ESTABLISHED) com.apple 590 etoledom 203u IPv4 0x28ffb9c04110ea8f 0t0 TCP 192.168.1.37:60299->31.13.83.8:443 (ESTABLISHED) com.apple 590 etoledom 204u IPv4 0x28ffb9c04111253f 0t0 TCP 192.168.1.37:60374->140.82.114.26:443 "#; #[test] fn test_iterator_multiline() { let iterator = RawConnections::new(String::from(FULL_RAW_OUTPUT)); let connections: Vec = iterator.collect(); assert_eq!(connections.len(), 4); } #[test] fn test_raw_connection_is_created_from_raw_output_ipv4() { test_raw_connection_is_created_from_raw_output(LINE_RAW_OUTPUT); } #[test] fn test_raw_connection_is_created_from_raw_output_ipv6() { test_raw_connection_is_created_from_raw_output(IPV6_LINE_RAW_OUTPUT); } fn test_raw_connection_is_created_from_raw_output(raw_output: &str) { let connection = RawConnection::new(raw_output); assert!(connection.is_some()); } #[test] fn test_raw_connection_is_not_created_from_wrong_raw_output() { let connection = RawConnection::new("not a process"); assert!(connection.is_none()); } #[test] fn test_raw_connection_parse_local_port_ipv4() { test_raw_connection_parse_local_port(LINE_RAW_OUTPUT); } #[test] fn test_raw_connection_parse_local_port_ipv6() { test_raw_connection_parse_local_port(IPV6_LINE_RAW_OUTPUT); } fn test_raw_connection_parse_local_port(raw_output: &str) { let connection = RawConnection::new(raw_output).unwrap(); assert_eq!(connection.get_local_port(), Some(1111)); } #[test] fn test_raw_connection_parse_protocol_ipv4() { test_raw_connection_parse_protocol(LINE_RAW_OUTPUT); } #[test] fn test_raw_connection_parse_protocol_ipv6() { test_raw_connection_parse_protocol(IPV6_LINE_RAW_OUTPUT); } fn test_raw_connection_parse_protocol(raw_line: &str) { let connection = RawConnection::new(raw_line).unwrap(); assert_eq!(connection.get_protocol(), Some(Protocol::Udp)); } #[test] fn test_raw_connection_parse_process_name_ipv4() { test_raw_connection_parse_process_name(LINE_RAW_OUTPUT); } #[test] fn test_raw_connection_parse_process_name_ipv6() { test_raw_connection_parse_process_name(IPV6_LINE_RAW_OUTPUT); } fn test_raw_connection_parse_process_name(raw_line: &str) { let connection = RawConnection::new(raw_line).unwrap(); assert_eq!(connection.proc_info.name, String::from("ProcessName")); } } ================================================ FILE: src/os/mod.rs ================================================ #[cfg(any(target_os = "android", target_os = "linux"))] mod linux; #[cfg(any(target_os = "macos", target_os = "freebsd"))] mod lsof; #[cfg(any(target_os = "macos", target_os = "freebsd"))] mod lsof_utils; #[cfg(target_os = "windows")] mod windows; mod errors; pub(crate) mod shared; pub use shared::*; ================================================ FILE: src/os/shared.rs ================================================ use std::{ io::{self, ErrorKind, Write}, net::Ipv4Addr, time::{self, Duration}, }; use crossterm::event::{poll, read, Event}; use eyre::{bail, eyre}; use itertools::Itertools; use log::{debug, warn}; use pnet::datalink::{self, Channel::Ethernet, Config, DataLinkReceiver, NetworkInterface}; use tokio::runtime::Runtime; use crate::{network::dns, os::errors::GetInterfaceError, OsInputOutput}; #[cfg(any(target_os = "android", target_os = "linux"))] use crate::os::linux::get_open_sockets; #[cfg(any(target_os = "macos", target_os = "freebsd"))] use crate::os::lsof::get_open_sockets; #[cfg(target_os = "windows")] use crate::os::windows::get_open_sockets; #[derive(Clone, Debug, Default, Hash, PartialEq, Eq)] pub struct ProcessInfo { pub name: String, pub pid: u32, } impl ProcessInfo { pub fn new(name: &str, pid: u32) -> Self { Self { name: name.to_string(), pid, } } } /// Poll timeout for terminal events. /// This allows the event loop to periodically check the `running` flag /// for graceful shutdown on SIGINT. const POLL_TIMEOUT: Duration = Duration::from_millis(100); pub struct TerminalEvents; impl Iterator for TerminalEvents { type Item = Event; /// Returns the next terminal event, or `None` if no event is available /// within the poll timeout. /// /// Note: `None` here means "no event right now", not "iteration complete". /// The consumer should use `while running` instead of `for evt in ...`. fn next(&mut self) -> Option { match poll(POLL_TIMEOUT) { Ok(true) => read().ok(), Ok(false) | Err(_) => None, } } } pub(crate) fn get_datalink_channel( interface: &NetworkInterface, ) -> Result, GetInterfaceError> { let config = Config { read_timeout: Some(time::Duration::new(1, 0)), read_buffer_size: 65536, ..Default::default() }; match datalink::channel(interface, config) { Ok(Ethernet(_tx, rx)) => Ok(rx), Ok(_) => Err(GetInterfaceError::OtherError(format!( "{}: Unsupported interface type", interface.name ))), Err(e) => match e.kind() { ErrorKind::PermissionDenied => Err(GetInterfaceError::PermissionError( interface.name.to_owned(), )), _ => Err(GetInterfaceError::OtherError(format!( "{}: {e}", &interface.name ))), }, } } fn get_interface(interface_name: &str) -> Option { datalink::interfaces() .into_iter() .find(|iface| iface.name == interface_name) } fn create_write_to_stdout() -> Box { let mut stdout = io::stdout(); Box::new({ move |output| match writeln!(stdout, "{output}") { Ok(_) => (), Err(e) if e.kind() == ErrorKind::BrokenPipe => { // A process that was listening to bandwhich stdout has exited // We can't do much here, lets just exit as well std::process::exit(0) } Err(e) => panic!("Failed to write to stdout: {e}"), } }) } pub fn get_input( interface_name: Option<&str>, resolve: bool, dns_server: Option, ) -> eyre::Result { // get the user's requested interface, if any // IDEA: allow requesting multiple interfaces let requested_interfaces = interface_name .map(|name| get_interface(name).ok_or_else(|| eyre!("Cannot find interface {name}"))) .transpose()? .map(|interface| vec![interface]); // take the user's requested interfaces (or all interfaces), and filter for up ones let available_interfaces = requested_interfaces .unwrap_or_else(datalink::interfaces) .into_iter() .filter(|interface| { // see https://github.com/libpnet/libpnet/issues/564 let keep = if cfg!(target_os = "windows") { !interface.ips.is_empty() } else { interface.is_up() && !interface.ips.is_empty() }; if !keep { debug!("{} is down. Skipping it.", interface.name); } keep }) .collect_vec(); // bail if no interfaces are up if available_interfaces.is_empty() { bail!("Failed to find any network interface to listen on."); } // try to get a frame receiver for each interface let interfaces_with_frames_res = available_interfaces .into_iter() .map(|interface| { let frames_res = get_datalink_channel(&interface); (interface, frames_res) }) .collect_vec(); // warn for all frame receivers we failed to acquire interfaces_with_frames_res .iter() .filter_map(|(interface, frames_res)| frames_res.as_ref().err().map(|err| (interface, err))) .for_each(|(interface, err)| { warn!( "Failed to acquire a frame receiver for {}: {err}", interface.name ) }); // bail if all of them fail // note that `Iterator::all` returns `true` for an empty iterator, so it is important to handle // that failure mode separately, which we already have if interfaces_with_frames_res .iter() .all(|(_, frames)| frames.is_err()) { let (permission_err_interfaces, other_errs) = interfaces_with_frames_res.iter().fold( (vec![], vec![]), |(mut perms, mut others), (_, res)| { match res { Ok(_) => (), Err(GetInterfaceError::PermissionError(interface)) => { perms.push(interface.as_str()) } Err(GetInterfaceError::OtherError(err)) => others.push(err.as_str()), } (perms, others) }, ); let err_msg = match (permission_err_interfaces.is_empty(), other_errs.is_empty()) { (false, false) => format!( "\n\n{}: {}\nAdditional errors:\n{}", permission_err_interfaces.join(", "), eperm_message(), other_errs.join("\n") ), (false, true) => format!( "\n\n{}: {}", permission_err_interfaces.join(", "), eperm_message() ), (true, false) => format!("\n\n{}", other_errs.join("\n")), (true, true) => unreachable!("Found no errors in error handling code path."), }; bail!(err_msg); } // filter out interfaces for which we failed to acquire a frame receiver let interfaces_with_frames = interfaces_with_frames_res .into_iter() .filter_map(|(interface, res)| res.ok().map(|frames| (interface, frames))) .collect(); let dns_client = if resolve { let runtime = Runtime::new()?; let resolver = runtime .block_on(dns::Resolver::new(dns_server)) .map_err(|err| { eyre!("Could not initialize the DNS resolver. Are you offline?\n\nReason: {err}") })?; let dns_client = dns::Client::new(resolver, runtime)?; Some(dns_client) } else { None }; let write_to_stdout = create_write_to_stdout(); Ok(OsInputOutput { interfaces_with_frames, get_open_sockets, terminal_events: Box::new(TerminalEvents), dns_client, write_to_stdout, }) } #[inline] #[cfg(any(target_os = "macos", target_os = "freebsd"))] fn eperm_message() -> &'static str { "Insufficient permissions to listen on network interface(s). Try running with sudo." } #[inline] #[cfg(any(target_os = "android", target_os = "linux"))] fn eperm_message() -> &'static str { r#" Insufficient permissions to listen on network interface(s). You can work around this issue like this: * Try running `bandwhich` with `sudo` * Build a `setcap(8)` wrapper for `bandwhich` with the following rules: `cap_sys_ptrace,cap_dac_read_search,cap_net_raw,cap_net_admin+ep` "# } #[inline] #[cfg(target_os = "windows")] fn eperm_message() -> &'static str { "Insufficient permissions to listen on network interface(s). Try running with administrator rights." } ================================================ FILE: src/os/windows.rs ================================================ use std::collections::HashMap; use netstat2::*; use sysinfo::{Pid, ProcessesToUpdate, System}; use crate::{ network::{LocalSocket, Protocol}, os::ProcessInfo, OpenSockets, }; pub(crate) fn get_open_sockets() -> OpenSockets { let mut open_sockets = HashMap::new(); let mut sysinfo = System::new_all(); sysinfo.refresh_processes(ProcessesToUpdate::All, true); let af_flags = AddressFamilyFlags::IPV4 | AddressFamilyFlags::IPV6; let proto_flags = ProtocolFlags::TCP | ProtocolFlags::UDP; let sockets_info = get_sockets_info(af_flags, proto_flags); if let Ok(sockets_info) = sockets_info { for si in sockets_info { let proc_info = si .associated_pids .into_iter() .find_map(|pid| sysinfo.process(Pid::from_u32(pid))) .map(|p| ProcessInfo::new(&p.name().to_string_lossy(), p.pid().as_u32())) .unwrap_or_default(); match si.protocol_socket_info { ProtocolSocketInfo::Tcp(tcp_si) => { open_sockets.insert( LocalSocket { ip: tcp_si.local_addr, port: tcp_si.local_port, protocol: Protocol::Tcp, }, proc_info, ); } ProtocolSocketInfo::Udp(udp_si) => { open_sockets.insert( LocalSocket { ip: udp_si.local_addr, port: udp_si.local_port, protocol: Protocol::Udp, }, proc_info, ); } } } } OpenSockets { sockets_to_procs: open_sockets, } } ================================================ FILE: src/tests/cases/mod.rs ================================================ pub mod raw_mode; pub mod test_utils; #[cfg(feature = "ui_test")] pub mod ui; ================================================ FILE: src/tests/cases/raw_mode.rs ================================================ use std::{ collections::HashMap, net::IpAddr, sync::{Arc, Mutex}, }; use insta::assert_snapshot; use once_cell::sync::Lazy; use packet_builder::*; use pnet::{datalink::DataLinkReceiver, packet::Packet}; use regex::Regex; use crate::{ start, tests::{ cases::test_utils::{ build_tcp_packet, opts_raw, os_input_output_dns, os_input_output_stdout, test_backend_factory, }, fakes::{create_fake_dns_client, NetworkFrames}, }, Opt, }; fn build_ip_tcp_packet( source_ip: &str, destination_ip: &str, source_port: u16, destination_port: u16, payload: &'static [u8], ) -> Vec { let mut pkt_buf = [0u8; 1500]; let pkt = packet_builder!( pkt_buf, ipv4({set_source => ipv4addr!(source_ip), set_destination => ipv4addr!(destination_ip) }) / tcp({set_source => source_port, set_destination => destination_port }) / payload(payload) ); pkt.packet().to_vec() } fn format_raw_stdout(raw: &Mutex>) -> String { static TIMESTAMP_MATCHER: Lazy = Lazy::new(|| Regex::new(r"<\d+>").unwrap()); let stdout = raw.lock().unwrap(); TIMESTAMP_MATCHER .replace_all(std::str::from_utf8(&stdout).unwrap(), "") .into() } #[test] fn one_ip_packet_of_traffic() { let network_frames = vec![NetworkFrames::new(vec![Some(build_ip_tcp_packet( "10.0.0.2", "1.1.1.1", 443, 12345, b"I am a fake tcp packet", ))]) as Box]; let (_, _, backend) = test_backend_factory(190, 50); let stdout = Arc::new(Mutex::new(Vec::new())); let os_input = os_input_output_stdout(network_frames, 2, Some(stdout.clone())); let opts = opts_raw(); start(backend, os_input, opts); assert_snapshot!(format_raw_stdout(&stdout)); } #[test] fn one_packet_of_traffic() { let network_frames = vec![NetworkFrames::new(vec![Some(build_tcp_packet( "10.0.0.2", "1.1.1.1", 443, 12345, b"I am a fake tcp packet", ))]) as Box]; let (_, _, backend) = test_backend_factory(190, 50); let stdout = Arc::new(Mutex::new(Vec::new())); let os_input = os_input_output_stdout(network_frames, 2, Some(stdout.clone())); let opts = opts_raw(); start(backend, os_input, opts); assert_snapshot!(format_raw_stdout(&stdout)); } #[test] fn bi_directional_traffic() { let network_frames = vec![NetworkFrames::new(vec![ Some(build_tcp_packet( "10.0.0.2", "1.1.1.1", 443, 12345, b"I am a fake tcp upload packet", )), Some(build_tcp_packet( "1.1.1.1", "10.0.0.2", 12345, 443, b"I am a fake tcp download packet", )), ]) as Box]; let (_, _, backend) = test_backend_factory(190, 50); let stdout = Arc::new(Mutex::new(Vec::new())); let os_input = os_input_output_stdout(network_frames, 2, Some(stdout.clone())); let opts = opts_raw(); start(backend, os_input, opts); assert_snapshot!(format_raw_stdout(&stdout)); } #[test] fn multiple_packets_of_traffic_from_different_connections() { let network_frames = vec![NetworkFrames::new(vec![ Some(build_tcp_packet( "2.2.2.2", "10.0.0.2", 12345, 443, b"I have come from 2.2.2.2", )), Some(build_tcp_packet( "2.2.2.2", "10.0.0.2", 54321, 4434, b"I come from 2.2.2.2", )), ]) as Box]; let (_, _, backend) = test_backend_factory(190, 50); let stdout = Arc::new(Mutex::new(Vec::new())); let os_input = os_input_output_stdout(network_frames, 2, Some(stdout.clone())); let opts = opts_raw(); start(backend, os_input, opts); assert_snapshot!(format_raw_stdout(&stdout)); } #[test] fn multiple_packets_of_traffic_from_single_connection() { let network_frames = vec![NetworkFrames::new(vec![ Some(build_tcp_packet( "1.1.1.1", "10.0.0.2", 12345, 443, b"I have come from 1.1.1.1", )), Some(build_tcp_packet( "1.1.1.1", "10.0.0.2", 12345, 443, b"I've come from 1.1.1.1 too!", )), ]) as Box]; let (_, _, backend) = test_backend_factory(190, 50); let stdout = Arc::new(Mutex::new(Vec::new())); let os_input = os_input_output_stdout(network_frames, 2, Some(stdout.clone())); let opts = opts_raw(); start(backend, os_input, opts); assert_snapshot!(format_raw_stdout(&stdout)); } #[test] fn one_process_with_multiple_connections() { let network_frames = vec![NetworkFrames::new(vec![ Some(build_tcp_packet( "1.1.1.1", "10.0.0.2", 12345, 443, b"I have come from 1.1.1.1", )), Some(build_tcp_packet( "1.1.1.1", "10.0.0.2", 12346, 443, b"Funny that, I'm from 1.1.1.1", )), ]) as Box]; let (_, _, backend) = test_backend_factory(190, 50); let stdout = Arc::new(Mutex::new(Vec::new())); let os_input = os_input_output_stdout(network_frames, 2, Some(stdout.clone())); let opts = opts_raw(); start(backend, os_input, opts); assert_snapshot!(format_raw_stdout(&stdout)); } #[test] fn multiple_processes_with_multiple_connections() { let network_frames = vec![NetworkFrames::new(vec![ Some(build_tcp_packet( "1.1.1.1", "10.0.0.2", 12345, 443, b"I have come from 1.1.1.1", )), Some(build_tcp_packet( "3.3.3.3", "10.0.0.2", 1337, 4435, b"Greetings traveller, I'm from 3.3.3.3", )), Some(build_tcp_packet( "2.2.2.2", "10.0.0.2", 54321, 4434, b"You know, 2.2.2.2 is really nice!", )), Some(build_tcp_packet( "4.4.4.4", "10.0.0.2", 1337, 4432, b"I'm partial to 4.4.4.4", )), ]) as Box]; let (_, _, backend) = test_backend_factory(190, 50); let stdout = Arc::new(Mutex::new(Vec::new())); let os_input = os_input_output_stdout(network_frames, 2, Some(stdout.clone())); let opts = opts_raw(); start(backend, os_input, opts); assert_snapshot!(format_raw_stdout(&stdout)); } #[test] fn multiple_connections_from_remote_address() { let network_frames = vec![NetworkFrames::new(vec![ Some(build_tcp_packet( "1.1.1.1", "10.0.0.2", 12345, 443, b"I have come from 1.1.1.1", )), Some(build_tcp_packet( "1.1.1.1", "10.0.0.2", 12346, 443, b"Me too, but on a different port", )), ]) as Box]; let (_, _, backend) = test_backend_factory(190, 50); let stdout = Arc::new(Mutex::new(Vec::new())); let os_input = os_input_output_stdout(network_frames, 2, Some(stdout.clone())); let opts = opts_raw(); start(backend, os_input, opts); assert_snapshot!(format_raw_stdout(&stdout)); } #[test] fn sustained_traffic_from_one_process() { let network_frames = vec![NetworkFrames::new(vec![ Some(build_tcp_packet( "1.1.1.1", "10.0.0.2", 12345, 443, b"I have come from 1.1.1.1", )), None, // sleep Some(build_tcp_packet( "1.1.1.1", "10.0.0.2", 12345, 443, b"Same here, but one second later", )), ]) as Box]; let (_, _, backend) = test_backend_factory(190, 50); let stdout = Arc::new(Mutex::new(Vec::new())); let os_input = os_input_output_stdout(network_frames, 3, Some(stdout.clone())); let opts = opts_raw(); start(backend, os_input, opts); assert_snapshot!(format_raw_stdout(&stdout)); } #[test] fn sustained_traffic_from_multiple_processes() { let network_frames = vec![NetworkFrames::new(vec![ Some(build_tcp_packet( "1.1.1.1", "10.0.0.2", 12345, 443, b"I have come from 1.1.1.1", )), Some(build_tcp_packet( "3.3.3.3", "10.0.0.2", 1337, 4435, b"I come from 3.3.3.3", )), None, // sleep Some(build_tcp_packet( "1.1.1.1", "10.0.0.2", 12345, 443, b"I have come from 1.1.1.1 one second later", )), Some(build_tcp_packet( "3.3.3.3", "10.0.0.2", 1337, 4435, b"I come 3.3.3.3 one second later", )), ]) as Box]; let (_, _, backend) = test_backend_factory(190, 50); let stdout = Arc::new(Mutex::new(Vec::new())); let os_input = os_input_output_stdout(network_frames, 3, Some(stdout.clone())); let opts = opts_raw(); start(backend, os_input, opts); assert_snapshot!(format_raw_stdout(&stdout)); } #[test] fn sustained_traffic_from_multiple_processes_bi_directional() { let network_frames = vec![NetworkFrames::new(vec![ Some(build_tcp_packet( "10.0.0.2", "3.3.3.3", 4435, 1337, b"omw to 3.3.3.3", )), Some(build_tcp_packet( "3.3.3.3", "10.0.0.2", 1337, 4435, b"I was just there!", )), Some(build_tcp_packet( "1.1.1.1", "10.0.0.2", 12345, 443, b"Is it nice there? I think 1.1.1.1 is dull", )), Some(build_tcp_packet( "10.0.0.2", "1.1.1.1", 443, 12345, b"Well, I heard 1.1.1.1 is all the rage", )), None, // sleep Some(build_tcp_packet( "10.0.0.2", "3.3.3.3", 4435, 1337, b"Wait for me!", )), Some(build_tcp_packet( "3.3.3.3", "10.0.0.2", 1337, 4435, b"They're waiting for you...", )), Some(build_tcp_packet( "1.1.1.1", "10.0.0.2", 12345, 443, b"1.1.1.1 forever!", )), Some(build_tcp_packet( "10.0.0.2", "1.1.1.1", 443, 12345, b"10.0.0.2 forever!", )), ]) as Box]; let (_, _, backend) = test_backend_factory(190, 50); let stdout = Arc::new(Mutex::new(Vec::new())); let os_input = os_input_output_stdout(network_frames, 3, Some(stdout.clone())); let opts = opts_raw(); start(backend, os_input, opts); assert_snapshot!(format_raw_stdout(&stdout)); } #[test] fn traffic_with_host_names() { let network_frames = vec![NetworkFrames::new(vec![ Some(build_tcp_packet( "10.0.0.2", "3.3.3.3", 4435, 1337, b"omw to 3.3.3.3", )), Some(build_tcp_packet( "3.3.3.3", "10.0.0.2", 1337, 4435, b"I was just there!", )), Some(build_tcp_packet( "1.1.1.1", "10.0.0.2", 12345, 443, b"Is it nice there? I think 1.1.1.1 is dull", )), Some(build_tcp_packet( "10.0.0.2", "1.1.1.1", 443, 12345, b"Well, I heard 1.1.1.1 is all the rage", )), None, // sleep Some(build_tcp_packet( "10.0.0.2", "3.3.3.3", 4435, 1337, b"Wait for me!", )), Some(build_tcp_packet( "3.3.3.3", "10.0.0.2", 1337, 4435, b"They're waiting for you...", )), Some(build_tcp_packet( "1.1.1.1", "10.0.0.2", 12345, 443, b"1.1.1.1 forever!", )), Some(build_tcp_packet( "10.0.0.2", "1.1.1.1", 443, 12345, b"10.0.0.2 forever!", )), ]) as Box]; let (_, _, backend) = test_backend_factory(190, 50); let mut ips_to_hostnames = HashMap::new(); ips_to_hostnames.insert( IpAddr::V4("1.1.1.1".parse().unwrap()), String::from("one.one.one.one"), ); ips_to_hostnames.insert( IpAddr::V4("3.3.3.3".parse().unwrap()), String::from("three.three.three.three"), ); ips_to_hostnames.insert( IpAddr::V4("10.0.0.2".parse().unwrap()), String::from("i-like-cheese.com"), ); let dns_client = create_fake_dns_client(ips_to_hostnames); let stdout = Arc::new(Mutex::new(Vec::new())); let os_input = os_input_output_dns(network_frames, 3, Some(stdout.clone()), dns_client); let opts = opts_raw(); start(backend, os_input, opts); assert_snapshot!(format_raw_stdout(&stdout)); } #[test] fn no_resolve_mode() { let network_frames = vec![NetworkFrames::new(vec![ Some(build_tcp_packet( "10.0.0.2", "3.3.3.3", 4435, 1337, b"omw to 3.3.3.3", )), Some(build_tcp_packet( "3.3.3.3", "10.0.0.2", 1337, 4435, b"I was just there!", )), Some(build_tcp_packet( "1.1.1.1", "10.0.0.2", 12345, 443, b"Is it nice there? I think 1.1.1.1 is dull", )), Some(build_tcp_packet( "10.0.0.2", "1.1.1.1", 443, 12345, b"Well, I heard 1.1.1.1 is all the rage", )), None, // sleep Some(build_tcp_packet( "10.0.0.2", "3.3.3.3", 4435, 1337, b"Wait for me!", )), Some(build_tcp_packet( "3.3.3.3", "10.0.0.2", 1337, 4435, b"They're waiting for you...", )), Some(build_tcp_packet( "1.1.1.1", "10.0.0.2", 12345, 443, b"1.1.1.1 forever!", )), Some(build_tcp_packet( "10.0.0.2", "1.1.1.1", 443, 12345, b"10.0.0.2 forever!", )), ]) as Box]; let (_, _, backend) = test_backend_factory(190, 50); let mut ips_to_hostnames = HashMap::new(); ips_to_hostnames.insert( IpAddr::V4("1.1.1.1".parse().unwrap()), String::from("one.one.one.one"), ); ips_to_hostnames.insert( IpAddr::V4("3.3.3.3".parse().unwrap()), String::from("three.three.three.three"), ); ips_to_hostnames.insert( IpAddr::V4("10.0.0.2".parse().unwrap()), String::from("i-like-cheese.com"), ); let stdout = Arc::new(Mutex::new(Vec::new())); let os_input = os_input_output_dns(network_frames, 3, Some(stdout.clone()), None); let opts = Opt { interface: Some(String::from("interface_name")), raw: true, no_resolve: true, ..Default::default() }; start(backend, os_input, opts); assert_snapshot!(format_raw_stdout(&stdout)); } ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__raw_mode__bi_directional_traffic.snap ================================================ --- source: src/tests/cases/raw_mode.rs expression: formatted --- Refreshing: Refreshing: process: "1" up/down Bps: 24/25 connections: 1 connection: :443 => 1.1.1.1:12345 (tcp) up/down Bps: 24/25 process: "1" remote_address: 1.1.1.1 up/down Bps: 24/25 connections: 1 ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__raw_mode__multiple_connections_from_remote_address.snap ================================================ --- source: src/tests/cases/raw_mode.rs expression: formatted --- Refreshing: Refreshing: process: "1" up/down Bps: 0/47 connections: 2 connection: :443 => 1.1.1.1:12346 (tcp) up/down Bps: 0/25 process: "1" connection: :443 => 1.1.1.1:12345 (tcp) up/down Bps: 0/22 process: "1" remote_address: 1.1.1.1 up/down Bps: 0/47 connections: 2 ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__raw_mode__multiple_packets_of_traffic_from_different_connections.snap ================================================ --- source: src/tests/cases/raw_mode.rs expression: formatted --- Refreshing: Refreshing: process: "1" up/down Bps: 0/22 connections: 1 process: "4" up/down Bps: 0/19 connections: 1 connection: :443 => 2.2.2.2:12345 (tcp) up/down Bps: 0/22 process: "1" connection: :4434 => 2.2.2.2:54321 (tcp) up/down Bps: 0/19 process: "4" remote_address: 2.2.2.2 up/down Bps: 0/41 connections: 2 ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__raw_mode__multiple_packets_of_traffic_from_single_connection.snap ================================================ --- source: src/tests/cases/raw_mode.rs expression: formatted --- Refreshing: Refreshing: process: "1" up/down Bps: 0/45 connections: 1 connection: :443 => 1.1.1.1:12345 (tcp) up/down Bps: 0/45 process: "1" remote_address: 1.1.1.1 up/down Bps: 0/45 connections: 1 ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__raw_mode__multiple_processes_with_multiple_connections.snap ================================================ --- source: src/tests/cases/raw_mode.rs expression: formatted --- Refreshing: Refreshing: process: "5" up/down Bps: 0/28 connections: 1 process: "4" up/down Bps: 0/26 connections: 1 process: "1" up/down Bps: 0/22 connections: 1 process: "2" up/down Bps: 0/21 connections: 1 connection: :4435 => 3.3.3.3:1337 (tcp) up/down Bps: 0/28 process: "5" connection: :4434 => 2.2.2.2:54321 (tcp) up/down Bps: 0/26 process: "4" connection: :443 => 1.1.1.1:12345 (tcp) up/down Bps: 0/22 process: "1" connection: :4432 => 4.4.4.4:1337 (tcp) up/down Bps: 0/21 process: "2" remote_address: 3.3.3.3 up/down Bps: 0/28 connections: 1 remote_address: 2.2.2.2 up/down Bps: 0/26 connections: 1 remote_address: 1.1.1.1 up/down Bps: 0/22 connections: 1 remote_address: 4.4.4.4 up/down Bps: 0/21 connections: 1 ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__raw_mode__no_resolve_mode.snap ================================================ --- source: src/tests/cases/raw_mode.rs expression: formatted --- Refreshing: Refreshing: process: "1" up/down Bps: 28/30 connections: 1 process: "5" up/down Bps: 17/18 connections: 1 connection: :443 => 1.1.1.1:12345 (tcp) up/down Bps: 28/30 process: "1" connection: :4435 => 3.3.3.3:1337 (tcp) up/down Bps: 17/18 process: "5" remote_address: 1.1.1.1 up/down Bps: 28/30 connections: 1 remote_address: 3.3.3.3 up/down Bps: 17/18 connections: 1 Refreshing: process: "1" up/down Bps: 31/32 connections: 1 process: "5" up/down Bps: 22/27 connections: 1 connection: :443 => 1.1.1.1:12345 (tcp) up/down Bps: 31/32 process: "1" connection: :4435 => 3.3.3.3:1337 (tcp) up/down Bps: 22/27 process: "5" remote_address: 1.1.1.1 up/down Bps: 31/32 connections: 1 remote_address: 3.3.3.3 up/down Bps: 22/27 connections: 1 ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__raw_mode__one_ip_packet_of_traffic.snap ================================================ --- source: src/tests/cases/raw_mode.rs expression: formatted --- Refreshing: Refreshing: process: "1" up/down Bps: 21/0 connections: 1 connection: :443 => 1.1.1.1:12345 (tcp) up/down Bps: 21/0 process: "1" remote_address: 1.1.1.1 up/down Bps: 21/0 connections: 1 ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__raw_mode__one_packet_of_traffic.snap ================================================ --- source: src/tests/cases/raw_mode.rs expression: formatted --- Refreshing: Refreshing: process: "1" up/down Bps: 21/0 connections: 1 connection: :443 => 1.1.1.1:12345 (tcp) up/down Bps: 21/0 process: "1" remote_address: 1.1.1.1 up/down Bps: 21/0 connections: 1 ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__raw_mode__one_process_with_multiple_connections.snap ================================================ --- source: src/tests/cases/raw_mode.rs expression: formatted --- Refreshing: Refreshing: process: "1" up/down Bps: 0/46 connections: 2 connection: :443 => 1.1.1.1:12346 (tcp) up/down Bps: 0/24 process: "1" connection: :443 => 1.1.1.1:12345 (tcp) up/down Bps: 0/22 process: "1" remote_address: 1.1.1.1 up/down Bps: 0/46 connections: 2 ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__raw_mode__sustained_traffic_from_multiple_processes.snap ================================================ --- source: src/tests/cases/raw_mode.rs expression: formatted --- Refreshing: Refreshing: process: "1" up/down Bps: 0/22 connections: 1 process: "5" up/down Bps: 0/19 connections: 1 connection: :443 => 1.1.1.1:12345 (tcp) up/down Bps: 0/22 process: "1" connection: :4435 => 3.3.3.3:1337 (tcp) up/down Bps: 0/19 process: "5" remote_address: 1.1.1.1 up/down Bps: 0/22 connections: 1 remote_address: 3.3.3.3 up/down Bps: 0/19 connections: 1 Refreshing: process: "1" up/down Bps: 0/35 connections: 1 process: "5" up/down Bps: 0/30 connections: 1 connection: :443 => 1.1.1.1:12345 (tcp) up/down Bps: 0/35 process: "1" connection: :4435 => 3.3.3.3:1337 (tcp) up/down Bps: 0/30 process: "5" remote_address: 1.1.1.1 up/down Bps: 0/35 connections: 1 remote_address: 3.3.3.3 up/down Bps: 0/30 connections: 1 ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__raw_mode__sustained_traffic_from_multiple_processes_bi_directional.snap ================================================ --- source: src/tests/cases/raw_mode.rs expression: formatted --- Refreshing: Refreshing: process: "1" up/down Bps: 28/30 connections: 1 process: "5" up/down Bps: 17/18 connections: 1 connection: :443 => 1.1.1.1:12345 (tcp) up/down Bps: 28/30 process: "1" connection: :4435 => 3.3.3.3:1337 (tcp) up/down Bps: 17/18 process: "5" remote_address: 1.1.1.1 up/down Bps: 28/30 connections: 1 remote_address: 3.3.3.3 up/down Bps: 17/18 connections: 1 Refreshing: process: "1" up/down Bps: 31/32 connections: 1 process: "5" up/down Bps: 22/27 connections: 1 connection: :443 => 1.1.1.1:12345 (tcp) up/down Bps: 31/32 process: "1" connection: :4435 => 3.3.3.3:1337 (tcp) up/down Bps: 22/27 process: "5" remote_address: 1.1.1.1 up/down Bps: 31/32 connections: 1 remote_address: 3.3.3.3 up/down Bps: 22/27 connections: 1 ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__raw_mode__sustained_traffic_from_one_process.snap ================================================ --- source: src/tests/cases/raw_mode.rs expression: formatted --- Refreshing: Refreshing: process: "1" up/down Bps: 0/22 connections: 1 connection: :443 => 1.1.1.1:12345 (tcp) up/down Bps: 0/22 process: "1" remote_address: 1.1.1.1 up/down Bps: 0/22 connections: 1 Refreshing: process: "1" up/down Bps: 0/31 connections: 1 connection: :443 => 1.1.1.1:12345 (tcp) up/down Bps: 0/31 process: "1" remote_address: 1.1.1.1 up/down Bps: 0/31 connections: 1 ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__raw_mode__traffic_with_host_names.snap ================================================ --- source: src/tests/cases/raw_mode.rs expression: formatted --- Refreshing: Refreshing: process: "1" up/down Bps: 28/30 connections: 1 process: "5" up/down Bps: 17/18 connections: 1 connection: :443 => 1.1.1.1:12345 (tcp) up/down Bps: 28/30 process: "1" connection: :4435 => 3.3.3.3:1337 (tcp) up/down Bps: 17/18 process: "5" remote_address: 1.1.1.1 up/down Bps: 28/30 connections: 1 remote_address: 3.3.3.3 up/down Bps: 17/18 connections: 1 Refreshing: process: "1" up/down Bps: 31/32 connections: 1 process: "5" up/down Bps: 22/27 connections: 1 connection: :443 => one.one.one.one:12345 (tcp) up/down Bps: 31/32 process: "1" connection: :4435 => three.three.three.three:1337 (tcp) up/down Bps: 22/27 process: "5" remote_address: one.one.one.one up/down Bps: 31/32 connections: 1 remote_address: three.three.three.three up/down Bps: 22/27 connections: 1 ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__basic_only_addresses.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR) --- IF: interface_name | Total Rate (Up / Down): 0.00B / 0.00B ┌Utilization by remote address───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │Remote Address Connections Rate (Up / Down) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Press to pause. Use to rearrange tables. (DNS queries hidden). ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__basic_only_connections.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR) --- IF: interface_name | Total Rate (Up / Down): 0.00B / 0.00B ┌Utilization by connection───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │Connection Process Rate (Up / Down) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Press to pause. Use to rearrange tables. (DNS queries hidden). ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__basic_only_processes.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR) --- IF: interface_name | Total Rate (Up / Down): 0.00B / 0.00B ┌Utilization by process name─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │Process PID Connections Rate (Up / Down) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Press to pause. Use to rearrange tables. (DNS queries hidden). ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__basic_processes_with_dns_queries.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR) --- IF: interface_name | Total Rate (Up / Down): 0.00B / 0.00B ┌Utilization by process name─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │Process PID Connections Rate (Up / Down) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Press to pause. Use to rearrange tables. (DNS queries shown). ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__basic_startup-2.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_events.lock().unwrap().as_slice() --- [ Clear, HideCursor, Draw, HideCursor, Flush, ShowCursor, ] ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__basic_startup.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR) --- IF: interface_name | Total Rate (Up / Down): 0.00B / 0.00B ┌Utilization by process name──────────────────────────────────────────────────────────────────┐┌Utilization by remote address────────────────────────────────────────────────────────────────┐ │Process PID Connections Rate (Up / Down) ││Remote Address Connections Rate (Up / Down) │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ └─────────────────────────────────────────────────────────────────────────────────────────────┘└─────────────────────────────────────────────────────────────────────────────────────────────┘ ┌Utilization by connection───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │Connection Process Rate (Up / Down) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Press to pause. Use to rearrange tables. (DNS queries hidden). ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__bi_directional_traffic-2.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_events.lock().unwrap().as_slice() --- [ Clear, HideCursor, Draw, HideCursor, Flush, Draw, HideCursor, Flush, ShowCursor, ] ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__bi_directional_traffic.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR) --- IF: interface_name | Total Rate (Up / Down): 0.00B / 0.00B ┌Utilization by process name──────────────────────────────────────────────────────────────────┐┌Utilization by remote address────────────────────────────────────────────────────────────────┐ │Process PID Connections Rate (Up / Down) ││Remote Address Connections Rate (Up / Down) │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ └─────────────────────────────────────────────────────────────────────────────────────────────┘└─────────────────────────────────────────────────────────────────────────────────────────────┘ ┌Utilization by connection───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │Connection Process Rate (Up / Down) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Press to pause. Use to rearrange tables. (DNS queries hidden). --- SECTION SEPARATOR --- 24. 0B / 25.00B 1 1 1 24.00B / 25.00B 1.1.1.1 1 24.00B / 25.00B :443 => 1.1.1.1:12345 (tcp) 1 24.00B / 25.00B ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__layout-full-width-under-30-height-draw_events.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR) --- IF: interface_name | Total Rate (Up / Down): 0.00B / 0.00B ┌Utilization by process name──────────────────────────────────────────────────────────────────┐┌Utilization by remote address────────────────────────────────────────────────────────────────┐ │Process PID Connections Rate (Up / Down) ││Remote Address Connections Rate (Up / Down) │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ └─────────────────────────────────────────────────────────────────────────────────────────────┘└─────────────────────────────────────────────────────────────────────────────────────────────┘ Press to pause. Use to rearrange tables. (DNS queries hidden). --- SECTION SEPARATOR --- 98. 0B 5 5 1 0.00B / 28.00B 3.3.3.3 1 0.00B / 28.00B 4 4 1 0.00B / 26.00B 2.2.2.2 1 0.00B / 26.00B 1 1 1 0.00B / 22.00B 1.1.1.1 1 0.00B / 22.00B 2 2 1 0.00B / 21.00B 4.4.4.4 1 0.00B / 21.00B ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__layout-full-width-under-30-height-events.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_events.lock().unwrap().as_slice() --- [ Clear, HideCursor, Draw, HideCursor, Flush, Draw, HideCursor, Flush, ShowCursor, ] ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__layout-under-120-width-full-height-draw_events.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR) --- IF: interface_name | Total Rate (Up / Down): 0.00B / 0.00B ┌Utilization by process name──────────────────────────────────────────────────────────────────────────────────────────┐ │Process PID Connections Rate (Up / Down) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ ┌Utilization by remote address────────────────────────────────────────────────────────────────────────────────────────┐ │Remote Address Connections Rate (Up / Down) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Press to pause. Use to rearrange tables. (DNS queries hidden). --- SECTION SEPARATOR --- 98. 0B 5 5 1 0.00B / 28.00B 4 4 1 0.00B / 26.00B 1 1 1 0.00B / 22.00B 2 2 1 0.00B / 21.00B 3.3.3.3 1 0.00B / 28.00B 2.2.2.2 1 0.00B / 26.00B 1.1.1.1 1 0.00B / 22.00B 4.4.4.4 1 0.00B / 21.00B ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__layout-under-120-width-full-height-events.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_events.lock().unwrap().as_slice() --- [ Clear, HideCursor, Draw, HideCursor, Flush, Draw, HideCursor, Flush, ShowCursor, ] ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__layout-under-120-width-under-30-height-draw_events.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR) --- IF: interface_name | Total Rate (Up / Down): 0.00B / 0.00B ┌Utilization by process name──────────────────────────────────────────────────────────────────────────────────────────┐ │Process PID Connections Rate (Up / Down) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Press to pause. Use to rearrange tables. (DNS queries hidden). --- SECTION SEPARATOR --- 98. 0B 5 5 1 0.00B / 28.00B 4 4 1 0.00B / 26.00B 1 1 1 0.00B / 22.00B 2 2 1 0.00B / 21.00B ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__layout-under-120-width-under-30-height-events.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_events.lock().unwrap().as_slice() --- [ Clear, HideCursor, Draw, HideCursor, Flush, Draw, HideCursor, Flush, ShowCursor, ] ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__layout-under-50-width-under-50-height-draw_events.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR) --- IF: interface_name | Total Rate (Up / Down): 0.00B ┌Utilization by process name─────────────────────┐ │Process Rate (Up / Down) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └────────────────────────────────────────────────┘ ┌Utilization by remote address───────────────────┐ │Remote Address Rate (Up / Down) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └────────────────────────────────────────────────┘ Press to pause. --- SECTION SEPARATOR --- 5 0.00B / 28.00B 4 0.00B / 26.00B 1 0.00B / 22.00B 2 0.00B / 21.00B 3.3.3.3 0.00B / 28.00B 2.2.2.2 0.00B / 26.00B 1.1.1.1 0.00B / 22.00B 4.4.4.4 0.00B / 21.00B ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__layout-under-50-width-under-50-height-events.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_events.lock().unwrap().as_slice() --- [ Clear, HideCursor, Draw, HideCursor, Flush, Draw, HideCursor, Flush, ShowCursor, ] ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__layout-under-70-width-under-30-height-draw_events.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR) --- IF: interface_name | Total Rate (Up / Down): 0.00B / 0.00B ┌Utilization by process name────────────────────────────────────────┐ │Process Connections Rate (Up / Down) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └───────────────────────────────────────────────────────────────────┘ Press to pause. Use to rearrange tables. --- SECTION SEPARATOR --- 98. 0B 5 1 0.00B / 28.00B 4 1 0.00B / 26.00B 1 1 0.00B / 22.00B 2 1 0.00B / 21.00B ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__layout-under-70-width-under-30-height-events.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_events.lock().unwrap().as_slice() --- [ Clear, HideCursor, Draw, HideCursor, Flush, Draw, HideCursor, Flush, ShowCursor, ] ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__multiple_connections_from_remote_address-2.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_events.lock().unwrap().as_slice() --- [ Clear, HideCursor, Draw, HideCursor, Flush, Draw, HideCursor, Flush, ShowCursor, ] ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__multiple_connections_from_remote_address.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR) --- IF: interface_name | Total Rate (Up / Down): 0.00B / 0.00B ┌Utilization by process name──────────────────────────────────────────────────────────────────┐┌Utilization by remote address────────────────────────────────────────────────────────────────┐ │Process PID Connections Rate (Up / Down) ││Remote Address Connections Rate (Up / Down) │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ └─────────────────────────────────────────────────────────────────────────────────────────────┘└─────────────────────────────────────────────────────────────────────────────────────────────┘ ┌Utilization by connection───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │Connection Process Rate (Up / Down) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Press to pause. Use to rearrange tables. (DNS queries hidden). --- SECTION SEPARATOR --- 47. 0B 1 1 2 0.00B / 47.00B 1.1.1.1 2 0.00B / 47.00B :443 => 1.1.1.1:12346 (tcp) 1 0.00B / 25.00B :443 => 1.1.1.1:12345 (tcp) 1 0.00B / 22.00B ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__multiple_packets_of_traffic_from_different_connections-2.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_events.lock().unwrap().as_slice() --- [ Clear, HideCursor, Draw, HideCursor, Flush, Draw, HideCursor, Flush, ShowCursor, ] ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__multiple_packets_of_traffic_from_different_connections.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR) --- IF: interface_name | Total Rate (Up / Down): 0.00B / 0.00B ┌Utilization by process name──────────────────────────────────────────────────────────────────┐┌Utilization by remote address────────────────────────────────────────────────────────────────┐ │Process PID Connections Rate (Up / Down) ││Remote Address Connections Rate (Up / Down) │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ └─────────────────────────────────────────────────────────────────────────────────────────────┘└─────────────────────────────────────────────────────────────────────────────────────────────┘ ┌Utilization by connection───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │Connection Process Rate (Up / Down) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Press to pause. Use to rearrange tables. (DNS queries hidden). --- SECTION SEPARATOR --- 41. 0B 1 1 1 0.00B / 22.00B 2.2.2.2 2 0.00B / 41.00B 4 4 1 0.00B / 19.00B :443 => 2.2.2.2:12345 (tcp) 1 0.00B / 22.00B :4434 => 2.2.2.2:54321 (tcp) 4 0.00B / 19.00B ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__multiple_packets_of_traffic_from_single_connection-2.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_events.lock().unwrap().as_slice() --- [ Clear, HideCursor, Draw, HideCursor, Flush, Draw, HideCursor, Flush, ShowCursor, ] ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__multiple_packets_of_traffic_from_single_connection.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR) --- IF: interface_name | Total Rate (Up / Down): 0.00B / 0.00B ┌Utilization by process name──────────────────────────────────────────────────────────────────┐┌Utilization by remote address────────────────────────────────────────────────────────────────┐ │Process PID Connections Rate (Up / Down) ││Remote Address Connections Rate (Up / Down) │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ └─────────────────────────────────────────────────────────────────────────────────────────────┘└─────────────────────────────────────────────────────────────────────────────────────────────┘ ┌Utilization by connection───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │Connection Process Rate (Up / Down) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Press to pause. Use to rearrange tables. (DNS queries hidden). --- SECTION SEPARATOR --- 45. 0B 1 1 1 0.00B / 45.00B 1.1.1.1 1 0.00B / 45.00B :443 => 1.1.1.1:12345 (tcp) 1 0.00B / 45.00B ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__multiple_processes_with_multiple_connections-2.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_events.lock().unwrap().as_slice() --- [ Clear, HideCursor, Draw, HideCursor, Flush, Draw, HideCursor, Flush, ShowCursor, ] ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__multiple_processes_with_multiple_connections.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR) --- IF: interface_name | Total Rate (Up / Down): 0.00B / 0.00B ┌Utilization by process name──────────────────────────────────────────────────────────────────┐┌Utilization by remote address────────────────────────────────────────────────────────────────┐ │Process PID Connections Rate (Up / Down) ││Remote Address Connections Rate (Up / Down) │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ └─────────────────────────────────────────────────────────────────────────────────────────────┘└─────────────────────────────────────────────────────────────────────────────────────────────┘ ┌Utilization by connection───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │Connection Process Rate (Up / Down) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Press to pause. Use to rearrange tables. (DNS queries hidden). --- SECTION SEPARATOR --- 98. 0B 5 5 1 0.00B / 28.00B 3.3.3.3 1 0.00B / 28.00B 4 4 1 0.00B / 26.00B 2.2.2.2 1 0.00B / 26.00B 1 1 1 0.00B / 22.00B 1.1.1.1 1 0.00B / 22.00B 2 2 1 0.00B / 21.00B 4.4.4.4 1 0.00B / 21.00B :4435 => 3.3.3.3:1337 (tcp) 5 0.00B / 28.00B :4434 => 2.2.2.2:54321 (tcp) 4 0.00B / 26.00B :443 => 1.1.1.1:12345 (tcp) 1 0.00B / 22.00B :4432 => 4.4.4.4:1337 (tcp) 2 0.00B / 21.00B ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__no_resolve_mode-2.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_events.lock().unwrap().as_slice() --- [ Clear, HideCursor, Draw, HideCursor, Flush, Draw, HideCursor, Flush, Draw, HideCursor, Flush, ShowCursor, ] ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__no_resolve_mode.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR) --- IF: interface_name | Total Rate (Up / Down): 0.00B / 0.00B ┌Utilization by process name──────────────────────────────────────────────────────────────────┐┌Utilization by remote address────────────────────────────────────────────────────────────────┐ │Process PID Connections Rate (Up / Down) ││Remote Address Connections Rate (Up / Down) │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ └─────────────────────────────────────────────────────────────────────────────────────────────┘└─────────────────────────────────────────────────────────────────────────────────────────────┘ ┌Utilization by connection───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │Connection Process Rate (Up / Down) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Press to pause. Use to rearrange tables. (DNS queries hidden). --- SECTION SEPARATOR --- 45. 0B / 49.00B 1 1 1 28.00B / 30.00B 1.1.1.1 1 28.00B / 30.00B 5 5 1 17.00B / 18.00B 3.3.3.3 1 17.00B / 18.00B :443 => 1.1.1.1:12345 (tcp) 1 28.00B / 30.00B :4435 => 3.3.3.3:1337 (tcp) 5 17.00B / 18.00B --- SECTION SEPARATOR --- 53 60 31 2 31 2 22 27 22 27 31 2 22 27 ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__one_packet_of_traffic-2.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_events.lock().unwrap().as_slice() --- [ Clear, HideCursor, Draw, HideCursor, Flush, Draw, HideCursor, Flush, ShowCursor, ] ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__one_packet_of_traffic.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR) --- IF: interface_name | Total Rate (Up / Down): 0.00B / 0.00B ┌Utilization by process name──────────────────────────────────────────────────────────────────┐┌Utilization by remote address────────────────────────────────────────────────────────────────┐ │Process PID Connections Rate (Up / Down) ││Remote Address Connections Rate (Up / Down) │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ └─────────────────────────────────────────────────────────────────────────────────────────────┘└─────────────────────────────────────────────────────────────────────────────────────────────┘ ┌Utilization by connection───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │Connection Process Rate (Up / Down) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Press to pause. Use to rearrange tables. (DNS queries hidden). --- SECTION SEPARATOR --- 21. 0B / 0. 0B 1 1 1 21.00B / 0.00B 1.1.1.1 1 21.00B / 0.00B :443 => 1.1.1.1:12345 (tcp) 1 21.00B / 0.00B ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__one_process_with_multiple_connections-2.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_events.lock().unwrap().as_slice() --- [ Clear, HideCursor, Draw, HideCursor, Flush, Draw, HideCursor, Flush, ShowCursor, ] ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__one_process_with_multiple_connections.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR) --- IF: interface_name | Total Rate (Up / Down): 0.00B / 0.00B ┌Utilization by process name──────────────────────────────────────────────────────────────────┐┌Utilization by remote address────────────────────────────────────────────────────────────────┐ │Process PID Connections Rate (Up / Down) ││Remote Address Connections Rate (Up / Down) │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ └─────────────────────────────────────────────────────────────────────────────────────────────┘└─────────────────────────────────────────────────────────────────────────────────────────────┘ ┌Utilization by connection───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │Connection Process Rate (Up / Down) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Press to pause. Use to rearrange tables. (DNS queries hidden). --- SECTION SEPARATOR --- 46. 0B 1 1 2 0.00B / 46.00B 1.1.1.1 2 0.00B / 46.00B :443 => 1.1.1.1:12346 (tcp) 1 0.00B / 24.00B :443 => 1.1.1.1:12345 (tcp) 1 0.00B / 22.00B ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__pause_by_space-2.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_events.lock().unwrap().as_slice() --- [ Clear, HideCursor, Draw, HideCursor, Flush, Draw, HideCursor, Flush, Draw, HideCursor, Flush, ShowCursor, ] ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__pause_by_space.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR) --- IF: interface_name | Total Rate (Up / Down): 0.00B / 0.00B ┌Utilization by process name──────────────────────────────────────────────────────────────────┐┌Utilization by remote address────────────────────────────────────────────────────────────────┐ │Process PID Connections Rate (Up / Down) ││Remote Address Connections Rate (Up / Down) │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ └─────────────────────────────────────────────────────────────────────────────────────────────┘└─────────────────────────────────────────────────────────────────────────────────────────────┘ ┌Utilization by connection───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │Connection Process Rate (Up / Down) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Press to pause. Use to rearrange tables. (DNS queries hidden). --- SECTION SEPARATOR --- IF: interface_name | Total Rate (Up / Down): 0.00B / 0.00B [PAUSED] resume. Use to rea range tables. (DNS queries hi den). --- SECTION SEPARATOR --- ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__rearranged_by_tab-2.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_events.lock().unwrap().as_slice() --- [ Clear, HideCursor, Draw, HideCursor, Flush, Draw, HideCursor, Flush, Draw, HideCursor, Flush, Draw, HideCursor, Flush, Draw, HideCursor, Flush, ShowCursor, ] ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__rearranged_by_tab.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR) --- IF: interface_name | Total Rate (Up / Down): 0.00B / 0.00B ┌Utilization by process name──────────────────────────────────────────────────────────────────┐┌Utilization by remote address────────────────────────────────────────────────────────────────┐ │Process PID Connections Rate (Up / Down) ││Remote Address Connections Rate (Up / Down) │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ └─────────────────────────────────────────────────────────────────────────────────────────────┘└─────────────────────────────────────────────────────────────────────────────────────────────┘ ┌Utilization by connection───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │Connection Process Rate (Up / Down) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Press to pause. Use to rearrange tables. (DNS queries hidden). --- SECTION SEPARATOR --- 22. 0B 1 1 1 0.00B / 22.00B 1.1.1.1 1 0.00B / 22.00B :443 => 1.1.1.1:12345 (tcp) 1 0.00B / 22.00B --- SECTION SEPARATOR --- remote addr ss connection──── Remote Address Connecti s Rate (Up / Down) Connection Process Rate (Up / Down) .1.1.1 1 0.00B / 22.00B :443 => 1.1.1.1:12345 (tcp) 1 0 / 22.00B proc ss name Proc ss PID Connections Rate (Up / Down) 1 1 1 0.00B / 22.00B --- SECTION SEPARATOR --- 14 14 14 14 --- SECTION SEPARATOR --- 1 1 1 1 ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes-2.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_events.lock().unwrap().as_slice() --- [ Clear, HideCursor, Draw, HideCursor, Flush, Draw, HideCursor, Flush, Draw, HideCursor, Flush, ShowCursor, ] ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR) --- IF: interface_name | Total Rate (Up / Down): 0.00B / 0.00B ┌Utilization by process name──────────────────────────────────────────────────────────────────┐┌Utilization by remote address────────────────────────────────────────────────────────────────┐ │Process PID Connections Rate (Up / Down) ││Remote Address Connections Rate (Up / Down) │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ └─────────────────────────────────────────────────────────────────────────────────────────────┘└─────────────────────────────────────────────────────────────────────────────────────────────┘ ┌Utilization by connection───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │Connection Process Rate (Up / Down) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Press to pause. Use to rearrange tables. (DNS queries hidden). --- SECTION SEPARATOR --- 41. 0B 1 1 1 0.00B / 22.00B 1.1.1.1 1 0.00B / 22.00B 5 5 1 0.00B / 19.00B 3.3.3.3 1 0.00B / 19.00B :443 => 1.1.1.1:12345 (tcp) 1 0.00B / 22.00B :4435 => 3.3.3.3:1337 (tcp) 5 0.00B / 19.00B --- SECTION SEPARATOR --- 65 35 35 30 30 35 30 ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes_bi_directional-2.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_events.lock().unwrap().as_slice() --- [ Clear, HideCursor, Draw, HideCursor, Flush, Draw, HideCursor, Flush, Draw, HideCursor, Flush, ShowCursor, ] ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes_bi_directional.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR) --- IF: interface_name | Total Rate (Up / Down): 0.00B / 0.00B ┌Utilization by process name──────────────────────────────────────────────────────────────────┐┌Utilization by remote address────────────────────────────────────────────────────────────────┐ │Process PID Connections Rate (Up / Down) ││Remote Address Connections Rate (Up / Down) │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ └─────────────────────────────────────────────────────────────────────────────────────────────┘└─────────────────────────────────────────────────────────────────────────────────────────────┘ ┌Utilization by connection───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │Connection Process Rate (Up / Down) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Press to pause. Use to rearrange tables. (DNS queries hidden). --- SECTION SEPARATOR --- 45. 0B / 49.00B 1 1 1 28.00B / 30.00B 1.1.1.1 1 28.00B / 30.00B 5 5 1 17.00B / 18.00B 3.3.3.3 1 17.00B / 18.00B :443 => 1.1.1.1:12345 (tcp) 1 28.00B / 30.00B :4435 => 3.3.3.3:1337 (tcp) 5 17.00B / 18.00B --- SECTION SEPARATOR --- 53 60 31 2 31 2 22 27 22 27 31 2 22 27 ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes_bi_directional_total-2.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_events.lock().unwrap().as_slice() --- [ Clear, HideCursor, Draw, HideCursor, Flush, Draw, HideCursor, Flush, Draw, HideCursor, Flush, ShowCursor, ] ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes_bi_directional_total.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR) --- IF: interface_name | Total Data (Up / Down): 0.00B / 0.00B ┌Utilization by process name──────────────────────────────────────────────────────────────────┐┌Utilization by remote address────────────────────────────────────────────────────────────────┐ │Process PID Connections Data (Up / Down) ││Remote Address Connections Data (Up / Down) │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ └─────────────────────────────────────────────────────────────────────────────────────────────┘└─────────────────────────────────────────────────────────────────────────────────────────────┘ ┌Utilization by connection───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │Connection Process Data (Up / Down) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Press to pause. Use to rearrange tables. (DNS queries hidden). --- SECTION SEPARATOR --- 45. 0B / 49.00B 1 1 1 28.00B / 30.00B 1.1.1.1 1 28.00B / 30.00B 5 5 1 17.00B / 18.00B 3.3.3.3 1 17.00B / 18.00B :443 => 1.1.1.1:12345 (tcp) 1 28.00B / 30.00B :4435 => 3.3.3.3:1337 (tcp) 5 17.00B / 18.00B --- SECTION SEPARATOR --- 98 109. 0B 59 62 59 62 39 45 39 45 59 62 39 45 ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes_total-2.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_events.lock().unwrap().as_slice() --- [ Clear, HideCursor, Draw, HideCursor, Flush, Draw, HideCursor, Flush, Draw, HideCursor, Flush, ShowCursor, ] ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__sustained_traffic_from_multiple_processes_total.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR) --- IF: interface_name | Total Data (Up / Down): 0.00B / 0.00B ┌Utilization by process name──────────────────────────────────────────────────────────────────┐┌Utilization by remote address────────────────────────────────────────────────────────────────┐ │Process PID Connections Data (Up / Down) ││Remote Address Connections Data (Up / Down) │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ └─────────────────────────────────────────────────────────────────────────────────────────────┘└─────────────────────────────────────────────────────────────────────────────────────────────┘ ┌Utilization by connection───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │Connection Process Data (Up / Down) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Press to pause. Use to rearrange tables. (DNS queries hidden). --- SECTION SEPARATOR --- 41. 0B 1 1 1 0.00B / 22.00B 1.1.1.1 1 0.00B / 22.00B 5 5 1 0.00B / 19.00B 3.3.3.3 1 0.00B / 19.00B :443 => 1.1.1.1:12345 (tcp) 1 0.00B / 22.00B :4435 => 3.3.3.3:1337 (tcp) 5 0.00B / 19.00B --- SECTION SEPARATOR --- 106. 0B 57 57 4 4 57 4 ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__sustained_traffic_from_one_process-2.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_events.lock().unwrap().as_slice() --- [ Clear, HideCursor, Draw, HideCursor, Flush, Draw, HideCursor, Flush, Draw, HideCursor, Flush, ShowCursor, ] ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__sustained_traffic_from_one_process.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR) --- IF: interface_name | Total Rate (Up / Down): 0.00B / 0.00B ┌Utilization by process name──────────────────────────────────────────────────────────────────┐┌Utilization by remote address────────────────────────────────────────────────────────────────┐ │Process PID Connections Rate (Up / Down) ││Remote Address Connections Rate (Up / Down) │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ └─────────────────────────────────────────────────────────────────────────────────────────────┘└─────────────────────────────────────────────────────────────────────────────────────────────┘ ┌Utilization by connection───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │Connection Process Rate (Up / Down) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Press to pause. Use to rearrange tables. (DNS queries hidden). --- SECTION SEPARATOR --- 22. 0B 1 1 1 0.00B / 22.00B 1.1.1.1 1 0.00B / 22.00B :443 => 1.1.1.1:12345 (tcp) 1 0.00B / 22.00B --- SECTION SEPARATOR --- 31 31 31 31 ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__sustained_traffic_from_one_process_total-2.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_events.lock().unwrap().as_slice() --- [ Clear, HideCursor, Draw, HideCursor, Flush, Draw, HideCursor, Flush, Draw, HideCursor, Flush, ShowCursor, ] ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__sustained_traffic_from_one_process_total.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR) --- IF: interface_name | Total Data (Up / Down): 0.00B / 0.00B ┌Utilization by process name──────────────────────────────────────────────────────────────────┐┌Utilization by remote address────────────────────────────────────────────────────────────────┐ │Process PID Connections Data (Up / Down) ││Remote Address Connections Data (Up / Down) │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ └─────────────────────────────────────────────────────────────────────────────────────────────┘└─────────────────────────────────────────────────────────────────────────────────────────────┘ ┌Utilization by connection───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │Connection Process Data (Up / Down) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Press to pause. Use to rearrange tables. (DNS queries hidden). --- SECTION SEPARATOR --- 22. 0B 1 1 1 0.00B / 22.00B 1.1.1.1 1 0.00B / 22.00B :443 => 1.1.1.1:12345 (tcp) 1 0.00B / 22.00B --- SECTION SEPARATOR --- 53 53 53 53 ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__traffic_with_host_names-2.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_events.lock().unwrap().as_slice() --- [ Clear, HideCursor, Draw, HideCursor, Flush, Draw, HideCursor, Flush, Draw, HideCursor, Flush, ShowCursor, ] ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__traffic_with_host_names.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR) --- IF: interface_name | Total Rate (Up / Down): 0.00B / 0.00B ┌Utilization by process name──────────────────────────────────────────────────────────────────┐┌Utilization by remote address────────────────────────────────────────────────────────────────┐ │Process PID Connections Rate (Up / Down) ││Remote Address Connections Rate (Up / Down) │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ └─────────────────────────────────────────────────────────────────────────────────────────────┘└─────────────────────────────────────────────────────────────────────────────────────────────┘ ┌Utilization by connection───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │Connection Process Rate (Up / Down) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Press to pause. Use to rearrange tables. (DNS queries hidden). --- SECTION SEPARATOR --- 45. 0B / 49.00B 1 1 1 28.00B / 30.00B 1.1.1.1 1 28.00B / 30.00B 5 5 1 17.00B / 18.00B 3.3.3.3 1 17.00B / 18.00B :443 => 1.1.1.1:12345 (tcp) 1 28.00B / 30.00B :4435 => 3.3.3.3:1337 (tcp) 5 17.00B / 18.00B --- SECTION SEPARATOR --- 53 60 31 2 one one.one.one 31 2 22 27 three three.three.three 22 27 one one.one.one:12345 (tcp) 31 2 three three.three.three:1337 (tcp) 22 27 ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__traffic_with_winch_event-2.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_events.lock().unwrap().as_slice() --- [ Clear, HideCursor, Draw, HideCursor, Flush, Draw, HideCursor, Flush, Draw, HideCursor, Flush, ShowCursor, ] ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__traffic_with_winch_event.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR) --- IF: interface_name | Total Rate (Up / Down): 0.00B / 0.00B ┌Utilization by process name──────────────────────────────────────────────────────────────────┐┌Utilization by remote address────────────────────────────────────────────────────────────────┐ │Process PID Connections Rate (Up / Down) ││Remote Address Connections Rate (Up / Down) │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ └─────────────────────────────────────────────────────────────────────────────────────────────┘└─────────────────────────────────────────────────────────────────────────────────────────────┘ ┌Utilization by connection───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │Connection Process Rate (Up / Down) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Press to pause. Use to rearrange tables. (DNS queries hidden). --- SECTION SEPARATOR --- 21. 0B / 0. 0B 1 1 1 21.00B / 0.00B 1.1.1.1 1 21.00B / 0.00B :443 => 1.1.1.1:12345 (tcp) 1 21.00B / 0.00B --- SECTION SEPARATOR --- ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__truncate_long_hostnames-2.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_events.lock().unwrap().as_slice() --- [ Clear, HideCursor, Draw, HideCursor, Flush, Draw, HideCursor, Flush, Draw, HideCursor, Flush, ShowCursor, ] ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__truncate_long_hostnames.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR) --- IF: interface_name | Total Rate (Up / Down): 0.00B / 0.00B ┌Utilization by process name──────────────────────────────────────────────────────────────────┐┌Utilization by remote address────────────────────────────────────────────────────────────────┐ │Process PID Connections Rate (Up / Down) ││Remote Address Connections Rate (Up / Down) │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ └─────────────────────────────────────────────────────────────────────────────────────────────┘└─────────────────────────────────────────────────────────────────────────────────────────────┘ ┌Utilization by connection───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │Connection Process Rate (Up / Down) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Press to pause. Use to rearrange tables. (DNS queries hidden). --- SECTION SEPARATOR --- 45. 0B / 49.00B 1 1 1 28.00B / 30.00B 1.1.1.1 1 28.00B / 30.00B 5 5 1 17.00B / 18.00B 3.3.3.3 1 17.00B / 18.00B :443 => 1.1.1.1:12345 (tcp) 1 28.00B / 30.00B :4435 => 3.3.3.3:1337 (tcp) 5 17.00B / 18.00B --- SECTION SEPARATOR --- 53 60 31 2 i am.not.too.long 31 2 22 27 i am.an.obnoxiosuly...do.this.really.i.ask 22 27 i am.not.too.long:12345 (tcp) 31 2 i am.an.obnoxiosuly.long.hostname.why.would.anyone.do.this.really.i.ask:1337 (tcp) 22 27 ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__two_packets_only_addresses.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR) --- IF: interface_name | Total Rate (Up / Down): 0.00B / 0.00B ┌Utilization by remote address───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │Remote Address Connections Rate (Up / Down) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Press to pause. Use to rearrange tables. (DNS queries hidden). --- SECTION SEPARATOR --- 24. 0B / 25.00B 1.1.1.1 1 24.00B / 25.00B ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__two_packets_only_connections.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR) --- IF: interface_name | Total Rate (Up / Down): 0.00B / 0.00B ┌Utilization by connection───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │Connection Process Rate (Up / Down) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Press to pause. Use to rearrange tables. (DNS queries hidden). --- SECTION SEPARATOR --- 24. 0B / 25.00B :443 => 1.1.1.1:12345 (tcp) 1 24.00B / 25.00B ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__two_packets_only_processes.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR) --- IF: interface_name | Total Rate (Up / Down): 0.00B / 0.00B ┌Utilization by process name─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │Process PID Connections Rate (Up / Down) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Press to pause. Use to rearrange tables. (DNS queries hidden). --- SECTION SEPARATOR --- 24. 0B / 25.00B 1 1 1 24.00B / 25.00B ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__two_windows_split_horizontally.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR) --- IF: interface_name | Total Rate (Up / Down): 0.00B / 0.00B ┌Utilization by remote address─────────────────────────────┐ │Remote Address Rate (Up / Down) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └──────────────────────────────────────────────────────────┘ ┌Utilization by connection─────────────────────────────────┐ │Connection Rate (Up / Down) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └──────────────────────────────────────────────────────────┘ Press to pause. Use to rearrange tables. --- SECTION SEPARATOR --- ================================================ FILE: src/tests/cases/snapshots/bandwhich__tests__cases__ui__two_windows_split_vertically.snap ================================================ --- source: src/tests/cases/ui.rs expression: terminal_draw_events.lock().unwrap().join(SNAPSHOT_SECTION_SEPARATOR) --- IF: interface_name | Total Rate (Up / Down): 0.00B / 0.00B ┌Utilization by remote address────────────────────────────────────────────────────────────────┐┌Utilization by connection────────────────────────────────────────────────────────────────────┐ │Remote Address Connections Rate (Up / Down) ││Connection Process Rate (Up / Down) │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ │ ││ │ └─────────────────────────────────────────────────────────────────────────────────────────────┘└─────────────────────────────────────────────────────────────────────────────────────────────┘ Press to pause. Use to rearrange tables. (DNS queries hidden). ================================================ FILE: src/tests/cases/test_utils.rs ================================================ #![cfg_attr(not(feature = "ui_test"), allow(dead_code))] use std::{ collections::HashMap, io::Write, iter, sync::{Arc, Mutex}, }; use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers}; use packet_builder::*; use pnet::{datalink::DataLinkReceiver, packet::Packet}; use pnet_base::MacAddr; use rstest::fixture; use crate::{ network::dns::Client, tests::fakes::{ create_fake_dns_client, get_interfaces_with_frames, get_open_sockets, NetworkFrames, TerminalEvent, TerminalEvents, TestBackend, }, Opt, OsInputOutput, }; pub fn sleep_and_quit_events(sleep_num: usize) -> Box { let events = iter::repeat_n(None, sleep_num) .chain([Some(Event::Key(KeyEvent::new( KeyCode::Char('q'), KeyModifiers::NONE, )))]) .collect(); Box::new(TerminalEvents::new(events)) } pub fn sleep_resize_and_quit_events(sleep_num: usize) -> Box { let events = iter::repeat_n(None, sleep_num) .chain([ Some(Event::Resize(100, 100)), Some(Event::Key(KeyEvent::new( KeyCode::Char('q'), KeyModifiers::NONE, ))), ]) .collect(); Box::new(TerminalEvents::new(events)) } pub fn build_tcp_packet( source_ip: &str, destination_ip: &str, source_port: u16, destination_port: u16, payload: &'static [u8], ) -> Vec { let mut pkt_buf = [0u8; 1500]; let pkt = packet_builder!( pkt_buf, ether({set_destination => MacAddr(0,0,0,0,0,0), set_source => MacAddr(0,0,0,0,0,0)}) / ipv4({set_source => ipv4addr!(source_ip), set_destination => ipv4addr!(destination_ip) }) / tcp({set_source => source_port, set_destination => destination_port }) / payload(payload) ); pkt.packet().to_vec() } #[fixture] pub fn sample_frames_short() -> Vec> { vec![NetworkFrames::new(vec![ Some(build_tcp_packet( "10.0.0.2", "1.1.1.1", 443, 12345, b"I am a fake tcp upload packet", )), Some(build_tcp_packet( "1.1.1.1", "10.0.0.2", 12345, 443, b"I am a fake tcp download packet", )), Some(build_tcp_packet( "10.0.0.2", "1.1.1.1", 54321, 53, b"I am a fake DNS query packet", )), ]) as Box] } #[fixture] pub fn sample_frames_sustained_one_process() -> Vec> { vec![NetworkFrames::new(vec![ Some(build_tcp_packet( "1.1.1.1", "10.0.0.2", 12345, 443, b"I have come from 1.1.1.1", )), None, // sleep Some(build_tcp_packet( "1.1.1.1", "10.0.0.2", 12345, 443, b"Same here, but one second later", )), ]) as Box] } #[fixture] pub fn sample_frames_sustained_multiple_processes() -> Vec> { vec![NetworkFrames::new(vec![ Some(build_tcp_packet( "1.1.1.1", "10.0.0.2", 12345, 443, b"I have come from 1.1.1.1", )), Some(build_tcp_packet( "3.3.3.3", "10.0.0.2", 1337, 4435, b"I come from 3.3.3.3", )), None, // sleep Some(build_tcp_packet( "1.1.1.1", "10.0.0.2", 12345, 443, b"I have come from 1.1.1.1 one second later", )), Some(build_tcp_packet( "3.3.3.3", "10.0.0.2", 1337, 4435, b"I come 3.3.3.3 one second later", )), ]) as Box] } #[fixture] pub fn sample_frames_sustained_long() -> Vec> { vec![NetworkFrames::new(vec![ Some(build_tcp_packet( "10.0.0.2", "3.3.3.3", 4435, 1337, b"omw to 3.3.3.3", )), Some(build_tcp_packet( "3.3.3.3", "10.0.0.2", 1337, 4435, b"I was just there!", )), Some(build_tcp_packet( "1.1.1.1", "10.0.0.2", 12345, 443, b"Is it nice there? I think 1.1.1.1 is dull", )), Some(build_tcp_packet( "10.0.0.2", "1.1.1.1", 443, 12345, b"Well, I heard 1.1.1.1 is all the rage", )), None, // sleep Some(build_tcp_packet( "10.0.0.2", "3.3.3.3", 4435, 1337, b"Wait for me!", )), Some(build_tcp_packet( "3.3.3.3", "10.0.0.2", 1337, 4435, b"They're waiting for you...", )), Some(build_tcp_packet( "1.1.1.1", "10.0.0.2", 12345, 443, b"1.1.1.1 forever!", )), Some(build_tcp_packet( "10.0.0.2", "1.1.1.1", 443, 12345, b"10.0.0.2 forever!", )), ]) as Box] } pub fn os_input_output( network_frames: Vec>, sleep_num: usize, ) -> OsInputOutput { os_input_output_factory( network_frames, None, create_fake_dns_client(HashMap::new()), sleep_and_quit_events(sleep_num), ) } pub fn os_input_output_stdout( network_frames: Vec>, sleep_num: usize, stdout: Option>>>, ) -> OsInputOutput { os_input_output_factory( network_frames, stdout, create_fake_dns_client(HashMap::new()), sleep_and_quit_events(sleep_num), ) } pub fn os_input_output_dns( network_frames: Vec>, sleep_num: usize, stdout: Option>>>, dns_client: Option, ) -> OsInputOutput { os_input_output_factory( network_frames, stdout, dns_client, sleep_and_quit_events(sleep_num), ) } pub fn os_input_output_factory( network_frames: impl IntoIterator>, stdout: Option>>>, dns_client: Option, keyboard_events: Box + Send>, ) -> OsInputOutput { let interfaces_with_frames = get_interfaces_with_frames(network_frames); let write_to_stdout: Box = match stdout { Some(stdout) => Box::new({ move |output| { let mut stdout = stdout.lock().unwrap(); writeln!(&mut stdout, "{output}").unwrap(); } }), None => Box::new(|_output| {}), }; OsInputOutput { interfaces_with_frames, get_open_sockets, terminal_events: keyboard_events, dns_client, write_to_stdout, } } pub fn opts_raw() -> Opt { Opt { interface: Some(String::from("interface_name")), raw: true, ..Default::default() } } pub fn opts_ui() -> Opt { Opt { interface: Some(String::from("interface_name")), ..Default::default() } } type BackendWithStreams = ( Arc>>, Arc>>, TestBackend, ); pub fn test_backend_factory(w: u16, h: u16) -> BackendWithStreams { let terminal_events: Arc>> = Arc::new(Mutex::new(Vec::new())); let terminal_draw_events: Arc>> = Arc::new(Mutex::new(Vec::new())); let backend = TestBackend::new( terminal_events.clone(), terminal_draw_events.clone(), Arc::new(Mutex::new(w)), Arc::new(Mutex::new(h)), ); (terminal_events, terminal_draw_events, backend) } ================================================ FILE: src/tests/cases/ui.rs ================================================ use std::{collections::HashMap, net::IpAddr}; use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers}; use insta::{assert_debug_snapshot, assert_snapshot}; use itertools::Itertools; use pnet::datalink::DataLinkReceiver; use rstest::rstest; use crate::{ cli::RenderOpts, start, tests::{ cases::test_utils::{ build_tcp_packet, opts_ui, os_input_output, os_input_output_factory, sample_frames_short, sample_frames_sustained_long, sample_frames_sustained_multiple_processes, sample_frames_sustained_one_process, sleep_and_quit_events, sleep_resize_and_quit_events, test_backend_factory, }, fakes::{ create_fake_dns_client, get_interfaces_with_frames, get_open_sockets, NetworkFrames, TerminalEvents, }, }, Opt, OsInputOutput, }; const SNAPSHOT_SECTION_SEPARATOR: &str = "\n--- SECTION SEPARATOR ---\n"; #[test] fn basic_startup() { let network_frames = vec![NetworkFrames::new(vec![ None, // sleep ]) as Box]; let (terminal_events, terminal_draw_events, backend) = test_backend_factory(190, 50); let os_input = os_input_output(network_frames, 1); let opts = opts_ui(); start(backend, os_input, opts); assert_snapshot!(terminal_draw_events .lock() .unwrap() .join(SNAPSHOT_SECTION_SEPARATOR)); assert_debug_snapshot!(terminal_events.lock().unwrap().as_slice()); } #[test] fn pause_by_space() { let network_frames = vec![NetworkFrames::new(vec![ Some(build_tcp_packet( "1.1.1.1", "10.0.0.2", 12345, 443, b"I have come from 1.1.1.1", )), None, // sleep None, // sleep None, // sleep Some(build_tcp_packet( "1.1.1.1", "10.0.0.2", 12345, 443, b"Same here, but one second later", )), ]) as Box]; let events = [ None, Some(KeyEvent::new(KeyCode::Char(' '), KeyModifiers::NONE)), None, None, Some(KeyEvent::new(KeyCode::Char(' '), KeyModifiers::NONE)), Some(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL)), ] .into_iter() .map(|ke| ke.map(Event::Key)) .collect_vec(); let events = Box::new(TerminalEvents::new(events)); let os_input = os_input_output_factory(network_frames, None, None, events); let (terminal_events, terminal_draw_events, backend) = test_backend_factory(190, 50); let opts = opts_ui(); start(backend, os_input, opts); assert_snapshot!(terminal_draw_events .lock() .unwrap() .join(SNAPSHOT_SECTION_SEPARATOR)); assert_debug_snapshot!(terminal_events.lock().unwrap().as_slice()); } #[test] fn rearranged_by_tab() { let network_frames = vec![NetworkFrames::new(vec![ Some(build_tcp_packet( "1.1.1.1", "10.0.0.2", 12345, 443, b"I have come from 1.1.1.1", )), None, // sleep None, // sleep None, // sleep Some(build_tcp_packet( "1.1.1.1", "10.0.0.2", 12345, 443, b"Same here, but one second later", )), ]) as Box]; let events = [ None, None, Some(KeyEvent::new(KeyCode::Tab, KeyModifiers::NONE)), None, None, Some(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL)), ] .into_iter() .map(|ke| ke.map(Event::Key)) .collect_vec(); let events = Box::new(TerminalEvents::new(events)); let os_input = os_input_output_factory(network_frames, None, None, events); let (terminal_events, terminal_draw_events, backend) = test_backend_factory(190, 50); let opts = opts_ui(); start(backend, os_input, opts); assert_snapshot!(terminal_draw_events .lock() .unwrap() .join(SNAPSHOT_SECTION_SEPARATOR)); assert_debug_snapshot!(terminal_events.lock().unwrap().as_slice()); } #[test] fn basic_only_processes() { let network_frames = vec![NetworkFrames::new(vec![ None, // sleep ]) as Box]; let (_, terminal_draw_events, backend) = test_backend_factory(190, 50); let os_input = os_input_output(network_frames, 1); let opts = Opt { render_opts: RenderOpts { processes: true, ..Default::default() }, ..opts_ui() }; start(backend, os_input, opts); assert_snapshot!(terminal_draw_events .lock() .unwrap() .join(SNAPSHOT_SECTION_SEPARATOR)); } #[test] fn basic_processes_with_dns_queries() { let network_frames = vec![NetworkFrames::new(vec![ None, // sleep ]) as Box]; let (_, terminal_draw_events, backend) = test_backend_factory(190, 50); let os_input = os_input_output(network_frames, 1); let opts = Opt { show_dns: true, render_opts: RenderOpts { processes: true, ..Default::default() }, ..opts_ui() }; start(backend, os_input, opts); assert_snapshot!(terminal_draw_events .lock() .unwrap() .join(SNAPSHOT_SECTION_SEPARATOR)); } #[test] fn basic_only_connections() { let network_frames = vec![NetworkFrames::new(vec![ None, // sleep ]) as Box]; let (_, terminal_draw_events, backend) = test_backend_factory(190, 50); let os_input = os_input_output(network_frames, 1); let opts = Opt { render_opts: RenderOpts { connections: true, ..Default::default() }, ..opts_ui() }; start(backend, os_input, opts); assert_snapshot!(terminal_draw_events .lock() .unwrap() .join(SNAPSHOT_SECTION_SEPARATOR)); } #[test] fn basic_only_addresses() { let network_frames = vec![NetworkFrames::new(vec![ None, // sleep ]) as Box]; let (_, terminal_draw_events, backend) = test_backend_factory(190, 50); let os_input = os_input_output(network_frames, 1); let opts = Opt { render_opts: RenderOpts { addresses: true, ..Default::default() }, ..opts_ui() }; start(backend, os_input, opts); assert_snapshot!(terminal_draw_events .lock() .unwrap() .join(SNAPSHOT_SECTION_SEPARATOR)); } #[rstest(sample_frames_short as frames)] fn two_packets_only_processes(frames: Vec>) { let (_, terminal_draw_events, backend) = test_backend_factory(190, 50); let os_input = os_input_output(frames, 2); let opts = Opt { render_opts: RenderOpts { processes: true, ..Default::default() }, ..opts_ui() }; start(backend, os_input, opts); assert_snapshot!(terminal_draw_events .lock() .unwrap() .join(SNAPSHOT_SECTION_SEPARATOR)); } #[rstest(sample_frames_short as frames)] fn two_packets_only_connections(frames: Vec>) { let (_, terminal_draw_events, backend) = test_backend_factory(190, 50); let os_input = os_input_output(frames, 2); let opts = Opt { render_opts: RenderOpts { connections: true, ..Default::default() }, ..opts_ui() }; start(backend, os_input, opts); assert_snapshot!(terminal_draw_events .lock() .unwrap() .join(SNAPSHOT_SECTION_SEPARATOR)); } #[rstest(sample_frames_short as frames)] fn two_packets_only_addresses(frames: Vec>) { let (_, terminal_draw_events, backend) = test_backend_factory(190, 50); let os_input = os_input_output(frames, 2); let opts = Opt { render_opts: RenderOpts { addresses: true, ..Default::default() }, ..opts_ui() }; start(backend, os_input, opts); assert_snapshot!(terminal_draw_events .lock() .unwrap() .join(SNAPSHOT_SECTION_SEPARATOR)); } #[test] fn two_windows_split_horizontally() { let network_frames = vec![NetworkFrames::new(vec![ None, // sleep ]) as Box]; let (_, terminal_draw_events, backend) = test_backend_factory(60, 50); let os_input = os_input_output(network_frames, 2); let opts = Opt { render_opts: RenderOpts { addresses: true, connections: true, ..Default::default() }, ..opts_ui() }; start(backend, os_input, opts); assert_snapshot!(terminal_draw_events .lock() .unwrap() .join(SNAPSHOT_SECTION_SEPARATOR)); } #[test] fn two_windows_split_vertically() { let network_frames = vec![NetworkFrames::new(vec![ None, // sleep ]) as Box]; let (_, terminal_draw_events, backend) = test_backend_factory(190, 50); let os_input = os_input_output(network_frames, 1); let opts = Opt { render_opts: RenderOpts { addresses: true, connections: true, ..Default::default() }, ..opts_ui() }; start(backend, os_input, opts); assert_snapshot!(terminal_draw_events .lock() .unwrap() .join(SNAPSHOT_SECTION_SEPARATOR)); } #[test] fn one_packet_of_traffic() { let network_frames = vec![NetworkFrames::new(vec![Some(build_tcp_packet( "10.0.0.2", "1.1.1.1", 443, 12345, b"I am a fake tcp packet", ))]) as Box]; let (terminal_events, terminal_draw_events, backend) = test_backend_factory(190, 50); let os_input = os_input_output(network_frames, 2); let opts = opts_ui(); start(backend, os_input, opts); assert_snapshot!(terminal_draw_events .lock() .unwrap() .join(SNAPSHOT_SECTION_SEPARATOR)); assert_debug_snapshot!(terminal_events.lock().unwrap().as_slice()); } #[rstest(sample_frames_short as frames)] fn bi_directional_traffic(frames: Vec>) { let (terminal_events, terminal_draw_events, backend) = test_backend_factory(190, 50); let os_input = os_input_output(frames, 2); let opts = opts_ui(); start(backend, os_input, opts); assert_snapshot!(terminal_draw_events .lock() .unwrap() .join(SNAPSHOT_SECTION_SEPARATOR)); assert_debug_snapshot!(terminal_events.lock().unwrap().as_slice()); } #[test] fn multiple_packets_of_traffic_from_different_connections() { let network_frames = vec![NetworkFrames::new(vec![ Some(build_tcp_packet( "2.2.2.2", "10.0.0.2", 12345, 443, b"I have come from 2.2.2.2", )), Some(build_tcp_packet( "2.2.2.2", "10.0.0.2", 54321, 4434, b"I come from 2.2.2.2", )), ]) as Box]; let (terminal_events, terminal_draw_events, backend) = test_backend_factory(190, 50); let os_input = os_input_output(network_frames, 2); let opts = opts_ui(); start(backend, os_input, opts); assert_snapshot!(terminal_draw_events .lock() .unwrap() .join(SNAPSHOT_SECTION_SEPARATOR)); assert_debug_snapshot!(terminal_events.lock().unwrap().as_slice()); } #[test] fn multiple_packets_of_traffic_from_single_connection() { let network_frames = vec![NetworkFrames::new(vec![ Some(build_tcp_packet( "1.1.1.1", "10.0.0.2", 12345, 443, b"I have come from 1.1.1.1", )), Some(build_tcp_packet( "1.1.1.1", "10.0.0.2", 12345, 443, b"I've come from 1.1.1.1 too!", )), ]) as Box]; let (terminal_events, terminal_draw_events, backend) = test_backend_factory(190, 50); let os_input = os_input_output(network_frames, 2); let opts = opts_ui(); start(backend, os_input, opts); assert_snapshot!(terminal_draw_events .lock() .unwrap() .join(SNAPSHOT_SECTION_SEPARATOR)); assert_debug_snapshot!(terminal_events.lock().unwrap().as_slice()); } #[test] fn one_process_with_multiple_connections() { let network_frames = vec![NetworkFrames::new(vec![ Some(build_tcp_packet( "1.1.1.1", "10.0.0.2", 12345, 443, b"I have come from 1.1.1.1", )), Some(build_tcp_packet( "1.1.1.1", "10.0.0.2", 12346, 443, b"Funny that, I'm from 1.1.1.1", )), ]) as Box]; let (terminal_events, terminal_draw_events, backend) = test_backend_factory(190, 50); let os_input = os_input_output(network_frames, 2); let opts = opts_ui(); start(backend, os_input, opts); assert_snapshot!(terminal_draw_events .lock() .unwrap() .join(SNAPSHOT_SECTION_SEPARATOR)); assert_debug_snapshot!(terminal_events.lock().unwrap().as_slice()); } #[test] fn multiple_processes_with_multiple_connections() { let network_frames = vec![NetworkFrames::new(vec![ Some(build_tcp_packet( "1.1.1.1", "10.0.0.2", 12345, 443, b"I have come from 1.1.1.1", )), Some(build_tcp_packet( "3.3.3.3", "10.0.0.2", 1337, 4435, b"Greetings traveller, I'm from 3.3.3.3", )), Some(build_tcp_packet( "2.2.2.2", "10.0.0.2", 54321, 4434, b"You know, 2.2.2.2 is really nice!", )), Some(build_tcp_packet( "4.4.4.4", "10.0.0.2", 1337, 4432, b"I'm partial to 4.4.4.4", )), ]) as Box]; let (terminal_events, terminal_draw_events, backend) = test_backend_factory(190, 50); let os_input = os_input_output(network_frames, 2); let opts = opts_ui(); start(backend, os_input, opts); assert_snapshot!(terminal_draw_events .lock() .unwrap() .join(SNAPSHOT_SECTION_SEPARATOR)); assert_debug_snapshot!(terminal_events.lock().unwrap().as_slice()); } #[test] fn multiple_connections_from_remote_address() { let network_frames = vec![NetworkFrames::new(vec![ Some(build_tcp_packet( "1.1.1.1", "10.0.0.2", 12345, 443, b"I have come from 1.1.1.1", )), Some(build_tcp_packet( "1.1.1.1", "10.0.0.2", 12346, 443, b"Me too, but on a different port", )), ]) as Box]; let (terminal_events, terminal_draw_events, backend) = test_backend_factory(190, 50); let os_input = os_input_output(network_frames, 2); let opts = opts_ui(); start(backend, os_input, opts); assert_snapshot!(terminal_draw_events .lock() .unwrap() .join(SNAPSHOT_SECTION_SEPARATOR)); assert_debug_snapshot!(terminal_events.lock().unwrap().as_slice()); } #[rstest(sample_frames_sustained_one_process as frames)] fn sustained_traffic_from_one_process(frames: Vec>) { let (terminal_events, terminal_draw_events, backend) = test_backend_factory(190, 50); let os_input = os_input_output(frames, 3); let opts = opts_ui(); start(backend, os_input, opts); assert_snapshot!(terminal_draw_events .lock() .unwrap() .join(SNAPSHOT_SECTION_SEPARATOR)); assert_debug_snapshot!(terminal_events.lock().unwrap().as_slice()); } #[rstest(sample_frames_sustained_one_process as frames)] fn sustained_traffic_from_one_process_total(frames: Vec>) { let (terminal_events, terminal_draw_events, backend) = test_backend_factory(190, 50); let os_input = os_input_output(frames, 3); let mut opts = opts_ui(); opts.render_opts.total_utilization = true; start(backend, os_input, opts); assert_snapshot!(terminal_draw_events .lock() .unwrap() .join(SNAPSHOT_SECTION_SEPARATOR)); assert_debug_snapshot!(terminal_events.lock().unwrap().as_slice()); } #[rstest(sample_frames_sustained_multiple_processes as frames)] fn sustained_traffic_from_multiple_processes(frames: Vec>) { let (terminal_events, terminal_draw_events, backend) = test_backend_factory(190, 50); let os_input = os_input_output(frames, 3); let opts = opts_ui(); start(backend, os_input, opts); assert_snapshot!(terminal_draw_events .lock() .unwrap() .join(SNAPSHOT_SECTION_SEPARATOR)); assert_debug_snapshot!(terminal_events.lock().unwrap().as_slice()); } #[rstest(sample_frames_sustained_multiple_processes as frames)] fn sustained_traffic_from_multiple_processes_total(frames: Vec>) { let (terminal_events, terminal_draw_events, backend) = test_backend_factory(190, 50); let os_input = os_input_output(frames, 3); let mut opts = opts_ui(); opts.render_opts.total_utilization = true; start(backend, os_input, opts); assert_snapshot!(terminal_draw_events .lock() .unwrap() .join(SNAPSHOT_SECTION_SEPARATOR)); assert_debug_snapshot!(terminal_events.lock().unwrap().as_slice()); } #[rstest(sample_frames_sustained_long as frames)] fn sustained_traffic_from_multiple_processes_bi_directional( frames: Vec>, ) { let (terminal_events, terminal_draw_events, backend) = test_backend_factory(190, 50); let os_input = os_input_output(frames, 3); let opts = opts_ui(); start(backend, os_input, opts); assert_snapshot!(terminal_draw_events .lock() .unwrap() .join(SNAPSHOT_SECTION_SEPARATOR)); assert_debug_snapshot!(terminal_events.lock().unwrap().as_slice()); } #[rstest(sample_frames_sustained_long as frames)] fn sustained_traffic_from_multiple_processes_bi_directional_total( frames: Vec>, ) { let (terminal_events, terminal_draw_events, backend) = test_backend_factory(190, 50); let os_input = os_input_output(frames, 3); let mut opts = opts_ui(); opts.render_opts.total_utilization = true; start(backend, os_input, opts); assert_snapshot!(terminal_draw_events .lock() .unwrap() .join(SNAPSHOT_SECTION_SEPARATOR)); assert_debug_snapshot!(terminal_events.lock().unwrap().as_slice()); } #[rstest(sample_frames_sustained_long as network_frames)] fn traffic_with_host_names(network_frames: Vec>) { let (terminal_events, terminal_draw_events, backend) = test_backend_factory(190, 50); let interfaces_with_frames = get_interfaces_with_frames(network_frames); let mut ips_to_hostnames = HashMap::new(); ips_to_hostnames.insert( IpAddr::V4("1.1.1.1".parse().unwrap()), String::from("one.one.one.one"), ); ips_to_hostnames.insert( IpAddr::V4("3.3.3.3".parse().unwrap()), String::from("three.three.three.three"), ); ips_to_hostnames.insert( IpAddr::V4("10.0.0.2".parse().unwrap()), String::from("i-like-cheese.com"), ); let dns_client = create_fake_dns_client(ips_to_hostnames); let write_to_stdout = Box::new(|_output: &_| {}); let os_input = OsInputOutput { interfaces_with_frames, get_open_sockets, terminal_events: sleep_and_quit_events(3), dns_client, write_to_stdout, }; let opts = opts_ui(); start(backend, os_input, opts); assert_snapshot!(terminal_draw_events .lock() .unwrap() .join(SNAPSHOT_SECTION_SEPARATOR)); assert_debug_snapshot!(terminal_events.lock().unwrap().as_slice()); } #[rstest(sample_frames_sustained_long as network_frames)] fn truncate_long_hostnames(network_frames: Vec>) { let (terminal_events, terminal_draw_events, backend) = test_backend_factory(190, 50); let interfaces_with_frames = get_interfaces_with_frames(network_frames); let mut ips_to_hostnames = HashMap::new(); ips_to_hostnames.insert( IpAddr::V4("1.1.1.1".parse().unwrap()), String::from("i.am.not.too.long"), ); ips_to_hostnames.insert( IpAddr::V4("3.3.3.3".parse().unwrap()), String::from("i.am.an.obnoxiosuly.long.hostname.why.would.anyone.do.this.really.i.ask"), ); ips_to_hostnames.insert( IpAddr::V4("10.0.0.2".parse().unwrap()), String::from("i-like-cheese.com"), ); let dns_client = create_fake_dns_client(ips_to_hostnames); let write_to_stdout = Box::new(|_output: &_| {}); let os_input = OsInputOutput { interfaces_with_frames, get_open_sockets, terminal_events: sleep_and_quit_events(3), dns_client, write_to_stdout, }; let opts = opts_ui(); start(backend, os_input, opts); assert_snapshot!(terminal_draw_events .lock() .unwrap() .join(SNAPSHOT_SECTION_SEPARATOR)); assert_debug_snapshot!(terminal_events.lock().unwrap().as_slice()); } #[rstest(sample_frames_sustained_long as network_frames)] fn no_resolve_mode(network_frames: Vec>) { let (terminal_events, terminal_draw_events, backend) = test_backend_factory(190, 50); let interfaces_with_frames = get_interfaces_with_frames(network_frames); let mut ips_to_hostnames = HashMap::new(); ips_to_hostnames.insert( IpAddr::V4("1.1.1.1".parse().unwrap()), String::from("one.one.one.one"), ); ips_to_hostnames.insert( IpAddr::V4("3.3.3.3".parse().unwrap()), String::from("three.three.three.three"), ); ips_to_hostnames.insert( IpAddr::V4("10.0.0.2".parse().unwrap()), String::from("i-like-cheese.com"), ); let dns_client = None; let write_to_stdout = Box::new(|_output: &_| {}); let os_input = OsInputOutput { interfaces_with_frames, get_open_sockets, terminal_events: sleep_and_quit_events(3), dns_client, write_to_stdout, }; let opts = opts_ui(); start(backend, os_input, opts); assert_snapshot!(terminal_draw_events .lock() .unwrap() .join(SNAPSHOT_SECTION_SEPARATOR)); assert_debug_snapshot!(terminal_events.lock().unwrap().as_slice()); } #[test] fn traffic_with_winch_event() { let network_frames = vec![NetworkFrames::new(vec![Some(build_tcp_packet( "10.0.0.2", "1.1.1.1", 443, 12345, b"I am a fake tcp packet", ))]) as Box]; let interfaces_with_frames = get_interfaces_with_frames(network_frames); let (terminal_events, terminal_draw_events, backend) = test_backend_factory(190, 50); let dns_client = create_fake_dns_client(HashMap::new()); let write_to_stdout = Box::new(|_output: &_| {}); let os_input = OsInputOutput { interfaces_with_frames, get_open_sockets, terminal_events: sleep_resize_and_quit_events(2), dns_client, write_to_stdout, }; let opts = opts_ui(); start(backend, os_input, opts); assert_snapshot!(terminal_draw_events .lock() .unwrap() .join(SNAPSHOT_SECTION_SEPARATOR)); assert_debug_snapshot!(terminal_events.lock().unwrap().as_slice()); } #[rstest] #[case("full-width-under-30-height", 190, 29)] #[case("under-120-width-full-height", 119, 50)] #[case("under-120-width-under-30-height", 119, 29)] #[case("under-50-width-under-50-height", 50, 50)] #[case("under-70-width-under-30-height", 69, 29)] fn layout(#[case] name: &str, #[case] width: u16, #[case] height: u16) { let network_frames = vec![NetworkFrames::new(vec![ Some(build_tcp_packet( "1.1.1.1", "10.0.0.2", 12345, 443, b"I have come from 1.1.1.1", )), Some(build_tcp_packet( "3.3.3.3", "10.0.0.2", 1337, 4435, b"Greetings traveller, I'm from 3.3.3.3", )), Some(build_tcp_packet( "2.2.2.2", "10.0.0.2", 54321, 4434, b"You know, 2.2.2.2 is really nice!", )), Some(build_tcp_packet( "4.4.4.4", "10.0.0.2", 1337, 4432, b"I'm partial to 4.4.4.4", )), ]) as Box]; let (terminal_events, terminal_draw_events, backend) = test_backend_factory(width, height); let os_input = os_input_output(network_frames, 2); let opts = opts_ui(); start(backend, os_input, opts); assert_snapshot!( format!("layout-{name}-draw_events"), terminal_draw_events .lock() .unwrap() .join(SNAPSHOT_SECTION_SEPARATOR) ); assert_debug_snapshot!( format!("layout-{name}-events"), terminal_events.lock().unwrap().as_slice() ); } ================================================ FILE: src/tests/fakes/fake_input.rs ================================================ use std::{ collections::HashMap, net::{IpAddr, Ipv4Addr, SocketAddr}, thread, time, }; use async_trait::async_trait; use crossterm::event::Event; use itertools::Itertools; use pnet::{ datalink::{DataLinkReceiver, NetworkInterface}, ipnetwork::IpNetwork, }; use tokio::runtime::Runtime; use crate::{ network::{ dns::{self, Lookup}, Connection, Protocol, }, os::ProcessInfo, OpenSockets, }; pub struct TerminalEvents { pub events: Vec>, } impl TerminalEvents { pub fn new(mut events: Vec>) -> Self { events.reverse(); // this is so that we do not have to shift the array TerminalEvents { events } } } impl Iterator for TerminalEvents { type Item = Event; fn next(&mut self) -> Option { match self.events.pop() { Some(ev) => match ev { Some(ev) => Some(ev), None => { thread::sleep(time::Duration::from_millis(900)); self.next() } }, None => None, } } } pub struct NetworkFrames { pub packets: Vec>>, pub current_index: usize, } impl NetworkFrames { pub fn new(packets: Vec>>) -> Box { Box::new(NetworkFrames { packets, current_index: 0, }) } fn next_packet(&mut self) -> Option<&[u8]> { let next_index = self.current_index; self.current_index += 1; self.packets.get(next_index).and_then(|p| p.as_deref()) } } impl DataLinkReceiver for NetworkFrames { fn next(&mut self) -> Result<&[u8], std::io::Error> { if self.current_index == 0 { // make it less likely to have a race condition with the display loop // this is so the tests pass consistently thread::sleep(time::Duration::from_millis(500)); } if self.current_index < self.packets.len() { let action = self.next_packet(); match action { Some(packet) => Ok(packet), None => { thread::sleep(time::Duration::from_secs(1)); Ok(&[]) } } } else { thread::sleep(time::Duration::from_secs(1)); Ok(&[]) } } } pub fn get_open_sockets() -> OpenSockets { let mut open_sockets = HashMap::new(); let local_ip = IpAddr::V4(Ipv4Addr::new(10, 0, 0, 2)); open_sockets.insert( Connection::new( SocketAddr::new(IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)), 12345), local_ip, 443, Protocol::Tcp, ), ProcessInfo::new("1", 1), ); open_sockets.insert( Connection::new( SocketAddr::new(IpAddr::V4(Ipv4Addr::new(2, 2, 2, 2)), 54321), local_ip, 4434, Protocol::Tcp, ), ProcessInfo::new("4", 4), ); open_sockets.insert( Connection::new( SocketAddr::new(IpAddr::V4(Ipv4Addr::new(3, 3, 3, 3)), 1337), local_ip, 4435, Protocol::Tcp, ), ProcessInfo::new("5", 5), ); open_sockets.insert( Connection::new( SocketAddr::new(IpAddr::V4(Ipv4Addr::new(4, 4, 4, 4)), 1337), local_ip, 4432, Protocol::Tcp, ), ProcessInfo::new("2", 2), ); open_sockets.insert( Connection::new( SocketAddr::new(IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)), 12346), local_ip, 443, Protocol::Tcp, ), ProcessInfo::new("1", 1), ); let mut local_socket_to_procs = HashMap::new(); let mut connections = std::vec::Vec::new(); for (connection, proc_info) in open_sockets { local_socket_to_procs.insert(connection.local_socket, proc_info); connections.push(connection); } OpenSockets { sockets_to_procs: local_socket_to_procs, } } pub fn get_interfaces() -> Vec { vec![NetworkInterface { name: String::from("interface_name"), description: String::from("Fake interface"), index: 42, mac: None, ips: vec![IpNetwork::V4("10.0.0.2".parse().unwrap())], // It's important that the IFF_LOOPBACK bit is set to 0. // Otherwise sniffer will attempt to start parse packets // at offset 14 flags: 0, }] } pub fn get_interfaces_with_frames( frames: impl IntoIterator>, ) -> Vec<(NetworkInterface, Box)> { get_interfaces().into_iter().zip_eq(frames).collect() } pub fn create_fake_dns_client(ips_to_hosts: HashMap) -> Option { let runtime = Runtime::new().unwrap(); let dns_client = dns::Client::new(FakeResolver(ips_to_hosts), runtime).unwrap(); Some(dns_client) } struct FakeResolver(HashMap); #[async_trait] impl Lookup for FakeResolver { async fn lookup(&self, ip: IpAddr) -> Option { self.0.get(&ip).cloned() } } ================================================ FILE: src/tests/fakes/fake_output.rs ================================================ use std::{ collections::HashMap, io, sync::{Arc, Mutex}, }; use ratatui::{ backend::{Backend, WindowSize}, buffer::Cell, layout::{Position, Size}, }; #[derive(Hash, Debug, PartialEq)] pub enum TerminalEvent { Clear, HideCursor, ShowCursor, GetCursor, Flush, Draw, } pub struct TestBackend { pub events: Arc>>, pub draw_events: Arc>>, terminal_width: Arc>, terminal_height: Arc>, } impl TestBackend { pub fn new( log: Arc>>, draw_log: Arc>>, terminal_width: Arc>, terminal_height: Arc>, ) -> TestBackend { TestBackend { events: log, draw_events: draw_log, terminal_width, terminal_height, } } } #[derive(Hash, Eq, PartialEq)] struct Point { x: u16, y: u16, } impl Backend for TestBackend { fn clear(&mut self) -> io::Result<()> { self.events.lock().unwrap().push(TerminalEvent::Clear); Ok(()) } fn hide_cursor(&mut self) -> io::Result<()> { self.events.lock().unwrap().push(TerminalEvent::HideCursor); Ok(()) } fn show_cursor(&mut self) -> io::Result<()> { self.events.lock().unwrap().push(TerminalEvent::ShowCursor); Ok(()) } fn get_cursor_position(&mut self) -> io::Result { self.events.lock().unwrap().push(TerminalEvent::GetCursor); Ok(Position::new(0, 0)) } fn set_cursor_position>(&mut self, _position: P) -> io::Result<()> { Ok(()) } fn draw<'a, I>(&mut self, content: I) -> io::Result<()> where I: Iterator, { // use std::fmt::Write; self.events.lock().unwrap().push(TerminalEvent::Draw); let mut string = String::with_capacity(content.size_hint().0 * 3); let mut coordinates = HashMap::new(); for (x, y, cell) in content { coordinates.insert(Point { x, y }, cell); } let terminal_height = self.terminal_height.lock().unwrap(); let terminal_width = self.terminal_width.lock().unwrap(); for y in 0..*terminal_height { for x in 0..*terminal_width { match coordinates.get(&Point { x, y }) { Some(cell) => { // this will contain no style information at all // should be good enough for testing string.push_str(cell.symbol()); } None => { string.push(' '); } } } string.push('\n'); } self.draw_events.lock().unwrap().push(string); Ok(()) } fn size(&self) -> io::Result { let terminal_height = self.terminal_height.lock().unwrap(); let terminal_width = self.terminal_width.lock().unwrap(); Ok(Size::new(*terminal_width, *terminal_height)) } fn window_size(&mut self) -> io::Result { let width = *self.terminal_width.lock().unwrap(); let height = *self.terminal_height.lock().unwrap(); Ok(WindowSize { columns_rows: Size { width, height }, pixels: Size::default(), }) } fn flush(&mut self) -> io::Result<()> { self.events.lock().unwrap().push(TerminalEvent::Flush); Ok(()) } } ================================================ FILE: src/tests/fakes/mod.rs ================================================ mod fake_input; mod fake_output; pub use fake_input::*; pub use fake_output::*; ================================================ FILE: src/tests/mod.rs ================================================ pub mod cases; pub mod fakes;