merge main

This commit is contained in:
Cooper Quintin
2024-01-31 17:52:49 -08:00
40 changed files with 1112 additions and 406 deletions

View File

@@ -4,6 +4,8 @@ rustflags = ["-C", "target-feature=+crt-static"]
[alias]
test_pc = "test --target=x86_64-unknown-linux-gnu"
build_pc = "build --target=x86_64-unknown-linux-gnu"
[target.armv7-unknown-linux-gnueabihf]
linker = "arm-linux-gnueabihf-gcc"

22
.github/workflows/rust.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
name: Rust
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build
run: cargo build_pc --verbose
- name: Run tests
run: cargo test_pc --verbose

327
Cargo.lock generated
View File

@@ -17,6 +17,18 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "ahash"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "aho-corasick"
version = "1.1.2"
@@ -26,6 +38,12 @@ dependencies = [
"memchr",
]
[[package]]
name = "allocator-api2"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
[[package]]
name = "android-tzdata"
version = "0.1.1"
@@ -60,9 +78,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "axum"
version = "0.7.3"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d09dbe0e490df5da9d69b36dca48a76635288a82f92eca90024883a56202026d"
checksum = "1236b4b292f6c4d6dc34604bb5120d85c3fe1d1aa596bd5cc52ca054d13e7b9e"
dependencies = [
"async-trait",
"axum-core",
@@ -94,9 +112,9 @@ dependencies = [
[[package]]
name = "axum-core"
version = "0.4.2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e87c8503f93e6d144ee5690907ba22db7ba79ab001a932ab99034f0fe836b3df"
checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3"
dependencies = [
"async-trait",
"bytes",
@@ -136,9 +154,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.1"
version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
[[package]]
name = "bitvec"
@@ -196,16 +214,17 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.31"
version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"serde",
"wasm-bindgen",
"windows-targets 0.48.5",
"windows-targets 0.52.0",
]
[[package]]
@@ -301,9 +320,9 @@ dependencies = [
[[package]]
name = "env_logger"
version = "0.10.1"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece"
checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580"
dependencies = [
"humantime",
"is-terminal",
@@ -343,6 +362,12 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]]
name = "funty"
version = "2.0.0"
@@ -364,6 +389,17 @@ version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
[[package]]
name = "futures-macro"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
]
[[package]]
name = "futures-sink"
version = "0.3.30"
@@ -383,9 +419,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
dependencies = [
"futures-core",
"futures-macro",
"futures-task",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
@@ -396,9 +434,9 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]]
name = "h2"
version = "0.4.0"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1d308f63daf4181410c242d34c11f928dcb3aa105852019e043c9d1f4e4368a"
checksum = "31d030e59af851932b72ceebadf4a2b5986dba4c3b99dd2493f8273a0f151943"
dependencies = [
"bytes",
"fnv",
@@ -418,12 +456,16 @@ name = "hashbrown"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
dependencies = [
"ahash",
"allocator-api2",
]
[[package]]
name = "hermit-abi"
version = "0.3.3"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f"
[[package]]
name = "http"
@@ -498,12 +540,11 @@ dependencies = [
[[package]]
name = "hyper-util"
version = "0.1.2"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdea9aac0dbe5a9240d68cfd9501e2db94222c6dc06843e06640b9e07f0fdc67"
checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa"
dependencies = [
"bytes",
"futures-channel",
"futures-util",
"http",
"http-body",
@@ -511,7 +552,6 @@ dependencies = [
"pin-project-lite",
"socket2",
"tokio",
"tracing",
]
[[package]]
@@ -564,9 +604,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.1.0"
version = "2.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520"
dependencies = [
"equivalent",
"hashbrown",
@@ -591,18 +631,18 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
[[package]]
name = "js-sys"
version = "0.3.66"
version = "0.3.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca"
checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.151"
version = "0.2.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "libusb1-sys"
@@ -618,9 +658,9 @@ dependencies = [
[[package]]
name = "linux-raw-sys"
version = "0.4.12"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
[[package]]
name = "lock_api"
@@ -720,21 +760,6 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "orca"
version = "0.1.0"
dependencies = [
"bytes",
"chrono",
"crc",
"deku",
"env_logger",
"libc",
"log",
"pcap-file",
"thiserror",
]
[[package]]
name = "parking_lot"
version = "0.12.1"
@@ -777,18 +802,18 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "pin-project"
version = "1.1.3"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422"
checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.3"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690"
dependencies = [
"proc-macro2",
"quote",
@@ -809,9 +834,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
version = "0.3.28"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a"
checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb"
[[package]]
name = "proc-macro-crate"
@@ -825,9 +850,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.75"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "907a61bd0f64c2f29cd1cf1dc34d05176426a3f504a78010f08416ddb7b13708"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
dependencies = [
"unicode-ident",
]
@@ -847,6 +872,79 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "rand"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
dependencies = [
"fuchsia-cprng",
"libc",
"rand_core 0.3.1",
"rdrand",
"winapi",
]
[[package]]
name = "rand_core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
dependencies = [
"rand_core 0.4.2",
]
[[package]]
name = "rand_core"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
[[package]]
name = "rayhunter"
version = "0.1.0"
dependencies = [
"bytes",
"chrono",
"crc",
"deku",
"env_logger",
"libc",
"log",
"pcap-file",
"thiserror",
]
[[package]]
name = "rayhunter-daemon"
version = "0.1.0"
dependencies = [
"axum",
"chrono",
"env_logger",
"futures-core",
"futures-macro",
"include_dir",
"log",
"mime_guess",
"rayhunter",
"serde",
"tempdir",
"thiserror",
"tokio",
"tokio-util",
"toml",
]
[[package]]
name = "rdrand"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "redox_syscall"
version = "0.4.1"
@@ -858,9 +956,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.10.2"
version = "1.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
dependencies = [
"aho-corasick",
"memchr",
@@ -870,9 +968,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.3"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
dependencies = [
"aho-corasick",
"memchr",
@@ -885,6 +983,15 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "remove_dir_all"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
dependencies = [
"winapi",
]
[[package]]
name = "rootshell"
version = "0.1.0"
@@ -907,11 +1014,11 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "rustix"
version = "0.38.28"
version = "0.38.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca"
dependencies = [
"bitflags 2.4.1",
"bitflags 2.4.2",
"errno",
"libc",
"linux-raw-sys",
@@ -938,18 +1045,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.194"
version = "1.0.196"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b114498256798c94a0689e1a15fec6005dee8ac1f41de56404b67afc2a4b773"
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.194"
version = "1.0.196"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3385e45322e8f9931410f01b3031ec534c3947d0e94c18049af4d9f9907d4e0"
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
dependencies = [
"proc-macro2",
"quote",
@@ -958,9 +1065,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.111"
version = "1.0.113"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4"
checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79"
dependencies = [
"itoa",
"ryu",
@@ -1025,9 +1132,9 @@ dependencies = [
[[package]]
name = "smallvec"
version = "1.11.2"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
[[package]]
name = "socket2"
@@ -1080,10 +1187,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "termcolor"
version = "1.4.0"
name = "tempdir"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449"
checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
dependencies = [
"rand",
"remove_dir_all",
]
[[package]]
name = "termcolor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
dependencies = [
"winapi-util",
]
@@ -1147,6 +1264,8 @@ dependencies = [
"bytes",
"futures-core",
"futures-sink",
"futures-util",
"hashbrown",
"pin-project-lite",
"tokio",
"tracing",
@@ -1154,14 +1273,14 @@ dependencies = [
[[package]]
name = "toml"
version = "0.8.8"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35"
checksum = "c6a4b9e8023eb94392d3dca65d717c53abc5dad49c07cb65bb8fcd87115fa325"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit 0.21.0",
"toml_edit 0.21.1",
]
[[package]]
@@ -1186,9 +1305,9 @@ dependencies = [
[[package]]
name = "toml_edit"
version = "0.21.0"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03"
checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1"
dependencies = [
"indexmap",
"serde",
@@ -1280,9 +1399,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.89"
version = "0.2.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e"
checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
@@ -1290,9 +1409,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.89"
version = "0.2.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826"
checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd"
dependencies = [
"bumpalo",
"log",
@@ -1305,9 +1424,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.89"
version = "0.2.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2"
checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -1315,9 +1434,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.89"
version = "0.2.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7"
dependencies = [
"proc-macro2",
"quote",
@@ -1328,27 +1447,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.89"
version = "0.2.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f"
[[package]]
name = "wavehunter"
version = "0.1.0"
dependencies = [
"axum",
"env_logger",
"futures-core",
"include_dir",
"log",
"mime_guess",
"orca",
"serde",
"thiserror",
"tokio",
"tokio-util",
"toml",
]
checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b"
[[package]]
name = "winapi"
@@ -1524,9 +1625,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
[[package]]
name = "winnow"
version = "0.5.32"
version = "0.5.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8434aeec7b290e8da5c3f0d628cb0eac6cabcb31d14bb74f779a08109a5914d6"
checksum = "818ce546a11a9986bc24f93d0cdf38a8a1a400f1473ea8c82e59f6e0ffab9249"
dependencies = [
"memchr",
]
@@ -1539,3 +1640,23 @@ checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
dependencies = [
"tap",
]
[[package]]
name = "zerocopy"
version = "0.7.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
]

View File

@@ -1,9 +1,9 @@
[workspace]
members = [
"orca",
"lib",
"bin",
"serial",
"rootshell",
"wavehunter",
]
resolver = "2"

View File

@@ -1,12 +1,13 @@
# Wave Hunter
# Rayhunter
```
@@@ @@@ @@@ @@@@@@ @@@ @@@ @@@@@@@@ @@@ @@@ @@@ @@@ @@@ @@@ @@@@@@@ @@@@@@@@ @@@@@@@
@@! @@! @@! @@! @@@ @@! @@@ @@! @@! @@@ @@! @@@ @@!@!@@@ @!! @@! @@! @@@
@!! !!@ @!@ @!@!@!@! @!@ !@! @!!!:! @!@!@!@! @!@ !@! @!@@!!@! @!! @!!!:! @!@!!@!
!: !!: !! !!: !!! !: .:! !!: !!: !!! !!: !!! !!: !!! !!: !!: !!: :!!
::.: ::: : : : :: : :: :: : : : :.:: : :: : : : :: :: : : :
@@@@@@@ @@@@@@ @@@ @@@ @@@ @@@ @@@ @@@ @@@ @@@ @@@@@@@ @@@@@@@@ @@@@@@@
@@! @@@ @@! @@@ @@! !@@ @@! @@@ @@! @@@ @@!@!@@@ @@! @@! @@! @@@
@!@!!@! @!@!@!@! !@!@! @!@!@!@! @!@ !@! @!@@!!@! @!! @!!!:! @!@!!@!
!!: :!! !!: !!! !!: !!: !!! !!: !!! !!: !!! !!: !!: !!: :!!
: : : : : : .: : : : :.:: : :: : : : :: ::: : : :
_ _ _ _ _ _ _ _
)`'-.,_)`'-.,_)`'-.,_)`'-.,_)`'-.,_)`'-.,_)`'-.,_)`'-.,_
@@ -24,4 +25,35 @@ _ _ _ _ _ _ _ _
\__;
```
diag helper binary for the Orbic mobile hotspot. Based on code from [QCSuper](https://github.com/P1sec/QCSuper)
Rayhunter is an IMSI Catcher Catcher for the Orbic mobile hotspot. Based on code from [QCSuper](https://github.com/P1sec/QCSuper)
**THIS CODE IS PROOF OF CONCEPT AND SHOULD NOT BE RELIED UPON IN HIGH RISK SITUATIONS**
Code is built and tested for the Orbic RC400L mobile hotspot, it may work on other orbics and other
linux/qualcom devices but this is the only one we have tested on. Buy the orbic [using bezos bucks](https://www.amazon.com/gp/product/B09CLS6Z7X/)
Root your device on windows using the instructions here: https://xdaforums.com/t/resetting-verizon-orbic-speed-rc400l-firmware-flash-kajeet.4334899/#post-87855183
(script to root on linux coming soon)
## Setup
on your linux laptop install rust the usual way and then install cross compiling dependences.
run `sudo apt install build-essential libc6-armhf-cross libc6-dev-armhf-cross gcc-arm-linux-gnueabihf`
set up cross compliing for rust:
```
rustup target add x86_64-unknown-linux-gnu
rustup target add armv7-unknown-linux-gnueabihf
```
Build for arm using `cargo build`
Run tests using `cargo test_pc`
Push the scripts in `scripts/` to /etc/init.d on device and make a directory called /data/rayhunter using `adb shell` (and sshell for your root shell if you followed the steps above)
you also need to copy `config.toml.example` to /data/rayhunter/config.toml
Then run ./make.sh this will build the binary and push it over adb. Restart your device or run `/etc/init.d/rayhunter_daemon start` on the device and you are good to go.

View File

@@ -1,20 +1,25 @@
[package]
name = "wavehunter"
name = "rayhunter-daemon"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[[bin]]
name = "rayhunter"
path = "src/main.rs"
[dependencies]
orca = { path = "../orca" }
rayhunter = { path = "../lib" }
toml = "0.8.8"
serde = { version = "1.0.193", features = ["derive"] }
tokio = { version = "1.35.1", features = ["full"] }
axum = "0.7.2"
axum = "0.7.3"
futures-core = "0.3.30"
thiserror = "1.0.52"
log = "0.4.20"
env_logger = "0.10.1"
tokio-util = { version = "0.7.10", features = ["io"] }
tokio-util = { version = "0.7.10", features = ["rt"] }
futures-macro = "0.3.30"
include_dir = "0.7.3"
mime_guess = "2.0.4"
tempdir = "0.3.7"
chrono = { version = "0.4.31", features = ["serde"] }

54
bin/src/config.rs Normal file
View File

@@ -0,0 +1,54 @@
use crate::error::RayhunterError;
use serde::Deserialize;
#[derive(Deserialize)]
struct ConfigFile {
qmdl_store_path: Option<String>,
port: Option<u16>,
readonly_mode: Option<bool>,
}
#[derive(Debug)]
pub struct Config {
pub qmdl_store_path: String,
pub port: u16,
pub readonly_mode: bool,
}
impl Default for Config {
fn default() -> Self {
Config {
qmdl_store_path: "/data/rayhunter/qmdl".to_string(),
port: 8080,
readonly_mode: false,
}
}
}
pub fn parse_config<P>(path: P) -> Result<Config, RayhunterError> where P: AsRef<std::path::Path> {
let mut config = Config::default();
if let Ok(config_file) = std::fs::read_to_string(&path) {
let parsed_config: ConfigFile = toml::from_str(&config_file)
.map_err(RayhunterError::ConfigFileParsingError)?;
if let Some(path) = parsed_config.qmdl_store_path { config.qmdl_store_path = path }
if let Some(port) = parsed_config.port { config.port = port }
if let Some(readonly_mode) = parsed_config.readonly_mode { config.readonly_mode = readonly_mode }
}
Ok(config)
}
pub struct Args {
pub config_path: String,
}
pub fn parse_args() -> Args {
let args: Vec<String> = std::env::args().collect();
if args.len() != 2 {
println!("Usage: {} /path/to/config/file", args[0]);
std::process::exit(1);
}
Args {
config_path: args[1].clone(),
}
}

110
bin/src/diag.rs Normal file
View File

@@ -0,0 +1,110 @@
use std::sync::Arc;
use axum::extract::State;
use axum::http::StatusCode;
use rayhunter::diag_device::DiagDevice;
use rayhunter::diag_reader::DiagReader;
use tokio::sync::RwLock;
use tokio::sync::mpsc::{Receiver, self};
use rayhunter::qmdl::QmdlWriter;
use log::{debug, info};
use tokio::sync::mpsc::error::TryRecvError;
use tokio::task::JoinHandle;
use tokio_util::task::TaskTracker;
use crate::error::RayhunterError;
use crate::qmdl_store::QmdlStore;
use crate::server::ServerState;
pub enum DiagDeviceCtrlMessage {
StopRecording,
StartRecording(QmdlWriter<std::fs::File>),
Exit,
}
pub fn run_diag_read_thread(task_tracker: &TaskTracker, mut dev: DiagDevice, mut qmdl_file_rx: Receiver<DiagDeviceCtrlMessage>, qmdl_store_lock: Arc<RwLock<QmdlStore>>) -> JoinHandle<Result<(), RayhunterError>> {
// mpsc channel for updating QmdlStore entry filesizes. First usize is the
// index, second is the size in bytes
let (tx, mut rx) = mpsc::channel::<(usize, usize)>(1);
// Spawn a thread to monitor the (usize, usize) channel for updates,
// triggering QmdlStore updates
let qmdl_store_lock_clone = qmdl_store_lock.clone();
task_tracker.spawn(async move {
while let Some((entry_idx, new_size)) = rx.recv().await {
let mut qmdl_store = qmdl_store_lock_clone.write().await;
qmdl_store.update_entry_size(entry_idx, new_size).await
.expect("failed to update qmdl file size");
}
info!("QMDL store size updater thread exiting...");
});
// Spawn a thread to drive the DiagDevice reading loop. Since DiagDevice
// works via synchronous I/O, we have to spawn a "blocking" thread to avoid
// gumming up tokio's event loop.
task_tracker.spawn_blocking(move || {
loop {
// First check if we've gotten any control meesages
match qmdl_file_rx.try_recv() {
Ok(DiagDeviceCtrlMessage::StartRecording(qmdl_writer)) => {
dev.qmdl_writer = Some(qmdl_writer);
},
Ok(DiagDeviceCtrlMessage::StopRecording) => dev.qmdl_writer = None,
// Disconnected means all the Senders have been dropped, so it's
// time to go
Ok(DiagDeviceCtrlMessage::Exit) | Err(TryRecvError::Disconnected) => {
info!("Diag reader thread exiting...");
return Ok(())
},
// empty just means there's no message for us, so continue as normal
Err(TryRecvError::Empty) => {},
}
// remember the QmdlStore current entry index so we can update its size later
let qmdl_store_index = qmdl_store_lock.blocking_read().current_entry;
// TODO: once we're actually doing analysis, we'll wanna use the messages
// returned here. Until then, the DiagDevice has already written those messages
// to the QMDL file, so we can just ignore them.
debug!("reading response from diag device...");
let _messages = dev.read_response().map_err(RayhunterError::DiagReadError)?;
debug!("got diag response ({} messages)", _messages.len());
// keep track of how many bytes were written to the QMDL file so we can read
// a valid block of data from it in the HTTP server
if let Some(qmdl_writer) = dev.qmdl_writer.as_ref() {
debug!("total QMDL bytes written: {}, sending update...", qmdl_writer.total_written);
let index = qmdl_store_index.expect("DiagDevice had qmdl_writer, but QmdlStore didn't have current entry???");
tx.blocking_send((index, qmdl_writer.total_written)).unwrap();
debug!("done!");
} else {
debug!("no qmdl_writer set, continuing...");
}
}
})
}
pub async fn start_recording(State(state): State<Arc<ServerState>>) -> Result<(StatusCode, String), (StatusCode, String)> {
if state.readonly_mode {
return Err((StatusCode::FORBIDDEN, format!("server is in readonly mode")));
}
let mut qmdl_store = state.qmdl_store_lock.write().await;
let qmdl_file = qmdl_store.new_entry().await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't create new qmdl entry: {}", e)))?;
let qmdl_writer = QmdlWriter::new(qmdl_file.into_std().await);
state.diag_device_ctrl_sender.send(DiagDeviceCtrlMessage::StartRecording(qmdl_writer)).await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't send stop recording message: {}", e)))?;
Ok((StatusCode::ACCEPTED, format!("ok")))
}
pub async fn stop_recording(State(state): State<Arc<ServerState>>) -> Result<(StatusCode, String), (StatusCode, String)> {
if state.readonly_mode {
return Err((StatusCode::FORBIDDEN, format!("server is in readonly mode")));
}
let mut qmdl_store = state.qmdl_store_lock.write().await;
qmdl_store.close_current_entry().await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't close current qmdl entry: {}", e)))?;
state.diag_device_ctrl_sender.send(DiagDeviceCtrlMessage::StopRecording).await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't send stop recording message: {}", e)))?;
Ok((StatusCode::ACCEPTED, format!("ok")))
}

View File

@@ -1,10 +1,10 @@
use thiserror::Error;
use orca::diag_device::DiagDeviceError;
use rayhunter::diag_device::DiagDeviceError;
use crate::qmdl_store::QmdlStoreError;
#[derive(Error, Debug)]
pub enum WavehunterError {
#[error("Missing config file: {0}")]
MissingConfigFile(String),
pub enum RayhunterError{
#[error("Config file parsing error: {0}")]
ConfigFileParsingError(#[from] toml::de::Error),
#[error("Diag intialization error: {0}")]
@@ -13,4 +13,8 @@ pub enum WavehunterError {
DiagReadError(DiagDeviceError),
#[error("Tokio error: {0}")]
TokioError(#[from] tokio::io::Error),
#[error("QmdlStore error: {0}")]
QmdlStoreError(#[from] QmdlStoreError),
#[error("No QMDL store found at path {0}, but can't create a new one due to readonly mode")]
NoStoreReadonlyMode(String),
}

148
bin/src/main.rs Normal file
View File

@@ -0,0 +1,148 @@
mod config;
mod error;
mod pcap;
mod server;
mod stats;
mod qmdl_store;
mod diag;
use crate::config::{parse_config, parse_args};
use crate::diag::run_diag_read_thread;
use crate::qmdl_store::QmdlStore;
use crate::server::{ServerState, get_qmdl, serve_static};
use crate::pcap::get_pcap;
use crate::stats::get_system_stats;
use crate::error::RayhunterError;
use axum::response::Redirect;
use diag::{DiagDeviceCtrlMessage, start_recording, stop_recording};
use log::{info, error};
use rayhunter::diag_device::DiagDevice;
use axum::routing::{get, post};
use axum::Router;
use rayhunter::qmdl::QmdlWriter;
use stats::get_qmdl_manifest;
use tokio::sync::mpsc::{self, Sender};
use tokio::task::JoinHandle;
use tokio_util::task::TaskTracker;
use std::net::SocketAddr;
use tokio::net::TcpListener;
use tokio::sync::{RwLock, oneshot};
use std::sync::Arc;
// Runs the axum server, taking all the elements needed to build up our
// ServerState and a oneshot Receiver that'll fire when it's time to shutdown
// (i.e. user hit ctrl+c)
async fn run_server(
task_tracker: &TaskTracker,
config: &config::Config,
qmdl_store_lock: Arc<RwLock<QmdlStore>>,
server_shutdown_rx: oneshot::Receiver<()>,
diag_device_sender: Sender<DiagDeviceCtrlMessage>
) -> JoinHandle<()> {
let state = Arc::new(ServerState {
qmdl_store_lock,
diag_device_ctrl_sender: diag_device_sender,
readonly_mode: config.readonly_mode,
});
let app = Router::new()
.route("/api/pcap/*name", get(get_pcap))
.route("/api/qmdl/*name", get(get_qmdl))
.route("/api/system-stats", get(get_system_stats))
.route("/api/qmdl-manifest", get(get_qmdl_manifest))
.route("/api/start-recording", post(start_recording))
.route("/api/stop-recording", post(stop_recording))
.route("/", get(|| async { Redirect::permanent("/index.html") }))
.route("/*path", get(serve_static))
.with_state(state);
let addr = SocketAddr::from(([127, 0, 0, 1], config.port));
let listener = TcpListener::bind(&addr).await.unwrap();
task_tracker.spawn(async move {
info!("The orca is hunting for stingrays...");
axum::serve(listener, app)
.with_graceful_shutdown(server_shutdown_signal(server_shutdown_rx))
.await.unwrap();
})
}
async fn server_shutdown_signal(server_shutdown_rx: oneshot::Receiver<()>) {
server_shutdown_rx.await.unwrap();
info!("Server received shutdown signal, exiting...");
}
// Loads a QmdlStore if one exists, and if not, only create one if we're not in
// readonly mode.
async fn init_qmdl_store(config: &config::Config) -> Result<QmdlStore, RayhunterError> {
match (QmdlStore::exists(&config.qmdl_store_path).await?, config.readonly_mode) {
(true, _) => Ok(QmdlStore::load(&config.qmdl_store_path).await?),
(false, false) => Ok(QmdlStore::create(&config.qmdl_store_path).await?),
(false, true) => Err(RayhunterError::NoStoreReadonlyMode(config.qmdl_store_path.clone())),
}
}
// Start a thread that'll track when user hits ctrl+c. When that happens,
// trigger various cleanup tasks, including sending signals to other threads to
// shutdown
fn run_ctrl_c_thread(
task_tracker: &TaskTracker,
diag_device_sender: Sender<DiagDeviceCtrlMessage>,
server_shutdown_tx: oneshot::Sender<()>,
qmdl_store_lock: Arc<RwLock<QmdlStore>>
) -> JoinHandle<Result<(), RayhunterError>> {
task_tracker.spawn(async move {
match tokio::signal::ctrl_c().await {
Ok(()) => {
let mut qmdl_store = qmdl_store_lock.write().await;
if qmdl_store.current_entry.is_some() {
info!("Closing current QMDL entry...");
qmdl_store.close_current_entry().await?;
info!("Done!");
}
server_shutdown_tx.send(())
.expect("couldn't send server shutdown signal");
diag_device_sender.send(DiagDeviceCtrlMessage::Exit).await
.expect("couldn't send Exit message to diag thread");
},
Err(err) => {
error!("Unable to listen for shutdown signal: {}", err);
}
}
Ok(())
})
}
#[tokio::main]
async fn main() -> Result<(), RayhunterError> {
env_logger::init();
let args = parse_args();
let config = parse_config(&args.config_path)?;
// TaskTrackers give us an interface to spawn tokio threads, and then
// eventually await all of them ending
let task_tracker = TaskTracker::new();
let qmdl_store_lock = Arc::new(RwLock::new(init_qmdl_store(&config).await?));
let (tx, rx) = mpsc::channel::<DiagDeviceCtrlMessage>(1);
if !config.readonly_mode {
let qmdl_file = qmdl_store_lock.write().await.new_entry().await?;
let qmdl_writer = QmdlWriter::new(qmdl_file.into_std().await);
let mut dev = DiagDevice::new(Some(qmdl_writer))
.map_err(RayhunterError::DiagInitError)?;
dev.config_logs()
.map_err(RayhunterError::DiagInitError)?;
run_diag_read_thread(&task_tracker, dev, rx, qmdl_store_lock.clone());
}
let (server_shutdown_tx, server_shutdown_rx) = oneshot::channel::<()>();
run_ctrl_c_thread(&task_tracker, tx.clone(), server_shutdown_tx, qmdl_store_lock.clone());
run_server(&task_tracker, &config, qmdl_store_lock.clone(), server_shutdown_rx, tx).await;
task_tracker.close();
task_tracker.wait().await;
Ok(())
}

View File

@@ -1,15 +1,14 @@
use crate::ServerState;
use orca::gsmtap_parser::GsmtapParser;
use orca::pcap::GsmtapPcapWriter;
use orca::qmdl::{QmdlReader, QmdlReaderError};
use orca::diag_reader::DiagReader;
use rayhunter::gsmtap_parser::GsmtapParser;
use rayhunter::pcap::GsmtapPcapWriter;
use rayhunter::qmdl::{QmdlReader, QmdlReaderError};
use rayhunter::diag_reader::DiagReader;
use axum::body::Body;
use axum::http::header::CONTENT_TYPE;
use axum::extract::State;
use axum::extract::{State, Path};
use axum::http::StatusCode;
use axum::response::{Response, IntoResponse};
use std::fs::File;
use std::io::Write;
use std::pin::Pin;
use std::sync::Arc;
@@ -22,14 +21,19 @@ use tokio::sync::mpsc;
// written so far. This is done by spawning a blocking thread (a tokio thread
// capable of handling blocking operations) which streams chunks of pcap data to
// a channel that's piped to the client.
pub async fn get_pcap(State(state): State<Arc<ServerState>>) -> Result<Response, (StatusCode, String)> {
let qmdl_bytes_written = *state.qmdl_bytes_written.read().await;
if qmdl_bytes_written == 0 {
pub async fn get_pcap(State(state): State<Arc<ServerState>>, Path(qmdl_name): Path<String>) -> Result<Response, (StatusCode, String)> {
let qmdl_store = state.qmdl_store_lock.read().await;
let entry = qmdl_store.entry_for_name(&qmdl_name)
.ok_or((StatusCode::NOT_FOUND, format!("couldn't find qmdl file with name {}", qmdl_name)))?;
if entry.size_bytes == 0 {
return Err((
StatusCode::SERVICE_UNAVAILABLE,
"QMDL file is empty, try again in a bit!".to_string()
));
}
let qmdl_file = qmdl_store.open_entry(&entry).await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("{:?}", e)))?
.into_std().await;
let (tx, rx) = mpsc::channel(1);
let channel_reader = ChannelReader { rx };
@@ -37,8 +41,7 @@ pub async fn get_pcap(State(state): State<Arc<ServerState>>) -> Result<Response,
tokio::task::spawn_blocking(move || {
// the QMDL reader should stop at the last successfully written data
// chunk (qmdl_bytes_written)
let qmdl_file = File::open(&state.qmdl_path).unwrap();
let mut qmdl_reader = QmdlReader::new(qmdl_file, Some(qmdl_bytes_written));
let mut qmdl_reader = QmdlReader::new(qmdl_file, Some(entry.size_bytes));
let mut gsmtap_parser = GsmtapParser::new();
let mut pcap_writer = GsmtapPcapWriter::new(channel_writer).unwrap();

213
bin/src/qmdl_store.rs Normal file
View File

@@ -0,0 +1,213 @@
use std::path::{PathBuf, Path};
use thiserror::Error;
use tokio::{fs::{self, File, try_exists}, io::AsyncWriteExt};
use serde::{Deserialize, Serialize};
use chrono::{DateTime, Local};
#[derive(Debug, Error)]
pub enum QmdlStoreError {
#[error("Can't close an entry when there's no current entry")]
NoCurrentEntry,
#[error("Couldn't create file: {0}")]
CreateFileError(tokio::io::Error),
#[error("Couldn't read file: {0}")]
ReadFileError(tokio::io::Error),
#[error("Couldn't open directory at path: {0}")]
OpenDirError(tokio::io::Error),
#[error("Couldn't read manifest file: {0}")]
ReadManifestError(tokio::io::Error),
#[error("Couldn't write manifest file: {0}")]
WriteManifestError(tokio::io::Error),
#[error("Couldn't parse QMDL store manifest file: {0}")]
ParseManifestError(toml::de::Error)
}
pub struct QmdlStore {
pub path: PathBuf,
pub manifest: Manifest,
pub current_entry: Option<usize>, // index into manifest
}
#[derive(Deserialize, Serialize, Clone, PartialEq, Debug)]
pub struct Manifest {
pub entries: Vec<ManifestEntry>,
}
#[derive(Deserialize, Serialize, Clone, PartialEq, Debug)]
pub struct ManifestEntry {
pub name: String,
pub start_time: DateTime<Local>,
pub end_time: Option<DateTime<Local>>,
pub size_bytes: usize,
}
impl ManifestEntry {
fn new() -> Self {
let now = Local::now();
ManifestEntry {
name: format!("{}", now.timestamp()),
start_time: now,
end_time: None,
size_bytes: 0,
}
}
}
impl QmdlStore {
// Returns whether a directory with a "manifest.toml" exists at the given
// path (though doesn't check if that manifest is valid)
pub async fn exists<P>(path: P) -> Result<bool, QmdlStoreError> where P: AsRef<Path> {
let manifest_path = path.as_ref().join("manifest.toml");
let dir_exists = try_exists(path).await.map_err(QmdlStoreError::OpenDirError)?;
let manifest_exists = try_exists(manifest_path).await.map_err(QmdlStoreError::ReadManifestError)?;
Ok(dir_exists && manifest_exists)
}
// Loads an existing QmdlStore at the given path. Errors if no store exists,
// or if it's malformed.
pub async fn load<P>(path: P) -> Result<Self, QmdlStoreError> where P: AsRef<Path> {
let path: PathBuf = path.as_ref().to_path_buf();
let manifest = QmdlStore::read_manifest(&path).await?;
Ok(QmdlStore {
path,
manifest,
current_entry: None,
})
}
// Creates a new QmdlStore at the given path. This involves creating a dir
// and writing an empty manifest.
pub async fn create<P>(path: P) -> Result<Self, QmdlStoreError> where P: AsRef<Path> {
let manifest_path = path.as_ref().join("manifest.toml");
fs::create_dir_all(&path).await
.map_err(QmdlStoreError::OpenDirError)?;
let mut manifest_file = File::create(&manifest_path).await
.map_err(QmdlStoreError::WriteManifestError)?;
let empty_manifest = Manifest { entries: Vec::new() };
let empty_manifest_contents = toml::to_string_pretty(&empty_manifest)
.expect("failed to serialize manifest");
manifest_file.write_all(empty_manifest_contents.as_bytes()).await
.map_err(QmdlStoreError::WriteManifestError)?;
QmdlStore::load(path).await
}
async fn read_manifest<P>(path: P) -> Result<Manifest, QmdlStoreError> where P: AsRef<Path> {
let manifest_path = path.as_ref().join("manifest.toml");
let file_contents = fs::read_to_string(&manifest_path).await
.map_err(QmdlStoreError::ReadManifestError)?;
toml::from_str(&file_contents)
.map_err(QmdlStoreError::ParseManifestError)
}
// Closes the current entry (if needed), creates a new entry based on the
// current time, and updates the manifest
pub async fn new_entry(&mut self) -> Result<File, QmdlStoreError> {
// if we've already got an entry open, close it
if self.current_entry.is_some() {
self.close_current_entry().await?;
}
let new_entry = ManifestEntry::new();
let mut file_path = self.path.join(&new_entry.name);
file_path.set_extension("qmdl");
let file = File::options()
.create(true)
.write(true)
.open(&file_path).await
.map_err(QmdlStoreError::CreateFileError)?;
self.manifest.entries.push(new_entry);
self.current_entry = Some(self.manifest.entries.len() - 1);
self.write_manifest().await?;
Ok(file)
}
// Returns the corresponding QMDL file for a given entry
pub async fn open_entry(&self, entry: &ManifestEntry) -> Result<File, QmdlStoreError> {
let mut file_path = self.path.join(&entry.name);
file_path.set_extension("qmdl");
File::open(file_path).await
.map_err(QmdlStoreError::ReadFileError)
}
// Sets the current entry's end_time, updates the manifest, and unsets the
// current entry
pub async fn close_current_entry(&mut self) -> Result<(), QmdlStoreError> {
let entry_index = self.current_entry.take()
.ok_or(QmdlStoreError::NoCurrentEntry)?;
self.manifest.entries[entry_index].end_time = Some(Local::now());
self.write_manifest().await
}
// Sets the given entry's size, updating the manifest
pub async fn update_entry_size(&mut self, entry_index: usize, size_bytes: usize) -> Result<(), QmdlStoreError> {
self.manifest.entries[entry_index].size_bytes = size_bytes;
self.write_manifest().await
}
async fn write_manifest(&mut self) -> Result<(), QmdlStoreError> {
let mut manifest_file = File::options()
.write(true)
.open(self.path.join("manifest.toml")).await
.map_err(QmdlStoreError::WriteManifestError)?;
let manifest_contents = toml::to_string_pretty(&self.manifest)
.expect("failed to serialize manifest");
manifest_file.write_all(manifest_contents.as_bytes()).await
.map_err(QmdlStoreError::WriteManifestError)?;
Ok(())
}
// Finds an entry by filename
pub fn entry_for_name(&self, name: &str) -> Option<ManifestEntry> {
self.manifest.entries.iter()
.find(|entry| entry.name == name)
.cloned()
}
}
#[cfg(test)]
mod tests {
use tempdir::TempDir;
use super::*;
#[tokio::test]
async fn test_load_from_empty_dir() {
let dir = TempDir::new("qmdl_store_test").unwrap();
assert!(!QmdlStore::exists(dir.path()).await.unwrap());
let _created_store = QmdlStore::create(dir.path()).await.unwrap();
assert!(QmdlStore::exists(dir.path()).await.unwrap());
let loaded_store = QmdlStore::load(dir.path()).await.unwrap();
assert_eq!(loaded_store.manifest.entries.len(), 0);
}
#[tokio::test]
async fn test_creating_updating_and_closing_entries() {
let dir = TempDir::new("qmdl_store_test").unwrap();
let mut store = QmdlStore::create(dir.path()).await.unwrap();
let _ = store.new_entry().await.unwrap();
let entry_index = store.current_entry.unwrap();
assert_eq!(QmdlStore::read_manifest(dir.path()).await.unwrap(), store.manifest);
store.update_entry_size(entry_index, 1000).await.unwrap();
assert_eq!(store.manifest.entries[entry_index].size_bytes, 1000);
assert_eq!(QmdlStore::read_manifest(dir.path()).await.unwrap(), store.manifest);
assert!(store.manifest.entries[entry_index].end_time.is_none());
store.close_current_entry().await.unwrap();
let entry = store.entry_for_name(&store.manifest.entries[entry_index].name).unwrap();
assert!(entry.end_time.is_some());
assert_eq!(QmdlStore::read_manifest(dir.path()).await.unwrap(), store.manifest);
assert!(matches!(store.close_current_entry().await, Err(QmdlStoreError::NoCurrentEntry)));
}
#[tokio::test]
async fn test_repeated_new_entries() {
let dir = TempDir::new("qmdl_store_test").unwrap();
let mut store = QmdlStore::create(dir.path()).await.unwrap();
let _ = store.new_entry().await.unwrap();
let entry_index = store.current_entry.unwrap();
let _ = store.new_entry().await.unwrap();
let new_entry_index = store.current_entry.unwrap();
assert_ne!(entry_index, new_entry_index);
assert_eq!(store.manifest.entries.len(), 2);
}
}

View File

@@ -5,22 +5,28 @@ use axum::http::{StatusCode, HeaderValue};
use axum::response::{Response, IntoResponse};
use axum::extract::Path;
use tokio::io::AsyncReadExt;
use tokio::sync::mpsc::Sender;
use std::sync::Arc;
use tokio::sync::RwLock;
use tokio::fs::File as AsyncFile;
use tokio_util::io::ReaderStream;
use include_dir::{include_dir, Dir};
use crate::DiagDeviceCtrlMessage;
use crate::qmdl_store::QmdlStore;
pub struct ServerState {
pub qmdl_bytes_written: Arc<RwLock<usize>>,
pub qmdl_path: String,
pub qmdl_store_lock: Arc<RwLock<QmdlStore>>,
pub diag_device_ctrl_sender: Sender<DiagDeviceCtrlMessage>,
pub readonly_mode: bool,
}
pub async fn get_qmdl(State(state): State<Arc<ServerState>>) -> Result<Response, (StatusCode, String)> {
let qmdl_file = AsyncFile::open(&state.qmdl_path).await
pub async fn get_qmdl(State(state): State<Arc<ServerState>>, Path(qmdl_name): Path<String>) -> Result<Response, (StatusCode, String)> {
let qmdl_store = state.qmdl_store_lock.read().await;
let entry = qmdl_store.entry_for_name(&qmdl_name)
.ok_or((StatusCode::NOT_FOUND, format!("couldn't find qmdl file with name {}", qmdl_name)))?;
let qmdl_file = qmdl_store.open_entry(&entry).await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("error opening QMDL file: {}", e)))?;
let qmdl_bytes_written = *state.qmdl_bytes_written.read().await;
let limited_qmdl_file = qmdl_file.take(qmdl_bytes_written as u64);
let limited_qmdl_file = qmdl_file.take(entry.size_bytes as u64);
let qmdl_stream = ReaderStream::new(limited_qmdl_file);
let headers = [(CONTENT_TYPE, "application/octet-stream")];

View File

@@ -1,10 +1,11 @@
use std::sync::Arc;
use crate::qmdl_store::ManifestEntry;
use crate::server::ServerState;
use axum::Json;
use axum::extract::State;
use axum::http::StatusCode;
use axum::response::IntoResponse;
use std::sync::Arc;
use log::error;
use serde::Serialize;
use tokio::process::Command;
@@ -97,7 +98,8 @@ fn humanize_kb(kb: usize) -> String {
}
pub async fn get_system_stats(State(state): State<Arc<ServerState>>) -> Result<Json<SystemStats>, (StatusCode, String)> {
match SystemStats::new(&state.qmdl_path).await {
let qmdl_store = state.qmdl_store_lock.read().await;
match SystemStats::new(qmdl_store.path.to_str().unwrap()).await {
Ok(stats) => Ok(Json(stats)),
Err(err) => {
error!("error getting system stats: {}", err);
@@ -109,13 +111,18 @@ pub async fn get_system_stats(State(state): State<Arc<ServerState>>) -> Result<J
}
}
#[derive(Debug, Serialize)]
pub struct DiagStats {
bytes_written: usize,
#[derive(Serialize)]
pub struct ManifestStats {
pub entries: Vec<ManifestEntry>,
pub current_entry: Option<ManifestEntry>,
}
pub async fn get_diag_stats(State(state): State<Arc<ServerState>>) -> impl IntoResponse {
Json(DiagStats {
bytes_written: *state.qmdl_bytes_written.read().await,
})
pub async fn get_qmdl_manifest(State(state): State<Arc<ServerState>>) -> Result<Json<ManifestStats>, (StatusCode, String)> {
let qmdl_store = state.qmdl_store_lock.read().await;
let mut entries = qmdl_store.manifest.entries.clone();
let current_entry = qmdl_store.current_entry.map(|index| entries.remove(index));
Ok(Json(ManifestStats {
entries,
current_entry,
}))
}

40
bin/static/css/style.css Normal file
View File

@@ -0,0 +1,40 @@
td,
th {
border: 1px solid rgb(190, 190, 190);
padding: 10px;
}
td {
text-align: center;
}
tr:nth-child(even) {
background-color: #eee;
}
th[scope='col'] {
background-color: #696969;
color: #fff;
}
th[scope='row'] {
background-color: #d7d9f2;
}
tr.current {
background-color: #fe537b;
font-weight: bold;
}
caption {
padding: 10px;
caption-side: bottom;
}
table {
border-collapse: collapse;
border: 2px solid rgb(200, 200, 200);
letter-spacing: 1px;
font-family: sans-serif;
font-size: 0.8rem;
}

38
bin/static/index.html Normal file
View File

@@ -0,0 +1,38 @@
<html>
<head>
<title>rayhunter</title>
<link rel="stylesheet" type="text/css" href="css/style.css">
<script src="js/main.js"></script>
<script>
async function repeatedlyPopulate() {
await populateDivs();
setTimeout(repeatedlyPopulate, 1000);
}
window.onload = function() {
repeatedlyPopulate();
}
</script>
</head>
<body>
<div>
<button onclick="startRecording()">Start Recording</button>
<button onclick="stopRecording()">Stop Recording</button>
</div>
<table id="qmdl-manifest-table">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Date Started</th>
<th scope="col">Date Stopped</th>
<th scope="col">Size (bytes)</th>
<th scope="col">PCAP</th>
<th scope="col">QMDL</th>
</tr>
</thead>
</table>
<div>
<h3>System stats</h3>
<pre id="system-stats">Loading...</pre>
</div>
</body>
</html>

90
bin/static/js/main.js Normal file
View File

@@ -0,0 +1,90 @@
async function populateDivs() {
const systemStats = await getSystemStats();
const systemStatsDiv = document.getElementById('system-stats');
systemStatsDiv.innerHTML = JSON.stringify(systemStats, null, 2);
const qmdlManifest = await getQmdlManifest();
updateQmdlManifestTable(qmdlManifest);
}
function updateQmdlManifestTable(manifest) {
const table = document.getElementById('qmdl-manifest-table');
const numRows = table.rows.length;
for (let i=1; i<numRows; i++) {
table.deleteRow(1);
}
if (manifest.current_entry) {
const row = createEntryRow(manifest.current_entry);
row.classList.add('current');
table.appendChild(row)
}
for (let entry of manifest.entries) {
table.appendChild(createEntryRow(entry));
}
}
function createEntryRow(entry) {
const row = document.createElement('tr');
const name = document.createElement('th');
name.scope = 'row';
name.innerText = entry.name;
row.appendChild(name);
for (const key of ['start_time', 'end_time', 'size_bytes']) {
const td = document.createElement('td');
td.innerText = entry[key];
row.appendChild(td);
}
const pcap_td = document.createElement('td');
const pcap_link = document.createElement('a');
pcap_link.href = `/api/pcap/${entry.name}`;
pcap_link.innerText = 'pcap';
pcap_td.appendChild(pcap_link);
row.appendChild(pcap_td);
const qmdl_td = document.createElement('td');
const qmdl_link = document.createElement('a');
qmdl_link.href = `/api/qmdl/${entry.name}`;
qmdl_link.innerText = 'qmdl';
qmdl_td.appendChild(qmdl_link);
row.appendChild(qmdl_td);
return row;
}
async function getSystemStats() {
return JSON.parse(await req('GET', '/api/system-stats'));
}
async function getQmdlManifest() {
const manifest = JSON.parse(await req('GET', '/api/qmdl-manifest'));
if (manifest.current_entry) {
manifest.current_entry.start_time = new Date(manifest.current_entry.start_time);
}
for (entry of manifest.entries) {
entry.start_time = new Date(entry.start_time);
entry.end_time = new Date(entry.end_time);
}
// sort them in reverse chronological order
manifest.entries.reverse();
return manifest;
}
async function startRecording() {
await req('POST', '/api/start-recording');
populateDivs();
}
async function stopRecording() {
await req('POST', '/api/stop-recording');
populateDivs();
}
async function req(method, url) {
const response = await fetch(url, {
method: method,
});
const body = await response.text();
if (response.status >= 200 && response.status < 300) {
return body;
} else {
throw new Error(body);
}
}

4
config.toml.example Normal file
View File

@@ -0,0 +1,4 @@
# cat config.toml
qmdl_store_path = "/data/rayhunter/qmdl"
port = 8080
readonly_mode = false

20
install.sh Executable file
View File

@@ -0,0 +1,20 @@
cargo build --release
# Force a switch into the debug mode to enable ADB
target/x86_64-unknown-linux-gnu/release/serial AT
echo -n "device rooted, waiting for reboot"
until adb shell true
do
echo -n .
sleep 1
done
echo
echo "it's alive!"
adb push target/armv7-unknown-linux-gnueabihf/release/rootshell /tmp/
target/x86_64-unknown-linux-gnu/release/serial "AT+SYSCMD=mv /tmp/rootshell /bin/rootshell"
sleep 1
target/x86_64-unknown-linux-gnu/release/serial "AT+SYSCMD=chown root /bin/rootshell"
sleep 1
target/x86_64-unknown-linux-gnu/release/serial "AT+SYSCMD=chmod 4755 /bin/rootshell"
echo "we have root!"
adb shell id
adb push target/armv7-unknown-linux-gnueabihf/release/rayhunter /data/rayhunter/rayhunter

View File

@@ -1,8 +1,8 @@
[package]
name = "orca"
name = "rayhunter"
version = "0.1.0"
edition = "2021"
description = "Orbic Realtime Cellular Analysis"
description = "Realtime cellular data decoding and analysis for IMSI catcher detection"
[dependencies]
bytes = "1.5.0"

View File

@@ -71,7 +71,7 @@ const DIAG_IOCTL_SWITCH_LOGGING: u64 = 7;
pub struct DiagDevice {
file: File,
pub qmdl_writer: QmdlWriter<File>,
pub qmdl_writer: Option<QmdlWriter<File>>,
fully_initialized: bool,
read_buf: Vec<u8>,
use_mdm: i32,
@@ -92,16 +92,18 @@ impl DiagReader for DiagDevice {
warn!("warning: {} leftover bytes when parsing MessagesContainer", leftover_bytes.len());
}
if self.fully_initialized {
self.qmdl_writer.write_container(&container)
.map_err(DiagDeviceError::QmdlFileWriteError)?;
if let Some(qmdl_writer) = self.qmdl_writer.as_mut() {
if self.fully_initialized {
qmdl_writer.write_container(&container)
.map_err(DiagDeviceError::QmdlFileWriteError)?;
}
}
Ok(container)
}
}
impl DiagDevice {
pub fn new<P>(qmdl_path: P) -> DiagResult<Self> where P: AsRef<std::path::Path> {
pub fn new(qmdl_writer: Option<QmdlWriter<File>>) -> DiagResult<Self> {
let diag_file = std::fs::File::options()
.read(true)
.write(true)
@@ -109,21 +111,6 @@ impl DiagDevice {
.map_err(DiagDeviceError::OpenDiagDeviceError)?;
let fd = diag_file.as_raw_fd();
let qmdl_file = File::options()
.create(true)
.append(true)
.open(&qmdl_path)
.map_err(DiagDeviceError::OpenQmdlFileError)?;
let qmdl_metadata = qmdl_file.metadata().map_err(DiagDeviceError::OpenQmdlFileError)?;
if qmdl_metadata.len() != 0 {
info!(
"QMDL file {} already contains data ({} bytes), appending to it",
qmdl_path.as_ref().display(),
qmdl_metadata.len()
);
}
let qmdl_writer = QmdlWriter::new_with_existing_size(qmdl_file, qmdl_metadata.len() as usize);
enable_frame_readwrite(fd, MEMORY_DEVICE_MODE)?;
let use_mdm = determine_use_mdm(fd)?;

View File

@@ -1,10 +1,10 @@
use orca::diag::{
use rayhunter::diag::{
Message,
LogBody,
LteRrcOtaPacket,
Timestamp,
};
use orca::gsmtap_parser::GsmtapParser;
use rayhunter::gsmtap_parser::GsmtapParser;
use deku::prelude::*;
// Tests here are based on https://github.com/fgsect/scat/blob/97442580e628de414c9f7c2a185f4e28d0ee7523/tests/test_diagltelogparser.py

10
make.sh
View File

@@ -1,10 +1,2 @@
cargo build --release
# Force a switch into the debug mode to enable ADB
target/x86_64-unknown-linux-gnu/release/serial AT
adb push target/armv7-unknown-linux-gnueabihf/release/rootshell /tmp/
target/x86_64-unknown-linux-gnu/release/serial "AT+SYSCMD=mv /tmp/rootshell /bin/rootshell"
sleep 1
target/x86_64-unknown-linux-gnu/release/serial "AT+SYSCMD=chown root /bin/rootshell"
sleep 1
target/x86_64-unknown-linux-gnu/release/serial "AT+SYSCMD=chmod 4755 /bin/rootshell"
adb push target/armv7-unknown-linux-gnueabihf/release/wavehunter /data/wavehunter/wavehunter
adb push target/armv7-unknown-linux-gnueabihf/release/rayhunter /data/rayhunter/rayhunter

View File

@@ -41,9 +41,9 @@ case "$1" in
/etc/init.d/start_atfwd_daemon start
fi
if [ -f /etc/init.d/wavehunter_daemon ]
if [ -f /etc/init.d/rayhunter_daemon ]
then
/etc/init.d/wavehunter_daemon start
/etc/init.d/rayhunter_daemon start
fi
if [ -f /etc/init.d/start_stop_qti_ppp_le ]
@@ -72,9 +72,9 @@ case "$1" in
/etc/init.d/start_loc_launcher stop
fi
if [ -f /etc/init.d/wavehunter_daemon ]
if [ -f /etc/init.d/rayhunter_daemon ]
then
/etc/init.d/wavehunter_daemon stop
/etc/init.d/rayhunter_daemon stop
fi
if [ -f /etc/init.d/init_qcom_audio ]

27
scripts/rayhunter_daemon Normal file
View File

@@ -0,0 +1,27 @@
#! /bin/sshell
set -e
case "$1" in
start)
echo -n "Starting rayhunter: "
start-stop-daemon -S -b --make-pidfile --pidfile /tmp/rayhunter.pid \
--startas /bin/bash -- -c "exec /data/rayhunter/rayhunter /data/rayhunter/config.toml > /data/rayhunter/rayhunter.log 2>&1"
echo "done"
;;
stop)
echo -n "Stopping rayhunter: "
start-stop-daemon -K -p /tmp/rayhunter.pid
echo "done"
;;
restart)
$0 stop
$0 start
;;
*)
echo "Usage rayhunter_daemon { start | stop | restart }" >&2
exit 1
;;
esac
exit 0

View File

@@ -1,27 +0,0 @@
#! /bin/sshell
set -e
case "$1" in
start)
echo -n "Starting wavehunter: "
start-stop-daemon -S -b --make-pidfile --pidfile /tmp/wavehunter.pid \
--startas /bin/bash -- -c "exec /data/wavehunter/wavehunter > /data/wavehunter/wavehunter.log 2>&1"
echo "done"
;;
stop)
echo -n "Stopping wavehunter: "
start-stop-daemon -K -p /tmp/wavehunter.pid
echo "done"
;;
restart)
$0 stop
$0 start
;;
*)
echo "Usage atfwd_daemon{ start | stop | restart }" >&2
exit 1
;;
esac
exit 0

View File

@@ -1,54 +0,0 @@
use crate::error::WavehunterError;
use serde::Deserialize;
#[derive(Deserialize)]
struct ConfigFile {
qmdl_path: Option<String>,
port: Option<u16>,
debug_mode: Option<bool>,
}
#[derive(Debug)]
pub struct Config {
pub qmdl_path: String,
pub port: u16,
pub debug_mode: bool,
}
impl Default for Config {
fn default() -> Self {
Config {
qmdl_path: "./wavehunter.qmdl".to_string(),
port: 8080,
debug_mode: false,
}
}
}
pub fn parse_config<P>(path: P) -> Result<Config, WavehunterError> where P: AsRef<std::path::Path> {
let config_file = std::fs::read_to_string(&path)
.map_err(|_| WavehunterError::MissingConfigFile(format!("{:?}", path.as_ref())))?;
let parsed_config: ConfigFile = toml::from_str(&config_file)
.map_err(WavehunterError::ConfigFileParsingError)?;
let mut config = Config::default();
if let Some(path) = parsed_config.qmdl_path { config.qmdl_path = path }
if let Some(port) = parsed_config.port { config.port = port }
if let Some(debug_mode) = parsed_config.debug_mode { config.debug_mode = debug_mode }
Ok(config)
}
pub struct Args {
pub config_path: String,
}
pub fn parse_args() -> Args {
let args: Vec<String> = std::env::args().collect();
if args.len() != 2 {
println!("Usage: {} /path/to/config/file", args[0]);
std::process::exit(1);
}
Args {
config_path: args[1].clone(),
}
}

View File

@@ -1,92 +0,0 @@
mod config;
mod error;
mod pcap;
mod server;
mod stats;
use crate::config::{parse_config, parse_args};
use crate::server::{ServerState, get_qmdl, serve_static};
use crate::pcap::get_pcap;
use crate::stats::{get_system_stats, get_diag_stats};
use crate::error::WavehunterError;
use axum::response::Redirect;
use orca::diag_device::DiagDevice;
use orca::diag_reader::DiagReader;
use axum::routing::get;
use axum::Router;
use tokio::fs::File;
use log::debug;
use std::net::SocketAddr;
use tokio::net::TcpListener;
use tokio::sync::RwLock;
use tokio::task::JoinHandle;
use std::sync::Arc;
fn run_diag_read_thread(mut dev: DiagDevice, bytes_read_lock: Arc<RwLock<usize>>) -> JoinHandle<Result<(), WavehunterError>> {
tokio::task::spawn_blocking(move || {
loop {
// TODO: once we're actually doing analysis, we'll wanna use the messages
// returned here. Until then, the DiagDevice has already written those messages
// to the QMDL file, so we can just ignore them.
debug!("reading response from diag device...");
let _messages = dev.read_response().map_err(WavehunterError::DiagReadError)?;
debug!("got diag response ({} messages)", _messages.len());
// keep track of how many bytes were written to the QMDL file so we can read
// a valid block of data from it in the HTTP server
debug!("total QMDL bytes written: {}, updating state...", dev.qmdl_writer.total_written);
let mut bytes_read = bytes_read_lock.blocking_write();
*bytes_read = dev.qmdl_writer.total_written;
debug!("done!");
}
})
}
async fn run_server(config: &config::Config, qmdl_bytes_written: Arc<RwLock<usize>>) -> Result<(), WavehunterError> {
let state = Arc::new(ServerState {
qmdl_bytes_written,
qmdl_path: config.qmdl_path.clone(),
});
let app = Router::new()
.route("/api/pcap/latest.pcap", get(get_pcap))
.route("/api/qmdl/latest.qmdl", get(get_qmdl))
.route("/api/system-stats", get(get_system_stats))
.route("/api/diag-stats", get(get_diag_stats))
.route("/", get(|| async { Redirect::permanent("/index.html") }))
.route("/*path", get(serve_static))
.with_state(state);
let addr = SocketAddr::from(([127, 0, 0, 1], config.port));
let listener = TcpListener::bind(&addr).await.unwrap();
axum::serve(listener, app).await.unwrap();
Ok(())
}
#[tokio::main]
async fn main() -> Result<(), WavehunterError> {
env_logger::init();
let args = parse_args();
let config = parse_config(&args.config_path)?;
let qmdl_bytes_lock: Arc<RwLock<usize>>;
if !config.debug_mode {
let mut dev = DiagDevice::new(&config.qmdl_path)
.map_err(WavehunterError::DiagInitError)?;
dev.config_logs()
.map_err(WavehunterError::DiagInitError)?;
qmdl_bytes_lock = Arc::new(RwLock::new(dev.qmdl_writer.total_written));
// TODO: handle exiting gracefully
let _read_thread_handle = run_diag_read_thread(dev, qmdl_bytes_lock.clone());
} else {
let qmdl_file = File::open(&config.qmdl_path).await.expect("couldn't open QMDL file");
let qmdl_file_size = qmdl_file.metadata().await.expect("couldn't get QMDL file metadata")
.len() as usize;
qmdl_bytes_lock = Arc::new(RwLock::new(qmdl_file_size));
}
println!("The orca is hunting for stingrays...");
run_server(&config, qmdl_bytes_lock).await
}

View File

@@ -1,22 +0,0 @@
<html>
<head>
<title>ORCA</title>
<link rel="stylesheet" type="text/css" href="css/style.css">
<script src="js/main.js"></script>
<script>
window.onload = function() {
setInterval(function() {
populateDivs();
}, 1000);
}
</script>
</head>
<body>
<div id="downloads">
<span><a href="/api/pcap/latest.pcap">Latest PCAP</a></span>
<span><a href="/api/qmdl/latest.qmdl">Latest QMDL</a></span>
</div>
<pre id="system-stats">Loading...</pre>
<pre id="diag-stats">Loading...</pre>
</body>
</html>

View File

@@ -1,24 +0,0 @@
async function populateDivs() {
const systemStats = await getSystemStats();
const diagStats = await getDiagStats();
const systemStatsDiv = document.getElementById('system-stats');
const diagStatsDiv = document.getElementById('diag-stats');
systemStatsDiv.innerHTML = JSON.stringify(systemStats, null, 2);
diagStatsDiv.innerHTML = JSON.stringify(diagStats, null, 2);
}
async function getSystemStats() {
return await getJson('/api/system-stats');
}
async function getDiagStats() {
return await getJson('/api/diag-stats');
}
async function getJson(url) {
const response = await fetch(url);
const data = await response.json();
return data;
}