diff --git a/Cargo.lock b/Cargo.lock index b720e2e..d29868d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", ] diff --git a/Cargo.toml b/Cargo.toml index 10c1465..a019f56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/orca/Cargo.toml b/orca/Cargo.toml new file mode 100644 index 0000000..085ee12 --- /dev/null +++ b/orca/Cargo.toml @@ -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" diff --git a/src/diag.rs b/orca/src/diag.rs similarity index 95% rename from src/diag.rs rename to orca/src/diag.rs index 3f5b2c5..786476d 100644 --- a/src/diag.rs +++ b/orca/src/diag.rs @@ -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, } -#[derive(Debug, Clone, DekuRead)] +#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)] pub struct HdlcEncapsulatedMessage { pub len: u32, #[deku(count = "len")] pub data: Vec, } -#[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")] diff --git a/src/diag_device.rs b/orca/src/diag_device.rs similarity index 64% rename from src/diag_device.rs rename to orca/src/diag_device.rs index 8cd9da3..1b1d471 100644 --- a/src/diag_device.rs +++ b/orca/src/diag_device.rs @@ -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 = Result; #[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, + pub qmdl_writer: QmdlWriter, + fully_initialized: bool, read_buf: Vec, use_mdm: i32, } impl DiagReader for DiagDevice { + type Err = DiagDeviceError; + fn get_next_messages_container(&mut self) -> DiagResult { 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 { - let file = std::fs::File::options() + pub fn new

(qmdl_path: P) -> DiagResult where P: AsRef { + 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

(&mut self, path: P) -> DiagResult<()> where P: AsRef { - 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 { 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)) } diff --git a/orca/src/diag_reader.rs b/orca/src/diag_reader.rs new file mode 100644 index 0000000..b8ef308 --- /dev/null +++ b/orca/src/diag_reader.rs @@ -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 = Algorithm { + poly: 0x1021, + init: 0xffff, + refin: true, + refout: true, + width: 16, + xorout: 0xffff, + check: 0x2189, + residue: 0x0000, +}; +pub const CRC_CCITT: Crc = Crc::::new(&CRC_CCITT_ALG); + +#[derive(Debug, PartialEq, Error)] +pub enum DiagParsingError { + #[error("Failed to parse Message: {0}, data: {1:?}")] + MessageParsingError(deku::DekuError, Vec), + #[error("HDLC decapsulation of message failed: {0}, data: {1:?}")] + HdlcDecapsulationError(hdlc::HdlcError, Vec), +} + +type MaybeMessage = Result; + +pub trait DiagReader { + type Err; + + fn get_next_messages_container(&mut self) -> Result; + + fn read_response(&mut self) -> Result, 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, 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, + } + + impl DiagReader for MockReader { + type Err = (); + + fn get_next_messages_container(&mut self) -> Result { + 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(_, _)))); + } +} diff --git a/src/gsmtap.rs b/orca/src/gsmtap.rs similarity index 100% rename from src/gsmtap.rs rename to orca/src/gsmtap.rs diff --git a/src/gsmtap_parser.rs b/orca/src/gsmtap_parser.rs similarity index 97% rename from src/gsmtap_parser.rs rename to orca/src/gsmtap_parser.rs index 540e300..8ca9cfb 100644 --- a/src/gsmtap_parser.rs +++ b/orca/src/gsmtap_parser.rs @@ -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), diff --git a/src/hdlc.rs b/orca/src/hdlc.rs similarity index 71% rename from src/hdlc.rs rename to orca/src/hdlc.rs index 7813e5b..3c99e1b 100644 --- a/src/hdlc.rs +++ b/orca/src/hdlc.rs @@ -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) -> Vec { - let mut result: Vec = vec![]; + let mut result: Vec = 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) -> Result, 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); diff --git a/src/lib.rs b/orca/src/lib.rs similarity index 87% rename from src/lib.rs rename to orca/src/lib.rs index 3a9f757..61de5e9 100644 --- a/src/lib.rs +++ b/orca/src/lib.rs @@ -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; diff --git a/src/log_codes.rs b/orca/src/log_codes.rs similarity index 100% rename from src/log_codes.rs rename to orca/src/log_codes.rs diff --git a/src/pcap.rs b/orca/src/pcap.rs similarity index 71% rename from src/pcap.rs rename to orca/src/pcap.rs index 66cf7ba..9ae59ca 100644 --- a/src/pcap.rs +++ b/orca/src/pcap.rs @@ -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, +pub struct GsmtapPcapWriter where T: Write { + writer: PcapNgWriter, ip_id: u16, } @@ -55,17 +56,13 @@ struct UdpHeader { checksum: u16, } -impl PcapFile { - pub fn new

(path: P) -> Result where P: AsRef { - let file = std::fs::File::options() - .create(true) - .write(true) - .open(path)?; - let writer = PcapNgWriter::new(file)?; - Ok(PcapFile { writer, ip_id: 0 }) +impl GsmtapPcapWriter where T: Write { + pub fn new(writer: T) -> Result { + 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, diff --git a/orca/src/qmdl.rs b/orca/src/qmdl.rs new file mode 100644 index 0000000..c9ff5d6 --- /dev/null +++ b/orca/src/qmdl.rs @@ -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 where T: Write { + writer: T, + pub total_written: usize, +} + +impl QmdlWriter 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 where T: Read { + reader: BufReader, + bytes_read: usize, + max_bytes: Option, +} + +impl QmdlReader where T: Read { + pub fn new(reader: T, max_bytes: Option) -> Self { + QmdlReader { + reader: BufReader::new(reader), + bytes_read: 0, + max_bytes, + } + } +} + +impl DiagReader for QmdlReader where T: Read { + type Err = QmdlReaderError; + + fn get_next_messages_container(&mut self) -> Result { + 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 { + let messages: Vec = (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 { + get_test_messages().iter() + .flat_map(|msg| msg.data.clone()) + .collect() + } + + fn get_test_containers() -> Vec { + 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(_)))); + } +} diff --git a/tests/test_lte_parsing.rs b/orca/tests/test_lte_parsing.rs similarity index 93% rename from tests/test_lte_parsing.rs rename to orca/tests/test_lte_parsing.rs index e79d951..48a219c 100644 --- a/tests/test_lte_parsing.rs +++ b/orca/tests/test_lte_parsing.rs @@ -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 + ], }, }, }); diff --git a/src/bin/wavehunter.rs b/src/bin/wavehunter.rs deleted file mode 100644 index ec4628c..0000000 --- a/src/bin/wavehunter.rs +++ /dev/null @@ -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(); - } - } - } -} diff --git a/src/bin/wavehunter_reader.rs b/src/bin/wavehunter_reader.rs deleted file mode 100644 index db9792c..0000000 --- a/src/bin/wavehunter_reader.rs +++ /dev/null @@ -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 = 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), - } - } -} diff --git a/src/debug_file.rs b/src/debug_file.rs deleted file mode 100644 index 55ad776..0000000 --- a/src/debug_file.rs +++ /dev/null @@ -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

(path: P) -> DiagResult where P: AsRef { - 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 { - 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) - } -} diff --git a/src/diag_reader.rs b/src/diag_reader.rs deleted file mode 100644 index b8d2d85..0000000 --- a/src/diag_reader.rs +++ /dev/null @@ -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 = Algorithm { - poly: 0x1021, - init: 0xffff, - refin: true, - refout: true, - width: 16, - xorout: 0xffff, - check: 0x2189, - residue: 0x0000, -}; -pub const CRC_CCITT: Crc = Crc::::new(&CRC_CCITT_ALG); - -pub trait DiagReader { - fn get_next_messages_container(&mut self) -> DiagResult; - - fn read_response(&mut self) -> DiagResult> { - 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> { - 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) - } -} diff --git a/wavehunter/Cargo.toml b/wavehunter/Cargo.toml new file mode 100644 index 0000000..067e353 --- /dev/null +++ b/wavehunter/Cargo.toml @@ -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"] } diff --git a/wavehunter/src/config.rs b/wavehunter/src/config.rs new file mode 100644 index 0000000..d8280f8 --- /dev/null +++ b/wavehunter/src/config.rs @@ -0,0 +1,54 @@ +use crate::error::WavehunterError; + +use serde::Deserialize; + +#[derive(Deserialize)] +struct ConfigFile { + qmdl_path: Option, + port: Option, + debug_mode: Option, +} + +#[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

(path: P) -> Result where P: AsRef { + 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 = 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(), + } +} diff --git a/wavehunter/src/error.rs b/wavehunter/src/error.rs new file mode 100644 index 0000000..72200a9 --- /dev/null +++ b/wavehunter/src/error.rs @@ -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), +} diff --git a/wavehunter/src/main.rs b/wavehunter/src/main.rs new file mode 100644 index 0000000..0b74fa8 --- /dev/null +++ b/wavehunter/src/main.rs @@ -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>) -> tokio::task::JoinHandle> { + 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>) -> 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>; + 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 +} diff --git a/wavehunter/src/server.rs b/wavehunter/src/server.rs new file mode 100644 index 0000000..f16e4ef --- /dev/null +++ b/wavehunter/src/server.rs @@ -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>) -> Result { + 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>, +} + +impl Write for ChannelWriter { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + 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>, +} + +impl Stream for ChannelReader { + type Item = Result, String>; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + 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>) -> Result { + 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>, + pub qmdl_path: String, +}