Merge pull request #17 from EFForg/http-server

Http server, lots more testing, and more!
This commit is contained in:
Cooper Quintin
2024-01-04 14:10:53 -08:00
committed by GitHub
23 changed files with 1604 additions and 343 deletions
Generated
+690 -29
View File
@@ -2,6 +2,21 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "addr2line"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aho-corasick"
version = "1.1.2"
@@ -26,12 +41,97 @@ dependencies = [
"libc",
]
[[package]]
name = "async-trait"
version = "0.1.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdf6721fb0140e4f897002dd086c06f6c27775df19cfe1fccb21181a48fd2c98"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.43",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "axum"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "202651474fe73c62d9e0a56c6133f7a0ff1dc1c8cf7a5b03381af2a26553ac9d"
dependencies = [
"async-trait",
"axum-core",
"bytes",
"futures-util",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-util",
"itoa",
"matchit",
"memchr",
"mime",
"percent-encoding",
"pin-project-lite",
"rustversion",
"serde",
"serde_json",
"serde_path_to_error",
"serde_urlencoded",
"sync_wrapper",
"tokio",
"tower",
"tower-layer",
"tower-service",
]
[[package]]
name = "axum-core"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77cb22c689c44d4c07b0ab44ebc25d69d8ae601a2f28fb8d672d344178fa17aa"
dependencies = [
"async-trait",
"bytes",
"futures-util",
"http",
"http-body",
"http-body-util",
"mime",
"pin-project-lite",
"rustversion",
"sync_wrapper",
"tower-layer",
"tower-service",
]
[[package]]
name = "backtrace"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.1"
@@ -232,12 +332,85 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "form_urlencoded"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
dependencies = [
"percent-encoding",
]
[[package]]
name = "funty"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]]
name = "futures-channel"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
dependencies = [
"futures-core",
]
[[package]]
name = "futures-core"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
[[package]]
name = "futures-sink"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
[[package]]
name = "futures-task"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
[[package]]
name = "futures-util"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
dependencies = [
"futures-core",
"futures-task",
"pin-project-lite",
"pin-utils",
]
[[package]]
name = "gimli"
version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]]
name = "h2"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1d308f63daf4181410c242d34c11f928dcb3aa105852019e043c9d1f4e4368a"
dependencies = [
"bytes",
"fnv",
"futures-core",
"futures-sink",
"futures-util",
"http",
"indexmap",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "hashbrown"
version = "0.14.3"
@@ -250,12 +423,95 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
[[package]]
name = "http"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]]
name = "http-body"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643"
dependencies = [
"bytes",
"http",
]
[[package]]
name = "http-body-util"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41cb79eb393015dadd30fc252023adb0b2400a0caee0fa2a077e6e21a551e840"
dependencies = [
"bytes",
"futures-util",
"http",
"http-body",
"pin-project-lite",
]
[[package]]
name = "httparse"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
[[package]]
name = "httpdate"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "hyper"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5aa53871fc917b1a9ed87b683a5d86db645e23acb32c2e0785a353e522fb75"
dependencies = [
"bytes",
"futures-channel",
"futures-util",
"h2",
"http",
"http-body",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
"tokio",
]
[[package]]
name = "hyper-util"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdea9aac0dbe5a9240d68cfd9501e2db94222c6dc06843e06640b9e07f0fdc67"
dependencies = [
"bytes",
"futures-channel",
"futures-util",
"http",
"http-body",
"hyper",
"pin-project-lite",
"socket2",
"tokio",
"tracing",
]
[[package]]
name = "iana-time-zone"
version = "0.1.58"
@@ -306,6 +562,12 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "itoa"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
[[package]]
name = "js-sys"
version = "0.3.66"
@@ -317,9 +579,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.150"
version = "0.2.151"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
[[package]]
name = "linux-raw-sys"
@@ -327,18 +589,60 @@ version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
[[package]]
name = "lock_api"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "matchit"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
[[package]]
name = "memchr"
version = "2.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
[[package]]
name = "mime"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "miniz_oxide"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
dependencies = [
"adler",
]
[[package]]
name = "mio"
version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
dependencies = [
"libc",
"wasi",
"windows-sys 0.48.0",
]
[[package]]
name = "num-traits"
version = "0.2.17"
@@ -349,10 +653,67 @@ dependencies = [
]
[[package]]
name = "once_cell"
version = "1.18.0"
name = "num_cpus"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "object"
version = "0.32.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-targets 0.48.5",
]
[[package]]
name = "pcap-file"
@@ -365,6 +726,44 @@ dependencies = [
"thiserror",
]
[[package]]
name = "percent-encoding"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "pin-project"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.43",
]
[[package]]
name = "pin-project-lite"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "proc-macro-crate"
version = "1.3.1"
@@ -372,14 +771,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
dependencies = [
"once_cell",
"toml_edit",
"toml_edit 0.19.15",
]
[[package]]
name = "proc-macro2"
version = "1.0.69"
version = "1.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8"
dependencies = [
"unicode-ident",
]
@@ -399,6 +798,15 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "redox_syscall"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "regex"
version = "1.10.2"
@@ -429,18 +837,138 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "rustix"
version = "0.38.26"
name = "rustc-demangle"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9470c4bf8246c8daf25f9598dca807fb6510347b1e1cfa55749113850c79d88a"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "rustix"
version = "0.38.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
dependencies = [
"bitflags",
"bitflags 2.4.1",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.52.0",
]
[[package]]
name = "rustversion"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
[[package]]
name = "ryu"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.193"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.193"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.43",
]
[[package]]
name = "serde_json"
version = "1.0.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "serde_path_to_error"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335"
dependencies = [
"itoa",
"serde",
]
[[package]]
name = "serde_spanned"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
dependencies = [
"serde",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
"form_urlencoded",
"itoa",
"ryu",
"serde",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
dependencies = [
"libc",
]
[[package]]
name = "slab"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
dependencies = [
"autocfg",
]
[[package]]
name = "smallvec"
version = "1.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
[[package]]
name = "socket2"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
dependencies = [
"libc",
"windows-sys 0.48.0",
]
[[package]]
name = "strsim"
version = "0.10.0"
@@ -460,15 +988,21 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.39"
version = "2.0.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "sync_wrapper"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
[[package]]
name = "tap"
version = "1.0.1"
@@ -486,22 +1020,78 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.50"
version = "1.0.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
checksum = "83a48fd946b02c0a526b2e9481c8e2a17755e47039164a86c4070446e3a4614d"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.50"
version = "1.0.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.43",
]
[[package]]
name = "tokio"
version = "1.35.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104"
dependencies = [
"backtrace",
"bytes",
"libc",
"mio",
"num_cpus",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.48.0",
]
[[package]]
name = "tokio-macros"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.43",
]
[[package]]
name = "tokio-util"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15"
dependencies = [
"bytes",
"futures-core",
"futures-sink",
"pin-project-lite",
"tokio",
"tracing",
]
[[package]]
name = "toml"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit 0.21.0",
]
[[package]]
@@ -509,6 +1099,9 @@ name = "toml_datetime"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
@@ -521,12 +1114,79 @@ dependencies = [
"winnow",
]
[[package]]
name = "toml_edit"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
]
[[package]]
name = "tower"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
dependencies = [
"futures-core",
"futures-util",
"pin-project",
"pin-project-lite",
"tokio",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "tower-layer"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
[[package]]
name = "tower-service"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
[[package]]
name = "tracing"
version = "0.1.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
dependencies = [
"log",
"pin-project-lite",
"tracing-core",
]
[[package]]
name = "tracing-core"
version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
dependencies = [
"once_cell",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.89"
@@ -548,7 +1208,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.43",
"wasm-bindgen-shared",
]
@@ -570,7 +1230,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.43",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -585,15 +1245,16 @@ checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f"
name = "wavehunter"
version = "0.1.0"
dependencies = [
"bytes",
"chrono",
"crc",
"deku",
"axum",
"env_logger",
"libc",
"futures-core",
"log",
"pcap-file",
"orca",
"serde",
"thiserror",
"tokio",
"tokio-util",
"toml",
]
[[package]]
@@ -770,9 +1431,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
[[package]]
name = "winnow"
version = "0.5.19"
version = "0.5.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b"
checksum = "97a4882e6b134d6c28953a387571f1acdd3496830d5e36c5e3a1075580ea641c"
dependencies = [
"memchr",
]
+6 -29
View File
@@ -1,30 +1,7 @@
[package]
name = "wavehunter"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "wavehunter"
path = "src/lib.rs"
[[bin]]
name = "wavehunter-reader"
path = "src/bin/wavehunter_reader.rs"
[[bin]]
name = "wavehunter"
path = "src/bin/wavehunter.rs"
[dependencies]
bytes = "1.5.0"
chrono = "0.4.31"
crc = "3.0.1"
deku = { version = "0.16.0", features = ["logging"] }
env_logger = "0.10.1"
libc = "0.2.150"
log = "0.4.20"
pcap-file = "2.0.0"
thiserror = "1.0.50"
[workspace]
members = [
"orca",
"wavehunter",
]
resolver = "2"
+16
View File
@@ -0,0 +1,16 @@
[package]
name = "orca"
version = "0.1.0"
edition = "2021"
description = "Orbic Realtime Cellular Analysis"
[dependencies]
bytes = "1.5.0"
chrono = "0.4.31"
crc = "3.0.1"
deku = { version = "0.16.0", features = ["logging"] }
env_logger = "0.10.1"
libc = "0.2.150"
log = "0.4.20"
pcap-file = "2.0.0"
thiserror = "1.0.50"
+14 -8
View File
@@ -3,6 +3,12 @@
use chrono::{DateTime, FixedOffset};
use deku::prelude::*;
pub const MESSAGE_TERMINATOR: u8 = 0x7e;
pub const MESSAGE_ESCAPE_CHAR: u8 = 0x7d;
pub const ESCAPED_MESSAGE_TERMINATOR: u8 = 0x5e;
pub const ESCAPED_MESSAGE_ESCAPE_CHAR: u8 = 0x5d;
#[derive(Debug, Clone, DekuWrite)]
pub struct RequestContainer {
pub data_type: DataType,
@@ -43,7 +49,7 @@ pub enum DataType {
Other(u32),
}
#[derive(Debug, Clone, DekuRead)]
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
pub struct MessagesContainer {
pub data_type: DataType,
pub num_messages: u32,
@@ -51,14 +57,14 @@ pub struct MessagesContainer {
pub messages: Vec<HdlcEncapsulatedMessage>,
}
#[derive(Debug, Clone, DekuRead)]
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
pub struct HdlcEncapsulatedMessage {
pub len: u32,
#[deku(count = "len")]
pub data: Vec<u8>,
}
#[derive(Debug, Clone, PartialEq, DekuRead)]
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
#[deku(type = "u8")]
pub enum Message {
#[deku(id = "16")]
@@ -87,7 +93,7 @@ pub enum Message {
},
}
#[derive(Debug, Clone, PartialEq, DekuRead)]
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
#[deku(ctx = "log_type: u16, hdr_len: u16", id = "log_type")]
pub enum LogBody {
#[deku(id = "0x412f")]
@@ -155,7 +161,7 @@ pub enum LogBody {
}
}
#[derive(Debug, Clone, PartialEq, DekuRead)]
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
#[deku(ctx = "ext_header_version: u8", id = "ext_header_version")]
pub enum LteRrcOtaPacket {
#[deku(id_pat = "0..=4")]
@@ -262,7 +268,7 @@ impl LteRrcOtaPacket {
}
}
#[derive(Debug, Clone, PartialEq, DekuRead)]
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
#[deku(endian = "little")]
pub struct Timestamp {
pub ts: u64,
@@ -282,14 +288,14 @@ impl Timestamp {
}
}
#[derive(Debug, Clone, PartialEq, DekuRead)]
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
#[deku(ctx = "opcode: u32, subopcode: u32", id = "opcode")]
pub enum ResponsePayload {
#[deku(id = "115")]
LogConfig(#[deku(ctx = "subopcode")] LogConfigResponse),
}
#[derive(Debug, Clone, PartialEq, DekuRead)]
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
#[deku(ctx = "subopcode: u32", id = "subopcode")]
pub enum LogConfigResponse {
#[deku(id = "1")]
+75 -52
View File
@@ -1,11 +1,11 @@
use crate::hdlc::{hdlc_encapsulate, HdlcError};
use crate::hdlc::hdlc_encapsulate;
use crate::diag::{Message, ResponsePayload, Request, LogConfigRequest, LogConfigResponse, build_log_mask_request, RequestContainer, DataType, MessagesContainer};
use crate::diag_reader::{DiagReader, CRC_CCITT};
use crate::debug_file::DebugFileBlock;
use crate::qmdl::QmdlWriter;
use crate::log_codes;
use std::fs::File;
use std::io::{Read, Write};
use std::io::Read;
use std::os::fd::AsRawFd;
use thiserror::Error;
use log::{info, warn, error};
@@ -15,20 +15,24 @@ pub type DiagResult<T> = Result<T, DiagDeviceError>;
#[derive(Error, Debug)]
pub enum DiagDeviceError {
#[error("IO error {0}")]
IO(#[from] std::io::Error),
#[error("Failed to initialize /dev/diag: {0}")]
InitializationFailed(String),
#[error("Failed to read diag device: {0}")]
DeviceReadFailed(String),
DeviceReadFailed(std::io::Error),
#[error("Failed to write diag device: {0}")]
DeviceWriteFailed(String),
#[error("Nonzero status code {0} for diag request: {1:?}")]
RequestFailed(u32, Request),
#[error("Didn't receive response for request: {0:?}")]
NoResponse(Request),
#[error("HDLC error {0}")]
HdlcError(#[from] HdlcError),
#[error("Deku error {0}")]
DekuError(#[from] DekuError),
#[error("Failed to open QMDL file: {0}")]
OpenQmdlFileError(std::io::Error),
#[error("Failed to write to QMDL file: {0}")]
QmdlFileWriteError(std::io::Error),
#[error("Failed to open diag device: {0}")]
OpenDiagDeviceError(std::io::Error),
#[error("Failed to parse MessagesContainer: {0}")]
ParseMessagesContainerError(deku::DekuError),
}
pub const LOG_CODES_FOR_RAW_PACKET_LOGGING: [u32; 11] = [
@@ -40,97 +44,113 @@ pub const LOG_CODES_FOR_RAW_PACKET_LOGGING: [u32; 11] = [
log_codes::WCDMA_SIGNALLING_MESSAGE, // 0x412f
log_codes::LOG_LTE_RRC_OTA_MSG_LOG_C, // 0xb0c0
log_codes::LOG_NR_RRC_OTA_MSG_LOG_C, // 0xb821
// NAS:
log_codes::LOG_UMTS_NAS_OTA_MESSAGE_LOG_PACKET_C, // 0x713a
log_codes::LOG_LTE_NAS_ESM_OTA_IN_MSG_LOG_C, // 0xb0e2
log_codes::LOG_LTE_NAS_ESM_OTA_OUT_MSG_LOG_C, // 0xb0e3
log_codes::LOG_LTE_NAS_EMM_OTA_IN_MSG_LOG_C, // 0xb0ec
log_codes::LOG_LTE_NAS_EMM_OTA_OUT_MSG_LOG_C, // 0xb0ed
// User IP traffic:
log_codes::LOG_DATA_PROTOCOL_LOGGING_C // 0x11eb
];
const BUFFER_LEN: usize = 1024 * 1024 * 10;
const MEMORY_DEVICE_MODE: i32 = 2;
#[cfg(target_arch = "arm")]
const DIAG_IOCTL_REMOTE_DEV: u32 = 32;
#[cfg(target_arch = "x86_64")]
const DIAG_IOCTL_REMOTE_DEV: u64 = 32;
#[cfg(target_arch = "arm")]
const DIAG_IOCTL_SWITCH_LOGGING: u32 = 7;
#[cfg(target_arch = "x86_64")]
const DIAG_IOCTL_SWITCH_LOGGING: u64 = 7;
pub struct DiagDevice {
file: File,
debug_file: Option<File>,
pub qmdl_writer: QmdlWriter<File>,
fully_initialized: bool,
read_buf: Vec<u8>,
use_mdm: i32,
}
impl DiagReader for DiagDevice {
type Err = DiagDeviceError;
fn get_next_messages_container(&mut self) -> DiagResult<MessagesContainer> {
let mut bytes_read = 0;
while bytes_read == 0 {
bytes_read = self.file.read(&mut self.read_buf)?;
bytes_read = self.file.read(&mut self.read_buf)
.map_err(DiagDeviceError::DeviceReadFailed)?;
}
if let Some(debug_file) = self.debug_file.as_mut() {
let debug_block = DebugFileBlock {
size: bytes_read as u32,
data: &self.read_buf[0..bytes_read],
};
let debug_block_bytes = debug_block.to_bytes()?;
debug_file.write_all(&debug_block_bytes)?;
}
let ((leftover_bytes, _), container) = MessagesContainer::from_bytes((&self.read_buf[0..bytes_read], 0))?;
if leftover_bytes.len() > 0 {
let ((leftover_bytes, _), container) = MessagesContainer::from_bytes((&self.read_buf[0..bytes_read], 0))
.map_err(DiagDeviceError::ParseMessagesContainerError)?;
if !leftover_bytes.is_empty() {
warn!("warning: {} leftover bytes when parsing MessagesContainer", leftover_bytes.len());
}
if self.fully_initialized {
self.qmdl_writer.write_container(&container)
.map_err(DiagDeviceError::QmdlFileWriteError)?;
}
Ok(container)
}
}
impl DiagDevice {
pub fn new() -> DiagResult<Self> {
let file = std::fs::File::options()
pub fn new<P>(qmdl_path: P) -> DiagResult<Self> where P: AsRef<std::path::Path> {
let diag_file = std::fs::File::options()
.read(true)
.write(true)
.open("/dev/diag")?;
let fd = file.as_raw_fd();
.open("/dev/diag")
.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)?;
Ok(DiagDevice {
read_buf: vec![0; BUFFER_LEN],
file,
debug_file: None,
file: diag_file,
fully_initialized: false,
qmdl_writer,
use_mdm,
})
}
// Creates a file at the given path where all binary output from /dev/diag
// will be recorded.
pub fn enable_debug_mode<P>(&mut self, path: P) -> DiagResult<()> where P: AsRef<std::path::Path> {
let debug_file = std::fs::File::options()
.create(true)
.write(true)
.open(path)?;
info!("enabling debug mode, writing debug output to {:?}", debug_file);
self.debug_file = Some(debug_file);
Ok(())
}
pub fn write_request(&mut self, req: &Request) -> DiagResult<()> {
let req_bytes = &req.to_bytes().expect("Failed to serialize Request");
let buf = RequestContainer {
data_type: DataType::UserSpace,
use_mdm: self.use_mdm > 0,
mdm_field: -1,
hdlc_encapsulated_request: hdlc_encapsulate(&req.to_bytes()?, &CRC_CCITT),
}.to_bytes()?;
hdlc_encapsulated_request: hdlc_encapsulate(req_bytes, &CRC_CCITT),
}.to_bytes().expect("Failed to serialize RequestContainer");
unsafe {
let fd = self.file.as_raw_fd();
let buf_ptr = buf.as_ptr() as *const libc::c_void;
let ret = libc::write(fd, buf_ptr, buf.len());
if ret < 0 {
let msg = format!("write failed with error code {}", ret);
return Err(DiagDeviceError::DeviceReadFailed(msg));
return Err(DiagDeviceError::DeviceWriteFailed(msg));
}
}
Ok(())
@@ -142,8 +162,8 @@ impl DiagDevice {
for msg in self.read_response()? {
match msg {
Message::Log { .. } => info!("skipping log response..."),
Message::Response { payload, status, .. } => match payload {
Ok(Message::Log { .. }) => info!("skipping log response..."),
Ok(Message::Response { payload, status, .. }) => match payload {
ResponsePayload::LogConfig(LogConfigResponse::RetrieveIdRanges { log_mask_sizes }) => {
if status != 0 {
return Err(DiagDeviceError::RequestFailed(status, req));
@@ -152,6 +172,7 @@ impl DiagDevice {
},
_ => info!("skipping non-LogConfigResponse response..."),
},
Err(e) => error!("error parsing message: {:?}", e),
}
}
@@ -164,8 +185,8 @@ impl DiagDevice {
for msg in self.read_response()? {
match msg {
Message::Log { .. } => info!("skipping log response..."),
Message::Response { payload, status, .. } => {
Ok(Message::Log { .. }) => info!("skipping log response..."),
Ok(Message::Response { payload, status, .. }) => {
if let ResponsePayload::LogConfig(LogConfigResponse::SetMask) = payload {
if status != 0 {
return Err(DiagDeviceError::RequestFailed(status, req));
@@ -173,6 +194,7 @@ impl DiagDevice {
return Ok(());
}
},
Err(e) => error!("error parsing message: {:?}", e),
}
}
@@ -190,6 +212,7 @@ impl DiagDevice {
}
}
self.fully_initialized = true;
Ok(())
}
}
@@ -197,10 +220,10 @@ impl DiagDevice {
// Triggers the diag device's debug logging mode
fn enable_frame_readwrite(fd: i32, mode: i32) -> DiagResult<()> {
unsafe {
if libc::ioctl(fd, DIAG_IOCTL_SWITCH_LOGGING.into(), mode, 0, 0, 0) < 0 {
if libc::ioctl(fd, DIAG_IOCTL_SWITCH_LOGGING, mode, 0, 0, 0) < 0 {
let ret = libc::ioctl(
fd,
DIAG_IOCTL_SWITCH_LOGGING.into(),
DIAG_IOCTL_SWITCH_LOGGING,
&mut [mode, -1, 0] as *mut _, // diag_logging_mode_param_t
std::mem::size_of::<[i32; 3]>(), 0, 0, 0, 0
);
@@ -218,7 +241,7 @@ fn enable_frame_readwrite(fd: i32, mode: i32) -> DiagResult<()> {
fn determine_use_mdm(fd: i32) -> DiagResult<i32> {
let use_mdm: i32 = 0;
unsafe {
if libc::ioctl(fd, DIAG_IOCTL_REMOTE_DEV.into(), &use_mdm as *const i32) < 0 {
if libc::ioctl(fd, DIAG_IOCTL_REMOTE_DEV, &use_mdm as *const i32) < 0 {
let msg = format!("DIAG_IOCTL_REMOTE_DEV ioctl failed with error code {}", 0);
return Err(DiagDeviceError::InitializationFailed(msg))
}
+223
View File
@@ -0,0 +1,223 @@
use crate::diag;
use crate::{diag::*, hdlc::hdlc_decapsulate};
use crate::hdlc;
use crc::{Crc, Algorithm};
use deku::prelude::*;
use log::{info, warn, error};
use thiserror::Error;
// this is sorta based on the params qcsuper uses, plus what seems to be used in
// https://github.com/fgsect/scat/blob/f1538b397721df3ab8ba12acd26716abcf21f78b/util.py#L47
pub const CRC_CCITT_ALG: Algorithm<u16> = Algorithm {
poly: 0x1021,
init: 0xffff,
refin: true,
refout: true,
width: 16,
xorout: 0xffff,
check: 0x2189,
residue: 0x0000,
};
pub const CRC_CCITT: Crc<u16> = Crc::<u16>::new(&CRC_CCITT_ALG);
#[derive(Debug, PartialEq, Error)]
pub enum DiagParsingError {
#[error("Failed to parse Message: {0}, data: {1:?}")]
MessageParsingError(deku::DekuError, Vec<u8>),
#[error("HDLC decapsulation of message failed: {0}, data: {1:?}")]
HdlcDecapsulationError(hdlc::HdlcError, Vec<u8>),
}
type MaybeMessage = Result<Message, DiagParsingError>;
pub trait DiagReader {
type Err;
fn get_next_messages_container(&mut self) -> Result<MessagesContainer, Self::Err>;
fn read_response(&mut self) -> Result<Vec<MaybeMessage>, Self::Err> {
loop {
let container = self.get_next_messages_container()?;
if container.data_type == DataType::UserSpace {
return self.parse_response_container(container);
} else {
info!("skipping non-userspace message...")
}
}
}
fn parse_response_container(&self, container: MessagesContainer) -> Result<Vec<MaybeMessage>, Self::Err> {
let mut result = Vec::new();
for msg in container.messages {
for sub_msg in msg.data.split_inclusive(|&b| b == diag::MESSAGE_TERMINATOR) {
match hdlc_decapsulate(sub_msg, &CRC_CCITT) {
Ok(data) => match Message::from_bytes((&data, 0)) {
Ok(((leftover_bytes, _), res)) => {
if !leftover_bytes.is_empty() {
warn!("warning: {} leftover bytes when parsing Message", leftover_bytes.len());
}
result.push(Ok(res));
},
Err(e) => {
result.push(Err(DiagParsingError::MessageParsingError(e, data)));
},
},
Err(err) => {
result.push(Err(DiagParsingError::HdlcDecapsulationError(err, sub_msg.to_vec())));
}
}
}
}
Ok(result)
}
}
#[cfg(test)]
mod test {
use super::*;
struct MockReader {
containers: Vec<MessagesContainer>,
}
impl DiagReader for MockReader {
type Err = ();
fn get_next_messages_container(&mut self) -> Result<MessagesContainer, Self::Err> {
Ok(self.containers.remove(0))
}
}
fn make_container(data_type: DataType, message: HdlcEncapsulatedMessage) -> MessagesContainer {
MessagesContainer {
data_type,
num_messages: 1,
messages: vec![message],
}
}
// this log is based on one captured on a real device -- if it fails to
// serialize or deserialize, that's probably a problem with this mock, not
// the DiagReader implementation
fn get_test_message(payload: &[u8]) -> (HdlcEncapsulatedMessage, Message) {
let length_with_payload = 31 + payload.len() as u16;
let message = Message::Log {
pending_msgs: 0,
outer_length: length_with_payload,
inner_length: length_with_payload,
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: payload.len() as u16,
packet: payload.to_vec(),
},
},
};
let serialized = message.to_bytes().expect("failed to serialize test message");
let encapsulated_data = hdlc::hdlc_encapsulate(&serialized, &CRC_CCITT);
let encapsulated = HdlcEncapsulatedMessage {
len: encapsulated_data.len() as u32,
data: encapsulated_data,
};
(encapsulated, message)
}
#[test]
fn test_skipping_nonuser_containers() {
let (encapsulated1, message1) = get_test_message(&[1]);
let (encapsulated2, _) = get_test_message(&[2]);
let (encapsulated3, message3) = get_test_message(&[3]);
let mut reader = MockReader {
containers: vec![
make_container(DataType::UserSpace, encapsulated1),
make_container(DataType::Other(0), encapsulated2),
make_container(DataType::UserSpace, encapsulated3),
],
};
assert_eq!(reader.read_response(), Ok(vec![Ok(message1)]));
assert_eq!(reader.read_response(), Ok(vec![Ok(message3)]));
}
#[test]
fn test_containers_with_multiple_messages() {
let (encapsulated1, message1) = get_test_message(&[1]);
let (encapsulated2, message2) = get_test_message(&[2]);
let mut container1 = make_container(DataType::UserSpace, encapsulated1);
container1.messages.push(encapsulated2);
container1.num_messages += 1;
let (encapsulated3, message3) = get_test_message(&[3]);
let mut reader = MockReader {
containers: vec![
container1,
make_container(DataType::UserSpace, encapsulated3),
],
};
assert_eq!(reader.read_response(), Ok(vec![Ok(message1), Ok(message2)]));
assert_eq!(reader.read_response(), Ok(vec![Ok(message3)]));
}
#[test]
fn test_containers_with_concatenated_message() {
let (mut encapsulated1, message1) = get_test_message(&[1]);
let (encapsulated2, message2) = get_test_message(&[2]);
encapsulated1.data.extend(encapsulated2.data);
encapsulated1.len += encapsulated2.len;
let (encapsulated3, message3) = get_test_message(&[3]);
let mut reader = MockReader {
containers: vec![
make_container(DataType::UserSpace, encapsulated1),
make_container(DataType::UserSpace, encapsulated3),
],
};
assert_eq!(reader.read_response(), Ok(vec![Ok(message1), Ok(message2)]));
assert_eq!(reader.read_response(), Ok(vec![Ok(message3)]));
}
#[test]
fn test_handles_parsing_errors() {
let (encapsulated1, message1) = get_test_message(&[1]);
let bad_message = hdlc::hdlc_encapsulate(&[0x01, 0x02, 0x03, 0x04], &CRC_CCITT);
let encapsulated2 = HdlcEncapsulatedMessage {
len: bad_message.len() as u32,
data: bad_message,
};
let mut container = make_container(DataType::UserSpace, encapsulated1);
container.messages.push(encapsulated2);
container.num_messages += 1;
let mut reader = MockReader {
containers: vec![container],
};
let result = reader.read_response().unwrap();
assert_eq!(result[0], Ok(message1));
assert!(matches!(result[1], Err(DiagParsingError::MessageParsingError(_, _))));
}
#[test]
fn test_handles_encapsulation_errors() {
let (encapsulated1, message1) = get_test_message(&[1]);
let bad_encapsulation = HdlcEncapsulatedMessage {
len: 4,
data: vec![0x01, 0x02, 0x03, 0x04],
};
let mut container = make_container(DataType::UserSpace, encapsulated1);
container.messages.push(bad_encapsulation);
container.num_messages += 1;
let mut reader = MockReader {
containers: vec![container],
};
let result = reader.read_response().unwrap();
assert_eq!(result[0], Ok(message1));
assert!(matches!(result[1], Err(DiagParsingError::HdlcDecapsulationError(_, _))));
}
}
@@ -7,6 +7,12 @@ use thiserror::Error;
pub struct GsmtapParser {
}
impl Default for GsmtapParser {
fn default() -> Self {
GsmtapParser::new()
}
}
#[derive(Debug, Error)]
pub enum GsmtapParserError {
#[error("Invalid LteRrcOtaMessage ext header version {0}")]
@@ -57,7 +63,7 @@ impl GsmtapParser {
15 => GsmtapType::LteRrc(LteRrcSubtype::UlDcch),
pdu => return Err(GsmtapParserError::InvalidLteRrcOtaHeaderPduNum(ext_header_version, pdu)),
},
0x0e | 0x0f | 0x10 => match packet.get_pdu_num() {
0x0e..=0x10 => match packet.get_pdu_num() {
1 => GsmtapType::LteRrc(LteRrcSubtype::BcchBch),
2 => GsmtapType::LteRrc(LteRrcSubtype::BcchDlSch),
4 => GsmtapType::LteRrc(LteRrcSubtype::MCCH),
+14 -12
View File
@@ -7,6 +7,8 @@ use crc::Crc;
use bytes::Buf;
use thiserror::Error;
use crate::diag::{MESSAGE_ESCAPE_CHAR, MESSAGE_TERMINATOR, ESCAPED_MESSAGE_ESCAPE_CHAR, ESCAPED_MESSAGE_TERMINATOR};
#[derive(Debug, Error, PartialEq)]
pub enum HdlcError {
#[error("Invalid checksum (expected {0}, got {1})")]
@@ -22,25 +24,25 @@ pub enum HdlcError {
}
pub fn hdlc_encapsulate(data: &[u8], crc: &Crc<u16>) -> Vec<u8> {
let mut result: Vec<u8> = vec![];
let mut result: Vec<u8> = Vec::with_capacity(data.len());
for &b in data {
match b {
0x7e => result.extend([0x7d, 0x5e]),
0x7d => result.extend([0x7d, 0x5d]),
MESSAGE_TERMINATOR => result.extend([MESSAGE_ESCAPE_CHAR, ESCAPED_MESSAGE_TERMINATOR]),
MESSAGE_ESCAPE_CHAR => result.extend([MESSAGE_ESCAPE_CHAR, ESCAPED_MESSAGE_ESCAPE_CHAR]),
_ => result.push(b),
}
}
for b in crc.checksum(&data).to_le_bytes() {
for b in crc.checksum(data).to_le_bytes() {
match b {
0x7e => result.extend([0x7d, 0x5e]),
0x7d => result.extend([0x7d, 0x5d]),
MESSAGE_TERMINATOR => result.extend([MESSAGE_ESCAPE_CHAR, ESCAPED_MESSAGE_TERMINATOR]),
MESSAGE_ESCAPE_CHAR => result.extend([MESSAGE_ESCAPE_CHAR, ESCAPED_MESSAGE_ESCAPE_CHAR]),
_ => result.push(b),
}
}
result.push(0x7e);
result.push(MESSAGE_TERMINATOR);
result
}
@@ -49,21 +51,21 @@ pub fn hdlc_decapsulate(data: &[u8], crc: &Crc<u16>) -> Result<Vec<u8>, HdlcErro
return Err(HdlcError::TooShort);
}
if data[data.len() - 1] != 0x7e {
if data[data.len() - 1] != MESSAGE_TERMINATOR {
return Err(HdlcError::NoTrailingCharacter(data[data.len() - 1]));
}
let mut unescaped = Vec::new();
let mut unescaped = Vec::with_capacity(data.len());
let mut escaping = false;
for &b in &data[..data.len() - 1] {
if escaping {
match b {
0x5e => unescaped.push(0x7e),
0x5d => unescaped.push(0x7d),
ESCAPED_MESSAGE_TERMINATOR => unescaped.push(MESSAGE_TERMINATOR),
ESCAPED_MESSAGE_ESCAPE_CHAR => unescaped.push(MESSAGE_ESCAPE_CHAR),
_ => return Err(HdlcError::InvalidEscapeSequence(b)),
}
escaping = false;
} else if b == 0x7d {
} else if b == MESSAGE_ESCAPE_CHAR {
escaping = true
} else {
unescaped.push(b);
+1 -1
View File
@@ -2,7 +2,7 @@ pub mod hdlc;
pub mod diag;
pub mod diag_device;
pub mod diag_reader;
pub mod debug_file;
pub mod qmdl;
pub mod log_codes;
pub mod gsmtap;
pub mod gsmtap_parser;
+21 -21
View File
@@ -1,9 +1,8 @@
use crate::gsmtap::GsmtapMessage;
use crate::diag::Timestamp;
use std::fs::File;
use std::io::Write;
use std::borrow::Cow;
use std::path::Path;
use chrono::prelude::*;
use deku::prelude::*;
use pcap_file::pcapng::blocks::enhanced_packet::EnhancedPacketBlock;
@@ -13,17 +12,19 @@ use pcap_file::PcapError;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum PcapFileError {
pub enum GsmtapPcapError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Pcap error: {0}")]
Pcap(#[from] PcapError),
#[error("Timestamp out of range: {0}")]
TimestampOutOfRange(#[from] chrono::OutOfRangeError),
#[error("Deku error: {0}")]
Deku(#[from] DekuError),
}
pub struct PcapFile {
writer: PcapNgWriter<File>,
pub struct GsmtapPcapWriter<T> where T: Write {
writer: PcapNgWriter<T>,
ip_id: u16,
}
@@ -55,17 +56,13 @@ struct UdpHeader {
checksum: u16,
}
impl PcapFile {
pub fn new<P>(path: P) -> Result<Self, PcapFileError> where P: AsRef<Path> {
let file = std::fs::File::options()
.create(true)
.write(true)
.open(path)?;
let writer = PcapNgWriter::new(file)?;
Ok(PcapFile { writer, ip_id: 0 })
impl<T> GsmtapPcapWriter<T> where T: Write {
pub fn new(writer: T) -> Result<Self, GsmtapPcapError> {
let writer = PcapNgWriter::new(writer)?;
Ok(GsmtapPcapWriter { writer, ip_id: 0 })
}
pub fn write_iface_header(&mut self) -> Result<(), PcapFileError> {
pub fn write_iface_header(&mut self) -> Result<(), GsmtapPcapError> {
let interface = InterfaceDescriptionBlock {
linktype: pcap_file::DataLink::IPV4,
snaplen: 0xffff,
@@ -75,13 +72,16 @@ impl PcapFile {
Ok(())
}
pub fn write_gsmtap_message(&mut self, msg: GsmtapMessage, timestamp: Timestamp) -> Result<(), PcapFileError> {
let time_since_epoch = timestamp.to_datetime().signed_duration_since(DateTime::UNIX_EPOCH);
let secs_since_epoch = time_since_epoch.num_seconds() as u64;
let nsecs_since_epoch = time_since_epoch.num_nanoseconds().unwrap_or(0) as u32;
// FIXME: although the duration value is correct here, when it shows up in
// the pcap it's WAY off, like in the year 55920
let duration = std::time::Duration::new(secs_since_epoch, nsecs_since_epoch);
pub fn write_gsmtap_message(&mut self, msg: GsmtapMessage, timestamp: Timestamp) -> Result<(), GsmtapPcapError> {
let duration = timestamp.to_datetime()
.signed_duration_since(DateTime::UNIX_EPOCH)
.to_std()?;
// despite the timestamp above being correct, we have reduce it by
// orders of magnitude due to a bug in pcap_file:
// https://github.com/courvoif/pcap-file/pull/32
let duration = std::time::Duration::from_nanos(duration.as_micros() as u64);
let msg_bytes = msg.to_bytes()?;
let ip_header = IpHeader {
version_and_ihl: 0x45,
+212
View File
@@ -0,0 +1,212 @@
//! Qualcomm Mobile Diagnostic Log (QMDL) files have a very simple format: just
//! a series of of concatenated HDLC encapsulated diag::Message structs.
//! QmdlReader and QmdlWriter can read and write MessagesContainers to and from
//! QMDL files.
use crate::diag_reader::DiagReader;
use crate::diag::{MessagesContainer, MESSAGE_TERMINATOR, HdlcEncapsulatedMessage, DataType};
use std::io::{Write, BufReader, BufRead, Read};
use thiserror::Error;
use log::error;
pub struct QmdlWriter<T> where T: Write {
writer: T,
pub total_written: usize,
}
impl<T> QmdlWriter<T> where T: Write {
pub fn new(writer: T) -> Self {
QmdlWriter::new_with_existing_size(writer, 0)
}
pub fn new_with_existing_size(writer: T, existing_size: usize) -> Self {
QmdlWriter {
writer,
total_written: existing_size,
}
}
pub fn write_container(&mut self, container: &MessagesContainer) -> std::io::Result<()> {
for msg in &container.messages {
self.writer.write_all(&msg.data)?;
self.total_written += msg.data.len();
}
Ok(())
}
}
#[derive(Debug, Error)]
pub enum QmdlReaderError {
#[error("IO error: {0}")]
IoError(#[from] std::io::Error),
#[error("Reached max_bytes count {0}")]
MaxBytesReached(usize),
}
pub struct QmdlReader<T> where T: Read {
reader: BufReader<T>,
bytes_read: usize,
max_bytes: Option<usize>,
}
impl<T> QmdlReader<T> where T: Read {
pub fn new(reader: T, max_bytes: Option<usize>) -> Self {
QmdlReader {
reader: BufReader::new(reader),
bytes_read: 0,
max_bytes,
}
}
}
impl<T> DiagReader for QmdlReader<T> where T: Read {
type Err = QmdlReaderError;
fn get_next_messages_container(&mut self) -> Result<MessagesContainer, Self::Err> {
if let Some(max_bytes) = self.max_bytes {
if self.bytes_read >= max_bytes {
if self.bytes_read > max_bytes {
error!("warning: {} bytes read, but max_bytes was {}", self.bytes_read, max_bytes);
}
return Err(QmdlReaderError::MaxBytesReached(max_bytes));
}
}
let mut buf = Vec::new();
let bytes_read = self.reader.read_until(MESSAGE_TERMINATOR, &mut buf)?;
self.bytes_read += bytes_read;
// Since QMDL is just a flat list of messages, we can't actually
// reproduce the container structure they came from in the original
// read. So we'll just pretend that all containers had exactly one
// message. As far as I know, the number of messages per container
// doesn't actually affect anything, so this should be fine.
Ok(MessagesContainer {
data_type: DataType::UserSpace,
num_messages: 1,
messages: vec![
HdlcEncapsulatedMessage {
len: bytes_read as u32,
data: buf,
},
]
})
}
}
#[cfg(test)]
mod test {
use std::io::Cursor;
use crate::hdlc::hdlc_encapsulate;
use crate::diag_reader::CRC_CCITT;
use super::*;
fn get_test_messages() -> Vec<HdlcEncapsulatedMessage> {
let messages: Vec<HdlcEncapsulatedMessage> = (10..20).map(|i| {
let data = hdlc_encapsulate(&vec![i as u8; i], &CRC_CCITT);
HdlcEncapsulatedMessage {
len: data.len() as u32,
data,
}
}).collect();
messages
}
// returns a byte array consisting of concatenated HDLC encapsulated
// test messages
fn get_test_message_bytes() -> Vec<u8> {
get_test_messages().iter()
.flat_map(|msg| msg.data.clone())
.collect()
}
fn get_test_containers() -> Vec<MessagesContainer> {
let messages = get_test_messages();
let (messages1, messages2) = messages.split_at(5);
vec![
MessagesContainer {
data_type: DataType::UserSpace,
num_messages: messages1.len() as u32,
messages: messages1.to_vec(),
},
MessagesContainer {
data_type: DataType::UserSpace,
num_messages: messages2.len() as u32,
messages: messages2.to_vec()
},
]
}
#[test]
fn test_unbounded_qmdl_reader() {
let mut buf = Cursor::new(get_test_message_bytes());
let mut reader = QmdlReader::new(&mut buf, None);
let expected_messages = get_test_messages();
for message in expected_messages {
let expected_container = MessagesContainer {
data_type: DataType::UserSpace,
num_messages: 1,
messages: vec![message],
};
assert_eq!(expected_container, reader.get_next_messages_container().unwrap());
}
}
#[test]
fn test_bounded_qmdl_reader() {
let mut buf = Cursor::new(get_test_message_bytes());
// bound the reader to the first two messages
let mut expected_messages = get_test_messages();
let limit = expected_messages[0].len + expected_messages[1].len;
let mut reader = QmdlReader::new(&mut buf, Some(limit as usize));
for message in expected_messages.drain(0..2) {
let expected_container = MessagesContainer {
data_type: DataType::UserSpace,
num_messages: 1,
messages: vec![message],
};
assert_eq!(expected_container, reader.get_next_messages_container().unwrap());
}
assert!(matches!(reader.get_next_messages_container(), Err(QmdlReaderError::MaxBytesReached(_))));
}
#[test]
fn test_qmdl_writer() {
let mut buf = Vec::new();
let mut writer = QmdlWriter::new(&mut buf);
let expected_containers = get_test_containers();
for container in &expected_containers {
writer.write_container(container).unwrap();
}
assert_eq!(writer.total_written, buf.len());
assert_eq!(buf, get_test_message_bytes());
}
#[test]
fn test_writing_and_reading() {
let mut buf = Vec::new();
let mut writer = QmdlWriter::new(&mut buf);
let expected_containers = get_test_containers();
for container in &expected_containers {
writer.write_container(container).unwrap();
}
let limit = Some(buf.len());
let mut reader = QmdlReader::new(Cursor::new(&mut buf), limit);
let expected_messages = get_test_messages();
for message in expected_messages {
let expected_container = MessagesContainer {
data_type: DataType::UserSpace,
num_messages: 1,
messages: vec![message],
};
assert_eq!(expected_container, reader.get_next_messages_container().unwrap());
}
assert!(matches!(reader.get_next_messages_container(), Err(QmdlReaderError::MaxBytesReached(_))));
}
}
@@ -1,10 +1,10 @@
use wavehunter::diag::{
use orca::diag::{
Message,
LogBody,
LteRrcOtaPacket,
Timestamp,
};
use wavehunter::gsmtap_parser::GsmtapParser;
use orca::gsmtap_parser::GsmtapParser;
use deku::prelude::*;
// Tests here are based on https://github.com/fgsect/scat/blob/97442580e628de414c9f7c2a185f4e28d0ee7523/tests/test_diagltelogparser.py
@@ -12,7 +12,11 @@ use deku::prelude::*;
#[test]
fn test_lte_rrc_ota() {
let mut parser = GsmtapParser::new();
let v26_binary = &[0x10, 0x0, 0x23, 0x0, 0x23, 0x0, 0xc0, 0xb0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1a, 0xf, 0x40, 0xf, 0x40, 0x1, 0xe, 0x1, 0x13, 0x7, 0x0, 0x0, 0x0, 0x0, 0xb, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x10, 0x15];
let v26_binary = &[
0x10, 0x0, 0x23, 0x0, 0x23, 0x0, 0xc0, 0xb0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x1a, 0xf, 0x40, 0xf, 0x40, 0x1, 0xe, 0x1, 0x13, 0x7,
0x0, 0x0, 0x0, 0x0, 0xb, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x10, 0x15
];
let (_, parsed) = Message::from_bytes((v26_binary, 0)).unwrap();
assert_eq!(&parsed, &Message::Log {
pending_msgs: 0,
@@ -84,7 +88,6 @@ fn test_lte_rrc_ota() {
let (_, gsmtap_msg) = parser.recv_message(parsed).unwrap().unwrap();
assert_eq!(&gsmtap_msg.payload, &[
0x10, 0x15,
]);
assert_eq!(gsmtap_msg.header.packet_type, 13);
assert_eq!(gsmtap_msg.header.timeslot, 0);
@@ -102,7 +105,6 @@ fn test_lte_rrc_ota() {
0x00, 0x00, 0x09, 0xdc, 0x05, 0x00, 0x00, 0x00,
0x00, 0x0d, 0x00, 0x40, 0x85, 0x8e, 0xc4, 0xe5,
0xbf, 0xe0, 0x50, 0xdc, 0x29, 0x15, 0x16, 0x00,
];
let (_, parsed) = Message::from_bytes((v24_binary, 0)).unwrap();
assert_eq!(&parsed, &Message::Log {
@@ -123,7 +125,10 @@ fn test_lte_rrc_ota() {
pdu_num: 5,
sib_mask: 0,
len: 13,
packet: vec![0x40, 0x85, 0x8e, 0xc4, 0xe5, 0xbf, 0xe0, 0x50, 0xdc, 0x29, 0x15, 0x16, 0x0],
packet: vec![
0x40, 0x85, 0x8e, 0xc4, 0xe5, 0xbf, 0xe0, 0x50, 0xdc, 0x29,
0x15, 0x16, 0x0
],
},
},
});
@@ -131,7 +136,6 @@ fn test_lte_rrc_ota() {
assert_eq!(&gsmtap_msg.payload, &[
0x40, 0x85, 0x8e, 0xc4, 0xe5, 0xbf, 0xe0, 0x50,
0xdc, 0x29, 0x15, 0x16, 0x00,
]);
assert_eq!(gsmtap_msg.header.packet_type, 13);
assert_eq!(gsmtap_msg.header.timeslot, 0);
@@ -171,7 +175,11 @@ fn test_lte_rrc_ota() {
pdu_num: 9,
sib_mask: 0,
len: 24,
packet: vec![0x8, 0x10, 0xa7, 0x14, 0x53, 0x59, 0xa6, 0x5, 0x43, 0x68, 0xc0, 0x3b, 0xda, 0x30, 0x4, 0xa6, 0x88, 0x2, 0x8d, 0xa2, 0x0, 0x9a, 0x68, 0x40],
packet: vec![
0x8, 0x10, 0xa7, 0x14, 0x53, 0x59, 0xa6, 0x5, 0x43, 0x68,
0xc0, 0x3b, 0xda, 0x30, 0x4, 0xa6, 0x88, 0x2, 0x8d, 0xa2,
0x0, 0x9a, 0x68, 0x40
],
},
},
});
@@ -269,7 +277,6 @@ fn test_lte_rrc_ota() {
let (_, gsmtap_msg) = parser.recv_message(parsed).unwrap().unwrap();
assert_eq!(&gsmtap_msg.payload, &[
0x40, 0x0c, 0x8e, 0xc9, 0x42, 0x89, 0xe0,
]);
assert_eq!(gsmtap_msg.header.packet_type, 13);
assert_eq!(gsmtap_msg.header.timeslot, 0);
@@ -289,7 +296,6 @@ fn test_lte_rrc_ota() {
0x41, 0xa3, 0x1c, 0x31, 0x68, 0x04, 0x40, 0x1a,
0x00, 0x49, 0x16, 0x7c, 0x23, 0x15, 0x9f, 0x00,
0x10, 0x67, 0xc1, 0x06, 0xd9, 0xe0, 0x00,
];
let (_, parsed) = Message::from_bytes((v15_binary, 0)).unwrap();
assert_eq!(&parsed, &Message::Log {
@@ -310,7 +316,11 @@ fn test_lte_rrc_ota() {
pdu_num: 9,
sib_mask: 0,
len: 28,
packet: vec![0x8, 0x10, 0xa5, 0x34, 0x61, 0x41, 0xa3, 0x1c, 0x31, 0x68, 0x4, 0x40, 0x1a, 0x0, 0x49, 0x16, 0x7c, 0x23, 0x15, 0x9f, 0x0, 0x10, 0x67, 0xc1, 0x6, 0xd9, 0xe0, 0x0],
packet: vec![
0x8, 0x10, 0xa5, 0x34, 0x61, 0x41, 0xa3, 0x1c, 0x31, 0x68,
0x4, 0x40, 0x1a, 0x0, 0x49, 0x16, 0x7c, 0x23, 0x15, 0x9f,
0x0, 0x10, 0x67, 0xc1, 0x6, 0xd9, 0xe0, 0x0
],
},
},
});
@@ -484,7 +494,10 @@ fn test_lte_rrc_ota() {
pdu_num: 2,
sib_mask: 2,
len: 18,
packet: vec![0x40, 0x49, 0x88, 0x5, 0xc0, 0x97, 0x2, 0xd3, 0xb0, 0x98, 0x1c, 0x20, 0xa0, 0x81, 0x8c, 0x43, 0x26, 0xd0],
packet: vec![
0x40, 0x49, 0x88, 0x5, 0xc0, 0x97, 0x2, 0xd3, 0xb0, 0x98,
0x1c, 0x20, 0xa0, 0x81, 0x8c, 0x43, 0x26, 0xd0
],
},
},
});
-31
View File
@@ -1,31 +0,0 @@
use wavehunter::diag_device::{DiagDevice, DiagResult};
use wavehunter::diag_reader::DiagReader;
use wavehunter::gsmtap_parser::GsmtapParser;
use wavehunter::pcap::PcapFile;
use log::debug;
fn main() -> DiagResult<()> {
env_logger::init();
let mut dev = DiagDevice::new()?;
dev.enable_debug_mode("/data/wavehunter/wavehunter-debug")?;
dev.config_logs()?;
println!("The orca is hunting for stingrays...");
let mut gsmtap_parser = GsmtapParser::new();
// We are going to want to add a timestamp to this pcap file eventually
let mut pcap_file = PcapFile::new("/data/wavehunter/wavehunter.pcap").unwrap();
pcap_file.write_iface_header().unwrap();
loop {
for msg in dev.read_response()? {
debug!("msg: {:?}", msg);
if let Some((timestamp, gsmtap_msg)) = gsmtap_parser.recv_message(msg).unwrap() {
debug!("gsmtap_msg: {:?}", gsmtap_msg);
pcap_file.write_gsmtap_message(gsmtap_msg, timestamp).unwrap();
}
}
}
}
-41
View File
@@ -1,41 +0,0 @@
use wavehunter::debug_file::DebugFileReader;
use wavehunter::diag_reader::DiagReader;
use wavehunter::diag_device::{DiagResult, DiagDeviceError};
use wavehunter::gsmtap_parser::GsmtapParser;
use wavehunter::pcap::PcapFile;
use log::{debug, error};
fn main() -> DiagResult<()> {
env_logger::init();
let args: Vec<String> = std::env::args().collect();
if args.len() != 2 {
error!("Usage: {} /path/to/debug/file", args[0]);
std::process::exit(1);
}
let mut debug_reader = DebugFileReader::new(&args[1])?;
let mut gsmtap_parser = GsmtapParser::new();
let mut pcap_file = PcapFile::new("./wavehunter.pcap").unwrap();
pcap_file.write_iface_header().unwrap();
loop {
match debug_reader.read_response() {
Ok(msgs) => {
for msg in msgs {
debug!("msg: {:?}", msg);
if let Some((timestamp, gsmtap_msg)) = gsmtap_parser.recv_message(msg).unwrap() {
debug!("gsmtap_msg: {:?}", gsmtap_msg);
pcap_file.write_gsmtap_message(gsmtap_msg, timestamp).unwrap();
}
}
},
Err(DiagDeviceError::IO(err)) if err.kind() == std::io::ErrorKind::UnexpectedEof => {
println!("Reached end of debug file, exiting...");
std::process::exit(0);
},
Err(err) => panic!("Error reading debug file {}", err),
}
}
}
-44
View File
@@ -1,44 +0,0 @@
use crate::diag_reader::DiagReader;
use crate::diag_device::DiagResult;
use crate::diag::*;
use deku::prelude::*;
use std::fs::File;
use std::io::Read;
use log::warn;
#[derive(Debug, DekuRead, DekuWrite)]
#[deku(endian = "little")]
pub struct DebugFileBlock<'a> {
pub size: u32,
#[deku(count = "size")]
pub data: &'a [u8],
}
pub struct DebugFileReader {
file: File,
}
impl DebugFileReader {
pub fn new<P>(path: P) -> DiagResult<Self> where P: AsRef<std::path::Path> {
let file = std::fs::File::options()
.read(true)
.open(path)?;
Ok(DebugFileReader { file })
}
}
impl DiagReader for DebugFileReader {
fn get_next_messages_container(&mut self) -> DiagResult<MessagesContainer> {
let mut bytes_read_buf = [0; 4];
self.file.read_exact(&mut bytes_read_buf)?;
let bytes_read = u32::from_le_bytes(bytes_read_buf) as usize;
let mut data = vec![0; bytes_read as usize];
self.file.read_exact(&mut data)?;
let ((leftover_bytes, _), container) = MessagesContainer::from_bytes((&data, 0))?;
if leftover_bytes.len() > 0 {
warn!("warning: {} leftover bytes when parsing MessagesContainer", leftover_bytes.len());
}
Ok(container)
}
}
-62
View File
@@ -1,62 +0,0 @@
use crate::{diag::*, hdlc::hdlc_decapsulate};
use crate::diag_device::DiagResult;
use crc::{Crc, Algorithm};
use deku::prelude::*;
use log::{debug, info, warn, error};
// this is sorta based on the params qcsuper uses, plus what seems to be used in
// https://github.com/fgsect/scat/blob/f1538b397721df3ab8ba12acd26716abcf21f78b/util.py#L47
pub const CRC_CCITT_ALG: Algorithm<u16> = Algorithm {
poly: 0x1021,
init: 0xffff,
refin: true,
refout: true,
width: 16,
xorout: 0xffff,
check: 0x2189,
residue: 0x0000,
};
pub const CRC_CCITT: Crc<u16> = Crc::<u16>::new(&CRC_CCITT_ALG);
pub trait DiagReader {
fn get_next_messages_container(&mut self) -> DiagResult<MessagesContainer>;
fn read_response(&mut self) -> DiagResult<Vec<Message>> {
loop {
let container = self.get_next_messages_container()?;
if container.data_type == DataType::UserSpace {
return self.parse_response_container(container);
} else {
info!("skipping non-userspace message...")
}
}
}
fn parse_response_container(&self, container: MessagesContainer) -> DiagResult<Vec<Message>> {
let mut result = Vec::new();
for msg in container.messages {
for sub_msg in msg.data.split_inclusive(|&b| b == 0x7e) {
match hdlc_decapsulate(&sub_msg, &CRC_CCITT) {
Ok(data) => match Message::from_bytes((&data, 0)) {
Ok(((leftover_bytes, _), res)) => {
if leftover_bytes.len() > 0 {
warn!("warning: {} leftover bytes when parsing Message", leftover_bytes.len());
}
result.push(res);
},
Err(e) => {
error!("error parsing response: {:?}", e);
debug!("{:?}", data);
},
},
Err(err) => {
error!("error decapsulating response: {:?}", err);
debug!("{:?}", &sub_msg);
}
}
}
}
Ok(result)
}
}
+18
View File
@@ -0,0 +1,18 @@
[package]
name = "wavehunter"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
orca = { path = "../orca" }
toml = "0.8.8"
serde = { version = "1.0.193", features = ["derive"] }
tokio = { version = "1.35.1", features = ["full"] }
axum = "0.7.2"
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"] }
+54
View File
@@ -0,0 +1,54 @@
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(),
}
}
+16
View File
@@ -0,0 +1,16 @@
use thiserror::Error;
use orca::diag_device::DiagDeviceError;
#[derive(Error, Debug)]
pub enum WavehunterError {
#[error("Missing config file: {0}")]
MissingConfigFile(String),
#[error("Config file parsing error: {0}")]
ConfigFileParsingError(#[from] toml::de::Error),
#[error("Diag intialization error: {0}")]
DiagInitError(DiagDeviceError),
#[error("Diag read error: {0}")]
DiagReadError(DiagDeviceError),
#[error("Tokio error: {0}")]
TokioError(#[from] tokio::io::Error),
}
+83
View File
@@ -0,0 +1,83 @@
mod config;
mod error;
mod server;
use crate::config::{parse_config, parse_args};
use crate::server::{ServerState, serve_pcap, serve_qmdl};
use crate::error::WavehunterError;
use log::debug;
use orca::diag_device::DiagDevice;
use orca::diag_reader::DiagReader;
use axum::routing::get;
use axum::Router;
use tokio::fs::File;
use std::net::SocketAddr;
use tokio::net::TcpListener;
use tokio::sync::RwLock;
use std::sync::Arc;
fn run_diag_read_thread(mut dev: DiagDevice, bytes_read_lock: Arc<RwLock<usize>>) -> tokio::task::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("/output.pcap", get(serve_pcap))
.route("/output.qmdl", get(serve_qmdl))
.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
}
+129
View File
@@ -0,0 +1,129 @@
use orca::gsmtap_parser::GsmtapParser;
use orca::pcap::GsmtapPcapWriter;
use orca::qmdl::{QmdlReader, QmdlReaderError};
use orca::diag_reader::DiagReader;
use axum::body::Body;
use axum::http::header::CONTENT_TYPE;
use axum::extract::State;
use axum::http::StatusCode;
use axum::response::{Response, IntoResponse};
use tokio::io::AsyncReadExt;
use std::fs::File;
use std::io::Write;
use std::pin::Pin;
use std::sync::Arc;
use tokio::sync::RwLock;
use tokio::fs::File as AsyncFile;
use tokio_util::io::ReaderStream;
use std::task::{Poll, Context};
use futures_core::Stream;
use log::error;
use tokio::sync::mpsc;
// Streams a pcap file chunk-by-chunk to the client by reading the QMDL data
// 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 serve_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 {
return Err((
StatusCode::SERVICE_UNAVAILABLE,
"QMDL file is empty, try again in a bit!".to_string()
));
}
let (tx, rx) = mpsc::channel(1);
let channel_reader = ChannelReader { rx };
let channel_writer = ChannelWriter { tx };
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 gsmtap_parser = GsmtapParser::new();
let mut pcap_writer = GsmtapPcapWriter::new(channel_writer).unwrap();
pcap_writer.write_iface_header().unwrap();
loop {
match qmdl_reader.read_response() {
Ok(messages) => {
for maybe_msg in messages {
match maybe_msg {
Ok(msg) => {
let maybe_gsmtap_msg = gsmtap_parser.recv_message(msg)
.expect("error parsing gsmtap message");
if let Some((timestamp, gsmtap_msg)) = maybe_gsmtap_msg {
pcap_writer.write_gsmtap_message(gsmtap_msg, timestamp)
.expect("error writing pcap packet");
}
},
Err(e) => error!("error parsing message: {:?}", e),
}
}
},
// this is expected, and just means we've reached the end of the
// safely written QMDL data
Err(QmdlReaderError::MaxBytesReached(_)) => break,
Err(e) => {
error!("error reading qmdl file: {:?}", e);
break;
},
}
}
});
let headers = [(CONTENT_TYPE, "application/vnd.tcpdump.pcap")];
let body = Body::from_stream(channel_reader);
Ok((headers, body).into_response())
}
struct ChannelWriter {
tx: mpsc::Sender<Vec<u8>>,
}
impl Write for ChannelWriter {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.tx.blocking_send(buf.to_vec())
.map_err(|_| std::io::Error::new(std::io::ErrorKind::Other, "channel closed"))?;
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
struct ChannelReader {
rx: mpsc::Receiver<Vec<u8>>,
}
impl Stream for ChannelReader {
type Item = Result<Vec<u8>, String>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
match self.rx.poll_recv(cx) {
Poll::Ready(Some(msg)) => Poll::Ready(Some(Ok(msg))),
Poll::Ready(None) => Poll::Ready(None),
Poll::Pending => Poll::Pending,
}
}
}
pub async fn serve_qmdl(State(state): State<Arc<ServerState>>) -> Result<Response, (StatusCode, String)> {
let qmdl_file = AsyncFile::open(&state.qmdl_path).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 qmdl_stream = ReaderStream::new(limited_qmdl_file);
let headers = [(CONTENT_TYPE, "application/octet-stream")];
let body = Body::from_stream(qmdl_stream);
Ok((headers, body).into_response())
}
pub struct ServerState {
pub qmdl_bytes_written: Arc<RwLock<usize>>,
pub qmdl_path: String,
}