diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 488c328..7eb9df9 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -15,7 +15,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Install - run: sudo apt install socat + run: sudo apt install socat libudev-dev - name: Build run: cargo build --release - name: Run tests diff --git a/Cargo.lock b/Cargo.lock index 6113580..2f61e1d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,29 +12,39 @@ dependencies = [ "log", "rppal", "serde", + "serialport", "simplelog", + "strum", "subprocess", + "test-case", "toml", + "zip", ] [[package]] -name = "aho-corasick" -version = "0.7.18" +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aes" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" dependencies = [ - "memchr", + "cfg-if", + "cipher", + "cpufeatures", ] [[package]] -name = "atty" -version = "0.2.14" +name = "aho-corasick" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ - "hermit-abi", - "libc", - "winapi", + "memchr", ] [[package]] @@ -43,11 +53,69 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bzip2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "cc" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "jobserver", + "libc", +] [[package]] name = "cfg-if" @@ -55,29 +123,108 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "cpufeatures" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +dependencies = [ + "libc", +] + [[package]] name = "crc" -version = "3.0.0" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53757d12b596c16c78b83458d732a5d1a17ab3f53f2f7412f6fb57cc8a140ab3" +checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" dependencies = [ "crc-catalog", ] [[package]] name = "crc-catalog" -version = "2.1.0" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc32fast" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d0165d2900ae6778e36e80bbc4da3b5eefccee9ba939761f9c2882a5d9af3ff" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d96137f14f244c37f989d9fff8f95e6c18b918e71f36638f8c49112e4c78f" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "deranged" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] [[package]] name = "env_logger" -version = "0.9.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" dependencies = [ - "atty", "humantime", + "is-terminal", "log", "regex", "termcolor", @@ -85,15 +232,25 @@ dependencies = [ [[package]] name = "equivalent" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] [[package]] name = "file-per-thread-logger" -version = "0.1.5" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e16290574b39ee41c71aeb90ae960c504ebaf1e2a1c87bd52aa56ed6e1a02f" +checksum = "8a3cc21c33af89af0930c8cae4ade5e6fdc17b5d2c97b3d2e2edb67a1cf683f3" dependencies = [ "env_logger", "log", @@ -107,19 +264,51 @@ dependencies = [ "serde", ] +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.1.19" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + +[[package]] +name = "hmac" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "libc", + "digest", ] [[package]] @@ -130,52 +319,137 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "indexmap" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", "hashbrown", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "io-kit-sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4769cb30e5dcf1710fc6730d3e94f78c47723a014a567de385e113c737394640" +dependencies = [ + "core-foundation-sys", + "mach2", +] + +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi", + "rustix", + "windows-sys 0.48.0", +] + [[package]] name = "itoa" -version = "1.0.3" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] -name = "lazy_static" -version = "1.4.0" +name = "jobserver" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +dependencies = [ + "libc", +] [[package]] name = "libc" -version = "0.2.123" +version = "0.2.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" + +[[package]] +name = "libudev" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb691a747a7ab48abc15c5b42066eaafde10dc427e3b6ee2a1cf43db04c763bd" +checksum = "78b324152da65df7bb95acfcaab55e3097ceaab02fb19b228a9eb74d55f135e0" +dependencies = [ + "libc", + "libudev-sys", +] + +[[package]] +name = "libudev-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "log" -version = "0.4.16" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "mach2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" +checksum = "6d0d1830bcd151a6fc4aea1369af235b36c1528fe976b8ff678683c9995eade8" dependencies = [ - "cfg-if", + "libc", ] [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "nix" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", +] [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] @@ -189,35 +463,88 @@ dependencies = [ "libc", ] +[[package]] +name = "password-hash" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + [[package]] name = "paste" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest", + "hmac", + "password-hash", + "sha2", +] + +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.31" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + [[package]] name = "regex" -version = "1.6.0" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick", "memchr", @@ -226,15 +553,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.27" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "rmp" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44519172358fd6d58656c86ab8e7fbc9e1490c3e8f14d35ed78ca0dd07403c9f" +checksum = "7f9860a6cc38ed1da53456442089b4dfa35e7cedaa326df63017af88385e6b20" dependencies = [ "byteorder", "num-traits", @@ -243,9 +570,9 @@ dependencies = [ [[package]] name = "rmp-serde" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5b13be192e0220b8afb7222aa5813cb62cc269ebb5cac346ca6487681d2913e" +checksum = "bffea85eea980d8a74453e5d02a8d93028f3c34725de143085a844ebe953258a" dependencies = [ "byteorder", "rmp", @@ -254,28 +581,52 @@ dependencies = [ [[package]] name = "rppal" -version = "0.13.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c88c9c6248de4d337747b619d8f671055ef48a87dc21b97998833f189a0bbd4f" +checksum = "23aff72e177b0e0b0e0801bbe9f41de2abafd396dfb69af9c602ffba8c2091b3" dependencies = [ - "lazy_static", "libc", ] +[[package]] +name = "rustix" +version = "0.38.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +dependencies = [ + "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 = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "serde" -version = "1.0.173" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91f70896d6720bc714a4a57d22fc91f1db634680e65c8efe13323f1fa38d53f" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.173" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6250dde8342e0232232be9ca3db7aa40aceb5a3e5dd9bddbc00d99a007cde49" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", @@ -284,39 +635,108 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80" dependencies = [ "serde", ] +[[package]] +name = "serialport" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f5a15d0be940df84846264b09b51b10b931fb2f275becb80934e3568a016828" +dependencies = [ + "bitflags 2.4.1", + "cfg-if", + "core-foundation-sys", + "io-kit-sys", + "libudev", + "mach2", + "nix", + "regex", + "scopeguard", + "unescaper", + "winapi", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "simplelog" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48dfff04aade74dd495b007c831cd6f4e0cee19c344dd9dc0884c0289b70a786" +checksum = "acee08041c5de3d5048c8b3f6f13fafb3026b24ba43c6a695a0c76179b844369" dependencies = [ "log", "termcolor", "time", ] +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "subprocess" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "055cf3ebc2981ad8f0a5a17ef6652f652d87831f79fddcba2ac57bcb9a0aa407" +checksum = "0c2e86926081dda636c546d8c5e641661049d7562a68f5488be4a1f7f66f6086" dependencies = [ "libc", "winapi", ] +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "syn" -version = "2.0.26" +version = "2.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970" +checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" dependencies = [ "proc-macro2", "quote", @@ -332,29 +752,95 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "test-case" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8" +dependencies = [ + "test-case-macros", +] + +[[package]] +name = "test-case-core" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "test-case-macros" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "test-case-core", +] + +[[package]] +name = "thiserror" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "time" -version = "0.3.14" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3f9a28b618c3a6b9251b6908e9c99e04b9e5c02e6581ccbb67d59c34ef7f9b" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" dependencies = [ + "deranged", "itoa", "libc", "num_threads", + "powerfmt", + "serde", + "time-core", "time-macros", ] +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + [[package]] name = "time-macros" -version = "0.2.4" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +dependencies = [ + "time-core", +] [[package]] name = "toml" -version = "0.7.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" +checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" dependencies = [ "serde", "serde_spanned", @@ -364,18 +850,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.19.14" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" dependencies = [ "indexmap", "serde", @@ -384,11 +870,32 @@ dependencies = [ "winnow", ] +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unescaper" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8f0f68e58d297ba8b22b8b5a96a87b863ba6bb46aaf51e19a4b02c5a6dd5b7f" +dependencies = [ + "thiserror", +] + [[package]] name = "unicode-ident" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "winapi" @@ -408,9 +915,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] @@ -421,11 +928,192 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "winnow" -version = "0.5.0" +version = "0.5.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fac9742fd1ad1bd9643b991319f72dd031016d44b77039a26977eb667141e7" +checksum = "6c830786f7720c2fd27a1a0e27a709dbd3c4d009b56d098fc742d4f4eab91fe2" dependencies = [ "memchr", ] + +[[package]] +name = "zip" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +dependencies = [ + "aes", + "byteorder", + "bzip2", + "constant_time_eq", + "crc32fast", + "crossbeam-utils", + "flate2", + "hmac", + "pbkdf2", + "sha1", + "time", + "zstd", +] + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.9+zstd.1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index dbacd6a..0304230 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,9 +11,12 @@ log = "*" simplelog = "^0.12.0" subprocess = "0.2.8" crc = "3.0.0" -rppal = "^0.13.1" -toml = "0.7.6" +toml = "0.8.8" +rppal = "0.16.0" serde = { version = "1.0.166", features = ["derive"] } +strum = { version = "0.25.0", features = ["derive"] } +serialport = "4.2.2" +test-case = "3.3.1" [dependencies.filevec] path = "lib/filevec" @@ -24,3 +27,4 @@ rpi = [] [dev-dependencies] file-per-thread-logger = "*" +zip = "0.6.6" diff --git a/Makefile b/Makefile index c80ec69..3019758 100644 --- a/Makefile +++ b/Makefile @@ -1,15 +1,15 @@ # Prerequisites 'rustup component add llvm-tools-preview' and 'cargo install grcov' build_with_cov: - RUSTFLAGS="-Cinstrument-coverage" cargo build + RUSTFLAGS="-Cinstrument-coverage" cargo build --release --features mock coverage: build_with_cov - RUSTFLAGS="-Cinstrument-coverage" LLVM_PROFILE_FILE="STS1-%p-%m.profraw" cargo test --features mock - grcov . -s . --binary-path ./target/debug/ -t html --branch --ignore-not-existing -o ./target/debug/coverage/ - firefox ./target/debug/coverage/index.html& + LLVM_PROFILE_FILE="STS1-%p-%m.profraw" cargo test --features mock --release + grcov . -s . --binary-path ./target/release/ -t html --branch --ignore-not-existing -o ./target/release/coverage/ + firefox ./target/release/coverage/index.html& sw_test: - RUST_LOG=info cargo test --release --features mock + cargo build --release && RUST_LOG=info cargo test --release --features mock packs: cargo test build_pack --features rpi @@ -24,4 +24,4 @@ clean: rebuild_student_archive: rm -f tests/student_program.zip - cd tests/test_data; zip -r ../student_program.zip * \ No newline at end of file + cd tests/test_data; zip -r ../student_program.zip * diff --git a/config.toml b/config.toml index 7bb1c24..d72a478 100644 --- a/config.toml +++ b/config.toml @@ -3,4 +3,3 @@ baudrate = 921600 heartbeat_pin = 34 update_pin = 35 heartbeat_freq = 10 # Hz -log_path = "./log" \ No newline at end of file diff --git a/src/command/common.rs b/src/command/common.rs index b34023c..f512657 100644 --- a/src/command/common.rs +++ b/src/command/common.rs @@ -1,13 +1,16 @@ -use std::time::Duration; - use super::{CommandError, CommandResult, SyncExecutionContext}; +use crate::communication::{CEPPacket, CommunicationHandle}; +use std::time::Duration; -pub const COM_TIMEOUT_DURATION: std::time::Duration = std::time::Duration::new(2, 0); - -pub fn check_length(vec: &Vec, n: usize) -> Result<(), CommandError> { +pub fn check_length( + com: &mut impl CommunicationHandle, + vec: &Vec, + n: usize, +) -> Result<(), CommandError> { let actual_len = vec.len(); if actual_len != n { log::error!("Command came with {actual_len} bytes, should have {n}"); + com.send_packet(&CEPPacket::Nack)?; Err(CommandError::ProtocolViolation( format!("Received command with {actual_len} bytes, expected {n}").into(), )) diff --git a/src/command/error.rs b/src/command/error.rs index a53913c..74b2085 100644 --- a/src/command/error.rs +++ b/src/command/error.rs @@ -25,10 +25,11 @@ impl From for CommandError { fn from(e: CommunicationError) -> Self { match e { CommunicationError::PacketInvalidError => CommandError::External(Box::new(e)), - CommunicationError::CRCError => CommandError::ProtocolViolation(Box::new(e)), - CommunicationError::InterfaceError => CommandError::NonRecoverable(Box::new(e)), - CommunicationError::STOPCondition => CommandError::External(Box::new(e)), - CommunicationError::TimeoutError => todo!("Timeout not yet specified"), + CommunicationError::CepParsing(_) => CommandError::ProtocolViolation(Box::new(e)), + CommunicationError::Io(_) => CommandError::NonRecoverable(Box::new(e)), + CommunicationError::StopCondition => CommandError::External(Box::new(e)), + CommunicationError::NotAcknowledged => CommandError::ProtocolViolation(Box::new(e)), + CommunicationError::TimedOut => todo!("Timeout not yet specified"), } } } diff --git a/src/command/execute_program.rs b/src/command/execute_program.rs index 127d3ec..1d3c832 100644 --- a/src/command/execute_program.rs +++ b/src/command/execute_program.rs @@ -19,8 +19,7 @@ pub fn execute_program( com: &mut impl CommunicationHandle, exec: &mut SyncExecutionContext, ) -> CommandResult { - check_length(&data, 9)?; - com.send_packet(CEPPacket::ACK)?; + check_length(com, &data, 9)?; let program_id = u16::from_le_bytes([data[1], data[2]]); let timestamp = u32::from_le_bytes([data[3], data[4], data[5], data[6]]); @@ -29,7 +28,13 @@ pub fn execute_program( terminate_student_program(exec).expect("to terminate a running program"); - let student_process = create_student_process(program_id, timestamp)?; + let student_process = match create_student_process(program_id, timestamp) { + Ok(p) => p, + Err(e) => { + com.send_packet(&CEPPacket::Nack)?; + return Err(e); + } + }; // WATCHDOG THREAD let mut wd_context = exec.clone(); @@ -55,7 +60,7 @@ pub fn execute_program( l_context.running_flag = true; drop(l_context); - com.send_packet(CEPPacket::ACK)?; + com.send_packet(&CEPPacket::Ack)?; Ok(()) } diff --git a/src/command/execution_context.rs b/src/command/execution_context.rs index 0e234fc..a2e6197 100644 --- a/src/command/execution_context.rs +++ b/src/command/execution_context.rs @@ -23,7 +23,10 @@ pub struct ExecutionContext { } impl ExecutionContext { - pub fn new(event_file_path: String, update_pin: u8) -> Result { + pub fn new( + event_file_path: String, + update_pin: u8, + ) -> Result>, std::io::Error> { let mut ec = ExecutionContext { thread_handle: None, running_flag: false, @@ -33,7 +36,7 @@ impl ExecutionContext { ec.check_update_pin(); - Ok(ec) + Ok(Arc::new(Mutex::new(ec))) } /// Checks and sets/resets the update pin accordingly @@ -85,7 +88,7 @@ pub struct UpdatePin { #[cfg(feature = "mock")] impl UpdatePin { - pub fn new(pin: u8) -> Self { + pub fn new(_pin: u8) -> Self { let update_pin = UpdatePin { pin: false }; return update_pin; } diff --git a/src/command/get_status.rs b/src/command/get_status.rs index 0175d6d..973fec1 100644 --- a/src/command/get_status.rs +++ b/src/command/get_status.rs @@ -9,12 +9,11 @@ pub fn get_status( com: &mut impl CommunicationHandle, exec: &mut SyncExecutionContext, ) -> CommandResult { - check_length(&data, 1)?; - com.send_packet(CEPPacket::ACK)?; + check_length(com, &data, 1)?; let mut l_exec = exec.lock().unwrap(); if !l_exec.has_data_ready() { - com.send_packet(CEPPacket::DATA(vec![0]))?; + com.send_packet(&CEPPacket::Data(vec![0]))?; return Ok(()); } @@ -22,11 +21,11 @@ pub fn get_status( l_exec.event_vec.as_ref().iter().position(|x| matches!(x, Event::Status(_))) { let event = l_exec.event_vec[index]; - com.send_packet(CEPPacket::DATA(event.to_bytes()))?; + com.send_packet(&CEPPacket::Data(event.to_bytes()))?; l_exec.event_vec.remove(index)?; } else { let event = *l_exec.event_vec.as_ref().last().unwrap(); // Safe, because we know it is not empty - com.send_packet(CEPPacket::DATA(event.to_bytes()))?; + com.send_packet(&CEPPacket::Data(event.to_bytes()))?; if !matches!(event, Event::Result(_)) { // Results are removed when deleted diff --git a/src/command/mod.rs b/src/command/mod.rs index 9df9bc4..28e5675 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -1,5 +1,4 @@ use crate::communication::{CEPPacket, CommunicationHandle}; -use std::time::Duration; mod common; pub use common::*; @@ -33,11 +32,10 @@ pub fn handle_command(com: &mut impl CommunicationHandle, exec: &mut SyncExecuti Err(CommandError::NonRecoverable(e)) => { log::error!("Non-Recoverable error: {e}"); - panic!("Aborting now"); + panic!("Aborting now {e:?}"); } Err(CommandError::ProtocolViolation(e)) => { log::error!("Protocol Violation: {e}"); - com.send_packet(CEPPacket::NACK).unwrap(); } Err(CommandError::External(e)) => { log::error!("External error: {e}"); @@ -49,12 +47,12 @@ pub fn process_command( com: &mut impl CommunicationHandle, exec: &mut SyncExecutionContext, ) -> CommandResult { - let packet = com.receive_packet(&Duration::MAX)?; + let packet = com.receive_packet()?; let data = match packet { - CEPPacket::DATA(data) => data, + CEPPacket::Data(data) => data, _ => { return Err(CommandError::NonRecoverable( - format!("Received {:?} as command start, expected DATA", packet).into(), + format!("Received {:?} as command start, expected Data", packet).into(), )); } }; diff --git a/src/command/return_result.rs b/src/command/return_result.rs index 31b3c57..109a886 100644 --- a/src/command/return_result.rs +++ b/src/command/return_result.rs @@ -1,25 +1,27 @@ +use std::time::Duration; + use crate::{ - command::{check_length, CommandError, Event, ResultId, COM_TIMEOUT_DURATION}, + command::{check_length, CommandError, Event, ResultId}, communication::{CEPPacket, CommunicationHandle}, }; use super::{truncate_to_size, CommandResult, SyncExecutionContext}; -/// Handles a complete return result command. The result zip file is only deleted if a final ACK is +/// Handles a complete return result command. The result zip file is only deleted if a final Ack is /// received. pub fn return_result( data: Vec, com: &mut impl CommunicationHandle, exec: &mut SyncExecutionContext, ) -> CommandResult { - check_length(&data, 7)?; - com.send_packet(CEPPacket::ACK)?; + check_length(com, &data, 7)?; let program_id = u16::from_le_bytes([data[1], data[2]]); let timestamp = u32::from_le_bytes([data[3], data[4], data[5], data[6]]); let result_path = format!("./data/{}_{}.zip", program_id, timestamp); if !std::path::Path::new(&result_path).exists() { + com.send_packet(&CEPPacket::Nack)?; return Err(CommandError::ProtocolViolation( format!("Result {}:{} does not exist", program_id, timestamp).into(), )); @@ -27,22 +29,17 @@ pub fn return_result( let bytes = std::fs::read(result_path)?; log::info!("Returning result for {}:{}", program_id, timestamp); - com.send_multi_packet(bytes, &COM_TIMEOUT_DURATION)?; - - let response = com.receive_packet(&COM_TIMEOUT_DURATION)?; - if response == CEPPacket::ACK { - let result_id = ResultId { program_id, timestamp }; - delete_result(result_id)?; - - let mut l_exec = exec.lock().unwrap(); - let event_index = - l_exec.event_vec.as_ref().iter().position(|x| x == &Event::Result(result_id)).unwrap(); - l_exec.event_vec.remove(event_index)?; - l_exec.check_update_pin(); - drop(l_exec); - } else { - log::error!("COBC did not acknowledge result"); - } + com.send_multi_packet(&bytes)?; + + com.await_ack(&Duration::from_secs(1))?; + let result_id = ResultId { program_id, timestamp }; + delete_result(result_id)?; + + let mut l_exec = exec.lock().unwrap(); + let event_index = + l_exec.event_vec.as_ref().iter().position(|x| x == &Event::Result(result_id)).unwrap(); + l_exec.event_vec.remove(event_index)?; + l_exec.check_update_pin(); Ok(()) } diff --git a/src/command/stop_program.rs b/src/command/stop_program.rs index 3899fe6..b2856ee 100644 --- a/src/command/stop_program.rs +++ b/src/command/stop_program.rs @@ -8,11 +8,10 @@ pub fn stop_program( com: &mut impl CommunicationHandle, exec: &mut SyncExecutionContext, ) -> CommandResult { - check_length(&data, 1)?; - com.send_packet(CEPPacket::ACK)?; + check_length(com, &data, 1)?; terminate_student_program(exec).expect("to terminate student program"); - com.send_packet(CEPPacket::ACK)?; + com.send_packet(&CEPPacket::Ack)?; Ok(()) } diff --git a/src/command/store_archive.rs b/src/command/store_archive.rs index 3e93d61..fac5ea1 100644 --- a/src/command/store_archive.rs +++ b/src/command/store_archive.rs @@ -2,7 +2,7 @@ use std::{io::Write, process::Command}; use super::{CommandError, CommandResult, SyncExecutionContext}; use crate::{ - command::{check_length, COM_TIMEOUT_DURATION}, + command::check_length, communication::{CEPPacket, CommunicationHandle}, }; @@ -12,16 +12,15 @@ pub fn store_archive( com: &mut impl CommunicationHandle, _exec: &mut SyncExecutionContext, ) -> CommandResult { - check_length(&data, 3)?; - com.send_packet(CEPPacket::ACK)?; + check_length(com, &data, 3)?; let id = u16::from_le_bytes([data[1], data[2]]).to_string(); log::info!("Storing Archive {}", id); - let bytes = com.receive_multi_packet(&COM_TIMEOUT_DURATION, || false)?; // !! TODO !! + let bytes = com.receive_multi_packet(|| false)?; // !! TODO !! unpack_archive(id, bytes)?; - com.send_packet(CEPPacket::ACK)?; + com.send_packet(&CEPPacket::Ack)?; Ok(()) } diff --git a/src/command/update_time.rs b/src/command/update_time.rs index 8ac08b0..14e5f27 100644 --- a/src/command/update_time.rs +++ b/src/command/update_time.rs @@ -10,13 +10,12 @@ pub fn update_time( com: &mut impl CommunicationHandle, _exec: &mut SyncExecutionContext, ) -> CommandResult { - check_length(&data, 5)?; - com.send_packet(CEPPacket::ACK)?; + check_length(com, &data, 5)?; let time = i32::from_le_bytes([data[1], data[2], data[3], data[4]]); set_system_time(time)?; - com.send_packet(CEPPacket::ACK)?; + com.send_packet(&CEPPacket::Ack)?; Ok(()) } diff --git a/src/communication/cep.rs b/src/communication/cep.rs index 9a06628..3e0c69c 100644 --- a/src/communication/cep.rs +++ b/src/communication/cep.rs @@ -1,37 +1,182 @@ +use std::io::Read; + use crc::{Crc, CRC_32_MPEG_2}; #[derive(Debug, Clone, PartialEq, Eq)] pub enum CEPPacket { - ACK, - NACK, - STOP, - EOF, - DATA(Vec), + Ack, + Nack, + Stop, + Eof, + Data(Vec), +} + +#[derive(Clone, Copy, strum::FromRepr)] +pub enum CEPPacketHeader { + Ack = 0xd7, + Nack = 0x27, + Stop = 0xb4, + Eof = 0x59, + Data = 0x8b, } impl CEPPacket { + pub const MAXIMUM_DATA_LENGTH: usize = 11000; + pub const MAXIMUM_PACKET_LENGTH: usize = 7 + Self::MAXIMUM_DATA_LENGTH; + const CRC: Crc = Crc::::new(&CRC_32_MPEG_2); - /// This function constructs a byte array, containing the raw bytes that can be sent + /// Calculates the CRC32 MPEG-2 checksum for the contained data. For variants other than Self::Data, 0 is returned + pub fn checksum(&self) -> u32 { + if let Self::Data(data) = self { + Self::CRC.checksum(data) + } else { + 0 + } + } + pub fn serialize(self) -> Vec { + let header = self.header(); match self { - CEPPacket::ACK => vec![0xd7], - CEPPacket::NACK => vec![0x27], - CEPPacket::STOP => vec![0xb4], - CEPPacket::EOF => vec![0x59], - CEPPacket::DATA(bytes) => { - let mut v = vec![0x8b]; + CEPPacket::Data(bytes) => { + let mut v = Vec::with_capacity(7 + bytes.len()); let crc32 = CEPPacket::CRC.checksum(&bytes); - v.reserve_exact(6 + bytes.len()); + v.push(header); v.extend((bytes.len() as u16).to_le_bytes()); v.extend(bytes); v.extend(crc32.to_le_bytes()); v } + _ => vec![header], } } - pub fn check(data: &Vec, checksum: u32) -> bool { + pub fn crc_is_valid(data: &[u8], checksum: u32) -> bool { CEPPacket::CRC.checksum(data) == checksum } + + pub const fn header(&self) -> u8 { + let header = match self { + CEPPacket::Ack => CEPPacketHeader::Ack, + CEPPacket::Nack => CEPPacketHeader::Nack, + CEPPacket::Stop => CEPPacketHeader::Stop, + CEPPacket::Eof => CEPPacketHeader::Eof, + CEPPacket::Data(_) => CEPPacketHeader::Data, + }; + header as u8 + } + + pub fn try_from_read(reader: &mut (impl Read + ?Sized)) -> Result { + let mut header_buffer = [0; 1]; + reader.read_exact(&mut header_buffer)?; + + let header = CEPPacketHeader::from_repr(header_buffer[0] as usize) + .ok_or(CEPParseError::InvalidLength)?; + let packet = match header { + CEPPacketHeader::Ack => CEPPacket::Ack, + CEPPacketHeader::Nack => CEPPacket::Nack, + CEPPacketHeader::Stop => CEPPacket::Stop, + CEPPacketHeader::Eof => CEPPacket::Eof, + CEPPacketHeader::Data => { + let mut length_buffer = [0; 2]; + reader.read_exact(&mut length_buffer)?; + let length = u16::from_le_bytes(length_buffer); + + if length as usize > Self::MAXIMUM_DATA_LENGTH { + return Err(CEPParseError::InvalidLength); + } + + let mut data_buffer = vec![0; length as usize]; + reader.read_exact(&mut data_buffer)?; + + let mut crc_buffer = [0; 4]; + reader.read_exact(&mut crc_buffer)?; + if !CEPPacket::crc_is_valid(&data_buffer, u32::from_le_bytes(crc_buffer)) { + return Err(CEPParseError::InvalidCRC); + } + + CEPPacket::Data(data_buffer) + } + }; + + Ok(packet) + } +} + +impl From<&CEPPacket> for Vec { + fn from(value: &CEPPacket) -> Self { + match value { + CEPPacket::Data(bytes) => { + let mut v = Vec::with_capacity(7 + bytes.len()); + v.push(value.header()); + let crc32 = CEPPacket::CRC.checksum(bytes); + v.extend((bytes.len() as u16).to_le_bytes()); + v.extend(bytes); + v.extend(crc32.to_le_bytes()); + v + } + _ => vec![value.header()], + } + } +} + +#[derive(Debug, strum::Display)] +pub enum CEPParseError { + InvalidLength, + InvalidHeader, + InvalidCRC, + Io(std::io::Error), +} + +impl std::error::Error for CEPParseError {} + +impl From for CEPParseError { + fn from(value: std::io::Error) -> Self { + CEPParseError::Io(value) + } +} + +impl TryFrom> for CEPPacket { + type Error = CEPParseError; + + fn try_from(value: Vec) -> Result { + Self::try_from_read(&mut std::io::Cursor::new(value)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use test_case::test_case; + + #[test_case(vec![0xD7], CEPPacket::Ack)] + #[test_case(vec![0x27], CEPPacket::Nack)] + #[test_case(vec![0x59], CEPPacket::Eof)] + #[test_case(vec![0xB4], CEPPacket::Stop)] + #[test_case(vec![0x8B, 0, 0, 0xff, 0xff, 0xff, 0xff], CEPPacket::Data(vec![]); "empty Data packet")] + #[test_case(vec![0x8B, 4, 0, 0x0a, 0x0b, 0x05, 0x73, 0x52, 0x27, 0x92, 0xf4], CEPPacket::Data(vec![0x0a, 0x0b, 0x05, 0x73]); "filled data packet")] + fn packet_is_parsed_and_serialized_correctly(vec: Vec, packet: CEPPacket) { + assert_eq!(&packet.clone().serialize(), &vec); + assert_eq!(CEPPacket::try_from(vec).unwrap(), packet); + } + + #[test] + fn invalid_crc_is_rejected() { + assert!( + matches!( + CEPPacket::try_from(vec![0x8B, 4, 0, 0x0a, 0x0b, 0x05, 0x74, 0x52, 0x27, 0x92, 0xf4]), + Err(CEPParseError::InvalidCRC) + ) + ) + } + + #[test] + fn invalid_length_is_rejected() { + assert!( + matches!( + CEPPacket::try_from(vec![0x8B, 0xff, 0xff]), + Err(CEPParseError::InvalidLength) + ) + ) + } } diff --git a/src/communication/communication.rs b/src/communication/communication.rs deleted file mode 100644 index 71c74ad..0000000 --- a/src/communication/communication.rs +++ /dev/null @@ -1,155 +0,0 @@ -use super::CEPPacket; - -pub type ComResult = Result; - -pub trait CommunicationHandle { - /// Sends the bytes to the COBC, packaged accordingly. This function shall block until all data - /// is sent. By returning a [`CommunicationError::InterfaceError`] it can signal that the underlying driver failed. - fn send(&mut self, bytes: Vec) -> ComResult<()>; - - /// Blocks until byte_count are received or the timeout is reached. A [`CommunicationError`] can signal that it failed - /// or timed out. - fn receive(&mut self, byte_count: u16, timeout: &std::time::Duration) -> ComResult>; - - /// Sends the supplied packet - fn send_packet(&mut self, p: CEPPacket) -> ComResult<()> { - if matches!(&p, CEPPacket::DATA(_)) { - loop { - self.send(p.clone().serialize())?; - match self.receive_packet(&std::time::Duration::MAX) { - Ok(CEPPacket::ACK) => break, - Ok(CEPPacket::NACK) => log::warn!("Received NACK after DATA, resending..."), - Ok(CEPPacket::STOP) => return Err(CommunicationError::STOPCondition), - Ok(_) => return Err(CommunicationError::PacketInvalidError), - Err(e) => return Err(e), - } - } - } else { - self.send(p.serialize())?; - } - - Ok(()) - } - - /// Blocks until it receives a CEPPacket - fn receive_packet(&mut self, timeout: &std::time::Duration) -> ComResult { - let pack = match self.receive(1, timeout)?[0] { - 0xd7 => CEPPacket::ACK, - 0x27 => CEPPacket::NACK, - 0xb4 => CEPPacket::STOP, - 0x59 => CEPPacket::EOF, - 0x8b => { - let length_field = self.receive(2, timeout)?; - let length = u16::from_le_bytes([length_field[0], length_field[1]]); - let bytes = self.receive(length, timeout)?; - let crc_field = self.receive(4, timeout)?; - let crc = - u32::from_le_bytes([crc_field[0], crc_field[1], crc_field[2], crc_field[3]]); - if !CEPPacket::check(&bytes, crc) { - return Err(CommunicationError::CRCError); - } else { - CEPPacket::DATA(bytes) - } - } - _ => { - return Err(CommunicationError::PacketInvalidError); - } - }; - - Ok(pack) - } - - /// Attempts to continously receive multidata packets and returns them in a concatenated byte vector - /// `stop_fn` is evaluated after every packet and terminates the communication with a STOP packet if true - /// An error is returned in this case - fn receive_multi_packet( - &mut self, - timeout: &std::time::Duration, - stop_fn: impl Fn() -> bool, - ) -> ComResult> { - let mut buffer = Vec::new(); - - loop { - let pack = self.receive_packet(timeout); - if stop_fn() { - self.send_packet(CEPPacket::STOP)?; - return Err(CommunicationError::STOPCondition); - } - - match pack { - Ok(CEPPacket::DATA(b)) => { - buffer.extend(b); - self.send_packet(CEPPacket::ACK)?; - } - Ok(CEPPacket::EOF) => { - break; - } - Ok(CEPPacket::STOP) => { - return Err(CommunicationError::STOPCondition); - } - Err(CommunicationError::InterfaceError) => { - return Err(CommunicationError::InterfaceError); - } - Err(CommunicationError::TimeoutError) => { - log::error!("Receive multipacket timed out"); - return Err(CommunicationError::TimeoutError); - } - e => { - log::error!("Received invalid data {:?}", e); - self.send_packet(CEPPacket::NACK)?; - } - }; - } - - self.send_packet(CEPPacket::ACK)?; - Ok(buffer) - } - - fn send_multi_packet( - &mut self, - bytes: Vec, - timeout: &std::time::Duration, - ) -> ComResult<()> { - let num_packets = bytes.len() / 32768 + 1; - let chunks: Vec<&[u8]> = bytes.chunks(32768).collect(); - - let mut i = 0; - loop { - if i == num_packets { - break; - } - - self.send_packet(CEPPacket::DATA(chunks[i].to_vec()))?; - i += 1; - } - - self.send_packet(CEPPacket::EOF)?; - if self.receive_packet(timeout)? != CEPPacket::ACK { - return Err(CommunicationError::PacketInvalidError); - } - - Ok(()) - } -} - -#[derive(Debug)] -pub enum CommunicationError { - /// Signals that an unknown command packet was received - PacketInvalidError, - /// Signals that the CRC checksum of a data packet was wrong - CRCError, - /// Signals that the underlying sending or receiving failed. Not recoverable on its own. - InterfaceError, - /// Signals that a multi packet receive or send was interrupted by a STOP condition - STOPCondition, - /// Signals that a receive timed out - TimeoutError, -} - -impl std::fmt::Display for CommunicationError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self) - } -} - -impl std::error::Error for CommunicationError {} diff --git a/src/communication/mod.rs b/src/communication/mod.rs index 8eb1ce0..f85c060 100644 --- a/src/communication/mod.rs +++ b/src/communication/mod.rs @@ -1,7 +1,306 @@ mod cep; -mod communication; -mod uart; - pub use cep::CEPPacket; -pub use communication::*; -pub use uart::*; + +use std::{ + io::{Read, Write}, + time::Duration, +}; + +use self::cep::CEPParseError; + +pub type ComResult = Result; + +pub trait CommunicationHandle: Read + Write { + const INTEGRITY_ACK_TIMEOUT: Duration; + const UNLIMITED_TIMEOUT: Duration = Duration::MAX; + + const DATA_PACKET_RETRIES: usize = 4; + + fn set_timeout(&mut self, timeout: &Duration); + + fn send_packet(&mut self, packet: &CEPPacket) -> ComResult<()> { + let bytes = Vec::from(packet); + + for _ in 0..Self::DATA_PACKET_RETRIES { + self.write_all(&bytes)?; + + if matches!(packet, CEPPacket::Data(_)) { + let response = self.receive_packet()?; + match response { + CEPPacket::Ack => return Ok(()), + CEPPacket::Nack => log::warn!("Received NACK after data packet; Retrying"), + p => { + log::error!("Received {p:?} after data packet"); + return Err(CommunicationError::PacketInvalidError); + } + } + } else { + return Ok(()); + } + } + + log::error!("No ACK after {} retries, giving up", Self::DATA_PACKET_RETRIES); + Err(CommunicationError::PacketInvalidError) + } + + fn send_multi_packet(&mut self, bytes: &[u8]) -> ComResult<()> { + let chunks = bytes.chunks(CEPPacket::MAXIMUM_DATA_LENGTH); + for chunk in chunks { + self.send_packet(&CEPPacket::Data(chunk.into()))?; + } + + self.send_packet(&CEPPacket::Eof)?; + self.await_ack(&Self::INTEGRITY_ACK_TIMEOUT)?; + + Ok(()) + } + + fn receive_packet(&mut self) -> ComResult { + for _ in 0..Self::DATA_PACKET_RETRIES { + match CEPPacket::try_from_read(self) { + Ok(p @ CEPPacket::Data(_)) => { + self.send_packet(&CEPPacket::Ack)?; + return Ok(p); + } + Ok(p) => return Ok(p), + Err(CEPParseError::InvalidCRC) => { + log::warn!("Received data packet with invalid CRC; Retrying") + } + Err(e) => { + log::error!("Failed to read packet: {e:?}"); + return Err(e.into()); + } + } + } + + todo!() + } + + fn receive_multi_packet(&mut self, stop_fn: impl Fn() -> bool) -> ComResult> { + let mut buffer = Vec::new(); + + loop { + let pack = self.receive_packet(); + if stop_fn() { + self.send_packet(&CEPPacket::Stop)?; + return Err(CommunicationError::StopCondition); + } + + match pack { + Ok(CEPPacket::Data(b)) => { + buffer.extend(b); + } + Ok(CEPPacket::Eof) => { + break; + } + Ok(CEPPacket::Stop) => { + return Err(CommunicationError::StopCondition); + } + Err(e @ CommunicationError::Io(_)) => { + return Err(e); + } + Err(CommunicationError::TimedOut) => { + log::error!("Receive multipacket timed out"); + return Err(CommunicationError::TimedOut); + } + e => { + log::error!("Received invalid data {:?}", e); + self.send_packet(&CEPPacket::Nack)?; + } + }; + } + + self.send_packet(&CEPPacket::Ack)?; + Ok(buffer) + } + + fn await_ack(&mut self, timeout: &Duration) -> ComResult<()> { + self.set_timeout(timeout); + match self.receive_packet()? { + CEPPacket::Ack => Ok(()), + CEPPacket::Nack => Err(CommunicationError::NotAcknowledged), + _ => Err(CommunicationError::PacketInvalidError), + } + } +} + +impl CommunicationHandle for Box { + const INTEGRITY_ACK_TIMEOUT: Duration = Duration::from_millis(100); + const UNLIMITED_TIMEOUT: Duration = Duration::MAX; + + fn set_timeout(&mut self, timeout: &Duration) { + serialport::SerialPort::set_timeout(self.as_mut(), *timeout).unwrap() + } +} + +#[derive(Debug)] +pub enum CommunicationError { + /// Signals that an unknown command packet was received + PacketInvalidError, + /// Relays an error from trying to parse a CEP packet + CepParsing(CEPParseError), + /// Signals that the underlying sending or receiving failed. Not recoverable on its own. + Io(std::io::Error), + /// Signals that a multi packet receive or send was interrupted by a Stop condition + StopCondition, + /// Signals that a receive timed out + TimedOut, + /// Nack was received when Ack was expected + NotAcknowledged, +} + +impl std::fmt::Display for CommunicationError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +impl From for CommunicationError { + fn from(value: std::io::Error) -> Self { + match value.kind() { + std::io::ErrorKind::TimedOut => CommunicationError::TimedOut, + std::io::ErrorKind::InvalidData => CommunicationError::PacketInvalidError, + _ => CommunicationError::Io(value), + } + } +} + +impl From for CommunicationError { + fn from(value: CEPParseError) -> Self { + match value { + CEPParseError::Io(e) => Self::Io(e), + e => Self::CepParsing(e), + } + } +} + +impl std::error::Error for CommunicationError {} + +#[cfg(test)] +mod tests { + use super::*; + use test_case::test_case; + + #[derive(Default)] + pub struct TestComHandle { + pub written_data: Vec, + pub data_to_read: Vec, + } + impl Read for TestComHandle { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + buf.copy_from_slice(&self.data_to_read[0..buf.len()]); + self.data_to_read.drain(0..buf.len()); + Ok(buf.len()) + } + } + impl Write for TestComHandle { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.written_data.extend(buf); + Ok(buf.len()) + } + + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } + } + impl CommunicationHandle for TestComHandle { + const INTEGRITY_ACK_TIMEOUT: Duration = Duration::from_millis(100); + const UNLIMITED_TIMEOUT: Duration = Duration::MAX; + fn set_timeout(&mut self, _timeout: &Duration) {} + } + + #[test_case(CEPPacket::Ack)] + #[test_case(CEPPacket::Nack)] + #[test_case(CEPPacket::Stop)] + #[test_case(CEPPacket::Eof)] + #[test_case(CEPPacket::Data(vec![1, 2, 3]))] + fn packet_is_sent_correctly(packet: CEPPacket) { + let mut com = TestComHandle::default(); + com.data_to_read.append(&mut CEPPacket::Ack.serialize()); + + com.send_packet(&packet).unwrap(); + + assert_eq!(com.written_data, packet.serialize()); + } + + #[test_case(CEPPacket::Ack)] + #[test_case(CEPPacket::Nack)] + #[test_case(CEPPacket::Stop)] + #[test_case(CEPPacket::Eof)] + #[test_case(CEPPacket::Data(vec![1, 2, 3]))] + fn packet_is_received_correctly(packet: CEPPacket) { + let mut com = TestComHandle::default(); + com.data_to_read.append(&mut packet.clone().serialize()); + + assert_eq!(com.receive_packet().unwrap(), packet); + + if matches!(packet, CEPPacket::Data(_)) { + assert_eq!(com.written_data, CEPPacket::Ack.serialize()); + } + } + + #[test] + fn retry_on_nack() { + let mut com = TestComHandle::default(); + com.data_to_read.append(&mut CEPPacket::Nack.serialize()); + com.data_to_read.append(&mut CEPPacket::Nack.serialize()); + com.data_to_read.append(&mut CEPPacket::Ack.serialize()); + + com.send_packet(&CEPPacket::Data(vec![1, 2, 3])).unwrap(); + + assert_eq!(com.written_data, CEPPacket::Data(vec![1, 2, 3]).serialize().repeat(3)); + assert!(com.data_to_read.is_empty()); + } + + #[test] + fn fail_after_retries() { + let mut com = TestComHandle::default(); + for _ in 0..TestComHandle::DATA_PACKET_RETRIES { + com.data_to_read.append(&mut CEPPacket::Nack.serialize()); + } + + assert!(matches!( + com.send_packet(&CEPPacket::Data(vec![1, 2, 3])), + Err(CommunicationError::PacketInvalidError) + )); + assert!(com.data_to_read.is_empty()); + assert_eq!( + com.written_data, + CEPPacket::Data(vec![1, 2, 3]).serialize().repeat(TestComHandle::DATA_PACKET_RETRIES) + ); + } + + #[test] + fn multi_packet_is_sent_correctly() { + let mut com = TestComHandle::default(); + + let data = vec![123u8; 2 * CEPPacket::MAXIMUM_DATA_LENGTH + 50]; + let chunks = data.chunks(CEPPacket::MAXIMUM_DATA_LENGTH); + com.data_to_read = CEPPacket::Ack.serialize().repeat(chunks.len() + 1); + + com.send_multi_packet(&data).unwrap(); + + assert!(com.data_to_read.is_empty()); + for c in chunks { + assert_eq!(com.written_data.drain(0..c.len()+7).as_slice(), CEPPacket::Data(c.to_vec()).serialize()); + } + assert_eq!(com.written_data, CEPPacket::Eof.serialize()); + } + + #[test] + fn multi_packet_is_received_correctly() { + let mut com = TestComHandle::default(); + + let data = vec![123u8; 2 * CEPPacket::MAXIMUM_DATA_LENGTH + 50]; + let chunks = data.chunks(CEPPacket::MAXIMUM_DATA_LENGTH); + for c in chunks.clone() { + com.data_to_read.append(&mut CEPPacket::Data(c.to_vec()).serialize()); + } + com.data_to_read.append(&mut CEPPacket::Eof.serialize()); + + assert_eq!(com.receive_multi_packet(|| false).unwrap(), data); + assert!(com.data_to_read.is_empty()); + assert_eq!(com.written_data, CEPPacket::Ack.serialize().repeat(chunks.len() + 1)) + } + +} diff --git a/src/communication/uart.rs b/src/communication/uart.rs deleted file mode 100644 index 1adec88..0000000 --- a/src/communication/uart.rs +++ /dev/null @@ -1,92 +0,0 @@ -use std::time::Duration; - -use crate::communication::{ComResult, CommunicationHandle}; -use rppal::uart::{Parity, Uart}; - -use super::CommunicationError; - -//Constants -const DATA_BITS: u8 = 8; -const STOP_BITS: u8 = 1; -const ALLOWED_SEND_RETRIES: u8 = 3; - -pub struct UARTHandle { - uart_PI: Uart, -} - -impl UARTHandle { - /// # ISSUE! - /// By Default UART Pins initiated this way will be TX: 14 and RX: 15. This is not coherent with PDD. - /// Pins need to be changed somehow - /// - /// ## Arguments - /// * `baud` - Bits per second - /// - /// ## Returns: - /// A `UARTHandle`(r) that uses Raspberry Pi's UART Peripheral - /// - pub fn new(path: &str, baud: u32) -> UARTHandle { - let mut uart_handler: UARTHandle = UARTHandle { - uart_PI: Uart::with_path(path, baud, Parity::None, DATA_BITS, STOP_BITS).unwrap(), - }; - - let _ = uart_handler.uart_PI.set_write_mode(true); - - uart_handler - } -} - -impl CommunicationHandle for UARTHandle { - /// Sends the given bytes via UART - /// ## Arguments - /// * `bytes` - a raw byte vector to send - /// ## Returns - /// * `Ok`: Sent correctly - /// * `InterfaceError`: UART Peripheral returns an error or failed to send everything - fn send(&mut self, bytes: Vec) -> ComResult<()> { - let mut sent_bytes: usize = 0; - - for _ in 0..=ALLOWED_SEND_RETRIES { - sent_bytes += self.uart_PI.write(&bytes[sent_bytes..]).unwrap(); - - if sent_bytes == bytes.len() { - return Ok(()); - } - } - Err(CommunicationError::InterfaceError) - } - - /// # Incomplete - /// Does not honor the supplied timeout. Planned for after HAF - /// - /// Receives a byte packet of some exepected length with a timeout (non inter-byte). Function while retry after failed attemps indefinitely during the given timeout - /// while the whole amount of expected bytes hasn't been received. - /// ## Arguments - /// * `byte_count`: the amount of bytes that is expected - /// * `timeout`: timeout for the set_read_mode from rppal. Global time of the function. Not inter-byte - /// ## Returns - /// A vector of bytes - fn receive(&mut self, byte_count: u16, _timeout: &std::time::Duration) -> ComResult> { - //Data buffer - let mut received_data_buffer: Vec = vec![0; byte_count as usize]; - - let mut received_bytes_counter: usize = 0; - let mut read_byte_count: u8; - - while received_bytes_counter < byte_count as usize { - read_byte_count = std::cmp::min(byte_count, 255) as u8; - self.uart_PI.set_read_mode(read_byte_count, Duration::ZERO).unwrap(); - - received_bytes_counter += - self.uart_PI.read(&mut received_data_buffer[received_bytes_counter..]).unwrap(); - } - - Ok(received_data_buffer) - } -} - -impl From for CommunicationError { - fn from(_: rppal::uart::Error) -> Self { - CommunicationError::InterfaceError - } -} diff --git a/src/lib.rs b/src/lib.rs index 5711b0c..9d296f8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,3 @@ +#![allow(non_snake_case)] pub mod command; pub mod communication; diff --git a/src/main.rs b/src/main.rs index 7c2698c..9e40868 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,8 @@ +#![allow(non_snake_case)] use core::time; use rppal::gpio::Gpio; -use std::{ - sync::{Arc, Mutex}, - thread, -}; +use std::{thread, time::Duration}; +use STS1_EDU_Scheduler::communication::CommunicationHandle; use simplelog as sl; @@ -37,11 +36,11 @@ fn main() -> ! { log::info!("Scheduler started"); // construct a wrapper for UART communication - let mut com = communication::UARTHandle::new(&config.uart, config.baudrate); + let mut com = serialport::new(&config.uart, config.baudrate).open().unwrap(); + com.set_timeout(&Duration::from_secs(60)); // construct a wrapper for resources that are shared between different commands - let ec = command::ExecutionContext::new("events".to_string(), config.update_pin).unwrap(); - let mut exec = Arc::new(Mutex::new(ec)); + let mut exec = command::ExecutionContext::new("events".to_string(), config.update_pin).unwrap(); // start a thread that will update the heartbeat pin thread::spawn(move || heartbeat_loop(config.heartbeat_pin, config.heartbeat_freq)); @@ -54,10 +53,10 @@ fn main() -> ! { fn heartbeat_loop(heartbeat_pin: u8, freq: u64) -> ! { if cfg!(feature = "mock") { - loop {} + std::thread::park(); } - let toogle_time = time::Duration::from_millis((1000 / freq / 2) as u64); + let toogle_time = time::Duration::from_millis(1000 / freq / 2); let gpio = Gpio::new().unwrap(); let mut pin = gpio.get(heartbeat_pin).unwrap().into_output(); diff --git a/tests/simulation/command_execution.rs b/tests/simulation/command_execution.rs index f42c722..d306168 100644 --- a/tests/simulation/command_execution.rs +++ b/tests/simulation/command_execution.rs @@ -2,11 +2,10 @@ use crate::simulation::*; #[test] fn simulate_archive_is_stored_correctly() -> Result<(), std::io::Error> { - let (mut scheduler, mut serial_port) = start_scheduler("archive_is_stored_correctly")?; - let mut cobc_in = serial_port.stdout.take().unwrap(); - let mut cobc_out = serial_port.stdin.take().unwrap(); + let (mut com, _socat) = SimulationComHandle::with_socat_proc("archive_is_stored_correctly"); + let _sched = start_scheduler("archive_is_stored_correctly")?; - simulate_test_store_archive(&mut cobc_in, &mut cobc_out, 1)?; + simulate_test_store_archive(&mut com, 1).unwrap(); std::thread::sleep(std::time::Duration::from_millis(400)); assert_eq!( @@ -23,6 +22,5 @@ fn simulate_archive_is_stored_correctly() -> Result<(), std::io::Error> { .unwrap() ); - scheduler.kill()?; Ok(()) } diff --git a/tests/simulation/full_run.rs b/tests/simulation/full_run.rs new file mode 100644 index 0000000..441fa9b --- /dev/null +++ b/tests/simulation/full_run.rs @@ -0,0 +1,27 @@ +use crate::simulation::*; +use std::{io::Cursor, time::Duration}; + +#[test] +fn full_run() { + let (mut com, _socat) = SimulationComHandle::with_socat_proc("full_run"); + let _sched = start_scheduler("full_run").unwrap(); + + // store and execute program + simulate_test_store_archive(&mut com, 1).unwrap(); + simulate_execute_program(&mut com, 1, 3, 3).unwrap(); + std::thread::sleep(Duration::from_secs(1)); + + // read program finished and result ready + assert_eq!(simulate_get_status(&mut com).unwrap(), [1, 1, 0, 3, 0, 0, 0, 0]); + assert_eq!(simulate_get_status(&mut com).unwrap(), [2, 1, 0, 3, 0, 0, 0]); + + // Check result + let result = simulate_return_result(&mut com, 1, 3).unwrap(); + let mut result_archive = zip::ZipArchive::new(Cursor::new(result)).unwrap(); + com.send_packet(&CEPPacket::Ack).unwrap(); + + let result_file = result_archive.by_name(&"3").unwrap(); + assert_eq!(result_file.bytes().map(|b| b.unwrap()).collect::>(), vec![0xde, 0xad]); + + assert_eq!(simulate_get_status(&mut com).unwrap(), [0]); +} diff --git a/tests/simulation/logging.rs b/tests/simulation/logging.rs index 1fe4de5..332af4c 100644 --- a/tests/simulation/logging.rs +++ b/tests/simulation/logging.rs @@ -2,9 +2,9 @@ use crate::simulation::*; #[test] fn logfile_is_created() -> Result<(), std::io::Error> { - let (mut scheduler, _) = start_scheduler("log_created")?; + let (_, _proc) = SimulationComHandle::with_socat_proc("log_created"); + let _sched = start_scheduler("log_created")?; std::thread::sleep(std::time::Duration::from_millis(400)); - scheduler.kill().unwrap(); assert!(std::path::Path::new("./tests/tmp/log_created/log").exists()); Ok(()) @@ -12,18 +12,17 @@ fn logfile_is_created() -> Result<(), std::io::Error> { #[test] fn logfile_is_cleared_after_sent() -> std::io::Result<()> { - let (mut scheduler, mut serial_port) = start_scheduler("log_is_cleared_after_sent")?; - let mut cobc_in = serial_port.stdout.take().unwrap(); - let mut cobc_out = serial_port.stdin.take().unwrap(); + let (mut com, _socat) = SimulationComHandle::with_socat_proc("log_is_cleared_after_sent"); + let _sched = start_scheduler("log_is_cleared_after_sent")?; - simulate_test_store_archive(&mut cobc_in, &mut cobc_out, 1)?; - simulate_execute_program(&mut cobc_in, &mut cobc_out, 1, 0, 3)?; - std::thread::sleep(std::time::Duration::from_millis(100)); - let _ = simulate_return_result(&mut cobc_in, &mut cobc_out, 1, 0)?; - cobc_out.write_all(&CEPPacket::ACK.serialize())?; + simulate_test_store_archive(&mut com, 1).unwrap(); + com.send_packet(&CEPPacket::Data(execute_program(1, 0, 3))).unwrap(); + com.await_ack(&Duration::MAX).unwrap(); std::thread::sleep(std::time::Duration::from_millis(100)); - scheduler.kill().unwrap(); + let _ = simulate_return_result(&mut com, 1, 0).unwrap(); + com.send_packet(&CEPPacket::Ack).unwrap(); + std::thread::sleep(std::time::Duration::from_millis(100)); let log_metadata = std::fs::metadata("./tests/tmp/log_is_cleared_after_sent/log")?; assert!(log_metadata.len() < 50, "Logfile is not empty"); diff --git a/tests/simulation/mod.rs b/tests/simulation/mod.rs index 4a96d33..6d457bc 100644 --- a/tests/simulation/mod.rs +++ b/tests/simulation/mod.rs @@ -1,14 +1,65 @@ mod command_execution; +mod full_run; mod logging; use std::{ - fmt::format, io::{Read, Write}, - process::{Child, Stdio}, + process::{Child, ChildStdin, ChildStdout, Stdio}, + time::Duration, }; +use STS1_EDU_Scheduler::communication::{CEPPacket, CommunicationError, CommunicationHandle}; -use crate::software_tests::common::*; -use STS1_EDU_Scheduler::communication::CEPPacket; +pub struct SimulationComHandle { + cobc_in: T, + cobc_out: U, +} + +impl SimulationComHandle { + fn with_socat_proc(unique: &str) -> (Self, PoisonedChild) { + let mut proc = std::process::Command::new("socat") + .arg("stdio") + .arg(format!("pty,raw,echo=0,link=/tmp/ttySTS1-{},b921600,wait-slave", unique)) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .unwrap(); + + loop { + if std::path::Path::new(&format!("/tmp/ttySTS1-{unique}")).exists() { + break; + } + std::thread::sleep(Duration::from_millis(50)); + } + + ( + Self { cobc_in: proc.stdout.take().unwrap(), cobc_out: proc.stdin.take().unwrap() }, + PoisonedChild(proc), + ) + } +} + +impl Read for SimulationComHandle { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + self.cobc_in.read(buf) + } +} + +impl Write for SimulationComHandle { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.cobc_out.write(buf) + } + + fn flush(&mut self) -> std::io::Result<()> { + self.cobc_out.flush() + } +} + +impl CommunicationHandle for SimulationComHandle { + const INTEGRITY_ACK_TIMEOUT: Duration = Duration::MAX; + const UNLIMITED_TIMEOUT: Duration = Duration::MAX; + + fn set_timeout(&mut self, _timeout: &std::time::Duration) {} +} fn get_config_str(unique: &str) -> String { format!( @@ -24,119 +75,100 @@ fn get_config_str(unique: &str) -> String { ) } -pub fn start_scheduler(unique: &str) -> Result<(Child, Child), std::io::Error> { +/// A simple wrapper that ensures child processes are killed when dropped +struct PoisonedChild(pub Child); +impl Drop for PoisonedChild { + fn drop(&mut self) { + self.0.kill().unwrap(); + } +} + +fn start_scheduler(unique: &str) -> Result { let test_dir = format!("./tests/tmp/{}", unique); let scheduler_bin = std::fs::canonicalize("./target/release/STS1_EDU_Scheduler")?; let _ = std::fs::remove_dir_all(&test_dir); std::fs::create_dir_all(&test_dir)?; std::fs::write(format!("{}/config.toml", &test_dir), get_config_str(unique))?; - let serial_port = std::process::Command::new("socat") - .arg("stdio") - .arg(format!("pty,raw,echo=0,link=/tmp/ttySTS1-{},b921600,wait-slave", unique)) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn()?; - std::thread::sleep(std::time::Duration::from_millis(100)); - let scheduler = std::process::Command::new(scheduler_bin).current_dir(test_dir).spawn().unwrap(); - Ok((scheduler, serial_port)) -} - -pub fn receive_ack(reader: &mut impl std::io::Read) -> Result<(), std::io::Error> { - let mut buffer = [0; 1]; - reader.read_exact(&mut buffer).unwrap(); - - if buffer[0] == CEPPacket::ACK.serialize()[0] { - Ok(()) - } else { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - format!("received {:#x} instead of ACK", buffer[0]), - )) - } + Ok(PoisonedChild(scheduler)) } pub fn simulate_test_store_archive( - cobc_in: &mut impl std::io::Read, - cobc_out: &mut impl std::io::Write, + com: &mut impl CommunicationHandle, program_id: u16, -) -> std::io::Result<()> { - let archive = std::fs::read("tests/student_program.zip")?; - cobc_out.write_all(&CEPPacket::DATA(store_archive(program_id)).serialize())?; - receive_ack(cobc_in)?; - cobc_out.write_all(&CEPPacket::DATA(archive).serialize())?; - receive_ack(cobc_in)?; - cobc_out.write_all(&CEPPacket::EOF.serialize())?; - receive_ack(cobc_in)?; - receive_ack(cobc_in)?; +) -> Result<(), CommunicationError> { + let archive = std::fs::read("tests/student_program.zip").unwrap(); + com.send_packet(&CEPPacket::Data(store_archive(program_id)))?; + com.send_multi_packet(&archive)?; + com.await_ack(&Duration::MAX)?; Ok(()) } pub fn simulate_execute_program( - cobc_in: &mut impl std::io::Read, - cobc_out: &mut impl std::io::Write, + com: &mut impl CommunicationHandle, program_id: u16, timestamp: u32, timeout: u16, -) -> std::io::Result<()> { - cobc_out - .write_all(&CEPPacket::DATA(execute_program(program_id, timestamp, timeout)).serialize())?; - receive_ack(cobc_in)?; - receive_ack(cobc_in)?; +) -> Result<(), CommunicationError> { + com.send_packet(&CEPPacket::Data(execute_program(program_id, timestamp, timeout)))?; + com.await_ack(&Duration::MAX)?; + Ok(()) } +pub fn simulate_get_status( + com: &mut impl CommunicationHandle, +) -> Result, CommunicationError> { + com.send_packet(&CEPPacket::Data(get_status()))?; + let response = com.receive_packet()?; + + if let CEPPacket::Data(data) = response { + Ok(data) + } else { + Err(CommunicationError::PacketInvalidError) + } +} + pub fn simulate_return_result( - cobc_in: &mut impl std::io::Read, - cobc_out: &mut impl std::io::Write, + com: &mut impl CommunicationHandle, program_id: u16, timestamp: u32, -) -> std::io::Result> { - cobc_out.write_all(&CEPPacket::DATA(return_result(program_id, timestamp)).serialize())?; - receive_ack(cobc_in)?; +) -> Result, CommunicationError> { + com.send_packet(&CEPPacket::Data(return_result(program_id, timestamp)))?; + let data = com.receive_multi_packet(|| false)?; - let data = read_multi_data_packets(cobc_in, cobc_out)?; Ok(data) } -/// Reads a data packet from input and returns the data content (does not check CRC) -pub fn read_data_packet(input: &mut impl std::io::Read, data: &mut Vec) -> std::io::Result<()> { - let mut header = [0; 3]; - input.read_exact(&mut header)?; - if header[0] != 0x8B { - return Err(std::io::Error::new( - std::io::ErrorKind::InvalidData, - format!("Expected data header (0x8B), received {:#04x}", header[0]), - )); - } +pub fn store_archive(program_id: u16) -> Vec { + let mut vec = vec![1u8]; + vec.extend(program_id.to_le_bytes()); + vec +} - let data_len = u16::from_le_bytes([header[1], header[2]]); - input.take((data_len + 4).into()).read_to_end(data)?; +pub fn execute_program(program_id: u16, timestamp: u32, timeout: u16) -> Vec { + let mut vec = vec![2u8]; + vec.extend(program_id.to_le_bytes()); + vec.extend(timestamp.to_le_bytes()); + vec.extend(timeout.to_le_bytes()); + vec +} - Ok(()) +pub fn stop_program() -> Vec { + vec![3u8] } -/// Reads a multi packet round without checking the CRC and returns the concatenated contents -pub fn read_multi_data_packets( - input: &mut impl std::io::Read, - output: &mut impl std::io::Write, -) -> std::io::Result> { - let mut eof_byte = [0; 1]; - let mut data = Vec::new(); - loop { - read_data_packet(input, &mut data)?; - output.write_all(&CEPPacket::ACK.serialize())?; - - input.read_exact(&mut eof_byte)?; - if eof_byte[0] == CEPPacket::EOF.serialize()[0] { - break; - } - } +pub fn get_status() -> Vec { + vec![4u8] +} - output.write_all(&CEPPacket::ACK.serialize())?; - Ok(data) +pub fn return_result(program_id: u16, timestamp: u32) -> Vec { + let mut vec = vec![5u8]; + vec.extend(program_id.to_le_bytes()); + vec.extend(timestamp.to_le_bytes()); + vec } diff --git a/tests/software_tests/command_integration.rs b/tests/software_tests/command_integration.rs index 7dce9cc..9612977 100644 --- a/tests/software_tests/command_integration.rs +++ b/tests/software_tests/command_integration.rs @@ -1,7 +1,5 @@ -use std::fs; -use std::io::{Read, Write}; -use STS1_EDU_Scheduler::command::{self, CommandError}; -use STS1_EDU_Scheduler::communication::{CEPPacket::*, CommunicationError}; +use STS1_EDU_Scheduler::command; +use STS1_EDU_Scheduler::communication::CEPPacket::*; use crate::software_tests::common; use crate::software_tests::common::ComEvent::*; @@ -11,16 +9,16 @@ type TestResult = Result<(), Box>; #[test] fn invalid_packets_from_cobc() -> TestResult { let packets = vec![ - COBC(DATA(vec![1, 2])), - EDU(NACK), - COBC(DATA(vec![2, 0, 1])), - EDU(NACK), - COBC_INVALID(vec![0x8b, 2, 0, 0, 0, 0, 0, 1, 10]), // Invalid CRC - EDU(NACK), + COBC(Data(vec![1, 2])), + EDU(Ack), + EDU(Nack), + COBC(Data(vec![2, 0, 1])), + EDU(Ack), + EDU(Nack), ]; let (mut com, mut exec) = common::prepare_handles(packets, "13"); - for _ in 0..3 { + for _ in 0..2 { command::handle_command(&mut com, &mut exec); } @@ -33,27 +31,27 @@ fn invalid_packets_from_cobc() -> TestResult { #[test] #[should_panic] fn ack_on_start_panic() { - let (mut com, mut exec) = common::prepare_handles(vec![COBC(ACK)], "99"); + let (mut com, mut exec) = common::prepare_handles(vec![COBC(Ack)], "99"); command::handle_command(&mut com, &mut exec); } #[test] #[should_panic] fn nack_on_start_panic() { - let (mut com, mut exec) = common::prepare_handles(vec![COBC(NACK)], "99"); + let (mut com, mut exec) = common::prepare_handles(vec![COBC(Nack)], "99"); command::handle_command(&mut com, &mut exec); } #[test] #[should_panic] fn eof_on_start_panic() { - let (mut com, mut exec) = common::prepare_handles(vec![COBC(EOF)], "99"); + let (mut com, mut exec) = common::prepare_handles(vec![COBC(Eof)], "99"); command::handle_command(&mut com, &mut exec); } #[test] #[should_panic] fn stop_on_start_panic() { - let (mut com, mut exec) = common::prepare_handles(vec![COBC(STOP)], "99"); + let (mut com, mut exec) = common::prepare_handles(vec![COBC(Stop)], "99"); command::handle_command(&mut com, &mut exec); } diff --git a/tests/software_tests/common.rs b/tests/software_tests/common.rs index 1de366b..8ec41bf 100644 --- a/tests/software_tests/common.rs +++ b/tests/software_tests/common.rs @@ -1,7 +1,8 @@ use std::{ + collections::VecDeque, + fmt::Debug, io::{Read, Write}, - process::{Child, Stdio}, - sync::{Arc, Mutex}, + time::Duration, }; use STS1_EDU_Scheduler::{ @@ -12,7 +13,6 @@ use STS1_EDU_Scheduler::{ pub enum ComEvent { /// EDU shall want to receive the given packet COBC(CEPPacket), - COBC_INVALID(Vec), /// EDU shall send the given packet EDU(CEPPacket), /// Makes the thread sleep for the given duration. Can be used to wait for execution to complete @@ -20,97 +20,89 @@ pub enum ComEvent { /// Allow the EDU to send any packet ANY, /// EDU shall send a packet, which is then passed to a given function (e.g. to allow for further checks on data) - ACTION(Box)>), + ACTION(Box), +} + +impl Debug for ComEvent { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::COBC(arg0) => f.debug_tuple("COBC").field(arg0).finish(), + Self::EDU(arg0) => f.debug_tuple("EDU").field(arg0).finish(), + Self::SLEEP(arg0) => f.debug_tuple("SLEEP").field(arg0).finish(), + Self::ANY => write!(f, "ANY"), + Self::ACTION(_) => f.debug_tuple("ACTION").finish(), + } + } } /// This communciation handle can simulate what is going on between EDU and COBC. Any send or receive call is /// checked against the supplied expected events vector pub struct TestCom { - expected_events: Vec, - receive_queue: Vec, - index: usize, + expected_events: VecDeque, } impl CommunicationHandle for TestCom { - fn send(&mut self, bytes: Vec) -> ComResult<()> { - match &self.expected_events[self.index] { - ComEvent::EDU(p) => { - assert_eq!( - bytes, - p.clone().serialize(), - "Wrong packet #{}, EDU: {:?}, should be {:?}", - self.index, - bytes, - p - ); - self.index += 1; - Ok(()) - } - ComEvent::SLEEP(d) => { - std::thread::sleep(*d); - self.index += 1; - Ok(()) - } - ComEvent::ANY => { - self.index += 1; - Ok(()) - } - ComEvent::ACTION(f) => { - f(bytes); - self.index += 1; - Ok(()) - } - _ => { - panic!("EDU should not send now, index {}", self.index); - } + fn send_packet(&mut self, packet: &CEPPacket) -> ComResult<()> { + println!("Sent {packet:?}"); + match self.expected_events.pop_front().unwrap() { + ComEvent::EDU(p) => assert_eq!(&p, packet), + ComEvent::SLEEP(d) => std::thread::sleep(d), + ComEvent::ANY => (), + ComEvent::ACTION(f) => f(packet), + event => panic!("Expected {event:?} instead of send_packet"), + } + + if matches!(packet, CEPPacket::Data(_)) { + self.await_ack(&Self::INTEGRITY_ACK_TIMEOUT)?; } + + Ok(()) } - fn receive(&mut self, n: u16, timeout: &std::time::Duration) -> ComResult> { - match &self.expected_events[self.index] { + fn receive_packet(&mut self) -> ComResult { + match self.expected_events.pop_front().unwrap() { ComEvent::COBC(p) => { - if !self.receive_queue.is_empty() { - let res: Vec = self.receive_queue.drain(0..(n as usize)).collect(); - if self.receive_queue.is_empty() { - self.index += 1; - } - Ok(res) - } else { - self.receive_queue.append(&mut p.clone().serialize()); - self.receive(n, timeout) - } - } - ComEvent::COBC_INVALID(b) => { - if !self.receive_queue.is_empty() { - let res: Vec = self.receive_queue.drain(0..(n as usize)).collect(); - if self.receive_queue.is_empty() { - self.index += 1; - } - Ok(res) - } else { - self.receive_queue.append(&mut b.clone()); - self.receive(n, timeout) + println!("Received {p:?}"); + if matches!(p, CEPPacket::Data(_)) { + self.send_packet(&CEPPacket::Ack)?; } + Ok(p) } ComEvent::SLEEP(d) => { - std::thread::sleep(*d); - self.index += 1; - self.receive(n, timeout) - } - _ => { - panic!("EDU should send now, index {}", self.index); + std::thread::sleep(d); + self.receive_packet() } + event => panic!("Expected {event:?} instead of receive_packet"), } } + + const INTEGRITY_ACK_TIMEOUT: std::time::Duration = Duration::MAX; + const UNLIMITED_TIMEOUT: std::time::Duration = Duration::MAX; + + fn set_timeout(&mut self, _timeout: &std::time::Duration) {} } impl TestCom { pub fn new(packets: Vec) -> Self { - TestCom { expected_events: packets, receive_queue: vec![], index: 0 } + TestCom { expected_events: packets.into() } } pub fn is_complete(&self) -> bool { - self.index == self.expected_events.len() + self.expected_events.is_empty() + } +} + +impl Read for TestCom { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + Ok(buf.len()) + } +} +impl Write for TestCom { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + Ok(buf.len()) + } + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) } } @@ -139,8 +131,7 @@ pub fn prepare_handles(packets: Vec, unique: &str) -> (TestCom, SyncEx file_per_thread_logger::allow_uninitialized(); file_per_thread_logger::initialize("tests/tmp/log-"); let com = TestCom::new(packets); - let ec = ExecutionContext::new(format!("tests/tmp/{unique}"), 12).unwrap(); - let exec = Arc::new(Mutex::new(ec)); + let exec = ExecutionContext::new(format!("tests/tmp/{unique}"), 12).unwrap(); (com, exec) } diff --git a/tests/software_tests/communication_tests.rs b/tests/software_tests/communication_tests.rs index 7628316..36bceab 100644 --- a/tests/software_tests/communication_tests.rs +++ b/tests/software_tests/communication_tests.rs @@ -3,14 +3,14 @@ use STS1_EDU_Scheduler::communication; #[test] fn csbi_command() { - assert!(CEPPacket::ACK.serialize() == vec![0xd7u8]); + assert!(CEPPacket::Ack.serialize() == vec![0xd7u8]); } #[test] fn csbi_data() { let b = vec![0x12u8, 0x34, 0x56]; assert_eq!( - CEPPacket::DATA(b).serialize(), + CEPPacket::Data(b).serialize(), vec![0x8bu8, 0x03, 0x00, 0x12, 0x34, 0x56, 0x57, 0x86, 0x98, 0xbe] ); } diff --git a/tests/software_tests/execute_program.rs b/tests/software_tests/execute_program.rs index f0e29f7..e16dbb8 100644 --- a/tests/software_tests/execute_program.rs +++ b/tests/software_tests/execute_program.rs @@ -11,9 +11,9 @@ type TestResult = Result<(), Box>; #[test] fn execute_program_normal() -> TestResult { let packets = vec![ - COBC(DATA(execute_program(1, 0, 2))), // Execute Program ID 1, Timestamp 0, Timeout 2s - EDU(ACK), - EDU(ACK), + COBC(Data(execute_program(1, 0, 2))), // Execute Program ID 1, Timestamp 0, Timeout 2s + EDU(Ack), + EDU(Ack), ]; common::prepare_program("1"); let (mut com, mut exec) = common::prepare_handles(packets, "1"); @@ -34,13 +34,13 @@ fn execute_program_normal() -> TestResult { #[test] fn execute_program_infinite() -> TestResult { let packets = vec![ - COBC(DATA(execute_program(2, 1, 1))), // Execute Program ID 2, Timestamp 1, Timeout 1s - EDU(ACK), - EDU(ACK), - COBC(DATA(get_status())), - EDU(ACK), - EDU(DATA(vec![1, 2, 0, 1, 0, 0, 0, 255])), - COBC(ACK), + COBC(Data(execute_program(2, 1, 1))), // Execute Program ID 2, Timestamp 1, Timeout 1s + EDU(Ack), + EDU(Ack), + COBC(Data(get_status())), + EDU(Ack), + EDU(Data(vec![1, 2, 0, 1, 0, 0, 0, 255])), + COBC(Ack), ]; common::prepare_program("2"); let (mut com, mut exec) = common::prepare_handles(packets, "2"); @@ -56,7 +56,7 @@ fn execute_program_infinite() -> TestResult { #[test] fn execute_missing_program() -> TestResult { - let packets = vec![COBC(DATA(execute_program(11, 0, 2))), EDU(ACK), EDU(NACK)]; + let packets = vec![COBC(Data(execute_program(11, 0, 2))), EDU(Ack), EDU(Nack)]; let (mut com, mut exec) = common::prepare_handles(packets, "12"); command::handle_command(&mut com, &mut exec); diff --git a/tests/software_tests/get_status.rs b/tests/software_tests/get_status.rs index acf9ca4..89b2be8 100644 --- a/tests/software_tests/get_status.rs +++ b/tests/software_tests/get_status.rs @@ -8,7 +8,7 @@ type TestResult = Result<(), Box>; #[test] fn get_status_none() -> TestResult { - let packets = vec![COBC(DATA(vec![4])), EDU(ACK), EDU(DATA(vec![0])), COBC(ACK)]; + let packets = vec![COBC(Data(vec![4])), EDU(Ack), EDU(Data(vec![0])), COBC(Ack)]; let (mut com, mut exec) = common::prepare_handles(packets, "5"); command::handle_command(&mut com, &mut exec); @@ -21,18 +21,18 @@ fn get_status_none() -> TestResult { #[test] fn get_status_finished() -> TestResult { let packets = vec![ - COBC(DATA(execute_program(6, 0, 1))), // Execute Program 6, Queue 0, Timeout 1s - EDU(ACK), - EDU(ACK), + COBC(Data(execute_program(6, 0, 1))), // Execute Program 6, Queue 0, Timeout 1s + EDU(Ack), + EDU(Ack), SLEEP(std::time::Duration::from_millis(500)), - COBC(DATA(vec![4])), // Get Status - EDU(ACK), - EDU(DATA(vec![1, 6, 0, 0, 0, 0, 0, 0])), // Program Finished - COBC(ACK), - COBC(DATA(vec![4])), // Get Status - EDU(ACK), - EDU(DATA(vec![2, 6, 0, 0, 0, 0, 0])), // Result Ready - COBC(ACK), + COBC(Data(vec![4])), // Get Status + EDU(Ack), + EDU(Data(vec![1, 6, 0, 0, 0, 0, 0, 0])), // Program Finished + COBC(Ack), + COBC(Data(vec![4])), // Get Status + EDU(Ack), + EDU(Data(vec![2, 6, 0, 0, 0, 0, 0])), // Result Ready + COBC(Ack), ]; common::prepare_program("6"); @@ -50,22 +50,22 @@ fn get_status_finished() -> TestResult { #[test] fn get_status_priority_for_status() -> TestResult { let packets = vec![ - COBC(DATA(execute_program(15, 0, 2))), - EDU(ACK), - EDU(ACK), + COBC(Data(execute_program(15, 0, 2))), + EDU(Ack), + EDU(Ack), SLEEP(std::time::Duration::from_millis(500)), - COBC(DATA(get_status())), - EDU(ACK), - EDU(DATA(vec![1, 15, 0, 0, 0, 0, 0, 0])), - COBC(ACK), - COBC(DATA(execute_program(15, 0, 2))), - EDU(ACK), - EDU(ACK), + COBC(Data(get_status())), + EDU(Ack), + EDU(Data(vec![1, 15, 0, 0, 0, 0, 0, 0])), + COBC(Ack), + COBC(Data(execute_program(15, 0, 2))), + EDU(Ack), + EDU(Ack), SLEEP(std::time::Duration::from_millis(500)), - COBC(DATA(get_status())), - EDU(ACK), - EDU(DATA(vec![1, 15, 0, 0, 0, 0, 0, 0])), - COBC(ACK), + COBC(Data(get_status())), + EDU(Ack), + EDU(Data(vec![1, 15, 0, 0, 0, 0, 0, 0])), + COBC(Ack), ]; common::prepare_program("15"); let (mut com, mut exec) = common::prepare_handles(packets, "15"); diff --git a/tests/software_tests/return_result.rs b/tests/software_tests/return_result.rs index b108e36..b8b9513 100644 --- a/tests/software_tests/return_result.rs +++ b/tests/software_tests/return_result.rs @@ -11,27 +11,30 @@ type TestResult = Result<(), Box>; #[test] fn returns_result_correctly() -> TestResult { let packets = vec![ - COBC(DATA(execute_program(7, 3, 1))), // Execute Program 7, Queue 0, Timeout 1s - EDU(ACK), - EDU(ACK), + COBC(Data(execute_program(7, 3, 1))), // Execute Program 7, Queue 0, Timeout 1s + EDU(Ack), + EDU(Ack), SLEEP(std::time::Duration::from_millis(500)), - COBC(DATA(get_status())), // Get Status - EDU(ACK), - EDU(DATA(vec![1, 7, 0, 3, 0, 0, 0, 0])), // Program Finished - COBC(ACK), - COBC(DATA(get_status())), // Get Status - EDU(ACK), - EDU(DATA(vec![2, 7, 0, 3, 0, 0, 0])), // Result Ready - COBC(ACK), - COBC(DATA(return_result(7, 3))), - EDU(ACK), - ACTION(Box::new(|bytes| { - std::fs::File::create("tests/tmp/7.zip").unwrap().write(&bytes).unwrap(); + COBC(Data(get_status())), // Get Status + EDU(Ack), + EDU(Data(vec![1, 7, 0, 3, 0, 0, 0, 0])), // Program Finished + COBC(Ack), + COBC(Data(get_status())), // Get Status + EDU(Ack), + EDU(Data(vec![2, 7, 0, 3, 0, 0, 0])), // Result Ready + COBC(Ack), + COBC(Data(return_result(7, 3))), + EDU(Ack), + ACTION(Box::new(|packet| { + std::fs::File::create("tests/tmp/7.zip") + .unwrap() + .write(&packet.clone().serialize()) + .unwrap(); })), - COBC(ACK), - EDU(EOF), - COBC(ACK), - COBC(ACK), + COBC(Ack), + EDU(Eof), + COBC(Ack), + COBC(Ack), ]; common::prepare_program("7"); @@ -60,14 +63,14 @@ fn returns_result_correctly() -> TestResult { #[test] fn truncate_result() -> TestResult { let packets = vec![ - COBC(DATA(execute_program(8, 5, 5))), // Execute Program 8, Queue 5, Timeout 2s - EDU(ACK), - EDU(ACK), + COBC(Data(execute_program(8, 5, 5))), // Execute Program 8, Queue 5, Timeout 2s + EDU(Ack), + EDU(Ack), SLEEP(std::time::Duration::from_millis(3000)), - COBC(DATA(get_status())), - EDU(ACK), - EDU(DATA(vec![1, 8, 0, 5, 0, 0, 0, 0])), - COBC(ACK), + COBC(Data(get_status())), + EDU(Ack), + EDU(Data(vec![1, 8, 0, 5, 0, 0, 0, 0])), + COBC(Ack), ]; common::prepare_program("8"); @@ -86,28 +89,28 @@ fn truncate_result() -> TestResult { #[test] fn stopped_return() -> TestResult { let packets = vec![ - COBC(DATA(execute_program(9, 5, 3))), - EDU(ACK), - EDU(ACK), + COBC(Data(execute_program(9, 5, 3))), + EDU(Ack), + EDU(Ack), SLEEP(std::time::Duration::from_millis(3000)), - COBC(DATA(get_status())), - EDU(ACK), - EDU(DATA(vec![1, 9, 0, 5, 0, 0, 0, 0])), - COBC(ACK), - COBC(DATA(get_status())), - EDU(ACK), - EDU(DATA(vec![2, 9, 0, 5, 0, 0, 0])), - COBC(ACK), - COBC(DATA(return_result(9, 5))), - EDU(ACK), + COBC(Data(get_status())), + EDU(Ack), + EDU(Data(vec![1, 9, 0, 5, 0, 0, 0, 0])), + COBC(Ack), + COBC(Data(get_status())), + EDU(Ack), + EDU(Data(vec![2, 9, 0, 5, 0, 0, 0])), + COBC(Ack), + COBC(Data(return_result(9, 5))), + EDU(Ack), ANY, - COBC(ACK), + COBC(Ack), ANY, - COBC(STOP), - COBC(DATA(get_status())), - EDU(ACK), - EDU(DATA(vec![2, 9, 0, 5, 0, 0, 0])), - COBC(ACK), + COBC(Stop), + COBC(Data(get_status())), + EDU(Ack), + EDU(Data(vec![2, 9, 0, 5, 0, 0, 0])), + COBC(Ack), ]; common::prepare_program("9"); let (mut com, mut exec) = common::prepare_handles(packets, "9"); @@ -127,7 +130,7 @@ fn stopped_return() -> TestResult { #[test] fn no_result_ready() -> TestResult { - let packets = vec![COBC(DATA(return_result(99, 0))), EDU(ACK), EDU(NACK)]; + let packets = vec![COBC(Data(return_result(99, 0))), EDU(Ack), EDU(Nack)]; let (mut com, mut exec) = common::prepare_handles(packets, "10"); command::handle_command(&mut com, &mut exec); @@ -140,17 +143,17 @@ fn no_result_ready() -> TestResult { #[test] fn result_is_not_deleted_after_corrupted_transfer() -> TestResult { let packets = vec![ - COBC(DATA(execute_program(50, 0, 3))), - EDU(ACK), - EDU(ACK), + COBC(Data(execute_program(50, 0, 3))), + EDU(Ack), + EDU(Ack), SLEEP(std::time::Duration::from_millis(2000)), - COBC(DATA(return_result(50, 0))), - EDU(ACK), + COBC(Data(return_result(50, 0))), + EDU(Ack), ANY, - COBC(ACK), - EDU(EOF), - COBC(ACK), - COBC(NACK), + COBC(Ack), + EDU(Eof), + COBC(Ack), + COBC(Nack), ]; common::prepare_program("50"); let (mut com, mut exec) = common::prepare_handles(packets, "50"); diff --git a/tests/software_tests/stop_program.rs b/tests/software_tests/stop_program.rs index f8cd0ef..facc61f 100644 --- a/tests/software_tests/stop_program.rs +++ b/tests/software_tests/stop_program.rs @@ -9,17 +9,17 @@ type TestResult = Result<(), Box>; #[test] fn stops_running_program() -> TestResult { let packets = vec![ - COBC(DATA(execute_program(3, 1, 10))), // Execute Program 3, Queue 1, Timeout 10s - EDU(ACK), - EDU(ACK), + COBC(Data(execute_program(3, 1, 10))), // Execute Program 3, Queue 1, Timeout 10s + EDU(Ack), + EDU(Ack), SLEEP(std::time::Duration::from_secs(1)), - COBC(DATA(stop_program())), - EDU(ACK), - EDU(ACK), - COBC(DATA(get_status())), - EDU(ACK), - EDU(DATA(vec![1, 3, 0, 1, 0, 0, 0, 255])), - COBC(ACK), + COBC(Data(stop_program())), + EDU(Ack), + EDU(Ack), + COBC(Data(get_status())), + EDU(Ack), + EDU(Data(vec![1, 3, 0, 1, 0, 0, 0, 255])), + COBC(Ack), ]; common::prepare_program("3"); let (mut com, mut exec) = common::prepare_handles(packets, "3"); @@ -35,7 +35,7 @@ fn stops_running_program() -> TestResult { #[test] fn stop_no_running_program() -> TestResult { - let packets = vec![COBC(DATA(stop_program())), EDU(ACK), EDU(ACK)]; + let packets = vec![COBC(Data(stop_program())), EDU(Ack), EDU(Ack)]; let (mut com, mut exec) = common::prepare_handles(packets, "11"); command::handle_command(&mut com, &mut exec); assert!(com.is_complete()); diff --git a/tests/software_tests/store_archive.rs b/tests/software_tests/store_archive.rs index ac82d57..a94b9b7 100644 --- a/tests/software_tests/store_archive.rs +++ b/tests/software_tests/store_archive.rs @@ -9,13 +9,13 @@ type TestResult = Result<(), Box>; fn store_archive() -> TestResult { // Define what should happen during communication. How this should look is defined in the PDD let packets = vec![ - COBC(DATA(vec![0x01, 0x00, 0x00])), // COBC sends Store Archive Command (0x01 -> Header, [0x00, 0x00] -> Program Id) - EDU(ACK), // EDU acknowledges packet integrity - COBC(DATA(std::fs::read("./tests/student_program.zip")?)), // COBC sends the archive - EDU(ACK), // EDU acknowledges packet integrity - COBC(EOF), // COBC signals end of packets - EDU(ACK), // EDU acknowledges EOF - EDU(ACK), // EDU signals successful Store Archive + COBC(Data(vec![0x01, 0x00, 0x00])), // COBC sends Store Archive Command (0x01 -> Header, [0x00, 0x00] -> Program Id) + EDU(Ack), // EDU acknowledges packet integrity + COBC(Data(std::fs::read("./tests/student_program.zip")?)), // COBC sends the archive + EDU(Ack), // EDU acknowledges packet integrity + COBC(Eof), // COBC signals end of packets + EDU(Ack), // EDU acknowledges Eof + EDU(Ack), // EDU signals successful Store Archive ]; // Setup testing environment @@ -45,13 +45,13 @@ fn store_archive() -> TestResult { #[test] fn stopped_store() -> TestResult { let packets = vec![ - COBC(DATA(vec![0x01, 0x04, 0x00])), // Store Archive with ID 0 - EDU(ACK), - COBC(DATA(std::fs::read("./tests/student_program.zip")?)), - EDU(ACK), - COBC(DATA(vec![0, 1, 2, 3])), - EDU(ACK), - COBC(STOP), + COBC(Data(vec![0x01, 0x04, 0x00])), // Store Archive with ID 0 + EDU(Ack), + COBC(Data(std::fs::read("./tests/student_program.zip")?)), + EDU(Ack), + COBC(Data(vec![0, 1, 2, 3])), + EDU(Ack), + COBC(Stop), ]; let (mut com, mut exec) = common::prepare_handles(packets, "4"); @@ -63,37 +63,3 @@ fn stopped_store() -> TestResult { common::cleanup("4"); Ok(()) } - -#[test] -fn invalid_crc() -> TestResult { - let mut bytes = std::fs::read("./tests/student_program.zip")?; - let packets = vec![ - COBC(DATA(vec![1, 14, 0])), - EDU(ACK), - COBC(DATA(bytes.drain(0..20).collect())), - EDU(ACK), - COBC_INVALID(vec![0x8b, 5, 0, 0, 0, 0, 0, 0, 0, 10, 10, 10]), - EDU(NACK), - COBC(DATA(bytes)), - EDU(ACK), - COBC(EOF), - EDU(ACK), - EDU(ACK), - ]; - let (mut com, mut exec) = common::prepare_handles(packets, "14"); - - command::handle_command(&mut com, &mut exec); - assert!(com.is_complete()); - - assert_eq!( - 0, - std::process::Command::new("diff") // check wether the archive was stored correctly - .args(["-yq", "--strip-trailing-cr", "tests/test_data", "archives/14"]) - .status()? - .code() - .unwrap() - ); - - common::cleanup("14"); - Ok(()) -} diff --git a/tests/tests.rs b/tests/tests.rs index 1f20ca4..df7fe93 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1,5 +1,4 @@ -/// This directory contains tests that only use command::handle_command(). This makes them ideal for debugging and finegrained tests -mod software_tests; - /// This directory contains tests that run the actual compiled binaries and uses socat to create a simulated serial port. mod simulation; + +mod software_tests;