From 741b79c05aad393d6b09a459382f0d5b555dac85 Mon Sep 17 00:00:00 2001 From: manu Date: Mon, 5 Aug 2024 21:49:21 +0200 Subject: [PATCH 01/10] Added CLI options with argh and implemented cli capturing --- Cargo.lock | 420 ++++++++++++++++++++++++++++------------ Cargo.toml | 3 + src/backend/cynthion.rs | 35 +++- src/cli_capture.rs | 159 +++++++++++++++ src/devices.rs | 82 ++++++++ src/main.rs | 106 +++++++--- 6 files changed, 653 insertions(+), 152 deletions(-) create mode 100644 src/cli_capture.rs create mode 100644 src/devices.rs diff --git a/Cargo.lock b/Cargo.lock index e149ce0e..4844736d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32,6 +32,37 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +[[package]] +name = "argh" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7af5ba06967ff7214ce4c7419c7d185be7ecd6cc4965a8f6e1d8ce0398aad219" +dependencies = [ + "argh_derive", + "argh_shared", +] + +[[package]] +name = "argh_derive" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56df0aeedf6b7a2fc67d06db35b09684c3e8da0c95f8f27685cb17e08413d87a" +dependencies = [ + "argh_shared", + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "argh_shared" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5693f39141bda5760ecc4111ab08da40565d1771038c4a0250f03457ec707531" +dependencies = [ + "serde", +] + [[package]] name = "arrayvec" version = "0.7.4" @@ -89,9 +120,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "built" @@ -103,11 +134,17 @@ dependencies = [ "git2", ] +[[package]] +name = "bytecount" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" + [[package]] name = "bytemuck" -version = "1.16.0" +version = "1.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78834c15cb5d5efe3452d58b1e8ba890dd62d21907f867f383358198e56ebca5" +checksum = "102087e286b4677862ea56cf8fc58bb2cdfa8725c40ffb80fe3a008eb7f2fc83" [[package]] name = "bytemuck_derive" @@ -117,7 +154,7 @@ checksum = "1ee891b04274a59bd38b412188e24b849617b2e45a0fd8d057deb63e7403761b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.72", ] [[package]] @@ -141,7 +178,7 @@ version = "0.19.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ac2a4d0e69036cf0062976f6efcba1aaee3e448594e6514bb2ddf87acce562" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "cairo-sys-rs", "glib", "libc", @@ -173,13 +210,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.99" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" +checksum = "26a5c3fd7bfa1ce3897a3a3501d362b2d87b7f2583ebcb4a949ec25911025cbc" dependencies = [ "jobserver", "libc", - "once_cell", ] [[package]] @@ -204,6 +240,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + [[package]] name = "convert_case" version = "0.4.0" @@ -263,7 +305,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f" dependencies = [ "quote", - "syn 2.0.66", + "syn 2.0.72", +] + +[[package]] +name = "ctrlc" +version = "3.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "672465ae37dc1bc6380a6547a8883d5dd397b0f1faaad4f265726cc7042a5345" +dependencies = [ + "nix", + "windows-sys 0.52.0", ] [[package]] @@ -287,14 +339,14 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.66", + "syn 2.0.72", ] [[package]] name = "either" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "equivalent" @@ -424,7 +476,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.72", ] [[package]] @@ -449,9 +501,9 @@ dependencies = [ [[package]] name = "gdk-pixbuf" -version = "0.19.2" +version = "0.19.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6a23f8a0b5090494fd04924662d463f8386cc678dd3915015a838c1a3679b92" +checksum = "624eaba126021103c7339b2e179ae4ee8cdab842daab419040710f38ed9f8699" dependencies = [ "gdk-pixbuf-sys", "gio", @@ -461,9 +513,9 @@ dependencies = [ [[package]] name = "gdk-pixbuf-sys" -version = "0.19.5" +version = "0.19.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fdbf021f8b9d19e30fb9ea6d6e5f2b6a712fe4645417c69f86f6ff1e1444a8f" +checksum = "4efa05a4f83c8cc50eb4d883787b919b85e5f1d8dd10b5a1df53bf5689782379" dependencies = [ "gio-sys", "glib-sys", @@ -534,9 +586,9 @@ checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" [[package]] name = "gio" -version = "0.19.5" +version = "0.19.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be548be810e45dd31d3bbb89c6210980bb7af9bca3ea1292b5f16b75f8e394a7" +checksum = "4c49f117d373ffcc98a35d114db5478bc223341cff53e39a5d6feced9e2ddffe" dependencies = [ "futures-channel", "futures-core", @@ -552,9 +604,9 @@ dependencies = [ [[package]] name = "gio-sys" -version = "0.19.5" +version = "0.19.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4bdbef451b0f0361e7f762987cc6bebd5facab1d535e85a3cf1115dfb08db40" +checksum = "2cd743ba4714d671ad6b6234e8ab2a13b42304d0e13ab7eba1dcdd78a7d6d4ef" dependencies = [ "glib-sys", "gobject-sys", @@ -569,7 +621,7 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "libc", "libgit2-sys", "log", @@ -578,11 +630,11 @@ dependencies = [ [[package]] name = "glib" -version = "0.19.7" +version = "0.19.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e52355166df21c7ed16b6a01f615669c7911ed74e27ef60eba339c0d2da12490" +checksum = "39650279f135469465018daae0ba53357942a5212137515777d5fdca74984a44" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "futures-channel", "futures-core", "futures-executor", @@ -600,22 +652,22 @@ dependencies = [ [[package]] name = "glib-macros" -version = "0.19.7" +version = "0.19.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70025dbfa1275cf7d0531c3317ba6270dae15d87e63342229d638246ff45202e" +checksum = "4429b0277a14ae9751350ad9b658b1be0abb5b54faa5bcdf6e74a3372582fad7" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.72", ] [[package]] name = "glib-sys" -version = "0.19.5" +version = "0.19.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "767d23ead9bbdfcbb1c2242c155c8128a7d13dde7bf69c176f809546135e2282" +checksum = "5c2dc18d3a82b0006d470b13304fbbb3e0a9bd4884cf985a60a7ed733ac2c4a5" dependencies = [ "libc", "system-deps", @@ -623,9 +675,9 @@ dependencies = [ [[package]] name = "gobject-sys" -version = "0.19.5" +version = "0.19.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3787b0bfacca12bb25f8f822b0dbee9f7e4a86e6469a29976d332d2c14c945b" +checksum = "2e697e252d6e0416fd1d9e169bda51c0f1c926026c39ca21fbe8b1bb5c3b8b9e" dependencies = [ "glib-sys", "libc", @@ -634,9 +686,9 @@ dependencies = [ [[package]] name = "graphene-rs" -version = "0.19.2" +version = "0.19.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e4d388e96c5f29e2b2f67045d229ddf826d0a8d6d282f94ed3b34452222c91" +checksum = "f5fb86031d24d9ec0a2a15978fc7a65d545a2549642cf1eb7c3dda358da42bcf" dependencies = [ "glib", "graphene-sys", @@ -645,9 +697,9 @@ dependencies = [ [[package]] name = "graphene-sys" -version = "0.19.5" +version = "0.19.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60e7381afdd7be43bd10a89d3b6741d162aabbca3a8db73505afb6a3aea59d" +checksum = "2f530e0944bccba4b55065e9c69f4975ad691609191ebac16e13ab8e1f27af05" dependencies = [ "glib-sys", "libc", @@ -716,7 +768,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.72", ] [[package]] @@ -744,6 +796,12 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "heck" version = "0.5.0" @@ -771,9 +829,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.6" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" dependencies = [ "equivalent", "hashbrown", @@ -834,9 +892,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" dependencies = [ "libc", ] @@ -853,9 +911,9 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" @@ -901,9 +959,9 @@ checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "lrumap" @@ -952,9 +1010,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", ] @@ -1001,6 +1059,18 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "nix" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" +dependencies = [ + "bitflags 2.6.0", + "cfg-if 1.0.0", + "cfg_aliases", + "libc", +] + [[package]] name = "nom" version = "7.1.3" @@ -1023,23 +1093,23 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.72", ] [[package]] @@ -1061,9 +1131,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.0" +version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434" +checksum = "3f203fa8daa7bb185f760ae12bd8e097f63d17041dcdcaf675ac54cdf863170e" dependencies = [ "memchr", ] @@ -1080,12 +1150,14 @@ version = "0.1.0" dependencies = [ "anyhow", "arc-swap", + "argh", "bitfield", "built", "bytemuck", "bytemuck_derive", "crc", "ctor", + "ctrlc", "derive_more", "futures-channel", "futures-lite", @@ -1106,6 +1178,7 @@ dependencies = [ "rand_xorshift", "serde", "serde_json", + "tabled", "tempfile", "usb-ids", "winapi 0.3.9", @@ -1123,9 +1196,9 @@ dependencies = [ [[package]] name = "pango" -version = "0.19.5" +version = "0.19.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504ce6e805439ea2c6791168fe7ef8e3da0c1b2ef82c44bc450dbc330592920d" +checksum = "3f0d328648058085cfd6897c9ae4272884098a926f3a833cd50c8c73e6eccecd" dependencies = [ "gio", "glib", @@ -1135,9 +1208,9 @@ dependencies = [ [[package]] name = "pango-sys" -version = "0.19.5" +version = "0.19.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4829555bdbb83692ddeaf5a6927fb2d025c8131e5ecaa4f7619fff6985d3505" +checksum = "ff03da4fa086c0b244d4a4587d3e20622a3ecdb21daea9edf66597224c634ba0" dependencies = [ "glib-sys", "gobject-sys", @@ -1145,6 +1218,17 @@ dependencies = [ "system-deps", ] +[[package]] +name = "papergrid" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ad43c07024ef767f9160710b3a6773976194758c7919b17e63b863db0bdf7fb" +dependencies = [ + "bytecount", + "fnv", + "unicode-width", +] + [[package]] name = "parking" version = "2.2.0" @@ -1226,9 +1310,12 @@ checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "proc-macro-crate" @@ -1239,11 +1326,35 @@ dependencies = [ "toml_edit 0.21.1", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" -version = "1.0.85" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] @@ -1372,7 +1483,7 @@ version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", @@ -1396,40 +1507,41 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.203" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.72", ] [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] [[package]] name = "serde_spanned" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" dependencies = [ "serde", ] @@ -1468,9 +1580,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.66" +version = "2.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" dependencies = [ "proc-macro2", "quote", @@ -1484,48 +1596,73 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" dependencies = [ "cfg-expr", - "heck", + "heck 0.5.0", "pkg-config", - "toml 0.8.14", + "toml 0.8.19", "version-compare", ] +[[package]] +name = "tabled" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c998b0c8b921495196a48aabaf1901ff28be0760136e31604f7967b0792050e" +dependencies = [ + "papergrid", + "tabled_derive", + "unicode-width", +] + +[[package]] +name = "tabled_derive" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c138f99377e5d653a371cdad263615634cfc8467685dfe8e73e2b8e98f44b17" +dependencies = [ + "heck 0.4.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "target-lexicon" -version = "0.12.14" +version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" -version = "3.10.1" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "b8fcd239983515c23a32fb82099f97d0b11b8c72f654ed659363a95c3dad7a53" dependencies = [ "cfg-if 1.0.0", "fastrand", + "once_cell", "rustix", "windows-sys 0.52.0", ] [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.72", ] [[package]] @@ -1557,21 +1694,21 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.14" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.14", + "toml_edit 0.22.20", ] [[package]] name = "toml_datetime" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] @@ -1602,15 +1739,15 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.14" +version = "0.22.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" +checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.13", + "winnow 0.6.18", ] [[package]] @@ -1634,6 +1771,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-width" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" + [[package]] name = "url" version = "2.5.2" @@ -1660,9 +1803,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" dependencies = [ "getrandom 0.2.15", ] @@ -1679,6 +1822,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -1740,7 +1889,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -1760,18 +1909,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -1782,9 +1931,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -1794,9 +1943,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -1806,15 +1955,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -1824,9 +1973,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -1836,9 +1985,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -1848,9 +1997,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -1860,9 +2009,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" @@ -1875,9 +2024,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.13" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" dependencies = [ "memchr", ] @@ -1891,3 +2040,24 @@ dependencies = [ "winapi 0.2.8", "winapi-build", ] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] diff --git a/Cargo.toml b/Cargo.toml index 069e66e0..4915e29e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,9 @@ page_size = "0.6.0" anyhow = { version = "1.0.79", features = ["backtrace"] } crc = "3.2.1" usb-ids = "1.2024.4" +argh = "0.1.12" +tabled = "0.15.0" +ctrlc = "3.4.4" [dev-dependencies] serde = { version = "1.0.196", features = ["derive"] } diff --git a/src/backend/cynthion.rs b/src/backend/cynthion.rs index 76a6264e..a6e8b9a6 100644 --- a/src/backend/cynthion.rs +++ b/src/backend/cynthion.rs @@ -36,7 +36,7 @@ const ENDPOINT: u8 = 0x81; const READ_LEN: usize = 0x4000; const NUM_TRANSFERS: usize = 4; -#[derive(Copy, Clone, FromPrimitive, IntoPrimitive)] +#[derive(Copy, Clone, FromPrimitive, IntoPrimitive, Eq, PartialEq)] #[repr(u8)] pub enum Speed { #[default] @@ -68,6 +68,33 @@ impl Speed { } } +impl std::str::FromStr for Speed { + type Err = Error; + + fn from_str(s: &str) -> Result { + use Speed::*; + match s { + "auto" => Ok(Auto), + "high" => Ok(High), + "full" => Ok(Full), + "low" => Ok(Low), + _ => bail!("Invalid speed: {}", s) + } + } +} + +impl std::fmt::Display for Speed { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.description()) + } +} + +impl std::fmt::Debug for Speed { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.description()) + } +} + bitfield! { #[derive(Copy, Clone)] struct State(u8); @@ -366,13 +393,13 @@ impl CynthionHandle { fn start_capture(&mut self, speed: Speed) -> Result<(), Error> { self.write_request(1, State::new(true, speed).0)?; - println!("Capture enabled, speed: {}", speed.description()); + eprintln!("Capture enabled, speed: {}", speed.description()); Ok(()) } fn stop_capture(&mut self) -> Result<(), Error> { self.write_request(1, State::new(false, Speed::High).0)?; - println!("Capture disabled"); + eprintln!("Capture disabled"); Ok(()) } @@ -545,7 +572,7 @@ impl CynthionStream { impl CynthionStop { pub fn stop(self) -> Result<(), Error> { - println!("Requesting capture stop"); + eprintln!("Requesting capture stop"); self.stop_request.send(()) .or_else(|_| bail!("Failed sending stop request"))?; handle_thread_panic(self.worker.join())?; diff --git a/src/cli_capture.rs b/src/cli_capture.rs new file mode 100644 index 00000000..7d2c66ce --- /dev/null +++ b/src/cli_capture.rs @@ -0,0 +1,159 @@ +use anyhow::Result; +use crate::backend::cynthion::{ + CynthionDevice, + CynthionUsability::*, + Speed +}; +use crate::capture::{ + create_capture, + PacketId +}; +use crate::decoder::Decoder; +use crate::pcap::Writer; +use std::fs::File; +use std::thread; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Mutex}; +use ctrlc; + +use argh::FromArgs; +#[derive(FromArgs, PartialEq, Debug)] +/// Start packetry in CLI mode +#[argh(subcommand, name = "capture")] +pub struct SubCommandCliCapture { + /// device (default: auto) + #[argh(option, short = 'd', long = "device", default = "String::from(\"auto\")")] + device_serial: String, + /// usb speed (default: low) + #[argh(option, short = 's', long = "speed", default = "Speed::Low")] + usb_speed: Speed, + /// output file (use '-' for stdout, default: cynthion.pcap) + #[argh(option, short = 'o', long = "output", default = "String::from(\"cynthion.pcap\")")] + output_file: String, +} + + +pub fn headless_capture(options: SubCommandCliCapture) -> Result<()> { + let device = select_device(&options.device_serial, options.usb_speed)?; + + let (capture_writer, mut capture_reader) = create_capture()?; + let mut decoder = Decoder::new(capture_writer)?; + + // Open a writer to stdout if the output file is set to '-' + let mut stdout_writer = if options.output_file == "-" { + let stdout_file = std::io::stdout(); + Some(Writer::open(stdout_file)?) + } else { + None + }; + + let cynthion = device.open()?; + let (stream_handle, stop_handle) = cynthion.start(options.usb_speed, |e| eprintln!("{:?}", e))?; + + // Stop the capture when CTRL+C is pressed + // For this set up a watchdog thread that checks if CTRL+C was pressed + let running = Arc::new(AtomicBool::new(true)); + let r = running.clone(); + let stop_handle = Arc::new(Mutex::new(Some(stop_handle))); + let stop_handle_clone = Arc::clone(&stop_handle); + + ctrlc::set_handler(move || { + r.store(false, Ordering::SeqCst); + }).expect("Error setting CTRL+C handler"); + + let ctrlc_watchdog = { + let running = running.clone(); + thread::spawn(move || { + while running.load(Ordering::SeqCst) { + thread::sleep(std::time::Duration::from_millis(100)); + } + + if let Some(handle) = stop_handle_clone.lock().unwrap().take() { + if let Err(e) = handle.stop() { + eprintln!("Error stopping capture: {}", e); + } + } + }) + }; + + let mut counter = 0; + for packet in stream_handle { + decoder.handle_raw_packet(&packet.bytes, packet.timestamp_ns)?; + + // Write the packet to stdout + if let Some(ref mut stdout_writer) = stdout_writer { + let packet_id = PacketId::from(counter); + let packet = capture_reader.packet(packet_id)?; + let timestamp_ns = capture_reader.packet_time(packet_id)?; + stdout_writer.add_packet(&packet, timestamp_ns)?; + } + + counter += 1; + } + + ctrlc_watchdog.join().expect("CTRL+C watchdog thread panicked"); + + if let Some(stdout_writer) = stdout_writer { + stdout_writer.close()?; + } + + // Save the capture to disk + if options.output_file != "-" { + let pcap_file = File::create(&options.output_file)?; + let mut pcap_writer = Writer::open(pcap_file)?; + + let packet_count = capture_reader.packet_index.len(); + for i in 0..packet_count { + let packet_id = PacketId::from(i); + let packet = capture_reader.packet(packet_id)?; + let timestamp_ns = capture_reader.packet_time(packet_id)?; + pcap_writer.add_packet(&packet, timestamp_ns)?; + } + pcap_writer.close()?; + eprintln!("Capture saved to {}", options.output_file); + } + + Ok(()) +} + +fn select_device(serial: &str, speed: Speed) -> Result { + let mut devices = CynthionDevice::scan()?; + let count = devices.len(); + + if count == 0 { + return Err(anyhow::anyhow!("No devices found")); + } + + let device_index = if serial == "auto" { + // Select the first device + Some(0) + } else { + // Find the device with the requested serial number + devices.iter().position(|d| d.device_info.serial_number() == Some(&serial.to_string())) + }; + + // Check if the device was found + let device_index = match device_index { + Some(index) => index, + None => return Err(anyhow::anyhow!("Device with serial {} not found", serial)), + }; + + // Remove the device from the list to get ownership + let device = devices.remove(device_index); + + // Check if the device is usable and supports the requested speed + match &device.usability { + Usable(_, speeds) => { + if !speeds.contains(&speed) { + return Err(anyhow::anyhow!("Device does not support speed {:?}", speed)); + } + }, + + Unusable(reason) => { + return Err(anyhow::anyhow!("Device is not usable: {}", reason)); + } + } + + Ok(device) +} + diff --git a/src/devices.rs b/src/devices.rs new file mode 100644 index 00000000..64d88705 --- /dev/null +++ b/src/devices.rs @@ -0,0 +1,82 @@ +use crate::backend::cynthion::{CynthionDevice, CynthionUsability::*}; +use tabled::{Table, Tabled}; +use anyhow::Result; + +#[derive(Tabled)] +pub struct DeviceInfo { + name: String, + serial: String, + useable: String, + bus: String, + address: String, + speeds: String, +} + +pub fn list_devices() -> Result<()> { + let devices = CynthionDevice::scan()?; + + let count = devices.len(); + let mut dev_names = Vec::with_capacity(count); + let mut dev_serialnumbers = Vec::with_capacity(count); + let mut dev_states = Vec::with_capacity(count); + let mut dev_busnumbers = Vec::with_capacity(count); + let mut dev_addresses = Vec::with_capacity(count); + let mut dev_speeds = Vec::with_capacity(count); + + for device in devices.iter() { + let info = &device.device_info; + + dev_names.push( + // Maybe in the future there are more devices to support. Hardcode the name for now. + "Cynthion".to_string() + ); + + + dev_serialnumbers.push( + if let Some(serial) = info.serial_number() { + serial.to_string() + } else { + "None".to_string() + } + ); + + dev_busnumbers.push(info.bus_number().to_string()); + dev_addresses.push(info.device_address().to_string()); + + match &device.usability { + Usable(_, speeds) => { + dev_states.push("Yes".to_string()); + dev_speeds.push( + speeds.iter() + .map(|speed| { + let desc = speed.description(); + desc.split(" ").next().unwrap_or("").to_string() + }) + .collect() + ); + }, + + Unusable(reason) => { + dev_states.push(reason.to_string()); + dev_speeds.push(Vec::new()); + } + } + } + + let mut device_table = Vec::with_capacity(count); + for i in 0..count { + device_table.push(DeviceInfo { + name: dev_names[i].clone(), + serial: dev_serialnumbers[i].clone(), + useable: dev_states[i].clone(), + bus: dev_busnumbers[i].clone(), + address: dev_addresses[i].clone(), + speeds: dev_speeds[i].join(", "), + }); + } + + let table = Table::new(device_table).to_string(); + println!("{}", table); + + Ok(()) +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 090abe34..da7e17dc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -38,6 +38,8 @@ mod usb; mod util; mod vec_map; mod version; +mod devices; +mod cli_capture; // Declare optional modules. #[cfg(any(test, feature="record-ui-test"))] @@ -47,7 +49,6 @@ mod test_replay; use gtk::prelude::*; use gtk::gio::ApplicationFlags; -use gtk::glib::{self, OptionArg, OptionFlags}; use ui::{ activate, @@ -56,9 +57,51 @@ use ui::{ stop_cynthion }; use version::{version, version_info}; +use devices::list_devices; +use cli_capture::{SubCommandCliCapture, headless_capture}; -fn have_argument(name: &str) -> bool { - std::env::args().any(|arg| arg == name) +use argh::FromArgs; +#[derive(FromArgs, PartialEq, Debug)] +/// packetry - a fast, intuitive USB 2.0 protocol analysis application for use with Cynthion +struct Args { + #[argh(subcommand)] + sub_commands: Option, + + /// recording (pcap) to open in the GUI + #[argh(positional)] + filename: Option, +} + +#[derive(FromArgs, PartialEq, Debug)] +#[argh(subcommand)] +enum SubcommandEnum { + One(SubCommandCliCapture), + Two(SubCommandDevices), + Three(SubCommandVersion), + Four(SubCommandTest) +} + +#[derive(FromArgs, PartialEq, Debug)] +/// List capture devices +#[argh(subcommand, name = "devices")] +struct SubCommandDevices {} + +#[derive(FromArgs, PartialEq, Debug)] +/// Print version information +#[argh(subcommand, name = "version")] +struct SubCommandVersion { + /// print dependency information with version + #[argh(switch, long = "dependencies")] + print_dependencies: bool, +} + +#[derive(FromArgs, PartialEq, Debug)] +/// Test an attached Cynthion +#[argh(subcommand, name = "test-cynthion")] +struct SubCommandTest { + /// save the captures to disk + #[argh(switch, long = "save-captures")] + save_captures: bool, } fn main() { @@ -69,37 +112,54 @@ fn main() { unsafe {AttachConsole(ATTACH_PARENT_PROCESS)}; } - if have_argument("--version") { - println!("Packetry version {}\n\n{}", - version(), - version_info(have_argument("--dependencies"))); - } else if have_argument("--test-cynthion") { - let save_captures = have_argument("--save-captures"); - test_cynthion::run_test(save_captures); + + let args: Args = argh::from_env(); + + if let Some(subcmd) = args.sub_commands { + match subcmd { + SubcommandEnum::One(captureoptions) => { + if let Err(e) = headless_capture(captureoptions) { + eprintln!("Error capturing: {}", e); + } + } + + SubcommandEnum::Two(_) => { + if let Err(e) = list_devices() { + eprintln!("Error listing devices: {}", e); + } + } + + SubcommandEnum::Three(versionoptions) => { + println!("Packetry version {}\n\n{}", + version(), + version_info(versionoptions.print_dependencies)); + } + + SubcommandEnum::Four(testoptions) => { + test_cynthion::run_test(testoptions.save_captures); + } + } + } else { + // Start the GUI application as no subcommand was provided. + // Here, args[1] is the filename if one was provided. This should be added to the argh enum. + let application = gtk::Application::new( Some("com.greatscottgadgets.packetry"), ApplicationFlags::NON_UNIQUE | ApplicationFlags::HANDLES_OPEN ); application.set_option_context_parameter_string( - Some("[filename.pcap]")); - application.add_main_option( - "version", glib::Char::from(0), - OptionFlags::NONE, OptionArg::None, - "Print version information", None); - application.add_main_option( - "test-cynthion", glib::Char::from(0), - OptionFlags::NONE, OptionArg::None, - "Test an attached Cynthion USB analyzer", None); + Some("[filename.pcap]")); application.connect_activate(|app| display_error(activate(app))); application.connect_open(|app, files, _hint| { - app.activate(); - if let Some(file) = files.first() { - display_error(open(file)); - } + app.activate(); + if let Some(file) = files.first() { + display_error(open(file)); + } }); application.run(); display_error(stop_cynthion()); } } + From e27ca9e0b70b8ef8ea39906a40c12fe9c671db76 Mon Sep 17 00:00:00 2001 From: manu Date: Tue, 6 Aug 2024 12:52:59 +0200 Subject: [PATCH 02/10] Do not print an empty table --- src/devices.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/devices.rs b/src/devices.rs index 64d88705..9008e09c 100644 --- a/src/devices.rs +++ b/src/devices.rs @@ -16,6 +16,12 @@ pub fn list_devices() -> Result<()> { let devices = CynthionDevice::scan()?; let count = devices.len(); + + if count == 0 { + println!("No devices found."); + return Ok(()); + } + let mut dev_names = Vec::with_capacity(count); let mut dev_serialnumbers = Vec::with_capacity(count); let mut dev_states = Vec::with_capacity(count); From 7aa40023325e6acb855b962b38bb25c511c2203c Mon Sep 17 00:00:00 2001 From: manu Date: Tue, 6 Aug 2024 17:29:52 +0200 Subject: [PATCH 03/10] updated target-lexicon version to work with tabled --- wix/rust_licenses.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wix/rust_licenses.py b/wix/rust_licenses.py index 9523eee0..c029c76c 100644 --- a/wix/rust_licenses.py +++ b/wix/rust_licenses.py @@ -24,7 +24,7 @@ # These packages have been validated for conditionally accepted licenses. validated = ( - ('target-lexicon', '0.12.14'), + ('target-lexicon', '0.12.16'), ('unicode-ident', '1.0.12'), ) @@ -119,6 +119,7 @@ def validate_license(package, version, expr): file_paths = ( ['LICENSE-MIT'], ['LICENSE-MIT.md'], + ['LICENSE.MIT'], ['license-mit'], ['LICENSES', 'MIT.txt'], ['LICENSE'], From 7980159ad8a973766f7974c9ef77a4bff2d0cc55 Mon Sep 17 00:00:00 2001 From: manu Date: Tue, 6 Aug 2024 17:31:21 +0200 Subject: [PATCH 04/10] simplified list_devices() to only construct one Vec --- src/devices.rs | 129 +++++++++++++++++++------------------------------ 1 file changed, 51 insertions(+), 78 deletions(-) diff --git a/src/devices.rs b/src/devices.rs index 9008e09c..201997d3 100644 --- a/src/devices.rs +++ b/src/devices.rs @@ -4,85 +4,58 @@ use anyhow::Result; #[derive(Tabled)] pub struct DeviceInfo { - name: String, - serial: String, - useable: String, - bus: String, - address: String, - speeds: String, + name: String, + serial: String, + useable: String, + bus: String, + address: String, + speeds: String, } pub fn list_devices() -> Result<()> { - let devices = CynthionDevice::scan()?; - - let count = devices.len(); - - if count == 0 { - println!("No devices found."); - return Ok(()); - } - - let mut dev_names = Vec::with_capacity(count); - let mut dev_serialnumbers = Vec::with_capacity(count); - let mut dev_states = Vec::with_capacity(count); - let mut dev_busnumbers = Vec::with_capacity(count); - let mut dev_addresses = Vec::with_capacity(count); - let mut dev_speeds = Vec::with_capacity(count); - - for device in devices.iter() { - let info = &device.device_info; - - dev_names.push( - // Maybe in the future there are more devices to support. Hardcode the name for now. - "Cynthion".to_string() - ); - - - dev_serialnumbers.push( - if let Some(serial) = info.serial_number() { - serial.to_string() - } else { - "None".to_string() - } - ); - - dev_busnumbers.push(info.bus_number().to_string()); - dev_addresses.push(info.device_address().to_string()); - - match &device.usability { - Usable(_, speeds) => { - dev_states.push("Yes".to_string()); - dev_speeds.push( - speeds.iter() - .map(|speed| { - let desc = speed.description(); - desc.split(" ").next().unwrap_or("").to_string() - }) - .collect() - ); - }, - - Unusable(reason) => { - dev_states.push(reason.to_string()); - dev_speeds.push(Vec::new()); - } - } - } - - let mut device_table = Vec::with_capacity(count); - for i in 0..count { - device_table.push(DeviceInfo { - name: dev_names[i].clone(), - serial: dev_serialnumbers[i].clone(), - useable: dev_states[i].clone(), - bus: dev_busnumbers[i].clone(), - address: dev_addresses[i].clone(), - speeds: dev_speeds[i].join(", "), - }); - } - - let table = Table::new(device_table).to_string(); - println!("{}", table); - - Ok(()) + let devices = CynthionDevice::scan()?; + + if devices.is_empty() { + println!("No devices found."); + return Ok(()); + } + + let device_table: Vec = devices.iter().map(|device| { + let info = &device.device_info; + + // Maybe in the future there are more devices to support. Hardcode the name for now. + let name = "Cynthion".to_string(); + + let serial = info.serial_number().map_or("None".to_string(), |s| s.to_string()); + let bus = info.bus_number().to_string(); + let address = info.device_address().to_string(); + + let (useable, speeds) = match &device.usability { + Usable(_, speeds) => ( + "Yes".to_string(), + speeds.iter() + .map(|speed| { + let desc = speed.description(); + desc.split(' ').next().unwrap_or("").to_string() + }) + .collect::>() + .join(", "), + ), + Unusable(reason) => (reason.to_string(), String::new()), + }; + + DeviceInfo { + name, + serial, + useable, + bus, + address, + speeds, + } + }).collect(); + + let table = Table::new(device_table).to_string(); + println!("{}", table); + + Ok(()) } \ No newline at end of file From d965d4304760bb4d3944305fe02f4f056c229945 Mon Sep 17 00:00:00 2001 From: manu Date: Tue, 6 Aug 2024 17:34:14 +0200 Subject: [PATCH 05/10] changes indent to 4 spaces + applied format --- src/cli_capture.rs | 279 +++++++++++++++++++++++---------------------- src/devices.rs | 76 ++++++------ src/main.rs | 46 +++----- 3 files changed, 203 insertions(+), 198 deletions(-) diff --git a/src/cli_capture.rs b/src/cli_capture.rs index 7d2c66ce..3112df5e 100644 --- a/src/cli_capture.rs +++ b/src/cli_capture.rs @@ -1,159 +1,166 @@ -use anyhow::Result; -use crate::backend::cynthion::{ - CynthionDevice, - CynthionUsability::*, - Speed -}; -use crate::capture::{ - create_capture, - PacketId -}; +use crate::backend::cynthion::{CynthionDevice, CynthionUsability::*, Speed}; +use crate::capture::{create_capture, PacketId}; use crate::decoder::Decoder; use crate::pcap::Writer; +use anyhow::Result; +use ctrlc; use std::fs::File; -use std::thread; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; -use ctrlc; +use std::thread; use argh::FromArgs; #[derive(FromArgs, PartialEq, Debug)] /// Start packetry in CLI mode #[argh(subcommand, name = "capture")] pub struct SubCommandCliCapture { - /// device (default: auto) - #[argh(option, short = 'd', long = "device", default = "String::from(\"auto\")")] - device_serial: String, - /// usb speed (default: low) - #[argh(option, short = 's', long = "speed", default = "Speed::Low")] - usb_speed: Speed, - /// output file (use '-' for stdout, default: cynthion.pcap) - #[argh(option, short = 'o', long = "output", default = "String::from(\"cynthion.pcap\")")] - output_file: String, + /// device (default: auto) + #[argh( + option, + short = 'd', + long = "device", + default = "String::from(\"auto\")" + )] + device_serial: String, + /// usb speed (default: low) + #[argh(option, short = 's', long = "speed", default = "Speed::Low")] + usb_speed: Speed, + /// output file (use '-' for stdout, default: cynthion.pcap) + #[argh( + option, + short = 'o', + long = "output", + default = "String::from(\"cynthion.pcap\")" + )] + output_file: String, } - pub fn headless_capture(options: SubCommandCliCapture) -> Result<()> { - let device = select_device(&options.device_serial, options.usb_speed)?; - - let (capture_writer, mut capture_reader) = create_capture()?; - let mut decoder = Decoder::new(capture_writer)?; - - // Open a writer to stdout if the output file is set to '-' - let mut stdout_writer = if options.output_file == "-" { - let stdout_file = std::io::stdout(); - Some(Writer::open(stdout_file)?) - } else { - None - }; - - let cynthion = device.open()?; - let (stream_handle, stop_handle) = cynthion.start(options.usb_speed, |e| eprintln!("{:?}", e))?; - - // Stop the capture when CTRL+C is pressed - // For this set up a watchdog thread that checks if CTRL+C was pressed - let running = Arc::new(AtomicBool::new(true)); - let r = running.clone(); - let stop_handle = Arc::new(Mutex::new(Some(stop_handle))); - let stop_handle_clone = Arc::clone(&stop_handle); - - ctrlc::set_handler(move || { - r.store(false, Ordering::SeqCst); - }).expect("Error setting CTRL+C handler"); - - let ctrlc_watchdog = { - let running = running.clone(); - thread::spawn(move || { - while running.load(Ordering::SeqCst) { - thread::sleep(std::time::Duration::from_millis(100)); - } - - if let Some(handle) = stop_handle_clone.lock().unwrap().take() { - if let Err(e) = handle.stop() { - eprintln!("Error stopping capture: {}", e); - } - } - }) - }; - - let mut counter = 0; - for packet in stream_handle { - decoder.handle_raw_packet(&packet.bytes, packet.timestamp_ns)?; - - // Write the packet to stdout - if let Some(ref mut stdout_writer) = stdout_writer { - let packet_id = PacketId::from(counter); - let packet = capture_reader.packet(packet_id)?; - let timestamp_ns = capture_reader.packet_time(packet_id)?; - stdout_writer.add_packet(&packet, timestamp_ns)?; - } - - counter += 1; - } - - ctrlc_watchdog.join().expect("CTRL+C watchdog thread panicked"); - - if let Some(stdout_writer) = stdout_writer { - stdout_writer.close()?; - } - - // Save the capture to disk - if options.output_file != "-" { - let pcap_file = File::create(&options.output_file)?; - let mut pcap_writer = Writer::open(pcap_file)?; - - let packet_count = capture_reader.packet_index.len(); - for i in 0..packet_count { - let packet_id = PacketId::from(i); - let packet = capture_reader.packet(packet_id)?; - let timestamp_ns = capture_reader.packet_time(packet_id)?; - pcap_writer.add_packet(&packet, timestamp_ns)?; - } - pcap_writer.close()?; - eprintln!("Capture saved to {}", options.output_file); - } - - Ok(()) + let device = select_device(&options.device_serial, options.usb_speed)?; + + let (capture_writer, mut capture_reader) = create_capture()?; + let mut decoder = Decoder::new(capture_writer)?; + + // Open a writer to stdout if the output file is set to '-' + let mut stdout_writer = if options.output_file == "-" { + let stdout_file = std::io::stdout(); + Some(Writer::open(stdout_file)?) + } else { + None + }; + + let cynthion = device.open()?; + let (stream_handle, stop_handle) = + cynthion.start(options.usb_speed, |e| eprintln!("{:?}", e))?; + + // Stop the capture when CTRL+C is pressed + // For this set up a watchdog thread that checks if CTRL+C was pressed + let running = Arc::new(AtomicBool::new(true)); + let r = running.clone(); + let stop_handle = Arc::new(Mutex::new(Some(stop_handle))); + let stop_handle_clone = Arc::clone(&stop_handle); + + ctrlc::set_handler(move || { + r.store(false, Ordering::SeqCst); + }) + .expect("Error setting CTRL+C handler"); + + let ctrlc_watchdog = { + let running = running.clone(); + thread::spawn(move || { + while running.load(Ordering::SeqCst) { + thread::sleep(std::time::Duration::from_millis(100)); + } + + if let Some(handle) = stop_handle_clone.lock().unwrap().take() { + if let Err(e) = handle.stop() { + eprintln!("Error stopping capture: {}", e); + } + } + }) + }; + + let mut counter = 0; + for packet in stream_handle { + decoder.handle_raw_packet(&packet.bytes, packet.timestamp_ns)?; + + // Write the packet to stdout + if let Some(ref mut stdout_writer) = stdout_writer { + let packet_id = PacketId::from(counter); + let packet = capture_reader.packet(packet_id)?; + let timestamp_ns = capture_reader.packet_time(packet_id)?; + stdout_writer.add_packet(&packet, timestamp_ns)?; + } + + counter += 1; + } + + ctrlc_watchdog + .join() + .expect("CTRL+C watchdog thread panicked"); + + if let Some(stdout_writer) = stdout_writer { + stdout_writer.close()?; + } + + // Save the capture to disk + if options.output_file != "-" { + let pcap_file = File::create(&options.output_file)?; + let mut pcap_writer = Writer::open(pcap_file)?; + + let packet_count = capture_reader.packet_index.len(); + for i in 0..packet_count { + let packet_id = PacketId::from(i); + let packet = capture_reader.packet(packet_id)?; + let timestamp_ns = capture_reader.packet_time(packet_id)?; + pcap_writer.add_packet(&packet, timestamp_ns)?; + } + pcap_writer.close()?; + eprintln!("Capture saved to {}", options.output_file); + } + + Ok(()) } fn select_device(serial: &str, speed: Speed) -> Result { - let mut devices = CynthionDevice::scan()?; - let count = devices.len(); - - if count == 0 { - return Err(anyhow::anyhow!("No devices found")); - } - - let device_index = if serial == "auto" { - // Select the first device - Some(0) - } else { - // Find the device with the requested serial number - devices.iter().position(|d| d.device_info.serial_number() == Some(&serial.to_string())) - }; - - // Check if the device was found + let mut devices = CynthionDevice::scan()?; + let count = devices.len(); + + if count == 0 { + return Err(anyhow::anyhow!("No devices found")); + } + + let device_index = if serial == "auto" { + // Select the first device + Some(0) + } else { + // Find the device with the requested serial number + devices + .iter() + .position(|d| d.device_info.serial_number() == Some(&serial.to_string())) + }; + + // Check if the device was found let device_index = match device_index { Some(index) => index, None => return Err(anyhow::anyhow!("Device with serial {} not found", serial)), }; - // Remove the device from the list to get ownership - let device = devices.remove(device_index); - - // Check if the device is usable and supports the requested speed - match &device.usability { - Usable(_, speeds) => { - if !speeds.contains(&speed) { - return Err(anyhow::anyhow!("Device does not support speed {:?}", speed)); - } - }, - - Unusable(reason) => { - return Err(anyhow::anyhow!("Device is not usable: {}", reason)); - } - } - - Ok(device) -} + // Remove the device from the list to get ownership + let device = devices.remove(device_index); + + // Check if the device is usable and supports the requested speed + match &device.usability { + Usable(_, speeds) => { + if !speeds.contains(&speed) { + return Err(anyhow::anyhow!("Device does not support speed {:?}", speed)); + } + } + Unusable(reason) => { + return Err(anyhow::anyhow!("Device is not usable: {}", reason)); + } + } + + Ok(device) +} diff --git a/src/devices.rs b/src/devices.rs index 201997d3..b338c58b 100644 --- a/src/devices.rs +++ b/src/devices.rs @@ -1,6 +1,6 @@ use crate::backend::cynthion::{CynthionDevice, CynthionUsability::*}; -use tabled::{Table, Tabled}; use anyhow::Result; +use tabled::{Table, Tabled}; #[derive(Tabled)] pub struct DeviceInfo { @@ -20,42 +20,48 @@ pub fn list_devices() -> Result<()> { return Ok(()); } - let device_table: Vec = devices.iter().map(|device| { - let info = &device.device_info; - - // Maybe in the future there are more devices to support. Hardcode the name for now. - let name = "Cynthion".to_string(); - - let serial = info.serial_number().map_or("None".to_string(), |s| s.to_string()); - let bus = info.bus_number().to_string(); - let address = info.device_address().to_string(); - - let (useable, speeds) = match &device.usability { - Usable(_, speeds) => ( - "Yes".to_string(), - speeds.iter() - .map(|speed| { - let desc = speed.description(); - desc.split(' ').next().unwrap_or("").to_string() - }) - .collect::>() - .join(", "), - ), - Unusable(reason) => (reason.to_string(), String::new()), - }; - - DeviceInfo { - name, - serial, - useable, - bus, - address, - speeds, - } - }).collect(); + let device_table: Vec = devices + .iter() + .map(|device| { + let info = &device.device_info; + + // Maybe in the future there are more devices to support. Hardcode the name for now. + let name = "Cynthion".to_string(); + + let serial = info + .serial_number() + .map_or("None".to_string(), |s| s.to_string()); + let bus = info.bus_number().to_string(); + let address = info.device_address().to_string(); + + let (useable, speeds) = match &device.usability { + Usable(_, speeds) => ( + "Yes".to_string(), + speeds + .iter() + .map(|speed| { + let desc = speed.description(); + desc.split(' ').next().unwrap_or("").to_string() + }) + .collect::>() + .join(", "), + ), + Unusable(reason) => (reason.to_string(), String::new()), + }; + + DeviceInfo { + name, + serial, + useable, + bus, + address, + speeds, + } + }) + .collect(); let table = Table::new(device_table).to_string(); println!("{}", table); Ok(()) -} \ No newline at end of file +} diff --git a/src/main.rs b/src/main.rs index da7e17dc..0c184a23 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,23 +6,25 @@ extern crate bitfield; // We need the ctor macro for the replay test on macOS. -#[cfg(all(test, target_os="macos"))] +#[cfg(all(test, target_os = "macos"))] #[allow(unused_imports)] #[macro_use] extern crate ctor; // Include build-time info. pub mod built { - // The file has been placed there by the build script. - include!(concat!(env!("OUT_DIR"), "/built.rs")); + // The file has been placed there by the build script. + include!(concat!(env!("OUT_DIR"), "/built.rs")); } // Declare all modules used. mod backend; mod capture; +mod cli_capture; mod compact_index; mod data_stream; mod decoder; +mod devices; mod expander; mod id; mod index_stream; @@ -38,27 +40,20 @@ mod usb; mod util; mod vec_map; mod version; -mod devices; -mod cli_capture; // Declare optional modules. -#[cfg(any(test, feature="record-ui-test"))] +#[cfg(any(test, feature = "record-ui-test"))] mod record_ui; #[cfg(test)] mod test_replay; -use gtk::prelude::*; use gtk::gio::ApplicationFlags; +use gtk::prelude::*; -use ui::{ - activate, - display_error, - open, - stop_cynthion -}; -use version::{version, version_info}; +use cli_capture::{headless_capture, SubCommandCliCapture}; use devices::list_devices; -use cli_capture::{SubCommandCliCapture, headless_capture}; +use ui::{activate, display_error, open, stop_cynthion}; +use version::{version, version_info}; use argh::FromArgs; #[derive(FromArgs, PartialEq, Debug)] @@ -78,7 +73,7 @@ enum SubcommandEnum { One(SubCommandCliCapture), Two(SubCommandDevices), Three(SubCommandVersion), - Four(SubCommandTest) + Four(SubCommandTest), } #[derive(FromArgs, PartialEq, Debug)] @@ -109,10 +104,9 @@ fn main() { #[cfg(windows)] if std::env::var("PACKETRY_ATTACH_CONSOLE").is_ok() { use winapi::um::wincon::{AttachConsole, ATTACH_PARENT_PROCESS}; - unsafe {AttachConsole(ATTACH_PARENT_PROCESS)}; + unsafe { AttachConsole(ATTACH_PARENT_PROCESS) }; } - let args: Args = argh::from_env(); if let Some(subcmd) = args.sub_commands { @@ -130,27 +124,26 @@ fn main() { } SubcommandEnum::Three(versionoptions) => { - println!("Packetry version {}\n\n{}", - version(), - version_info(versionoptions.print_dependencies)); + println!( + "Packetry version {}\n\n{}", + version(), + version_info(versionoptions.print_dependencies) + ); } SubcommandEnum::Four(testoptions) => { test_cynthion::run_test(testoptions.save_captures); } } - } else { // Start the GUI application as no subcommand was provided. // Here, args[1] is the filename if one was provided. This should be added to the argh enum. let application = gtk::Application::new( Some("com.greatscottgadgets.packetry"), - ApplicationFlags::NON_UNIQUE | - ApplicationFlags::HANDLES_OPEN + ApplicationFlags::NON_UNIQUE | ApplicationFlags::HANDLES_OPEN, ); - application.set_option_context_parameter_string( - Some("[filename.pcap]")); + application.set_option_context_parameter_string(Some("[filename.pcap]")); application.connect_activate(|app| display_error(activate(app))); application.connect_open(|app, files, _hint| { app.activate(); @@ -162,4 +155,3 @@ fn main() { display_error(stop_cynthion()); } } - From cd95346d5483c27c2567c2c75e9d9943c5766c73 Mon Sep 17 00:00:00 2001 From: manu Date: Tue, 6 Aug 2024 17:53:15 +0200 Subject: [PATCH 06/10] removed CTRLC watchdog by passing handle directly --- src/cli_capture.rs | 38 ++++++++------------------------------ 1 file changed, 8 insertions(+), 30 deletions(-) diff --git a/src/cli_capture.rs b/src/cli_capture.rs index 3112df5e..84ed8722 100644 --- a/src/cli_capture.rs +++ b/src/cli_capture.rs @@ -5,9 +5,7 @@ use crate::pcap::Writer; use anyhow::Result; use ctrlc; use std::fs::File; -use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; -use std::thread; use argh::FromArgs; #[derive(FromArgs, PartialEq, Debug)] @@ -53,32 +51,17 @@ pub fn headless_capture(options: SubCommandCliCapture) -> Result<()> { let (stream_handle, stop_handle) = cynthion.start(options.usb_speed, |e| eprintln!("{:?}", e))?; - // Stop the capture when CTRL+C is pressed - // For this set up a watchdog thread that checks if CTRL+C was pressed - let running = Arc::new(AtomicBool::new(true)); - let r = running.clone(); let stop_handle = Arc::new(Mutex::new(Some(stop_handle))); - let stop_handle_clone = Arc::clone(&stop_handle); - ctrlc::set_handler(move || { - r.store(false, Ordering::SeqCst); + let mut handle_opt = stop_handle.lock().unwrap(); + if let Some(handle) = handle_opt.take() { + if let Err(e) = handle.stop() { + eprintln!("Failed to stop Cynthion: {}", e); + } + } }) .expect("Error setting CTRL+C handler"); - - let ctrlc_watchdog = { - let running = running.clone(); - thread::spawn(move || { - while running.load(Ordering::SeqCst) { - thread::sleep(std::time::Duration::from_millis(100)); - } - - if let Some(handle) = stop_handle_clone.lock().unwrap().take() { - if let Err(e) = handle.stop() { - eprintln!("Error stopping capture: {}", e); - } - } - }) - }; + let mut counter = 0; for packet in stream_handle { @@ -95,10 +78,6 @@ pub fn headless_capture(options: SubCommandCliCapture) -> Result<()> { counter += 1; } - ctrlc_watchdog - .join() - .expect("CTRL+C watchdog thread panicked"); - if let Some(stdout_writer) = stdout_writer { stdout_writer.close()?; } @@ -124,9 +103,8 @@ pub fn headless_capture(options: SubCommandCliCapture) -> Result<()> { fn select_device(serial: &str, speed: Speed) -> Result { let mut devices = CynthionDevice::scan()?; - let count = devices.len(); - if count == 0 { + if devices.is_empty() { return Err(anyhow::anyhow!("No devices found")); } From 39cf35e9aadc4dab5e4916027c7c2bf4f77bfbfa Mon Sep 17 00:00:00 2001 From: manu Date: Tue, 6 Aug 2024 20:46:45 +0200 Subject: [PATCH 07/10] simplified headless_capture() to only use one pcap::Writer --- src/cli_capture.rs | 49 +++++++--------------------------------------- 1 file changed, 7 insertions(+), 42 deletions(-) diff --git a/src/cli_capture.rs b/src/cli_capture.rs index 84ed8722..debe38b0 100644 --- a/src/cli_capture.rs +++ b/src/cli_capture.rs @@ -1,10 +1,9 @@ use crate::backend::cynthion::{CynthionDevice, CynthionUsability::*, Speed}; -use crate::capture::{create_capture, PacketId}; -use crate::decoder::Decoder; use crate::pcap::Writer; use anyhow::Result; use ctrlc; use std::fs::File; +use std::io::{self, Write}; use std::sync::{Arc, Mutex}; use argh::FromArgs; @@ -36,16 +35,12 @@ pub struct SubCommandCliCapture { pub fn headless_capture(options: SubCommandCliCapture) -> Result<()> { let device = select_device(&options.device_serial, options.usb_speed)?; - let (capture_writer, mut capture_reader) = create_capture()?; - let mut decoder = Decoder::new(capture_writer)?; - - // Open a writer to stdout if the output file is set to '-' - let mut stdout_writer = if options.output_file == "-" { - let stdout_file = std::io::stdout(); - Some(Writer::open(stdout_file)?) + let output_writer: Box = if options.output_file == "-" { + Box::new(io::stdout()) } else { - None + Box::new(File::create(&options.output_file)?) }; + let mut writer = Writer::open(output_writer)?; let cynthion = device.open()?; let (stream_handle, stop_handle) = @@ -61,42 +56,12 @@ pub fn headless_capture(options: SubCommandCliCapture) -> Result<()> { } }) .expect("Error setting CTRL+C handler"); - - let mut counter = 0; for packet in stream_handle { - decoder.handle_raw_packet(&packet.bytes, packet.timestamp_ns)?; - - // Write the packet to stdout - if let Some(ref mut stdout_writer) = stdout_writer { - let packet_id = PacketId::from(counter); - let packet = capture_reader.packet(packet_id)?; - let timestamp_ns = capture_reader.packet_time(packet_id)?; - stdout_writer.add_packet(&packet, timestamp_ns)?; - } - - counter += 1; + writer.add_packet(&packet.bytes, packet.timestamp_ns)?; } - if let Some(stdout_writer) = stdout_writer { - stdout_writer.close()?; - } - - // Save the capture to disk - if options.output_file != "-" { - let pcap_file = File::create(&options.output_file)?; - let mut pcap_writer = Writer::open(pcap_file)?; - - let packet_count = capture_reader.packet_index.len(); - for i in 0..packet_count { - let packet_id = PacketId::from(i); - let packet = capture_reader.packet(packet_id)?; - let timestamp_ns = capture_reader.packet_time(packet_id)?; - pcap_writer.add_packet(&packet, timestamp_ns)?; - } - pcap_writer.close()?; - eprintln!("Capture saved to {}", options.output_file); - } + writer.close()?; Ok(()) } From 79b6b5b91b47d98d20da68f01379bab29ab54038 Mon Sep 17 00:00:00 2001 From: manu Date: Tue, 6 Aug 2024 22:13:36 +0200 Subject: [PATCH 08/10] moved cli capturing to cli.rs --- src/cli.rs | 297 ++++++++++++++++++++++++++++++++++++++++----- src/cli_capture.rs | 109 ----------------- src/devices.rs | 67 ---------- src/main.rs | 128 ++++++------------- 4 files changed, 307 insertions(+), 294 deletions(-) delete mode 100644 src/cli_capture.rs delete mode 100644 src/devices.rs diff --git a/src/cli.rs b/src/cli.rs index d8150953..635c00ca 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -8,36 +8,277 @@ const MAIN_BINARY: &str = "packetry.exe"; #[cfg(not(windows))] const MAIN_BINARY: &str = "packetry"; +// We need the bitfield macro. +#[macro_use] +extern crate bitfield; + +mod backend; +mod pcap; + +use crate::backend::cynthion::{CynthionDevice, CynthionUsability::*, Speed}; + +use crate::pcap::Writer; + +use anyhow::Error; +use std::fs::File; +use std::io::{self, Write}; +use std::sync::{Arc, Mutex}; +use tabled::{Table, Tabled}; + +use argh::FromArgs; +#[derive(FromArgs, PartialEq, Debug)] +/// packetry - a fast, intuitive USB 2.0 protocol analysis application for use with Cynthion +struct Args { + #[argh(subcommand)] + sub_commands: Option, +} + +#[derive(FromArgs, PartialEq, Debug)] +#[argh(subcommand)] +enum SubcommandEnum { + One(SubCommandOpenFileInGui), + Two(SubCommandCliCapture), + Three(SubCommandDevices), +} +#[derive(FromArgs, PartialEq, Debug)] +/// Opens a recording in the GUI +#[argh(subcommand, name = "open")] +struct SubCommandOpenFileInGui { + /// recording (pcap) to open in the GUI + #[argh(positional)] + filename: String, +} + +#[derive(FromArgs, PartialEq, Debug)] +/// Start packetry in CLI mode +#[argh(subcommand, name = "capture")] +struct SubCommandCliCapture { + /// device (default: auto) + #[argh( + option, + short = 'd', + long = "device", + default = "String::from(\"auto\")" + )] + device_serial: String, + /// usb speed (default: low) + #[argh(option, short = 's', long = "speed", default = "Speed::Low")] + usb_speed: Speed, + /// output file (use '-' for stdout, default: cynthion.pcap) + #[argh( + option, + short = 'o', + long = "output", + default = "String::from(\"cynthion.pcap\")" + )] + output_file: String, +} + +#[derive(FromArgs, PartialEq, Debug)] +/// List capture devices +#[argh(subcommand, name = "devices")] +struct SubCommandDevices {} + +#[derive(Tabled)] +pub struct DeviceInfo { + name: String, + serial: String, + useable: String, + bus: String, + address: String, + speeds: String, +} + fn main() -> ExitCode { - // Find the main Packetry executable. - let packetry_binary_path = std::env::current_exe() - .expect("Failed to find path to current executable") - .parent() - .expect("Failed to find parent directory of current executable") - .join(MAIN_BINARY); - - // Prepare to call it, passing through all arguments we were passed. - let mut command = Command::new(packetry_binary_path); - command.args(std::env::args().skip(1)); - - // If on Windows, tell the child that it needs to attach to our console. - #[cfg(windows)] - command.env("PACKETRY_ATTACH_CONSOLE", "1"); - - // Spawn the main binary as a child process, and wait for its exit status. - let exit_status = command - .status() - .expect("Failed to start main packetry binary"); - - // Try to exit with the same code the child did. - match exit_status.code() { - Some(code) => ExitCode::from(code as u8), - None => { - #[cfg(unix)] - if let Some(signal) = exit_status.signal() { - panic!("Packetry was terminated by signal {signal}"); + let args: Args = argh::from_env(); + + let exit_code = if let Some(subcmd) = args.sub_commands { + match subcmd { + SubcommandEnum::One(file) => { + // Find the main Packetry executable. + let packetry_binary_path = std::env::current_exe() + .expect("Failed to find path to current executable") + .parent() + .expect("Failed to find parent directory of current executable") + .join(MAIN_BINARY); + + // Prepare to call it, passing through all arguments we were passed. + let mut command = Command::new(packetry_binary_path); + command.args(&[&file.filename]); + + // If on Windows, tell the child that it needs to attach to our console. + #[cfg(windows)] + command.env("PACKETRY_ATTACH_CONSOLE", "1"); + + // Spawn the main binary as a child process, and wait for its exit status. + let exit_status = command + .status() + .expect("Failed to start main packetry binary"); + + // Try to exit with the same code the child did. + match exit_status.code() { + Some(code) => ExitCode::from(code as u8), + None => { + #[cfg(unix)] + if let Some(signal) = exit_status.signal() { + panic!("Packetry was terminated by signal {signal}"); + } + panic!("Packetry was terminated without an exit code"); + } + } + } + + SubcommandEnum::Two(captureoptions) => { + if let Err(e) = headless_capture(captureoptions) { + eprintln!("Error capturing packets: {}", e); + ExitCode::FAILURE + } else { + ExitCode::SUCCESS + } + } + + SubcommandEnum::Three(_) => { + if let Err(e) = list_devices() { + eprintln!("Error listing devices: {}", e); + ExitCode::FAILURE + } else { + ExitCode::SUCCESS + } } - panic!("Packetry was terminated without an exit code"); } + } else { + ExitCode::SUCCESS + }; + + exit_code +} + +fn headless_capture(options: SubCommandCliCapture) -> Result<(), Error> { + let device = select_device(&options.device_serial, options.usb_speed)?; + + let output_writer: Box = if options.output_file == "-" { + Box::new(io::stdout()) + } else { + Box::new(File::create(&options.output_file)?) + }; + let mut writer = Writer::open(output_writer)?; + + let cynthion = device.open()?; + let (stream_handle, stop_handle) = + cynthion.start(options.usb_speed, |e| eprintln!("{:?}", e))?; + + let stop_handle = Arc::new(Mutex::new(Some(stop_handle))); + ctrlc::set_handler(move || { + let mut handle_opt = stop_handle.lock().unwrap(); + if let Some(handle) = handle_opt.take() { + if let Err(e) = handle.stop() { + eprintln!("Failed to stop Cynthion: {}", e); + } + } + }) + .expect("Error setting CTRL+C handler"); + + for packet in stream_handle { + writer.add_packet(&packet.bytes, packet.timestamp_ns)?; + } + + writer.close()?; + + Ok(()) +} + +fn select_device(serial: &str, speed: Speed) -> Result { + let mut devices = CynthionDevice::scan()?; + + if devices.is_empty() { + return Err(anyhow::anyhow!("No devices found")); + } + + let device_index = if serial == "auto" { + // Select the first device + Some(0) + } else { + // Find the device with the requested serial number + devices + .iter() + .position(|d| d.device_info.serial_number() == Some(&serial.to_string())) + }; + + // Check if the device was found + let device_index = match device_index { + Some(index) => index, + None => return Err(anyhow::anyhow!("Device with serial {} not found", serial)), + }; + + // Remove the device from the list to get ownership + let device = devices.remove(device_index); + + // Check if the device is usable and supports the requested speed + match &device.usability { + Usable(_, speeds) => { + if !speeds.contains(&speed) { + return Err(anyhow::anyhow!("Device does not support speed {:?}", speed)); + } + } + + Unusable(reason) => { + return Err(anyhow::anyhow!("Device is not usable: {}", reason)); + } + } + + Ok(device) +} + +pub fn list_devices() -> Result<(), Error> { + let devices = CynthionDevice::scan()?; + + if devices.is_empty() { + println!("No devices found."); + return Ok(()); } + + let device_table: Vec = devices + .iter() + .map(|device| { + let info = &device.device_info; + + // Maybe in the future there are more devices to support. Hardcode the name for now. + let name = "Cynthion".to_string(); + + let serial = info + .serial_number() + .map_or("None".to_string(), |s| s.to_string()); + let bus = info.bus_number().to_string(); + let address = info.device_address().to_string(); + + let (useable, speeds) = match &device.usability { + Usable(_, speeds) => ( + "Yes".to_string(), + speeds + .iter() + .map(|speed| { + let desc = speed.description(); + desc.split(' ').next().unwrap_or("").to_string() + }) + .collect::>() + .join(", "), + ), + Unusable(reason) => (reason.to_string(), String::new()), + }; + + DeviceInfo { + name, + serial, + useable, + bus, + address, + speeds, + } + }) + .collect(); + + let table = Table::new(device_table).to_string(); + println!("{}", table); + + Ok(()) } diff --git a/src/cli_capture.rs b/src/cli_capture.rs deleted file mode 100644 index debe38b0..00000000 --- a/src/cli_capture.rs +++ /dev/null @@ -1,109 +0,0 @@ -use crate::backend::cynthion::{CynthionDevice, CynthionUsability::*, Speed}; -use crate::pcap::Writer; -use anyhow::Result; -use ctrlc; -use std::fs::File; -use std::io::{self, Write}; -use std::sync::{Arc, Mutex}; - -use argh::FromArgs; -#[derive(FromArgs, PartialEq, Debug)] -/// Start packetry in CLI mode -#[argh(subcommand, name = "capture")] -pub struct SubCommandCliCapture { - /// device (default: auto) - #[argh( - option, - short = 'd', - long = "device", - default = "String::from(\"auto\")" - )] - device_serial: String, - /// usb speed (default: low) - #[argh(option, short = 's', long = "speed", default = "Speed::Low")] - usb_speed: Speed, - /// output file (use '-' for stdout, default: cynthion.pcap) - #[argh( - option, - short = 'o', - long = "output", - default = "String::from(\"cynthion.pcap\")" - )] - output_file: String, -} - -pub fn headless_capture(options: SubCommandCliCapture) -> Result<()> { - let device = select_device(&options.device_serial, options.usb_speed)?; - - let output_writer: Box = if options.output_file == "-" { - Box::new(io::stdout()) - } else { - Box::new(File::create(&options.output_file)?) - }; - let mut writer = Writer::open(output_writer)?; - - let cynthion = device.open()?; - let (stream_handle, stop_handle) = - cynthion.start(options.usb_speed, |e| eprintln!("{:?}", e))?; - - let stop_handle = Arc::new(Mutex::new(Some(stop_handle))); - ctrlc::set_handler(move || { - let mut handle_opt = stop_handle.lock().unwrap(); - if let Some(handle) = handle_opt.take() { - if let Err(e) = handle.stop() { - eprintln!("Failed to stop Cynthion: {}", e); - } - } - }) - .expect("Error setting CTRL+C handler"); - - for packet in stream_handle { - writer.add_packet(&packet.bytes, packet.timestamp_ns)?; - } - - writer.close()?; - - Ok(()) -} - -fn select_device(serial: &str, speed: Speed) -> Result { - let mut devices = CynthionDevice::scan()?; - - if devices.is_empty() { - return Err(anyhow::anyhow!("No devices found")); - } - - let device_index = if serial == "auto" { - // Select the first device - Some(0) - } else { - // Find the device with the requested serial number - devices - .iter() - .position(|d| d.device_info.serial_number() == Some(&serial.to_string())) - }; - - // Check if the device was found - let device_index = match device_index { - Some(index) => index, - None => return Err(anyhow::anyhow!("Device with serial {} not found", serial)), - }; - - // Remove the device from the list to get ownership - let device = devices.remove(device_index); - - // Check if the device is usable and supports the requested speed - match &device.usability { - Usable(_, speeds) => { - if !speeds.contains(&speed) { - return Err(anyhow::anyhow!("Device does not support speed {:?}", speed)); - } - } - - Unusable(reason) => { - return Err(anyhow::anyhow!("Device is not usable: {}", reason)); - } - } - - Ok(device) -} diff --git a/src/devices.rs b/src/devices.rs deleted file mode 100644 index b338c58b..00000000 --- a/src/devices.rs +++ /dev/null @@ -1,67 +0,0 @@ -use crate::backend::cynthion::{CynthionDevice, CynthionUsability::*}; -use anyhow::Result; -use tabled::{Table, Tabled}; - -#[derive(Tabled)] -pub struct DeviceInfo { - name: String, - serial: String, - useable: String, - bus: String, - address: String, - speeds: String, -} - -pub fn list_devices() -> Result<()> { - let devices = CynthionDevice::scan()?; - - if devices.is_empty() { - println!("No devices found."); - return Ok(()); - } - - let device_table: Vec = devices - .iter() - .map(|device| { - let info = &device.device_info; - - // Maybe in the future there are more devices to support. Hardcode the name for now. - let name = "Cynthion".to_string(); - - let serial = info - .serial_number() - .map_or("None".to_string(), |s| s.to_string()); - let bus = info.bus_number().to_string(); - let address = info.device_address().to_string(); - - let (useable, speeds) = match &device.usability { - Usable(_, speeds) => ( - "Yes".to_string(), - speeds - .iter() - .map(|speed| { - let desc = speed.description(); - desc.split(' ').next().unwrap_or("").to_string() - }) - .collect::>() - .join(", "), - ), - Unusable(reason) => (reason.to_string(), String::new()), - }; - - DeviceInfo { - name, - serial, - useable, - bus, - address, - speeds, - } - }) - .collect(); - - let table = Table::new(device_table).to_string(); - println!("{}", table); - - Ok(()) -} diff --git a/src/main.rs b/src/main.rs index 0c184a23..347c753d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,25 +6,23 @@ extern crate bitfield; // We need the ctor macro for the replay test on macOS. -#[cfg(all(test, target_os = "macos"))] +#[cfg(all(test, target_os="macos"))] #[allow(unused_imports)] #[macro_use] extern crate ctor; // Include build-time info. pub mod built { - // The file has been placed there by the build script. - include!(concat!(env!("OUT_DIR"), "/built.rs")); + // The file has been placed there by the build script. + include!(concat!(env!("OUT_DIR"), "/built.rs")); } // Declare all modules used. mod backend; mod capture; -mod cli_capture; mod compact_index; mod data_stream; mod decoder; -mod devices; mod expander; mod id; mod index_stream; @@ -42,61 +40,25 @@ mod vec_map; mod version; // Declare optional modules. -#[cfg(any(test, feature = "record-ui-test"))] +#[cfg(any(test, feature="record-ui-test"))] mod record_ui; #[cfg(test)] mod test_replay; -use gtk::gio::ApplicationFlags; use gtk::prelude::*; - -use cli_capture::{headless_capture, SubCommandCliCapture}; -use devices::list_devices; -use ui::{activate, display_error, open, stop_cynthion}; +use gtk::gio::ApplicationFlags; +use gtk::glib::{self, OptionArg, OptionFlags}; + +use ui::{ + activate, + display_error, + open, + stop_cynthion +}; use version::{version, version_info}; -use argh::FromArgs; -#[derive(FromArgs, PartialEq, Debug)] -/// packetry - a fast, intuitive USB 2.0 protocol analysis application for use with Cynthion -struct Args { - #[argh(subcommand)] - sub_commands: Option, - - /// recording (pcap) to open in the GUI - #[argh(positional)] - filename: Option, -} - -#[derive(FromArgs, PartialEq, Debug)] -#[argh(subcommand)] -enum SubcommandEnum { - One(SubCommandCliCapture), - Two(SubCommandDevices), - Three(SubCommandVersion), - Four(SubCommandTest), -} - -#[derive(FromArgs, PartialEq, Debug)] -/// List capture devices -#[argh(subcommand, name = "devices")] -struct SubCommandDevices {} - -#[derive(FromArgs, PartialEq, Debug)] -/// Print version information -#[argh(subcommand, name = "version")] -struct SubCommandVersion { - /// print dependency information with version - #[argh(switch, long = "dependencies")] - print_dependencies: bool, -} - -#[derive(FromArgs, PartialEq, Debug)] -/// Test an attached Cynthion -#[argh(subcommand, name = "test-cynthion")] -struct SubCommandTest { - /// save the captures to disk - #[argh(switch, long = "save-captures")] - save_captures: bool, +fn have_argument(name: &str) -> bool { + std::env::args().any(|arg| arg == name) } fn main() { @@ -107,49 +69,35 @@ fn main() { unsafe { AttachConsole(ATTACH_PARENT_PROCESS) }; } - let args: Args = argh::from_env(); - - if let Some(subcmd) = args.sub_commands { - match subcmd { - SubcommandEnum::One(captureoptions) => { - if let Err(e) = headless_capture(captureoptions) { - eprintln!("Error capturing: {}", e); - } - } - - SubcommandEnum::Two(_) => { - if let Err(e) = list_devices() { - eprintln!("Error listing devices: {}", e); - } - } - - SubcommandEnum::Three(versionoptions) => { - println!( - "Packetry version {}\n\n{}", - version(), - version_info(versionoptions.print_dependencies) - ); - } - - SubcommandEnum::Four(testoptions) => { - test_cynthion::run_test(testoptions.save_captures); - } - } + if have_argument("--version") { + println!("Packetry version {}\n\n{}", + version(), + version_info(have_argument("--dependencies"))); + } else if have_argument("--test-cynthion") { + let save_captures = have_argument("--save-captures"); + test_cynthion::run_test(save_captures); } else { - // Start the GUI application as no subcommand was provided. - // Here, args[1] is the filename if one was provided. This should be added to the argh enum. - let application = gtk::Application::new( Some("com.greatscottgadgets.packetry"), - ApplicationFlags::NON_UNIQUE | ApplicationFlags::HANDLES_OPEN, + ApplicationFlags::NON_UNIQUE | + ApplicationFlags::HANDLES_OPEN ); - application.set_option_context_parameter_string(Some("[filename.pcap]")); + application.set_option_context_parameter_string( + Some("[filename.pcap]")); + application.add_main_option( + "version", glib::Char::from(0), + OptionFlags::NONE, OptionArg::None, + "Print version information", None); + application.add_main_option( + "test-cynthion", glib::Char::from(0), + OptionFlags::NONE, OptionArg::None, + "Test an attached Cynthion USB analyzer", None); application.connect_activate(|app| display_error(activate(app))); application.connect_open(|app, files, _hint| { - app.activate(); - if let Some(file) = files.first() { - display_error(open(file)); - } + app.activate(); + if let Some(file) = files.first() { + display_error(open(file)); + } }); application.run(); display_error(stop_cynthion()); From 94bee135aaddf1573b3c4f0469eb9846f6de14ff Mon Sep 17 00:00:00 2001 From: manu Date: Sat, 10 Aug 2024 22:09:34 +0200 Subject: [PATCH 09/10] removed open option --- src/cli.rs | 62 +++++------------------------------------------------- 1 file changed, 5 insertions(+), 57 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 635c00ca..84a8e855 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,14 +1,5 @@ -use std::process::{Command, ExitCode}; +use std::process::ExitCode; -#[cfg(unix)] -use std::os::unix::process::ExitStatusExt; - -#[cfg(windows)] -const MAIN_BINARY: &str = "packetry.exe"; -#[cfg(not(windows))] -const MAIN_BINARY: &str = "packetry"; - -// We need the bitfield macro. #[macro_use] extern crate bitfield; @@ -36,17 +27,8 @@ struct Args { #[derive(FromArgs, PartialEq, Debug)] #[argh(subcommand)] enum SubcommandEnum { - One(SubCommandOpenFileInGui), - Two(SubCommandCliCapture), - Three(SubCommandDevices), -} -#[derive(FromArgs, PartialEq, Debug)] -/// Opens a recording in the GUI -#[argh(subcommand, name = "open")] -struct SubCommandOpenFileInGui { - /// recording (pcap) to open in the GUI - #[argh(positional)] - filename: String, + One(SubCommandCliCapture), + Two(SubCommandDevices), } #[derive(FromArgs, PartialEq, Debug)] @@ -94,41 +76,7 @@ fn main() -> ExitCode { let exit_code = if let Some(subcmd) = args.sub_commands { match subcmd { - SubcommandEnum::One(file) => { - // Find the main Packetry executable. - let packetry_binary_path = std::env::current_exe() - .expect("Failed to find path to current executable") - .parent() - .expect("Failed to find parent directory of current executable") - .join(MAIN_BINARY); - - // Prepare to call it, passing through all arguments we were passed. - let mut command = Command::new(packetry_binary_path); - command.args(&[&file.filename]); - - // If on Windows, tell the child that it needs to attach to our console. - #[cfg(windows)] - command.env("PACKETRY_ATTACH_CONSOLE", "1"); - - // Spawn the main binary as a child process, and wait for its exit status. - let exit_status = command - .status() - .expect("Failed to start main packetry binary"); - - // Try to exit with the same code the child did. - match exit_status.code() { - Some(code) => ExitCode::from(code as u8), - None => { - #[cfg(unix)] - if let Some(signal) = exit_status.signal() { - panic!("Packetry was terminated by signal {signal}"); - } - panic!("Packetry was terminated without an exit code"); - } - } - } - - SubcommandEnum::Two(captureoptions) => { + SubcommandEnum::One(captureoptions) => { if let Err(e) = headless_capture(captureoptions) { eprintln!("Error capturing packets: {}", e); ExitCode::FAILURE @@ -137,7 +85,7 @@ fn main() -> ExitCode { } } - SubcommandEnum::Three(_) => { + SubcommandEnum::Two(_) => { if let Err(e) = list_devices() { eprintln!("Error listing devices: {}", e); ExitCode::FAILURE From 820991a1a9495f84ccf0674a7869c88ab0a72b1f Mon Sep 17 00:00:00 2001 From: manu Date: Sat, 10 Aug 2024 22:17:01 +0200 Subject: [PATCH 10/10] added version back to packetry-cli --- src/cli.rs | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/cli.rs b/src/cli.rs index 84a8e855..ba28b654 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -5,10 +5,17 @@ extern crate bitfield; mod backend; mod pcap; +mod version; use crate::backend::cynthion::{CynthionDevice, CynthionUsability::*, Speed}; - use crate::pcap::Writer; +use version::{version, version_info}; + +// Include build-time info. +pub mod built { + // The file has been placed there by the build script. + include!(concat!(env!("OUT_DIR"), "/built.rs")); +} use anyhow::Error; use std::fs::File; @@ -29,6 +36,7 @@ struct Args { enum SubcommandEnum { One(SubCommandCliCapture), Two(SubCommandDevices), + Three(SubCommandVersion), } #[derive(FromArgs, PartialEq, Debug)] @@ -61,6 +69,15 @@ struct SubCommandCliCapture { #[argh(subcommand, name = "devices")] struct SubCommandDevices {} +#[derive(FromArgs, PartialEq, Debug)] +/// Print version information +#[argh(subcommand, name = "version")] +struct SubCommandVersion { + /// print dependency information with version + #[argh(switch, long = "dependencies")] + print_dependencies: bool, +} + #[derive(Tabled)] pub struct DeviceInfo { name: String, @@ -93,6 +110,13 @@ fn main() -> ExitCode { ExitCode::SUCCESS } } + + SubcommandEnum::Three(versionoptions) => { + println!("Packetry version {}\n\n{}", + version(), + version_info(versionoptions.print_dependencies)); + ExitCode::SUCCESS + } } } else { ExitCode::SUCCESS