diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef862 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +target/ +.idea/ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..2013c20 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,520 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "annotate-snippets" +version = "0.12.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92570a3f9c98e7e84df84b71d0965ac99b1871fcd75a3773a3bd1bad13f64cf7" +dependencies = [ + "anstyle", + "memchr", + "unicode-width", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "arraydeque" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" + +[[package]] +name = "atomic" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "encoding_rs_io" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cc3c5651fb62ab8aa3103998dade57efdd028544bd300516baa31840c252a83" +dependencies = [ + "encoding_rs", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "figment" +version = "0.10.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3" +dependencies = [ + "atomic", + "serde", + "uncased", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "js-sys" +version = "0.3.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.184" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memfd_ringbuf" +version = "0.1.0" +dependencies = [ + "bytemuck", + "ringbuf", + "rustix", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "ringbuf" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d3ecbcab081b935fb9c618b07654924f27686b4aac8818e700580a83eedcb7f" +dependencies = [ + "crossbeam-utils", + "portable-atomic", + "portable-atomic-util", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "safetyguard" +version = "0.1.0" +dependencies = [ + "figment", + "serde", + "serde-saphyr", +] + +[[package]] +name = "saphyr-parser-bw" +version = "0.0.611" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dec0c833db75dc98957956b303fe447ffc5eb13f2325ef4c2350f7f3aa69e3" +dependencies = [ + "arraydeque", + "smallvec", + "thiserror", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-saphyr" +version = "0.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75e214449d107a81daf1453eb46c9314457660509534883e82db6faca2034a8a" +dependencies = [ + "ahash", + "annotate-snippets", + "base64", + "encoding_rs_io", + "figment", + "getrandom", + "nohash-hasher", + "num-traits", + "saphyr-parser-bw", + "serde", + "smallvec", + "zmij", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "uncased" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..95fb62d --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,3 @@ +[workspace] +members = ["safetyguard", "memfd_ringbuf"] +resolver = "3" \ No newline at end of file diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..cf74f49 --- /dev/null +++ b/build.rs @@ -0,0 +1,3 @@ +fn main() { + +} \ No newline at end of file diff --git a/doc/config.yaml b/doc/config.yaml new file mode 100644 index 0000000..d7b788e --- /dev/null +++ b/doc/config.yaml @@ -0,0 +1,94 @@ +# ========================= +# SafetyGuard Configuration +# +# All config parameter are lower case seperated by "_" if required. +# ========================= + +config_version: 1 + +global: + log_level: info + check_interval_ms: 500 # cyclic time to check states of apps + state_file: /var/lib/safetyguard/state.yaml + log_file: /var/log/safetyguard.log + init_file: /etc/safetygurad/config.yaml + init_file_gen: /var/lib/safetyguard/gen_config.yaml + mode: production # Can be development. In case of development, the app configuration + # will be generated which can act as guidance, for example heartbeat_interval_ms as average heartbeat + endpoint: /tmp/safetyguard.sock + +watchdog: + device: /dev/watchdog + timeout_sec: 10 + pretimeout_sec: 5 + kick_interval_ms: 3000 + +recover: + system_reset_on_failure: true + max_global_failures: 5 + +system_apps: + - name: systemd + timeout_ms: 5000 + safemonitor: + - heartbeat: + heartbeat_update_interval_ms: 500 # Interval it is expected update the heartbeat to safetyguard. It should be less then heartbeat Intervals of threads. Ideally half of the minimum heartbeat interval threads. + threads: + - main: + heartbeat_interval_ms: 1000 + safeaction: + - coresystem: + restart: true + wait_time_ms: 1000 + wait_for_reboot_accept: true # for example, safeapp sends this info or user accepts reboot + - safestate: + - app_name: safeapp + manager: systemd + systemd: + - service_name: safeapp # Runs this app, till system is safe. + +apps: + - name: app_a + timeout_ms: 3000 + max_retries: 3 + critical: true + manager: systemd + systemd: + - service_name: app_a # unit file name. + safeaction: + - systemd: + restart: true + - coresystem: # If not present, system will not rebooted. + restart: true #Upon all retries failure. + - safestate: + - app_name: safeapp + manager: systemd + systemd: + - service_name: safeapp # Runs this app, till system is safe. + safemonitor: + - heartbeat: + heartbeat_update_interval_ms: 300 + threads: + - main: + heartbeat_interval_ms: 1000 + - thread_b: + heartbeat_interval_ms: 500 + - memory: + max_mem: 10kb + + - name: app_b + timeout_ms: 2000 + max_retries: 5 + critical: false + manager: docker # Can be another runtime + docker: + - container_name: app_b + safeaction: + - container: + restart: true + safemonitor: + - heartbeat: + threads: + - main: + heartbeat_interval_ms: 500 + diff --git a/doc/designdecision.md b/doc/designdecision.md new file mode 100644 index 0000000..cbc8c89 --- /dev/null +++ b/doc/designdecision.md @@ -0,0 +1,7 @@ +Control loop: +* No memory allocation is allowed. The various IPC in linux involves memory allocation, where as shared memory doesn't. So shared memory is good for the use-case. Also, it is better to be lock-free. So Single producer and signle consumer queue with shared memory will be use as communication mechanism between apps and safetygurad. +* The application will update the shared memory and then send event via eventfd to indicate new data available in the shared memory. The safety guard will update the app specific details in the safetyguard. +* Also, there is timerfd in the control loop of safety guard. With this the state of all apps will be evaluvated. Such as heartbeats, memory-consuption details, or safeinstruments of the entire system. The timerfd is also responsible for update the watchdog of the system based on the monitored data. +* + + diff --git a/doc/papyrus_uml/.project b/doc/papyrus_uml/.project new file mode 100644 index 0000000..e452601 --- /dev/null +++ b/doc/papyrus_uml/.project @@ -0,0 +1,11 @@ + + + SafeGuard + + + + + + + + diff --git a/doc/papyrus_uml/SafeGuard.aird b/doc/papyrus_uml/SafeGuard.aird new file mode 100644 index 0000000..880d4a8 --- /dev/null +++ b/doc/papyrus_uml/SafeGuard.aird @@ -0,0 +1,534 @@ + + + + SafeGuard.uml + pathmap://UML_LIBRARIES/UMLPrimitiveTypes.library.uml + pathmap://UML_PROFILES/Ecore.profile.uml + pathmap://UML_LIBRARIES/EcorePrimitiveTypes.library.uml + pathmap://UML_PROFILES/Standard.profile.uml + pathmap://UML_METAMODELS/UML.metamodel.uml + pathmap://UML_METAMODELS/Ecore.metamodel.uml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/papyrus_uml/SafeGuard.di b/doc/papyrus_uml/SafeGuard.di new file mode 100644 index 0000000..8c549ee --- /dev/null +++ b/doc/papyrus_uml/SafeGuard.di @@ -0,0 +1,2 @@ + + diff --git a/doc/papyrus_uml/SafeGuard.notation b/doc/papyrus_uml/SafeGuard.notation new file mode 100644 index 0000000..51045df --- /dev/null +++ b/doc/papyrus_uml/SafeGuard.notation @@ -0,0 +1,732 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/papyrus_uml/SafeGuard.uml b/doc/papyrus_uml/SafeGuard.uml new file mode 100644 index 0000000..3ff29cb --- /dev/null +++ b/doc/papyrus_uml/SafeGuard.uml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Enable security measures + + + Threads update the datestore via API which is part of client library + + + + Heartbeat update are based on heartbeat_update_interval. +Either use threaded model or can call API as part of client library + + + + + + + + + + + + + + + + + + + + + + + Enable security aspects as required. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/memfd_ringbuf/Cargo.toml b/memfd_ringbuf/Cargo.toml new file mode 100644 index 0000000..e9676c4 --- /dev/null +++ b/memfd_ringbuf/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "memfd_ringbuf" +version = "0.1.0" +edition = "2024" + +[dependencies] +ringbuf = { version = "0.5.0", features = []} +rustix = { version = "1.1.4", features = [ "fs", "mm", "event"] } +bytemuck = { version = "1.25.0", features = ["derive"] } diff --git a/memfd_ringbuf/src/eventfd_notifier.rs b/memfd_ringbuf/src/eventfd_notifier.rs new file mode 100644 index 0000000..3261c6e --- /dev/null +++ b/memfd_ringbuf/src/eventfd_notifier.rs @@ -0,0 +1,116 @@ + +use rustix::{ + event::{eventfd, EventfdFlags}, + fd::OwnedFd, + io::{read, write, Result}, +}; +use ringbuf::traits::{Observer, Producer}; +use crate::traits::RbProducerNotify; + +pub struct EventFdNotifier { + pub fd: OwnedFd, +} + +impl EventFdNotifier { + pub fn new() -> Result { + let fd = eventfd(0, EventfdFlags::CLOEXEC | EventfdFlags::NONBLOCK)?; + Ok(Self { fd }) + } + + pub fn from_fd(fd: OwnedFd) -> Self { + Self { fd } + } + + pub fn wait(&self) -> Result { + let mut buf = [0u8; 8]; + read(&self.fd, &mut buf)?; + Ok(u64::from_ne_bytes(buf)) + } +} + +impl RbProducerNotify for EventFdNotifier { + fn notify(&self) -> Result<()> { + let val: u64 = 1; + write(&self.fd, &val.to_ne_bytes())?; + Ok(()) + } +} + +pub struct NotifyingProducer { + base: P, + notifier: N, +} + +impl NotifyingProducer { + pub fn new(base: P, notifier: N) -> Self { + Self { base, notifier } + } +} + +impl Observer for NotifyingProducer { + type Item = P::Item; + + fn capacity(&self) -> std::num::NonZeroUsize { + self.base.capacity() + } + + fn read_index(&self) -> usize { + self.base.read_index() + } + + fn write_index(&self) -> usize { + self.base.write_index() + } + + unsafe fn unsafe_slices(&self, start: usize, end: usize) -> (&[std::mem::MaybeUninit], &[std::mem::MaybeUninit]) { + unsafe { self.base.unsafe_slices(start, end) } + } + + unsafe fn unsafe_slices_mut(&self, start: usize, end: usize) -> (&mut [std::mem::MaybeUninit], &mut [std::mem::MaybeUninit]) { + unsafe { self.base.unsafe_slices_mut(start, end) } + } + + fn read_is_held(&self) -> bool { + self.base.read_is_held() + } + + fn write_is_held(&self) -> bool { + self.base.write_is_held() + } +} + +impl Producer for NotifyingProducer { + unsafe fn set_write_index(&self, value: usize) { + unsafe { self.base.set_write_index(value); } + let _ = self.notifier.notify(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::memfd_rb::MemfdRb; + use ringbuf::traits::{Producer, Consumer}; + + #[test] + fn test_eventfd_notify() { + let storage = crate::memfd_rb::MemfdStorage::::create("test_notify").unwrap(); + let rb = MemfdRb { storage }; + + let prod = rb.get_producer(); + let mut cons = rb.get_consumer(); + + let notifier = EventFdNotifier::new().unwrap(); + let mut notifying_prod = NotifyingProducer::new(prod, notifier); + + notifying_prod.try_push(42).unwrap(); + + let mut buf = [0u8; 8]; + rustix::io::read(¬ifying_prod.notifier.fd, &mut buf).unwrap(); + let val = u64::from_ne_bytes(buf); + assert_eq!(val, 1); + + assert_eq!(cons.try_pop(), Some(42)); + } +} + diff --git a/memfd_ringbuf/src/lib.rs b/memfd_ringbuf/src/lib.rs new file mode 100644 index 0000000..ae123b6 --- /dev/null +++ b/memfd_ringbuf/src/lib.rs @@ -0,0 +1,22 @@ +pub mod traits; +pub mod memfd_rb; +pub mod eventfd_notifier; + +pub use memfd_rb::{MemfdRb, MemfdStorage, MemfdStorageTrait}; +pub use traits::{SharedPod, RbProducerNotify}; +pub use eventfd_notifier::EventFdNotifier; + +pub fn add(left: u64, right: u64) -> u64 { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} diff --git a/memfd_ringbuf/src/memfd_rb.rs b/memfd_ringbuf/src/memfd_rb.rs new file mode 100644 index 0000000..30d5fb4 --- /dev/null +++ b/memfd_ringbuf/src/memfd_rb.rs @@ -0,0 +1,605 @@ +// Core +use std::{ + io, + mem::{size_of, MaybeUninit}, + os::fd::{ AsFd, OwnedFd }, + ptr::{self, NonNull, addr_of_mut}, + sync::atomic::{AtomicU32, AtomicU64, AtomicBool, Ordering}, + num::NonZeroUsize +}; + +// libraries +use rustix::{ + fs::{ftruncate, memfd_create, MemfdFlags}, + mm::{mmap, munmap, MapFlags, ProtFlags }, + io::dup, +}; +use ringbuf::{ + storage::Storage, + traits::{Observer, Consumer, Producer, RingBuffer}, + wrap::{Prod, Cons} +}; + +// Crate +use crate::traits::SharedPod; + +const MAGIC: u64 = u64::from_be_bytes(*b"SHMRINGU"); +#[repr(C, align(64))] +pub struct CacheAligned(pub T); + +#[repr(C, align(64))] +pub struct Header { + pub magic: u64, + pub version: u32, + pub flags: u32, + pub total_size: u64, + pub ring_capacity: u64, + pub initialized: AtomicU32, +} + +#[repr(C, align(64))] +pub struct SharedLayout { + pub header: Header, + pub read_index: CacheAligned, + pub write_index: CacheAligned, + pub read_held: AtomicBool, + pub write_held: AtomicBool, + pub storage: [MaybeUninit; N], +} + +pub struct MemfdStorage { + fd: OwnedFd, + ptr: NonNull>, + size: usize, +} + +impl MemfdStorage { + pub fn create(name: &str) -> io::Result { + let fd = memfd_create(name, MemfdFlags::CLOEXEC | MemfdFlags::ALLOW_SEALING) + .map_err(io::Error::from)?; + + let total_size = size_of::>(); + + /* Resize memfd */ + ftruncate(fd.as_fd(), total_size as u64) + .map_err(io::Error::from)?; + + let raw_ptr = unsafe { + mmap( + ptr::null_mut(), + total_size, + ProtFlags::READ | ProtFlags::WRITE, + MapFlags::SHARED, + fd.as_fd(), + 0, + ) + }.map_err(io::Error::from)? + as *mut SharedLayout; + + let ptr = NonNull::new(raw_ptr) + .ok_or_else(|| { + io::Error::new( + io::ErrorKind::InvalidData, + "Mmap returned null pointer", + ) + })?; + + unsafe { + ptr::write( + raw_ptr, + SharedLayout { + header: Header { + magic: MAGIC, + version: 1, + flags: 0, + total_size: total_size as u64, + ring_capacity: N as u64, + initialized: AtomicU32::new(0), + }, + + read_index: CacheAligned(AtomicU64::new(0)), + + write_index: CacheAligned(AtomicU64::new(0)), + + read_held: AtomicBool::new(false), + + write_held: AtomicBool::new(false), + + storage: std::array::from_fn(|_i| { + MaybeUninit::uninit() + }), + }, + ); + + /* Marks as fully initialized state */ + ptr.as_ref() + .header + .initialized + .store(1, Ordering::Release); + + Ok(Self { + fd, + ptr, + size: total_size, + }) + } + } + + pub fn attach(fd: OwnedFd) -> io::Result { + let total_size = size_of::>(); + + let raw_ptr = unsafe { + mmap( + ptr::null_mut(), + total_size, + ProtFlags::READ | ProtFlags::WRITE, + MapFlags::SHARED, + fd.as_fd(), + 0 + ) + }.map_err(io::Error::from)? as *mut SharedLayout; + + let ptr = NonNull::new(raw_ptr) + .ok_or_else(|| { + io::Error::new(io::ErrorKind::InvalidInput, "Null map pointer", + ) + })?; + + let cleanup = || unsafe { + munmap(raw_ptr.cast(), total_size).ok(); + }; + + unsafe { + let layout = ptr.as_ref(); + + if layout.header.magic != MAGIC { + cleanup(); + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Invalid memfd magic", + )); + } + + /* Validate Version */ + if layout.header.version != 1 { + cleanup(); + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Unsupported shared memory version", + )); + } + + if layout.header.ring_capacity != N as u64 { + cleanup(); + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Ring capacity mismatch", + )); + } + + /* Wait/check initialization */ + if layout.header.initialized.load(Ordering::Acquire) != 1 { + cleanup(); + return Err(io::Error::new( + io::ErrorKind::WouldBlock, + "Shared memory not initialized", + )); + } + + Ok(Self { + fd, + ptr, + size: total_size, + }) + } + } + + pub fn dup_fd(&self) -> io::Result { + dup(self.fd.as_fd()).map_err(io::Error::from) + } + + pub fn layout( &self) -> &SharedLayout { + unsafe { self.ptr.as_ref() } + } +} + +impl Drop for MemfdStorage { + fn drop(&mut self) { + unsafe { + munmap(self.ptr.as_ptr().cast(), self.size).ok(); + } + } +} + +unsafe impl Storage for MemfdStorage { + type Item = T; + + fn len(&self) -> usize { + N + } + + fn as_mut_ptr(&self) -> *mut MaybeUninit { + unsafe { + let layout_ptr = self.ptr.as_ptr(); + (*layout_ptr).storage.as_mut_ptr() + } + } +} + +pub unsafe trait MemfdStorageTrait: Storage { + fn header_ptr(&self) -> *mut Header; + fn read_index_ptr(&self) -> *mut AtomicU64; + fn write_index_ptr(&self) -> *mut AtomicU64; + fn read_held_ptr(&self) -> *mut u8; + fn write_held_ptr(&self) -> *mut u8; +} + +unsafe impl MemfdStorageTrait for MemfdStorage { + fn header_ptr(&self) -> *mut Header { + unsafe { + let layout_ptr = self.ptr.as_ptr(); + addr_of_mut!((*layout_ptr).header) + } + } + + fn read_index_ptr(&self) -> *mut AtomicU64 { + unsafe { + let layout_ptr = self.ptr.as_ptr(); + addr_of_mut!((*layout_ptr).read_index.0) + } + } + + fn write_index_ptr(&self) -> *mut AtomicU64 { + unsafe { + let layout_ptr = self.ptr.as_ptr(); + addr_of_mut!((*layout_ptr).write_index.0) + } + } + + fn read_held_ptr(&self) -> *mut u8 { + unsafe { + let layout_ptr = self.ptr.as_ptr(); + addr_of_mut!((*layout_ptr).read_held) as *mut u8 + } + } + + fn write_held_ptr(&self) -> *mut u8 { + unsafe { + let layout_ptr = self.ptr.as_ptr(); + addr_of_mut!((*layout_ptr).write_held) as *mut u8 + } + } +} + +pub struct MemfdRb { + pub storage: S, +} + +impl AsRef for MemfdRb { + fn as_ref(&self) -> &Self { + self + } +} + +impl Observer for MemfdRb { + type Item = S::Item; + + #[inline] + fn capacity(&self) -> NonZeroUsize { + NonZeroUsize::new(self.storage.len()).expect("Capacity must be non-zero") + } + + fn read_index(&self) -> usize { + unsafe { (*self.storage.read_index_ptr()).load(Ordering::Acquire) as usize } + } + + fn write_index(&self) -> usize { + unsafe { (*self.storage.write_index_ptr()).load(Ordering::Acquire) as usize } + } + + unsafe fn unsafe_slices(&self, start: usize, end: usize) + -> (&[MaybeUninit], &[MaybeUninit]) { + unsafe { + let (first, second) = self.unsafe_slices_mut(start, end); + (first, second) + } + } + + unsafe fn unsafe_slices_mut(&self, start: usize, end: usize) + -> (&mut [MaybeUninit], &mut [MaybeUninit]) { + let ptr = self.storage.as_mut_ptr(); + let len = self.storage.len(); + unsafe { + if start <= end { + (std::slice::from_raw_parts_mut(ptr.add(start), end - start), &mut []) + } else { + ( + std::slice::from_raw_parts_mut(ptr.add(start), len - start), + std::slice::from_raw_parts_mut(ptr, end), + ) + } + } + } + + fn read_is_held(&self) -> bool { + unsafe { + let ptr = self.storage.read_held_ptr() as *const AtomicBool; + (*ptr).load(Ordering::Acquire) + } + } + + fn write_is_held(&self) -> bool { + unsafe { + let ptr = self.storage.write_held_ptr() as *const AtomicBool; + (*ptr).load(Ordering::Acquire) + } + } +} + +impl Producer for MemfdRb { + unsafe fn set_write_index(&self, value: usize) { + unsafe { (*self.storage.write_index_ptr()).store(value as u64, Ordering::Release); } + } +} + +impl Consumer for MemfdRb { + unsafe fn set_read_index(&self, value: usize) { + unsafe { (*self.storage.read_index_ptr()).store(value as u64, Ordering::Release); } + } +} + +impl RingBuffer for MemfdRb { + unsafe fn hold_read(&self, flag: bool) -> bool { + unsafe { + let ptr = self.storage.read_held_ptr() as *const AtomicBool; + (*ptr).swap(flag, Ordering::AcqRel) + } + } + + unsafe fn hold_write(&self, flag: bool) -> bool { + unsafe { + let ptr = self.storage.write_held_ptr() as *const AtomicBool; + (*ptr).swap(flag, Ordering::AcqRel) + } + } +} + +impl Drop for MemfdRb { + fn drop(&mut self) { + unsafe { + self.hold_read(false); + self.hold_write(false); + } + } +} + +impl MemfdRb { + pub fn get_producer(&self) -> Prod<&Self> { + Prod::new(self) + } + + pub fn get_consumer(&self) -> Cons<&Self> { + Cons::new(self) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use bytemuck::{Pod, Zeroable}; + use std::mem::{align_of, size_of}; + use std::sync::atomic::Ordering; + + #[repr(C)] + #[derive(Copy, Clone, Zeroable, Pod, Debug, PartialEq)] + struct TestMessage { + pub id: u64, + pub value: u32, + _padding: u32, + } + + unsafe impl SharedPod for TestMessage {} + + #[test] + fn create_memfd_storage() { + const N: usize = 128; + + let storage = + MemfdStorage::::create("test-ring") + .unwrap(); + + let layout = unsafe { + storage.ptr.as_ref() + }; + + /* + * Validate header + */ + assert_eq!( + layout.header.magic, + MAGIC + ); + + assert_eq!(layout.header.version, 1); + + assert_eq!( + layout.header.ring_capacity, + N as u64 + ); + + assert_eq!( + layout.header.initialized.load(Ordering::Acquire), + 1 + ); + + /* + * Validate indexes + */ + assert_eq!( + layout.read_index.0.load(Ordering::Acquire), + 0 + ); + + assert_eq!( + layout.write_index.0.load(Ordering::Acquire), + 0 + ); + + } + + #[test] + fn validate_alignment() { + assert_eq!( + align_of::
(), + 64 + ); + + assert_eq!( + align_of::>(), + 64 + ); + + assert_eq!( + align_of::>(), + 64 + ); + } + + #[test] + fn validate_sizes() { + println!( + "Header size: {}", + size_of::
() + ); + + println!( + "SharedLayout size: {}", + size_of::>() + ); + + /* + * CacheAligned should occupy full cacheline. + */ + assert_eq!( + size_of::>(), + 64 + ); + } + + #[test] + fn write_and_read_() { + const N: usize = 16; + + let storage = + MemfdStorage::::create("rw-test") + .unwrap(); + + let layout = storage.layout(); + + let slot = &layout.storage[0]; + + let msg = TestMessage { + id: 42, + value: 777, + _padding: 0, + }; + + unsafe { + slot.as_ptr().cast::().cast_mut().write(msg); + } + + let read_back = unsafe { + slot.assume_init() + }; + + assert_eq!(msg, read_back); + } + + #[test] + fn producer_consumer() { + const N: usize = 16; + let storage = MemfdStorage::::create("pc-test").unwrap(); + let rb = MemfdRb { storage }; + + let mut prod = rb.get_producer(); + let mut cons = rb.get_consumer(); + + let msg = TestMessage { id: 1, value: 100, _padding: 0 }; + prod.try_push(msg).unwrap(); + + let read = cons.try_pop().unwrap(); + assert_eq!(msg, read); + } + + + #[test] + fn producer_consumer_attach_storage() { + const N: usize = 16; + let storage = MemfdStorage::::create("pc-test-attach").unwrap(); + + let dup_fd = storage.dup_fd().unwrap(); + let attached_storage = MemfdStorage::::attach(dup_fd).unwrap(); + + let rb_prod = MemfdRb { storage }; + let rb_cons = MemfdRb { storage: attached_storage }; + + let mut prod = rb_prod.get_producer(); + let mut cons = rb_cons.get_consumer(); + + let msg = TestMessage { id: 1, value: 100, _padding: 0 }; + prod.try_push(msg).unwrap(); + + let read = cons.try_pop().unwrap(); + assert_eq!(msg, read); + } + + #[test] + fn attach_existing_memfd() { + const N: usize = 64; + let storage = MemfdStorage::::create( + "attach-test", + ).unwrap(); + + let dup_fd = dup(storage.fd.as_fd()).unwrap(); + + /* Now use attach function to get the attached fd */ + let attached_storage = + MemfdStorage::::attach(dup_fd).unwrap(); + + assert_eq!( + attached_storage.layout().header.magic, + MAGIC + ); + + assert_eq!(attached_storage.layout().header.version, 1); + + /* write test */ + storage.layout() + .write_index + .0 + .store(42, Ordering::Release); + + /* Read from attached storage */ + let value = attached_storage.layout().write_index.0.load(Ordering::Acquire); + assert_eq!(value, 42); + + let msg = TestMessage { + id: 134, + value: 1466, + _padding: 0, + }; + + unsafe { + storage.layout().storage[0].as_ptr().cast::().cast_mut().write(msg); + } + + /* Read back from attached fd */ + let read_back = unsafe { + attached_storage.layout().storage[0].assume_init() + }; + + assert_eq!(msg, read_back); + + } +} diff --git a/memfd_ringbuf/src/traits.rs b/memfd_ringbuf/src/traits.rs new file mode 100644 index 0000000..3892ab6 --- /dev/null +++ b/memfd_ringbuf/src/traits.rs @@ -0,0 +1,17 @@ +use bytemuck::Pod; +use rustix::io::Result; + +pub unsafe trait SharedPod: Pod {} + +unsafe impl SharedPod for u32 {} +unsafe impl SharedPod for u64 {} +unsafe impl SharedPod for i32 {} +unsafe impl SharedPod for i64 {} +unsafe impl SharedPod for u8 {} +unsafe impl SharedPod for i8 {} +unsafe impl SharedPod for usize {} +unsafe impl SharedPod for isize {} + +pub trait RbProducerNotify { + fn notify(&self) -> Result<()>; +} \ No newline at end of file diff --git a/safetyguard/Cargo.toml b/safetyguard/Cargo.toml new file mode 100644 index 0000000..9122a69 --- /dev/null +++ b/safetyguard/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "safetyguard" +version = "0.1.0" +edition = "2024" + +[dependencies] +serde-saphyr = { version = "0.0.25", features = ["serialize","deserialize","figment"] } +figment = { version = "0.10" } +serde = { version = "1.0.228", features = ["derive"] } + diff --git a/safetyguard/src/lib.rs b/safetyguard/src/lib.rs new file mode 100644 index 0000000..56a27a4 --- /dev/null +++ b/safetyguard/src/lib.rs @@ -0,0 +1,3 @@ +#![forbid(unsafe_code)] + +pub mod safeappdb; diff --git a/safetyguard/src/main.rs b/safetyguard/src/main.rs new file mode 100644 index 0000000..729f031 --- /dev/null +++ b/safetyguard/src/main.rs @@ -0,0 +1,5 @@ + + +fn main() { + println!("Hello, world!"); +} \ No newline at end of file diff --git a/safetyguard/src/safeaction.rs b/safetyguard/src/safeaction.rs new file mode 100644 index 0000000..e69de29 diff --git a/safetyguard/src/safeappdb.rs b/safetyguard/src/safeappdb.rs new file mode 100644 index 0000000..059665b --- /dev/null +++ b/safetyguard/src/safeappdb.rs @@ -0,0 +1,2 @@ +mod appsdb; +pub mod global; \ No newline at end of file diff --git a/safetyguard/src/safeappdb/appsdb.rs b/safetyguard/src/safeappdb/appsdb.rs new file mode 100644 index 0000000..e69de29 diff --git a/safetyguard/src/safeappdb/global.rs b/safetyguard/src/safeappdb/global.rs new file mode 100644 index 0000000..743266a --- /dev/null +++ b/safetyguard/src/safeappdb/global.rs @@ -0,0 +1,201 @@ +use figment::{Figment, providers::Format}; +use serde::{Serialize, Deserialize}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Config { + #[serde(default = "Config::default_config_version")] + config_version: u64, + #[serde(default)] + global: Global, + #[serde(default)] + watchdog: Watchdog, + #[serde(default)] + recover: Recover, +} +impl Config { + fn default_config_version() -> u64 { + 1 + } +} +#[derive(Debug, Serialize, Deserialize)] +#[serde(default)] +pub struct Global { + log_level: LogLevel, + check_interval_ms: u64, + state_file: String, + log_file: String, + config_file: String, + config_file_gen: String, + mode: Mode, + endpoint: String, +} + +impl Default for Global { + fn default() -> Self { + Self { + log_level: LogLevel::INFO, + check_interval_ms: 500, + state_file: "/tmp/safeappdb.state".to_string(), + log_file: "/tmp/safeappdb.log".to_string(), + config_file: "/tmp/safeappdb.config".to_string(), + config_file_gen: "/tmp/safeappdb.config.gen".to_string(), + mode: Mode::DEVELOPMENT, + endpoint: "/tmp/saftyguard.sock".to_string(), + } + } +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum LogLevel { + DEDUG, + INFO, + WARN, + ERROR, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum Mode { + DEVELOPMENT, + PRODUCTION, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(default)] +pub struct Watchdog { + device: String, + timeout_sec: u64, + pretimeout_sec: u64, + kick_interval_ms: u64, +} + +impl Default for Watchdog { + fn default() -> Self { + Self { + device: "/dev/watchdog".to_string(), + timeout_sec: 10, + pretimeout_sec: 5, + kick_interval_ms: 3000, + } + } +} +#[derive(Debug, Serialize, Deserialize)] +#[serde(default)] +pub struct Recover { + system_reset_on_failure: bool, + max_global_failures: u64, +} +impl Default for Recover { + fn default() -> Self { + Self { + system_reset_on_failure: true, + max_global_failures: 5, + } + } +} + + +impl Config { + pub fn get_config(fp : String ) -> Result> { + let cfg: Config = Figment::from(serde_saphyr::figment::Yaml::file(fp)) + .extract::()?; + Ok(cfg) + } +} + +#[cfg(test)] +mod tests { + use std::fs; + use std::io::Write; + use super::*; + + + #[test] + fn test_config() -> Result<(), Box> { + let yml = r#" +config_version: 1 +global: + log_level: info + check_interval_ms: 1000 + state_file: /tmp/safeappdb.state + log_file: /tmp/safeappdb.log + config_file: /tmp/safeappdb.config + config_file_gen: /tmp/safeappdb.config.gen + mode: development # Can be development. + endpoint: /tmp/saftyguard.sock + +watchdog: + device: /dev/watchdog2 + timeout_sec: 100 + pretimeout_sec: 15 + kick_interval_ms: 2000 + +recover: + system_reset_on_failure: false + max_global_failures: 6 + +"#; + + let mut fp = fs::File::create("/tmp/tst.yaml")?; + fp.write_all(yml.as_bytes())?; + fp.sync_all()?; + + let config = Config::get_config("/tmp/tst.yaml".to_string())?; + println!("{config:#?}"); + + assert_eq!(config.global.check_interval_ms, 1000); + assert_eq!(config.global.state_file, "/tmp/safeappdb.state"); + assert_eq!(config.global.log_file, "/tmp/safeappdb.log"); + assert_eq!(config.global.config_file, "/tmp/safeappdb.config"); + assert_eq!(config.global.config_file_gen, "/tmp/safeappdb.config.gen"); + assert_eq!(config.global.endpoint, "/tmp/saftyguard.sock"); + assert_eq!(config.global.log_level, LogLevel::INFO); + assert_eq!(config.global.mode, Mode::DEVELOPMENT); + + assert_eq!(config.recover.system_reset_on_failure, false); + assert_eq!(config.recover.max_global_failures, 6); + + assert_eq!(config.watchdog.device, "/dev/watchdog2"); + assert_eq!(config.watchdog.timeout_sec, 100); + assert_eq!(config.watchdog.pretimeout_sec, 15); + assert_eq!(config.watchdog.kick_interval_ms, 2000); + + fs::remove_file("/tmp/tst.yaml")?; + + Ok(()) + } + #[test] + fn test_config_default() -> Result<(), Box> { + let yml = r#" +"#; + + let mut fp = fs::File::create("/tmp/tst2.yaml")?; + fp.write_all(yml.as_bytes())?; + fp.sync_all()?; + + let config = Config::get_config("/tmp/tst2.yaml".to_string())?; + println!("{config:#?}"); + + assert_eq!(config.global.check_interval_ms, 500); + assert_eq!(config.global.state_file, "/tmp/safeappdb.state"); + assert_eq!(config.global.log_file, "/tmp/safeappdb.log"); + assert_eq!(config.global.config_file, "/tmp/safeappdb.config"); + assert_eq!(config.global.config_file_gen, "/tmp/safeappdb.config.gen"); + assert_eq!(config.global.endpoint, "/tmp/saftyguard.sock"); + assert_eq!(config.global.log_level, LogLevel::INFO); + assert_eq!(config.global.mode, Mode::DEVELOPMENT); + + assert_eq!(config.recover.system_reset_on_failure, true); + assert_eq!(config.recover.max_global_failures, 5); + + assert_eq!(config.watchdog.device, "/dev/watchdog"); + assert_eq!(config.watchdog.timeout_sec, 10); + assert_eq!(config.watchdog.pretimeout_sec, 5); + assert_eq!(config.watchdog.kick_interval_ms, 3000); + + fs::remove_file("/tmp/tst2.yaml")?; + + Ok(()) + } +} \ No newline at end of file diff --git a/safetyguard/src/safeinstrument.rs b/safetyguard/src/safeinstrument.rs new file mode 100644 index 0000000..e69de29 diff --git a/safetyguard/src/safemonitor.rs b/safetyguard/src/safemonitor.rs new file mode 100644 index 0000000..e69de29