├── .dockerignore
├── .gitattributes
├── .github
└── workflows
│ ├── ci.yml
│ ├── release.md
│ └── release.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE-APACHE
├── LICENSE-MIT
├── README.md
├── android
├── .gitignore
├── Cargo.lock
├── Cargo.toml
├── app
│ ├── build.gradle
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ └── zoxc
│ │ │ └── crusader
│ │ │ └── MainActivity.java
│ │ └── res
│ │ └── values
│ │ ├── colors.xml
│ │ └── themes.xml
├── build.gradle
├── debugInstall.ps1
├── gradle.properties
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
│ └── lib.rs
├── data
├── v0.crr
├── v1.crr
└── v2.crr
├── docker
├── README.md
├── remote-static.Dockerfile
└── server-static.Dockerfile
├── docs
├── BUILDING.md
├── CLI.md
├── LOCAL_TESTS.md
├── RESULTS.md
└── TROUBLESHOOTING.md
├── media
├── Crusader Screen Shots.md
├── Crusader-Client.png
├── Crusader-Latency.png
├── Crusader-Loss.png
├── Crusader-Monitor.png
├── Crusader-Remote.png
├── Crusader-Result-with-stats.png
├── Crusader-Result.png
├── Crusader-Server.png
├── Crusader-Throughput.png
└── batch_add_border.sh
└── src
├── Cargo.lock
├── Cargo.toml
├── crusader-gui-lib
├── Cargo.toml
└── src
│ ├── client.rs
│ └── lib.rs
├── crusader-gui
├── Cargo.toml
└── src
│ └── main.rs
├── crusader-lib
├── Cargo.toml
├── UFL.txt
├── Ubuntu-Light.ttf
├── assets
│ ├── vue.js
│ └── vue.prod.js
├── build.rs
└── src
│ ├── common.rs
│ ├── discovery.rs
│ ├── file_format.rs
│ ├── latency.rs
│ ├── lib.rs
│ ├── peer.rs
│ ├── plot.rs
│ ├── protocol.rs
│ ├── remote.html
│ ├── remote.rs
│ ├── serve.rs
│ └── test.rs
└── crusader
├── Cargo.toml
└── src
└── main.rs
/.dockerignore:
--------------------------------------------------------------------------------
1 | /src/target
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | src/crusader-lib/assets/* linguist-vendored
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: ci
2 |
3 | on:
4 | push:
5 | branches: [master]
6 | pull_request:
7 |
8 | env:
9 | CARGO_INCREMENTAL: 0
10 |
11 | jobs:
12 | build:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v2
16 |
17 | - uses: actions-rs/toolchain@v1
18 | with:
19 | toolchain: stable
20 | override: true
21 | components: rustfmt, clippy
22 |
23 | - name: Build server-only binary
24 | run: cargo build -p crusader --no-default-features
25 | working-directory: src
26 |
27 | - name: Build
28 | run: cargo build
29 | working-directory: src
30 |
31 | - name: Lint
32 | run: cargo clippy --all -- -D warnings
33 | working-directory: src
34 |
35 | - name: Format
36 | run: cargo fmt --all -- --check
37 | working-directory: src
38 |
39 | android:
40 | runs-on: ubuntu-latest
41 | steps:
42 | - uses: actions/checkout@v2
43 |
44 | - uses: actions-rs/toolchain@v1
45 | with:
46 | toolchain: stable
47 | override: true
48 | components: rustfmt, clippy
49 |
50 | - name: Install Rust targets
51 | run: >
52 | rustup target add
53 | aarch64-linux-android
54 | armv7-linux-androideabi
55 | x86_64-linux-android
56 | i686-linux-android
57 |
58 | - name: Install cargo-ndk
59 | run: cargo install cargo-ndk
60 |
61 | - name: Setup Java
62 | uses: actions/setup-java@v3
63 | with:
64 | distribution: 'temurin'
65 | java-version: '17'
66 |
67 | - name: Setup Android SDK
68 | uses: android-actions/setup-android@v2
69 |
70 | - name: Build Android Rust crates
71 | working-directory: android
72 | run: cargo ndk -t arm64-v8a -o app/src/main/jniLibs/ -- build
73 |
74 | - name: Build Android APK
75 | working-directory: android
76 | run: ./gradlew buildDebug
77 |
78 | # Wait for a new cargo ndk release for better clippy support
79 | #- name: Lint
80 | # run: cargo ndk -t arm64-v8a -- clippy --all -- -D warnings
81 | # working-directory: android
82 |
83 | - name: Format
84 | run: cargo fmt --all -- --check
85 | working-directory: android
86 |
--------------------------------------------------------------------------------
/.github/workflows/release.md:
--------------------------------------------------------------------------------
1 | Crusader has pre-built binaries for a number of operating systems. Download the appropriate binary below for your OS.
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: release
2 | on:
3 | push:
4 | tags:
5 | - "v*"
6 |
7 | env:
8 | CARGO_INCREMENTAL: 0
9 |
10 | jobs:
11 | create-release:
12 | name: create-release
13 | runs-on: ubuntu-latest
14 | outputs:
15 | upload_url: ${{ steps.release.outputs.upload_url }}
16 | permissions:
17 | contents: write
18 | steps:
19 | - uses: actions/checkout@v2
20 |
21 | - name: Get the release version from the tag
22 | shell: bash
23 | run: echo "TAG_NAME=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
24 |
25 | - name: Create GitHub release
26 | id: release
27 | uses: actions/create-release@v1.1.4
28 | env:
29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
30 | with:
31 | tag_name: ${{ env.TAG_NAME }}
32 | release_name: Automated build of ${{ env.TAG_NAME }}
33 | prerelease: true
34 | body_path: .github/workflows/release.md
35 |
36 | release-assets:
37 | name: Release assets
38 | needs: create-release
39 | runs-on: ${{ matrix.build.os }}
40 | strategy:
41 | fail-fast: false
42 | matrix:
43 | build:
44 | - os: ubuntu-latest
45 | target: arm-unknown-linux-musleabihf
46 | friendly: Linux-ARM-32-bit
47 | exe_postfix:
48 | cargo: cross
49 | gui: false
50 |
51 | - os: ubuntu-latest
52 | target: aarch64-unknown-linux-musl
53 | friendly: Linux-ARM-64-bit
54 | exe_postfix:
55 | cargo: cross
56 | gui: false
57 |
58 | - os: ubuntu-latest
59 | target: x86_64-unknown-linux-musl
60 | friendly: Linux-X86-64-bit
61 | exe_postfix:
62 | cargo: cargo
63 | gui: false
64 |
65 | - os: macos-latest
66 | target: aarch64-apple-darwin
67 | friendly: macOS-ARM-64-bit
68 | exe_postfix:
69 | cargo: cargo
70 | gui: true
71 |
72 | - os: macos-latest
73 | target: x86_64-apple-darwin
74 | friendly: macOS-X86-64-bit
75 | exe_postfix:
76 | cargo: cargo
77 | gui: true
78 |
79 | - os: windows-latest
80 | target: i686-pc-windows-msvc
81 | friendly: Windows-X86-32-bit
82 | exe_postfix: .exe
83 | cargo: cargo
84 | gui: true
85 |
86 | - os: windows-latest
87 | target: x86_64-pc-windows-msvc
88 | friendly: Windows-X86-64-bit
89 | exe_postfix: .exe
90 | cargo: cargo
91 | gui: true
92 | steps:
93 | - uses: actions/checkout@v2
94 |
95 | - uses: actions-rs/toolchain@v1
96 | with:
97 | toolchain: stable
98 | override: true
99 | target: ${{ matrix.build.target }}
100 |
101 | - name: Install cross
102 | if: matrix.build.cargo == 'cross'
103 | run: cargo install cross
104 |
105 | - name: Install and use musl
106 | if: matrix.build.os == 'ubuntu-latest' && matrix.build.cargo != 'cross'
107 | run: |
108 | sudo apt-get install -y --no-install-recommends musl-tools
109 | echo "CC=musl-gcc" >> $GITHUB_ENV
110 | echo "AR=ar" >> $GITHUB_ENV
111 |
112 | - name: Build command line binary
113 | if: ${{ !matrix.build.gui }}
114 | run: ${{ matrix.build.cargo }} build -p crusader --target ${{ matrix.build.target }} --release
115 | working-directory: src
116 | env:
117 | RUSTFLAGS: "-C target-feature=+crt-static"
118 |
119 | - name: Build
120 | if: matrix.build.gui
121 | run: ${{ matrix.build.cargo }} build --target ${{ matrix.build.target }} --release
122 | working-directory: src
123 | env:
124 | RUSTFLAGS: "-C target-feature=+crt-static"
125 |
126 | - name: Build output
127 | shell: bash
128 | run: |
129 | staging="Crusader-${{ matrix.build.friendly }}"
130 | mkdir -p "$staging"
131 | cp src/target/${{ matrix.build.target }}/release/crusader${{ matrix.build.exe_postfix }} "$staging/"
132 |
133 | - name: Copy GUI binary
134 | if: matrix.build.gui
135 | shell: bash
136 | run: |
137 | cp src/target/${{ matrix.build.target }}/release/crusader-gui${{ matrix.build.exe_postfix }} "crusader-${{ matrix.build.friendly }}/"
138 |
139 | - name: Archive output
140 | if: matrix.build.os == 'windows-latest'
141 | shell: bash
142 | run: |
143 | staging="Crusader-${{ matrix.build.friendly }}"
144 | 7z a "$staging.zip" "$staging"
145 | echo "ASSET=$staging.zip" >> $GITHUB_ENV
146 |
147 | - name: Archive output
148 | if: matrix.build.os != 'windows-latest'
149 | shell: bash
150 | run: |
151 | staging="Crusader-${{ matrix.build.friendly }}"
152 | tar czf "$staging.tar.gz" "$staging"
153 | echo "ASSET=$staging.tar.gz" >> $GITHUB_ENV
154 |
155 | - name: Upload archive
156 | uses: actions/upload-release-asset@v1.0.2
157 | env:
158 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
159 | with:
160 | upload_url: ${{ needs.create-release.outputs.upload_url }}
161 | asset_name: ${{ env.ASSET }}
162 | asset_path: ${{ env.ASSET }}
163 | asset_content_type: application/octet-stream
164 |
165 | release-android-assets:
166 | name: Android APK
167 | needs: create-release
168 | runs-on: ubuntu-latest
169 | steps:
170 | - uses: actions/checkout@v2
171 |
172 | - uses: actions-rs/toolchain@v1
173 | with:
174 | toolchain: stable
175 | override: true
176 |
177 | - name: Install Rust targets
178 | run: >
179 | rustup target add
180 | aarch64-linux-android
181 | armv7-linux-androideabi
182 | x86_64-linux-android
183 | i686-linux-android
184 |
185 | - name: Install cargo-ndk
186 | run: cargo install cargo-ndk
187 |
188 | - name: Setup Java
189 | uses: actions/setup-java@v3
190 | with:
191 | distribution: 'temurin'
192 | java-version: '17'
193 |
194 | - name: Setup Android SDK
195 | uses: android-actions/setup-android@v2
196 |
197 | - name: Build Android Rust crates
198 | working-directory: android
199 | run: >
200 | cargo ndk
201 | -t arm64-v8a
202 | -t armeabi-v7a
203 | -t x86_64
204 | -t x86
205 | -o app/src/main/jniLibs/ -- build --release
206 |
207 | - name: Decode Keystore
208 | env:
209 | ENCODED_STRING: ${{ secrets.KEYSTORE }}
210 | run: echo "$ENCODED_STRING" | base64 -di > ../android.keystore
211 |
212 | - name: Build Android APK
213 | working-directory: android
214 | run: ./gradlew build
215 | env:
216 | SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
217 | SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
218 | SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
219 |
220 | - name: Upload APK
221 | uses: actions/upload-release-asset@v1.0.2
222 | env:
223 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
224 | with:
225 | upload_url: ${{ needs.create-release.outputs.upload_url }}
226 | asset_name: Crusader-Android.apk
227 | asset_path: android/app/build/outputs/apk/release/app-release.apk
228 | asset_content_type: application/octet-stream
229 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /src/target
2 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # CHANGELOG
2 |
3 | The **Crusader Network Tester** measures network rates and latency
4 | in the presence of upload and download traffic.
5 | It produces plots of the traffic rates,
6 | latency and packet loss.
7 |
8 | This file lists the changes that have occurred since January 2024 in the project.
9 |
10 | ## Unreleased
11 |
12 | ## 0.3.2 - 2024-10-03
13 |
14 | * Fix saved raw data path printed after a test
15 | * Avoid duplicate legends when plotting transferred bytes
16 | * Make `--plot-transferred` increase default plot height
17 | * Fix unique output path generation
18 |
19 | ## 0.3.1 - 2024-09-30
20 |
21 | * Increase samples used for clock synchronization and idle latency measurement
22 | * Clock synchronization now uses the average of the lowest 1/3rd of samples
23 | * Adjust for clock drift in tests
24 | * Fix connecting to servers on non-standard port with peers
25 | * Make discovery more robust by sending multiple packets
26 |
27 | ## 0.3 - 2024-09-16
28 |
29 | * Show throughput, latency, and packet loss summaries in plots and with the `test` command
30 | * Rename both option to bidirectional
31 | * Rename `--latency-peer-server` to `--latency-peer-address`
32 | * Continuous clock synchronization with the latency monitor
33 | * Support opening result files in the GUI by drag and drop
34 | * Add `--out-name` command line option to specify result filename prefix
35 | * Change filename prefix for both raw result and plots to `test`
36 | * Add file dialog to save options in GUI
37 | * Add buttons to save and load from the `crusader-results` folder in GUI
38 | * Add an `export` command line command to convert result files to JSON
39 | * Change timeout when connecting a peer to the server to 8 seconds
40 | * Hide advanced parameters in GUI
41 | * Add a reset parameters button in GUI
42 | * Add an option to measure latency-only for the client in the GUI
43 | * Don't allow peers to connect with the regular server
44 | * Added average lines in GUI
45 |
46 | ## 0.2 - 2024-08-29
47 |
48 | * Added support for local discovery of server and peers using UDP port 35483
49 | * The `test` command line option `--latency-peer` is renamed to `--latency-peer-server`.
50 | A new flag `--latency-peer` will instead search for a local peer.
51 | * Improved error messages
52 | * Fix date/time display in remote web page
53 | * Rename the `Latency` tab to `Monitor`
54 | * Change default streams from 16 to 8.
55 | * Change default throughput sample interval from 20 ms to 60 ms.
56 | * Change default load duration from 5 s to 10 s.
57 | * Change default grace duration from 5 s to 10 s.
58 | * Fix serving from link-local interfaces on Linux
59 | * Fix peers on link-local interfaces
60 | * Show download and upload plots for aggregate tests in the GUI
61 | * Added a shortcut (space) to stop the latency monitor
62 | * Change timeout when connecting to servers and peers to 8 seconds
63 | * Added average lines to the plot output
64 | * Show interface IPs when starting servers
65 |
66 | ## 0.1 - 2024-08-21
67 |
68 | * Added `crusader remote` command to start a web server listening on port 35482.
69 | It allows starting tests on a separate machine and
70 | displays the resulting charts in the web page.
71 | * Use system fonts in GUI
72 | * Improved error handling and error messages
73 | * Added `--idle` option to the client to test without traffic
74 | * Save results in a `crusader-results` folder
75 | * Allow building of a server-only binary
76 | * Generated files will use a YYYY-MM-DD HH.MM.SS format
77 | * Rename bandwidth to throughput
78 | * Rename sample rate to sample interval
79 | * Rename `Both` to `Aggregate` and `Total` to `Round-trip` in plots
80 |
81 | ## 0.0.12 - 2024-07-31
82 |
83 | * Create UDP server for each server IP (fixes #22)
84 | * Improved error handling for log messages
85 | * Changed date format to use YYYY-MM-DD in logs
86 |
87 | ## 0.0.11 - 2024-07-29
88 |
89 | * Log file includes timestamps and version number
90 | * Added peer latency measurements
91 | * Added version to title bar of GUI
92 | * Added `plot_max_bandwidth` and `plot_max_latency` command line options
93 |
94 | ## 0.0.10 - 2024-01-09
95 |
96 | * Specify plot title
97 | * Ignore ENOBUFS error
98 |
--------------------------------------------------------------------------------
/LICENSE-APACHE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
--------------------------------------------------------------------------------
/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | Permission is hereby granted, free of charge, to any
2 | person obtaining a copy of this software and associated
3 | documentation files (the "Software"), to deal in the
4 | Software without restriction, including without
5 | limitation the rights to use, copy, modify, merge,
6 | publish, distribute, sublicense, and/or sell copies of
7 | the Software, and to permit persons to whom the Software
8 | is furnished to do so, subject to the following
9 | conditions:
10 |
11 | The above copyright notice and this permission notice
12 | shall be included in all copies or substantial portions
13 | of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23 | DEALINGS IN THE SOFTWARE.
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Crusader Network Tester
2 |
3 | [](https://github.com/Zoxc/crusader/releases)
4 | [](https://hub.docker.com/r/zoxc/crusader)
5 | [](https://github.com/Zoxc/crusader/blob/master/LICENSE-MIT)
6 | [](https://github.com/Zoxc/crusader/blob/master/LICENSE-APACHE)
7 |
8 | 
9 |
10 | The **Crusader Network Tester** measures network throughput, latency and packet loss
11 | in the presence of upload and download traffic.
12 | It also incorporates a continuous latency tester for
13 | monitoring background responsiveness.
14 |
15 | Crusader makes throughput measurements using TCP on port 35481
16 | and latency tests using UDP port 35481.
17 | The remote web server option uses TCP port 35482.
18 | Local server discovery uses UDP port 35483.
19 |
20 | **Pre-built binaries** for Windows, Mac, Linux,
21 | and Android are available on the
22 | [Releases](https://github.com/Zoxc/crusader/releases) page.
23 | The GUI is not prebuilt for Linux and must be built from source.
24 |
25 | **Documentation** See the [Documentation](#documentation)
26 | section below.
27 |
28 | **Status:** The latest Crusader release version is shown above.
29 | The [pre-built binaries](https://github.com/Zoxc/crusader/releases)
30 | always provide the latest version.
31 | See the [CHANGELOG.md](./CHANGELOG.md) file for details.
32 |
33 | ## Crusader GUI
34 |
35 | A test run requires two separate computers,
36 | both running Crusader:
37 | a **server** that listens for connections, and
38 | a **client** that initiates the test.
39 |
40 | The Crusader GUI incorporates both the server and
41 | the client and allows you to interact with results.
42 | To use it, download the proper binary from the
43 | [Releases](https://github.com/Zoxc/crusader/releases) page.
44 |
45 | When you open the `crusader-gui` you see this window.
46 | Enter the address of another computer that's
47 | running the Crusader server, then click **Start test**.
48 | When the test is complete, the **Result** tab shows a
49 | chart like the second image below.
50 |
51 | An easy way to use Crusader is to download
52 | the Crusader GUI onto two computers, then
53 | start the server on one computer, and the client on the other.
54 |
55 | 
56 |
57 | The Crusader GUI has five tabs:
58 |
59 | * **Client tab**
60 | Runs the Crusader client program.
61 | The options shown above are described in the
62 | [Command-line options](./docs/CLI.md) page.
63 |
64 | * **Server tab**
65 | Runs the Crusader server, listening for connections from other clients
66 |
67 | * **Remote tab**
68 | Starts a webserver (default port 35482).
69 | A browser that connects to that port can initiate
70 | a test to a Crusader server.
71 |
72 | * **Monitor tab**
73 | Continually displays the latency to the selected
74 | Crusader server until stopped.
75 |
76 | * **Result tab**
77 | Displays the result of the most recent client run
78 |
79 | ## The Result Tab
80 |
81 | 
82 |
83 | A Crusader test creates three bursts of traffic.
84 | By default, it generates ten seconds each of
85 | download only, upload only, then bi-directional traffic.
86 | Each burst is separated by several seconds of idle time.
87 |
88 | The Crusader Result tab displays the results of the test with
89 | three plots (see image above):
90 |
91 | * The **Throughput** plot shows the bursts of traffic.
92 | Green is download (from server to client),
93 | blue is upload, and
94 | the purple line is the instantaneous
95 | sum of the download plus upload.
96 |
97 | * The **Latency** plot shows the corresponding latency.
98 | Green shows the (uni-directional) time from the server to the client.
99 | Blue is the (uni-directional) time from the client to the server.
100 | Black shows the sum from the client to the server
101 | and back (round-trip time).
102 |
103 | * The **Packet Loss** plot has green and blue marks
104 | that indicate times when packets were lost.
105 |
106 | For more details, see the
107 | [Understanding Crusader Results](./docs/RESULTS.md) page.
108 |
109 | ## Documentation
110 |
111 | * [This README](./README.md)
112 | * [Understanding Crusader Results](./docs/RESULTS.md)
113 | * [Local Testing](./docs/LOCAL_TESTS.md)
114 | * [Command-line Options](./docs/CLI.md)
115 | * [Building Crusader from source](./docs/BUILDING.md)
116 | * [Troubleshooting](./docs/TROUBLESHOOTING.md)
117 | * [Docker container](https://hub.docker.com/r/zoxc/crusader)
118 | for the server is available on
119 | [dockerhub](https://hub.docker.com/r/zoxc/crusader).
120 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | /target
3 | /app/build
4 | /app/src/main/jniLibs
--------------------------------------------------------------------------------
/android/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "crusader-android"
3 | version = "0.1.0"
4 | edition = "2021"
5 | resolver = "2"
6 |
7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8 |
9 | [dependencies]
10 | log = "0.4"
11 | eframe = { version = "0.28.1", features = ["wgpu"] }
12 | crusader-gui-lib = { path = "../src/crusader-gui-lib" }
13 | crusader-lib = { path = "../src/crusader-lib" }
14 | winit = "0.29.15"
15 | jni = "0.19.0"
16 | ndk-context = "0.1"
17 |
18 | [target.'cfg(target_os = "android")'.dependencies]
19 | android_logger = "0.11.0"
20 | android-activity = { version = "0.5", features = ["game-activity"] }
21 |
22 | [patch.crates-io]
23 | winit = { git = "https://github.com/Zoxc/winit", branch = "crusader2" }
24 | egui = { git = "https://github.com/Zoxc/egui", branch = "crusader2" }
25 | epaint = { git = "https://github.com/Zoxc/egui", branch = "crusader2" }
26 | emath = { git = "https://github.com/Zoxc/egui", branch = "crusader2" }
27 | egui_plot = { git = "https://github.com/Zoxc/egui_plot", branch = "crusader" }
28 |
29 | [lib]
30 | name = "main"
31 | crate-type = ["cdylib"]
32 |
--------------------------------------------------------------------------------
/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | }
4 |
5 | android {
6 | compileSdk 31
7 |
8 | defaultConfig {
9 | applicationId "zoxc.crusader"
10 | minSdk 28
11 | targetSdk 31
12 | versionCode 1
13 | versionName "1.0"
14 |
15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
16 | }
17 |
18 | signingConfigs {
19 | release {
20 | storeFile = file("../../../android.keystore")
21 | storePassword System.getenv("SIGNING_STORE_PASSWORD")
22 | keyAlias System.getenv("SIGNING_KEY_ALIAS")
23 | keyPassword System.getenv("SIGNING_KEY_PASSWORD")
24 | }
25 | }
26 |
27 | buildTypes {
28 | release {
29 | minifyEnabled false
30 | signingConfig signingConfigs.release
31 | }
32 | debug {
33 | minifyEnabled false
34 | //packagingOptions {
35 | // doNotStrip '**/*.so'
36 | //}
37 | //debuggable true
38 | }
39 | }
40 | compileOptions {
41 | sourceCompatibility JavaVersion.VERSION_1_8
42 | targetCompatibility JavaVersion.VERSION_1_8
43 | }
44 | }
45 |
46 | dependencies {
47 | implementation 'com.google.android.material:material:1.5.0'
48 | implementation "androidx.games:games-activity:2.0.2"
49 |
50 | // To use the Games Controller Library
51 | //implementation "androidx.games:games-controller:1.1.0"
52 |
53 | // To use the Games Text Input Library
54 | //implementation "androidx.games:games-text-input:1.1.0"
55 | }
56 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/main/java/zoxc/crusader/MainActivity.java:
--------------------------------------------------------------------------------
1 | package zoxc.crusader;
2 |
3 | import androidx.appcompat.app.AppCompatActivity;
4 | import androidx.core.view.WindowCompat;
5 | import androidx.core.view.WindowInsetsCompat;
6 | import androidx.core.view.WindowInsetsControllerCompat;
7 |
8 | import com.google.androidgamesdk.GameActivity;
9 |
10 | import android.os.Bundle;
11 | import android.content.pm.PackageManager;
12 | import android.os.Build.VERSION;
13 | import android.os.Build.VERSION_CODES;
14 | import android.os.Bundle;
15 | import android.view.View;
16 | import android.view.WindowManager;
17 | import android.util.Log;
18 | import android.content.Intent;
19 | import android.net.Uri;
20 | import android.app.Activity;
21 | import android.view.inputmethod.InputMethodManager;
22 | import android.provider.OpenableColumns;
23 | import android.database.Cursor;
24 | import android.content.Context;
25 |
26 | import java.io.ByteArrayOutputStream;
27 | import java.io.InputStream;
28 | import java.io.OutputStream;
29 |
30 | public class MainActivity extends GameActivity {
31 | static {
32 | System.loadLibrary("main");
33 | }
34 |
35 | public void showKeyboard(boolean show) {
36 | InputMethodManager input = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
37 | if (show) {
38 | input.showSoftInput(getWindow().getDecorView().getRootView(), 0);
39 | } else {
40 | input.hideSoftInputFromWindow(getWindow().getDecorView().getRootView().getWindowToken(), 0);
41 | }
42 | }
43 |
44 | public void loadFile() {
45 | Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
46 | intent.addCategory(Intent.CATEGORY_OPENABLE);
47 | intent.setType("*/*");
48 | startActivityForResult(intent, ACTIVITY_LOAD_FILE);
49 | }
50 |
51 | public void saveFile(boolean image, String name, byte[] data) {
52 | Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
53 | intent.addCategory(Intent.CATEGORY_OPENABLE);
54 | if (image) {
55 | intent.setType("image/png");
56 | } else {
57 | intent.setType("application/octet-stream");
58 | }
59 | intent.putExtra(Intent.EXTRA_TITLE, name);
60 | saveFileData = data;
61 | saveImage = image;
62 | startActivityForResult(intent, ACTIVITY_CREATE_FILE);
63 | }
64 |
65 | private byte[] saveFileData = null;
66 | private boolean saveImage;
67 |
68 | private static final int ACTIVITY_LOAD_FILE = 1;
69 | private static final int ACTIVITY_CREATE_FILE = 2;
70 |
71 | static native void fileLoaded(String name, byte[] data);
72 |
73 | static native void fileSaved(boolean image, String name);
74 |
75 | @Override
76 | public void onActivityResult(int requestCode, int resultCode,
77 | Intent resultData) {
78 | super.onActivityResult(requestCode, resultCode, resultData);
79 |
80 | if (requestCode == ACTIVITY_CREATE_FILE) {
81 | if (resultCode == Activity.RESULT_OK && resultData != null) {
82 | Uri uri = resultData.getData();
83 | String name = getName(uri);
84 | try {
85 | OutputStream stream = getContentResolver().openOutputStream(uri);
86 | stream.write(saveFileData);
87 | stream.close();
88 | fileSaved(saveImage, name);
89 | }
90 | catch(Exception e) {}
91 | }
92 | saveFileData = null;
93 | }
94 |
95 | if (requestCode == ACTIVITY_LOAD_FILE
96 | && resultCode == Activity.RESULT_OK
97 | && resultData != null) {
98 | Uri uri = resultData.getData();
99 | String name = getName(uri);
100 |
101 | try {
102 | InputStream stream = getContentResolver().openInputStream(uri);
103 | ByteArrayOutputStream buffer = new ByteArrayOutputStream();
104 | int read;
105 | byte[] byte_buffer = new byte[0x1000];
106 | while ((read = stream.read(byte_buffer, 0, byte_buffer.length)) != -1) {
107 | buffer.write(byte_buffer, 0, read);
108 | }
109 | stream.close();
110 | byte[] data = buffer.toByteArray();
111 | fileLoaded(name, data);
112 | }
113 | catch(Exception e) {}
114 | }
115 | }
116 |
117 | public String getName(Uri uri) {
118 | Cursor cursor = getContentResolver().query(uri, null, null, null, null, null);
119 | String name = "";
120 | try {
121 | if (cursor != null && cursor.moveToFirst()) {
122 | int column = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
123 | if (column != -1) {
124 | name = cursor.getString(column);
125 | }
126 | return name;
127 | }
128 | } finally {
129 | cursor.close();
130 | }
131 | return name;
132 | }
133 | }
--------------------------------------------------------------------------------
/android/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 | #FFE6E6E6
11 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
18 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | plugins {
3 | id 'com.android.application' version '7.1.2' apply false
4 | id 'com.android.library' version '7.1.2' apply false
5 | }
6 |
7 | task clean(type: Delete) {
8 | delete rootProject.buildDir
9 | }
10 |
--------------------------------------------------------------------------------
/android/debugInstall.ps1:
--------------------------------------------------------------------------------
1 | $ErrorActionPreference = "Stop"
2 | cargo ndk -t arm64-v8a -o app/src/main/jniLibs/ -- build --release
3 | if ($lastexitcode -ne 0) {
4 | throw "Error"
5 | }
6 |
7 | ./gradlew.bat buildDebug
8 | if ($lastexitcode -ne 0) {
9 | throw "Error"
10 | }
11 |
12 | ./gradlew.bat installDebug
13 | if ($lastexitcode -ne 0) {
14 | throw "Error"
15 | }
16 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Enables namespacing of each library's R class so that its R class includes only the
19 | # resources declared in the library itself and none from the library's dependencies,
20 | # thereby reducing the size of the R class for that library
21 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zoxc/crusader/5d1e6e76fb9b2e6e618c72d2fe9f99dffac5abab/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon May 02 15:39:12 BST 2022
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/android/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | google()
5 | mavenCentral()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 |
16 | include ':app'
17 |
--------------------------------------------------------------------------------
/android/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![allow(
2 | clippy::field_reassign_with_default,
3 | clippy::option_map_unit_fn,
4 | clippy::missing_safety_doc
5 | )]
6 |
7 | use crusader_gui_lib::Tester;
8 | use crusader_lib::file_format::RawResult;
9 | use eframe::egui::{self, vec2, Align, FontFamily, Layout};
10 | use jni::{
11 | objects::{JClass, JObject, JString},
12 | sys::{jboolean, jbyteArray},
13 | JNIEnv,
14 | };
15 | use std::{
16 | error::Error,
17 | io::Cursor,
18 | path::Path,
19 | sync::{Arc, Mutex},
20 | };
21 |
22 | #[cfg(target_os = "android")]
23 | use {
24 | android_activity::AndroidApp,
25 | crusader_lib::test::PlotConfig,
26 | eframe::{NativeOptions, Renderer, Theme},
27 | log::Level,
28 | std::fs,
29 | winit::platform::android::EventLoopBuilderExtAndroid,
30 | };
31 |
32 | struct App {
33 | tester: Tester,
34 | keyboard_shown: bool,
35 | }
36 |
37 | impl eframe::App for App {
38 | fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
39 | use eframe::egui::FontFamily::Proportional;
40 | use eframe::egui::FontId;
41 | use eframe::egui::TextStyle::*;
42 |
43 | let mut style = ctx.style();
44 | let style_ = Arc::make_mut(&mut style);
45 | style_.spacing.button_padding = vec2(10.0, 0.0);
46 | style_.spacing.interact_size.y = 40.0;
47 | style_.spacing.item_spacing = vec2(10.0, 10.0);
48 |
49 | style_.text_styles = [
50 | (Heading, FontId::new(26.0, Proportional)),
51 | (Body, FontId::new(16.0, Proportional)),
52 | (Monospace, FontId::new(16.0, FontFamily::Monospace)),
53 | (Button, FontId::new(16.0, Proportional)),
54 | (Small, FontId::new(16.0, Proportional)),
55 | ]
56 | .into();
57 |
58 | ctx.set_style(style);
59 |
60 | egui::CentralPanel::default().show(ctx, |ui| {
61 | let mut rect = ui.max_rect();
62 | rect.set_top(rect.top() + 40.0);
63 | rect.set_height(rect.height() - 60.0);
64 | let mut ui = ui.child_ui(rect, Layout::left_to_right(Align::Center), None);
65 | ui.vertical(|ui| {
66 | ui.heading("Crusader Network Benchmark");
67 | ui.separator();
68 |
69 | SAVED_FILE.lock().unwrap().take().map(|(image, name)| {
70 | if !image {
71 | self.tester.save_raw(Path::new(&name).to_owned());
72 | }
73 | });
74 |
75 | LOADED_FILE.lock().unwrap().take().map(|(name, data)| {
76 | RawResult::load_from_reader(Cursor::new(data))
77 | .map(|data| self.tester.load_file(Path::new(&name).to_owned(), data));
78 | });
79 |
80 | self.tester.show(ctx, ui);
81 | });
82 | });
83 |
84 | if ctx.wants_keyboard_input() != self.keyboard_shown {
85 | show_keyboard(ctx.wants_keyboard_input()).unwrap();
86 | self.keyboard_shown = ctx.wants_keyboard_input();
87 | }
88 | }
89 | }
90 |
91 | fn show_keyboard(show: bool) -> Result<(), Box> {
92 | let context = ndk_context::android_context();
93 |
94 | let vm = unsafe { jni::JavaVM::from_raw(context.vm().cast())? };
95 |
96 | let activity: JObject = (context.context() as jni::sys::jobject).into();
97 |
98 | let env = vm.attach_current_thread()?;
99 |
100 | env.call_method(activity, "showKeyboard", "(Z)V", &[show.into()])?
101 | .v()?;
102 | Ok(())
103 | }
104 |
105 | fn save_file(image: bool, name: String, data: Vec) -> Result<(), Box> {
106 | let context = ndk_context::android_context();
107 | let vm = unsafe { jni::JavaVM::from_raw(context.vm().cast())? };
108 | let activity: JObject = (context.context() as jni::sys::jobject).into();
109 | let env = vm.attach_current_thread()?;
110 | env.call_method(
111 | activity,
112 | "saveFile",
113 | "(ZLjava/lang/String;[B)V",
114 | &[
115 | image.into(),
116 | env.new_string(name).unwrap().into(),
117 | env.byte_array_from_slice(&data).unwrap().into(),
118 | ],
119 | )?
120 | .v()?;
121 | Ok(())
122 | }
123 |
124 | static SAVED_FILE: Mutex