Compare commits

..

4 Commits

Author SHA1 Message Date
Will Greenberg b8aa1c13a8 trim test case
These measurement packets are of a fixed length, and the SCAT test case
had excess data
2026-06-04 13:18:03 -07:00
Will Greenberg 18bff68c88 lib/gsmtap_parser: downgrade unsupported log to debug msg
Previously this was an error message to help underscore when a device
was sending unexpected messages, but now that we're receiving
measurement logs which have no place in GSMTAP frames, it's expected to
skip some log messages.
2026-06-04 12:14:52 -07:00
Will Greenberg 8383342b34 lib/diag: add ML1 Serving/Neighbor cell measurement
This adds support for two new log messages:

* 0xB17F: Serving Cell Measurement and Evaluation
* 0xB180: Neighboring Cells Measurements

With these, we can retrieve realtime RSRQ/RSRP values for the UE's
current cell as well as neighboring ones.
2026-06-04 12:14:52 -07:00
Will Greenberg 973557e810 lib/diag.rs refactor
This splits diag.rs, which was growing way too big for my taste, into a
number of submodules. This should help us compartmentalize tests better,
as well as use mod namespaces to shorten our struct/enum names.
2026-06-04 12:14:52 -07:00
25 changed files with 901 additions and 1018 deletions
+1 -2
View File
@@ -44,12 +44,11 @@ jobs:
# We rebuild everything if any of these conditions hold:
# * We are on main
# * The run was triggered by a tag
# * Changes are made to github workflows
# * A cargo-workspace file changed (lockfile or .cargo), as that could affect any crate anywhere
# * Something from the script or dist folder changed (could be gated to installer, but some scripts like build_wpa_supplicant are part of the build process)
# * #build-all was used by the user to explicitly ask for this
if [ ${GITHUB_REF} = 'refs/heads/main' ] || [ ${GITHUB_REF_TYPE} = 'tag' ] || git diff --name-only $lcommit..HEAD | grep -qe ^.github/workflows/ -e ^.cargo -e '^Cargo\.lock$' -e '^Cargo\.toml$' -e ^dist/ -e ^scripts/ || git log -1 --format='%s %b' | grep -qF '#build-all'
if [ ${GITHUB_REF} = 'refs/heads/main' ] || git diff --name-only $lcommit..HEAD | grep -qe ^.github/workflows/ -e ^.cargo -e '^Cargo\.lock$' -e '^Cargo\.toml$' -e ^dist/ -e ^scripts/ || git log -1 --format='%s %b' | grep -qF '#build-all'
then
echo "building everything"
echo code_count=forced >> "$GITHUB_OUTPUT"
+3 -4
View File
@@ -1,9 +1,8 @@
# To learn how to use this workflow, please read CONTRIBUTING.md.
# To use: navigate on Github to Actions, select "Release rayhunter" on the left, click "Run workflow" > "Run workflow" on the right.
# https://github.com/EFForg/rayhunter/actions/workflows/release.yml
name: Release rayhunter
on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
workflow_dispatch:
env:
GH_TOKEN: ${{ github.token }}
+6 -4
View File
@@ -75,9 +75,11 @@ You can read our [full policy](https://www.eff.org/about/opportunities/volunteer
This one is for maintainers of Rayhunter.
1. Make a PR changing the versions in `Cargo.toml` and other files.
This can be done by running `scripts/set-versions.sh VERSION_NUM`.
This could be automated better but right now it's manual. You can do this easily with sed:
`sed -i "" -E 's/x.x.x/y.y.y/g' */Cargo.toml installer-gui/src-tauri/Cargo.toml`
2. Merge the PR, make a tag, and push the tag to GitHub. Pushing the tag should
trigger the [release workflow](https://github.com/EFForg/rayhunter/actions/workflows/release.yml).
2. Merge PR and make a tag.
3. Write changelog, edit it into the release, announce on mattermost.
3. [Run release workflow.](https://github.com/EFForg/rayhunter/actions/workflows/release.yml)
4. Write changelog, edit it into the release, announce on mattermost.
Generated
-2
View File
@@ -2947,9 +2947,7 @@ name = "installer-gui"
version = "0.11.2"
dependencies = [
"anyhow",
"clap",
"installer",
"log",
"serde",
"serde_json",
"shlex",
+3 -11
View File
@@ -7,18 +7,10 @@ use crate::config;
use crate::display::DisplayState;
pub fn update_ui(
task_tracker: &TaskTracker,
_task_tracker: &TaskTracker,
_config: &config::Config,
shutdown_token: CancellationToken,
mut ui_update_rx: Receiver<DisplayState>,
_shutdown_token: CancellationToken,
_ui_update_rx: Receiver<DisplayState>,
) {
info!("Headless mode, not spawning UI.");
task_tracker.spawn(async move {
loop {
tokio::select! {
_ = shutdown_token.cancelled() => break,
_ = ui_update_rx.recv() => {}
}
}
});
}
+89 -107
View File
@@ -27,7 +27,7 @@
"tailwindcss": "^4.2.2",
"typescript": "^6.0.3",
"typescript-eslint": "^8.59.0",
"vite": "^8.0.16",
"vite": "^8.0.10",
"vitest": "^4.1.5"
}
},
@@ -329,9 +329,9 @@
}
},
"node_modules/@oxc-project/types": {
"version": "0.133.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz",
"integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==",
"version": "0.127.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz",
"integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==",
"dev": true,
"license": "MIT",
"funding": {
@@ -346,9 +346,9 @@
"license": "MIT"
},
"node_modules/@rolldown/binding-android-arm64": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz",
"integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==",
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz",
"integrity": "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==",
"cpu": [
"arm64"
],
@@ -363,9 +363,9 @@
}
},
"node_modules/@rolldown/binding-darwin-arm64": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz",
"integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==",
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz",
"integrity": "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==",
"cpu": [
"arm64"
],
@@ -380,9 +380,9 @@
}
},
"node_modules/@rolldown/binding-darwin-x64": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz",
"integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==",
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz",
"integrity": "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==",
"cpu": [
"x64"
],
@@ -397,9 +397,9 @@
}
},
"node_modules/@rolldown/binding-freebsd-x64": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz",
"integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==",
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz",
"integrity": "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==",
"cpu": [
"x64"
],
@@ -414,9 +414,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz",
"integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==",
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz",
"integrity": "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==",
"cpu": [
"arm"
],
@@ -431,16 +431,13 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-gnu": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz",
"integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==",
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz",
"integrity": "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -451,16 +448,13 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-musl": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz",
"integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==",
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz",
"integrity": "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -471,16 +465,13 @@
}
},
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz",
"integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==",
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz",
"integrity": "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==",
"cpu": [
"ppc64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -491,16 +482,13 @@
}
},
"node_modules/@rolldown/binding-linux-s390x-gnu": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz",
"integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==",
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz",
"integrity": "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==",
"cpu": [
"s390x"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -511,16 +499,13 @@
}
},
"node_modules/@rolldown/binding-linux-x64-gnu": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz",
"integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==",
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.17.tgz",
"integrity": "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -531,16 +516,13 @@
}
},
"node_modules/@rolldown/binding-linux-x64-musl": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz",
"integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==",
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.17.tgz",
"integrity": "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -551,9 +533,9 @@
}
},
"node_modules/@rolldown/binding-openharmony-arm64": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz",
"integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==",
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz",
"integrity": "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==",
"cpu": [
"arm64"
],
@@ -568,9 +550,9 @@
}
},
"node_modules/@rolldown/binding-wasm32-wasi": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz",
"integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==",
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz",
"integrity": "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==",
"cpu": [
"wasm32"
],
@@ -587,9 +569,9 @@
}
},
"node_modules/@rolldown/binding-win32-arm64-msvc": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz",
"integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==",
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz",
"integrity": "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==",
"cpu": [
"arm64"
],
@@ -604,9 +586,9 @@
}
},
"node_modules/@rolldown/binding-win32-x64-msvc": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz",
"integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==",
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz",
"integrity": "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==",
"cpu": [
"x64"
],
@@ -621,9 +603,9 @@
}
},
"node_modules/@rolldown/pluginutils": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
"integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz",
"integrity": "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==",
"dev": true,
"license": "MIT"
},
@@ -2564,9 +2546,9 @@
"license": "MIT"
},
"node_modules/nanoid": {
"version": "3.3.12",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
"integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"dev": true,
"funding": [
{
@@ -2698,9 +2680,9 @@
}
},
"node_modules/postcss": {
"version": "8.5.15",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
"integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
"version": "8.5.10",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz",
"integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==",
"dev": true,
"funding": [
{
@@ -2718,7 +2700,7 @@
],
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.12",
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
@@ -2896,14 +2878,14 @@
}
},
"node_modules/rolldown": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz",
"integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==",
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.17.tgz",
"integrity": "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@oxc-project/types": "=0.133.0",
"@rolldown/pluginutils": "^1.0.0"
"@oxc-project/types": "=0.127.0",
"@rolldown/pluginutils": "1.0.0-rc.17"
},
"bin": {
"rolldown": "bin/cli.mjs"
@@ -2912,21 +2894,21 @@
"node": "^20.19.0 || >=22.12.0"
},
"optionalDependencies": {
"@rolldown/binding-android-arm64": "1.0.3",
"@rolldown/binding-darwin-arm64": "1.0.3",
"@rolldown/binding-darwin-x64": "1.0.3",
"@rolldown/binding-freebsd-x64": "1.0.3",
"@rolldown/binding-linux-arm-gnueabihf": "1.0.3",
"@rolldown/binding-linux-arm64-gnu": "1.0.3",
"@rolldown/binding-linux-arm64-musl": "1.0.3",
"@rolldown/binding-linux-ppc64-gnu": "1.0.3",
"@rolldown/binding-linux-s390x-gnu": "1.0.3",
"@rolldown/binding-linux-x64-gnu": "1.0.3",
"@rolldown/binding-linux-x64-musl": "1.0.3",
"@rolldown/binding-openharmony-arm64": "1.0.3",
"@rolldown/binding-wasm32-wasi": "1.0.3",
"@rolldown/binding-win32-arm64-msvc": "1.0.3",
"@rolldown/binding-win32-x64-msvc": "1.0.3"
"@rolldown/binding-android-arm64": "1.0.0-rc.17",
"@rolldown/binding-darwin-arm64": "1.0.0-rc.17",
"@rolldown/binding-darwin-x64": "1.0.0-rc.17",
"@rolldown/binding-freebsd-x64": "1.0.0-rc.17",
"@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17",
"@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17",
"@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17",
"@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17",
"@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17",
"@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17",
"@rolldown/binding-linux-x64-musl": "1.0.0-rc.17",
"@rolldown/binding-openharmony-arm64": "1.0.0-rc.17",
"@rolldown/binding-wasm32-wasi": "1.0.0-rc.17",
"@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17",
"@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17"
}
},
"node_modules/sade": {
@@ -3201,9 +3183,9 @@
}
},
"node_modules/tinyglobby": {
"version": "0.2.17",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
"integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
"version": "0.2.16",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
"integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3334,17 +3316,17 @@
"license": "MIT"
},
"node_modules/vite": {
"version": "8.0.16",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz",
"integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==",
"version": "8.0.10",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.10.tgz",
"integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==",
"dev": true,
"license": "MIT",
"dependencies": {
"lightningcss": "^1.32.0",
"picomatch": "^4.0.4",
"postcss": "^8.5.15",
"rolldown": "1.0.3",
"tinyglobby": "^0.2.17"
"postcss": "^8.5.10",
"rolldown": "1.0.0-rc.17",
"tinyglobby": "^0.2.16"
},
"bin": {
"vite": "bin/vite.js"
@@ -3360,7 +3342,7 @@
},
"peerDependencies": {
"@types/node": "^20.19.0 || >=22.12.0",
"@vitejs/devtools": "^0.1.18",
"@vitejs/devtools": "^0.1.0",
"esbuild": "^0.27.0 || ^0.28.0",
"jiti": ">=1.21.0",
"less": "^4.0.0",
+1 -1
View File
@@ -34,7 +34,7 @@
"tailwindcss": "^4.2.2",
"typescript": "^6.0.3",
"typescript-eslint": "^8.59.0",
"vite": "^8.0.16",
"vite": "^8.0.10",
"vitest": "^4.1.5"
}
}
+104 -122
View File
@@ -29,13 +29,13 @@
"svelte-check": "^4.4.6",
"typescript": "~6.0.3",
"typescript-eslint": "^8.58.2",
"vite": "^8.0.16"
"vite": "^8.0.9"
}
},
"node_modules/@emnapi/core": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
"integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
"version": "1.9.2",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz",
"integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==",
"license": "MIT",
"optional": true,
"dependencies": {
@@ -44,9 +44,9 @@
}
},
"node_modules/@emnapi/runtime": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
"version": "1.9.2",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz",
"integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==",
"license": "MIT",
"optional": true,
"dependencies": {
@@ -289,13 +289,13 @@
}
},
"node_modules/@napi-rs/wasm-runtime": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz",
"integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==",
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz",
"integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==",
"license": "MIT",
"optional": true,
"dependencies": {
"@tybys/wasm-util": "^0.10.2"
"@tybys/wasm-util": "^0.10.1"
},
"funding": {
"type": "github",
@@ -307,9 +307,9 @@
}
},
"node_modules/@oxc-project/types": {
"version": "0.133.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz",
"integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==",
"version": "0.126.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.126.0.tgz",
"integrity": "sha512-oGfVtjAgwQVVpfBrbtk4e1XDyWHRFta6BS3GWVzrF8xYBT2VGQAk39yJS/wFSMrZqoiCU4oghT3Ch0HaHGIHcQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/Boshen"
@@ -323,9 +323,9 @@
"license": "MIT"
},
"node_modules/@rolldown/binding-android-arm64": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz",
"integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==",
"version": "1.0.0-rc.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.16.tgz",
"integrity": "sha512-rhY3k7Bsae9qQfOtph2Pm2jZEA+s8Gmjoz4hhmx70K9iMQ/ddeae+xhRQcM5IuVx5ry1+bGfkvMn7D6MJggVSA==",
"cpu": [
"arm64"
],
@@ -339,9 +339,9 @@
}
},
"node_modules/@rolldown/binding-darwin-arm64": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz",
"integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==",
"version": "1.0.0-rc.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.16.tgz",
"integrity": "sha512-rNz0yK078yrNn3DrdgN+PKiMOW8HfQ92jQiXxwX8yW899ayV00MLVdaCNeVBhG/TbH3ouYVObo8/yrkiectkcQ==",
"cpu": [
"arm64"
],
@@ -355,9 +355,9 @@
}
},
"node_modules/@rolldown/binding-darwin-x64": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz",
"integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==",
"version": "1.0.0-rc.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.16.tgz",
"integrity": "sha512-r/OmdR00HmD4i79Z//xO06uEPOq5hRXdhw7nzkxQxwSavs3PSHa1ijntdpOiZ2mzOQ3fVVu8C1M19FoNM+dMUQ==",
"cpu": [
"x64"
],
@@ -371,9 +371,9 @@
}
},
"node_modules/@rolldown/binding-freebsd-x64": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz",
"integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==",
"version": "1.0.0-rc.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.16.tgz",
"integrity": "sha512-KcRE5w8h0OnjUatG8pldyD14/CQ5Phs1oxfR+3pKDjboHRo9+MkqQaiIZlZRpsxC15paeXme/I127tUa9TXJ6g==",
"cpu": [
"x64"
],
@@ -387,9 +387,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz",
"integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==",
"version": "1.0.0-rc.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.16.tgz",
"integrity": "sha512-bT0guA1bpxEJ/ZhTRniQf7rNF8ybvXOuWbNIeLABaV5NGjx4EtOWBTSRGWFU9ZWVkPOZ+HNFP8RMcBokBiZ0Kg==",
"cpu": [
"arm"
],
@@ -403,15 +403,12 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-gnu": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz",
"integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==",
"version": "1.0.0-rc.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.16.tgz",
"integrity": "sha512-+tHktCHWV8BDQSjemUqm/Jl/TPk3QObCTIjmdDy/nlupcujZghmKK2962LYrqFpWu+ai01AN/REOH3NEpqvYQg==",
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -422,15 +419,12 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-musl": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz",
"integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==",
"version": "1.0.0-rc.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.16.tgz",
"integrity": "sha512-3fPzdREH806oRLxpTWW1Gt4tQHs0TitZFOECB2xzCFLPKnSOy90gwA7P29cksYilFO6XVRY1kzga0cL2nRjKPg==",
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -441,15 +435,12 @@
}
},
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz",
"integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==",
"version": "1.0.0-rc.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.16.tgz",
"integrity": "sha512-EKwI1tSrLs7YVw+JPJT/G2dJQ1jl9qlTTTEG0V2Ok/RdOenRfBw2PQdLPyjhIu58ocdBfP7vIRN/pvMsPxs/AQ==",
"cpu": [
"ppc64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -460,15 +451,12 @@
}
},
"node_modules/@rolldown/binding-linux-s390x-gnu": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz",
"integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==",
"version": "1.0.0-rc.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.16.tgz",
"integrity": "sha512-Uknladnb3Sxqu6SEcqBldQyJUpk8NleooZEc0MbRBJ4inEhRYWZX0NJu12vNf2mqAq7gsofAxHrGghiUYjhaLQ==",
"cpu": [
"s390x"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -479,15 +467,12 @@
}
},
"node_modules/@rolldown/binding-linux-x64-gnu": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz",
"integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==",
"version": "1.0.0-rc.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.16.tgz",
"integrity": "sha512-FIb8+uG49sZBtLTn+zt1AJ20TqVcqWeSIyoVt0or7uAWesgKaHbiBh6OpA/k9v0LTt+PTrb1Lao133kP4uVxkg==",
"cpu": [
"x64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -498,15 +483,12 @@
}
},
"node_modules/@rolldown/binding-linux-x64-musl": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz",
"integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==",
"version": "1.0.0-rc.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.16.tgz",
"integrity": "sha512-RuERhF9/EgWxZEXYWCOaViUWHIboceK4/ivdtQ3R0T44NjLkIIlGIAVAuCddFxsZ7vnRHtNQUrt2vR2n2slB2w==",
"cpu": [
"x64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -517,9 +499,9 @@
}
},
"node_modules/@rolldown/binding-openharmony-arm64": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz",
"integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==",
"version": "1.0.0-rc.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.16.tgz",
"integrity": "sha512-mXcXnvd9GpazCxeUCCnZ2+YF7nut+ZOEbE4GtaiPtyY6AkhZWbK70y1KK3j+RDhjVq5+U8FySkKRb/+w0EeUwA==",
"cpu": [
"arm64"
],
@@ -533,17 +515,17 @@
}
},
"node_modules/@rolldown/binding-wasm32-wasi": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz",
"integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==",
"version": "1.0.0-rc.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.16.tgz",
"integrity": "sha512-3Q2KQxnC8IJOLqXmUMoYwyIPZU9hzRbnHaoV3Euz+VVnjZKcY8ktnNP8T9R4/GGQtb27C/UYKABxesKWb8lsvQ==",
"cpu": [
"wasm32"
],
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/core": "1.10.0",
"@emnapi/runtime": "1.10.0",
"@emnapi/core": "1.9.2",
"@emnapi/runtime": "1.9.2",
"@napi-rs/wasm-runtime": "^1.1.4"
},
"engines": {
@@ -551,9 +533,9 @@
}
},
"node_modules/@rolldown/binding-win32-arm64-msvc": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz",
"integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==",
"version": "1.0.0-rc.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.16.tgz",
"integrity": "sha512-tj7XRemQcOcFwv7qhpUxMTBbI5mWMlE4c1Omhg5+h8GuLXzyj8HviYgR+bB2DMDgRqUE+jiDleqSCRjx4aYk/Q==",
"cpu": [
"arm64"
],
@@ -567,9 +549,9 @@
}
},
"node_modules/@rolldown/binding-win32-x64-msvc": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz",
"integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==",
"version": "1.0.0-rc.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.16.tgz",
"integrity": "sha512-PH5DRZT+F4f2PTXRXR8uJxnBq2po/xFtddyabTJVJs/ZYVHqXPEgNIr35IHTEa6bpa0Q8Awg+ymkTaGnKITw4g==",
"cpu": [
"x64"
],
@@ -583,9 +565,9 @@
}
},
"node_modules/@rolldown/pluginutils": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
"integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
"version": "1.0.0-rc.16",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.16.tgz",
"integrity": "sha512-45+YtqxLYKDWQouLKCrpIZhke+nXxhsw+qAHVzHDVwttyBlHNBVs2K25rDXrZzhpTp9w1FlAlvweV1H++fdZoA==",
"license": "MIT"
},
"node_modules/@standard-schema/spec": {
@@ -1229,9 +1211,9 @@
}
},
"node_modules/@tybys/wasm-util": {
"version": "0.10.2",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz",
"integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==",
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
"integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
"license": "MIT",
"optional": true,
"dependencies": {
@@ -2629,9 +2611,9 @@
"license": "MIT"
},
"node_modules/nanoid": {
"version": "3.3.12",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
"integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"funding": [
{
"type": "github",
@@ -2753,9 +2735,9 @@
}
},
"node_modules/postcss": {
"version": "8.5.15",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
"integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
"version": "8.5.10",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz",
"integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==",
"funding": [
{
"type": "opencollective",
@@ -2772,7 +2754,7 @@
],
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.12",
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
@@ -2950,13 +2932,13 @@
}
},
"node_modules/rolldown": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz",
"integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==",
"version": "1.0.0-rc.16",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.16.tgz",
"integrity": "sha512-rzi5WqKzEZw3SooTt7cgm4eqIoujPIyGcJNGFL7iPEuajQw7vxMHUkXylu4/vhCkJGXsgRmxqMKXUpT6FEgl0g==",
"license": "MIT",
"dependencies": {
"@oxc-project/types": "=0.133.0",
"@rolldown/pluginutils": "^1.0.0"
"@oxc-project/types": "=0.126.0",
"@rolldown/pluginutils": "1.0.0-rc.16"
},
"bin": {
"rolldown": "bin/cli.mjs"
@@ -2965,21 +2947,21 @@
"node": "^20.19.0 || >=22.12.0"
},
"optionalDependencies": {
"@rolldown/binding-android-arm64": "1.0.3",
"@rolldown/binding-darwin-arm64": "1.0.3",
"@rolldown/binding-darwin-x64": "1.0.3",
"@rolldown/binding-freebsd-x64": "1.0.3",
"@rolldown/binding-linux-arm-gnueabihf": "1.0.3",
"@rolldown/binding-linux-arm64-gnu": "1.0.3",
"@rolldown/binding-linux-arm64-musl": "1.0.3",
"@rolldown/binding-linux-ppc64-gnu": "1.0.3",
"@rolldown/binding-linux-s390x-gnu": "1.0.3",
"@rolldown/binding-linux-x64-gnu": "1.0.3",
"@rolldown/binding-linux-x64-musl": "1.0.3",
"@rolldown/binding-openharmony-arm64": "1.0.3",
"@rolldown/binding-wasm32-wasi": "1.0.3",
"@rolldown/binding-win32-arm64-msvc": "1.0.3",
"@rolldown/binding-win32-x64-msvc": "1.0.3"
"@rolldown/binding-android-arm64": "1.0.0-rc.16",
"@rolldown/binding-darwin-arm64": "1.0.0-rc.16",
"@rolldown/binding-darwin-x64": "1.0.0-rc.16",
"@rolldown/binding-freebsd-x64": "1.0.0-rc.16",
"@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.16",
"@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.16",
"@rolldown/binding-linux-arm64-musl": "1.0.0-rc.16",
"@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.16",
"@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.16",
"@rolldown/binding-linux-x64-gnu": "1.0.0-rc.16",
"@rolldown/binding-linux-x64-musl": "1.0.0-rc.16",
"@rolldown/binding-openharmony-arm64": "1.0.0-rc.16",
"@rolldown/binding-wasm32-wasi": "1.0.0-rc.16",
"@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.16",
"@rolldown/binding-win32-x64-msvc": "1.0.0-rc.16"
}
},
"node_modules/sade": {
@@ -3164,9 +3146,9 @@
}
},
"node_modules/tinyglobby": {
"version": "0.2.17",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
"integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
"version": "0.2.16",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
"integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
@@ -3278,16 +3260,16 @@
"license": "MIT"
},
"node_modules/vite": {
"version": "8.0.16",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz",
"integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==",
"version": "8.0.9",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.9.tgz",
"integrity": "sha512-t7g7GVRpMXjNpa67HaVWI/8BWtdVIQPCL2WoozXXA7LBGEFK4AkkKkHx2hAQf5x1GZSlcmEDPkVLSGahxnEEZw==",
"license": "MIT",
"dependencies": {
"lightningcss": "^1.32.0",
"picomatch": "^4.0.4",
"postcss": "^8.5.15",
"rolldown": "1.0.3",
"tinyglobby": "^0.2.17"
"postcss": "^8.5.10",
"rolldown": "1.0.0-rc.16",
"tinyglobby": "^0.2.16"
},
"bin": {
"vite": "bin/vite.js"
@@ -3303,7 +3285,7 @@
},
"peerDependencies": {
"@types/node": "^20.19.0 || >=22.12.0",
"@vitejs/devtools": "^0.1.18",
"@vitejs/devtools": "^0.1.0",
"esbuild": "^0.27.0 || ^0.28.0",
"jiti": ">=1.21.0",
"less": "^4.0.0",
+1 -1
View File
@@ -37,6 +37,6 @@
"svelte-check": "^4.4.6",
"typescript": "~6.0.3",
"typescript-eslint": "^8.58.2",
"vite": "^8.0.16"
"vite": "^8.0.9"
}
}
-2
View File
@@ -16,7 +16,6 @@ crate-type = ["staticlib", "cdylib", "rlib"]
tauri-build = { version = "2", features = [] }
[dependencies]
clap = { version = "4.5.37" }
tauri = { version = "2", features = [] }
tauri-plugin-opener = "2"
serde = { version = "1", features = ["derive"] }
@@ -24,4 +23,3 @@ serde_json = "1"
anyhow = "1.0.100"
shlex = "1"
installer = { path = "../../installer" }
log = "0.4.20"
-117
View File
@@ -1,117 +0,0 @@
//! Combines the values we care about from the clap CLI and the modifiers module into something
//! serializable by serde.
use std::collections::HashMap;
use anyhow::Context;
use log::error;
use serde::Serialize;
use crate::modifiers;
#[derive(Debug, Serialize)]
pub struct Command<'a> {
subcommands: Vec<Subcommand<'a>>,
}
impl Command<'_> {
pub fn new(command: &clap::Command) -> Command<'_> {
let subcommand_map: HashMap<&str, &clap::Command> = command
.get_subcommands()
.map(|s| (s.get_name(), s))
.collect();
Command {
// this resulting vector contains the subcommands that are found in both
// command.get_subcommands() and modifiers::subcommand_modifiers() in the order defined
// by subcommand_modifiers()
subcommands: modifiers::subcommand_modifiers()
.iter()
.filter_map(|modifier| match subcommand_map.get(modifier.command) {
Some(clap_command) => Some(Subcommand::new(clap_command, modifier)),
None => {
error!(
"failed to find clap command for subcommand {}",
modifier.command
);
None
}
})
.collect(),
}
}
}
#[derive(Debug, Serialize)]
struct Argument<'a> {
advanced: bool,
flag: String,
label: &'a str,
takes_values: bool,
}
#[derive(Debug, Serialize)]
struct Subcommand<'a> {
arguments: Vec<Argument<'a>>,
command: &'a str,
label: &'a str,
}
impl Argument<'_> {
fn try_new<'a>(
argument: &'a clap::Arg,
modifier: &modifiers::ArgumentModifier<'static>,
) -> anyhow::Result<Argument<'a>> {
let partial_flag = argument.get_long().with_context(|| {
format!(
"Missing long form command line flag for {}",
argument.get_id().as_str(),
)
})?;
Ok(Argument {
advanced: modifier.advanced,
flag: format!("--{}", partial_flag),
label: modifier.gui_label,
takes_values: argument.get_action().takes_values(),
})
}
}
impl Subcommand<'_> {
fn new<'a>(
command: &'a clap::Command,
modifier: &modifiers::SubcommandModifier<'static>,
) -> Subcommand<'a> {
let argument_map: HashMap<&str, &clap::Arg> = command
.get_arguments()
.map(|a| (a.get_id().as_str(), a))
.collect();
Subcommand {
// this resulting vector contains the arguments that are found in both
// command.get_arguments() and modifier.arg_modifiers in the order defined by by
// arg_modifiers
arguments: modifier
.arg_modifiers
.iter()
.filter_map(|arg_modifier| {
let Some(arg) = argument_map.get(arg_modifier.clap_id) else {
error!(
"failed to find clap argument with id {}",
arg_modifier.clap_id
);
return None;
};
match Argument::try_new(arg, arg_modifier) {
Ok(modified_arg) => Some(modified_arg),
Err(err) => {
error!("failed to create modified argument: {:?}", err);
None
}
}
})
.collect(),
command: modifier.command,
label: modifier.gui_label,
}
}
}
-14
View File
@@ -1,14 +1,6 @@
use std::sync::LazyLock;
use anyhow::Context;
use clap::CommandFactory;
use tauri::Emitter;
mod introspect;
mod modifiers;
static INSTALLER_COMMAND: LazyLock<clap::Command> = LazyLock::new(installer::Args::command);
async fn run_installer(app_handle: tauri::AppHandle, args: String) -> anyhow::Result<()> {
let args_vec = shlex::split(&args).context("Failed to parse arguments: unclosed quote")?;
tauri::async_runtime::spawn_blocking(move || {
@@ -33,17 +25,11 @@ async fn install_rayhunter(app_handle: tauri::AppHandle, args: String) -> Result
.map_err(|error| format!("{error:?}"))
}
#[tauri::command]
fn rayhunter_options() -> introspect::Command<'static> {
introspect::Command::new(&INSTALLER_COMMAND)
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_opener::init())
.invoke_handler(tauri::generate_handler![install_rayhunter])
.invoke_handler(tauri::generate_handler![rayhunter_options])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
-268
View File
@@ -1,268 +0,0 @@
//! Adds or "modifies" installer CLI attributes for use in the GUI.
//!
//! This module contains little logic (outside of tests) and instead just provides additional
//! metadata about CLI commands and options for the GUI installer.
//!
//! If we like this approach, I think we should consider renaming this file something like
//! gui_modifiers.rs and moving it into the crate for the CLI installer. I think this would simplify
//! development as any breaking changes to the CLI installer interface would cause tests to fail in
//! its own crate instead of installer-gui and it'd help to keep the two interfaces to the installer
//! in sync.
#[derive(Debug, Copy, Clone)]
pub struct ArgumentModifier<'a> {
/// The name or "ID" of the argument as defined in clap. This will usually be the name of the
/// field in the struct the argument is derived from.
pub clap_id: &'a str,
/// The text for displaying this argument in the GUI.
pub gui_label: &'a str,
/// Whether this argument should be hidden behind a menu for "advanced" options.
pub advanced: bool,
}
#[derive(Debug)]
pub struct SubcommandModifier<'a> {
/// The name of the subcommand on the CLI.
pub command: &'a str,
/// The text for displaying this subcommand in the GUI.
pub gui_label: &'a str,
/// Modifications to the arguments of this subcommand. The order arguments are defined in this
/// vector will match the order the arguments are displayed in the GUI.
pub arg_modifiers: Vec<ArgumentModifier<'a>>,
}
/// Provides "modifiers" or additional metadata about each subcommand.
///
/// The order of the subcommands in the returned vector is the same order that subcommands will be
/// shown in the GUI. Similarly, the order of the elements in each arg_modifiers field controls the
/// order that a subcommand's options will be shown in the GUI.
pub fn subcommand_modifiers() -> Vec<SubcommandModifier<'static>> {
let admin_ip = ArgumentModifier {
clap_id: "admin_ip",
gui_label: "Admin IP",
advanced: true,
};
let admin_username = ArgumentModifier {
clap_id: "admin_username",
gui_label: "Admin Username",
advanced: true,
};
let admin_password = ArgumentModifier {
clap_id: "admin_password",
gui_label: "Admin Password",
advanced: false,
};
let data_dir = ArgumentModifier {
clap_id: "data_dir",
gui_label: "Data Directory",
advanced: true,
};
let reset_config = ArgumentModifier {
clap_id: "reset_config",
gui_label: "Reset config.toml",
advanced: true,
};
let orbic_and_moxee_args = vec![
admin_password,
admin_ip,
admin_username,
reset_config,
data_dir,
];
vec![
SubcommandModifier {
command: "orbic",
gui_label: "Orbic/Kajeet (via network)",
arg_modifiers: orbic_and_moxee_args.clone(),
},
SubcommandModifier {
command: "orbic-usb",
gui_label: "Orbic/Kajeet (via legacy USB+ADB installer)",
arg_modifiers: vec![reset_config],
},
SubcommandModifier {
command: "tplink",
gui_label: "TP-Link",
arg_modifiers: vec![
admin_ip,
reset_config,
data_dir,
ArgumentModifier {
clap_id: "skip_sdcard",
gui_label: "Skip SD Card",
advanced: true,
},
ArgumentModifier {
clap_id: "sdcard_path",
gui_label: "SD Card Path",
advanced: true,
},
],
},
SubcommandModifier {
command: "moxee",
gui_label: "Moxee",
arg_modifiers: orbic_and_moxee_args,
},
SubcommandModifier {
command: "pinephone",
gui_label: "PinePhone",
arg_modifiers: vec![],
},
SubcommandModifier {
command: "tmobile",
gui_label: "TMobile",
arg_modifiers: vec![admin_password, admin_ip],
},
SubcommandModifier {
command: "uz801",
gui_label: "UZ801",
arg_modifiers: vec![admin_ip],
},
SubcommandModifier {
command: "wingtech",
gui_label: "Wingtech",
arg_modifiers: vec![admin_password, admin_ip],
},
]
}
#[cfg(test)]
mod tests {
//! Subcommands and arguments not returned from subcommand_modifiers() will be excluded from the
//! GUI. This is by design as it allows us to exclude things like some or all of the installer
//! utils from the GUI. The tests below help ensure that exclusions were done deliberately
//! rather than on accident.
use super::*;
use std::collections::HashMap;
/// Lists the subcommands that are purposefully excluded from subcommand_modifiers().
fn excluded_subcommands() -> Vec<&'static str> {
vec!["util"]
}
/// Lists the arguments that are purposefully excluded from subcommand_modifiers(). Items in the
/// list take the form of (subcommand, argument_id) tuples.
fn excluded_arguments() -> Vec<(&'static str, &'static str)> {
// if for example we wanted to exclude the "--admin-password" argument for "orbic", we'd
// return vec![("orbic", "admin_password")] here
vec![]
}
#[test]
fn test_subcommands_excluded_or_modified() {
let mut all_subcommands: Vec<&str> = crate::INSTALLER_COMMAND
.get_subcommands()
.map(|c| c.get_name())
.collect();
let mut excluded_or_modified_subcommands: Vec<&str> = subcommand_modifiers()
.into_iter()
.map(|m| m.command)
.chain(excluded_subcommands())
.collect();
all_subcommands.sort_unstable();
excluded_or_modified_subcommands.sort_unstable();
assert_eq!(
all_subcommands, excluded_or_modified_subcommands,
"Every subcommand must be included exactly once in subcommand_modifiers() or excluded_subcommands()."
);
}
#[test]
fn test_arguments_excluded_or_modified() {
// create maps of subcommand name to lists of argument names
let all_args_for_nonexcluded_subcommands: HashMap<&str, Vec<&str>> =
nonexcluded_subcommand_objects()
.into_iter()
.map(|c| {
(
c.get_name(),
c.get_arguments().map(|a| a.get_id().as_str()).collect(),
)
})
.collect();
let modified_args: HashMap<&str, Vec<&str>> = subcommand_modifiers()
.into_iter()
.map(|m| {
(
m.command,
m.arg_modifiers
.into_iter()
.map(|arg_m| arg_m.clap_id)
.collect(),
)
})
.collect();
// add excluded_arguments to modified_args
let mut excluded_or_modified_args = modified_args;
for (subcommand_name, arg_name) in excluded_arguments() {
excluded_or_modified_args
.entry(subcommand_name)
.or_default()
.push(arg_name);
}
// assert that all arguments are excluded or modified
for (subcommand_name, mut expected_args) in all_args_for_nonexcluded_subcommands {
let mut found_args = excluded_or_modified_args
.remove(subcommand_name)
.unwrap_or_default();
expected_args.sort_unstable();
found_args.sort_unstable();
assert_eq!(
expected_args, found_args,
"Excluded and modified arguments differ from expected arguments for {subcommand_name}."
)
}
assert!(
excluded_or_modified_args.is_empty(),
"Excluded or modified arguments found for unexpected subcommands. Map of unexpected arguments is {:?}",
excluded_or_modified_args
);
}
#[test]
fn test_arguments_have_long_flag() {
// any arguments without a long form command line flag will be excluded from the GUI so
// let's test for it here to avoid surprises
let excluded_args = excluded_arguments();
let nonexcluded_args: Vec<(&str, &clap::Arg)> = nonexcluded_subcommand_objects()
.into_iter()
.flat_map(|c| {
c.get_arguments().filter_map(|a| {
let subcommand_name = c.get_name();
if excluded_args.contains(&(subcommand_name, a.get_id().as_str())) {
None
} else {
Some((subcommand_name, a))
}
})
})
.collect();
for (subcommand_name, arg) in nonexcluded_args {
assert!(
arg.get_long().is_some(),
"The {} argument for {subcommand_name} is missing a long form command line flag.",
arg.get_id().as_str()
)
}
}
fn nonexcluded_subcommand_objects() -> Vec<&'static clap::Command> {
let excluded_subcommands = excluded_subcommands();
crate::INSTALLER_COMMAND
.get_subcommands()
.filter(|s| !excluded_subcommands.contains(&s.get_name()))
.collect()
}
}
+1 -3
View File
@@ -30,11 +30,9 @@ use crate::output::eprintln;
static CONFIG_TOML: &str = include_str!("../../dist/config.toml.in");
static RAYHUNTER_DAEMON_INIT: &str = include_str!("../../dist/scripts/rayhunter_daemon");
// We mark this as public so it can be used by installer-gui to programmatically introspect the
// installer's options.
#[derive(Parser, Debug)]
#[command(version, about)]
pub struct Args {
struct Args {
#[command(subcommand)]
command: Command,
}
+2 -6
View File
@@ -346,13 +346,9 @@ async fn tplink_launch_telnet_v5(admin_ip: &str) -> Result<(), Error> {
admin_ip: admin_ip.to_owned(),
});
let bind_addr = "127.0.0.1:4000";
let listener = tokio::net::TcpListener::bind(bind_addr)
let listener = tokio::net::TcpListener::bind("127.0.0.1:4000")
.await
.with_context(|| format!(
"Failed to bind to {bind_addr}. Is another process using this port?\n\
Try closing any application that might be listening on port 4000 and rerun the installer."
))?;
.unwrap();
println!("Listening on http://{}", listener.local_addr().unwrap());
println!("Please open above URL in your browser and log into the router to continue.");
+351
View File
@@ -0,0 +1,351 @@
//! Diag ML1 measurement log serialization/deserialization. These are pretty
//! much entirely based on Shinjo Park's work in scat, since we couldn't find
//! any other documentation for the logs' structure.
use deku::prelude::*;
use deku::ctx::Order;
fn decode_rsrp(rsrp: u16) -> f32 {
rsrp as f32 / 16.0 - 180.0
}
fn decode_rssi(rssi: u16) -> f32 {
rssi as f32 / 16.0 - 110.0
}
fn decode_rsrq(rsrq: u16) -> f32 {
rsrq as f32 / 16.0 - 30.0
}
pub mod serving_cell {
use super::*;
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
#[deku(bit_order = "lsb")]
pub struct MeasurementAndEvaluation {
pub header: MeasurementAndEvaluationHeader,
#[deku(bits = 12, pad_bits_after = "20")]
meas_rsrp: u16,
avg_rsrp: u32,
#[deku(bits = 10, pad_bits_after = "22")]
meas_rsrq: u16,
#[deku(pad_bits_before = "10", bits = 11, pad_bits_after = "11")]
meas_rssi: u16,
rxlev: u32,
s_search: u32,
#[deku(cond = "header.get_rrc_rel() == 0x01")]
r9_data: Option<u32>,
}
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
#[deku(ctx = "_: Order", id_type = "u8", bit_order = "lsb")]
pub enum MeasurementAndEvaluationHeader {
#[deku(id = "4")]
V4 {
rrc_rel: u8,
_reserved: u16,
earfcn: u16,
#[deku(bits = 9)]
pci: u16,
#[deku(bits = 7)]
serv_layer_priority: u8,
},
#[deku(id = "5")]
V5 {
rrc_rel: u8,
_reserved: u16,
earfcn: u32,
#[deku(bits = 9)]
pci: u16,
#[deku(bits = 7, pad_bytes_after = "2")]
serv_layer_priority: u8,
},
}
impl MeasurementAndEvaluationHeader {
fn get_rrc_rel(&self) -> u8 {
match self {
MeasurementAndEvaluationHeader::V4 { rrc_rel, .. } => *rrc_rel,
MeasurementAndEvaluationHeader::V5 { rrc_rel, .. } => *rrc_rel,
}
}
}
impl MeasurementAndEvaluation {
pub fn get_pci(&self) -> u16 {
match &self.header {
MeasurementAndEvaluationHeader::V4 { pci, .. } => *pci,
MeasurementAndEvaluationHeader::V5 { pci, .. } => *pci,
}
}
pub fn get_earfcn(&self) -> u32 {
match &self.header {
MeasurementAndEvaluationHeader::V4 { earfcn, .. } => *earfcn as u32,
MeasurementAndEvaluationHeader::V5 { earfcn, .. } => *earfcn,
}
}
pub fn get_meas_rsrp(&self) -> f32 {
decode_rsrp(self.meas_rsrp)
}
pub fn get_meas_rssi(&self) -> f32 {
decode_rssi(self.meas_rssi)
}
pub fn get_meas_rsrq(&self) -> f32 {
decode_rsrq(self.meas_rsrq)
}
}
}
pub mod neighbor_cells {
use super::*;
#[derive(Clone, Debug, DekuRead, DekuWrite, PartialEq)]
#[deku(id_type = "u8", bit_order = "lsb")]
pub enum MeasurementsHeader {
#[deku(id = "4")]
V4 {
rrc_rel: u8,
_reserved1: u16,
earfcn: u16,
#[deku(bits = 6)]
q_rxlevmin: u8,
#[deku(bits = 10)]
n_cells: u16,
},
#[deku(id = "5")]
V5 {
rrc_rel: u8,
_reserved1: u16,
earfcn: u32,
#[deku(bits = 6)]
q_rxlevmin: u8,
#[deku(bits = 26)]
n_cells: u32,
},
}
impl MeasurementsHeader {
fn get_n_cells(&self) -> usize {
match self {
MeasurementsHeader::V4 { n_cells, .. } => *n_cells as usize,
MeasurementsHeader::V5 { n_cells, .. } => *n_cells as usize,
}
}
}
#[derive(Clone, Debug, DekuRead, DekuWrite, PartialEq)]
pub struct Measurements {
pub header: MeasurementsHeader,
#[deku(count = "header.get_n_cells()")]
pub cells: Vec<MeasurementsCell>
}
impl Measurements {
pub fn get_earfcn(&self) -> u32 {
match &self.header {
MeasurementsHeader::V4 { earfcn, .. } => *earfcn as u32,
MeasurementsHeader::V5 { earfcn, .. } => *earfcn,
}
}
}
#[derive(Clone, Debug, DekuRead, DekuWrite, PartialEq)]
#[deku(bit_order = "lsb")]
pub struct MeasurementsCell {
#[deku(bits = 9)]
pub pci: u16,
#[deku(bits = 11)]
meas_rssi: u16,
#[deku(bits = 12)]
meas_rsrp: u16,
#[deku(pad_bits_before = "12", bits = 12, pad_bits_after = "8")]
avg_rsrp: u16,
#[deku(pad_bits_before = "12", bits = 10, pad_bits_after = "10")]
meas_rsrq: u16,
#[deku(bits = 10, pad_bits_after = "10")]
avg_rsrq: u16,
#[deku(bits = 6, pad_bits_after = "6")]
s_rxlev: u16,
n_freq_offset: u16,
val5: u16,
ant0_offset: u32,
ant1_offset: u32,
unk1: u32,
}
impl MeasurementsCell {
pub fn get_meas_rsrp(&self) -> f32 {
decode_rsrp(self.meas_rsrp)
}
pub fn get_meas_rssi(&self) -> f32 {
decode_rssi(self.meas_rssi)
}
pub fn get_meas_rsrq(&self) -> f32 {
decode_rsrq(self.meas_rsrq)
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::diag::diaglog::LogBody;
use crate::log_codes::{LOG_LTE_ML1_NEIGHBOR_MEAS, LOG_LTE_ML1_SERVING_CELL_MEAS_RESP_AND_EVAL};
use std::io::{Cursor, Seek};
fn unhexlify(hexlified_bytes: &str) -> (usize, Reader<Cursor<Vec<u8>>>) {
let byte_len = hexlified_bytes.len() / 2;
let bytes = (0..hexlified_bytes.len())
.step_by(2)
.map(|i| u8::from_str_radix(&hexlified_bytes[i..i+2], 16).unwrap())
.collect();
(byte_len, Reader::new(Cursor::new(bytes)))
}
fn parse_ncell_measurements(hexlified_bytes: &str) -> (u8, neighbor_cells::Measurements) {
let (total_size, mut reader) = unhexlify(hexlified_bytes);
match LogBody::from_reader_with_ctx(&mut reader, (LOG_LTE_ML1_NEIGHBOR_MEAS as u16, 0)) {
Ok(LogBody::LteMl1NeighborCellsMeasurements { data }) => {
if !reader.end() {
let leftover_bits = reader.rest();
let leftover_bytes = total_size - reader.stream_position().unwrap() as usize;
panic!("failed to read entire buffer ({} bytes, {} bits left)", leftover_bytes, leftover_bits.len());
}
let pkt_version = match data.header {
neighbor_cells::MeasurementsHeader::V4 { .. } => 4,
neighbor_cells::MeasurementsHeader::V5 { .. } => 5,
};
(pkt_version, data)
},
Ok(x) => panic!("expected MeasurementAndEvaluation, but parsed {:?}", x),
Err(x) => panic!("failed to parse MeasurementAndEvaluation {:?}", x),
}
}
fn parse_meas_eval(hexlified_bytes: &str) -> (u8, serving_cell::MeasurementAndEvaluation) {
let (total_size, mut reader) = unhexlify(hexlified_bytes);
match LogBody::from_reader_with_ctx(&mut reader, (LOG_LTE_ML1_SERVING_CELL_MEAS_RESP_AND_EVAL as u16, 0)) {
Ok(LogBody::LteMl1ServingCellMeasurementAndEvaluation { data }) => {
if !reader.end() {
let leftover_bits = reader.rest();
let leftover_bytes = total_size - reader.stream_position().unwrap() as usize;
panic!("failed to read entire buffer ({} bytes, {} bits left)", leftover_bytes, leftover_bits.len());
}
let pkt_version = match data.header {
serving_cell::MeasurementAndEvaluationHeader::V4 { .. } => 4,
serving_cell::MeasurementAndEvaluationHeader::V5 { .. } => 5,
};
(pkt_version, data)
},
Ok(x) => panic!("expected MeasurementAndEvaluation, but parsed {:?}", x),
Err(x) => panic!("failed to parse MeasurementAndEvaluation {:?}", x),
}
}
fn scell_meas_and_eval_case(
hexlified_bytes: &str,
pkt_version: u8,
pci: u16,
earfcn: u32,
rsrp: f32,
rsrq: f32,
rssi: f32
) {
let (parsed_pkt_version, data) = parse_meas_eval(hexlified_bytes);
assert_eq!(parsed_pkt_version, pkt_version);
assert_eq!(data.get_pci(), pci, "incorrect pci");
assert_eq!(data.get_earfcn(), earfcn, "incorrect earfcn");
assert_eq!(data.get_meas_rsrp(), rsrp, "incorrect rsrp");
assert_eq!(data.get_meas_rsrq(), rsrq, "incorrect rsrq");
assert_eq!(data.get_meas_rssi(), rssi, "incorrect rssi");
}
// Adapted from scat's TestDiagLteLogParser::test_parse_lte_ml1_scell_meas,
// but edited to print full-precision floats
#[test]
fn test_scell_meas() {
scell_meas_and_eval_case(
"040100009C18D60AECC44E00E2244E00FFFCE30FFED80A0047AD56021D310100A2624100",
4,
214,
6300,
-101.25,
-14.0625,
-66.625
);
scell_meas_and_eval_case(
"05010000160d0000d40e00004bb444005444450039e514133149070048adfe019f310100a23f0000",
5,
212,
3350,
-111.3125,
-10.4375,
-80.875,
);
scell_meas_and_eval_case(
"05010000f424000a4d43434d4e434d41524b45527c307c3236327c317c34323330333233347c7c4d",
5,
333,
167781620,
-127.125,
-22.25,
2.75,
);
scell_meas_and_eval_case(
"0501000000190000a90d0000d9944d00d9944d006081d5d55d2568bc48ad3e027f314fe0891900e0",
5,
425,
6400,
-102.4375,
-8.0,
-77.4375,
);
}
fn ncell_meas_case(
hexlified_bytes: &str,
pkt_version: u8,
earfcn: u32,
cells: Vec<(u16, f32, f32, f32)>,
) {
let (parsed_pkt_version, data) = parse_ncell_measurements(hexlified_bytes);
assert_eq!(parsed_pkt_version, pkt_version, "incorrect pkt_version");
assert_eq!(data.cells.len(), cells.len(), "incorrect number of cells");
assert_eq!(data.get_earfcn(), earfcn, "incorrect earfcn");
for (parsed, (pci, rsrp, rssi, rsrq)) in data.cells.iter().zip(cells) {
assert_eq!(parsed.pci, pci, "incorrect pci");
assert_eq!(parsed.get_meas_rsrp(), rsrp, "incorrect rsrp");
assert_eq!(parsed.get_meas_rssi(), rssi, "incorrect rssi");
assert_eq!(parsed.get_meas_rsrq(), rsrq, "incorrect rsrq");
}
}
// Adapted from scat's TestDiagLteLogParser::test_parse_lte_ml1_ncell_meas,
// but edited to print full-precision floats
#[test]
fn test_ncell_meas() {
ncell_meas_case(
"040100009C1847008348E44DDEA44C00CAB4CC32B6D8420300000000FF773301FF77330122020100",
4,
6300,
vec![
(131, -102.125, -75.75, -17.3125),
]
);
ncell_meas_case(
"05010000160d0000480000006cea413bb4433b00b4f3cc33cf3c130200000000ffefc00fffefc00f45081600",
5,
3350,
vec![
(108, -120.75, -94.6875, -17.0625),
]
);
}
}
+206
View File
@@ -0,0 +1,206 @@
//! Diag LogBody serialization/deserialization
use chrono::{DateTime, FixedOffset};
use deku::prelude::*;
pub mod measurement;
pub mod rrc;
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
#[deku(ctx = "log_type: u16, hdr_len: u16", id = "log_type")]
pub enum LogBody {
#[deku(id = "0x412f")]
WcdmaSignallingMessage {
channel_type: u8,
radio_bearer: u8,
length: u16,
#[deku(count = "length")]
msg: Vec<u8>,
},
#[deku(id = "0x512f")]
GsmRrSignallingMessage {
channel_type: u8,
message_type: u8,
length: u8,
#[deku(count = "length")]
msg: Vec<u8>,
},
#[deku(id = "0x5226")]
GprsMacSignallingMessage {
channel_type: u8,
message_type: u8,
length: u8,
#[deku(count = "length")]
msg: Vec<u8>,
},
#[deku(id = "0xb0c0")]
LteRrcOtaMessage {
ext_header_version: u8,
#[deku(ctx = "*ext_header_version")]
packet: rrc::LteRrcOtaPacket,
},
// the four NAS command opcodes refer to:
// * 0xb0e2: plain ESM NAS message (incoming)
// * 0xb0e3: plain ESM NAS message (outgoing)
// * 0xb0ec: plain EMM NAS message (incoming)
// * 0xb0ed: plain EMM NAS message (outgoing)
#[deku(id_pat = "0xb0e2 | 0xb0e3 | 0xb0ec | 0xb0ed")]
Nas4GMessage {
#[deku(skip, default = "log_type")]
log_type: u16,
#[deku(ctx = "*log_type")]
direction: Nas4GMessageDirection,
ext_header_version: u8,
rrc_rel: u8,
rrc_version_minor: u8,
rrc_version_major: u8,
// message length = hdr_len - (sizeof(ext_header_version) + sizeof(rrc_rel) + sizeof(rrc_version_minor) + sizeof(rrc_version_major))
#[deku(count = "hdr_len.saturating_sub(4)")]
msg: Vec<u8>,
},
#[deku(id = "0x11eb")]
IpTraffic {
// is this right?? based on https://github.com/P1sec/QCSuper/blob/81dbaeee15ec7747e899daa8e3495e27cdcc1264/src/modules/pcap_dump.py#L378
#[deku(count = "hdr_len.saturating_sub(8)")]
msg: Vec<u8>,
},
#[deku(id = "0x713a")]
UmtsNasOtaMessage {
is_uplink: u8,
length: u32,
#[deku(count = "length")]
msg: Vec<u8>,
},
#[deku(id = "0xb821")]
NrRrcOtaMessage {
#[deku(count = "hdr_len")]
msg: Vec<u8>,
},
#[deku(id = "0xb17f")]
LteMl1ServingCellMeasurementAndEvaluation {
data: measurement::serving_cell::MeasurementAndEvaluation,
},
#[deku(id = "0xb180")]
LteMl1NeighborCellsMeasurements {
data: measurement::neighbor_cells::Measurements,
},
}
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
#[deku(ctx = "log_type: u16", id = "log_type")]
pub enum Nas4GMessageDirection {
// * 0xb0e2: plain ESM NAS message (incoming)
// * 0xb0e3: plain ESM NAS message (outgoing)
// * 0xb0ec: plain EMM NAS message (incoming)
// * 0xb0ed: plain EMM NAS message (outgoing)
#[deku(id_pat = "0xb0e2 | 0xb0ec")]
Downlink,
#[deku(id_pat = "0xb0e3 | 0xb0ed")]
Uplink,
}
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
#[deku(endian = "little")]
pub struct Timestamp {
pub ts: u64,
}
impl Timestamp {
pub fn to_datetime(&self) -> DateTime<FixedOffset> {
// Upper 48 bits: epoch at 1980-01-06 00:00:00, incremented by 1 for 1/800s
// Lower 16 bits: time since last 1/800s tick in 1/32 chip units
let ts_upper = self.ts >> 16;
let ts_lower = self.ts & 0xffff;
let epoch = chrono::DateTime::parse_from_rfc3339("1980-01-06T00:00:00-00:00").unwrap();
let mut delta_seconds = ts_upper as f64 * 1.25;
delta_seconds += ts_lower as f64 / 40960.0;
let ts_delta = chrono::Duration::milliseconds(delta_seconds as i64);
epoch + ts_delta
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::diag::Message;
#[test]
fn test_logs() {
let data = vec![
16, 0, 38, 0, 38, 0, 192, 176, 26, 165, 245, 135, 118, 35, 2, 1, 20, 14, 48, 0, 160, 0,
2, 8, 0, 0, 217, 15, 5, 0, 0, 0, 0, 7, 0, 64, 1, 238, 173, 213, 77, 208,
];
let msg = Message::from_bytes((&data, 0)).unwrap().1;
assert_eq!(
msg,
Message::Log {
pending_msgs: 0,
outer_length: 38,
inner_length: 38,
log_type: 0xb0c0,
timestamp: Timestamp {
ts: 72659535985485082
},
body: LogBody::LteRrcOtaMessage {
ext_header_version: 20,
packet: rrc::LteRrcOtaPacket::V8 {
rrc_rel_maj: 14,
rrc_rel_min: 48,
bearer_id: 0,
phy_cell_id: 160,
earfcn: 2050,
sfn_subfn: 4057,
pdu_num: 5,
sib_mask: 0,
len: 7,
packet: vec![0x40, 0x1, 0xee, 0xad, 0xd5, 0x4d, 0xd0],
},
},
}
);
}
#[test]
fn test_fuzz_crash_inner_length_underflow() {
// Regression test: inner_length < 12 previously caused panic.
// Fixed by using saturating_sub in Message::Log body length calculation.
let fuzz_data = b"\x10\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
let _ = Message::from_bytes((fuzz_data, 0));
}
#[test]
fn test_fuzz_crash_nas_hdr_len_underflow() {
// Regression test for two things:
// - hdr_len < 4 previously caused panic in Nas4GMessage.
// - Upgrading to deku 0.20 caused incorrect parsing behavior (double-read of discriminant)
let nas_msg =
b"\x10\x00\x14\x00\x02\x00\xe2\xb0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00";
let ((rest, _), msg) = Message::from_bytes((nas_msg, 0)).unwrap();
assert_eq!(rest.len(), 0);
assert!(
matches!(
msg,
Message::Log {
log_type: 0xb0e2,
body: LogBody::Nas4GMessage {
direction: Nas4GMessageDirection::Downlink,
..
},
..
}
),
"Unexpected message: {:?}",
msg
);
}
#[test]
fn test_fuzz_crash_ip_traffic_hdr_len_underflow() {
// Regression test: hdr_len < 8 previously caused panic in IpTraffic.
// Fixed by using saturating_sub for msg length calculation.
let ip_msg = b"\x10\x00\x14\x00\x02\x00\xeb\x11\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00";
let _ = Message::from_bytes((ip_msg, 0));
}
}
+110
View File
@@ -0,0 +1,110 @@
//! Diag LTE RRC serialization/deserialization
use deku::prelude::*;
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
#[deku(ctx = "ext_header_version: u8", id = "ext_header_version")]
pub enum LteRrcOtaPacket {
#[deku(id_pat = "0..=4")]
V0 {
rrc_rel_maj: u8,
rrc_rel_min: u8,
bearer_id: u8,
phy_cell_id: u16,
earfcn: u16,
sfn_subfn: u16,
pdu_num: u8,
len: u16,
#[deku(count = "len")]
packet: Vec<u8>,
},
#[deku(id_pat = "5..=7")]
V5 {
rrc_rel_maj: u8,
rrc_rel_min: u8,
bearer_id: u8,
phy_cell_id: u16,
earfcn: u16,
sfn_subfn: u16,
pdu_num: u8,
sib_mask: u32,
len: u16,
#[deku(count = "len")]
packet: Vec<u8>,
},
#[deku(id_pat = "8..=24")]
V8 {
rrc_rel_maj: u8,
rrc_rel_min: u8,
bearer_id: u8,
phy_cell_id: u16,
earfcn: u32,
sfn_subfn: u16,
pdu_num: u8,
sib_mask: u32,
len: u16,
#[deku(count = "len")]
packet: Vec<u8>,
},
#[deku(id_pat = "25..")]
V25 {
rrc_rel_maj: u8,
rrc_rel_min: u8,
nr_rrc_rel_maj: u8,
nr_rrc_rel_min: u8,
bearer_id: u8,
phy_cell_id: u16,
earfcn: u32,
sfn_subfn: u16,
pdu_num: u8,
sib_mask: u32,
len: u16,
#[deku(count = "len")]
packet: Vec<u8>,
},
}
impl LteRrcOtaPacket {
fn get_sfn_subfn(&self) -> u16 {
match self {
LteRrcOtaPacket::V0 { sfn_subfn, .. } => *sfn_subfn,
LteRrcOtaPacket::V5 { sfn_subfn, .. } => *sfn_subfn,
LteRrcOtaPacket::V8 { sfn_subfn, .. } => *sfn_subfn,
LteRrcOtaPacket::V25 { sfn_subfn, .. } => *sfn_subfn,
}
}
pub fn get_sfn(&self) -> u32 {
self.get_sfn_subfn() as u32 >> 4
}
pub fn get_subfn(&self) -> u8 {
(self.get_sfn_subfn() & 0xf) as u8
}
pub fn get_pdu_num(&self) -> u8 {
match self {
LteRrcOtaPacket::V0 { pdu_num, .. } => *pdu_num,
LteRrcOtaPacket::V5 { pdu_num, .. } => *pdu_num,
LteRrcOtaPacket::V8 { pdu_num, .. } => *pdu_num,
LteRrcOtaPacket::V25 { pdu_num, .. } => *pdu_num,
}
}
pub fn get_earfcn(&self) -> u32 {
match self {
LteRrcOtaPacket::V0 { earfcn, .. } => *earfcn as u32,
LteRrcOtaPacket::V5 { earfcn, .. } => *earfcn as u32,
LteRrcOtaPacket::V8 { earfcn, .. } => *earfcn,
LteRrcOtaPacket::V25 { earfcn, .. } => *earfcn,
}
}
pub fn take_payload(self) -> Vec<u8> {
match self {
LteRrcOtaPacket::V0 { packet, .. } => packet,
LteRrcOtaPacket::V5 { packet, .. } => packet,
LteRrcOtaPacket::V8 { packet, .. } => packet,
LteRrcOtaPacket::V25 { packet, .. } => packet,
}
}
}
+5 -294
View File
@@ -1,6 +1,5 @@
//! Diag protocol serialization/deserialization
use chrono::{DateTime, FixedOffset};
use crc::{Algorithm, Crc};
use deku::prelude::*;
@@ -8,6 +7,10 @@ use crate::hdlc::{self, hdlc_decapsulate};
use log::warn;
use thiserror::Error;
pub mod diaglog;
use diaglog::{LogBody, Timestamp};
pub const MESSAGE_TERMINATOR: u8 = 0x7e;
pub const MESSAGE_ESCAPE_CHAR: u8 = 0x7d;
@@ -152,218 +155,6 @@ pub enum Message {
},
}
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
#[deku(ctx = "log_type: u16, hdr_len: u16", id = "log_type")]
pub enum LogBody {
#[deku(id = "0x412f")]
WcdmaSignallingMessage {
channel_type: u8,
radio_bearer: u8,
length: u16,
#[deku(count = "length")]
msg: Vec<u8>,
},
#[deku(id = "0x512f")]
GsmRrSignallingMessage {
channel_type: u8,
message_type: u8,
length: u8,
#[deku(count = "length")]
msg: Vec<u8>,
},
#[deku(id = "0x5226")]
GprsMacSignallingMessage {
channel_type: u8,
message_type: u8,
length: u8,
#[deku(count = "length")]
msg: Vec<u8>,
},
#[deku(id = "0xb0c0")]
LteRrcOtaMessage {
ext_header_version: u8,
#[deku(ctx = "*ext_header_version")]
packet: LteRrcOtaPacket,
},
// the four NAS command opcodes refer to:
// * 0xb0e2: plain ESM NAS message (incoming)
// * 0xb0e3: plain ESM NAS message (outgoing)
// * 0xb0ec: plain EMM NAS message (incoming)
// * 0xb0ed: plain EMM NAS message (outgoing)
#[deku(id_pat = "0xb0e2 | 0xb0e3 | 0xb0ec | 0xb0ed")]
Nas4GMessage {
#[deku(skip, default = "log_type")]
log_type: u16,
#[deku(ctx = "*log_type")]
direction: Nas4GMessageDirection,
ext_header_version: u8,
rrc_rel: u8,
rrc_version_minor: u8,
rrc_version_major: u8,
// message length = hdr_len - (sizeof(ext_header_version) + sizeof(rrc_rel) + sizeof(rrc_version_minor) + sizeof(rrc_version_major))
#[deku(count = "hdr_len.saturating_sub(4)")]
msg: Vec<u8>,
},
#[deku(id = "0x11eb")]
IpTraffic {
// is this right?? based on https://github.com/P1sec/QCSuper/blob/81dbaeee15ec7747e899daa8e3495e27cdcc1264/src/modules/pcap_dump.py#L378
#[deku(count = "hdr_len.saturating_sub(8)")]
msg: Vec<u8>,
},
#[deku(id = "0x713a")]
UmtsNasOtaMessage {
is_uplink: u8,
length: u32,
#[deku(count = "length")]
msg: Vec<u8>,
},
#[deku(id = "0xb821")]
NrRrcOtaMessage {
#[deku(count = "hdr_len")]
msg: Vec<u8>,
},
}
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
#[deku(ctx = "log_type: u16", id = "log_type")]
pub enum Nas4GMessageDirection {
// * 0xb0e2: plain ESM NAS message (incoming)
// * 0xb0e3: plain ESM NAS message (outgoing)
// * 0xb0ec: plain EMM NAS message (incoming)
// * 0xb0ed: plain EMM NAS message (outgoing)
#[deku(id_pat = "0xb0e2 | 0xb0ec")]
Downlink,
#[deku(id_pat = "0xb0e3 | 0xb0ed")]
Uplink,
}
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
#[deku(ctx = "ext_header_version: u8", id = "ext_header_version")]
pub enum LteRrcOtaPacket {
#[deku(id_pat = "0..=4")]
V0 {
rrc_rel_maj: u8,
rrc_rel_min: u8,
bearer_id: u8,
phy_cell_id: u16,
earfcn: u16,
sfn_subfn: u16,
pdu_num: u8,
len: u16,
#[deku(count = "len")]
packet: Vec<u8>,
},
#[deku(id_pat = "5..=7")]
V5 {
rrc_rel_maj: u8,
rrc_rel_min: u8,
bearer_id: u8,
phy_cell_id: u16,
earfcn: u16,
sfn_subfn: u16,
pdu_num: u8,
sib_mask: u32,
len: u16,
#[deku(count = "len")]
packet: Vec<u8>,
},
#[deku(id_pat = "8..=24")]
V8 {
rrc_rel_maj: u8,
rrc_rel_min: u8,
bearer_id: u8,
phy_cell_id: u16,
earfcn: u32,
sfn_subfn: u16,
pdu_num: u8,
sib_mask: u32,
len: u16,
#[deku(count = "len")]
packet: Vec<u8>,
},
#[deku(id_pat = "25..")]
V25 {
rrc_rel_maj: u8,
rrc_rel_min: u8,
nr_rrc_rel_maj: u8,
nr_rrc_rel_min: u8,
bearer_id: u8,
phy_cell_id: u16,
earfcn: u32,
sfn_subfn: u16,
pdu_num: u8,
sib_mask: u32,
len: u16,
#[deku(count = "len")]
packet: Vec<u8>,
},
}
impl LteRrcOtaPacket {
fn get_sfn_subfn(&self) -> u16 {
match self {
LteRrcOtaPacket::V0 { sfn_subfn, .. } => *sfn_subfn,
LteRrcOtaPacket::V5 { sfn_subfn, .. } => *sfn_subfn,
LteRrcOtaPacket::V8 { sfn_subfn, .. } => *sfn_subfn,
LteRrcOtaPacket::V25 { sfn_subfn, .. } => *sfn_subfn,
}
}
pub fn get_sfn(&self) -> u32 {
self.get_sfn_subfn() as u32 >> 4
}
pub fn get_subfn(&self) -> u8 {
(self.get_sfn_subfn() & 0xf) as u8
}
pub fn get_pdu_num(&self) -> u8 {
match self {
LteRrcOtaPacket::V0 { pdu_num, .. } => *pdu_num,
LteRrcOtaPacket::V5 { pdu_num, .. } => *pdu_num,
LteRrcOtaPacket::V8 { pdu_num, .. } => *pdu_num,
LteRrcOtaPacket::V25 { pdu_num, .. } => *pdu_num,
}
}
pub fn get_earfcn(&self) -> u32 {
match self {
LteRrcOtaPacket::V0 { earfcn, .. } => *earfcn as u32,
LteRrcOtaPacket::V5 { earfcn, .. } => *earfcn as u32,
LteRrcOtaPacket::V8 { earfcn, .. } => *earfcn,
LteRrcOtaPacket::V25 { earfcn, .. } => *earfcn,
}
}
pub fn take_payload(self) -> Vec<u8> {
match self {
LteRrcOtaPacket::V0 { packet, .. } => packet,
LteRrcOtaPacket::V5 { packet, .. } => packet,
LteRrcOtaPacket::V8 { packet, .. } => packet,
LteRrcOtaPacket::V25 { packet, .. } => packet,
}
}
}
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
#[deku(endian = "little")]
pub struct Timestamp {
pub ts: u64,
}
impl Timestamp {
pub fn to_datetime(&self) -> DateTime<FixedOffset> {
// Upper 48 bits: epoch at 1980-01-06 00:00:00, incremented by 1 for 1/800s
// Lower 16 bits: time since last 1/800s tick in 1/32 chip units
let ts_upper = self.ts >> 16;
let ts_lower = self.ts & 0xffff;
let epoch = chrono::DateTime::parse_from_rfc3339("1980-01-06T00:00:00-00:00").unwrap();
let mut delta_seconds = ts_upper as f64 * 1.25;
delta_seconds += ts_lower as f64 / 40960.0;
let ts_delta = chrono::Duration::milliseconds(delta_seconds as i64);
epoch + ts_delta
}
}
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
#[deku(ctx = "opcode: u32, subopcode: u32", id = "opcode")]
pub enum ResponsePayload {
@@ -478,42 +269,6 @@ mod test {
);
}
#[test]
fn test_logs() {
let data = vec![
16, 0, 38, 0, 38, 0, 192, 176, 26, 165, 245, 135, 118, 35, 2, 1, 20, 14, 48, 0, 160, 0,
2, 8, 0, 0, 217, 15, 5, 0, 0, 0, 0, 7, 0, 64, 1, 238, 173, 213, 77, 208,
];
let msg = Message::from_bytes((&data, 0)).unwrap().1;
assert_eq!(
msg,
Message::Log {
pending_msgs: 0,
outer_length: 38,
inner_length: 38,
log_type: 0xb0c0,
timestamp: Timestamp {
ts: 72659535985485082
},
body: LogBody::LteRrcOtaMessage {
ext_header_version: 20,
packet: LteRrcOtaPacket::V8 {
rrc_rel_maj: 14,
rrc_rel_min: 48,
bearer_id: 0,
phy_cell_id: 160,
earfcn: 2050,
sfn_subfn: 4057,
pdu_num: 5,
sib_mask: 0,
len: 7,
packet: vec![0x40, 0x1, 0xee, 0xad, 0xd5, 0x4d, 0xd0],
},
},
}
);
}
fn make_container(data_type: DataType, message: HdlcEncapsulatedMessage) -> MessagesContainer {
MessagesContainer {
data_type,
@@ -537,7 +292,7 @@ mod test {
},
body: LogBody::LteRrcOtaMessage {
ext_header_version: 20,
packet: LteRrcOtaPacket::V8 {
packet: diaglog::rrc::LteRrcOtaPacket::V8 {
rrc_rel_maj: 14,
rrc_rel_min: 48,
bearer_id: 0,
@@ -619,50 +374,6 @@ mod test {
));
}
#[test]
fn test_fuzz_crash_inner_length_underflow() {
// Regression test: inner_length < 12 previously caused panic.
// Fixed by using saturating_sub in Message::Log body length calculation.
let fuzz_data = b"\x10\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
let _ = Message::from_bytes((fuzz_data, 0));
}
#[test]
fn test_fuzz_crash_nas_hdr_len_underflow() {
// Regression test for two things:
// - hdr_len < 4 previously caused panic in Nas4GMessage.
// - Upgrading to deku 0.20 caused incorrect parsing behavior (double-read of discriminant)
let nas_msg =
b"\x10\x00\x14\x00\x02\x00\xe2\xb0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00";
let ((rest, _), msg) = Message::from_bytes((nas_msg, 0)).unwrap();
assert_eq!(rest.len(), 0);
assert!(
matches!(
msg,
Message::Log {
log_type: 0xb0e2,
body: LogBody::Nas4GMessage {
direction: Nas4GMessageDirection::Downlink,
..
},
..
}
),
"Unexpected message: {:?}",
msg
);
}
#[test]
fn test_fuzz_crash_ip_traffic_hdr_len_underflow() {
// Regression test: hdr_len < 8 previously caused panic in IpTraffic.
// Fixed by using saturating_sub for msg length calculation.
let ip_msg = b"\x10\x00\x14\x00\x02\x00\xeb\x11\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00";
let _ = Message::from_bytes((ip_msg, 0));
}
#[test]
fn test_fuzz_crash_response_opcode_parsing() {
// Regression test: Upgrading to deku 0.20 caused incorrect parsing of Response messages.
+5 -1
View File
@@ -40,7 +40,7 @@ pub enum DiagDeviceError {
ParseMessagesContainerError(deku::DekuError),
}
pub const LOG_CODES_FOR_RAW_PACKET_LOGGING: [u32; 11] = [
pub const LOG_CODES_FOR_RAW_PACKET_LOGGING: [u32; 14] = [
// Layer 2:
log_codes::LOG_GPRS_MAC_SIGNALLING_MESSAGE_C, // 0x5226
// Layer 3:
@@ -56,6 +56,10 @@ pub const LOG_CODES_FOR_RAW_PACKET_LOGGING: [u32; 11] = [
log_codes::LOG_LTE_NAS_EMM_OTA_OUT_MSG_LOG_C, // 0xb0ed
// User IP traffic:
log_codes::LOG_DATA_PROTOCOL_LOGGING_C, // 0x11eb
// Statistics
log_codes::LOG_LTE_ML1_SERVING_CELL_MEAS_RESP_AND_EVAL, // 0xb17f
log_codes::LOG_LTE_ML1_SERVING_CELL_MEAS_RESPONSE, // 0xb193
log_codes::LOG_LTE_ML1_NEIGHBOR_MEAS, // 0xb180
];
const BUFFER_LEN: usize = 1024 * 1024 * 10;
+6 -25
View File
@@ -1,7 +1,8 @@
use crate::diag::*;
use crate::gsmtap::*;
use crate::diag::Message;
use crate::diag::diaglog::{Timestamp, LogBody, Nas4GMessageDirection};
use crate::gsmtap::{GsmtapHeader, GsmtapMessage, GsmtapType, LteNasSubtype, LteRrcSubtype};
use log::error;
use log::debug;
use thiserror::Error;
#[derive(Debug, Error)]
@@ -135,7 +136,7 @@ fn log_to_gsmtap(value: LogBody) -> Result<Option<GsmtapMessage>, GsmtapParserEr
}
};
let mut header = GsmtapHeader::new(gsmtap_type);
header.arfcn = (packet.get_earfcn() as u16) & 0x3FFF;
header.arfcn = packet.get_earfcn().try_into().unwrap_or(0);
header.frame_number = packet.get_sfn();
header.subslot = packet.get_subfn();
Ok(Some(GsmtapMessage {
@@ -153,28 +154,8 @@ fn log_to_gsmtap(value: LogBody) -> Result<Option<GsmtapMessage>, GsmtapParserEr
}))
}
_ => {
error!("gsmtap_sink: ignoring unhandled log type: {value:?}");
debug!("gsmtap_sink: ignoring unhandled log type: {value:?}");
Ok(None)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use deku::DekuContainerWrite;
#[test]
fn test_arfcn_exceeding_14_bits_does_not_panic() {
let mut header = GsmtapHeader::new(GsmtapType::LteRrc(LteRrcSubtype::DlDcch));
// EARFCN 54540 (band 46) exceeds 14-bit max of 16383
let large_earfcn: u32 = 54540;
header.arfcn = (large_earfcn as u16) & 0x3FFF;
let msg = GsmtapMessage {
header,
payload: vec![0x00],
};
// This would panic before the fix with "bit size of input is larger than bit requested size"
assert!(msg.to_bytes().is_ok());
}
}
+4
View File
@@ -103,3 +103,7 @@ pub const WCDMA_SIGNALLING_MESSAGE: u32 = 0x412f;
pub const LOG_DATA_PROTOCOL_LOGGING_C: u32 = 0x11eb;
pub const LOG_UMTS_NAS_OTA_MESSAGE_LOG_PACKET_C: u32 = 0x713a;
pub const LOG_LTE_ML1_SERVING_CELL_MEAS_RESP_AND_EVAL: u32 = 0xb17f;
pub const LOG_LTE_ML1_SERVING_CELL_MEAS_RESPONSE: u32 = 0xb193;
pub const LOG_LTE_ML1_NEIGHBOR_MEAS: u32 = 0xb180;
+1 -1
View File
@@ -1,6 +1,6 @@
//! Parse QMDL files and create a pcap file.
//! Creates a plausible IP header and [GSMtap](https://osmocom.org/projects/baseband/wiki/GSMTAP) header and then puts the rest of the data under that for wireshark to parse.
use crate::diag::Timestamp;
use crate::diag::diaglog::Timestamp;
use crate::gsmtap::GsmtapMessage;
use chrono::prelude::*;
+2 -1
View File
@@ -1,6 +1,7 @@
use deku::prelude::*;
use rayhunter::{
diag::{LogBody, LteRrcOtaPacket, Message, Timestamp},
diag::Message,
diag::diaglog::{LogBody, rrc::LteRrcOtaPacket, Timestamp},
gsmtap_parser,
};
-32
View File
@@ -1,32 +0,0 @@
#!/bin/bash
# Sets Rayhunter package versions in preparation for a release.
#
# Usage: ./scripts/set-versions.sh VERSION_NUM
# Example: ./scripts/set-versions.sh 0.12.3
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
cd "$PROJECT_DIR"
if [ -z "$1" ]; then
echo "Error: Missing required version number argument."
exit 1
fi
SED_COMMAND="s/^version = \".*\"/version = \"$1\"/"
TOML_FILES=(*/Cargo.toml installer-gui/src-tauri/Cargo.toml)
echo "Updating Cargo.toml files"
if sed --version > /dev/null 2>&1; then
# we have GNU sed
sed -i -E "$SED_COMMAND" "${TOML_FILES[@]}"
else
# we have macOS/BSD sed
sed -i "" -E "$SED_COMMAND" "${TOML_FILES[@]}"
fi
echo "Updating Cargo.lock"
cargo update --workspace