diff --git a/erpc_rust/.gitignore b/erpc_rust/.gitignore new file mode 100644 index 000000000..d913617bc --- /dev/null +++ b/erpc_rust/.gitignore @@ -0,0 +1,2 @@ +target + diff --git a/erpc_rust/Cargo.lock b/erpc_rust/Cargo.lock new file mode 100644 index 000000000..e865781e8 --- /dev/null +++ b/erpc_rust/Cargo.lock @@ -0,0 +1,1002 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-trait" +version = "0.1.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "erpc_rust" +version = "0.1.0" +dependencies = [ + "async-trait", + "byteorder", + "bytes", + "futures", + "serde", + "serialport", + "tempfile", + "thiserror 1.0.69", + "tokio", + "tokio-test", + "tracing", + "tracing-subscriber", + "tracing-test", +] + +[[package]] +name = "errno" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "io-kit-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b" +dependencies = [ + "core-foundation-sys", + "mach2", +] + +[[package]] +name = "io-uring" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "libc", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libudev" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +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.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys", +] + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +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 = "redox_syscall" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustc-demangle" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" + +[[package]] +name = "rustix" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +dependencies = [ + "bitflags 2.9.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[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.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serialport" +version = "4.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb0bc984f6af6ef8bab54e6cf2071579ee75b9286aa9f2319a0d220c28b0a2b" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "core-foundation", + "core-foundation-sys", + "io-kit-sys", + "libudev", + "mach2", + "nix", + "scopeguard", + "unescaper", + "winapi", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tokio" +version = "1.47.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +dependencies = [ + "backtrace", + "bytes", + "io-uring", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "slab", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-test" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2468baabc3311435b55dd935f702f42cd1b8abb7e754fb7dfb16bd36aa88f9f7" +dependencies = [ + "async-stream", + "bytes", + "futures-core", + "tokio", + "tokio-stream", +] + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "tracing-test" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "557b891436fe0d5e0e363427fc7f217abf9ccd510d5136549847bdcbcd011d68" +dependencies = [ + "tracing-core", + "tracing-subscriber", + "tracing-test-macro", +] + +[[package]] +name = "tracing-test-macro" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "unescaper" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c01d12e3a56a4432a8b436f293c25f4808bdf9e9f9f98f9260bba1f1bc5a1f26" +dependencies = [ + "thiserror 2.0.12", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.9.1", +] diff --git a/erpc_rust/Cargo.toml b/erpc_rust/Cargo.toml new file mode 100644 index 000000000..5bc5bd7a9 --- /dev/null +++ b/erpc_rust/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "erpc_rust" +version = "0.1.0" +edition = "2021" +description = "Rust implementation of eRPC (Embedded RPC) protocol" +license = "BSD-3-Clause" +authors = ["etherealjoy"] +repository = "https://github.com/etherealjoy/erpc" + +[dependencies] +tokio = { version = "1.0", features = ["full"] } +bytes = "1.0" +thiserror = "1.0" +serde = { version = "1.0", features = ["derive"] } +tracing = "0.1" +async-trait = "0.1" +futures = "0.3" +byteorder = "1.4" +tracing-subscriber = "0.3" + +[dev-dependencies] +tokio-test = "0.4" +tracing-test = "0.2" +tempfile = "3.0" + +[features] +default = ["tcp", "serial"] +tcp = [] +serial = ["serialport"] + +[dependencies.serialport] +version = "4.0" +optional = true diff --git a/erpc_rust/README_rust.md b/erpc_rust/README_rust.md new file mode 100644 index 000000000..6710a0976 --- /dev/null +++ b/erpc_rust/README_rust.md @@ -0,0 +1,524 @@ +# eRPC Rust + +A Rust implementation of the eRPC (Embedded RPC) protocol, providing efficient client and server functionality with primary focus on TCP transport. + +## Features + +- **TCP-First Design**: Primary focus on TCP transport for reliable network communication +- **Async/Await Support**: Built on Tokio for high-performance async I/O +- **Codec System**: Binary protocol with little-endian encoding using BasicCodec +- **Message Types**: Support for invocation, oneway, reply, and notification messages +- **Error Handling**: Comprehensive error types with detailed error information +- **Thread Safe**: All components are designed for concurrent use +- **Additional Transports**: Memory transport for testing, Serial transport available as optional feature + +## Complete Workflow: From IDL to Implementation + +### Step 1: Define Your Service (IDL) + +Create an `.erpc` file defining your service interface: + +```idl +// temp_alarm.erpc +program TempAlarm + +type SensorAddress = uint8 + +struct SensorInfo { + SensorAddress address + float readInterval +} + +interface Temp { + add_sensor(SensorAddress address) -> bool + remove_sensor(SensorAddress address) -> bool + set_interval(SensorAddress address, float interval) -> bool + read_one_sensor(SensorAddress address) -> float +} + +interface TempAsync { + oneway sensor_reading(SensorAddress addr, float temp) +} +``` + +### Step 2: Generate Rust Code + +Use the eRPC code generator to create Rust bindings: + +```bash +# Navigate to your eRPC installation +cd /path/to/erpc + +# Generate Rust code from your IDL file +./Release/Linux/erpcgen/erpcgen -g rust -o ./generated/ ./temp_alarm.erpc + +# Alternative: If you built eRPC locally +make -C erpcgen # Build the generator first +./erpcgen/bin/erpcgen -g rust -o ./generated/ ./temp_alarm.erpc + +# This creates: +# - generated/temp_alarm.rs (service definitions, client/server traits) +``` + +**Generated Code Structure:** +- **Service IDs**: Enums defining unique service identifiers +- **Method IDs**: Enums for each service's method identifiers +- **Data Types**: Structs, enums, and type aliases from your IDL +- **Client Traits**: Async trait definitions for service clients +- **Client Structs**: Concrete client implementations +- **Server Traits**: Async trait definitions you implement for services +- **Server Structs**: Server wrappers that handle the eRPC protocol + +### Step 3: Setup Your Rust Project + +Add dependencies to your `Cargo.toml`: + +```toml +[dependencies] +erpc_rust = "0.1.0" +tokio = { version = "1.0", features = ["full"] } +async-trait = "0.1" +serde = { version = "1.0", features = ["derive"] } +``` + +Include the generated code in your project: + +```rust +// src/main.rs +mod temp_alarm; // Include the generated temp_alarm.rs + +use temp_alarm::temp_server::{Temp, TempClient, TempServer}; +use temp_alarm::temp_async_server::{TempAsync, TempAsyncClient, TempAsyncServer}; +``` + +### Step 4: Implement Server + +Create your service implementation: + +```rust +// src/server_impl.rs +use crate::temp_alarm::temp_server::{Temp, TempServer}; +use crate::temp_alarm::temp_async_server::{TempAsync, TempAsyncServer}; +use async_trait::async_trait; +use erpc_rust::{ + codec::BasicCodecFactory, + server::{MultiTransportServerBuilder, Server}, +}; + +#[derive(Clone)] +struct TempServiceImpl { + // Your service state here +} + +#[async_trait] +impl Temp for TempServiceImpl { + async fn add_sensor(&self, address: u8) -> Result> { + println!("Adding sensor with address: {}", address); + // Your implementation logic here + Ok(true) + } + + async fn remove_sensor(&self, address: u8) -> Result> { + println!("Removing sensor with address: {}", address); + Ok(true) + } + + async fn set_interval(&self, address: u8, interval: f32) -> Result> { + println!("Setting interval for sensor {}: {}", address, interval); + Ok(true) + } + + async fn read_one_sensor(&self, address: u8) -> Result> { + println!("Reading sensor {}", address); + Ok(23.5) // Mock temperature reading + } +} + +#[async_trait] +impl TempAsync for TempServiceImpl { + async fn sensor_reading(&self, addr: u8, temp: f32) { + println!("Received sensor reading: addr={}, temp={}", addr, temp); + } +} + +pub async fn run_server() -> Result<(), Box> { + println!("🚀 Starting eRPC Temperature Server..."); + + // Create service implementations + let temp_service = TempServiceImpl {}; + let temp_async_service = TempServiceImpl {}; + + // Build and start server + let mut server = MultiTransportServerBuilder::new() + .add_tcp_transport("127.0.0.1:40000") + .await? + .codec_factory(BasicCodecFactory::new()) + .add_service(TempServer::new(temp_service)) + .add_service(TempAsyncServer::new(temp_async_service)) + .build(); + + println!("✅ Server listening on 127.0.0.1:40000"); + server.run().await?; + Ok(()) +} +``` + +#### Server Wiring Patterns + +The eRPC Rust server supports multiple wiring patterns for different deployment scenarios: + +**Single Service, Single Transport:** +```rust +use erpc_rust::{ + codec::BasicCodecFactory, + server::{Server, SingleTransportServerBuilder}, + transport::TcpTransport, +}; + +// Simple single service setup +let transport = TcpTransport::bind("127.0.0.1:40000").await?; +let mut server = SingleTransportServerBuilder::new(transport) + .codec_factory(BasicCodecFactory::new()) + .add_service(TempServer::new(TempServiceImpl::new())) + .build(); + +server.run().await?; +``` + +**Multiple Services, Single Transport:** +```rust +// Multiple services on same transport +let mut server = MultiTransportServerBuilder::new() + .add_tcp_transport("127.0.0.1:40000").await? + .codec_factory(BasicCodecFactory::new()) + .add_service(TempServer::new(temp_service)) + .add_service(TempAsyncServer::new(temp_async_service)) + .add_service(OtherServer::new(other_service)) // Add more services + .build(); +``` + +**Multiple Services, Multiple Transports:** +```rust +// Services across different transports +let mut server = MultiTransportServerBuilder::new() + .add_tcp_transport("127.0.0.1:40000").await? // TCP for main services + .add_tcp_transport("127.0.0.1:40001").await? // TCP for admin services + .codec_factory(BasicCodecFactory::new()) + .add_service(TempServer::new(temp_service)) + .add_service(TempAsyncServer::new(temp_async_service)) + .build(); +``` + +**Server with Serial Transport:** +```rust +// Mixed TCP and Serial transports +let mut server = MultiTransportServerBuilder::new() + .add_tcp_transport("127.0.0.1:40000").await? + .add_serial_transport("/dev/ttyUSB0", 115200).await? + .codec_factory(BasicCodecFactory::new()) + .add_service(TempServer::new(temp_service)) + .build(); +``` + +**Service Discovery and Registration:** +```rust +// Advanced server setup with service registration +pub async fn setup_production_server() -> Result<(), Box> { + let mut builder = MultiTransportServerBuilder::new(); + + // Add transports + builder = builder + .add_tcp_transport("0.0.0.0:40000").await? // Listen on all interfaces + .codec_factory(BasicCodecFactory::new()); + + // Register services dynamically + let services = vec![ + Box::new(TempServer::new(TempServiceImpl::new())) as Box, + Box::new(TempAsyncServer::new(TempAsyncServiceImpl::new())), + // Add more services as needed + ]; + + for service in services { + builder = builder.add_service(service); + } + + let mut server = builder.build(); + + // Graceful shutdown handling + tokio::select! { + result = server.run() => { + if let Err(e) = result { + eprintln!("Server error: {}", e); + } + } + _ = tokio::signal::ctrl_c() => { + println!("Received Ctrl+C, shutting down gracefully..."); + } + } + + Ok(()) +} +``` + +### Step 5: Implement Client + +Create your client application: + +```rust +// src/client_impl.rs +use crate::temp_alarm::temp_server::TempClient; +use crate::temp_alarm::temp_async_server::TempAsyncClient; +use erpc_rust::client::{ClientManager, CodecConfig}; + +pub async fn run_client() -> Result<(), Box> { + println!("🚀 Starting eRPC Temperature Client..."); + + // Connect to server + let mut client_manager = ClientManager::builder() + .tcp_connection("127.0.0.1:40000") + .codec(CodecConfig::Basic) + .connect() + .await?; + + // Create service clients + let mut temp_client = TempClient::new(&mut client_manager); + let mut temp_async_client = TempAsyncClient::new(&mut client_manager); + + // Use the services + let sensor_added = temp_client.add_sensor(1).await?; + println!("✅ Sensor added: {}", sensor_added); + + let interval_set = temp_client.set_interval(1, 2.5).await?; + println!("✅ Interval set: {}", interval_set); + + let temperature = temp_client.read_one_sensor(1).await?; + println!("✅ Temperature reading: {}", temperature); + + // Send oneway notification + temp_async_client.sensor_reading(1, 25.3).await; + println!("✅ Sent sensor reading notification"); + + Ok(()) +} +``` + +#### Client Connection Patterns + +**Single Service Client:** +```rust +// Connect to specific service +let mut client_manager = ClientManager::builder() + .tcp_connection("127.0.0.1:40000") + .codec(CodecConfig::Basic) + .connect().await?; + +let mut temp_client = TempClient::new(&mut client_manager); +``` + +**Multiple Service Clients (Shared Connection):** +```rust +// Share connection across multiple service clients +let mut client_manager = ClientManager::builder() + .tcp_connection("127.0.0.1:40000") + .codec(CodecConfig::Basic) + .connect().await?; + +// All clients share the same underlying connection +let mut temp_client = TempClient::new(&mut client_manager); +let mut temp_async_client = TempAsyncClient::new(&mut client_manager); +let mut other_client = OtherServiceClient::new(&mut client_manager); +``` + +**Multiple Independent Connections:** +```rust +// Separate connections for different services/servers +let mut temp_manager = ClientManager::builder() + .tcp_connection("127.0.0.1:40000") // Temperature service server + .codec(CodecConfig::Basic) + .connect().await?; + +let mut admin_manager = ClientManager::builder() + .tcp_connection("127.0.0.1:40001") // Admin service server + .codec(CodecConfig::Basic) + .connect().await?; + +let mut temp_client = TempClient::new(&mut temp_manager); +let mut admin_client = AdminClient::new(&mut admin_manager); +``` + +**Client with Error Handling and Reconnection:** +```rust +use tokio::time::{sleep, Duration}; + +pub async fn robust_client() -> Result<(), Box> { + let mut retry_count = 0; + const MAX_RETRIES: u32 = 5; + + loop { + match ClientManager::builder() + .tcp_connection("127.0.0.1:40000") + .codec(CodecConfig::Basic) + .connect().await + { + Ok(mut client_manager) => { + let mut temp_client = TempClient::new(&mut client_manager); + + // Use the client + match temp_client.add_sensor(1).await { + Ok(result) => { + println!("✅ Operation successful: {}", result); + break; // Success, exit retry loop + } + Err(e) => { + eprintln!("❌ RPC call failed: {}", e); + // Connection established but RPC failed - this might be a different issue + break; + } + } + } + Err(e) => { + retry_count += 1; + if retry_count >= MAX_RETRIES { + return Err(format!("Failed to connect after {} retries: {}", MAX_RETRIES, e).into()); + } + + eprintln!("🔄 Connection failed (attempt {}/{}): {}", retry_count, MAX_RETRIES, e); + sleep(Duration::from_secs(2_u64.pow(retry_count))).await; // Exponential backoff + } + } + } + + Ok(()) +} +``` + +### Step 6: Main Application + +Wire everything together: + +```rust +// src/main.rs +use std::env; + +mod temp_alarm; // Generated code +mod server_impl; // Your server implementation +mod client_impl; // Your client implementation + +use client_impl::run_client; +use server_impl::run_server; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let args: Vec = env::args().collect(); + + if args.len() < 2 { + println!("Usage: {} ", args[0]); + return Ok(()); + } + + match args[1].as_str() { + "server" => run_server().await, + "client" => run_client().await.map_err(|e| e.into()), + _ => { + println!("Invalid argument. Use 'server' or 'client'"); + Ok(()) + } + } +} +``` + +### Step 7: Run Your Application + +```bash +# Build the project +cargo build + +# Run server in one terminal +cargo run server + +# Run client in another terminal +cargo run client +``` + +## Alternative Client Connection Patterns + +### Direct Transport Access +For advanced use cases or manual RPC handling: + +```rust +use erpc_rust::client::ClientManager; + +// Get TCP transport directly +let transport = ClientManager::tcp_connection("127.0.0.1:8080").await?; +let mut client = YourGeneratedClient::new(transport); +``` + +### Explicit Component Construction +Maximum control over transport and codec creation: + +```rust +use erpc_rust::{ + client::ClientManager, + codec::BasicCodecFactory, + transport::TcpTransport, +}; + +let transport = TcpTransport::connect("127.0.0.1:8080").await?; +let codec_factory = BasicCodecFactory::new(); +let client_manager = ClientManager::new(transport, codec_factory); +``` + +## Serial Transport Support + +Enable serial transport with the `serial` feature: + +```toml +[dependencies] +erpc_rust = { version = "0.1.0", features = ["serial"] } +``` + +### Serial Connection Examples + +```rust +use erpc_rust::client::{ClientManager, CodecConfig}; + +// Serial with default baud rate (115200) +let client = ClientManager::builder() + .serial_connection("/dev/ttyUSB0") + .codec(CodecConfig::Basic) + .connect().await?; + +// Serial with custom baud rate +let client = ClientManager::builder() + .serial_connection_with_baud("/dev/ttyACM0", 9600) + .codec(CodecConfig::Basic) + .connect().await?; +``` + +## Transport Options + +### TCP Transport (Primary Focus) +The main transport implementation providing: +- Reliable network communication +- Framed message support with CRC validation +- Connection management and error handling +- Compatible with eRPC TCP implementations in other languages + +### Additional Transports +- **Memory Transport**: For testing and in-process communication +- **Serial Transport**: Available with `serial` feature for embedded applications + +## Features + +### Default Features +- `tcp`: TCP transport (always enabled) +- `serial`: Serial port transport (optional, requires system dependencies) + +### Building with specific features: +```bash +# TCP only (default) +cargo build --no-default-features --features tcp + +# TCP + Serial +cargo build --features "tcp,serial" +``` diff --git a/erpc_rust/src/auxiliary.rs b/erpc_rust/src/auxiliary.rs new file mode 100644 index 000000000..ebdec2168 --- /dev/null +++ b/erpc_rust/src/auxiliary.rs @@ -0,0 +1,385 @@ +//! Auxiliary types and utilities for eRPC + +use serde::{Deserialize, Serialize}; +use std::fmt; + +/// Maximum number of nodes supported +pub const MAX_NODES: u32 = 16; + +/// Heartbeat timeout in seconds +pub const HEARTBEAT_TIMEOUT: u32 = 30; + +/// Maximum packages per agent +pub const MAX_PACKAGES_PER_AGENT: u32 = 64; + +/// Message types for eRPC communication +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[repr(u8)] +pub enum MessageType { + /// Invocation message (expects reply) + Invocation = 0, + /// One-way message (no reply expected) + Oneway = 1, + /// Reply message + Reply = 2, + /// Notification message + Notification = 3, +} + +impl MessageType { + /// Convert from u8 value + pub fn from_u8(value: u8) -> Option { + match value { + 0 => Some(MessageType::Invocation), + 1 => Some(MessageType::Oneway), + 2 => Some(MessageType::Reply), + 3 => Some(MessageType::Notification), + _ => None, + } + } + + /// Convert to u8 value + pub fn to_u8(self) -> u8 { + self as u8 + } +} + +impl fmt::Display for MessageType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + MessageType::Invocation => write!(f, "Invocation"), + MessageType::Oneway => write!(f, "Oneway"), + MessageType::Reply => write!(f, "Reply"), + MessageType::Notification => write!(f, "Notification"), + } + } +} + +/// Message information containing metadata about an eRPC message +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MessageInfo { + /// Message type + pub message_type: MessageType, + /// Service ID + pub service: u8, + /// Request/method ID + pub request: u8, + /// Sequence number + pub sequence: u32, +} + +impl MessageInfo { + /// Create new message info + pub fn new(message_type: MessageType, service: u8, request: u8, sequence: u32) -> Self { + Self { + message_type, + service, + request, + sequence, + } + } +} + +/// Request context for managing RPC calls +#[derive(Debug, Clone)] +pub struct RequestContext { + /// Sequence number for this request + pub sequence: u32, + /// Whether this is a one-way request + pub is_oneway: bool, + /// Service ID (optional) + pub service_id: Option, + /// Message buffer/codec data + pub buffer: Vec, +} + +impl RequestContext { + /// Create new request context (original signature for compatibility) + pub fn new(sequence: u32, is_oneway: bool) -> Self { + Self { + sequence, + is_oneway, + service_id: None, + buffer: Vec::new(), + } + } + + /// Create new request context with service ID + pub fn with_service(sequence: u32, service_id: Option, is_oneway: bool) -> Self { + Self { + sequence, + is_oneway, + service_id, + buffer: Vec::new(), + } + } + + /// Check if request is one-way + pub fn is_oneway(&self) -> bool { + self.is_oneway + } + + /// Get sequence number + pub fn sequence(&self) -> u32 { + self.sequence + } + + /// Get service ID + pub fn service_id(&self) -> Option { + self.service_id + } + + /// Set codec data + pub fn set_codec_data(&mut self, data: Vec) { + self.buffer = data; + } + + /// Get codec data + pub fn codec_data(&self) -> &[u8] { + &self.buffer + } + + /// Take codec data (consumes the data) + pub fn take_codec_data(self) -> Vec { + self.buffer + } +} + +/// Reference wrapper for optional values +#[derive(Debug, Clone)] +pub struct Reference { + value: Option, +} + +impl Reference { + /// Create new reference with value + pub fn new(value: T) -> Self { + Self { value: Some(value) } + } + + /// Create empty reference + pub fn empty() -> Self { + Self { value: None } + } + + /// Check if reference has value + pub fn is_some(&self) -> bool { + self.value.is_some() + } + + /// Check if reference is empty + pub fn is_none(&self) -> bool { + self.value.is_none() + } + + /// Get reference to value + pub fn as_ref(&self) -> Option<&T> { + self.value.as_ref() + } + + /// Get mutable reference to value + pub fn as_mut(&mut self) -> Option<&mut T> { + self.value.as_mut() + } + + /// Take value out of reference + pub fn take(&mut self) -> Option { + self.value.take() + } +} + +impl From> for Reference { + fn from(value: Option) -> Self { + Self { value } + } +} + +impl From for Reference { + fn from(value: T) -> Self { + Self::new(value) + } +} + +/// Utility functions for type conversions and validation +pub mod utils { + use crate::error::{ErpcError, ErpcResult}; + + /// Check if uint8 value is in valid range + pub fn check_uint8(value: u64) -> ErpcResult<()> { + if value > u8::MAX as u64 { + Err(ErpcError::InvalidValue(format!( + "Value has to be in range from 0 to 2^8, but was {value}" + ))) + } else { + Ok(()) + } + } + + /// Check if uint16 value is in valid range + pub fn check_uint16(value: u64) -> ErpcResult<()> { + if value > u16::MAX as u64 { + Err(ErpcError::InvalidValue(format!( + "Value has to be in range from 0 to 2^16, but was {value}" + ))) + } else { + Ok(()) + } + } + + /// Check if uint32 value is in valid range + pub fn check_uint32(value: u64) -> ErpcResult<()> { + if value > u32::MAX as u64 { + Err(ErpcError::InvalidValue(format!( + "Value has to be in range from 0 to 2^32, but was {value}" + ))) + } else { + Ok(()) + } + } + + /// Check if object is not null + pub fn check_not_null(value: Option, message: &str) -> ErpcResult { + value.ok_or_else(|| ErpcError::InvalidValue(message.to_string())) + } + + /// Convert uint32 to i32 preserving bits + pub fn uint32_to_int(value: u32) -> i32 { + value as i32 + } + + /// Convert uint16 to i16 preserving bits + pub fn uint16_to_short(value: u16) -> i16 { + value as i16 + } + + /// Convert uint8 to i8 preserving bits + pub fn uint8_to_byte(value: u8) -> i8 { + value as i8 + } + + /// Convert i32 to uint32 preserving bits + pub fn int_to_uint32(value: i32) -> u32 { + value as u32 + } + + /// Convert i16 to uint16 preserving bits + pub fn short_to_uint16(value: i16) -> u16 { + value as u16 + } + + /// Convert i8 to uint8 preserving bits + pub fn byte_to_uint8(value: i8) -> u8 { + value as u8 + } + + /// Convert uint16 to little-endian bytes + pub fn uint16_to_bytes(value: u16) -> [u8; 2] { + value.to_le_bytes() + } + + /// Convert byte array to hex string + pub fn byte_array_to_hex(data: &[u8]) -> String { + data.iter().map(|b| format!("{b:02x}")).collect::() + } + + /// Convert hex string to byte array + pub fn hex_to_byte_array(hex: &str) -> ErpcResult> { + if hex.len() % 2 != 0 { + return Err(ErpcError::InvalidValue( + "Hex string must have even length".to_string(), + )); + } + + hex.chars() + .collect::>() + .chunks(2) + .map(|chunk| { + let hex_byte = chunk.iter().collect::(); + u8::from_str_radix(&hex_byte, 16) + .map_err(|_| ErpcError::InvalidValue(format!("Invalid hex string: {hex}"))) + }) + .collect() + } + + /// Convert uint8 to i8 for storage + pub fn uint8_to_i8(value: u8) -> i8 { + value as i8 + } + + /// Convert i8 back to uint8 + pub fn i8_to_uint8(value: i8) -> u8 { + value as u8 + } + + /// Convert uint16 to i16 for storage + pub fn uint16_to_i16(value: u16) -> i16 { + value as i16 + } + + /// Convert i16 back to uint16 + pub fn i16_to_uint16(value: i16) -> u16 { + value as u16 + } + + /// Convert uint32 to i32 for storage + pub fn uint32_to_i32(value: u32) -> i32 { + value as i32 + } + + /// Convert i32 back to uint32 + pub fn i32_to_uint32(value: i32) -> u32 { + value as u32 + } +} + +/// CRC-16 implementation for message integrity +pub mod crc16 { + /// CRC-16 polynomial + const CRC16_POLY: u16 = 0x1021; + + /// Default CRC start value + const CRC_START: u16 = 0xEF4A; + + /// Precomputed CRC table for efficiency + static CRC_TABLE: [u16; 256] = compute_crc_table(); + + /// Compute CRC table at compile time + const fn compute_crc_table() -> [u16; 256] { + let mut table = [0u16; 256]; + let mut i = 0; + while i < 256 { + let mut crc = 0u16; + let mut y = (i as u16) << 8; + let mut b = 0; + while b < 8 { + let temp = crc ^ y; + crc <<= 1; + if temp & 0x8000 != 0 { + crc ^= CRC16_POLY; + } + y <<= 1; + b += 1; + } + table[i] = crc; + i += 1; + } + table + } + + /// Calculate CRC-16 checksum using lookup table + pub fn calculate(data: &[u8]) -> u16 { + let mut crc = CRC_START; + + for &byte in data { + let index = ((crc >> 8) ^ (byte as u16)) & 0xFF; + crc = ((crc << 8) ^ CRC_TABLE[index as usize]); + } + + crc + } + + /// Verify CRC-16 checksum + pub fn verify(data: &[u8], expected_crc: u16) -> bool { + calculate(data) == expected_crc + } +} diff --git a/erpc_rust/src/client.rs b/erpc_rust/src/client.rs new file mode 100644 index 000000000..8319ae40e --- /dev/null +++ b/erpc_rust/src/client.rs @@ -0,0 +1,440 @@ +//! Client implementation for eRPC + +use crate::auxiliary::{MessageInfo, MessageType, RequestContext}; +use crate::codec::{BasicCodecFactory, Codec, CodecFactory}; +use crate::error::{ErpcResult, RequestError}; +#[cfg(feature = "serial")] +use crate::transport::SerialTransport; +#[cfg(unix)] +use crate::transport::SocketTransport; +use crate::transport::{TcpTransport, Transport}; +use async_trait::async_trait; +use std::sync::atomic::{AtomicU32, Ordering}; +use std::sync::Arc; +use std::time::Duration; + +/// Transport wrapper that can hold different transport types +pub enum TransportWrapper { + Tcp(TcpTransport), + #[cfg(unix)] + Socket(SocketTransport), + #[cfg(feature = "serial")] + Serial(SerialTransport), +} + +#[async_trait] +impl Transport for TransportWrapper { + async fn send(&mut self, data: &[u8]) -> ErpcResult<()> { + match self { + TransportWrapper::Tcp(tcp) => tcp.send(data).await, + #[cfg(unix)] + TransportWrapper::Socket(socket) => socket.send(data).await, + #[cfg(feature = "serial")] + TransportWrapper::Serial(serial) => serial.send(data).await, + } + } + + async fn receive(&mut self) -> ErpcResult> { + match self { + TransportWrapper::Tcp(tcp) => tcp.receive().await, + #[cfg(unix)] + TransportWrapper::Socket(socket) => socket.receive().await, + #[cfg(feature = "serial")] + TransportWrapper::Serial(serial) => serial.receive().await, + } + } + + async fn close(&mut self) -> ErpcResult<()> { + match self { + TransportWrapper::Tcp(tcp) => tcp.close().await, + #[cfg(unix)] + TransportWrapper::Socket(socket) => socket.close().await, + #[cfg(feature = "serial")] + TransportWrapper::Serial(serial) => serial.close().await, + } + } + + fn is_connected(&self) -> bool { + match self { + TransportWrapper::Tcp(tcp) => tcp.is_connected(), + #[cfg(unix)] + TransportWrapper::Socket(socket) => socket.is_connected(), + #[cfg(feature = "serial")] + TransportWrapper::Serial(serial) => serial.is_connected(), + } + } + + fn set_timeout(&mut self, timeout: Duration) { + match self { + TransportWrapper::Tcp(tcp) => tcp.set_timeout(timeout), + #[cfg(unix)] + TransportWrapper::Socket(socket) => socket.set_timeout(timeout), + #[cfg(feature = "serial")] + TransportWrapper::Serial(serial) => serial.set_timeout(timeout), + } + } +} + +/// Client manager for making RPC calls +pub struct ClientManager +where + T: Transport, + F: CodecFactory, +{ + transport: T, + codec_factory: F, + sequence_counter: Arc, +} + +impl ClientManager +where + T: Transport, + F: CodecFactory, +{ + /// Create new client manager + pub fn new(transport: T, codec_factory: F) -> Self { + Self { + transport, + codec_factory, + sequence_counter: Arc::new(AtomicU32::new(0)), + } + } + + /// Create a new client manager builder + pub fn generic_builder() -> ClientManagerBuilder { + ClientManagerBuilder::new() + } + + /// Get next sequence number + fn next_sequence(&self) -> u32 { + self.sequence_counter.fetch_add(1, Ordering::SeqCst) + 1 + } + + /// Create a new request context + pub fn create_request(&self, is_oneway: bool) -> RequestContext { + let sequence = self.next_sequence(); + RequestContext::new(sequence, is_oneway) + } + + /// Create a new request context with service ID (enhanced) + pub fn create_request_with_service(&self, service_id: u32, is_oneway: bool) -> RequestContext { + let sequence = self.next_sequence(); + RequestContext::with_service(sequence, Some(service_id), is_oneway) + } + + /// Perform a request-response call for generated code (4 parameters) + pub async fn perform_request( + &mut self, + service_id: u8, + method_id: u8, + is_oneway: bool, + request_data: Vec, + ) -> ErpcResult> { + let sequence = self.next_sequence(); + + // Create message + let message_type = if is_oneway { + MessageType::Oneway + } else { + MessageType::Invocation + }; + + let message_info = MessageInfo::new(message_type, service_id, method_id, sequence); + + // Encode request + let mut codec = self.codec_factory.create(); + codec.start_write_message(&message_info)?; + codec.write_bytes(&request_data)?; + + // Send request + self.transport.send(codec.as_bytes()).await?; + + if is_oneway { + return Ok(Vec::new()); + } + + // Receive response + let response_data = self.transport.receive().await?; + let mut response_codec = self.codec_factory.create_from_data(response_data); + + // Validate response + let response_info = response_codec.start_read_message()?; + + if response_info.message_type != MessageType::Reply { + return Err(RequestError::InvalidMessageType.into()); + } + + if response_info.sequence != sequence { + return Err(RequestError::UnexpectedSequence { + expected: sequence, + actual: response_info.sequence, + } + .into()); + } + + // Read response data + let response_payload = response_codec.get_remaining_bytes()?; + Ok(response_payload) + } + + /// Simplified perform request for direct use (2 parameters) + pub async fn send_raw_request( + &mut self, + request_data: &[u8], + is_oneway: bool, + ) -> ErpcResult> { + let _context = self.create_request(is_oneway); + + // Write request data to transport + self.transport.send(request_data).await?; + + if is_oneway { + // For oneway calls, don't wait for response + Ok(Vec::new()) + } else { + // For regular calls, wait for response + self.transport.receive().await + } + } + + /// Send a request without waiting for response + pub async fn send_request(&mut self, request_data: &[u8]) -> ErpcResult<()> { + self.send_raw_request(request_data, true).await?; + Ok(()) + } + + /// Receive a response for a previous request + pub async fn receive_response(&mut self) -> ErpcResult> { + self.transport.receive().await + } + + /// Get the codec factory + pub fn codec_factory(&self) -> &F { + &self.codec_factory + } + + /// Check if client is connected + pub fn is_connected(&self) -> bool { + self.transport.is_connected() + } + + /// Close the client connection + pub async fn close(&mut self) -> ErpcResult<()> { + self.transport.close().await + } +} + +/// Builder for creating ClientManager with configurable transport and codec +pub struct ClientBuilder { + transport_config: Option, + codec_config: Option, +} + +/// Transport configuration options +pub enum TransportConfig { + Tcp(String), + #[cfg(unix)] + Socket(String), + #[cfg(feature = "serial")] + Serial { + port: String, + baud_rate: u32, + }, +} + +/// Codec configuration options +pub enum CodecConfig { + Basic, + // Future: Custom(Box), +} + +impl ClientBuilder { + /// Create a new builder with default (null) transport and codec + pub fn new() -> Self { + Self { + transport_config: None, + codec_config: None, + } + } + + /// Configure TCP connection + pub fn tcp_connection(mut self, address: &str) -> Self { + self.transport_config = Some(TransportConfig::Tcp(address.to_string())); + self + } + + /// Configure Unix socket connection + #[cfg(unix)] + pub fn socket_connection(mut self, path: &str) -> Self { + self.transport_config = Some(TransportConfig::Socket(path.to_string())); + self + } + + /// Configure serial connection + pub fn serial_connection(mut self, port: &str) -> Self { + self.serial_connection_with_baud(port, 115200) // Default baud rate + } + + /// Configure serial connection with custom baud rate + #[cfg(feature = "serial")] + pub fn serial_connection_with_baud(mut self, port: &str, baud_rate: u32) -> Self { + self.transport_config = Some(TransportConfig::Serial { + port: port.to_string(), + baud_rate, + }); + self + } + + /// Configure serial connection with custom baud rate (no-op when serial feature disabled) + #[cfg(not(feature = "serial"))] + pub fn serial_connection_with_baud(self, _port: &str, _baud_rate: u32) -> Self { + // No-op when serial feature is disabled + self + } + + /// Configure codec + pub fn codec(mut self, codec: CodecConfig) -> Self { + self.codec_config = Some(codec); + self + } + + /// Connect and build the ClientManager + pub async fn connect(self) -> ErpcResult> { + let transport = match self.transport_config { + Some(TransportConfig::Tcp(address)) => { + let tcp = TcpTransport::connect(&address).await?; + TransportWrapper::Tcp(tcp) + } + #[cfg(unix)] + Some(TransportConfig::Socket(path)) => { + let socket = SocketTransport::connect(&path).await?; + TransportWrapper::Socket(socket) + } + #[cfg(feature = "serial")] + Some(TransportConfig::Serial { port, baud_rate }) => { + let serial = SerialTransport::open(&port, baud_rate)?; + TransportWrapper::Serial(serial) + } + None => return Err(RequestError::InvalidServiceId(0).into()), // Transport not configured + }; + + let codec_factory = match self.codec_config { + Some(CodecConfig::Basic) | None => BasicCodecFactory::new(), // Default to Basic + }; + + Ok(ClientManager::new(transport, codec_factory)) + } +} + +impl Default for ClientBuilder { + fn default() -> Self { + Self::new() + } +} + +// Add static method to ClientManager for builder access +impl ClientManager { + /// Create a new builder for configuring ClientManager + pub fn builder() -> ClientBuilder { + ClientBuilder::new() + } +} + +/// Builder for creating client managers +pub struct ClientManagerBuilder +where + T: Transport, + F: CodecFactory, +{ + transport: Option, + codec_factory: Option, +} + +impl ClientManagerBuilder +where + T: Transport, + F: CodecFactory, +{ + /// Create new builder + pub fn new() -> Self { + Self { + transport: None, + codec_factory: None, + } + } + + /// Set transport + pub fn transport(mut self, transport: T) -> Self { + self.transport = Some(transport); + self + } + + /// Set codec factory + pub fn codec_factory(mut self, codec_factory: F) -> Self { + self.codec_factory = Some(codec_factory); + self + } + + /// Build client manager + pub fn build(self) -> Result, &'static str> { + let transport = self.transport.ok_or("Transport not set")?; + let codec_factory = self.codec_factory.ok_or("Codec factory not set")?; + + Ok(ClientManager::new(transport, codec_factory)) + } +} + +impl Default for ClientManagerBuilder +where + T: Transport, + F: CodecFactory, +{ + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::codec::BasicCodecFactory; + use crate::transport::memory::MemoryTransport; + + #[tokio::test] + async fn test_client_manager_creation() { + let (transport, _) = MemoryTransport::pair(); + let codec_factory = BasicCodecFactory::new(); + + let client = ClientManager::new(transport, codec_factory); + assert!(client.is_connected()); + } + + #[tokio::test] + async fn test_request_context() { + let (transport, _) = MemoryTransport::pair(); + let codec_factory = BasicCodecFactory::new(); + let client = ClientManager::new(transport, codec_factory); + + let ctx = client.create_request(false); + assert!(!ctx.is_oneway()); + assert_eq!(ctx.sequence(), 1); + + let ctx2 = client.create_request(true); + assert!(ctx2.is_oneway()); + assert_eq!(ctx2.sequence(), 2); + } + + #[tokio::test] + async fn test_client_builder() { + let (transport, _) = MemoryTransport::pair(); + let codec_factory = BasicCodecFactory::new(); + + let client = ClientManagerBuilder::new() + .transport(transport) + .codec_factory(codec_factory) + .build() + .unwrap(); + + assert!(client.is_connected()); + } +} diff --git a/erpc_rust/src/codec.rs b/erpc_rust/src/codec.rs new file mode 100644 index 000000000..996087894 --- /dev/null +++ b/erpc_rust/src/codec.rs @@ -0,0 +1,463 @@ +//! Codec implementation for eRPC message serialization/deserialization + +use crate::auxiliary::{MessageInfo, MessageType}; +use crate::error::{CodecError, ErpcResult}; +use byteorder::{ByteOrder, LittleEndian, ReadBytesExt, WriteBytesExt}; +use std::io::{Cursor, Write}; + +/// Basic codec version +const BASIC_CODEC_VERSION: u8 = 1; + +/// Default buffer size for codec +const DEFAULT_BUFFER_SIZE: usize = 256; + +/// Codec trait for message serialization/deserialization +pub trait Codec: Send + Sync { + /// Reset the codec buffer + fn reset(&mut self); + + /// Get the current buffer as a byte slice + fn as_bytes(&self) -> &[u8]; + + /// Set the buffer from a byte array + fn set_buffer(&mut self, data: Vec); + + /// Start writing a message with header + fn start_write_message(&mut self, info: &MessageInfo) -> ErpcResult<()>; + + /// Write boolean value + fn write_bool(&mut self, value: bool) -> ErpcResult<()>; + + /// Write signed 8-bit integer + fn write_int8(&mut self, value: i8) -> ErpcResult<()>; + + /// Write signed 16-bit integer + fn write_int16(&mut self, value: i16) -> ErpcResult<()>; + + /// Write signed 32-bit integer + fn write_int32(&mut self, value: i32) -> ErpcResult<()>; + + /// Write signed 64-bit integer + fn write_int64(&mut self, value: i64) -> ErpcResult<()>; + + /// Write unsigned 8-bit integer + fn write_uint8(&mut self, value: u8) -> ErpcResult<()>; + + /// Write unsigned 16-bit integer + fn write_uint16(&mut self, value: u16) -> ErpcResult<()>; + + /// Write unsigned 32-bit integer + fn write_uint32(&mut self, value: u32) -> ErpcResult<()>; + + /// Write unsigned 64-bit integer + fn write_uint64(&mut self, value: u64) -> ErpcResult<()>; + + /// Write 32-bit float + fn write_float(&mut self, value: f32) -> ErpcResult<()>; + + /// Write 64-bit double + fn write_double(&mut self, value: f64) -> ErpcResult<()>; + + /// Write string + fn write_string(&mut self, value: &str) -> ErpcResult<()>; + + /// Write binary data + fn write_binary(&mut self, value: &[u8]) -> ErpcResult<()>; + + /// Write raw bytes without length prefix + fn write_bytes(&mut self, value: &[u8]) -> ErpcResult<()>; + + /// Get remaining bytes from current read position + fn get_remaining_bytes(&mut self) -> ErpcResult>; + + /// Start writing a list with length + fn start_write_list(&mut self, length: u32) -> ErpcResult<()>; + + /// Start writing a union with discriminator + fn start_write_union(&mut self, discriminator: u32) -> ErpcResult<()>; + + /// Write null flag + fn write_null_flag(&mut self, value: bool) -> ErpcResult<()>; + + /// Start reading a message and return header info + fn start_read_message(&mut self) -> ErpcResult; + + /// Read boolean value + fn read_bool(&mut self) -> ErpcResult; + + /// Read signed 8-bit integer + fn read_int8(&mut self) -> ErpcResult; + + /// Read signed 16-bit integer + fn read_int16(&mut self) -> ErpcResult; + + /// Read signed 32-bit integer + fn read_int32(&mut self) -> ErpcResult; + + /// Read signed 64-bit integer + fn read_int64(&mut self) -> ErpcResult; + + /// Read unsigned 8-bit integer + fn read_uint8(&mut self) -> ErpcResult; + + /// Read unsigned 16-bit integer + fn read_uint16(&mut self) -> ErpcResult; + + /// Read unsigned 32-bit integer + fn read_uint32(&mut self) -> ErpcResult; + + /// Read unsigned 64-bit integer + fn read_uint64(&mut self) -> ErpcResult; + + /// Read 32-bit float + fn read_float(&mut self) -> ErpcResult; + + /// Read 64-bit double + fn read_double(&mut self) -> ErpcResult; + + /// Read string + fn read_string(&mut self) -> ErpcResult; + + /// Read binary data + fn read_binary(&mut self) -> ErpcResult>; + + /// Start reading a list and return length + fn start_read_list(&mut self) -> ErpcResult; + + /// Start reading a union and return discriminator + fn start_read_union(&mut self) -> ErpcResult; + + /// Read null flag + fn read_null_flag(&mut self) -> ErpcResult; +} + +/// Basic codec implementation using little-endian byte order +pub struct BasicCodec { + buffer: Vec, + read_cursor: Cursor>, + write_position: usize, +} + +impl BasicCodec { + /// Create a new basic codec + pub fn new() -> Self { + Self { + buffer: Vec::with_capacity(DEFAULT_BUFFER_SIZE), + read_cursor: Cursor::new(Vec::new()), + write_position: 0, + } + } + + /// Create codec from existing data + pub fn from_data(data: Vec) -> Self { + let read_cursor = Cursor::new(data.clone()); + Self { + buffer: data, + read_cursor, + write_position: 0, + } + } + + /// Ensure buffer has enough capacity for writing + fn ensure_capacity(&mut self, additional: usize) { + let required = self.write_position + additional; + if self.buffer.len() < required { + self.buffer.resize(required, 0); + } + } +} + +impl Default for BasicCodec { + fn default() -> Self { + Self::new() + } +} + +impl Codec for BasicCodec { + fn reset(&mut self) { + self.buffer.clear(); + self.buffer.resize(DEFAULT_BUFFER_SIZE, 0); + self.read_cursor = Cursor::new(Vec::new()); + self.write_position = 0; + } + + fn as_bytes(&self) -> &[u8] { + &self.buffer[..self.write_position] + } + + fn set_buffer(&mut self, data: Vec) { + self.read_cursor = Cursor::new(data.clone()); + self.buffer = data; + self.write_position = 0; + } + + fn start_write_message(&mut self, info: &MessageInfo) -> ErpcResult<()> { + // When written as little-endian 32-bit, the wire format becomes: + // [version][request][service][type][sequence_le] + let header = ((BASIC_CODEC_VERSION as u32) << 24) + | ((info.service as u32) << 16) + | ((info.request as u32) << 8) + | (info.message_type.to_u8() as u32); + + self.write_uint32(header)?; + self.write_uint32(info.sequence)?; + Ok(()) + } + + fn write_bool(&mut self, value: bool) -> ErpcResult<()> { + self.write_uint8(if value { 1 } else { 0 }) + } + + fn write_int8(&mut self, value: i8) -> ErpcResult<()> { + self.write_bytes(&[value as u8]) + } + + fn write_int16(&mut self, value: i16) -> ErpcResult<()> { + let mut bytes = [0u8; 2]; + LittleEndian::write_i16(&mut bytes, value); + self.write_bytes(&bytes) + } + + fn write_int32(&mut self, value: i32) -> ErpcResult<()> { + let mut bytes = [0u8; 4]; + LittleEndian::write_i32(&mut bytes, value); + self.write_bytes(&bytes) + } + + fn write_int64(&mut self, value: i64) -> ErpcResult<()> { + let mut bytes = [0u8; 8]; + LittleEndian::write_i64(&mut bytes, value); + self.write_bytes(&bytes) + } + + fn write_uint8(&mut self, value: u8) -> ErpcResult<()> { + self.write_bytes(&[value]) + } + + fn write_uint16(&mut self, value: u16) -> ErpcResult<()> { + let mut bytes = [0u8; 2]; + LittleEndian::write_u16(&mut bytes, value); + self.write_bytes(&bytes) + } + + fn write_uint32(&mut self, value: u32) -> ErpcResult<()> { + let mut bytes = [0u8; 4]; + LittleEndian::write_u32(&mut bytes, value); + self.write_bytes(&bytes) + } + + fn write_uint64(&mut self, value: u64) -> ErpcResult<()> { + let mut bytes = [0u8; 8]; + LittleEndian::write_u64(&mut bytes, value); + self.write_bytes(&bytes) + } + + fn write_float(&mut self, value: f32) -> ErpcResult<()> { + let mut bytes = [0u8; 4]; + LittleEndian::write_f32(&mut bytes, value); + self.write_bytes(&bytes) + } + + fn write_double(&mut self, value: f64) -> ErpcResult<()> { + let mut bytes = [0u8; 8]; + LittleEndian::write_f64(&mut bytes, value); + self.write_bytes(&bytes) + } + + fn write_string(&mut self, value: &str) -> ErpcResult<()> { + self.write_binary(value.as_bytes()) + } + + fn write_binary(&mut self, value: &[u8]) -> ErpcResult<()> { + self.write_int32(value.len() as i32)?; + self.write_bytes(value) + } + + fn write_bytes(&mut self, value: &[u8]) -> ErpcResult<()> { + self.ensure_capacity(value.len()); + let end = self.write_position + value.len(); + self.buffer[self.write_position..end].copy_from_slice(value); + self.write_position = end; + Ok(()) + } + + fn get_remaining_bytes(&mut self) -> ErpcResult> { + let current_pos = self.read_cursor.position() as usize; + let buffer = self.read_cursor.get_ref(); + if current_pos <= buffer.len() { + Ok(buffer[current_pos..].to_vec()) + } else { + Ok(Vec::new()) + } + } + + fn start_write_list(&mut self, length: u32) -> ErpcResult<()> { + self.write_uint32(length) + } + + fn start_write_union(&mut self, discriminator: u32) -> ErpcResult<()> { + self.write_uint32(discriminator) + } + + fn write_null_flag(&mut self, value: bool) -> ErpcResult<()> { + self.write_uint32(if value { 1 } else { 0 }) + } + + fn start_read_message(&mut self) -> ErpcResult { + let header = self.read_uint32()?; + let sequence = self.read_uint32()?; + + let version = ((header >> 24) & 0xff) as u8; + if version != BASIC_CODEC_VERSION { + return Err(CodecError::UnsupportedVersion(version).into()); + } + + let service = ((header >> 16) & 0xff) as u8; + let request = ((header >> 8) & 0xff) as u8; + let message_type_raw = (header & 0xff) as u8; + + let message_type = MessageType::from_u8(message_type_raw).ok_or_else(|| { + CodecError::InvalidFormat(format!("Invalid message type: {message_type_raw}")) + })?; + + Ok(MessageInfo::new(message_type, service, request, sequence)) + } + + fn read_bool(&mut self) -> ErpcResult { + let value = self.read_uint8()?; + Ok(value != 0) + } + + fn read_int8(&mut self) -> ErpcResult { + self.read_cursor + .read_i8() + .map_err(|_| CodecError::BufferUnderflow.into()) + } + + fn read_int16(&mut self) -> ErpcResult { + self.read_cursor + .read_i16::() + .map_err(|_| CodecError::BufferUnderflow.into()) + } + + fn read_int32(&mut self) -> ErpcResult { + self.read_cursor + .read_i32::() + .map_err(|_| CodecError::BufferUnderflow.into()) + } + + fn read_int64(&mut self) -> ErpcResult { + self.read_cursor + .read_i64::() + .map_err(|_| CodecError::BufferUnderflow.into()) + } + + fn read_uint8(&mut self) -> ErpcResult { + self.read_cursor + .read_u8() + .map_err(|_| CodecError::BufferUnderflow.into()) + } + + fn read_uint16(&mut self) -> ErpcResult { + self.read_cursor + .read_u16::() + .map_err(|_| CodecError::BufferUnderflow.into()) + } + + fn read_uint32(&mut self) -> ErpcResult { + self.read_cursor + .read_u32::() + .map_err(|_| CodecError::BufferUnderflow.into()) + } + + fn read_uint64(&mut self) -> ErpcResult { + self.read_cursor + .read_u64::() + .map_err(|_| CodecError::BufferUnderflow.into()) + } + + fn read_float(&mut self) -> ErpcResult { + self.read_cursor + .read_f32::() + .map_err(|_| CodecError::BufferUnderflow.into()) + } + + fn read_double(&mut self) -> ErpcResult { + self.read_cursor + .read_f64::() + .map_err(|_| CodecError::BufferUnderflow.into()) + } + + fn read_string(&mut self) -> ErpcResult { + let data = self.read_binary()?; + String::from_utf8(data) + .map_err(|e| CodecError::InvalidFormat(format!("Invalid UTF-8: {e}")).into()) + } + + fn read_binary(&mut self) -> ErpcResult> { + let length = self.read_int32()? as usize; + + // Validate length to prevent DoS attacks + if length > 1024 * 1024 { + return Err(CodecError::InvalidFormat("Binary data too large".to_string()).into()); + } + + let mut data = vec![0u8; length]; + std::io::Read::read_exact(&mut self.read_cursor, &mut data) + .map_err(|_| CodecError::BufferUnderflow)?; + Ok(data) + } + + fn start_read_list(&mut self) -> ErpcResult { + self.read_uint32() + } + + fn start_read_union(&mut self) -> ErpcResult { + self.read_uint32() + } + + fn read_null_flag(&mut self) -> ErpcResult { + let value = self.read_uint32()?; + Ok(value != 0) + } +} + +/// Factory for creating codec instances +pub trait CodecFactory: Send + Sync { + type Codec: Codec; + + /// Create a new codec instance + fn create(&self) -> Self::Codec; + + /// Create codec from existing data + fn create_from_data(&self, data: Vec) -> Self::Codec; +} + +/// Basic codec factory +#[derive(Debug, Clone)] +pub struct BasicCodecFactory; + +impl BasicCodecFactory { + /// Create new basic codec factory + pub fn new() -> Self { + Self + } +} + +impl Default for BasicCodecFactory { + fn default() -> Self { + Self::new() + } +} + +impl CodecFactory for BasicCodecFactory { + type Codec = BasicCodec; + + fn create(&self) -> Self::Codec { + BasicCodec::new() + } + + fn create_from_data(&self, data: Vec) -> Self::Codec { + BasicCodec::from_data(data) + } +} diff --git a/erpc_rust/src/error.rs b/erpc_rust/src/error.rs new file mode 100644 index 000000000..d98be6cdd --- /dev/null +++ b/erpc_rust/src/error.rs @@ -0,0 +1,120 @@ +//! Error types for the eRPC Rust implementation + +use thiserror::Error; + +/// Result type for eRPC operations +pub type ErpcResult = Result; + +/// Main error type for eRPC operations +#[derive(Error, Debug)] +pub enum ErpcError { + /// Transport layer errors + #[error("Transport error: {0}")] + Transport(#[from] TransportError), + + /// Codec errors during serialization/deserialization + #[error("Codec error: {0}")] + Codec(#[from] CodecError), + + /// Serialization/deserialization errors + #[error("Serialization error: {0}")] + Serialization(#[from] SerializationError), + + /// Request processing errors + #[error("Request error: {0}")] + Request(#[from] RequestError), + + /// I/O errors + #[error("I/O error: {0}")] + Io(#[from] std::io::Error), + + /// Invalid value error (for validation) + #[error("Invalid value: {0}")] + InvalidValue(String), + + /// Generic error with message + #[error("{0}")] + Other(String), +} + +/// Transport layer specific errors +#[derive(Error, Debug)] +pub enum TransportError { + #[error("Connection failed: {0}")] + ConnectionFailed(String), + + #[error("Send failed: {0}")] + SendFailed(String), + + #[error("Receive failed: {0}")] + ReceiveFailed(String), + + #[error("Transport is closed")] + Closed, + + #[error("Timeout occurred")] + Timeout, +} + +/// Codec specific errors +#[derive(Error, Debug)] +pub enum CodecError { + #[error("Unsupported codec version: {0}")] + UnsupportedVersion(u8), + + #[error("Invalid message format: {0}")] + InvalidFormat(String), + + #[error("Buffer overflow")] + BufferOverflow, + + #[error("Buffer underflow")] + BufferUnderflow, + + #[error("Invalid parameter: {0}")] + InvalidParameter(String), + + #[error("Operation not supported: {0}")] + NotSupported(String), +} + +/// Serialization/Deserialization specific errors +#[derive(Error, Debug)] +pub enum SerializationError { + #[error("Serialization failed: {0}")] + SerializationFailed(String), + + #[error("Deserialization failed: {0}")] + DeserializationFailed(String), + + #[error("Invalid enum value: {value} for type {type_name}")] + InvalidEnumValue { value: i32, type_name: String }, + + #[error("Missing required field: {0}")] + MissingField(String), + + #[error("Type mismatch: expected {expected}, found {found}")] + TypeMismatch { expected: String, found: String }, + + #[error("Invalid data format: {0}")] + InvalidDataFormat(String), +} + +/// Request processing errors +#[derive(Error, Debug)] +pub enum RequestError { + #[error("Invalid message type")] + InvalidMessageType, + + #[error("Unexpected sequence number: expected {expected}, got {actual}")] + UnexpectedSequence { expected: u32, actual: u32 }, + + #[error("Invalid service ID: {0}")] + InvalidServiceId(u32), + + #[error("Invalid method ID: {0}")] + InvalidMethodId(u32), + + #[error("Method implementation error: {0}")] + MethodError(String), +} diff --git a/erpc_rust/src/lib.rs b/erpc_rust/src/lib.rs new file mode 100644 index 000000000..d1619a67d --- /dev/null +++ b/erpc_rust/src/lib.rs @@ -0,0 +1,58 @@ +//! # eRPC Rust Implementation +//! +//! A pure Rust implementation of the eRPC (Embedded RPC) protocol, +//! providing client and server functionality with multiple transport options. +//! +//! ## Features +//! +//! - **Transport Layer**: TCP, Serial, and in-memory transports +//! - **Codec**: Binary protocol with little-endian encoding +//! - **Client/Server**: Async client manager and server implementations +//! - **Message Types**: Support for invocation, oneway, reply, and notification messages +//! +//! ## Example +//! +//! ```rust +//! use erpc_rust::{ +//! client::ClientManager, +//! codec::BasicCodecFactory, +//! transport::memory::MemoryTransport, +//! }; +//! +//! #[tokio::main] +//! async fn main() -> Result<(), Box> { +//! // Create paired memory transports for testing +//! let (client_transport, _server_transport) = MemoryTransport::pair(); +//! let codec_factory = BasicCodecFactory::new(); +//! let _client = ClientManager::new(client_transport, codec_factory); +//! +//! // Use client for RPC calls +//! Ok(()) +//! } +//! ``` + +#![allow(unused)] +#![allow(dead_code)] + +pub mod auxiliary; +pub mod client; +pub mod codec; +pub mod error; +pub mod server; +pub mod transport; + +// Re-export commonly used types +pub use auxiliary::{MessageInfo, MessageType, RequestContext}; +pub use client::ClientManager; +pub use codec::{BasicCodec, BasicCodecFactory, Codec}; +pub use error::{ErpcError, ErpcResult}; +pub use server::{ + MultiTransportServer, MultiTransportServerBuilder, Server, Service, SimpleServer, +}; +pub use transport::{FramedTransport, Transport}; + +#[cfg(feature = "tcp")] +pub use transport::TcpTransport; + +#[cfg(feature = "serial")] +pub use transport::SerialTransport; diff --git a/erpc_rust/src/server.rs b/erpc_rust/src/server.rs new file mode 100644 index 000000000..3eb2ffb78 --- /dev/null +++ b/erpc_rust/src/server.rs @@ -0,0 +1,1093 @@ +//! Server implementation for eRPC + +use crate::auxiliary::{MessageInfo, MessageType}; +use crate::codec::{Codec, CodecFactory}; +use crate::error::{ErpcResult, RequestError}; +use crate::transport::Transport; +use async_trait::async_trait; +use std::collections::HashMap; +use std::sync::Arc; +use tokio::sync::mpsc; +use tokio::sync::RwLock; +use tracing::{debug, error, info, warn}; + +/// Server trait for handling eRPC requests +#[async_trait] +pub trait Server { + /// Add a service to the server + async fn add_service(&mut self, service: Arc) -> ErpcResult<()>; + + /// Remove a service from the server + async fn remove_service(&mut self, service_id: u8) -> ErpcResult<()>; + + /// Run the server + async fn run(&mut self) -> ErpcResult<()>; + + /// Stop the server + async fn stop(&mut self) -> ErpcResult<()>; + + /// Check if server is running + fn is_running(&self) -> bool; +} + +/// Service trait for handling method calls +#[async_trait] +pub trait Service: Send + Sync { + /// Get service ID + fn service_id(&self) -> u8; + + /// Handle method invocation + async fn handle_invocation( + &self, + method_id: u8, + sequence: u32, + codec: &mut dyn Codec, + ) -> ErpcResult<()>; + + /// Get list of supported method IDs + fn supported_methods(&self) -> Vec; +} + +/// Simple single-threaded server implementation +pub struct SimpleServer +where + T: Transport, + F: CodecFactory, +{ + transport: T, + codec_factory: F, + services: Arc>>>, + running: bool, +} + +impl SimpleServer +where + T: Transport, + F: CodecFactory, +{ + /// Create new simple server + pub fn new(transport: T, codec_factory: F) -> Self { + Self { + transport, + codec_factory, + services: Arc::new(RwLock::new(HashMap::new())), + running: false, + } + } + + /// Process a single request + async fn process_request(&mut self, data: Vec) -> ErpcResult>> { + let mut codec = self.codec_factory.create_from_data(data); + + // Read message header + let message_info = codec.start_read_message()?; + + debug!( + "Processing request: type={}, service={}, method={}, sequence={}", + message_info.message_type, + message_info.service, + message_info.request, + message_info.sequence + ); + + // Validate message type + match message_info.message_type { + MessageType::Invocation | MessageType::Oneway => {} + _ => { + return Err(RequestError::InvalidMessageType.into()); + } + } + + // Find service + let services = self.services.read().await; + let service = services + .get(&message_info.service) + .ok_or(RequestError::InvalidServiceId(message_info.service as u32))? + .clone(); + drop(services); + + // Handle the invocation + service + .handle_invocation(message_info.request, message_info.sequence, &mut codec) + .await?; + + // Return response data if not oneway + if message_info.message_type == MessageType::Invocation { + Ok(Some(codec.as_bytes().to_vec())) + } else { + Ok(None) + } + } +} + +#[async_trait] +impl Server for SimpleServer +where + T: Transport + 'static, + F: CodecFactory + 'static, +{ + async fn add_service(&mut self, service: Arc) -> ErpcResult<()> { + let service_id = service.service_id(); + let mut services = self.services.write().await; + + if services.contains_key(&service_id) { + warn!("Service {} already exists, replacing", service_id); + } + + services.insert(service_id, service); + info!("Added service {}", service_id); + Ok(()) + } + + async fn remove_service(&mut self, service_id: u8) -> ErpcResult<()> { + let mut services = self.services.write().await; + + if services.remove(&service_id).is_some() { + info!("Removed service {}", service_id); + Ok(()) + } else { + Err(RequestError::InvalidServiceId(service_id as u32).into()) + } + } + + async fn run(&mut self) -> ErpcResult<()> { + self.running = true; + info!("Server started"); + + while self.running && self.transport.is_connected() { + match self.transport.receive().await { + Ok(data) => { + match self.process_request(data).await { + Ok(Some(response)) => { + if let Err(e) = self.transport.send(&response).await { + error!("Failed to send response: {}", e); + break; + } + } + Ok(None) => { + // Oneway message, no response needed + } + Err(e) => { + error!("Error processing request: {}", e); + // Continue running on request errors + } + } + } + Err(e) => { + error!("Transport error: {}", e); + break; + } + } + } + + info!("Server stopped"); + Ok(()) + } + + async fn stop(&mut self) -> ErpcResult<()> { + self.running = false; + self.transport.close().await?; + Ok(()) + } + + fn is_running(&self) -> bool { + self.running + } +} + +/// Base service implementation with method routing +pub struct BaseService { + service_id: u8, + methods: HashMap>, +} + +impl BaseService { + /// Create new base service + pub fn new(service_id: u8) -> Self { + Self { + service_id, + methods: HashMap::new(), + } + } + + /// Add method handler + pub fn add_method(&mut self, method_id: u8, handler: H) + where + H: MethodHandler + 'static, + { + self.methods.insert(method_id, Box::new(handler)); + } +} + +#[async_trait] +impl Service for BaseService { + fn service_id(&self) -> u8 { + self.service_id + } + + async fn handle_invocation( + &self, + method_id: u8, + sequence: u32, + codec: &mut dyn Codec, + ) -> ErpcResult<()> { + let handler = self + .methods + .get(&method_id) + .ok_or_else(|| RequestError::InvalidMethodId(method_id as u32))?; + + handler.handle(sequence, codec).await + } + + fn supported_methods(&self) -> Vec { + self.methods.keys().copied().collect() + } +} + +/// Method handler trait +#[async_trait] +pub trait MethodHandler: Send + Sync { + /// Handle method call + async fn handle(&self, sequence: u32, codec: &mut dyn Codec) -> ErpcResult<()>; +} + +/// Function-based method handler +pub struct FunctionHandler +where + F: Fn(u32, &mut dyn Codec) -> ErpcResult<()> + Send + Sync, +{ + func: F, +} + +impl FunctionHandler +where + F: Fn(u32, &mut dyn Codec) -> ErpcResult<()> + Send + Sync, +{ + /// Create new function handler + pub fn new(func: F) -> Self { + Self { func } + } +} + +#[async_trait] +impl MethodHandler for FunctionHandler +where + F: Fn(u32, &mut dyn Codec) -> ErpcResult<()> + Send + Sync, +{ + async fn handle(&self, sequence: u32, codec: &mut dyn Codec) -> ErpcResult<()> { + (self.func)(sequence, codec) + } +} + +/// Server builder for easy configuration +pub struct ServerBuilder +where + T: Transport, + F: CodecFactory, +{ + transport: Option, + codec_factory: Option, + services: Vec>, +} + +impl ServerBuilder +where + T: Transport + 'static, + F: CodecFactory + 'static, +{ + /// Create new server builder + pub fn new() -> Self { + Self { + transport: None, + codec_factory: None, + services: Vec::new(), + } + } + + /// Set transport + pub fn transport(mut self, transport: T) -> Self { + self.transport = Some(transport); + self + } + + /// Set codec factory + pub fn codec_factory(mut self, codec_factory: F) -> Self { + self.codec_factory = Some(codec_factory); + self + } + + /// Add service + pub fn service(mut self, service: Arc) -> Self { + self.services.push(service); + self + } + + /// Build server + pub async fn build(self) -> Result, &'static str> { + let transport = self.transport.ok_or("Transport not set")?; + let codec_factory = self.codec_factory.ok_or("Codec factory not set")?; + + let mut server = SimpleServer::new(transport, codec_factory); + + for service in self.services { + server + .add_service(service) + .await + .map_err(|_| "Failed to add service")?; + } + + Ok(server) + } +} + +impl Default for ServerBuilder +where + T: Transport + 'static, + F: CodecFactory + 'static, +{ + fn default() -> Self { + Self::new() + } +} + +/// Enhanced multi-transport server that supports TCP, Socket, and Serial transports +/// This version can handle multiple TCP listeners, Unix socket listeners, AND serial ports in a single event loop +pub struct MultiTransportServer +where + F: CodecFactory, +{ + codec_factory: F, + services: Arc>>>, + tcp_listeners: Vec, + #[cfg(unix)] + socket_listeners: Vec, + #[cfg(feature = "serial")] + serial_ports: Vec<(String, u32)>, // (port_name, baud_rate) pairs + running: bool, +} + +impl MultiTransportServer +where + F: CodecFactory + Clone + Send + Sync + 'static, +{ + /// Create new multi-transport server + pub fn new(codec_factory: F) -> Self { + Self { + codec_factory, + services: Arc::new(RwLock::new(HashMap::new())), + tcp_listeners: Vec::new(), + #[cfg(unix)] + socket_listeners: Vec::new(), + #[cfg(feature = "serial")] + serial_ports: Vec::new(), + running: false, + } + } + + /// Add a TCP listener on the specified address + pub async fn add_tcp_listener(&mut self, addr: &str) -> ErpcResult<()> { + let listener = tokio::net::TcpListener::bind(addr) + .await + .map_err(|e| RequestError::MethodError(format!("Failed to bind to {addr}: {e}")))?; + + info!("Added TCP listener on {}", addr); + self.tcp_listeners.push(listener); + Ok(()) + } + + /// Add a Unix socket listener on the specified path + #[cfg(unix)] + pub async fn add_socket_listener(&mut self, path: &str) -> ErpcResult<()> { + use crate::transport::SocketTransport; + + let listener = SocketTransport::listen(path) + .await + .map_err(|e| RequestError::MethodError(format!("Failed to bind socket to {path}: {e}")))?; + + info!("Added socket listener on {}", path); + self.socket_listeners.push(listener); + Ok(()) + } + + /// Add a serial port + #[cfg(feature = "serial")] + pub async fn add_serial_port(&mut self, port_name: &str, baud_rate: u32) -> ErpcResult<()> { + info!("Added serial port {} at {} baud", port_name, baud_rate); + self.serial_ports.push((port_name.to_string(), baud_rate)); + Ok(()) + } + + /// Get TCP listener addresses + pub fn tcp_addresses(&self) -> Vec { + self.tcp_listeners + .iter() + .filter_map(|listener| listener.local_addr().ok()) + .collect() + } + + /// Static version for spawned tasks - handles serial connections with robust reconnection + #[cfg(feature = "serial")] + async fn handle_serial_client_connection( + port_name: String, + baud_rate: u32, + services: Arc>>>, + codec_factory: F, + ) where + F: CodecFactory + Clone + Send + Sync + 'static, + { + use crate::transport::SerialTransport; + + info!( + "Starting serial connection handler for {} at {} baud", + &port_name, baud_rate + ); + + loop { + // Main reconnection loop + match SerialTransport::open(&port_name, baud_rate) { + Ok(mut transport) => { + info!("Serial port {} connected successfully", &port_name); + + // Continuous read loop for the current connection + while transport.is_connected() { + match transport.receive().await { + Ok(data) => { + info!( + "Received {} bytes from serial port {}", + data.len(), + &port_name + ); + + // Process request using static method + match Self::process_request_static(data, &services, &codec_factory) + .await + { + Ok(Some(response)) => { + if let Err(e) = transport.send(&response).await { + error!( + "Failed to send response to serial port {}: {}", + &port_name, e + ); + break; // Break inner loop to attempt reconnection + } + } + Ok(None) => { + // Oneway message, no response needed + } + Err(e) => { + error!( + "Error processing request from serial port {}: {}", + &port_name, e + ); + // Continue running on request errors + } + } + } + Err(e) => { + error!( + "Serial port {} receive error: {}. Will attempt to reconnect.", + &port_name, e + ); + break; // Break inner loop to trigger reconnection + } + } + } + } + Err(e) => { + error!("Failed to open serial port {}: {}", &port_name, e); + } + }; + + // If we are here, the connection was lost or failed to open. + // Wait before attempting to reconnect. + info!( + "Retrying connection to serial port {} in 5 seconds...", + &port_name + ); + tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; + } + } + + /// Static version for spawned tasks - handles TCP connections + async fn handle_tcp_client_connection( + stream: tokio::net::TcpStream, + services: Arc>>>, + codec_factory: F, + ) where + F: CodecFactory + Clone + Send + Sync + 'static, + { + use crate::transport::TcpTransport; + + let peer_addr = stream + .peer_addr() + .unwrap_or_else(|_| "unknown".parse().unwrap()); + info!("New TCP connection from {}", peer_addr); + + let mut transport = TcpTransport::from_stream(stream); + + while transport.is_connected() { + match transport.receive().await { + Ok(data) => { + // Process request using static method + match Self::process_request_static(data, &services, &codec_factory).await { + Ok(Some(response)) => { + if let Err(e) = transport.send(&response).await { + error!("Failed to send response to {}: {}", peer_addr, e); + break; + } + } + Ok(None) => { + // Oneway message, no response needed + } + Err(e) => { + error!("Error processing request from {}: {}", peer_addr, e); + // Continue running on request errors + } + } + } + Err(e) => { + error!("Transport error from {}: {}", peer_addr, e); + break; + } + } + } + + info!("TCP connection from {} closed", peer_addr); + } + + /// Static version for spawned tasks - handles Unix socket connections + #[cfg(unix)] + async fn handle_socket_client_connection( + stream: tokio::net::UnixStream, + services: Arc>>>, + codec_factory: F, + ) where + F: CodecFactory + Clone + Send + Sync + 'static, + { + use crate::transport::SocketTransport; + + let peer_addr = stream + .peer_addr() + .map(|addr| format!("{:?}", addr)) + .unwrap_or_else(|_| "unknown".to_string()); + info!("New socket connection from {}", peer_addr); + + let mut transport = SocketTransport::from_stream(stream); + + while transport.is_connected() { + match transport.receive().await { + Ok(data) => { + // Process request using static method + match Self::process_request_static(data, &services, &codec_factory).await { + Ok(Some(response)) => { + if let Err(e) = transport.send(&response).await { + error!("Failed to send response to {}: {}", peer_addr, e); + break; + } + } + Ok(None) => { + // Oneway message, no response needed + } + Err(e) => { + error!("Error processing request from {}: {}", peer_addr, e); + // Continue running on request errors + } + } + } + Err(e) => { + error!("Transport error from {}: {}", peer_addr, e); + break; + } + } + } + + info!("Socket connection from {} closed", peer_addr); + } + + /// Static version of process_request for use in spawned tasks + async fn process_request_static( + data: Vec, + services: &Arc>>>, + codec_factory: &F, + ) -> ErpcResult>> { + let mut codec = codec_factory.create_from_data(data); + + // Read message header + let message_info = codec.start_read_message()?; + + debug!( + "Processing request: type={}, service={}, method={}, sequence={}", + message_info.message_type, + message_info.service, + message_info.request, + message_info.sequence + ); + + // Validate message type + match message_info.message_type { + MessageType::Invocation | MessageType::Oneway => {} + _ => { + return Err(RequestError::InvalidMessageType.into()); + } + } + + // Find service + let services_lock = services.read().await; + let service = services_lock + .get(&message_info.service) + .ok_or(RequestError::InvalidServiceId(message_info.service as u32))? + .clone(); + drop(services_lock); + + // Handle the invocation + service + .handle_invocation(message_info.request, message_info.sequence, &mut codec) + .await?; + + // Return response data if not oneway + if message_info.message_type == MessageType::Invocation { + Ok(Some(codec.as_bytes().to_vec())) + } else { + Ok(None) + } + } +} + +#[async_trait] +impl Server for MultiTransportServer +where + F: CodecFactory + Clone + Send + Sync + 'static, +{ + async fn add_service(&mut self, service: Arc) -> ErpcResult<()> { + let service_id = service.service_id(); + let mut services = self.services.write().await; + + if services.contains_key(&service_id) { + warn!("Service {} already exists, replacing", service_id); + } + + services.insert(service_id, service); + info!("Added service {}", service_id); + Ok(()) + } + + async fn remove_service(&mut self, service_id: u8) -> ErpcResult<()> { + let mut services = self.services.write().await; + + if services.remove(&service_id).is_some() { + info!("Removed service {}", service_id); + Ok(()) + } else { + Err(RequestError::InvalidServiceId(service_id as u32).into()) + } + } + + async fn run(&mut self) -> ErpcResult<()> { + if self.tcp_listeners.is_empty() && { + #[cfg(unix)] + { + self.socket_listeners.is_empty() + } + #[cfg(not(unix))] + { + true + } + } && { + #[cfg(feature = "serial")] + { + self.serial_ports.is_empty() + } + #[cfg(not(feature = "serial"))] + { + true + } + } { + return Err(RequestError::InvalidServiceId(0).into()); + } + + self.running = true; + info!( + "Multi-transport server started with {} TCP listeners{}{}", + self.tcp_listeners.len(), + { + #[cfg(unix)] + { + format!(", {} socket listeners", self.socket_listeners.len()) + } + #[cfg(not(unix))] + { + String::new() + } + }, + { + #[cfg(feature = "serial")] + { + format!(", and {} serial ports", self.serial_ports.len()) + } + #[cfg(not(feature = "serial"))] + { + String::new() + } + } + ); + + // Create a vector to track listener tasks + let mut listener_tasks = Vec::new(); + + // Move the TCP listeners out so we can spawn tasks with them + let listeners = std::mem::take(&mut self.tcp_listeners); + + // Spawn a task for each TCP listener + for (i, listener) in listeners.into_iter().enumerate() { + let services = self.services.clone(); + let codec_factory = self.codec_factory.clone(); + + let task = tokio::spawn(async move { + let local_addr = listener + .local_addr() + .map_err(|_| RequestError::InvalidServiceId(i as u32))?; + + info!("TCP Listener {} started on {}", i, local_addr); + + loop { + match listener.accept().await { + Ok((stream, peer_addr)) => { + info!("TCP Listener {} accepted connection from {}", i, peer_addr); + + let services_clone = services.clone(); + let codec_factory_clone = codec_factory.clone(); + + // Spawn a task to handle this connection + tokio::spawn(async move { + Self::handle_tcp_client_connection( + stream, + services_clone, + codec_factory_clone, + ) + .await; + }); + } + Err(e) => { + error!("TCP Listener {} accept error: {}", i, e); + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + } + } + } + + #[allow(unreachable_code)] + Ok::<(), RequestError>(()) + }); + + listener_tasks.push(task); + } + + // Move the socket listeners out so we can spawn tasks with them + #[cfg(unix)] + { + let socket_listeners = std::mem::take(&mut self.socket_listeners); + + // Spawn a task for each socket listener + for (i, listener) in socket_listeners.into_iter().enumerate() { + let services = self.services.clone(); + let codec_factory = self.codec_factory.clone(); + + let task = tokio::spawn(async move { + let local_addr = listener + .local_addr() + .map(|addr| format!("{:?}", addr)) + .unwrap_or_else(|_| format!("socket-{}", i)); + + info!("Socket Listener {} started on {}", i, local_addr); + + loop { + match listener.accept().await { + Ok((stream, peer_addr)) => { + let peer_str = format!("{:?}", peer_addr); + info!("Socket Listener {} accepted connection from {}", i, peer_str); + + let services_clone = services.clone(); + let codec_factory_clone = codec_factory.clone(); + + // Spawn a task to handle this connection + tokio::spawn(async move { + Self::handle_socket_client_connection( + stream, + services_clone, + codec_factory_clone, + ) + .await; + }); + } + Err(e) => { + error!("Socket Listener {} accept error: {}", i, e); + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + } + } + } + + #[allow(unreachable_code)] + Ok::<(), RequestError>(()) + }); + + listener_tasks.push(task); + } + } + + // Spawn tasks for each serial port + #[cfg(feature = "serial")] + for (i, (port_name, baud_rate)) in self.serial_ports.iter().enumerate() { + let services = self.services.clone(); + let codec_factory = self.codec_factory.clone(); + let port_name = port_name.clone(); + let baud_rate = *baud_rate; + + let task = tokio::spawn(async move { + info!( + "Serial port {} starting on {} at {} baud", + i, port_name, baud_rate + ); + + // Handle serial connection with automatic reconnection + loop { + Self::handle_serial_client_connection( + port_name.clone(), + baud_rate, + services.clone(), + codec_factory.clone(), + ) + .await; + + // If we reach here, the serial connection was lost + // Wait before attempting to reconnect + error!( + "Serial port {} connection lost, retrying in 5 seconds", + port_name + ); + tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; + } + + #[allow(unreachable_code)] + Ok::<(), RequestError>(()) + }); + + listener_tasks.push(task); + } + + // Wait for any task to complete (or until stopped) + loop { + if !self.running { + break; + } + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + } + + // Clean up tasks + for task in listener_tasks { + task.abort(); + } + + info!("Multi-transport server stopped"); + Ok(()) + } + + async fn stop(&mut self) -> ErpcResult<()> { + self.running = false; + info!("Stopping multi-transport server"); + Ok(()) + } + + fn is_running(&self) -> bool { + self.running + } +} + +/// Builder for multi-transport server that supports TCP, Socket, and Serial +pub struct MultiTransportServerBuilder +where + F: CodecFactory, +{ + codec_factory: Option, + tcp_addresses: Vec, + #[cfg(unix)] + socket_paths: Vec, + #[cfg(feature = "serial")] + serial_ports: Vec<(String, u32)>, // (port_name, baud_rate) pairs + services: Vec>, +} + +impl MultiTransportServerBuilder +where + F: CodecFactory + Clone + Send + Sync + 'static, +{ + /// Create new multi-transport server builder + pub fn new() -> Self { + Self { + codec_factory: None, + tcp_addresses: Vec::new(), + #[cfg(unix)] + socket_paths: Vec::new(), + #[cfg(feature = "serial")] + serial_ports: Vec::new(), + services: Vec::new(), + } + } + + /// Set codec factory + pub fn codec_factory(mut self, codec_factory: F) -> Self { + self.codec_factory = Some(codec_factory); + self + } + + /// Add a TCP listener address + pub fn tcp_listener(mut self, addr: impl Into) -> Self { + self.tcp_addresses.push(addr.into()); + self + } + + /// Add a Unix socket listener path + #[cfg(unix)] + pub fn socket_listener(mut self, path: impl Into) -> Self { + self.socket_paths.push(path.into()); + self + } + + /// Add a serial port + #[cfg(feature = "serial")] + pub fn serial_port(mut self, port_name: impl Into, baud_rate: u32) -> Self { + self.serial_ports.push((port_name.into(), baud_rate)); + self + } + + /// Add service + pub fn service(mut self, service: Arc) -> Self { + self.services.push(service); + self + } + + /// Build the multi-transport server + pub async fn build(self) -> Result, &'static str> { + let codec_factory = self.codec_factory.ok_or("Codec factory not set")?; + let mut server = MultiTransportServer::new(codec_factory); + + // Add all TCP listeners + for addr in self.tcp_addresses { + server + .add_tcp_listener(&addr) + .await + .map_err(|_| "Failed to add TCP listener")?; + } + + // Add all socket listeners + #[cfg(unix)] + for path in self.socket_paths { + server + .add_socket_listener(&path) + .await + .map_err(|_| "Failed to add socket listener")?; + } + + // Add all serial ports + #[cfg(feature = "serial")] + for (port_name, baud_rate) in self.serial_ports { + server + .add_serial_port(&port_name, baud_rate) + .await + .map_err(|_| "Failed to add serial port")?; + } + + // Add all services + for service in self.services { + server + .add_service(service) + .await + .map_err(|_| "Failed to add service")?; + } + + Ok(server) + } +} + +impl Default for MultiTransportServerBuilder +where + F: CodecFactory + Clone + Send + Sync + 'static, +{ + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::codec::BasicCodecFactory; + use crate::transport::memory::MemoryTransport; + + struct TestService; + + #[async_trait] + impl Service for TestService { + fn service_id(&self) -> u8 { + 1 + } + + async fn handle_invocation( + &self, + method_id: u8, + sequence: u32, + codec: &mut dyn Codec, + ) -> ErpcResult<()> { + // Echo service - read input and write it back + let input = codec.read_string()?; + + // Start reply message + let reply_info = MessageInfo::new(MessageType::Reply, 1, method_id, sequence); + codec.start_write_message(&reply_info)?; + codec.write_string(&format!("Echo: {}", input))?; + + Ok(()) + } + + fn supported_methods(&self) -> Vec { + vec![1] + } + } + + #[tokio::test] + async fn test_server_creation() { + let (transport, _) = MemoryTransport::pair(); + let codec_factory = BasicCodecFactory::new(); + + let server = SimpleServer::new(transport, codec_factory); + assert!(!server.is_running()); + } + + #[tokio::test] + async fn test_service_management() { + let (transport, _) = MemoryTransport::pair(); + let codec_factory = BasicCodecFactory::new(); + let mut server = SimpleServer::new(transport, codec_factory); + + let service = Arc::new(TestService); + server.add_service(service).await.unwrap(); + + let services = server.services.read().await; + assert!(services.contains_key(&1)); + } + + #[tokio::test] + async fn test_server_builder() { + let (transport, _) = MemoryTransport::pair(); + let codec_factory = BasicCodecFactory::new(); + let service = Arc::new(TestService); + + let server = ServerBuilder::new() + .transport(transport) + .codec_factory(codec_factory) + .service(service) + .build() + .await + .unwrap(); + + assert!(!server.is_running()); + } +} diff --git a/erpc_rust/src/transport/framed.rs b/erpc_rust/src/transport/framed.rs new file mode 100644 index 000000000..0f04341be --- /dev/null +++ b/erpc_rust/src/transport/framed.rs @@ -0,0 +1,118 @@ +//! Framed transport base implementation with CRC validation +//! +//! This module provides the abstract FramedTransport that implements the Transport trait +//! and delegates the actual raw I/O to concrete implementations via base_send() and base_receive(). +//! This matches the Java FramedTransport design. + +use crate::{ + auxiliary::{crc16, utils}, + codec::{BasicCodec, Codec}, + error::{ErpcResult, TransportError}, + transport::Transport, +}; +use async_trait::async_trait; +use std::time::Duration; + +/// Header length for framed transport (6 bytes: crc_header + length + crc_body) +const HEADER_LEN: usize = 6; + +/// Abstract framed transport that provides CRC validation and message framing. +/// Concrete implementations must provide base_send() and base_receive() methods. +#[async_trait] +pub trait FramedTransport: Send + Sync { + /// Send raw data without framing (implemented by concrete transports) + async fn base_send(&mut self, data: &[u8]) -> ErpcResult<()>; + + /// Receive exact number of raw bytes without framing (implemented by concrete transports) + async fn base_receive(&mut self, length: usize) -> ErpcResult>; + + /// Check if the transport is connected (implemented by concrete transports) + fn is_connected(&self) -> bool; + + /// Close the transport (implemented by concrete transports) + async fn close(&mut self) -> ErpcResult<()>; + + /// Set timeout for operations (implemented by concrete transports) + fn set_timeout(&mut self, timeout: Duration); +} + +#[async_trait] +impl Transport for T { + async fn send(&mut self, data: &[u8]) -> ErpcResult<()> { + // Create codec for header + let mut codec = BasicCodec::new(); + + let message_length = data.len() as u16; + let crc_body = crc16::calculate(data); + + // Calculate header CRC: sum of separate CRCs on little-endian bytes + let length_bytes = utils::uint16_to_bytes(message_length); + let crc_body_bytes = utils::uint16_to_bytes(crc_body); + + let crc_length = crc16::calculate(&length_bytes); + let crc_body_crc = crc16::calculate(&crc_body_bytes); + let crc_header = (crc_length.wrapping_add(crc_body_crc)); + + // Write header: [crc_header:16][length:16][crc_body:16] + codec.write_uint16(crc_header as u16)?; + codec.write_uint16(message_length)?; + codec.write_uint16(crc_body)?; + + let header = codec.as_bytes(); + + // Send header then body + self.base_send(header).await?; + self.base_send(data).await?; + + Ok(()) + } + + async fn receive(&mut self) -> ErpcResult> { + // Receive header + let header_data = self.base_receive(HEADER_LEN).await?; + let mut codec = BasicCodec::from_data(header_data); + + let crc_header = codec.read_uint16()?; + let message_length = codec.read_uint16()?; + let crc_body = codec.read_uint16()?; + + // Verify header CRC + let length_bytes = utils::uint16_to_bytes(message_length); + let crc_body_bytes = utils::uint16_to_bytes(crc_body); + + let computed_crc_length = crc16::calculate(&length_bytes); + let computed_crc_body_crc = crc16::calculate(&crc_body_bytes); + let computed_crc_header = (computed_crc_length.wrapping_add(computed_crc_body_crc)); + + if computed_crc_header != crc_header as u16 { + return Err( + TransportError::ReceiveFailed("Invalid message (header) CRC".to_string()).into(), + ); + } + + // Receive body + let data = self.base_receive(message_length as usize).await?; + + // Verify body CRC + let computed_body_crc = crc16::calculate(&data); + if computed_body_crc != crc_body { + return Err( + TransportError::ReceiveFailed("Invalid message (body) CRC".to_string()).into(), + ); + } + + Ok(data) + } + + fn is_connected(&self) -> bool { + FramedTransport::is_connected(self) + } + + async fn close(&mut self) -> ErpcResult<()> { + FramedTransport::close(self).await + } + + fn set_timeout(&mut self, timeout: Duration) { + FramedTransport::set_timeout(self, timeout) + } +} diff --git a/erpc_rust/src/transport/memory.rs b/erpc_rust/src/transport/memory.rs new file mode 100644 index 000000000..fc8f7403d --- /dev/null +++ b/erpc_rust/src/transport/memory.rs @@ -0,0 +1,125 @@ +//! In-memory transport for testing and local communication +//! +//! This transport implements FramedTransport for consistency with TCP transport + +use crate::error::{ErpcResult, TransportError}; +use crate::transport::FramedTransport; +use async_trait::async_trait; +use std::collections::VecDeque; +use std::sync::Arc; +use std::time::Duration; +use tokio::sync::Mutex; + +/// Channel for bidirectional in-memory communication +#[derive(Debug)] +pub struct MemoryChannel { + a_to_b: Arc>>>, + b_to_a: Arc>>>, +} + +impl MemoryChannel { + /// Create a new memory channel pair + pub fn create_pair() -> (MemoryTransport, MemoryTransport) { + let channel = Self { + a_to_b: Arc::new(Mutex::new(VecDeque::new())), + b_to_a: Arc::new(Mutex::new(VecDeque::new())), + }; + + let transport_a = MemoryTransport { + send_queue: channel.a_to_b.clone(), + recv_queue: channel.b_to_a.clone(), + timeout: Duration::from_secs(30), + connected: true, + }; + + let transport_b = MemoryTransport { + send_queue: channel.b_to_a.clone(), + recv_queue: channel.a_to_b.clone(), + timeout: Duration::from_secs(30), + connected: true, + }; + + (transport_a, transport_b) + } +} + +/// In-memory transport for testing and local communication that extends FramedTransport +pub struct MemoryTransport { + send_queue: Arc>>>, + recv_queue: Arc>>>, + timeout: Duration, + connected: bool, +} + +impl MemoryTransport { + /// Create new memory transports for testing + pub fn new() -> (Self, Self) { + MemoryChannel::create_pair() + } +} + +#[async_trait] +impl FramedTransport for MemoryTransport { + async fn base_send(&mut self, data: &[u8]) -> ErpcResult<()> { + if !self.connected { + return Err(TransportError::Closed.into()); + } + + let mut queue = self.send_queue.lock().await; + queue.push_back(data.to_vec()); + Ok(()) + } + + async fn base_receive(&mut self, length: usize) -> ErpcResult> { + if !self.connected { + return Err(TransportError::Closed.into()); + } + + let start = std::time::Instant::now(); + let mut buffer = Vec::new(); + + while buffer.len() < length { + { + let mut queue = self.recv_queue.lock().await; + if let Some(data) = queue.pop_front() { + buffer.extend_from_slice(&data); + } + } + + if buffer.len() >= length { + break; + } + + // Check timeout + if start.elapsed() > self.timeout { + return Err(TransportError::Timeout.into()); + } + + // Brief yield to avoid busy waiting + tokio::task::yield_now().await; + } + + // Return exactly the requested number of bytes + if buffer.len() > length { + // Put excess data back in queue + let excess = buffer.split_off(length); + let mut queue = self.recv_queue.lock().await; + queue.push_front(excess); + } + + Ok(buffer) + } + + fn is_connected(&self) -> bool { + self.connected + } + + async fn close(&mut self) -> ErpcResult<()> { + self.connected = false; + Ok(()) + } + + fn set_timeout(&mut self, timeout: Duration) { + self.timeout = timeout; + } +} diff --git a/erpc_rust/src/transport/mod.rs b/erpc_rust/src/transport/mod.rs new file mode 100644 index 000000000..d07a77bac --- /dev/null +++ b/erpc_rust/src/transport/mod.rs @@ -0,0 +1,51 @@ +//! Transport layer abstraction for eRPC + +use crate::error::{ErpcResult, TransportError}; +use async_trait::async_trait; +use std::time::Duration; + +/// Transport trait for different communication methods +#[async_trait] +pub trait Transport: Send + Sync { + /// Send data through the transport + async fn send(&mut self, data: &[u8]) -> ErpcResult<()>; + + /// Receive data from the transport + async fn receive(&mut self) -> ErpcResult>; + + /// Close the transport + async fn close(&mut self) -> ErpcResult<()>; + + /// Check if transport is connected + fn is_connected(&self) -> bool; + + /// Set timeout for operations + fn set_timeout(&mut self, timeout: Duration); +} + +/// Transport factory trait for creating transport instances +#[async_trait] +pub trait TransportFactory: Send + Sync { + type Transport: Transport; + + /// Create a new transport instance + async fn create(&self) -> ErpcResult; +} + +pub mod framed; +pub mod memory; +pub mod serial; +pub mod socket; +pub mod tcp; + +#[cfg(feature = "tcp")] +pub use tcp::TcpTransport; + +#[cfg(feature = "serial")] +pub use serial::SerialTransport; + +#[cfg(unix)] +pub use socket::SocketTransport; + +pub use framed::FramedTransport; +pub use memory::MemoryTransport; diff --git a/erpc_rust/src/transport/serial.rs b/erpc_rust/src/transport/serial.rs new file mode 100644 index 000000000..533e810e0 --- /dev/null +++ b/erpc_rust/src/transport/serial.rs @@ -0,0 +1,130 @@ +//! Serial transport implementation +//! +//! This transport implements FramedTransport for consistency with TCP transport + +#[cfg(feature = "serial")] +use crate::error::{ErpcResult, TransportError}; +#[cfg(feature = "serial")] +use crate::transport::FramedTransport; +#[cfg(feature = "serial")] +use async_trait::async_trait; +#[cfg(feature = "serial")] +use serialport::{SerialPort, SerialPortBuilder}; +#[cfg(feature = "serial")] +use std::time::Duration; +#[cfg(feature = "serial")] +use tokio::sync::Mutex; + +#[cfg(feature = "serial")] +/// Serial transport for eRPC communication +pub struct SerialTransport { + port: Mutex>, + timeout: Duration, + connected: bool, +} + +#[cfg(feature = "serial")] +impl SerialTransport { + /// Open a serial port + pub fn open(port_name: &str, baud_rate: u32) -> ErpcResult { + let mut builder = serialport::new(port_name, baud_rate); + builder = builder.timeout(Duration::from_secs(1)); + + let port = builder + .open() + .map_err(|e| TransportError::ConnectionFailed(e.to_string()))?; + + Ok(Self { + port: Mutex::new(port), + timeout: Duration::from_secs(30), + connected: true, + }) + } + + /// Create with custom serial port settings + pub fn with_settings(settings: SerialPortBuilder) -> ErpcResult { + let port = settings + .open() + .map_err(|e| TransportError::ConnectionFailed(e.to_string()))?; + + Ok(Self { + port: Mutex::new(port), + timeout: Duration::from_secs(30), + connected: true, + }) + } +} + +#[cfg(feature = "serial")] +#[async_trait] +impl FramedTransport for SerialTransport { + async fn base_send(&mut self, data: &[u8]) -> ErpcResult<()> { + if !self.connected { + return Err(TransportError::Closed.into()); + } + + let mut port = self.port.lock().await; + + port.write_all(data) + .map_err(|e| TransportError::SendFailed(e.to_string()))?; + + port.flush() + .map_err(|e| TransportError::SendFailed(e.to_string()))?; + + Ok(()) + } + + async fn base_receive(&mut self, length: usize) -> ErpcResult> { + if !self.connected { + return Err(TransportError::Closed.into()); + } + + let mut port = self.port.lock().await; + let mut data = vec![0u8; length]; + let start = std::time::Instant::now(); + + while start.elapsed() < self.timeout { + match port.read_exact(&mut data) { + Ok(()) => return Ok(data), + Err(ref e) if e.kind() == std::io::ErrorKind::TimedOut => { + tokio::time::sleep(Duration::from_millis(10)).await; + continue; + } + Err(e) => { + self.connected = false; + return Err(TransportError::ReceiveFailed(e.to_string()).into()); + } + } + } + + self.connected = false; + Err(TransportError::Timeout.into()) + } + + async fn close(&mut self) -> ErpcResult<()> { + self.connected = false; + Ok(()) + } + + fn is_connected(&self) -> bool { + self.connected + } + + fn set_timeout(&mut self, timeout: Duration) { + self.timeout = timeout; + } +} + +#[cfg(not(feature = "serial"))] +/// Placeholder when serial feature is disabled +pub struct SerialTransport; + +#[cfg(not(feature = "serial"))] +impl SerialTransport { + pub fn open(_port_name: &str, _baud_rate: u32) -> Result { + Err( + crate::error::CodecError::NotSupported("Serial transport not enabled".to_string()) + .into(), + ) + } +} diff --git a/erpc_rust/src/transport/socket.rs b/erpc_rust/src/transport/socket.rs new file mode 100644 index 000000000..b486977f0 --- /dev/null +++ b/erpc_rust/src/transport/socket.rs @@ -0,0 +1,222 @@ +//! Unix Domain Socket transport implementation that extends FramedTransport +//! +//! This provides a socket-based transport for local inter-process communication + +use crate::error::{ErpcResult, TransportError}; +use crate::transport::FramedTransport; +use async_trait::async_trait; +use std::path::Path; +use std::time::Duration; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::net::{UnixListener, UnixStream}; +use tokio::time::timeout; + +/// Unix Domain Socket transport for eRPC communication that extends FramedTransport +pub struct SocketTransport { + stream: UnixStream, + timeout: Duration, + connected: bool, +} + +impl SocketTransport { + /// Connect to a Unix domain socket + pub async fn connect>(path: P) -> ErpcResult { + let stream = UnixStream::connect(path) + .await + .map_err(|e| TransportError::ConnectionFailed(e.to_string()))?; + + Ok(Self { + stream, + timeout: Duration::from_secs(30), + connected: true, + }) + } + + /// Create from existing Unix stream + pub fn from_stream(stream: UnixStream) -> Self { + Self { + stream, + timeout: Duration::from_secs(30), + connected: true, + } + } + + /// Create a listener on the given socket path + pub async fn listen>(path: P) -> ErpcResult { + // Remove existing socket file if it exists + if path.as_ref().exists() { + std::fs::remove_file(&path) + .map_err(|e| TransportError::ConnectionFailed(format!("Failed to remove existing socket: {}", e)))?; + } + + Ok(UnixListener::bind(path) + .map_err(|e| TransportError::ConnectionFailed(e.to_string()))?) + } + + /// Accept a connection from a Unix listener + pub async fn accept(listener: &UnixListener) -> ErpcResult { + let (stream, _) = listener + .accept() + .await + .map_err(|e| TransportError::ConnectionFailed(e.to_string()))?; + + Ok(Self::from_stream(stream)) + } + + /// Get the local socket address (if available) + pub fn local_addr(&self) -> std::io::Result { + self.stream.local_addr() + } + + /// Get the peer socket address (if available) + pub fn peer_addr(&self) -> std::io::Result { + self.stream.peer_addr() + } +} + +#[async_trait] +impl FramedTransport for SocketTransport { + async fn base_send(&mut self, data: &[u8]) -> ErpcResult<()> { + if !self.connected { + return Err(TransportError::Closed.into()); + } + + let send_future = self.stream.write_all(data); + + match timeout(self.timeout, send_future).await { + Ok(result) => result.map_err(|e| TransportError::SendFailed(e.to_string()).into()), + Err(_) => Err(TransportError::Timeout.into()), + } + } + + async fn base_receive(&mut self, length: usize) -> ErpcResult> { + if !self.connected { + return Err(TransportError::Closed.into()); + } + + let mut buffer = vec![0u8; length]; + let mut total_read = 0; + + while total_read < length { + let read_future = self.stream.read(&mut buffer[total_read..]); + + match timeout(self.timeout, read_future).await { + Ok(Ok(0)) => { + // Connection closed + self.connected = false; + return Err(TransportError::ConnectionFailed( + "Connection closed by peer".to_string(), + ) + .into()); + } + Ok(Ok(bytes_read)) => { + total_read += bytes_read; + } + Ok(Err(e)) => { + return Err(TransportError::ReceiveFailed(e.to_string()).into()); + } + Err(_) => { + return Err(TransportError::Timeout.into()); + } + } + } + + Ok(buffer) + } + + fn is_connected(&self) -> bool { + self.connected + } + + async fn close(&mut self) -> ErpcResult<()> { + if self.connected { + self.connected = false; + // UnixStream doesn't have a shutdown method like TcpStream, + // but dropping the stream will close the connection + } + Ok(()) + } + + fn set_timeout(&mut self, timeout: Duration) { + self.timeout = timeout; + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::transport::Transport; + use std::path::PathBuf; + use tempfile::tempdir; + use tokio::time::Duration; + + #[tokio::test] + async fn test_socket_transport_basic() { + let temp_dir = tempdir().unwrap(); + let socket_path = temp_dir.path().join("test_socket"); + + // Create listener + let listener = SocketTransport::listen(&socket_path).await.unwrap(); + + // Spawn server task + let server_socket_path = socket_path.clone(); + let server_handle = tokio::spawn(async move { + let mut server_transport = SocketTransport::accept(&listener).await.unwrap(); + + // Receive message + let received = server_transport.receive().await.unwrap(); + assert_eq!(received, b"Hello, Socket!"); + + // Echo back + server_transport.send(b"Echo: Hello, Socket!").await.unwrap(); + }); + + // Give server time to start + tokio::time::sleep(Duration::from_millis(100)).await; + + // Create client + let mut client_transport = SocketTransport::connect(&socket_path).await.unwrap(); + + // Send message + client_transport.send(b"Hello, Socket!").await.unwrap(); + + // Receive echo + let response = client_transport.receive().await.unwrap(); + assert_eq!(response, b"Echo: Hello, Socket!"); + + // Clean up + Transport::close(&mut client_transport).await.unwrap(); + server_handle.await.unwrap(); + } + + #[tokio::test] + async fn test_socket_transport_timeout() { + let temp_dir = tempdir().unwrap(); + let socket_path = temp_dir.path().join("test_timeout_socket"); + + // Create listener + let listener = SocketTransport::listen(&socket_path).await.unwrap(); + + // Spawn server that doesn't respond + let _server_handle = tokio::spawn(async move { + let _server_transport = SocketTransport::accept(&listener).await.unwrap(); + // Server doesn't send anything - just waits + tokio::time::sleep(Duration::from_secs(60)).await; + }); + + // Give server time to start + tokio::time::sleep(Duration::from_millis(100)).await; + + // Create client with short timeout + let mut client_transport = SocketTransport::connect(&socket_path).await.unwrap(); + Transport::set_timeout(&mut client_transport, Duration::from_millis(100)); + + // Send message + client_transport.send(b"Hello").await.unwrap(); + + // Try to receive - should timeout + let result = client_transport.receive().await; + assert!(result.is_err()); + // Note: The exact error type depends on the framed transport implementation + } +} diff --git a/erpc_rust/src/transport/tcp.rs b/erpc_rust/src/transport/tcp.rs new file mode 100644 index 000000000..fa3c053ed --- /dev/null +++ b/erpc_rust/src/transport/tcp.rs @@ -0,0 +1,122 @@ +//! TCP transport implementation that extends FramedTransport +//! +//! This matches the Java implementation where TCPTransport extends FramedTransport + +use crate::error::{ErpcResult, TransportError}; +use crate::transport::FramedTransport; +use async_trait::async_trait; +use std::time::Duration; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::net::{TcpListener, TcpStream}; +use tokio::time::timeout; + +/// TCP transport for eRPC communication that extends FramedTransport +pub struct TcpTransport { + stream: TcpStream, + timeout: Duration, + connected: bool, +} + +impl TcpTransport { + /// Connect to a TCP server + pub async fn connect(addr: &str) -> ErpcResult { + let stream = TcpStream::connect(addr) + .await + .map_err(|e| TransportError::ConnectionFailed(e.to_string()))?; + + Ok(Self { + stream, + timeout: Duration::from_secs(30), + connected: true, + }) + } + + /// Create from existing TCP stream + pub fn from_stream(stream: TcpStream) -> Self { + Self { + stream, + timeout: Duration::from_secs(30), + connected: true, + } + } + + /// Get local address + pub fn local_addr(&self) -> std::io::Result { + self.stream.local_addr() + } + + /// Get peer address + pub fn peer_addr(&self) -> std::io::Result { + self.stream.peer_addr() + } +} + +#[async_trait] +impl FramedTransport for TcpTransport { + async fn base_send(&mut self, data: &[u8]) -> ErpcResult<()> { + if !self.connected { + return Err(TransportError::Closed.into()); + } + + let send_future = self.stream.write_all(data); + + match timeout(self.timeout, send_future).await { + Ok(result) => result.map_err(|e| TransportError::SendFailed(e.to_string()).into()), + Err(_) => Err(TransportError::Timeout.into()), + } + } + + async fn base_receive(&mut self, length: usize) -> ErpcResult> { + if !self.connected { + return Err(TransportError::Closed.into()); + } + + let mut buffer = vec![0u8; length]; + let mut total_read = 0; + + while total_read < length { + let read_future = self.stream.read(&mut buffer[total_read..]); + + match timeout(self.timeout, read_future).await { + Ok(Ok(0)) => { + // Connection closed + self.connected = false; + return Err(TransportError::ConnectionFailed( + "Connection closed by peer".to_string(), + ) + .into()); + } + Ok(Ok(bytes_read)) => { + total_read += bytes_read; + } + Ok(Err(e)) => { + return Err(TransportError::ReceiveFailed(e.to_string()).into()); + } + Err(_) => { + return Err(TransportError::Timeout.into()); + } + } + } + + Ok(buffer) + } + + fn is_connected(&self) -> bool { + self.connected + } + + async fn close(&mut self) -> ErpcResult<()> { + if self.connected { + self.connected = false; + self.stream + .shutdown() + .await + .map_err(|e| TransportError::SendFailed(e.to_string()))?; + } + Ok(()) + } + + fn set_timeout(&mut self, timeout: Duration) { + self.timeout = timeout; + } +} diff --git a/erpc_rust/tests/client_server_integration.rs b/erpc_rust/tests/client_server_integration.rs new file mode 100644 index 000000000..414852d51 --- /dev/null +++ b/erpc_rust/tests/client_server_integration.rs @@ -0,0 +1,519 @@ +//! Client-Server integration tests with TCP transport + +use erpc_rust::{ + auxiliary::{MessageInfo, MessageType}, + client::ClientManager, + codec::{BasicCodec, BasicCodecFactory, Codec}, + server::{BaseService, FunctionHandler, Server, ServerBuilder}, + transport::{memory::MemoryTransport, TcpTransport}, +}; +use std::sync::Arc; +use std::time::Duration; +use tokio::net::TcpListener; +use tokio::time::sleep; + +/// Helper function to find an available port +async fn find_available_port() -> u16 { + use tokio::net::TcpListener; + + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + let port = listener.local_addr().unwrap().port(); + drop(listener); + port +} + +/// Create an echo service for testing +fn create_echo_service() -> BaseService { + let mut service = BaseService::new(1); + + // Echo method (service 1, method 1) + service.add_method( + 1, + FunctionHandler::new(|sequence, codec| { + let input = codec.read_string()?; + + let reply_info = MessageInfo::new(MessageType::Reply, 1, 1, sequence); + codec.start_write_message(&reply_info)?; + + let response = format!("Echo: {}", input); + codec.write_string(&response)?; + Ok(()) + }), + ); + + // Uppercase method (service 1, method 2) + service.add_method( + 2, + FunctionHandler::new(|sequence, codec| { + let input = codec.read_string()?; + + let reply_info = MessageInfo::new(MessageType::Reply, 1, 2, sequence); + codec.start_write_message(&reply_info)?; + + let response = input.to_uppercase(); + codec.write_string(&response)?; + Ok(()) + }), + ); + + // Oneway notification (service 1, method 3) + service.add_method( + 3, + FunctionHandler::new(|_sequence, codec| { + let _message = codec.read_string()?; + // Oneway - no response + Ok(()) + }), + ); + + service +} + +/// Create a calculator service for testing +fn create_calculator_service() -> BaseService { + let mut service = BaseService::new(42); + + // Add operation + service.add_method( + 1, + FunctionHandler::new(|sequence, codec| { + let a = codec.read_float()?; + let b = codec.read_float()?; + let result = a + b; + + let reply_info = MessageInfo::new(MessageType::Reply, 42, 1, sequence); + codec.start_write_message(&reply_info)?; + codec.write_float(result)?; + Ok(()) + }), + ); + + // Multiply operation + service.add_method( + 2, + FunctionHandler::new(|sequence, codec| { + let a = codec.read_int32()?; + let b = codec.read_int32()?; + let result = a * b; + + let reply_info = MessageInfo::new(MessageType::Reply, 42, 2, sequence); + codec.start_write_message(&reply_info)?; + codec.write_int32(result)?; + Ok(()) + }), + ); + + service +} + +#[tokio::test] +async fn test_tcp_client_server_echo() { + let port = find_available_port().await; + let addr = format!("127.0.0.1:{}", port); + + // Start server in background task + let server_addr = addr.clone(); + let server_task = tokio::spawn(async move { + let listener = TcpListener::bind(&server_addr).await.unwrap(); + + // Accept one connection + let (stream, _) = listener.accept().await.unwrap(); + let tcp_transport = TcpTransport::from_stream(stream); + let codec_factory = BasicCodecFactory::new(); + + let echo_service = create_echo_service(); + let mut server = ServerBuilder::new() + .transport(tcp_transport) + .codec_factory(codec_factory) + .service(Arc::new(echo_service)) + .build() + .await + .unwrap(); + + // Run server for a limited time + tokio::select! { + result = server.run() => { + if let Err(e) = result { + eprintln!("Server error: {}", e); + } + } + _ = sleep(Duration::from_secs(10)) => { + // Timeout after 10 seconds + } + } + }); + + // Give server time to start + sleep(Duration::from_millis(100)).await; + + // Connect client + let tcp_transport = TcpTransport::connect(&addr).await.unwrap(); + let codec_factory = BasicCodecFactory::new(); + let mut client = ClientManager::new(tcp_transport, codec_factory); + + // Test echo method + let mut request_codec = BasicCodec::new(); + request_codec.write_string("Hello, TCP server!").unwrap(); + let request_data = request_codec.as_bytes().to_vec(); + + let response_data = client + .perform_request(1, 1, false, request_data) + .await + .unwrap(); + + let mut response_codec = BasicCodec::from_data(response_data); + let response = response_codec.read_string().unwrap(); + assert_eq!(response, "Echo: Hello, TCP server!"); + + // Test uppercase method + let mut request_codec = BasicCodec::new(); + request_codec.write_string("make me uppercase").unwrap(); + let request_data = request_codec.as_bytes().to_vec(); + + let response_data = client + .perform_request(1, 2, false, request_data) + .await + .unwrap(); + + let mut response_codec = BasicCodec::from_data(response_data); + let response = response_codec.read_string().unwrap(); + assert_eq!(response, "MAKE ME UPPERCASE"); + + // Test oneway method + let mut oneway_codec = BasicCodec::new(); + oneway_codec.write_string("Oneway notification").unwrap(); + let oneway_data = oneway_codec.as_bytes().to_vec(); + + client + .perform_request(1, 3, true, oneway_data) + .await + .unwrap(); + + // Close client + client.close().await.unwrap(); + + // Clean up server task + server_task.abort(); +} + +#[tokio::test] +async fn test_tcp_calculator_service() { + let port = find_available_port().await; + let addr = format!("127.0.0.1:{}", port); + + // Start server + let server_addr = addr.clone(); + let server_task = tokio::spawn(async move { + let listener = TcpListener::bind(&server_addr).await.unwrap(); + let (stream, _) = listener.accept().await.unwrap(); + let tcp_transport = TcpTransport::from_stream(stream); + let codec_factory = BasicCodecFactory::new(); + + let calc_service = create_calculator_service(); + let mut server = ServerBuilder::new() + .transport(tcp_transport) + .codec_factory(codec_factory) + .service(Arc::new(calc_service)) + .build() + .await + .unwrap(); + + tokio::select! { + result = server.run() => { + if let Err(e) = result { + eprintln!("Server error: {}", e); + } + } + _ = sleep(Duration::from_secs(10)) => {} + } + }); + + sleep(Duration::from_millis(100)).await; + + // Connect client + let tcp_transport = TcpTransport::connect(&addr).await.unwrap(); + let codec_factory = BasicCodecFactory::new(); + let mut client = ClientManager::new(tcp_transport, codec_factory); + + // Test float addition + let mut request_codec = BasicCodec::new(); + request_codec.write_float(3.14).unwrap(); + request_codec.write_float(2.86).unwrap(); + let request_data = request_codec.as_bytes().to_vec(); + + let response_data = client + .perform_request(42, 1, false, request_data) + .await + .unwrap(); + + let mut response_codec = BasicCodec::from_data(response_data); + let result = response_codec.read_float().unwrap(); + assert!((result - 6.0).abs() < 0.001); + + // Test integer multiplication + let mut request_codec = BasicCodec::new(); + request_codec.write_int32(7).unwrap(); + request_codec.write_int32(6).unwrap(); + let request_data = request_codec.as_bytes().to_vec(); + + let response_data = client + .perform_request(42, 2, false, request_data) + .await + .unwrap(); + + let mut response_codec = BasicCodec::from_data(response_data); + let result = response_codec.read_int32().unwrap(); + assert_eq!(result, 42); + + client.close().await.unwrap(); + server_task.abort(); +} + +#[tokio::test] +async fn test_multiple_clients_tcp() { + let port = find_available_port().await; + let addr = format!("127.0.0.1:{}", port); + + // Start server that handles multiple connections + let server_addr = addr.clone(); + let server_task = tokio::spawn(async move { + let listener = TcpListener::bind(&server_addr).await.unwrap(); + + for _ in 0..3 { + if let Ok((stream, _)) = listener.accept().await { + let tcp_transport = TcpTransport::from_stream(stream); + let codec_factory = BasicCodecFactory::new(); + let echo_service = create_echo_service(); + + tokio::spawn(async move { + let mut server = ServerBuilder::new() + .transport(tcp_transport) + .codec_factory(codec_factory) + .service(Arc::new(echo_service)) + .build() + .await + .unwrap(); + + tokio::select! { + result = server.run() => { + if let Err(e) = result { + eprintln!("Server error: {}", e); + } + } + _ = sleep(Duration::from_secs(5)) => {} + } + }); + } + } + + sleep(Duration::from_secs(10)).await; + }); + + sleep(Duration::from_millis(100)).await; + + // Start multiple clients + let mut client_tasks = Vec::new(); + + for i in 0..3 { + let client_addr = addr.clone(); + let client_task = tokio::spawn(async move { + let tcp_transport = TcpTransport::connect(&client_addr).await.unwrap(); + let codec_factory = BasicCodecFactory::new(); + let mut client = ClientManager::new(tcp_transport, codec_factory); + + let message = format!("Hello from client {}", i); + let mut request_codec = BasicCodec::new(); + request_codec.write_string(&message).unwrap(); + let request_data = request_codec.as_bytes().to_vec(); + + let response_data = client + .perform_request(1, 1, false, request_data) + .await + .unwrap(); + + let mut response_codec = BasicCodec::from_data(response_data); + let response = response_codec.read_string().unwrap(); + + client.close().await.unwrap(); + response + }); + + client_tasks.push(client_task); + } + + // Wait for all clients to complete + let results = futures::future::join_all(client_tasks).await; + + for (i, result) in results.into_iter().enumerate() { + let response = result.unwrap(); + let expected = format!("Echo: Hello from client {}", i); + assert_eq!(response, expected); + } + + server_task.abort(); +} + +#[tokio::test] +async fn test_simple_memory_debug() { + let (client_transport, server_transport) = MemoryTransport::pair(); + + // Start server + let server_task = tokio::spawn(async move { + let codec_factory = BasicCodecFactory::new(); + let echo_service = create_echo_service(); + + let mut server = ServerBuilder::new() + .transport(server_transport) + .codec_factory(codec_factory) + .service(Arc::new(echo_service)) + .build() + .await + .unwrap(); + + tokio::select! { + result = server.run() => { + if let Err(e) = result { + eprintln!("Server error: {}", e); + } + } + _ = sleep(Duration::from_secs(5)) => {} + } + }); + + sleep(Duration::from_millis(50)).await; + + // Connect client + let codec_factory = BasicCodecFactory::new(); + let mut client = ClientManager::new(client_transport, codec_factory); + + // Create request data directly without using write_binary wrapper + let test_string = "Memory transport test"; + + // Write just the string data, not as binary + let mut request_codec = BasicCodec::new(); + request_codec.write_string(test_string).unwrap(); + let request_data = request_codec.as_bytes().to_vec(); + + println!("Request data: {:?}", request_data); + + let response_data = client + .perform_request(1, 1, false, request_data) + .await + .unwrap(); + + println!("Response data: {:?}", response_data); + + let mut response_codec = BasicCodec::from_data(response_data); + let response = response_codec.read_string().unwrap(); + assert_eq!(response, "Echo: Memory transport test"); + + client.close().await.unwrap(); + server_task.abort(); +} + +#[tokio::test] +async fn test_error_handling_invalid_service() { + let (client_transport, server_transport) = MemoryTransport::pair(); + + // Start server with limited services + let server_task = tokio::spawn(async move { + let codec_factory = BasicCodecFactory::new(); + let echo_service = create_echo_service(); // Only service ID 1 + + let mut server = ServerBuilder::new() + .transport(server_transport) + .codec_factory(codec_factory) + .service(Arc::new(echo_service)) + .build() + .await + .unwrap(); + + tokio::select! { + result = server.run() => { + if let Err(e) = result { + eprintln!("Server error: {}", e); + } + } + _ = sleep(Duration::from_secs(3)) => {} + } + }); + + sleep(Duration::from_millis(50)).await; + + let codec_factory = BasicCodecFactory::new(); + let mut client = ClientManager::new(client_transport, codec_factory); + + // Try to call non-existent service + let mut request_codec = BasicCodec::new(); + request_codec.write_string("test").unwrap(); + let request_data = request_codec.as_bytes().to_vec(); + + let result = client.perform_request(99, 1, false, request_data).await; + + // Should get an error for invalid service + assert!(result.is_err()); + + client.close().await.unwrap(); + server_task.abort(); +} + +#[tokio::test] +async fn test_stress_multiple_requests() { + let (client_transport, server_transport) = MemoryTransport::pair(); + + // Start server + let server_task = tokio::spawn(async move { + let codec_factory = BasicCodecFactory::new(); + let calc_service = create_calculator_service(); + + let mut server = ServerBuilder::new() + .transport(server_transport) + .codec_factory(codec_factory) + .service(Arc::new(calc_service)) + .build() + .await + .unwrap(); + + tokio::select! { + result = server.run() => { + if let Err(e) = result { + eprintln!("Server error: {}", e); + } + } + _ = sleep(Duration::from_secs(10)) => {} + } + }); + + sleep(Duration::from_millis(50)).await; + + let codec_factory = BasicCodecFactory::new(); + let mut client = ClientManager::new(client_transport, codec_factory); + + // Make many sequential requests + for i in 1..=50 { + let mut request_codec = BasicCodec::new(); + request_codec.write_float(i as f32).unwrap(); + request_codec.write_float(2.0).unwrap(); + let request_data = request_codec.as_bytes().to_vec(); + + let response_data = client + .perform_request(42, 1, false, request_data) + .await + .unwrap(); + + let mut response_codec = BasicCodec::from_data(response_data); + let result = response_codec.read_float().unwrap(); + let expected = i as f32 + 2.0; + + assert!( + (result - expected).abs() < 0.001, + "Request {}: expected {}, got {}", + i, + expected, + result + ); + } + + client.close().await.unwrap(); + server_task.abort(); +} diff --git a/erpc_rust/tests/integration_tests.rs b/erpc_rust/tests/integration_tests.rs new file mode 100644 index 000000000..f19cf5cb7 --- /dev/null +++ b/erpc_rust/tests/integration_tests.rs @@ -0,0 +1,736 @@ +//! Integration tests for eRPC Rust implementation + +use erpc_rust::{ + auxiliary::{MessageInfo, MessageType}, + codec::{BasicCodec, BasicCodecFactory, Codec, CodecFactory}, +}; + +#[tokio::test] +async fn test_basic_codec_round_trip() { + let mut codec = BasicCodec::new(); + + // Test basic types + codec.write_bool(true).unwrap(); + codec.write_int8(-42).unwrap(); + codec.write_int16(-1234).unwrap(); + codec.write_int32(-123456).unwrap(); + codec.write_int64(-1234567890).unwrap(); + codec.write_uint8(42).unwrap(); + codec.write_uint16(1234).unwrap(); + codec.write_uint32(123456).unwrap(); + codec.write_uint64(9876543210).unwrap(); + codec.write_float(3.14159).unwrap(); + codec.write_double(2.718281828).unwrap(); + codec.write_string("Hello, eRPC!").unwrap(); + codec.write_binary(b"binary data").unwrap(); + + // Create read codec from written data + let data = codec.as_bytes().to_vec(); + let mut read_codec = BasicCodec::from_data(data); + + // Read back and verify + assert_eq!(read_codec.read_bool().unwrap(), true); + assert_eq!(read_codec.read_int8().unwrap(), -42); + assert_eq!(read_codec.read_int16().unwrap(), -1234); + assert_eq!(read_codec.read_int32().unwrap(), -123456); + assert_eq!(read_codec.read_int64().unwrap(), -1234567890); + assert_eq!(read_codec.read_uint8().unwrap(), 42); + assert_eq!(read_codec.read_uint16().unwrap(), 1234); + assert_eq!(read_codec.read_uint32().unwrap(), 123456); + assert_eq!(read_codec.read_uint64().unwrap(), 9876543210); + assert!((read_codec.read_float().unwrap() - 3.14159).abs() < 0.0001); + assert!((read_codec.read_double().unwrap() - 2.718281828).abs() < 0.000001); + assert_eq!(read_codec.read_string().unwrap(), "Hello, eRPC!"); + assert_eq!(read_codec.read_binary().unwrap(), b"binary data"); +} + +#[tokio::test] +async fn test_message_header_round_trip() { + let mut codec = BasicCodec::new(); + + let original_info = MessageInfo::new(MessageType::Invocation, 5, 10, 12345); + codec.start_write_message(&original_info).unwrap(); + + let data = codec.as_bytes().to_vec(); + let mut read_codec = BasicCodec::from_data(data); + + let read_info = read_codec.start_read_message().unwrap(); + + assert_eq!(read_info.message_type, original_info.message_type); + assert_eq!(read_info.service, original_info.service); + assert_eq!(read_info.request, original_info.request); + assert_eq!(read_info.sequence, original_info.sequence); +} + +#[tokio::test] +async fn test_client_context() { + use erpc_rust::{ + client::ClientManager, codec::BasicCodecFactory, transport::memory::MemoryTransport, + }; + + let (client_transport, _server_transport) = MemoryTransport::pair(); + let codec_factory = BasicCodecFactory::new(); + let client = ClientManager::new(client_transport, codec_factory); + + // Test Java-style request creation + let request = client.create_request(false); + assert_eq!(request.is_oneway(), false); + assert!(request.sequence() > 0); + + let oneway_request = client.create_request(true); + assert_eq!(oneway_request.is_oneway(), true); + + // Test request with service ID + let service_request = client.create_request_with_service(5, false); + assert_eq!(service_request.service_id(), Some(5)); + assert_eq!(service_request.is_oneway(), false); +} + +#[tokio::test] +async fn test_codec_uint_validation() { + use erpc_rust::codec::{BasicCodec, Codec}; + + let mut codec = BasicCodec::new(); + + // Test uint8 validation - should accept valid values + assert!(codec.write_uint8(0).is_ok()); + assert!(codec.write_uint8(255).is_ok()); + + // Test uint16 validation - should accept valid values + assert!(codec.write_uint16(0).is_ok()); + assert!(codec.write_uint16(65535).is_ok()); + + // Test uint32 validation - should accept valid values + assert!(codec.write_uint32(0).is_ok()); + assert!(codec.write_uint32(u32::MAX).is_ok()); + + // Test uint64 support + assert!(codec.write_uint64(0).is_ok()); + assert!(codec.write_uint64(u64::MAX).is_ok()); +} + +#[tokio::test] +async fn test_codec_round_trip() { + use erpc_rust::codec::{BasicCodec, Codec}; + + let mut codec = BasicCodec::new(); + + // Test all data types that Java supports + codec.write_bool(true).unwrap(); + codec.write_int8(-42).unwrap(); + codec.write_int16(-1234).unwrap(); + codec.write_int32(-123456).unwrap(); + codec.write_int64(-1234567890).unwrap(); + codec.write_uint8(42).unwrap(); + codec.write_uint16(1234).unwrap(); + codec.write_uint32(123456).unwrap(); + codec.write_uint64(9876543210).unwrap(); + codec.write_float(3.14159).unwrap(); + codec.write_double(2.718281828).unwrap(); + codec.write_string("Hello, eRPC!").unwrap(); + codec.write_binary(b"binary data").unwrap(); + + // Test list and union operations + codec.start_write_list(5).unwrap(); + codec.start_write_union(1).unwrap(); + codec.write_null_flag(true).unwrap(); + + // Create read codec from written data + let data = codec.as_bytes().to_vec(); + let mut read_codec = BasicCodec::from_data(data); + + // Read back and verify + assert_eq!(read_codec.read_bool().unwrap(), true); + assert_eq!(read_codec.read_int8().unwrap(), -42); + assert_eq!(read_codec.read_int16().unwrap(), -1234); + assert_eq!(read_codec.read_int32().unwrap(), -123456); + assert_eq!(read_codec.read_int64().unwrap(), -1234567890); + assert_eq!(read_codec.read_uint8().unwrap(), 42); + assert_eq!(read_codec.read_uint16().unwrap(), 1234); + assert_eq!(read_codec.read_uint32().unwrap(), 123456); + assert_eq!(read_codec.read_uint64().unwrap(), 9876543210); + assert!((read_codec.read_float().unwrap() - 3.14159).abs() < 0.0001); + assert!((read_codec.read_double().unwrap() - 2.718281828).abs() < 0.000001); + assert_eq!(read_codec.read_string().unwrap(), "Hello, eRPC!"); + assert_eq!(read_codec.read_binary().unwrap(), b"binary data"); + + // Test list and union read operations + assert_eq!(read_codec.start_read_list().unwrap(), 5); + assert_eq!(read_codec.start_read_union().unwrap(), 1); + assert_eq!(read_codec.read_null_flag().unwrap(), true); +} + +#[tokio::test] +async fn test_codec_factory() { + let factory = BasicCodecFactory::new(); + + let codec1 = factory.create(); + let codec2 = factory.create_from_data(vec![1, 2, 3, 4]); + + // New codec starts empty + assert_eq!(codec1.as_bytes().len(), 0); + + // Codec created from data is set up for reading, not writing + // as_bytes() returns the written data, which is initially empty + assert_eq!(codec2.as_bytes().len(), 0); + + // Test that we can actually read from codec2 + // The data [1, 2, 3, 4] should be readable as a 32-bit little-endian integer + let mut read_codec = factory.create_from_data(vec![1, 2, 3, 4]); + let value = read_codec.read_uint32().unwrap(); + assert_eq!(value, 0x04030201); // Little-endian interpretation +} + +#[tokio::test] +async fn test_message_type_conversion() { + assert_eq!(MessageType::from_u8(0), Some(MessageType::Invocation)); + assert_eq!(MessageType::from_u8(1), Some(MessageType::Oneway)); + assert_eq!(MessageType::from_u8(2), Some(MessageType::Reply)); + assert_eq!(MessageType::from_u8(3), Some(MessageType::Notification)); + assert_eq!(MessageType::from_u8(4), None); + + assert_eq!(MessageType::Invocation.to_u8(), 0); + assert_eq!(MessageType::Oneway.to_u8(), 1); + assert_eq!(MessageType::Reply.to_u8(), 2); + assert_eq!(MessageType::Notification.to_u8(), 3); +} + +#[tokio::test] +async fn test_framed_transport_compatibility() { + use erpc_rust::transport::{memory::MemoryTransport, Transport}; + + let (mut client_transport, mut server_transport) = MemoryTransport::pair(); + + let test_data = b"Java-style framed message with CRC validation"; + + // Send data with framing and CRC + client_transport.send(test_data).await.unwrap(); + + // Receive and verify CRC validation works + let received = server_transport.receive().await.unwrap(); + assert_eq!(received, test_data); + + // Test multiple messages + let messages = [ + b"Message 1".as_slice(), + b"Message 2 with more data", + b"Final message", + ]; + + for msg in &messages { + client_transport.send(msg).await.unwrap(); + } + + for expected_msg in &messages { + let received = server_transport.receive().await.unwrap(); + assert_eq!(received, *expected_msg); + } +} + +#[tokio::test] +async fn test_crc16_calculation() { + use erpc_rust::auxiliary::{crc16, utils}; + + // Test basic string + let data = b"123456789"; + let crc = crc16::calculate(data); + assert_eq!(crc, 0x89ac, "CRC16 of '123456789' should be 0x89ac"); + + // Test with hex data + let test_cases = [ + ("5b108fe061377bb0844f6a469b7b2544", 0x4547), + ("6ebe6f4b01a33686310102398daf883f", 0xf033), + ("82121c9510a01971366a71fc46c27eff", 0x60bc), + ("311275c4ce315456aea1a75993403be3", 0xd127), + ("4fd3abc6b911c737c66f750f55fc4216", 0x2fba), + ]; + + for (hex_data, expected_crc) in test_cases.iter() { + let data = utils::hex_to_byte_array(hex_data).unwrap(); + let crc = crc16::calculate(&data); + assert_eq!( + crc, *expected_crc, + "CRC16 mismatch for hex data: {}", + hex_data + ); + } + + // Verify CRC validation + assert!(crc16::verify(data, 0x89ac)); + assert!(!crc16::verify(data, 0x89ad)); +} + +#[tokio::test] +async fn test_utils_validation() { + use erpc_rust::auxiliary::utils; + + // Test uint8 validation + assert!(utils::check_uint8(0).is_ok()); + assert!(utils::check_uint8(255).is_ok()); + assert!(utils::check_uint8(256).is_err()); + assert!(utils::check_uint8(1000).is_err()); + + // Test uint16 validation + assert!(utils::check_uint16(0).is_ok()); + assert!(utils::check_uint16(65535).is_ok()); + assert!(utils::check_uint16(65536).is_err()); + assert!(utils::check_uint16(100000).is_err()); + + // Test uint32 validation + assert!(utils::check_uint32(0).is_ok()); + assert!(utils::check_uint32(u32::MAX as u64).is_ok()); + assert!(utils::check_uint32(u32::MAX as u64 + 1).is_err()); + + // Test null validation + assert!(utils::check_not_null(Some(42), "should not fail").is_ok()); + assert!(utils::check_not_null(None::, "should fail").is_err()); + + // Test conversion functions + assert_eq!(utils::uint32_to_int(0xFFFFFFFF), -1i32); + assert_eq!(utils::int_to_uint32(-1i32), 0xFFFFFFFF); + + assert_eq!(utils::uint16_to_short(0xFFFF), -1i16); + assert_eq!(utils::short_to_uint16(-1i16), 0xFFFF); + + assert_eq!(utils::uint8_to_byte(0xFF), -1i8); + assert_eq!(utils::byte_to_uint8(-1i8), 0xFF); + + // Test hex conversion + let data = &[0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF]; + let hex = utils::byte_array_to_hex(data); + assert_eq!(hex, "0123456789abcdef"); + + let converted_back = utils::hex_to_byte_array(&hex).unwrap(); + assert_eq!(converted_back, data); + + // Test invalid hex + assert!(utils::hex_to_byte_array("invalid").is_err()); + assert!(utils::hex_to_byte_array("abc").is_err()); // odd length +} + +#[tokio::test] +async fn test_codec_edge_cases() { + use erpc_rust::codec::{BasicCodec, Codec}; + + let mut codec = BasicCodec::new(); + + // Test empty strings and binary data + codec.write_string("").unwrap(); + codec.write_binary(b"").unwrap(); + + // Test boundary values + codec.write_int8(i8::MIN).unwrap(); + codec.write_int8(i8::MAX).unwrap(); + codec.write_int16(i16::MIN).unwrap(); + codec.write_int16(i16::MAX).unwrap(); + codec.write_int32(i32::MIN).unwrap(); + codec.write_int32(i32::MAX).unwrap(); + codec.write_int64(i64::MIN).unwrap(); + codec.write_int64(i64::MAX).unwrap(); + + codec.write_uint8(u8::MIN).unwrap(); + codec.write_uint8(u8::MAX).unwrap(); + codec.write_uint16(u16::MIN).unwrap(); + codec.write_uint16(u16::MAX).unwrap(); + codec.write_uint32(u32::MIN).unwrap(); + codec.write_uint32(u32::MAX).unwrap(); + codec.write_uint64(u64::MIN).unwrap(); + codec.write_uint64(u64::MAX).unwrap(); + + // Test special float values + codec.write_float(f32::MIN).unwrap(); + codec.write_float(f32::MAX).unwrap(); + codec.write_float(0.0).unwrap(); + codec.write_float(-0.0).unwrap(); + codec.write_float(f32::INFINITY).unwrap(); + codec.write_float(f32::NEG_INFINITY).unwrap(); + + codec.write_double(f64::MIN).unwrap(); + codec.write_double(f64::MAX).unwrap(); + codec.write_double(0.0).unwrap(); + codec.write_double(-0.0).unwrap(); + codec.write_double(f64::INFINITY).unwrap(); + codec.write_double(f64::NEG_INFINITY).unwrap(); + + // Read back and verify + let data = codec.as_bytes().to_vec(); + let mut read_codec = BasicCodec::from_data(data); + + assert_eq!(read_codec.read_string().unwrap(), ""); + assert_eq!(read_codec.read_binary().unwrap(), b""); + + assert_eq!(read_codec.read_int8().unwrap(), i8::MIN); + assert_eq!(read_codec.read_int8().unwrap(), i8::MAX); + assert_eq!(read_codec.read_int16().unwrap(), i16::MIN); + assert_eq!(read_codec.read_int16().unwrap(), i16::MAX); + assert_eq!(read_codec.read_int32().unwrap(), i32::MIN); + assert_eq!(read_codec.read_int32().unwrap(), i32::MAX); + assert_eq!(read_codec.read_int64().unwrap(), i64::MIN); + assert_eq!(read_codec.read_int64().unwrap(), i64::MAX); + + assert_eq!(read_codec.read_uint8().unwrap(), u8::MIN); + assert_eq!(read_codec.read_uint8().unwrap(), u8::MAX); + assert_eq!(read_codec.read_uint16().unwrap(), u16::MIN); + assert_eq!(read_codec.read_uint16().unwrap(), u16::MAX); + assert_eq!(read_codec.read_uint32().unwrap(), u32::MIN); + assert_eq!(read_codec.read_uint32().unwrap(), u32::MAX); + assert_eq!(read_codec.read_uint64().unwrap(), u64::MIN); + assert_eq!(read_codec.read_uint64().unwrap(), u64::MAX); + + assert_eq!(read_codec.read_float().unwrap(), f32::MIN); + assert_eq!(read_codec.read_float().unwrap(), f32::MAX); + assert_eq!(read_codec.read_float().unwrap(), 0.0); + assert_eq!(read_codec.read_float().unwrap(), -0.0); + assert_eq!(read_codec.read_float().unwrap(), f32::INFINITY); + assert_eq!(read_codec.read_float().unwrap(), f32::NEG_INFINITY); + + assert_eq!(read_codec.read_double().unwrap(), f64::MIN); + assert_eq!(read_codec.read_double().unwrap(), f64::MAX); + assert_eq!(read_codec.read_double().unwrap(), 0.0); + assert_eq!(read_codec.read_double().unwrap(), -0.0); + assert_eq!(read_codec.read_double().unwrap(), f64::INFINITY); + assert_eq!(read_codec.read_double().unwrap(), f64::NEG_INFINITY); +} + +#[tokio::test] +async fn test_unicode_string_handling() { + use erpc_rust::codec::{BasicCodec, Codec}; + + let mut codec = BasicCodec::new(); + + // Test various Unicode strings like Java tests + let unicode_strings = [ + "Hello, World! 🌍", + "中文测试", + "Русский текст", + "العربية", + "हिंदी", + "🎉🚀✨💫🌟", + "Ñoño piñata jalapeño", + "Café naïve résumé", + "Здравствуй мир!", + "こんにちは世界", + "안녕하세요 세계", + "", // Empty string + " ", // Single space + "\n\t\r", // Whitespace + "\"'`\\", // Special characters + ]; + + for unicode_str in &unicode_strings { + codec.write_string(unicode_str).unwrap(); + } + + let data = codec.as_bytes().to_vec(); + let mut read_codec = BasicCodec::from_data(data); + + for expected_str in &unicode_strings { + let read_str = read_codec.read_string().unwrap(); + assert_eq!( + read_str, *expected_str, + "Unicode string mismatch: {}", + expected_str + ); + } +} + +#[tokio::test] +async fn test_large_binary_data() { + use erpc_rust::codec::{BasicCodec, Codec}; + + // Test large binary data like Java's 65536-byte test + let large_data: Vec = (0..65536).map(|i| (i % 256) as u8).collect(); + + let mut codec = BasicCodec::new(); + codec.write_binary(&large_data).unwrap(); + + let encoded = codec.as_bytes().to_vec(); + let mut read_codec = BasicCodec::from_data(encoded); + + let read_data = read_codec.read_binary().unwrap(); + assert_eq!(read_data.len(), 65536); + assert_eq!(read_data, large_data); + + // Test various sizes + let sizes = [0, 1, 255, 256, 1024, 4096, 16384, 32768, 65535, 65536]; + for size in sizes { + let test_data: Vec = (0..size).map(|i| (i % 256) as u8).collect(); + + let mut codec = BasicCodec::new(); + codec.write_binary(&test_data).unwrap(); + + let encoded = codec.as_bytes().to_vec(); + let mut read_codec = BasicCodec::from_data(encoded); + + let read_data = read_codec.read_binary().unwrap(); + assert_eq!(read_data.len(), size); + assert_eq!( + read_data, test_data, + "Large binary data mismatch for size: {}", + size + ); + } +} + +#[tokio::test] +async fn test_comprehensive_crc16_vectors() { + use erpc_rust::auxiliary::{crc16, utils}; + + // All CRC test vectors from Java CRC16Test.java + let test_vectors = [ + ("5b108fe061377bb0844f6a469b7b2544", 0x4547), + ("6ebe6f4b01a33686310102398daf883f", 0xf033), + ("82121c9510a01971366a71fc46c27eff", 0x60bc), + ("311275c4ce315456aea1a75993403be3", 0xd127), + ("4fd3abc6b911c737c66f750f55fc4216", 0x2fba), + ("5a63996ab77d2202f480687069ca8ffe", 0x045a), + ("b8dbfee5a566214c0f1b39a4028d9b20", 0x4a0a), + ("6ba116397d22b71869581742e3886867", 0x9a37), + ("00de0fad00cc885707a2b13ce999eb1c", 0x8af4), + ("da61c52377ca5bb717de30f44df43cb3", 0x07a0), + ("00396c95c8733faa47ee70aeaa123942", 0x82fa), + ("99ce4dbe8a0588c03f81a071b6df26e1", 0xde52), + ("72f68bc19da85b9e077c46d8a190d497", 0x3429), + ("aa6ca4918b16fac5c69c463da851edb3", 0x6e45), + ("373610cb7d89a2c52089bb7cad7603ae", 0x05df), + ("d47cdd4425e5e96b70f8ff0c15716433", 0xb3d7), + ("b8337e68949d675e71e27340a18d1d2b", 0x0451), + ("1fe2ae3bdb44afbe591d777ce9a0a352", 0x8e36), + ("eab6d63286db5d7b5d33fa3193ec1650", 0xcd8c), + ("14d39b146713049a646cb16e812fa04a", 0xa92c), + ("7b7e00e55ed3ec0dc12ad60ff9d5d2cf", 0x492d), + ("9f10125c276cdc518b4d61fe2ec7d5fa", 0x88ba), + ("a0a5763a92b232b886f95094f50c95b4", 0x247a), + ("1db2fa23acbf6c6bc60e6a4d8f1b6266", 0x0751), + ("b7286f6879db13d871bc9b06aeee8932", 0xa7ba), + ("e067284662792f25583655e547a07227", 0x2082), + ("2615f97b172ff6b8799f88afddd1e189", 0xa92c), + ("df70b5e237c110f452b1acc965140911", 0xfbb9), + ("d5f91e44cb9be394e5831d3d291eee7c", 0x0af5), + ("5e74de47e74fc901fb76e278f9abb541", 0x9209), + ("416c54f49c8dcf093d72cc8a3aa195c9", 0x7a2e), + ("d0593cb671d8899448f603863aca5c0b", 0xe915), + ("a106b5858d9e5464eb01a388e4829f36", 0xff91), + ("21705e23f29cb1465db3f410a887bf4f", 0x6524), + ("8d39ccf4c244963a29c6dd531f8861f9", 0xa82e), + ("8a31810b0c634ff15e5540a36b075504", 0x7765), + ("b48ac1deffbbc515f82508408470344a", 0x0491), + ("265d6a6e206aa888190a512a9120f2d3", 0x8435), + ("514a168af0a8cd99145e0cab1f311707", 0x2a56), + ("89cf0b02699b14c375b6c21fef58b572", 0x39f1), + ("7ea6196fe85f569065957e14206d8f75", 0x7e5d), + ("fdd1e68dcfae80ae3dad3aefbe7ef158", 0x912a), + ("4cf3ee7c96b9d3679295b2cb93a979bf", 0x395e), + ("b6f3691f7401ed685f23ace4f7b3b3db", 0xea51), + ("0e86d611995e8a2ed6c4b0e0d97304a5", 0x1b39), + ("70dc6ed45f9410813dfd1600629a6080", 0x530c), + ("dc96ad1d88643f01df321e9a6fa43e0c", 0xc2df), + ("ab5cc581ff755a28aa91bc1a23272630", 0xd9e7), + ("24db03e36048be8da3b268fd7d7580f5", 0xf1de), + ("d66c1a23d5bf97808c662a595a474125", 0xf1ad), + ]; + + for (hex_data, expected_crc) in test_vectors.iter() { + let data = utils::hex_to_byte_array(hex_data).unwrap(); + let crc = crc16::calculate(&data); + assert_eq!( + crc, *expected_crc, + "CRC16 mismatch for hex data: {}", + hex_data + ); + + // Also test verification + assert!(crc16::verify(&data, *expected_crc)); + assert!(!crc16::verify(&data, (*expected_crc).wrapping_add(1))); + } + + // Test the known string case + let data = b"123456789"; + let crc = crc16::calculate(data); + assert_eq!(crc, 0x89ac, "CRC16 of '123456789' should be 0x89ac"); +} + +#[tokio::test] +async fn test_message_header_validation() { + use erpc_rust::{ + auxiliary::{MessageInfo, MessageType}, + codec::{BasicCodec, Codec}, + }; + + // Test various message types and values + let test_cases = [ + (MessageType::Invocation, 0, 0, 0), + (MessageType::Oneway, 1, 1, 1), + (MessageType::Reply, 255, 255, 65535), + (MessageType::Notification, 127, 200, 32767), + (MessageType::Invocation, 42, 99, 12345), + ]; + + for (msg_type, service, request, sequence) in test_cases.iter() { + let original_info = MessageInfo::new(*msg_type, *service, *request, *sequence); + + let mut write_codec = BasicCodec::new(); + write_codec.start_write_message(&original_info).unwrap(); + + let data = write_codec.as_bytes().to_vec(); + let mut read_codec = BasicCodec::from_data(data); + + let read_info = read_codec.start_read_message().unwrap(); + + assert_eq!(read_info.message_type, original_info.message_type); + assert_eq!(read_info.service, original_info.service); + assert_eq!(read_info.request, original_info.request); + assert_eq!(read_info.sequence, original_info.sequence); + } +} + +#[tokio::test] +async fn test_hex_representation() { + use erpc_rust::auxiliary::utils; + + // Test hex conversion with various patterns + let test_cases = [ + (vec![0x00], "00"), + (vec![0xFF], "ff"), + (vec![0x01, 0x23, 0x45, 0x67], "01234567"), + (vec![0x89, 0xAB, 0xCD, 0xEF], "89abcdef"), + (vec![0xDE, 0xAD, 0xBE, 0xEF], "deadbeef"), + (vec![0xCA, 0xFE, 0xBA, 0xBE], "cafebabe"), + (vec![], ""), + ]; + + for (data, expected_hex) in test_cases.iter() { + let hex = utils::byte_array_to_hex(data); + assert_eq!( + hex, *expected_hex, + "Hex conversion failed for data: {:?}", + data + ); + + let converted_back = utils::hex_to_byte_array(&hex).unwrap(); + assert_eq!(converted_back, *data, "Hex parsing failed for hex: {}", hex); + } + + // Test uppercase hex input + let uppercase_cases = [ + ("DEADBEEF", vec![0xDE, 0xAD, 0xBE, 0xEF]), + ("CAFEBABE", vec![0xCA, 0xFE, 0xBA, 0xBE]), + ( + "0123456789ABCDEF", + vec![0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF], + ), + ]; + + for (hex, expected_data) in uppercase_cases.iter() { + let converted = utils::hex_to_byte_array(hex).unwrap(); + assert_eq!( + converted, *expected_data, + "Uppercase hex parsing failed for: {}", + hex + ); + } + + // Test mixed case + let mixed = utils::hex_to_byte_array("DeAdBeEf").unwrap(); + assert_eq!(mixed, vec![0xDE, 0xAD, 0xBE, 0xEF]); +} + +#[tokio::test] +async fn test_error_handling() { + use erpc_rust::{ + auxiliary::utils, + codec::{BasicCodec, Codec}, + }; + + // Test reading from empty codec + let mut empty_codec = BasicCodec::new(); + assert!(empty_codec.read_bool().is_err()); + assert!(empty_codec.read_int32().is_err()); + assert!(empty_codec.read_string().is_err()); + + // Test reading more data than available + let mut small_codec = BasicCodec::from_data(vec![0x01]); + assert!(small_codec.read_bool().is_ok()); // Should work + assert!(small_codec.read_bool().is_err()); // Should fail - no more data + + // Test invalid hex strings + let invalid_hex_cases = [ + "xyz", // Invalid characters + "abc", // Odd length + "12 34", // Contains space + "12g4", // Invalid character + ]; + + for invalid_hex in invalid_hex_cases.iter() { + assert!( + utils::hex_to_byte_array(invalid_hex).is_err(), + "Should fail for invalid hex: {}", + invalid_hex + ); + } + + // Test validation functions + assert!(utils::check_uint8(256).is_err()); + assert!(utils::check_uint16(65536).is_err()); + assert!(utils::check_uint32(u64::MAX).is_err()); + + assert!(utils::check_not_null(None::, "test").is_err()); +} + +#[tokio::test] +async fn test_message_type_java_compatibility() { + //! Test that message type handling exactly matches Java eRPC implementation + //! This verifies enum values, serialization format, and error handling + + use erpc_rust::{ + auxiliary::{MessageInfo, MessageType}, + codec::{BasicCodec, Codec}, + }; + + // Test that enum values match Java exactly + assert_eq!(MessageType::Invocation as u8, 0); + assert_eq!(MessageType::Oneway as u8, 1); + assert_eq!(MessageType::Reply as u8, 2); + assert_eq!(MessageType::Notification as u8, 3); + + // Test conversion functions + assert_eq!(MessageType::from_u8(0), Some(MessageType::Invocation)); + assert_eq!(MessageType::from_u8(1), Some(MessageType::Oneway)); + assert_eq!(MessageType::from_u8(2), Some(MessageType::Reply)); + assert_eq!(MessageType::from_u8(3), Some(MessageType::Notification)); + assert_eq!(MessageType::from_u8(99), None); // Invalid type + + // Test serialization matches Java byte-for-byte + // Java constructs: (version << 24) | (service << 16) | (request << 8) | type + // Written as little-endian 32-bit produces the following wire format: + let test_cases = [ + (MessageType::Invocation, "000a050131d40000"), + (MessageType::Oneway, "010a050131d40000"), + (MessageType::Reply, "020a050131d40000"), + (MessageType::Notification, "030a050131d40000"), + ]; + + for (msg_type, expected_hex) in test_cases.iter() { + let mut codec = BasicCodec::new(); + let msg_info = MessageInfo::new(*msg_type, 5, 10, 54321); + codec.start_write_message(&msg_info).unwrap(); + + let serialized = codec.as_bytes(); + let hex_string: String = serialized.iter().map(|b| format!("{:02x}", b)).collect(); + + assert_eq!( + hex_string, *expected_hex, + "Message type {:?} serialization should match Java", + msg_type + ); + + // Test round-trip deserialization + let mut read_codec = BasicCodec::from_data(serialized.to_vec()); + let deserialized = read_codec.start_read_message().unwrap(); + + assert_eq!(deserialized.message_type, *msg_type); + assert_eq!(deserialized.service, 5); + assert_eq!(deserialized.request, 10); + assert_eq!(deserialized.sequence, 54321); + } +} diff --git a/erpcgen/CMakeLists.txt b/erpcgen/CMakeLists.txt index d3bb9b849..3fbb798b7 100644 --- a/erpcgen/CMakeLists.txt +++ b/erpcgen/CMakeLists.txt @@ -37,6 +37,7 @@ set(ERPCGEN_SOURCE ${ERPC_ERPCGEN}/src/CGenerator.cpp ${ERPC_ERPCGEN}/src/PythonGenerator.cpp ${ERPC_ERPCGEN}/src/JavaGenerator.cpp + ${ERPC_ERPCGEN}/src/RustGenerator.cpp ${ERPC_ERPCGEN}/src/erpcgen.cpp ${ERPC_ERPCGEN}/src/ErpcLexer.cpp ${ERPC_ERPCGEN}/src/Generator.cpp @@ -98,6 +99,7 @@ set(ERPCGEN_TEMPLATE_NAMES java_const.template java_coders.template java_client.template + rust_template.template ) # For each name in ERPCGEN_TEMPLATE_NAMES add custom command diff --git a/erpcgen/Makefile b/erpcgen/Makefile index 980c53249..610816be5 100644 --- a/erpcgen/Makefile +++ b/erpcgen/Makefile @@ -57,6 +57,7 @@ SOURCES += $(OBJS_ROOT)/erpcgen_parser.tab.cpp \ $(ERPC_ROOT)/erpcgen/src/CGenerator.cpp \ $(ERPC_ROOT)/erpcgen/src/PythonGenerator.cpp \ $(ERPC_ROOT)/erpcgen/src/JavaGenerator.cpp \ + $(ERPC_ROOT)/erpcgen/src/RustGenerator.cpp \ $(ERPC_ROOT)/erpcgen/src/erpcgen.cpp \ $(ERPC_ROOT)/erpcgen/src/ErpcLexer.cpp \ $(ERPC_ROOT)/erpcgen/src/Generator.cpp \ @@ -99,6 +100,7 @@ SOURCES += $(OBJS_ROOT)/erpcgen_parser.tab.cpp \ $(OBJS_ROOT)/erpcgen/src/templates/java_const.c \ $(OBJS_ROOT)/erpcgen/src/templates/java_coders.c \ $(OBJS_ROOT)/erpcgen/src/templates/java_client.c \ + $(OBJS_ROOT)/erpcgen/src/templates/rust_template.c \ SOURCES += $(OBJS_ROOT)/erpcgen/src/templates/c_common_header.c \ $(OBJS_ROOT)/erpcgen/src/templates/cpp_interface_header.c \ @@ -128,6 +130,7 @@ SOURCES += $(OBJS_ROOT)/erpcgen/src/templates/c_common_header.c \ $(OBJS_ROOT)/erpcgen/src/templates/java_const.c \ $(OBJS_ROOT)/erpcgen/src/templates/java_coders.c \ $(OBJS_ROOT)/erpcgen/src/templates/java_client.c \ + $(OBJS_ROOT)/erpcgen/src/templates/rust_template.c \ OBJECT_DEP := $(OBJS_ROOT)/erpcgen_lexer.cpp diff --git a/erpcgen/VisualStudio_v14/erpcgen.vcxproj b/erpcgen/VisualStudio_v14/erpcgen.vcxproj index eefaadebf..e6b52fcb9 100644 --- a/erpcgen/VisualStudio_v14/erpcgen.vcxproj +++ b/erpcgen/VisualStudio_v14/erpcgen.vcxproj @@ -124,7 +124,8 @@ python.exe ..\bin\txt_to_c.py --output .\java_interface.cpp ..\src\templates\jav python.exe ..\bin\txt_to_c.py --output .\java_struct.cpp ..\src\templates\java_struct.template python.exe ..\bin\txt_to_c.py --output .\java_server.cpp ..\src\templates\java_server.template python.exe ..\bin\txt_to_c.py --output .\java_client.cpp ..\src\templates\java_client.template -python.exe ..\bin\txt_to_c.py --output .\java_const.cpp ..\src\templates\java_const.template +python.exe ..\bin\txt_to_c.py --output .\java_const.cpp ..\src\templates\java_const.template +python.exe ..\bin\txt_to_c.py --output .\rust_template.cpp ..\src\templates\rust_template.template @@ -178,7 +179,8 @@ python.exe ..\bin\txt_to_c.py --output .\java_interface.cpp ..\src\templates\jav python.exe ..\bin\txt_to_c.py --output .\java_struct.cpp ..\src\templates\java_struct.template python.exe ..\bin\txt_to_c.py --output .\java_server.cpp ..\src\templates\java_server.template python.exe ..\bin\txt_to_c.py --output .\java_client.cpp ..\src\templates\java_client.template -python.exe ..\bin\txt_to_c.py --output .\java_const.cpp ..\src\templates\java_const.template +python.exe ..\bin\txt_to_c.py --output .\java_const.cpp ..\src\templates\java_const.template +python.exe ..\bin\txt_to_c.py --output .\rust_template.cpp ..\src\templates\rust_template.template $(ProjectDir)\%(Filename).flex.cpp @@ -212,6 +214,7 @@ python.exe ..\bin\txt_to_c.py --output .\java_const.cpp ..\src\templates\java_co + @@ -254,6 +257,7 @@ python.exe ..\bin\txt_to_c.py --output .\java_const.cpp ..\src\templates\java_co + @@ -280,6 +284,7 @@ python.exe ..\bin\txt_to_c.py --output .\java_const.cpp ..\src\templates\java_co + diff --git a/erpcgen/src/Generator.cpp b/erpcgen/src/Generator.cpp index 1feae7975..1eb562118 100644 --- a/erpcgen/src/Generator.cpp +++ b/erpcgen/src/Generator.cpp @@ -620,6 +620,10 @@ Annotation::program_lang_t Generator::getAnnotationLang() { return Annotation::program_lang_t::kJava; } + else if (m_generatorType == generator_type_t::kRust) + { + return Annotation::program_lang_t::kRust; + } throw internal_error("Unsupported generator type specified for annotation."); } diff --git a/erpcgen/src/Generator.hpp b/erpcgen/src/Generator.hpp index 18dfb5a03..c509ffde1 100644 --- a/erpcgen/src/Generator.hpp +++ b/erpcgen/src/Generator.hpp @@ -49,7 +49,8 @@ class Generator { kC, kPython, - kJava + kJava, + kRust }; /*!< Type of generator. */ typedef std::vector datatype_vector_t; /*!< Vector of data types. */ diff --git a/erpcgen/src/RustGenerator.cpp b/erpcgen/src/RustGenerator.cpp new file mode 100644 index 000000000..215d14b0b --- /dev/null +++ b/erpcgen/src/RustGenerator.cpp @@ -0,0 +1,2061 @@ +/* + * Copyright (c) 2024-2025 NXP + * All rights reserved. + * + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "RustGenerator.hpp" +#include "Logging.hpp" +#include "ParseErrors.hpp" +#include "annotations.h" +#include "erpc_version.h" +#include "format_string.hpp" + +#include +#include +#include +#include +#include + +using namespace erpcgen; +using namespace cpptempl; +using namespace std; + +//////////////////////////////////////////////////////////////////////////////// +// Variables +//////////////////////////////////////////////////////////////////////////////// + +// Templates strings converted from text files by txt_to_c.py. +extern const char *const kRustTemplate; + +//////////////////////////////////////////////////////////////////////////////// +// Code +//////////////////////////////////////////////////////////////////////////////// + +RustGenerator::RustGenerator(InterfaceDefinition *def) + : Generator(def, generator_type_t::kRust) { + /* Set copyright rules. */ + m_templateData["erpcVersion"] = ERPC_VERSION; + m_templateData["todaysDate"] = getTime(); + + initRustKeywords(); +} + +void RustGenerator::initRustKeywords() { + // Rust keywords that need to be escaped + const char *keywords[] = { + "as", "break", "const", "continue", "crate", "else", + "enum", "extern", "false", "fn", "for", "if", + "impl", "in", "let", "loop", "match", "mod", + "move", "mut", "pub", "ref", "return", "self", + "Self", "static", "struct", "super", "trait", "true", + "type", "unsafe", "use", "where", "while", "async", + "await", "dyn", "abstract", "become", "box", "do", + "final", "macro", "override", "priv", "typeof", "unsized", + "virtual", "yield", "try", "union"}; + + for (const char *keyword : keywords) { + m_rustKeywords.insert(keyword); + } +} + +string RustGenerator::escapeKeyword(const string &name) { + if (m_rustKeywords.find(name) != m_rustKeywords.end()) { + return "r#" + name; + } + return name; +} + +string RustGenerator::toSnakeCase(const string &name) { + string result; + bool previousWasUpper = false; + + for (size_t i = 0; i < name.length(); ++i) { + char c = name[i]; + + if (isupper(c)) { + // Insert underscore before uppercase letter if: + // 1. It's not the first character AND + // 2. The previous character was lowercase OR the next character is + // lowercase + if (i > 0 && (!previousWasUpper || + (i + 1 < name.length() && islower(name[i + 1])))) { + result += '_'; + } + result += tolower(c); + previousWasUpper = true; + } else { + result += c; + previousWasUpper = false; + } + } + + return result; +} + +string RustGenerator::toUpperSnakeCase(const string &name) { + string snakeCase = toSnakeCase(name); + transform(snakeCase.begin(), snakeCase.end(), snakeCase.begin(), ::toupper); + return snakeCase; +} + +string RustGenerator::toPascalCase(const string &name) { + string result; + bool capitalizeNext = true; + + for (size_t i = 0; i < name.length(); ++i) { + char c = name[i]; + + if (c == '_') { + capitalizeNext = true; + } else if (capitalizeNext) { + result += toupper(c); + capitalizeNext = false; + } else if (isupper(c)) { + // Handle camelCase -> PascalCase: preserve uppercase letters as word + // boundaries + result += c; + capitalizeNext = false; + } else { + result += tolower(c); + } + } + + return result; +} + +string RustGenerator::getDefaultValue(DataType *dataType) { + if (!dataType) { + return "Default::default()"; + } + + switch (dataType->getDataType()) { + case DataType::data_type_t::kBuiltinType: { + BuiltinType *builtinType = dynamic_cast(dataType); + switch (builtinType->getBuiltinType()) { + case BuiltinType::builtin_type_t::kBoolType: + return "false"; + case BuiltinType::builtin_type_t::kInt8Type: + case BuiltinType::builtin_type_t::kInt16Type: + case BuiltinType::builtin_type_t::kInt32Type: + case BuiltinType::builtin_type_t::kInt64Type: + case BuiltinType::builtin_type_t::kUInt8Type: + case BuiltinType::builtin_type_t::kUInt16Type: + case BuiltinType::builtin_type_t::kUInt32Type: + case BuiltinType::builtin_type_t::kUInt64Type: + return "0"; + case BuiltinType::builtin_type_t::kFloatType: + case BuiltinType::builtin_type_t::kDoubleType: + return "0.0"; + case BuiltinType::builtin_type_t::kStringType: + return "String::new()"; + case BuiltinType::builtin_type_t::kBinaryType: + return "Vec::new()"; + default: + return "Default::default()"; + } + } + case DataType::data_type_t::kArrayType: + case DataType::data_type_t::kListType: + return "Vec::new()"; + case DataType::data_type_t::kStructType: + case DataType::data_type_t::kEnumType: + case DataType::data_type_t::kUnionType: + return escapeKeyword(dataType->getName()) + "::default()"; + case DataType::data_type_t::kVoidType: + return "()"; + case DataType::data_type_t::kAliasType: { + AliasType *aliasType = dynamic_cast(dataType); + return getDefaultValue(aliasType->getElementType()); + } + default: + return "Default::default()"; + } +} + +void RustGenerator::generate() { + findGroupDataTypes(); + + for (Group *group : m_groups) { + Log::info("Generating Rust code for group: %s\n", group->getName().c_str()); + + // Set the group's template data + group->setTemplate(makeGroupSymbolsTemplateData(group)); + + generateGroupOutputFiles(group); + } +} + +void RustGenerator::generateOutputFiles(const string &fileNameExtension) { + // Add program multiline comment if it exists + if (m_def->hasProgramSymbol()) { + Program *program = m_def->getProgramSymbol(); + m_templateData["mlComment"] = program->getMlComment(); + } else { + m_templateData["mlComment"] = ""; + } + + // Convert filename to snake_case for Rust naming conventions + string rustFileName = toSnakeCase(fileNameExtension); + + // For Rust, we generate a single .rs file + generateOutputFile(rustFileName + ".rs", "rust_template", m_templateData, + kRustTemplate); +} + +cpptempl::data_map RustGenerator::makeGroupSymbolsTemplateData(Group *group) { + data_map groupSymbols; + + Log::info("Generating symbols template data for group %s\n", + group->getName().c_str()); + + groupSymbols["name"] = group->getName(); + groupSymbols["id"] = 0; // Groups don't have unique IDs like interfaces + + // Process each symbol type + groupSymbols["constants"] = processGroupConstants(group); + groupSymbols["typeAliases"] = processGroupTypeAliases(group); + groupSymbols["interfaces"] = processGroupInterfaces(group); + groupSymbols["enums"] = processGroupEnums(group); + groupSymbols["structs"] = processGroupStructs(group); + + return groupSymbols; +} + +cpptempl::data_map RustGenerator::getFunctionTemplateData(Group *group, + Function *fn) { + data_map functionInfo; + + functionInfo["name"] = toSnakeCase(fn->getName()); + functionInfo["id"] = fn->getUniqueId(); + functionInfo["constName"] = toPascalCase(fn->getName()); + functionInfo["handlerName"] = toPascalCase(fn->getName()) + "Handler"; + functionInfo["prototype"] = getFunctionPrototype(group, fn); + + setTemplateComments(fn, functionInfo); + + // Build parameters string for async trait method + stringstream asyncParams; + stringstream clientParams; + + // Collect @length annotated parameters to skip in trait method signatures + std::set lengthParams = collectLengthParams(fn); + + // Process parameters + data_list params; + vector outParamNames; + vector outParamTypes; + bool hasOutParams = + processFunctionParameters(fn, lengthParams, params, asyncParams, + clientParams, outParamNames, outParamTypes); + + functionInfo["parameters"] = asyncParams.str(); + functionInfo["clientParameters"] = clientParams.str(); + functionInfo["paramCallList"] = generateParameterCallList(fn); + + // Return type handling + string rustReturnType; + string clientReturnType; + determineReturnTypes(fn, hasOutParams, outParamTypes, rustReturnType, + clientReturnType); + + functionInfo["returnType"] = rustReturnType; + functionInfo["clientReturnType"] = clientReturnType; + + // Determine if codec parameter should be prefixed with underscore + // This happens when method has no parameters and no response + bool hasParamsForCodec = !fn->getParameters().getMembers().empty(); + DataType *funcReturnType = fn->getReturnType(); + bool hasReturnValue = funcReturnType && funcReturnType->getDataType() != + DataType::data_type_t::kVoidType; + bool hasResponse = !fn->isOneway() || hasReturnValue; + + bool shouldPrefixCodec = !hasParamsForCodec && !hasResponse; + string codecParamName = shouldPrefixCodec ? "_codec" : "codec"; + functionInfo["codecParam"] = codecParamName; + + // Determine if sequence parameter should be prefixed with underscore + // This happens for oneway methods (they don't send responses so sequence is + // unused) + bool shouldPrefixSequence = fn->isOneway(); + string sequenceParamName = shouldPrefixSequence ? "_sequence" : "sequence"; + functionInfo["sequenceParam"] = sequenceParamName; + + // Generate server handler code + string serverHandlerCode; + if (fn->isOneway()) { + serverHandlerCode = generateOnewayServerHandler(fn, codecParamName); + } else { + serverHandlerCode = + generateRegularServerHandler(fn, codecParamName, sequenceParamName); + } + functionInfo["serverHandlerCode"] = serverHandlerCode; + + // Generate client method code + vector clientOutParamTypes; + bool clientHasOutParams; + string clientMethodCode = + generateClientMethodCode(fn, clientOutParamTypes, clientHasOutParams); + functionInfo["clientMethodCode"] = clientMethodCode; + functionInfo["params"] = params; + + return functionInfo; +} + +cpptempl::data_map +RustGenerator::getFunctionTypeTemplateData(Group *group, FunctionType *fn) { + // For callback/function types - similar to getFunctionTemplateData but for + // function pointers + data_map functionTypeInfo; + + functionTypeInfo["name"] = escapeKeyword(fn->getName()); + + setTemplateComments(fn, functionTypeInfo); + + // Add parameters + data_list params; + for (auto param : fn->getParameters().getMembers()) { + data_map paramInfo; + paramInfo["name"] = escapeKeyword(toSnakeCase(param->getName())); + paramInfo["type"] = getTypeString(param->getDataType()); + paramInfo["direction"] = getDirectionString(param); + setTemplateComments(param, paramInfo); + params.push_back(paramInfo); + } + functionTypeInfo["parameters"] = params; + + // Return type + DataType *returnType = fn->getReturnType(); + if (returnType && + returnType->getDataType() != DataType::data_type_t::kVoidType) { + functionTypeInfo["returnType"] = getTypeString(returnType); + functionTypeInfo["hasReturn"] = true; + } else { + functionTypeInfo["hasReturn"] = false; + } + + return functionTypeInfo; +} + +void RustGenerator::setTemplateComments(Symbol *symbol, + cpptempl::data_map &symbolInfo) { + string comment = symbol->getDescription(); + if (!comment.empty()) { + // Convert to Rust doc comments + if (symbol->isDatatypeSymbol()) { + // For datatypes (structs, enums, etc.), use outer doc comments (///) + // Convert inner doc comments (//!) to outer doc comments (///) + string mlComment = symbol->getMlComment(); + // Only replace the beginning of mlComment with "///" if it starts with + // "//!" + if (mlComment.rfind("//!", 0) == 0) { + mlComment.replace(0, 3, "///"); + } + symbolInfo["comment"] = mlComment; + + } else { + symbolInfo["comment"] = "/// " + comment; + } + } else { + symbolInfo["comment"] = ""; + } +} + +string RustGenerator::getFunctionPrototype(Group *group, FunctionBase *fn, + const string &interfaceName, + const string &name, + bool insideInterfaceCall) { + stringstream prototype; + + // Cast fn to Function to access getName() - FunctionBase doesn't have + // getName() + Function *function = dynamic_cast(fn); + if (function) { + prototype << "fn " << escapeKeyword(function->getName()) << "("; + } else { + // Fallback for non-Function types + prototype << "fn unknown_function("; + } + + // Add parameters + bool first = true; + for (auto param : fn->getParameters().getMembers()) { + if (!first) { + prototype << ", "; + } + first = false; + + prototype << escapeKeyword(toSnakeCase(param->getName())) << ": "; + + param_direction_t dir = getDirection(param); + DataType *paramType = param->getDataType(); + + if (dir == param_direction_t::kOutDirection || + dir == param_direction_t::kInoutDirection) { + prototype << "&mut "; + prototype << getTypeString(paramType); + } else { + // For input parameters, determine the correct parameter type + if (paramType->getDataType() == DataType::data_type_t::kAliasType) { + AliasType *aliasType = dynamic_cast(paramType); + if (isStringType(aliasType->getElementType())) { + // String alias types use the alias name directly (owned) + prototype << escapeKeyword(paramType->getName()); + } else { + // Non-string alias types use the alias name directly (owned) + prototype << escapeKeyword(paramType->getName()); + } + } else if (isStringType(paramType)) { + // Regular strings use references + prototype << "&" << getTypeString(paramType); + } else { + // All other types (primitives, structs, enums) use owned values + prototype << getTypeString(paramType); + } + } + } + + prototype << ")"; + + // Return type + DataType *returnType = fn->getReturnType(); + if (returnType && + returnType->getDataType() != DataType::data_type_t::kVoidType) { + prototype << " -> " << getTypeString(returnType); + } + + return prototype.str(); +} + +string RustGenerator::getTypeString(DataType *dataType) { + if (!dataType) { + return "()"; + } + + switch (dataType->getDataType()) { + case DataType::data_type_t::kBuiltinType: { + BuiltinType *builtinType = dynamic_cast(dataType); + switch (builtinType->getBuiltinType()) { + case BuiltinType::builtin_type_t::kBoolType: + return "bool"; + case BuiltinType::builtin_type_t::kInt8Type: + return "i8"; + case BuiltinType::builtin_type_t::kInt16Type: + return "i16"; + case BuiltinType::builtin_type_t::kInt32Type: + return "i32"; + case BuiltinType::builtin_type_t::kInt64Type: + return "i64"; + case BuiltinType::builtin_type_t::kUInt8Type: + return "u8"; + case BuiltinType::builtin_type_t::kUInt16Type: + return "u16"; + case BuiltinType::builtin_type_t::kUInt32Type: + return "u32"; + case BuiltinType::builtin_type_t::kUInt64Type: + return "u64"; + case BuiltinType::builtin_type_t::kFloatType: + return "f32"; + case BuiltinType::builtin_type_t::kDoubleType: + return "f64"; + case BuiltinType::builtin_type_t::kStringType: + return "String"; + case BuiltinType::builtin_type_t::kBinaryType: + return "Vec"; + default: + return "()"; + } + } + case DataType::data_type_t::kArrayType: { + ArrayType *arrayType = dynamic_cast(dataType); + return format_string("[%s; %d]", + getTypeString(arrayType->getElementType()).c_str(), + arrayType->getElementCount()); + } + case DataType::data_type_t::kListType: { + ListType *listType = dynamic_cast(dataType); + return format_string("Vec<%s>", + getTypeString(listType->getElementType()).c_str()); + } + case DataType::data_type_t::kStructType: + case DataType::data_type_t::kEnumType: + case DataType::data_type_t::kUnionType: + return escapeKeyword(dataType->getName()); + case DataType::data_type_t::kVoidType: + return "()"; + case DataType::data_type_t::kAliasType: { + AliasType *aliasType = dynamic_cast(dataType); + return getTypeString(aliasType->getElementType()); + } + case DataType::data_type_t::kFunctionType: { + FunctionType *fnType = dynamic_cast(dataType); + (void)fnType; // Suppress unused variable warning + return format_string( + "fn(%s) -> %s", "/* params */", + "/* return */"); // TODO: Implement proper function type + } + default: + return "()"; + } +} + +bool RustGenerator::isListType(DataType *dataType) { + return dataType && + (dataType->getDataType() == DataType::data_type_t::kListType || + dataType->getDataType() == DataType::data_type_t::kArrayType); +} + +bool RustGenerator::isStringType(DataType *dataType) { + if (!dataType) + return false; + + if (dataType->getDataType() == DataType::data_type_t::kBuiltinType) { + BuiltinType *builtinType = dynamic_cast(dataType); + return builtinType->getBuiltinType() == + BuiltinType::builtin_type_t::kStringType; + } + + return false; +} + +param_direction_t RustGenerator::getDirection(StructMember *param) { + // Check for direction annotations + auto direction = param->getDirection(); + if (direction != param_direction_t::kInDirection && + direction != param_direction_t::kOutDirection && + direction != param_direction_t::kInoutDirection) { + // Default to input if no direction specified + return param_direction_t::kInDirection; + } + return direction; +} + +std::string RustGenerator::getDirectionString(StructMember *param) { + param_direction_t direction = getDirection(param); + switch (direction) { + case param_direction_t::kInDirection: + return "in"; + case param_direction_t::kOutDirection: + return "out"; + case param_direction_t::kInoutDirection: + return "inout"; + case param_direction_t::kReturn: + return "return"; + default: + return "in"; + } +} + +std::string RustGenerator::generateParamWrite(StructMember *param, + const std::string ¶mName) { + DataType *dataType = param->getDataType(); + + if (!dataType) + return "/* unknown parameter type */"; + + switch (dataType->getDataType()) { + case DataType::data_type_t::kBuiltinType: { + BuiltinType *builtinType = dynamic_cast(dataType); + return generateBuiltinTypeWrite(builtinType, "request_codec", paramName, + false); + } + case DataType::data_type_t::kStructType: + case DataType::data_type_t::kEnumType: + return paramName + ".write(&mut request_codec)"; + case DataType::data_type_t::kListType: { + ListType *listType = dynamic_cast(dataType); + return string("request_codec.start_write_list(") + paramName + + ".len() as u32)?;\n" + " for item in &" + paramName + + " {\n" + " " + + generateListItemWrite(listType->getElementType(), "item") + "?;\n" + + " }"; + } + case DataType::data_type_t::kAliasType: { + AliasType *aliasType = dynamic_cast(dataType); + return generateParamWriteForDataType(aliasType->getElementType(), + paramName); + } + default: + return "/* unsupported parameter type */"; + } +} + +std::string RustGenerator::generateTypeRead(DataType *dataType, + const std::string &codecName) { + if (!dataType) + return "()"; + + switch (dataType->getDataType()) { + case DataType::data_type_t::kBuiltinType: { + BuiltinType *builtinType = dynamic_cast(dataType); + string mutCodecName = codecName; + // Only add &mut for response_codec, not for codec parameter which is + // already &mut + if (mutCodecName == "response_codec") { + mutCodecName = "(&mut " + mutCodecName + ")"; + } + return generateBuiltinTypeRead(builtinType, mutCodecName); + } + case DataType::data_type_t::kStructType: + case DataType::data_type_t::kEnumType: { + string mutCodecName = codecName; + // Only add &mut for response_codec, not for codec parameter which is + // already &mut + if (mutCodecName == "response_codec") { + mutCodecName = "(&mut " + mutCodecName + ")"; + } + return escapeKeyword(dataType->getName()) + "::read(" + mutCodecName + ")"; + } + case DataType::data_type_t::kListType: { + ListType *listType = dynamic_cast(dataType); + string mutCodecName = codecName; + // Only add &mut for response_codec, not for codec parameter which is + // already &mut + if (mutCodecName == "response_codec") { + mutCodecName = "(&mut " + mutCodecName + ")"; + } + string elementRead = + generateTypeRead(listType->getElementType(), codecName); + return string("(|| -> ErpcResult> {\n") + + " let list_len = " + mutCodecName + + ".start_read_list()?;\n" + + " let mut list = Vec::new();\n" + + " for _ in 0..list_len {\n" + + " list.push(" + elementRead + "?);\n" + + " }\n" + + " Ok(list)\n" + " })()"; + } + case DataType::data_type_t::kAliasType: { + AliasType *aliasType = dynamic_cast(dataType); + return generateTypeRead(aliasType->getElementType(), codecName); + } + case DataType::data_type_t::kVoidType: + return "()"; + default: + return "Default::default()"; + } +} + +std::string RustGenerator::generateListItemWrite(DataType *elementType, + const std::string &itemName) { + return generateParamWriteForDataType(elementType, itemName); +} + +std::string +RustGenerator::generateParamWriteForDataType(DataType *dataType, + const std::string ¶mName) { + if (!dataType) + return "/* unknown type */"; + + switch (dataType->getDataType()) { + case DataType::data_type_t::kBuiltinType: { + BuiltinType *builtinType = dynamic_cast(dataType); + string actualParam = paramName; + // For list items, dereference primitive types + if (paramName == "item") { + actualParam = "*" + paramName; + } + + // Special handling for string and binary types + if (builtinType->getBuiltinType() == + BuiltinType::builtin_type_t::kStringType || + builtinType->getBuiltinType() == + BuiltinType::builtin_type_t::kBinaryType) { + actualParam = paramName; // Don't dereference for strings/binary + } + + return generateBuiltinTypeWrite(builtinType, "request_codec", actualParam, + false); + } + case DataType::data_type_t::kStructType: + case DataType::data_type_t::kEnumType: + return paramName + ".write(&mut request_codec)"; + case DataType::data_type_t::kAliasType: { + AliasType *aliasType = dynamic_cast(dataType); + return generateParamWriteForDataType(aliasType->getElementType(), + paramName); + } + default: + return "/* unsupported type for list item */"; + } +} + +std::string RustGenerator::generateMemberWrite(StructMember *member, + const std::string &memberName) { + DataType *dataType = member->getDataType(); + + if (!dataType) + return "/* unknown member type */"; + + switch (dataType->getDataType()) { + case DataType::data_type_t::kBuiltinType: { + BuiltinType *builtinType = dynamic_cast(dataType); + return generateBuiltinTypeWrite(builtinType, "codec", memberName, true) + + ";"; + } + case DataType::data_type_t::kStructType: + case DataType::data_type_t::kEnumType: + return memberName + ".write(codec)?;"; + case DataType::data_type_t::kListType: { + ListType *listType = dynamic_cast(dataType); + DataType *elementType = listType->getElementType(); + + string code = "codec.start_write_list(" + memberName + ".len() as u32)?;\n"; + code += " for item in &" + memberName + " {\n"; + + // Check if we need to dereference (for primitive types) or not (for + // structs) + string itemRef = "item"; + if (elementType->getDataType() == DataType::data_type_t::kBuiltinType || + (elementType->getDataType() == DataType::data_type_t::kAliasType && + dynamic_cast(elementType) + ->getElementType() + ->getDataType() == DataType::data_type_t::kBuiltinType)) { + itemRef = "*item"; + } + + code += " " + generateTypeWrite(elementType, itemRef) + ";\n"; + code += " }"; + return code; + } + case DataType::data_type_t::kAliasType: { + // For alias types, handle the underlying type directly + AliasType *aliasType = dynamic_cast(dataType); + StructMember aliasedMember(*member); + aliasedMember.setDataType(aliasType->getElementType()); + return generateMemberWrite(&aliasedMember, memberName); + } + default: + return "/* unsupported member type */"; + } +} + +string RustGenerator::determineEnumReprType(EnumType *enumType) { + // Use the actual underlying data type from the enum definition + // In eRPC, enums follow C/C++ convention where the underlying type is int32 + // This ensures wire compatibility with C++ and Java implementations + (void)enumType; // Suppress unused parameter warning + + return "i32"; +} + +string RustGenerator::getCodecMethodForReprType(const string &reprType) { + // Map Rust repr types to codec method names + if (reprType == "u8") { + return "uint8"; + } else if (reprType == "u16") { + return "uint16"; + } else if (reprType == "u32") { + return "uint32"; + } else if (reprType == "u64") { + return "uint64"; + } else if (reprType == "i8") { + return "int8"; + } else if (reprType == "i16") { + return "int16"; + } else if (reprType == "i32") { + return "int32"; + } else if (reprType == "i64") { + return "int64"; + } else { + // Default to uint32 for unknown types + return "uint32"; + } +} + +std::string RustGenerator::generateClientRequestSerialization( + Function *fn, const std::vector &inParams) { + std::ostringstream requestCode; + + Interface *interface = fn->getInterface(); + string interfaceServiceId = "ServiceId::" + interface->getName(); + string interfaceMethodId = + interface->getName() + "Method::" + toPascalCase(fn->getName()); + + // Check if we have parameters to determine if codec needs to be mutable + bool hasParams = !inParams.empty(); + + requestCode << " // Serialize parameters to request_data\n"; + if (hasParams) { + requestCode << " let mut request_codec = BasicCodec::new();\n"; + } else { + requestCode << " let request_codec = BasicCodec::new();\n"; + } + requestCode << " \n"; + + // Write each input parameter + for (StructMember *param : inParams) { + string paramName = escapeKeyword(toSnakeCase(param->getName())); + requestCode << " // Write " << paramName << "\n"; + string paramWriteCode = generateParamWrite(param, paramName); + if (paramWriteCode.find("for item in") != string::npos) { + // This is a list with a for loop, don't add ?; + requestCode << " " << paramWriteCode << "\n"; + } else { + // Regular parameter, add ?; + requestCode << " " << paramWriteCode << "?;\n"; + } + } + + if (!hasParams) { + requestCode << " // No parameters to serialize\n"; + } + + requestCode << " \n"; + requestCode + << " let request_data = request_codec.as_bytes().to_vec();\n"; + requestCode << " \n"; + requestCode << " let " + << (fn->isOneway() ? "_" : "response_data") + << " = self.client\n"; + requestCode << " .perform_request(\n"; + requestCode << " " << interfaceServiceId << ".as_u8(),\n"; + requestCode << " " << interfaceMethodId << ".as_u8(),\n"; + requestCode << " " << (fn->isOneway() ? "true" : "false") + << ",\n"; + requestCode << " request_data\n"; + requestCode << " )\n"; + requestCode << " .await?;\n"; + + return requestCode.str(); +} + +std::string RustGenerator::generateClientResponseDeserialization( + Function *fn, const std::vector &outParamTypes, + const std::vector &outParamNames, bool hasOutParams) { + std::ostringstream responseCode; + + DataType *returnType = fn->getReturnType(); + bool isVoidReturn = !returnType || returnType->getDataType() == + DataType::data_type_t::kVoidType; + + // For void return types without out params, or oneway methods, no response + // deserialization needed + if (isVoidReturn || fn->isOneway()) { + return " Ok(())"; // No response data to deserialize + } + + responseCode << " // Deserialize response_data\n"; + responseCode << " if response_data.is_empty() {\n"; + responseCode << " return Err(\"Empty response data " + "received\".into());\n"; + responseCode << " }\n"; + responseCode << " \n"; + responseCode << " let mut response_codec = " + "BasicCodec::from_data(response_data);\n"; + responseCode << " \n"; + + // Collect @length annotated parameters to skip reading them separately + std::set lengthParams; + for (auto param : fn->getParameters().getMembers()) { + param_direction_t direction = getDirection(param); + if (direction == param_direction_t::kOutDirection || + direction == param_direction_t::kInoutDirection) { + // Check for @length annotation + string annotations = param->getDescription(); + if (annotations.find("@length(") != string::npos) { + // Extract the length parameter name from @length(paramName) + size_t start = annotations.find("@length(") + 8; + size_t end = annotations.find(")", start); + if (end != string::npos) { + string lengthParamName = annotations.substr(start, end - start); + lengthParams.insert(lengthParamName); + } + } + } + } + + if (hasOutParams && returnType && + returnType->getDataType() != DataType::data_type_t::kVoidType) { + // Function has both return value and out parameters + responseCode << " // Read return value and out parameters\n"; + + // Read out parameters first, deriving @length parameter values from lists + for (size_t i = 0; i < outParamTypes.size(); ++i) { + string paramName = outParamNames[i]; + + // Skip reading @length parameters separately - they're derived from list + // headers + if (lengthParams.find(paramName) != lengthParams.end()) { + // This is a @length parameter - derive its value from the corresponding + // list + responseCode << " // Note: " << paramName + << " is derived from list length\n"; + continue; + } + + responseCode << generateOutParamRead(outParamTypes[i], outParamNames[i]); + } + + // Then read return value + responseCode << " let return_value = " + << generateTypeRead(returnType, "&mut response_codec") + << "?;\n"; + + // Derive @length parameter values from lists + for (size_t i = 0; i < outParamNames.size(); ++i) { + string paramName = outParamNames[i]; + if (lengthParams.find(paramName) != lengthParams.end()) { + // Find the corresponding list parameter to get length from + for (size_t j = 0; j < outParamNames.size(); ++j) { + if (i != j && outParamTypes[j]->getDataType() == + DataType::data_type_t::kListType) { + responseCode << " let " << paramName << " = " + << outParamNames[j] << ".len() as u32;\n"; + break; + } + } + } + } + + // Return tuple with return value and all out parameters + responseCode << " Ok((return_value"; + for (const auto ¶mName : outParamNames) { + responseCode << ", " << paramName; + } + responseCode << "))"; + } else if (hasOutParams) { + // Function has only out parameters + if (outParamTypes.size() == 1) { + responseCode << " // Read out parameter\n"; + string paramName = outParamNames[0]; + + // Skip reading @length parameters separately + if (lengthParams.find(paramName) == lengthParams.end()) { + responseCode << generateOutParamRead(outParamTypes[0], + outParamNames[0]); + } + responseCode << " Ok(" << outParamNames[0] << ")"; + } else { + responseCode << " // Read out parameters\n"; + for (size_t i = 0; i < outParamTypes.size(); ++i) { + string paramName = outParamNames[i]; + + // Skip reading @length parameters separately + if (lengthParams.find(paramName) != lengthParams.end()) { + responseCode << " // Note: " << paramName + << " is derived from list length\n"; + continue; + } + + responseCode << generateOutParamRead(outParamTypes[i], + outParamNames[i]); + } + + // Derive @length parameter values from lists + for (size_t i = 0; i < outParamNames.size(); ++i) { + string paramName = outParamNames[i]; + if (lengthParams.find(paramName) != lengthParams.end()) { + // Find the corresponding list parameter to get length from + for (size_t j = 0; j < outParamNames.size(); ++j) { + if (i != j && outParamTypes[j]->getDataType() == + DataType::data_type_t::kListType) { + responseCode << " let " << paramName << " = " + << outParamNames[j] << ".len() as u32;\n"; + break; + } + } + } + } + + responseCode << " Ok(("; + for (size_t i = 0; i < outParamNames.size(); ++i) { + if (i > 0) + responseCode << ", "; + responseCode << outParamNames[i]; + } + responseCode << "))"; + } + } else if (returnType && + returnType->getDataType() != DataType::data_type_t::kVoidType) { + responseCode << " // Read return value\n"; + responseCode << " let result = " + << generateTypeRead(returnType, "response_codec") << "?;\n"; + responseCode << " Ok(result)"; + } else { + responseCode << " Ok(())"; + } + + return responseCode.str(); +} + +std::string RustGenerator::generateOutParamRead(DataType *paramType, + const std::string ¶mName) { + std::ostringstream outParamCode; + + string codecName = getCodecNameForType(paramType, "response_codec"); + string readCode = generateTypeRead(paramType, codecName); + + if (paramType->getDataType() == DataType::data_type_t::kListType) { + outParamCode << " let " << paramName << " = " << readCode + << "?;\n"; + } else { + outParamCode << " let " << paramName << " = " << readCode + << "?;\n"; + } + + return outParamCode.str(); +} + +std::string +RustGenerator::getCodecNameForType(DataType *dataType, + const std::string &baseCodecName) { + if (dataType->getDataType() == DataType::data_type_t::kStructType || + dataType->getDataType() == DataType::data_type_t::kEnumType) { + return "&mut " + baseCodecName; + } else { + return baseCodecName; + } +} + +std::string RustGenerator::generateServerParameterDeserialization( + Function *fn, const std::string &codecParamName) { + stringstream code; + + // Collect @length annotated parameters to skip reading them separately + std::set lengthParams; + for (auto param : fn->getParameters().getMembers()) { + // Check if this parameter is referenced by any @length annotation + StructMember *referencedFrom = findParamReferencedFromAnn( + fn->getParameters().getMembers(), param->getName(), LENGTH_ANNOTATION); + if (referencedFrom) { + lengthParams.insert(param->getName()); + } + } + + // Collect input parameters + std::vector inParams; + for (auto param : fn->getParameters().getMembers()) { + param_direction_t direction = getDirection(param); + if (direction == param_direction_t::kInDirection || + direction == param_direction_t::kInoutDirection) { + inParams.push_back(param); + } + } + + for (auto param : inParams) { + string paramName = escapeKeyword(toSnakeCase(param->getName())); + + // Skip @length parameters - they're transmitted in list headers + if (lengthParams.find(param->getName()) != lengthParams.end()) { + continue; + } + + DataType *paramType = param->getDataType(); + + code << " // Deserialize " << paramName + << " parameter\n"; + + if (isStringType(paramType)) { + code << " let " << paramName << " = " << codecParamName + << ".read_string()?;\n"; + } else if (paramType->getDataType() == DataType::data_type_t::kAliasType) { + // Handle type aliases - get the underlying type + AliasType *aliasType = dynamic_cast(paramType); + DataType *underlyingType = aliasType->getElementType(); + + if (isStringType(underlyingType)) { + code << " let " << paramName << " = " + << codecParamName << ".read_string()?;\n"; + } else if (underlyingType->getDataType() == + DataType::data_type_t::kBuiltinType) { + BuiltinType *builtinType = dynamic_cast(underlyingType); + switch (builtinType->getBuiltinType()) { + case BuiltinType::builtin_type_t::kUInt8Type: + code << " let " << paramName << " = " + << codecParamName << ".read_uint8()?;\n"; + break; + case BuiltinType::builtin_type_t::kUInt16Type: + code << " let " << paramName << " = " + << codecParamName << ".read_uint16()?;\n"; + break; + case BuiltinType::builtin_type_t::kUInt32Type: + code << " let " << paramName << " = " + << codecParamName << ".read_uint32()?;\n"; + break; + case BuiltinType::builtin_type_t::kUInt64Type: + code << " let " << paramName << " = " + << codecParamName << ".read_uint64()?;\n"; + break; + case BuiltinType::builtin_type_t::kInt8Type: + code << " let " << paramName << " = " + << codecParamName << ".read_int8()?;\n"; + break; + case BuiltinType::builtin_type_t::kInt16Type: + code << " let " << paramName << " = " + << codecParamName << ".read_int16()?;\n"; + break; + case BuiltinType::builtin_type_t::kInt32Type: + code << " let " << paramName << " = " + << codecParamName << ".read_int32()?;\n"; + break; + case BuiltinType::builtin_type_t::kInt64Type: + code << " let " << paramName << " = " + << codecParamName << ".read_int64()?;\n"; + break; + case BuiltinType::builtin_type_t::kFloatType: + code << " let " << paramName << " = " + << codecParamName << ".read_float()?;\n"; + break; + case BuiltinType::builtin_type_t::kDoubleType: + code << " let " << paramName << " = " + << codecParamName << ".read_double()?;\n"; + break; + case BuiltinType::builtin_type_t::kBoolType: + code << " let " << paramName << " = " + << codecParamName << ".read_bool()?;\n"; + break; + case BuiltinType::builtin_type_t::kBinaryType: + code << " let " << paramName << " = " + << codecParamName << ".read_binary()?;\n"; + break; + default: + code << " let " << paramName + << " = Default::default(); // TODO: Handle " + << builtinType->getName() << "\n"; + break; + } + } else { + string typeName = getTypeString(underlyingType); + code << " let " << paramName << " = " << typeName + << "::read(codec)?;\n"; + } + } else if (paramType->getDataType() == + DataType::data_type_t::kBuiltinType) { + BuiltinType *builtinType = dynamic_cast(paramType); + switch (builtinType->getBuiltinType()) { + case BuiltinType::builtin_type_t::kUInt8Type: + code << " let " << paramName << " = " + << codecParamName << ".read_uint8()?;\n"; + break; + case BuiltinType::builtin_type_t::kUInt16Type: + code << " let " << paramName << " = " + << codecParamName << ".read_uint16()?;\n"; + break; + case BuiltinType::builtin_type_t::kUInt32Type: + code << " let " << paramName << " = " + << codecParamName << ".read_uint32()?;\n"; + break; + case BuiltinType::builtin_type_t::kUInt64Type: + code << " let " << paramName << " = " + << codecParamName << ".read_uint64()?;\n"; + break; + case BuiltinType::builtin_type_t::kInt8Type: + code << " let " << paramName << " = " + << codecParamName << ".read_int8()?;\n"; + break; + case BuiltinType::builtin_type_t::kInt16Type: + code << " let " << paramName << " = " + << codecParamName << ".read_int16()?;\n"; + break; + case BuiltinType::builtin_type_t::kInt32Type: + code << " let " << paramName << " = " + << codecParamName << ".read_int32()?;\n"; + break; + case BuiltinType::builtin_type_t::kInt64Type: + code << " let " << paramName << " = " + << codecParamName << ".read_int64()?;\n"; + break; + case BuiltinType::builtin_type_t::kFloatType: + code << " let " << paramName << " = " + << codecParamName << ".read_float()?;\n"; + break; + case BuiltinType::builtin_type_t::kDoubleType: + code << " let " << paramName << " = " + << codecParamName << ".read_double()?;\n"; + break; + case BuiltinType::builtin_type_t::kBoolType: + code << " let " << paramName << " = " + << codecParamName << ".read_bool()?;\n"; + break; + case BuiltinType::builtin_type_t::kBinaryType: + code << " let " << paramName << " = " + << codecParamName << ".read_binary()?;\n"; + break; + default: + code << " let " << paramName + << " = Default::default(); // TODO: Handle " + << builtinType->getName() << "\n"; + break; + } + } else if (paramType->getDataType() == DataType::data_type_t::kStructType || + paramType->getDataType() == DataType::data_type_t::kEnumType) { + string typeName = getTypeString(paramType); + code << " let " << paramName << " = " << typeName + << "::read(codec)?;\n"; + } else if (paramType->getDataType() == DataType::data_type_t::kListType) { + code << " let " << paramName << " = " + << generateTypeRead(paramType, codecParamName) << "?;\n"; + } else { + code << " let " << paramName + << " = Default::default(); // TODO: Handle type " + << getTypeString(paramType) << "\n"; + } + + code << " \n"; + } + + return code.str(); +} + +std::string RustGenerator::generateServerResponseSerialization( + Function *fn, const std::string &codecParamName) { + stringstream code; + + // Collect @length annotated parameters to skip serializing them separately + std::set lengthParams; + for (auto param : fn->getParameters().getMembers()) { + param_direction_t direction = getDirection(param); + if (direction == param_direction_t::kOutDirection || + direction == param_direction_t::kInoutDirection) { + // Check for @length annotation + string annotations = param->getDescription(); + if (annotations.find("@length(") != string::npos) { + // Extract the length parameter name from @length(paramName) + size_t start = annotations.find("@length(") + 8; + size_t end = annotations.find(")", start); + if (end != string::npos) { + string lengthParamName = annotations.substr(start, end - start); + lengthParams.insert(lengthParamName); + } + } + } + } + + // Collect output parameters with names, filtering out @length parameters + std::vector> outParams; + bool hasOutParams = false; + for (auto param : fn->getParameters().getMembers()) { + param_direction_t direction = getDirection(param); + if (direction == param_direction_t::kOutDirection || + direction == param_direction_t::kInoutDirection) { + string paramName = escapeKeyword(toSnakeCase(param->getName())); + + // Skip @length parameters - they're transmitted in list headers + if (lengthParams.find(param->getName()) != lengthParams.end()) { + continue; + } + + outParams.push_back(std::make_pair(paramName, param->getDataType())); + hasOutParams = true; + } + } + + DataType *returnType = fn->getReturnType(); + bool hasReturnValue = (returnType && returnType->getDataType() != + DataType::data_type_t::kVoidType); + + code << " // Serialize response\n"; + + if (hasReturnValue && hasOutParams) { + // Function has both return value and out parameters - extract tuple + // variables for clarity + code << " let ("; + code << "return_value"; + for (const auto &outParam : outParams) { + code << ", " << outParam.first; // Use actual parameter name + } + code << ") = response;\n"; + + // Serialize out parameters first + for (const auto &outParam : outParams) { + code << " " + << generateTypeWrite(outParam.second, outParam.first) + << "; // Out parameter: " << outParam.first << "\n"; + } + + // Then serialize return value + code << " " + << generateTypeWrite(returnType, "return_value") + << "; // Return value\n"; + } else if (hasOutParams) { + // Function has only out parameters + if (outParams.size() == 1) { + code << " " + << generateTypeWrite(outParams[0].second, "response") + << "; // Single out parameter: " << outParams[0].first << "\n"; + } else { + // Multiple out parameters - extract tuple variables + code << " let ("; + for (size_t i = 0; i < outParams.size(); ++i) { + if (i > 0) + code << ", "; + code << outParams[i].first; // Use actual parameter name + } + code << ") = response;\n"; + + for (const auto &outParam : outParams) { + code << " " + << generateTypeWrite(outParam.second, outParam.first) + << "; // Out parameter: " << outParam.first << "\n"; + } + } + } else if (hasReturnValue) { + // Function has only return value + code << " " + << generateTypeWrite(returnType, "response") << "; // Return value\n"; + } + // If no return value and no out parameters, nothing to serialize + + return code.str(); +} + +std::string RustGenerator::generateServerErrorResponseSerialization( + Function *fn, const std::string &codecParamName) { + stringstream code; + + // Collect @length annotated parameters to skip serializing them separately + std::set lengthParams; + for (auto param : fn->getParameters().getMembers()) { + param_direction_t direction = getDirection(param); + if (direction == param_direction_t::kOutDirection || + direction == param_direction_t::kInoutDirection) { + // Check for @length annotation + string annotations = param->getDescription(); + if (annotations.find("@length(") != string::npos) { + // Extract the length parameter name from @length(paramName) + size_t start = annotations.find("@length(") + 8; + size_t end = annotations.find(")", start); + if (end != string::npos) { + string lengthParamName = annotations.substr(start, end - start); + lengthParams.insert(lengthParamName); + } + } + } + } + + // Collect output parameters with names, filtering out @length parameters + std::vector> outParams; + bool hasOutParams = false; + for (auto param : fn->getParameters().getMembers()) { + param_direction_t direction = getDirection(param); + if (direction == param_direction_t::kOutDirection || + direction == param_direction_t::kInoutDirection) { + string paramName = escapeKeyword(toSnakeCase(param->getName())); + + // Skip @length parameters - they're transmitted in list headers + if (lengthParams.find(param->getName()) != lengthParams.end()) { + continue; + } + + outParams.push_back(std::make_pair(paramName, param->getDataType())); + hasOutParams = true; + } + } + + DataType *returnType = fn->getReturnType(); + bool hasReturnValue = (returnType && returnType->getDataType() != + DataType::data_type_t::kVoidType); + + code << " // Send error response with default out " + "parameters\n"; + + if (hasOutParams) { + // Serialize default values for out parameters first + for (const auto &outParam : outParams) { + code << " " + << generateDefaultErrorWrite(outParam.second) + << "; // Default out parameter: " << outParam.first << "\n"; + } + } + + if (hasReturnValue) { + // Then serialize error return value + code << " " + << generateDefaultErrorWrite(returnType) + << "; // Error return value\n"; + } + + return code.str(); +} + +std::string RustGenerator::generateDefaultErrorWrite(DataType *dataType) { + stringstream code; + + if (dataType->getDataType() == DataType::data_type_t::kListType) { + // For lists, write empty list + code << "codec.start_write_list(0)?"; + } else if (dataType->getDataType() == DataType::data_type_t::kBuiltinType) { + BuiltinType *builtinType = dynamic_cast(dataType); + code << generateBuiltinTypeDefaultWrite(builtinType, "codec"); + } else if (dataType->getDataType() == DataType::data_type_t::kAliasType) { + // Handle type aliases - get the underlying type + AliasType *aliasType = dynamic_cast(dataType); + DataType *underlyingType = aliasType->getElementType(); + return generateDefaultErrorWrite(underlyingType); + } else if (dataType->getDataType() == DataType::data_type_t::kStructType) { + // Use the struct's type name with Default::default() + string typeName = escapeKeyword(dataType->getName()); + code << typeName << "::default().write(codec)?"; + } else if (dataType->getDataType() == DataType::data_type_t::kEnumType) { + // Use the enum's type name with Default::default() + string typeName = escapeKeyword(dataType->getName()); + code << typeName << "::default().write(codec)?"; + } else { + code << "Default::default().write(codec)?"; + } + + return code.str(); +} + +std::string RustGenerator::generateTypeWrite(DataType *dataType, + const std::string &variableName) { + stringstream code; + + if (dataType->getDataType() == DataType::data_type_t::kListType) { + // For lists, we need to write the length and then each element + ListType *listType = dynamic_cast(dataType); + DataType *elementType = listType->getElementType(); + + code << "codec.start_write_list(" << variableName << ".len() as u32)?;\n"; + code << " for item in &" << variableName + << " {\n"; + + // Check if we need to dereference (for primitive types) or not (for + // structs) + string itemRef = "item"; + if (elementType->getDataType() == DataType::data_type_t::kBuiltinType || + (elementType->getDataType() == DataType::data_type_t::kAliasType && + dynamic_cast(elementType) + ->getElementType() + ->getDataType() == DataType::data_type_t::kBuiltinType)) { + itemRef = "*item"; + } + + code << " " + << generateTypeWrite(elementType, itemRef) << ";\n"; + code << " }"; + } else if (dataType->getDataType() == DataType::data_type_t::kBuiltinType) { + BuiltinType *builtinType = dynamic_cast(dataType); + code << generateBuiltinTypeWrite(builtinType, "codec", variableName, true); + } else if (dataType->getDataType() == DataType::data_type_t::kAliasType) { + // Handle type aliases - get the underlying type + AliasType *aliasType = dynamic_cast(dataType); + DataType *underlyingType = aliasType->getElementType(); + return generateTypeWrite(underlyingType, variableName); + } else if (dataType->getDataType() == DataType::data_type_t::kStructType) { + code << variableName << ".write(codec)?"; + } else if (dataType->getDataType() == DataType::data_type_t::kEnumType) { + code << "codec.write_int32(" << variableName << " as i32)?"; + } else { + code << variableName << ".write(codec)? // TODO: Handle type"; + } + + return code.str(); +} + +std::set RustGenerator::collectLengthParams(Function *fn) { + std::set lengthParams; + for (auto param : fn->getParameters().getMembers()) { + // Check if this parameter is referenced by any @length annotation + StructMember *referencedFrom = findParamReferencedFromAnn( + fn->getParameters().getMembers(), param->getName(), LENGTH_ANNOTATION); + if (referencedFrom) { + lengthParams.insert(param->getName()); + } + } + return lengthParams; +} + +bool RustGenerator::processFunctionParameters( + Function *fn, const std::set &lengthParams, + cpptempl::data_list ¶ms, std::stringstream &asyncParams, + std::stringstream &clientParams, std::vector &outParamNames, + std::vector &outParamTypes) { + bool hasOutParams = false; + + for (auto param : fn->getParameters().getMembers()) { + cpptempl::data_map paramInfo; + paramInfo["name"] = escapeKeyword(toSnakeCase(param->getName())); + paramInfo["type"] = getTypeString(param->getDataType()); + paramInfo["direction"] = getDirectionString(param); + + setTemplateComments(param, paramInfo); + params.push_back(paramInfo); + + param_direction_t direction = getDirection(param); + if (direction == param_direction_t::kInDirection || + direction == param_direction_t::kInoutDirection) { + // Skip @length parameters in trait method signatures + if (lengthParams.find(param->getName()) != lengthParams.end()) { + continue; + } + + asyncParams << ", " << escapeKeyword(toSnakeCase(param->getName())) + << ": "; + clientParams << ", " << escapeKeyword(toSnakeCase(param->getName())) + << ": "; + + if (isStringType(param->getDataType())) { + asyncParams << "&str"; + clientParams << "&str"; + } else { + asyncParams << getTypeString(param->getDataType()); + clientParams << getTypeString(param->getDataType()); + } + } + + if (direction == param_direction_t::kOutDirection || + direction == param_direction_t::kInoutDirection) { + hasOutParams = true; + outParamNames.push_back(escapeKeyword(toSnakeCase(param->getName()))); + outParamTypes.push_back(param->getDataType()); + } + } + + return hasOutParams; +} + +std::string RustGenerator::generateParameterCallList(Function *fn) { + std::stringstream paramCallList; + bool firstParam = true; + for (auto param : fn->getParameters().getMembers()) { + param_direction_t direction = getDirection(param); + if (direction == param_direction_t::kInDirection || + direction == param_direction_t::kInoutDirection) { + if (!firstParam) + paramCallList << ", "; + firstParam = false; + paramCallList << escapeKeyword(toSnakeCase(param->getName())); + } + } + return paramCallList.str(); +} + +void RustGenerator::determineReturnTypes( + Function *fn, bool hasOutParams, + const std::vector &outParamTypes, std::string &rustReturnType, + std::string &clientReturnType) { + DataType *returnType = fn->getReturnType(); + + if (hasOutParams && returnType && + returnType->getDataType() != DataType::data_type_t::kVoidType) { + // Function has both return value and out parameters + rustReturnType = " -> Result<(" + getTypeString(returnType); + clientReturnType = " -> Result<(" + getTypeString(returnType); + for (size_t i = 0; i < outParamTypes.size(); ++i) { + rustReturnType += ", " + getTypeString(outParamTypes[i]); + clientReturnType += ", " + getTypeString(outParamTypes[i]); + } + rustReturnType += "), Box>"; + clientReturnType += "), Box>"; + } else if (hasOutParams) { + // Function has only out parameters + if (outParamTypes.size() == 1) { + rustReturnType = " -> Result<" + getTypeString(outParamTypes[0]) + + ", Box>"; + clientReturnType = rustReturnType; + } else { + rustReturnType = " -> Result<("; + clientReturnType = " -> Result<("; + for (size_t i = 0; i < outParamTypes.size(); ++i) { + if (i > 0) { + rustReturnType += ", "; + clientReturnType += ", "; + } + rustReturnType += getTypeString(outParamTypes[i]); + clientReturnType += getTypeString(outParamTypes[i]); + } + rustReturnType += "), Box>"; + clientReturnType += "), Box>"; + } + } else if (returnType && + returnType->getDataType() != DataType::data_type_t::kVoidType) { + // Function has only return value + rustReturnType = " -> Result<" + getTypeString(returnType) + + ", Box>"; + clientReturnType = rustReturnType; + } else { + // Function has no return value or out parameters + // Always use Result type for consistency and better error handling + rustReturnType = + " -> Result<(), Box>"; + clientReturnType = rustReturnType; + } +} + +std::string RustGenerator::generateMethodCallParameters( + Function *fn, const std::set &lengthParams) { + std::stringstream paramList; + bool first = true; + + for (auto param : fn->getParameters().getMembers()) { + param_direction_t direction = getDirection(param); + if (direction == param_direction_t::kInDirection || + direction == param_direction_t::kInoutDirection) { + // Skip @length parameters + if (lengthParams.find(param->getName()) != lengthParams.end()) { + continue; + } + + if (!first) + paramList << ", "; + first = false; + + std::string paramName = escapeKeyword(toSnakeCase(param->getName())); + DataType *paramType = param->getDataType(); + + // Handle parameter passing based on trait signature expectations + if (paramType->getDataType() == DataType::data_type_t::kAliasType) { + AliasType *aliasType = dynamic_cast(paramType); + if (isStringType(aliasType->getElementType())) { + // String alias types expect owned values in trait + paramList << paramName; + } else { + // Non-string alias types expect owned values in trait + paramList << paramName; + } + } else if (isStringType(paramType)) { + // Regular strings expect references in trait + paramList << "&" << paramName; + } else { + // All other types (primitives, structs, enums) expect owned values in + // trait + paramList << paramName; + } + } + } + + return paramList.str(); +} + +std::string +RustGenerator::generateOnewayServerHandler(Function *fn, + const std::string &codecParamName) { + std::stringstream serverHandlerCode; + std::set lengthParams = collectLengthParams(fn); + + serverHandlerCode << " \n"; + + // Generate parameter deserialization + serverHandlerCode << generateServerParameterDeserialization(fn, + codecParamName); + + // Generate method call with parameters + serverHandlerCode << " let _ = self.service." + << toSnakeCase(fn->getName()) << "("; + serverHandlerCode << generateMethodCallParameters(fn, lengthParams); + serverHandlerCode << ").await;\n"; + serverHandlerCode << " Ok(())"; + + return serverHandlerCode.str(); +} + +std::string RustGenerator::generateRegularServerHandler( + Function *fn, const std::string &codecParamName, + const std::string &sequenceParamName) { + std::stringstream serverHandlerCode; + std::set lengthParams = collectLengthParams(fn); + + // Get the interface name to construct proper constant names + Interface *interface = fn->getInterface(); + std::string interfaceServiceId = "ServiceId::" + interface->getName(); + std::string interfaceMethodId = + interface->getName() + "Method::" + toPascalCase(fn->getName()); + + serverHandlerCode << " \n"; + + // Generate parameter deserialization + serverHandlerCode << generateServerParameterDeserialization(fn, + codecParamName); + + // Generate method call with parameters + serverHandlerCode << " let result = self.service." + << toSnakeCase(fn->getName()) << "("; + serverHandlerCode << generateMethodCallParameters(fn, lengthParams); + serverHandlerCode << ").await;\n"; + + // Check if there are any response parameters to serialize + DataType *returnType = fn->getReturnType(); + bool hasReturnValue = (returnType && returnType->getDataType() != + DataType::data_type_t::kVoidType); + + bool hasOutParams = false; + for (auto param : fn->getParameters().getMembers()) { + param_direction_t direction = getDirection(param); + if (direction == param_direction_t::kOutDirection || + direction == param_direction_t::kInoutDirection) { + hasOutParams = true; + break; + } + } + + bool hasResponseParams = hasReturnValue || hasOutParams; + + serverHandlerCode << " match result {\n"; + if (hasResponseParams) { + serverHandlerCode << " Ok(response) => {\n"; + } else { + serverHandlerCode << " Ok(_) => {\n"; + } + serverHandlerCode << " let reply_info = " + "MessageInfo::new(MessageType::Reply, " + << interfaceServiceId << ".as_u8(), " << interfaceMethodId + << ".as_u8(), " << sequenceParamName << ");\n"; + serverHandlerCode << " " << codecParamName + << ".start_write_message(&reply_info)?;\n"; + + // Generate response serialization + serverHandlerCode << generateServerResponseSerialization(fn, codecParamName); + + serverHandlerCode << " Ok(())\n"; + serverHandlerCode << " },\n"; + serverHandlerCode << " Err(_e) => {\n"; + serverHandlerCode << " let reply_info = " + "MessageInfo::new(MessageType::Reply, " + << interfaceServiceId << ".as_u8(), " << interfaceMethodId + << ".as_u8(), " << sequenceParamName << ");\n"; + serverHandlerCode << " " << codecParamName + << ".start_write_message(&reply_info)?;\n"; + + // Generate error response with default out parameters + serverHandlerCode << generateServerErrorResponseSerialization(fn, + codecParamName); + + serverHandlerCode << " Ok(())\n"; + serverHandlerCode << " }\n"; + serverHandlerCode << " }"; + + return serverHandlerCode.str(); +} + +std::string RustGenerator::generateClientMethodCode( + Function *fn, std::vector &outParamTypes, bool &hasOutParams) { + std::stringstream clientMethodCode; + + // Clear and reset collections + outParamTypes.clear(); + hasOutParams = false; + + // Collect input parameters and output parameter types with names + std::vector inParams; + std::vector clientOutParamNames; + // Collect @length annotated parameters to skip in client serialization + std::set clientLengthParams = collectLengthParams(fn); + + for (auto param : fn->getParameters().getMembers()) { + param_direction_t direction = getDirection(param); + if (direction == param_direction_t::kInDirection || + direction == param_direction_t::kInoutDirection) { + // Skip @length parameters in client serialization + if (clientLengthParams.find(param->getName()) == + clientLengthParams.end()) { + inParams.push_back(param); + } + } + if (direction == param_direction_t::kOutDirection || + direction == param_direction_t::kInoutDirection) { + outParamTypes.push_back(param->getDataType()); + clientOutParamNames.push_back( + escapeKeyword(toSnakeCase(param->getName()))); + hasOutParams = true; + } + } + + // Generate request serialization + clientMethodCode << generateClientRequestSerialization(fn, inParams); + + if (fn->isOneway()) { + clientMethodCode << " Ok(())"; + } else { + // Generate response deserialization + clientMethodCode << generateClientResponseDeserialization( + fn, outParamTypes, clientOutParamNames, hasOutParams); + } + + return clientMethodCode.str(); +} + +cpptempl::data_list RustGenerator::processGroupConstants(Group *group) { + cpptempl::data_list constants; + for (Symbol *symbol : group->getSymbols()) { + if (symbol->getSymbolType() == Symbol::symbol_type_t::kConstSymbol) { + ConstType *constType = dynamic_cast(symbol); + if (constType) { + cpptempl::data_map constInfo; + constInfo["name"] = escapeKeyword(constType->getName()); + constInfo["type"] = getTypeString(constType->getDataType()); + constInfo["value"] = constType->getValue()->toString(); + setTemplateComments(constType, constInfo); + constants.push_back(constInfo); + } + } + } + return constants; +} + +cpptempl::data_list RustGenerator::processGroupTypeAliases(Group *group) { + cpptempl::data_list typeAliases; + for (Symbol *symbol : group->getSymbols()) { + if (symbol->getSymbolType() == Symbol::symbol_type_t::kTypenameSymbol) { + DataType *dataType = dynamic_cast(symbol); + if (dataType && + dataType->getDataType() == DataType::data_type_t::kAliasType) { + AliasType *aliasType = dynamic_cast(dataType); + if (aliasType) { + cpptempl::data_map aliasInfo; + aliasInfo["name"] = escapeKeyword(aliasType->getName()); + aliasInfo["type"] = getTypeString(aliasType->getElementType()); + setTemplateComments(aliasType, aliasInfo); + typeAliases.push_back(aliasInfo); + } + } + } + } + return typeAliases; +} + +cpptempl::data_list RustGenerator::processGroupInterfaces(Group *group) { + cpptempl::data_list interfaces; + for (auto interface : group->getInterfaces()) { + cpptempl::data_map interfaceInfo; + interfaceInfo["name"] = escapeKeyword(interface->getName()); + interfaceInfo["id"] = interface->getUniqueId(); + interfaceInfo["moduleName"] = toSnakeCase(interface->getName()) + "_server"; + interfaceInfo["description"] = interface->getDescription(); + interfaceInfo["serviceIdName"] = + toUpperSnakeCase(interface->getName()) + "_SERVICE_ID"; + interfaceInfo["methodConstName"] = + toUpperSnakeCase(interface->getName()) + "_METHOD"; + interfaceInfo["generateServer"] = true; + interfaceInfo["generateClient"] = true; + + setTemplateComments(interface, interfaceInfo); + + // Add functions + cpptempl::data_list functions; + for (auto function : interface->getFunctions()) { + functions.push_back(getFunctionTemplateData(group, function)); + } + interfaceInfo["functions"] = functions; + + interfaces.push_back(interfaceInfo); + } + return interfaces; +} + +cpptempl::data_list RustGenerator::processGroupEnums(Group *group) { + cpptempl::data_list enums; + for (Symbol *symbol : group->getSymbols()) { + if (symbol->getSymbolType() == Symbol::symbol_type_t::kTypenameSymbol) { + DataType *dataType = dynamic_cast(symbol); + if (dataType && + dataType->getDataType() == DataType::data_type_t::kEnumType) { + EnumType *enumType = dynamic_cast(dataType); + if (enumType) { + cpptempl::data_map enumInfo; + enumInfo["name"] = escapeKeyword(enumType->getName()); + + setTemplateComments(enumType, enumInfo); + + cpptempl::data_list members; + string firstMemberName = ""; + for (auto member : enumType->getMembers()) { + cpptempl::data_map memberInfo; + string enumMemberName = member->getName(); + memberInfo["name"] = escapeKeyword(enumMemberName); + memberInfo["value"] = member->getValue(); + memberInfo["originalName"] = member->getName(); + setTemplateComments(member, memberInfo); + members.push_back(memberInfo); + + // Store first member name for default implementation + if (firstMemberName.empty()) { + firstMemberName = escapeKeyword(enumMemberName); + } + } + enumInfo["members"] = members; + enumInfo["firstMember"] = firstMemberName; + + // Determine optimal representation type based on enum values from + // lexical analyzer input + string reprType = determineEnumReprType(enumType); + string codecMethod = getCodecMethodForReprType(reprType); + + enumInfo["reprType"] = reprType; + enumInfo["writeCode"] = + "codec.write_" + codecMethod + "(*self as " + reprType + ")?;"; + enumInfo["readCode"] = "codec.read_" + codecMethod + "()"; + + enums.push_back(enumInfo); + } + } + } + } + return enums; +} + +cpptempl::data_list RustGenerator::processGroupStructs(Group *group) { + cpptempl::data_list structs; + for (Symbol *symbol : group->getSymbols()) { + if (symbol->getSymbolType() == Symbol::symbol_type_t::kTypenameSymbol) { + DataType *dataType = dynamic_cast(symbol); + if (dataType && + dataType->getDataType() == DataType::data_type_t::kStructType) { + StructType *structType = dynamic_cast(dataType); + if (structType) { + cpptempl::data_map structInfo; + structInfo["name"] = escapeKeyword(structType->getName()); + + setTemplateComments(structType, structInfo); + + cpptempl::data_list members; + for (auto member : structType->getMembers()) { + cpptempl::data_map memberInfo; + string memberName = escapeKeyword(toSnakeCase(member->getName())); + memberInfo["name"] = memberName; + memberInfo["type"] = getTypeString(member->getDataType()); + memberInfo["originalType"] = member->getDataType()->getName(); + memberInfo["originalName"] = member->getName(); + memberInfo["defaultValue"] = getDefaultValue(member->getDataType()); + memberInfo["writeCode"] = + generateMemberWrite(member, "self." + memberName); + memberInfo["readCode"] = + generateTypeRead(member->getDataType(), "codec"); + setTemplateComments(member, memberInfo); + members.push_back(memberInfo); + } + structInfo["members"] = members; + structs.push_back(structInfo); + } + } + } + } + return structs; +} + +std::string RustGenerator::generateBuiltinTypeWrite( + BuiltinType *builtinType, const std::string &codecName, + const std::string &variableName, bool addErrorHandling) { + std::string operation; + std::string errorSuffix = addErrorHandling ? "?" : ""; + + switch (builtinType->getBuiltinType()) { + case BuiltinType::builtin_type_t::kBoolType: + operation = codecName + ".write_bool(" + variableName + ")" + errorSuffix; + break; + case BuiltinType::builtin_type_t::kInt8Type: + operation = codecName + ".write_int8(" + variableName + ")" + errorSuffix; + break; + case BuiltinType::builtin_type_t::kInt16Type: + operation = codecName + ".write_int16(" + variableName + ")" + errorSuffix; + break; + case BuiltinType::builtin_type_t::kInt32Type: + operation = codecName + ".write_int32(" + variableName + ")" + errorSuffix; + break; + case BuiltinType::builtin_type_t::kInt64Type: + operation = codecName + ".write_int64(" + variableName + ")" + errorSuffix; + break; + case BuiltinType::builtin_type_t::kUInt8Type: + operation = codecName + ".write_uint8(" + variableName + ")" + errorSuffix; + break; + case BuiltinType::builtin_type_t::kUInt16Type: + operation = codecName + ".write_uint16(" + variableName + ")" + errorSuffix; + break; + case BuiltinType::builtin_type_t::kUInt32Type: + operation = codecName + ".write_uint32(" + variableName + ")" + errorSuffix; + break; + case BuiltinType::builtin_type_t::kUInt64Type: + operation = codecName + ".write_uint64(" + variableName + ")" + errorSuffix; + break; + case BuiltinType::builtin_type_t::kFloatType: + operation = codecName + ".write_float(" + variableName + ")" + errorSuffix; + break; + case BuiltinType::builtin_type_t::kDoubleType: + operation = codecName + ".write_double(" + variableName + ")" + errorSuffix; + break; + case BuiltinType::builtin_type_t::kStringType: + operation = + codecName + ".write_string(&" + variableName + ")" + errorSuffix; + break; + case BuiltinType::builtin_type_t::kBinaryType: + operation = + codecName + ".write_binary(&" + variableName + ")" + errorSuffix; + break; + default: + operation = variableName + ".write(" + codecName + ")" + errorSuffix; + break; + } + + return operation; +} + +std::string +RustGenerator::generateBuiltinTypeRead(BuiltinType *builtinType, + const std::string &codecName) { + switch (builtinType->getBuiltinType()) { + case BuiltinType::builtin_type_t::kBoolType: + return codecName + ".read_bool()"; + case BuiltinType::builtin_type_t::kInt8Type: + return codecName + ".read_int8()"; + case BuiltinType::builtin_type_t::kInt16Type: + return codecName + ".read_int16()"; + case BuiltinType::builtin_type_t::kInt32Type: + return codecName + ".read_int32()"; + case BuiltinType::builtin_type_t::kInt64Type: + return codecName + ".read_int64()"; + case BuiltinType::builtin_type_t::kUInt8Type: + return codecName + ".read_uint8()"; + case BuiltinType::builtin_type_t::kUInt16Type: + return codecName + ".read_uint16()"; + case BuiltinType::builtin_type_t::kUInt32Type: + return codecName + ".read_uint32()"; + case BuiltinType::builtin_type_t::kUInt64Type: + return codecName + ".read_uint64()"; + case BuiltinType::builtin_type_t::kFloatType: + return codecName + ".read_float()"; + case BuiltinType::builtin_type_t::kDoubleType: + return codecName + ".read_double()"; + case BuiltinType::builtin_type_t::kStringType: + return codecName + ".read_string()"; + case BuiltinType::builtin_type_t::kBinaryType: + return codecName + ".read_binary()"; + default: + return "Default::default()"; + } +} + +std::string +RustGenerator::generateBuiltinTypeDefaultWrite(BuiltinType *builtinType, + const std::string &codecName) { + switch (builtinType->getBuiltinType()) { + case BuiltinType::builtin_type_t::kBoolType: + return codecName + ".write_bool(bool::default())?"; + case BuiltinType::builtin_type_t::kInt8Type: + return codecName + ".write_int8(i8::default())?"; + case BuiltinType::builtin_type_t::kInt16Type: + return codecName + ".write_int16(i16::default())?"; + case BuiltinType::builtin_type_t::kInt32Type: + return codecName + ".write_int32(i32::default())?"; + case BuiltinType::builtin_type_t::kInt64Type: + return codecName + ".write_int64(i64::default())?"; + case BuiltinType::builtin_type_t::kUInt8Type: + return codecName + ".write_uint8(u8::default())?"; + case BuiltinType::builtin_type_t::kUInt16Type: + return codecName + ".write_uint16(u16::default())?"; + case BuiltinType::builtin_type_t::kUInt32Type: + return codecName + ".write_uint32(u32::default())?"; + case BuiltinType::builtin_type_t::kUInt64Type: + return codecName + ".write_uint64(u64::default())?"; + case BuiltinType::builtin_type_t::kFloatType: + return codecName + ".write_float(f32::default())?"; + case BuiltinType::builtin_type_t::kDoubleType: + return codecName + ".write_double(f64::default())?"; + case BuiltinType::builtin_type_t::kStringType: + return codecName + ".write_string(&String::default())?"; + case BuiltinType::builtin_type_t::kBinaryType: + return codecName + ".write_binary(&Vec::::default())?"; + default: + return codecName + + ".write_uint8(u8::default())?"; // Fallback for unknown types + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Template data - This will be populated when we create the template +//////////////////////////////////////////////////////////////////////////////// + +// Template strings converted from text files by txt_to_c.py. +extern const char *const kRustTemplate; diff --git a/erpcgen/src/RustGenerator.hpp b/erpcgen/src/RustGenerator.hpp new file mode 100644 index 000000000..1516b31d3 --- /dev/null +++ b/erpcgen/src/RustGenerator.hpp @@ -0,0 +1,606 @@ +/* + * Copyright (c) 2024-2025 NXP + * All rights reserved. + * + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _EMBEDDED_RPC__RUSTGENERATOR_H_ +#define _EMBEDDED_RPC__RUSTGENERATOR_H_ + +#include "Generator.hpp" +#include "cpptempl.hpp" + +#include +#include + +//////////////////////////////////////////////////////////////////////////////// +// Classes +//////////////////////////////////////////////////////////////////////////////// + +namespace erpcgen { + +/*! + * @brief Code generator for Rust. + */ +class RustGenerator : public Generator { +public: + /*! + * @brief This function is constructor of RustGenerator class. + * + * @param[in] def Contains all Symbols parsed from IDL files. + */ + explicit RustGenerator(InterfaceDefinition *def); + + /*! + * @brief This function is destructor of RustGenerator class. + * + * This function close opened files. + */ + virtual ~RustGenerator() {} + + /*! + * @brief This function generate output code for output files. + * + * This code call all necessary functions for prepare output code and parse it + * into output files. + */ + virtual void generate() override; + +protected: + /*! + * @brief This function sets group symbols template data. + * + * This function sets group symbols template data with all data, which + * are necessary for generating output code for output files. + * + * @param[in] group Pointer to a group. + * + * @return Data map with group symbols templates. + */ + virtual cpptempl::data_map + makeGroupSymbolsTemplateData(Group *group) override; + + /*! + * @brief This function return interface function template data. + * + * This function return interface function template data with all data, which + * are necessary for generating output code for output files. + * + * @param[in] group Pointer to a group. + * @param[in] fn From this are set interface function template data. + * + * @return Contains interface function data. + */ + virtual cpptempl::data_map getFunctionTemplateData(Group *group, + Function *fn) override; + + /*! + * @brief This function returns function type (callbacks type) template data. + * + * This function returns function type (callbacks type) template data with all + * data, which are necessary for generating output code for output files. Shim + * code is generating common function for serialization/deserialization of + * data. + * + * @param[in] group Group to which function belongs. + * @param[in] fn From this are set function type template data. + * + * @return Contains interface function data. + */ + virtual cpptempl::data_map + getFunctionTypeTemplateData(Group *group, FunctionType *fn) override; + + /*! + * @brief This function will get symbol comments and convert to language + * specific ones + * + * @param[in] symbol Pointer to symbol. + * @param[inout] symbolInfo Data map, which contains information about symbol. + */ + virtual void setTemplateComments(Symbol *symbol, + cpptempl::data_map &symbolInfo) override; + + /*! + * @brief This function generates output files. + * + * This function call functions for generating client/server header/source + * files. + * + * @param[in] fileNameExtension Extension for file name (for example for case + * that each interface will be generated in its set of output files). + */ + virtual void + generateOutputFiles(const std::string &fileNameExtension) override; + + /*! + * @brief This function return interface function prototype. + * + * @param[in] group Group to which function belongs. + * @param[in] fn Function for prototyping. + * @param[in] interfaceName Interface name used for function declaration. + * @param[in] name Name used for shared code in case of function type. + * @param[in] insideInterfaceCall interfaceClass specific. + * + * @return String prototype representation for given function. + */ + virtual std::string getFunctionPrototype( + Group *group, FunctionBase *fn, const std::string &interfaceName = "", + const std::string &name = "", bool insideInterfaceCall = false) override; + + /*! + * @brief This function returns Rust equivalent name for given data type. + * + * @param[in] dataType Given data type. + * + * @return String with Rust equivalent name for given data type. + */ + std::string getTypeString(DataType *dataType); + + /*! + * @brief This function returns information if given data type is list. + * + * @param[in] dataType Given data type. + * + * @retval true Given data type is list. + * @retval false Given data type is not list. + */ + bool isListType(DataType *dataType); + + /*! + * @brief This function returns information if given data type is string. + * + * @param[in] dataType Given data type. + * + * @retval true Given data type is string. + * @retval false Given data type is not string. + */ + bool isStringType(DataType *dataType); + + /*! + * @brief This function returns direction for function parameter. + * + * @param[in] param Function parameter. + * + * @return Direction of function parameter. + */ + param_direction_t getDirection(StructMember *param); + + /*! + * @brief This function returns direction string for function parameter. + * + * @param[in] param Function parameter. + * + * @return Direction string of function parameter. + */ + std::string getDirectionString(StructMember *param); + + /*! + * @brief This function generates code to write a parameter to the codec. + * + * @param[in] param Function parameter to serialize. + * @param[in] paramName Name of the parameter variable. + * + * @return Code string for writing the parameter to codec. + */ + std::string generateParamWrite(StructMember *param, + const std::string ¶mName); + + /*! + * @brief This function generates code to read a type from the codec. + * + * @param[in] dataType Data type to deserialize. + * @param[in] codecName Name of the codec variable. + * + * @return Code string for reading the type from codec. + */ + std::string generateTypeRead(DataType *dataType, + const std::string &codecName); + + /*! + * @brief This function generates code to write a list item to the codec. + * + * @param[in] elementType Type of the list element. + * @param[in] itemName Name of the item variable. + * + * @return Code string for writing the list item to codec. + */ + std::string generateListItemWrite(DataType *elementType, + const std::string &itemName); + + /*! + * @brief This function generates code to write a data type to the codec. + * + * @param[in] dataType Data type to serialize. + * @param[in] paramName Name of the parameter variable. + * + * @return Code string for writing the data type to codec. + */ + std::string generateParamWriteForDataType(DataType *dataType, + const std::string ¶mName); + + /*! + * @brief This function generates code to write a struct member to the codec. + * + * @param[in] member Struct member to serialize. + * @param[in] memberName Name of the member variable. + * + * @return Code string for writing the member to codec. + */ + std::string generateMemberWrite(StructMember *member, + const std::string &memberName); + +private: + std::set + m_rustKeywords; /*!< Set of Rust keywords that need to be escaped. */ + + /*! + * @brief This function initialize Rust keywords set. + */ + void initRustKeywords(); + + /*! + * @brief This function escapes Rust keywords. + * + * @param[in] name Input name to check against Rust keywords. + * + * @return Escaped name if it was a Rust keyword, original name otherwise. + */ + std::string escapeKeyword(const std::string &name); + + /*! + * @brief This function converts PascalCase/camelCase to snake_case. + * + * @param[in] name Input name in PascalCase or camelCase. + * + * @return snake_case version of the name. + */ + std::string toSnakeCase(const std::string &name); + + /*! + * @brief This function converts PascalCase/camelCase to UPPER_SNAKE_CASE. + * + * @param[in] name Input name in PascalCase or camelCase. + * + * @return UPPER_SNAKE_CASE version of the name. + */ + std::string toUpperSnakeCase(const std::string &name); + + /*! + * @brief This function returns the default value for a given data type in + * Rust. + * + * @param[in] dataType Given data type. + * + * @return String with Rust default value for given data type. + */ + std::string getDefaultValue(DataType *dataType); + + /*! + * @brief This function determines the optimal Rust repr type for an enum + * based on its values. + * + * @param[in] enumType Enum type from lexical analyzer input. + * + * @return String with optimal Rust repr type (u8, u16, u32, etc.). + */ + std::string determineEnumReprType(EnumType *enumType); + + /*! + * @brief This function returns the codec method name for a given repr type. + * + * @param[in] reprType Rust repr type (u8, u16, u32, etc.). + * + * @return String with codec method name (uint8, uint16, uint32, etc.). + */ + std::string getCodecMethodForReprType(const std::string &reprType); + + /*! + * @brief This function generates the client method request serialization + * code. + * + * @param[in] fn Function to generate request serialization for. + * @param[in] inParams Vector of input parameters. + * + * @return String with request serialization code. + */ + std::string generateClientRequestSerialization( + Function *fn, const std::vector &inParams); + + /*! + * @brief This function generates the client method response deserialization + * code. + * + * @param[in] fn Function to generate response deserialization for. + * @param[in] outParamTypes Vector of output parameter types. + * @param[in] hasOutParams Whether the function has output parameters. + * + * @return String with response deserialization code. + */ + std::string generateClientResponseDeserialization( + Function *fn, const std::vector &outParamTypes, + const std::vector &outParamNames, bool hasOutParams); + + /*! + * @brief This function generates code to read a single output parameter. + * + * @param[in] paramType Type of the output parameter. + * @param[in] paramIndex Index of the parameter (for naming). + * + * @return String with output parameter read code. + */ + std::string generateOutParamRead(DataType *paramType, + const std::string ¶mName); + + /*! + * @brief This function determines the appropriate codec name for a data type. + * + * @param[in] dataType Type to determine codec name for. + * @param[in] baseCodecName Base codec variable name. + * + * @return String with appropriate codec reference. + */ + std::string getCodecNameForType(DataType *dataType, + const std::string &baseCodecName); + + /*! + * @brief This function generates server-side parameter deserialization code. + * + * @param[in] fn Function to generate parameter deserialization for. + * @param[in] codecParamName Name of the codec parameter to use. + * + * @return String with parameter deserialization code. + */ + std::string + generateServerParameterDeserialization(Function *fn, + const std::string &codecParamName); + + /*! + * @brief This function generates server-side response serialization code. + * + * @param[in] fn Function to generate response serialization for. + * @param[in] codecParamName Name of the codec parameter to use. + * + * @return String with response serialization code. + */ + std::string + generateServerResponseSerialization(Function *fn, + const std::string &codecParamName); + + /*! + * @brief This function generates server-side error response serialization + * code with default out parameters. + * + * @param[in] fn Function to generate error response serialization for. + * @param[in] codecParamName Name of the codec parameter to use. + * + * @return String with error response serialization code. + */ + std::string + generateServerErrorResponseSerialization(Function *fn, + const std::string &codecParamName); + + /*! + * @brief This function generates code to write default error values for a + * specific type. + * + * @param[in] dataType Type to generate default error value for. + * + * @return String with default error value write code. + */ + std::string generateDefaultErrorWrite(DataType *dataType); + + /*! + * @brief This function generates code to write a value of a specific type to + * the codec. + * + * @param[in] dataType Type of the value to write. + * @param[in] variableName Name of the variable to write. + * + * @return String with type-specific write code. + */ + std::string generateTypeWrite(DataType *dataType, + const std::string &variableName); + + /*! + * @brief This function converts a name to PascalCase. + * + * @param[in] name Name to convert. + * + * @return String with PascalCase conversion. + */ + std::string toPascalCase(const std::string &name); + + /*! + * @brief This function collects @length annotated parameters to skip. + * + * @param[in] fn Function to process. + * + * @return Set of parameter names that are referenced by @length annotations. + */ + std::set collectLengthParams(Function *fn); + + /*! + * @brief This function processes function parameters and builds parameter + * lists. + * + * @param[in] fn Function to process. + * @param[in] lengthParams Set of @length parameters to skip. + * @param[out] params Template data list for parameters. + * @param[out] asyncParams String stream for async parameters. + * @param[out] clientParams String stream for client parameters. + * @param[out] outParamNames Vector of output parameter names. + * @param[out] outParamTypes Vector of output parameter types. + * + * @return True if function has output parameters. + */ + bool processFunctionParameters(Function *fn, + const std::set &lengthParams, + cpptempl::data_list ¶ms, + std::stringstream &asyncParams, + std::stringstream &clientParams, + std::vector &outParamNames, + std::vector &outParamTypes); + + /*! + * @brief This function generates parameter call list for function invocation. + * + * @param[in] fn Function to process. + * + * @return String with comma-separated parameter names. + */ + std::string generateParameterCallList(Function *fn); + + /*! + * @brief This function determines return type strings for function. + * + * @param[in] fn Function to process. + * @param[in] hasOutParams Whether function has output parameters. + * @param[in] outParamTypes Vector of output parameter types. + * @param[out] rustReturnType String for trait return type. + * @param[out] clientReturnType String for client return type. + */ + void determineReturnTypes(Function *fn, bool hasOutParams, + const std::vector &outParamTypes, + std::string &rustReturnType, + std::string &clientReturnType); + + /*! + * @brief This function generates server handler code for oneway functions. + * + * @param[in] fn Function to process. + * @param[in] codecParamName Name of codec parameter. + * + * @return String with server handler code. + */ + std::string generateOnewayServerHandler(Function *fn, + const std::string &codecParamName); + + /*! + * @brief This function generates server handler code for regular (non-oneway) + * functions. + * + * @param[in] fn Function to process. + * @param[in] codecParamName Name of codec parameter. + * @param[in] sequenceParamName Name of sequence parameter. + * + * @return String with server handler code. + */ + std::string + generateRegularServerHandler(Function *fn, const std::string &codecParamName, + const std::string &sequenceParamName); + + /*! + * @brief This function generates method call parameters for server handler. + * + * @param[in] fn Function to process. + * @param[in] lengthParams Set of @length parameters to skip. + * + * @return String with method call parameters. + */ + std::string + generateMethodCallParameters(Function *fn, + const std::set &lengthParams); + + /*! + * @brief This function generates client method code. + * + * @param[in] fn Function to process. + * @param[out] outParamTypes Vector to store output parameter types. + * @param[out] hasOutParams Reference to store whether function has output + * parameters. + * + * @return String with client method code. + */ + std::string generateClientMethodCode(Function *fn, + std::vector &outParamTypes, + bool &hasOutParams); + + /*! + * @brief This function processes constants from group symbols. + * + * @param[in] group Group to process constants from. + * + * @return Template data list for constants. + */ + cpptempl::data_list processGroupConstants(Group *group); + + /*! + * @brief This function processes type aliases from group symbols. + * + * @param[in] group Group to process type aliases from. + * + * @return Template data list for type aliases. + */ + cpptempl::data_list processGroupTypeAliases(Group *group); + + /*! + * @brief This function processes interfaces from group. + * + * @param[in] group Group to process interfaces from. + * + * @return Template data list for interfaces. + */ + cpptempl::data_list processGroupInterfaces(Group *group); + + /*! + * @brief This function processes enums from group symbols. + * + * @param[in] group Group to process enums from. + * + * @return Template data list for enums. + */ + cpptempl::data_list processGroupEnums(Group *group); + + /*! + * @brief This function processes structs from group symbols. + * + * @param[in] group Group to process structs from. + * + * @return Template data list for structs. + */ + cpptempl::data_list processGroupStructs(Group *group); + + /*! + * @brief This function generates codec write operation for a builtin type. + * + * @param[in] builtinType The builtin type to generate write operation for. + * @param[in] codecName Name of the codec variable. + * @param[in] variableName Name of the variable to write. + * @param[in] addErrorHandling Whether to add ? for error handling. + * + * @return String with codec write operation. + */ + std::string generateBuiltinTypeWrite(BuiltinType *builtinType, + const std::string &codecName, + const std::string &variableName, + bool addErrorHandling = true); + + /*! + * @brief This function generates codec read operation for a builtin type. + * + * @param[in] builtinType The builtin type to generate read operation for. + * @param[in] codecName Name of the codec variable. + * + * @return String with codec read operation. + */ + std::string generateBuiltinTypeRead(BuiltinType *builtinType, + const std::string &codecName); + + /*! + * @brief This function generates default value write operation for a builtin + * type. + * + * @param[in] builtinType The builtin type to generate default write for. + * @param[in] codecName Name of the codec variable. + * + * @return String with default value write operation. + */ + std::string generateBuiltinTypeDefaultWrite(BuiltinType *builtinType, + const std::string &codecName); +}; + +} // namespace erpcgen + +#endif // _EMBEDDED_RPC__RUSTGENERATOR_H_ diff --git a/erpcgen/src/erpcgen.cpp b/erpcgen/src/erpcgen.cpp index fe520d2cd..205c2dbbf 100644 --- a/erpcgen/src/erpcgen.cpp +++ b/erpcgen/src/erpcgen.cpp @@ -15,6 +15,7 @@ #include "Logging.hpp" #include "PythonGenerator.hpp" #include "JavaGenerator.hpp" +#include "RustGenerator.hpp" #include "SearchPath.hpp" #include "UniqueIdChecker.hpp" #include "options.hpp" @@ -71,6 +72,7 @@ Available languages (use with -g option):\n\ c C/C++\n\ py Python\n\ java Java\n\ + rust Rust\n\ \n\ Available codecs (use with --c option):\n\ basic BasicCodec\n\ @@ -103,6 +105,7 @@ class erpcgenTool kCLanguage, kPythonLanguage, kJavaLanguage, + kRustLanguage, }; /*!< Generated outputs format. */ typedef vector string_vector_t; /*!< Vector of positional arguments. */ @@ -218,6 +221,10 @@ class erpcgenTool { m_outputLanguage = languages_t::kJavaLanguage; } + else if (lang == "rust") + { + m_outputLanguage = languages_t::kRustLanguage; + } else { Log::error("error: unknown language %s", lang.c_str()); @@ -352,6 +359,11 @@ class erpcgenTool JavaGenerator(&def, m_javaPackageName).generate(); break; } + case languages_t::kRustLanguage: + { + RustGenerator(&def).generate(); + break; + } } } catch (exception &e) diff --git a/erpcgen/src/templates/rust_template.template b/erpcgen/src/templates/rust_template.template new file mode 100644 index 000000000..18e16cb86 --- /dev/null +++ b/erpcgen/src/templates/rust_template.template @@ -0,0 +1,300 @@ +{% if mlComment != "" %} +{$mlComment} + +{% endif %} +// +// Generated by erpcgen {$erpcVersion} on {$todaysDate}. +// +// AUTOGENERATED - DO NOT EDIT +// + +#![allow(unused_imports)] +#![allow(unused_variables)] +#![allow(dead_code)] +#![allow(non_camel_case_types)] +#![allow(unused_parens)] + +use async_trait::async_trait; +use erpc_rust::{ + auxiliary::{MessageInfo, MessageType}, + codec::{BasicCodec, BasicCodecFactory, Codec}, + error::SerializationError, + server::{BaseService, MethodHandler, Service}, + transport::Transport, + ClientManager, ErpcResult, +}; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; + +{% if not empty(group.constants) %} +/// Constants from IDL +{% for constant in group.constants %} +{$constant.comment} +pub const {$constant.name}: {$constant.type} = {$constant.value}; +{% endfor -- constants %} + +{% endif -- constants %} +{% if not empty(group.interfaces) %} +/// Service IDs (automatically assigned by eRPC generator based on interface order) +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ServiceId { +{% for interface in group.interfaces %} + {$interface.name} = {$interface.id}, +{% endfor -- interfaces %} +} + +impl ServiceId { + pub fn as_u8(self) -> u8 { + self as u8 + } +} + +// Method IDs for each service +{% for interface in group.interfaces %} +/// {$interface.name} method IDs +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum {$interface.name}Method { +{% for function in interface.functions %} + {$function.constName} = {$function.id}, +{% endfor -- functions %} +} + +impl {$interface.name}Method { + pub fn as_u8(self) -> u8 { + self as u8 + } +} + +{% endfor -- interfaces %} +{% endif -- interfaces %} +{% if not empty(group.typeAliases) %} +{% for alias in group.typeAliases %} +{$alias.comment} +pub type {$alias.name} = {$alias.type}; +{% endfor -- aliases %} + +{% endif -- typeAliases %} +{% if not empty(group.enums) %} +{% for enum in group.enums %} +{$enum.comment} +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[repr({$enum.reprType})] +pub enum {$enum.name} { +{% for member in enum.members %} + {$member.comment} + {$member.name} = {$member.value}, // {$member.originalName} in eRPC +{% endfor -- members %} +} + +impl Default for {$enum.name} { + fn default() -> Self { + {$enum.name}::{$enum.firstMember} + } +} + +impl {$enum.name} { + pub fn write(&self, codec: &mut dyn Codec) -> ErpcResult<()> { + {$enum.writeCode} + Ok(()) + } + + pub fn read(codec: &mut dyn Codec) -> ErpcResult { + let value = {$enum.readCode}?; + match value { +{% for member in enum.members %} + {$member.value} => Ok(Self::{$member.name}), +{% endfor -- members %} + _ => Err(SerializationError::InvalidEnumValue { + value: value as i32, + type_name: "{$enum.name}".to_string() + }.into()), + } + } +} + +{% endfor -- enums %} +{% endif -- enums %} +{% if not empty(group.structs) %} +{% for struct in group.structs %} +{$struct.comment} +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct {$struct.name} { +{% for member in struct.members %} + {$member.comment} + pub {$member.name}: {$member.type}, // {$member.originalType} {$member.originalName} +{% endfor -- members %} +} + +impl Default for {$struct.name} { + fn default() -> Self { + Self { +{% for member in struct.members %} + {$member.name}: {$member.defaultValue}, +{% endfor -- members %} + } + } +} + +impl {$struct.name} { + pub fn write(&self, codec: &mut dyn Codec) -> ErpcResult<()> { +{% for member in struct.members %} + {$member.writeCode} +{% endfor -- members %} + Ok(()) + } + + pub fn read(codec: &mut dyn Codec) -> ErpcResult { + Ok(Self { +{% for member in struct.members %} + {$member.name}: {$member.readCode}?, +{% endfor -- members %} + }) + } +} + +{% endfor -- structs %} +{% endif -- structs %} +{% if not empty(group.interfaces) %} +{% for interface in group.interfaces %} +/// ======================================================================= +/// {$interface.name} +/// ======================================================================= + +pub mod {$interface.moduleName} { + use super::*; + use std::sync::Arc; + +{% if interface.generateServer %} + /// Generated trait containing eRPC methods that should be implemented for use with {$interface.name}Server. + #[async_trait] + pub trait {$interface.name}: Send + Sync + 'static { +{% for function in interface.functions %} + {$function.comment} + async fn {$function.name}(&self{$function.parameters}){$function.returnType}; +{% endfor -- functions %} + } + + /// Server wrapper for {$interface.name} service + pub struct {$interface.name}Server + where + T: {$interface.name}, + { + inner: Arc, + base_service: BaseService, + } + + impl {$interface.name}Server + where + T: {$interface.name}, + { + /// Create a new {$interface.name}Server with service implementation + pub fn new(inner: T) -> Self { + let inner_arc = Arc::new(inner); + let base_service = Self::create_base_service(&inner_arc); + Self { + inner: inner_arc, + base_service, + } + } + + /// Get a reference to the inner service implementation + pub fn get_ref(&self) -> &T { + &self.inner + } + + /// Get the inner service implementation + pub fn into_inner(self) -> Arc { + self.inner + } + + fn create_base_service(service: &Arc) -> BaseService { + let mut base_service = BaseService::new(ServiceId::{$interface.name}.as_u8()); + + // Register all method handlers +{% for function in interface.functions %} + Self::register_{$function.name}(&mut base_service, Arc::clone(service)); +{% endfor -- functions %} + + base_service + } + +{% for function in interface.functions %} + fn register_{$function.name}(base_service: &mut BaseService, service: Arc) { + struct {$function.handlerName} { + service: Arc, + } + + #[async_trait] + impl MethodHandler for {$function.handlerName} { + async fn handle(&self, sequence: u32, codec: &mut dyn Codec) -> ErpcResult<()> { +{$function.serverHandlerCode} + } + } + + base_service.add_method({$interface.name}Method::{$function.constName}.as_u8(), {$function.handlerName} { service }); + } + +{% endfor -- functions %} + } + + /// Implementation of Service trait for {$interface.name}Server + #[async_trait] + impl Service for {$interface.name}Server + where + T: {$interface.name}, + { + fn service_id(&self) -> u8 { + self.base_service.service_id() + } + + async fn handle_invocation( + &self, + method_id: u8, + sequence: u32, + codec: &mut dyn Codec, + ) -> ErpcResult<()> { + self.base_service.handle_invocation(method_id, sequence, codec).await + } + + fn supported_methods(&self) -> Vec { + self.base_service.supported_methods() + } + } +{% endif -- generateServer %} + +{% if interface.generateClient %} + /// Client implementation for {$interface.name} + pub struct {$interface.name}Client<'a, T> + where + T: Transport, + { + client: &'a mut ClientManager, + } + + /// {$interface.name} client implementation + /// Matches eRPC IDL interface exactly with proper serialization + impl<'a, T> {$interface.name}Client<'a, T> + where + T: Transport, + { + pub fn new(client_manager: &'a mut ClientManager) -> Self { + Self { + client: client_manager, + } + } + +{% for function in interface.functions %} + {$function.comment} + pub async fn {$function.name}(&mut self{$function.clientParameters}){$function.clientReturnType} { + {$function.clientMethodCode} + } + +{% endfor -- functions %} + } +{% endif -- generateClient %} +} + +{% endfor -- interfaces %} +{% endif -- interfaces %} diff --git a/erpcgen/src/types/Annotation.hpp b/erpcgen/src/types/Annotation.hpp index e10d92b54..d4e1d3901 100644 --- a/erpcgen/src/types/Annotation.hpp +++ b/erpcgen/src/types/Annotation.hpp @@ -33,7 +33,8 @@ class Annotation kAll, kC, kPython, - kJava + kJava, + kRust }; /*! diff --git a/examples/temp_alarm_rust/Cargo.lock b/examples/temp_alarm_rust/Cargo.lock new file mode 100644 index 000000000..ac5fab7f3 --- /dev/null +++ b/examples/temp_alarm_rust/Cargo.lock @@ -0,0 +1,1001 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "anstream" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +dependencies = [ + "windows-sys 0.60.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.60.2", +] + +[[package]] +name = "async-trait" +version = "0.1.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "clap" +version = "4.5.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed87a9d530bb41a67537289bafcac159cb3ee28460e0a4571123d2a778a6a882" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64f4f3f3c77c94aff3c7e9aac9a2ca1974a5adf392a8bb751e827d6d127ab966" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "erpc_rust" +version = "0.1.0" +dependencies = [ + "async-trait", + "byteorder", + "bytes", + "futures", + "serde", + "serialport", + "thiserror 1.0.69", + "tokio", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "io-kit-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b" +dependencies = [ + "core-foundation-sys", + "mach2", +] + +[[package]] +name = "io-uring" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "libc", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libudev" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +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 = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.59.0", +] + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" + +[[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.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serialport" +version = "4.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb0bc984f6af6ef8bab54e6cf2071579ee75b9286aa9f2319a0d220c28b0a2b" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "core-foundation", + "core-foundation-sys", + "io-kit-sys", + "libudev", + "mach2", + "nix", + "scopeguard", + "unescaper", + "winapi", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "temp_alarm_rust" +version = "0.1.0" +dependencies = [ + "async-trait", + "clap", + "erpc_rust", + "serde", + "tokio", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tokio" +version = "1.47.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +dependencies = [ + "backtrace", + "bytes", + "io-uring", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "slab", + "socket2", + "tokio-macros", + "windows-sys 0.59.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "unescaper" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c01d12e3a56a4432a8b436f293c25f4808bdf9e9f9f98f9260bba1f1bc5a1f26" +dependencies = [ + "thiserror 2.0.12", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.3", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" diff --git a/examples/temp_alarm_rust/Cargo.toml b/examples/temp_alarm_rust/Cargo.toml new file mode 100644 index 000000000..e974ded84 --- /dev/null +++ b/examples/temp_alarm_rust/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "temp_alarm_rust" +version = "0.1.0" +edition = "2021" +description = "Rust eRPC Temperature Alarm Example" +license = "BSD-3-Clause" +authors = ["eRPC Team"] + +[features] +default = [] +serial = ["erpc_rust/serial"] + +[dependencies] +# Local dependency on the erpc_rust crate +erpc_rust = { path = "../../erpc_rust" } +tokio = { version = "1.0", features = ["full"] } +tracing = "0.1" +tracing-subscriber = "0.3" +clap = { version = "4.0", features = ["derive"] } +serde = { version = "1.0", features = ["derive"] } +async-trait = "0.1" diff --git a/examples/temp_alarm_rust/src/client_impl.rs b/examples/temp_alarm_rust/src/client_impl.rs new file mode 100644 index 000000000..5746cce72 --- /dev/null +++ b/examples/temp_alarm_rust/src/client_impl.rs @@ -0,0 +1,119 @@ +use crate::temp_alarm::temp_async_server::TempAsyncClient; +use crate::temp_alarm::temp_server::TempClient; +use crate::temp_alarm::SensorReadResult; +use erpc_rust::client::{ClientManager, CodecConfig}; + +pub async fn run_client() -> Result<(), Box> { + println!("🚀 Starting eRPC Temperature Client..."); + + let mut shared_client_manager = ClientManager::builder() + .tcp_connection("127.0.0.1:40000") + .codec(CodecConfig::Basic) + .connect() + .await?; + println!("✅ Connection validated and ClientManager created"); + + let mut temp_client = TempClient::new(&mut shared_client_manager); + + // Add a sensor + let sensor_added = temp_client + .add_sensor(1) + .await + .map_err(|e| format!("add_sensor error: {}", e))?; + println!("✅ Sensor added via TempClient: {}", sensor_added); + + // Set interval for the sensor + let interval_set = temp_client + .set_interval(1, 2.5) + .await + .map_err(|e| format!("set_interval error: {}", e))?; + println!("✅ Interval set via TempClient: {}", interval_set); + + // Set an alarm + let alarm_set = temp_client + .set_alarm(1, crate::temp_alarm::AlarmType::kHighAlarm, 30.0) + .await + .map_err(|e| format!("set_alarm error: {}", e))?; + println!("✅ Alarm set via TempClient: {}", alarm_set); + + // Enable the alarm + let alarm_enabled = temp_client + .enable_alarm(1, crate::temp_alarm::AlarmType::kHighAlarm) + .await + .map_err(|e| format!("enable_alarm error: {}", e))?; + println!("✅ Alarm enabled via TempClient: {}", alarm_enabled); + + // Get sensor info + let sensor_info = temp_client + .get_one_sensor(1) + .await + .map_err(|e| format!("get_one_sensor error: {}", e))?; + println!( + "✅ Sensor info retrieved via TempClient: addr={}, interval={}", + sensor_info.address, sensor_info.read_interval + ); + let mut temp_async_client = TempAsyncClient::new(&mut shared_client_manager); + + // Fire an alarm notification + temp_async_client + .alarm_fired(1, 75.0) + .await + .map_err(|e| format!("alarm_fired error: {}", e))?; + println!("✅ Alarm fired via TempAsyncClient"); + + // Send sensor reading results + let results = vec![ + SensorReadResult { + address: 1, + temp: 25.5, + }, + SensorReadResult { + address: 2, + temp: 30.2, + }, + SensorReadResult { + address: 3, + temp: 28.7, + }, + ]; + + temp_async_client + .read_results(results) + .await + .map_err(|e| format!("read_results error: {}", e))?; + println!("✅ Multiple sensor results sent via TempAsyncClient"); + + // Fire another alarm notification + temp_async_client + .alarm_fired(2, 45.5) + .await + .map_err(|e| format!("alarm_fired(2) error: {}", e))?; + println!("✅ Second alarm fired via TempAsyncClient"); + + // Send more sensor reading results + let more_results = vec![ + SensorReadResult { + address: 4, + temp: 32.1, + }, + SensorReadResult { + address: 5, + temp: 27.8, + }, + ]; + + temp_async_client + .read_results(more_results) + .await + .map_err(|e| format!("read_results(2) error: {}", e))?; + println!("✅ Additional sensor results sent via TempAsyncClient"); + + // Fire one more alarm with different parameters + temp_async_client + .alarm_fired(3, 85.2) + .await + .map_err(|e| format!("alarm_fired(3) error: {}", e))?; + println!("✅ Third alarm fired via TempAsyncClient"); + + Ok(()) +} diff --git a/examples/temp_alarm_rust/src/main.rs b/examples/temp_alarm_rust/src/main.rs new file mode 100644 index 000000000..5d2f361d8 --- /dev/null +++ b/examples/temp_alarm_rust/src/main.rs @@ -0,0 +1,33 @@ +use std::env; +use tokio; + +mod client_impl; +mod server_impl; +mod temp_alarm; + +use client_impl::run_client; +use server_impl::run_server; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let args: Vec = env::args().collect(); + + if args.len() < 2 { + println!("Usage: {} ", args[0]); + println!(" server - Start the eRPC agent callback server"); + println!(" client - Start the eRPC orchestrator client"); + return Ok(()); + } + + match args[1].as_str() { + "server" => run_server().await, + "client" => run_client() + .await + .map_err(|e| format!("Client error: {}", e).into()), + _ => { + println!("Invalid argument. Use 'server' or 'client'"); + println!("Usage: {} ", args[0]); + Ok(()) + } + } +} diff --git a/examples/temp_alarm_rust/src/server_impl.rs b/examples/temp_alarm_rust/src/server_impl.rs new file mode 100644 index 000000000..438fcbeb7 --- /dev/null +++ b/examples/temp_alarm_rust/src/server_impl.rs @@ -0,0 +1,217 @@ +use crate::temp_alarm::temp_async_server::{TempAsync, TempAsyncServer}; +use crate::temp_alarm::temp_server::{Temp, TempServer}; +use crate::temp_alarm::*; +use async_trait::async_trait; +use erpc_rust::{ + codec::BasicCodecFactory, + server::{MultiTransportServerBuilder, Server}, +}; + +/// Implementation of the Temp service +#[derive(Clone)] +struct TempServiceImpl { + // In a real implementation, you'd store sensor data here +} + +impl TempServiceImpl { + fn new() -> Self { + Self {} + } +} + +#[async_trait] +impl Temp for TempServiceImpl { + async fn add_sensor( + &self, + address: u8, + ) -> Result> { + println!("🔧 Adding sensor with address: {}", address); + Ok(true) + } + + async fn remove_sensor( + &self, + address: u8, + ) -> Result> { + println!("🗑️ Removing sensor with address: {}", address); + Ok(true) + } + + async fn set_interval( + &self, + address: u8, + interval: f32, + ) -> Result> { + println!( + "⏰ Setting interval for sensor {}: {} seconds", + address, interval + ); + Ok(true) + } + + async fn set_alarm( + &self, + address: u8, + alarm_type: AlarmType, + alarm_temp: f32, + ) -> Result> { + println!( + "🚨 Setting alarm for sensor {}: type {:?}, temp {}", + address, alarm_type, alarm_temp + ); + Ok(true) + } + + async fn enable_alarm( + &self, + address: u8, + alarm_type: AlarmType, + ) -> Result> { + println!( + "✅ Enabling alarm for sensor {}: type {:?}", + address, alarm_type + ); + Ok(true) + } + + async fn disable_alarm( + &self, + address: u8, + alarm_type: AlarmType, + ) -> Result> { + println!( + "❌ Disabling alarm for sensor {}: type {:?}", + address, alarm_type + ); + Ok(true) + } + + async fn get_one_sensor( + &self, + address: u8, + ) -> Result> { + println!("📊 Getting info for sensor: {}", address); + Ok(SensorInfo { + address, + read_interval: 1.0, + high_alarm: AlarmInfo { + temp: 25.0, + enabled: false, + }, + low_alarm: AlarmInfo { + temp: 10.0, + enabled: false, + }, + }) + } + + async fn get_all_sensors_b( + &self, + ) -> Result, Box> { + println!("📋 Getting all sensors"); + Ok(vec![SensorInfo { + address: 1, + read_interval: 1.0, + high_alarm: AlarmInfo { + temp: 25.0, + enabled: false, + }, + low_alarm: AlarmInfo { + temp: 10.0, + enabled: false, + }, + }]) + } + + async fn save_settings(&self) -> Result, Box> { + println!("💾 Saving settings"); + Ok(vec![0x01, 0x02, 0x03]) // Dummy saved state + } + + async fn load_settings( + &self, + saved_state: Vec, + ) -> Result> { + println!("📥 Loading settings: {} bytes", saved_state.len()); + Ok(true) + } + + async fn read_one_sensor( + &self, + address: u8, + ) -> Result> { + println!("🌡️ Reading sensor: {}", address); + // Return a dummy temperature reading + Ok(22.5) + } + + async fn read_sensors( + &self, + addresses: Vec, + ) -> Result> { + println!("📊 Reading {} sensors:", addresses.len()); + for address in &addresses { + println!(" 🌡️ Sensor address: {}", address); + } + Ok(true) + } +} + +/// Implementation of the TempAsync service +#[derive(Clone)] +struct TempAsyncServiceImpl { + // In a real implementation, you'd store callback state here +} + +impl TempAsyncServiceImpl { + fn new() -> Self { + Self {} + } +} + +#[async_trait] +impl TempAsync for TempAsyncServiceImpl { + async fn alarm_fired( + &self, + addr: u8, + temp: f32, + ) -> Result<(), Box> { + println!("🚨🔥 ALARM FIRED! Sensor {}: temperature {}", addr, temp); + Ok(()) + } + + async fn read_results( + &self, + results: Vec, + ) -> Result<(), Box> { + println!("📊 Received {} sensor readings:", results.len()); + for result in results { + println!(" 🌡️ Sensor {}: {}°C", result.address, result.temp); + } + Ok(()) + } +} + +/// Main server runner - Multi-Transport Server with TCP support +pub async fn run_server() -> Result<(), Box> { + use std::sync::Arc; + + let temp_service_impl = TempServiceImpl::new(); + let temp_async_service_impl = TempAsyncServiceImpl::new(); + // Create server with TCP transport + let mut server = MultiTransportServerBuilder::new() + .codec_factory(BasicCodecFactory::new()) + // TCP listeners + .tcp_listener("127.0.0.1:40000") // Main TCP API + // .serial_port("/dev/ttyUSB0", 115200) // Primary serial interface (commented out) + .service(Arc::new(TempServer::new(temp_service_impl))) + .service(Arc::new(TempAsyncServer::new(temp_async_service_impl))) + .build() + .await + .map_err(|e| format!("Failed to build multi-transport server: {}", e))?; + + println!("🚀 eRPC Server is running on TCP port 40000"); + // Run the server - handles ALL transport types in one loop! + server.run().await?; + Ok(()) +} diff --git a/examples/temp_alarm_rust/src/temp_alarm.rs b/examples/temp_alarm_rust/src/temp_alarm.rs new file mode 100644 index 000000000..eb93ef391 --- /dev/null +++ b/examples/temp_alarm_rust/src/temp_alarm.rs @@ -0,0 +1,1669 @@ +/*! + * Copyright (c) 2016, Freescale Semiconductor, Inc. + * Copyright 2016-2017 NXP + * All rights reserved. + * + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +// +// Generated by erpcgen 1.14.0 on Thu Aug 7 14:41:40 2025. +// +// AUTOGENERATED - DO NOT EDIT +// + +#![allow(unused_imports)] +#![allow(unused_variables)] +#![allow(dead_code)] +#![allow(non_camel_case_types)] +#![allow(unused_parens)] + +use async_trait::async_trait; +use erpc_rust::{ + auxiliary::{MessageInfo, MessageType}, + codec::{BasicCodec, BasicCodecFactory, Codec}, + error::SerializationError, + server::{BaseService, MethodHandler, Service}, + transport::Transport, + ClientManager, ErpcResult, +}; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; + +/// Service IDs (automatically assigned by eRPC generator based on interface order) +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ServiceId { + Temp = 1, + TempAsync = 2, +} + +impl ServiceId { + pub fn as_u8(self) -> u8 { + self as u8 + } +} + +// Method IDs for each service +/// Temp method IDs +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TempMethod { + AddSensor = 1, + RemoveSensor = 2, + SetInterval = 3, + SetAlarm = 4, + EnableAlarm = 5, + DisableAlarm = 6, + GetOneSensor = 7, + GetAllSensorsB = 8, + SaveSettings = 9, + LoadSettings = 10, + ReadOneSensor = 11, + ReadSensors = 12, +} + +impl TempMethod { + pub fn as_u8(self) -> u8 { + self as u8 + } +} + +/// TempAsync method IDs +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TempAsyncMethod { + AlarmFired = 1, + ReadResults = 2, +} + +impl TempAsyncMethod { + pub fn as_u8(self) -> u8 { + self as u8 + } +} + +pub type SensorAddress = u8; + +pub type SensorInfoList = Vec; + +pub type SavedState = Vec; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[repr(i32)] +pub enum AlarmType { + /// + kHighAlarm = 0, // kHighAlarm in eRPC + /// + kLowAlarm = 1, // kLowAlarm in eRPC + /// + kBothAlarms = 2, // kBothAlarms in eRPC +} + +impl Default for AlarmType { + fn default() -> Self { + AlarmType::kHighAlarm + } +} + +impl AlarmType { + pub fn write(&self, codec: &mut dyn Codec) -> ErpcResult<()> { + codec.write_int32(*self as i32)?; + Ok(()) + } + + pub fn read(codec: &mut dyn Codec) -> ErpcResult { + let value = codec.read_int32()?; + match value { + 0 => Ok(Self::kHighAlarm), + 1 => Ok(Self::kLowAlarm), + 2 => Ok(Self::kBothAlarms), + _ => Err(SerializationError::InvalidEnumValue { + value: value as i32, + type_name: "AlarmType".to_string(), + } + .into()), + } + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct AlarmInfo { + /// + pub temp: f32, // float temp + /// + pub enabled: bool, // bool enabled +} + +impl Default for AlarmInfo { + fn default() -> Self { + Self { + temp: 0.0, + enabled: false, + } + } +} + +impl AlarmInfo { + pub fn write(&self, codec: &mut dyn Codec) -> ErpcResult<()> { + codec.write_float(self.temp)?; + codec.write_bool(self.enabled)?; + Ok(()) + } + + pub fn read(codec: &mut dyn Codec) -> ErpcResult { + Ok(Self { + temp: codec.read_float()?, + enabled: codec.read_bool()?, + }) + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct SensorInfo { + /// + pub address: u8, // SensorAddress address + /// + pub read_interval: f32, // float readInterval + /// + pub high_alarm: AlarmInfo, // AlarmInfo highAlarm + /// + pub low_alarm: AlarmInfo, // AlarmInfo lowAlarm +} + +impl Default for SensorInfo { + fn default() -> Self { + Self { + address: 0, + read_interval: 0.0, + high_alarm: AlarmInfo::default(), + low_alarm: AlarmInfo::default(), + } + } +} + +impl SensorInfo { + pub fn write(&self, codec: &mut dyn Codec) -> ErpcResult<()> { + codec.write_uint8(self.address)?; + codec.write_float(self.read_interval)?; + self.high_alarm.write(codec)?; + self.low_alarm.write(codec)?; + Ok(()) + } + + pub fn read(codec: &mut dyn Codec) -> ErpcResult { + Ok(Self { + address: codec.read_uint8()?, + read_interval: codec.read_float()?, + high_alarm: AlarmInfo::read(codec)?, + low_alarm: AlarmInfo::read(codec)?, + }) + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct SensorReadResult { + /// + pub address: u8, // SensorAddress address + /// + pub temp: f32, // float temp +} + +impl Default for SensorReadResult { + fn default() -> Self { + Self { + address: 0, + temp: 0.0, + } + } +} + +impl SensorReadResult { + pub fn write(&self, codec: &mut dyn Codec) -> ErpcResult<()> { + codec.write_uint8(self.address)?; + codec.write_float(self.temp)?; + Ok(()) + } + + pub fn read(codec: &mut dyn Codec) -> ErpcResult { + Ok(Self { + address: codec.read_uint8()?, + temp: codec.read_float()?, + }) + } +} + +/// ======================================================================= +/// Temp +/// ======================================================================= + +pub mod temp_server { + use super::*; + use std::sync::Arc; + + /// Generated trait containing eRPC methods that should be implemented for use with TempServer. + #[async_trait] + pub trait Temp: Send + Sync + 'static { + /// []>]> + async fn add_sensor( + &self, + address: u8, + ) -> Result>; + /// []>]> + async fn remove_sensor( + &self, + address: u8, + ) -> Result>; + /// [, 1:]>]> + async fn set_interval( + &self, + address: u8, + interval: f32, + ) -> Result>; + /// [, 1:, 2:]>]> + async fn set_alarm( + &self, + address: u8, + alarm_type: AlarmType, + alarm_temp: f32, + ) -> Result>; + /// [, 1:]>]> + async fn enable_alarm( + &self, + address: u8, + alarm_type: AlarmType, + ) -> Result>; + /// [, 1:]>]> + async fn disable_alarm( + &self, + address: u8, + alarm_type: AlarmType, + ) -> Result>; + /// []>]> + async fn get_one_sensor( + &self, + address: u8, + ) -> Result>; + /// []> + async fn get_all_sensors_b( + &self, + ) -> Result, Box>; + /// []> + async fn save_settings(&self) -> Result, Box>; + /// []>]> + async fn load_settings( + &self, + saved_state: Vec, + ) -> Result>; + /// []>]> + async fn read_one_sensor( + &self, + address: u8, + ) -> Result>; + /// [, 1:]>]> + async fn read_sensors( + &self, + addresses: Vec, + ) -> Result>; + } + + /// Server wrapper for Temp service + pub struct TempServer + where + T: Temp, + { + inner: Arc, + base_service: BaseService, + } + + impl TempServer + where + T: Temp, + { + /// Create a new TempServer with service implementation + pub fn new(inner: T) -> Self { + let inner_arc = Arc::new(inner); + let base_service = Self::create_base_service(&inner_arc); + Self { + inner: inner_arc, + base_service, + } + } + + /// Get a reference to the inner service implementation + pub fn get_ref(&self) -> &T { + &self.inner + } + + /// Get the inner service implementation + pub fn into_inner(self) -> Arc { + self.inner + } + + fn create_base_service(service: &Arc) -> BaseService { + let mut base_service = BaseService::new(ServiceId::Temp.as_u8()); + + // Register all method handlers + Self::register_add_sensor(&mut base_service, Arc::clone(service)); + Self::register_remove_sensor(&mut base_service, Arc::clone(service)); + Self::register_set_interval(&mut base_service, Arc::clone(service)); + Self::register_set_alarm(&mut base_service, Arc::clone(service)); + Self::register_enable_alarm(&mut base_service, Arc::clone(service)); + Self::register_disable_alarm(&mut base_service, Arc::clone(service)); + Self::register_get_one_sensor(&mut base_service, Arc::clone(service)); + Self::register_get_all_sensors_b(&mut base_service, Arc::clone(service)); + Self::register_save_settings(&mut base_service, Arc::clone(service)); + Self::register_load_settings(&mut base_service, Arc::clone(service)); + Self::register_read_one_sensor(&mut base_service, Arc::clone(service)); + Self::register_read_sensors(&mut base_service, Arc::clone(service)); + + base_service + } + + fn register_add_sensor(base_service: &mut BaseService, service: Arc) { + struct AddSensorHandler { + service: Arc, + } + + #[async_trait] + impl MethodHandler for AddSensorHandler { + async fn handle(&self, sequence: u32, codec: &mut dyn Codec) -> ErpcResult<()> { + // Deserialize address parameter + let address = codec.read_uint8()?; + + let result = self.service.add_sensor(address).await; + match result { + Ok(response) => { + let reply_info = MessageInfo::new( + MessageType::Reply, + ServiceId::Temp.as_u8(), + TempMethod::AddSensor.as_u8(), + sequence, + ); + codec.start_write_message(&reply_info)?; + // Serialize response + codec.write_bool(response)?; // Return value + Ok(()) + } + Err(_e) => { + let reply_info = MessageInfo::new( + MessageType::Reply, + ServiceId::Temp.as_u8(), + TempMethod::AddSensor.as_u8(), + sequence, + ); + codec.start_write_message(&reply_info)?; + // Send error response with default out parameters + codec.write_bool(bool::default())?; // Error return value + Ok(()) + } + } + } + } + + base_service.add_method(TempMethod::AddSensor.as_u8(), AddSensorHandler { service }); + } + + fn register_remove_sensor(base_service: &mut BaseService, service: Arc) { + struct RemoveSensorHandler { + service: Arc, + } + + #[async_trait] + impl MethodHandler for RemoveSensorHandler { + async fn handle(&self, sequence: u32, codec: &mut dyn Codec) -> ErpcResult<()> { + // Deserialize address parameter + let address = codec.read_uint8()?; + + let result = self.service.remove_sensor(address).await; + match result { + Ok(response) => { + let reply_info = MessageInfo::new( + MessageType::Reply, + ServiceId::Temp.as_u8(), + TempMethod::RemoveSensor.as_u8(), + sequence, + ); + codec.start_write_message(&reply_info)?; + // Serialize response + codec.write_bool(response)?; // Return value + Ok(()) + } + Err(_e) => { + let reply_info = MessageInfo::new( + MessageType::Reply, + ServiceId::Temp.as_u8(), + TempMethod::RemoveSensor.as_u8(), + sequence, + ); + codec.start_write_message(&reply_info)?; + // Send error response with default out parameters + codec.write_bool(bool::default())?; // Error return value + Ok(()) + } + } + } + } + + base_service.add_method( + TempMethod::RemoveSensor.as_u8(), + RemoveSensorHandler { service }, + ); + } + + fn register_set_interval(base_service: &mut BaseService, service: Arc) { + struct SetIntervalHandler { + service: Arc, + } + + #[async_trait] + impl MethodHandler for SetIntervalHandler { + async fn handle(&self, sequence: u32, codec: &mut dyn Codec) -> ErpcResult<()> { + // Deserialize address parameter + let address = codec.read_uint8()?; + + // Deserialize interval parameter + let interval = codec.read_float()?; + + let result = self.service.set_interval(address, interval).await; + match result { + Ok(response) => { + let reply_info = MessageInfo::new( + MessageType::Reply, + ServiceId::Temp.as_u8(), + TempMethod::SetInterval.as_u8(), + sequence, + ); + codec.start_write_message(&reply_info)?; + // Serialize response + codec.write_bool(response)?; // Return value + Ok(()) + } + Err(_e) => { + let reply_info = MessageInfo::new( + MessageType::Reply, + ServiceId::Temp.as_u8(), + TempMethod::SetInterval.as_u8(), + sequence, + ); + codec.start_write_message(&reply_info)?; + // Send error response with default out parameters + codec.write_bool(bool::default())?; // Error return value + Ok(()) + } + } + } + } + + base_service.add_method( + TempMethod::SetInterval.as_u8(), + SetIntervalHandler { service }, + ); + } + + fn register_set_alarm(base_service: &mut BaseService, service: Arc) { + struct SetAlarmHandler { + service: Arc, + } + + #[async_trait] + impl MethodHandler for SetAlarmHandler { + async fn handle(&self, sequence: u32, codec: &mut dyn Codec) -> ErpcResult<()> { + // Deserialize address parameter + let address = codec.read_uint8()?; + + // Deserialize alarm_type parameter + let alarm_type = AlarmType::read(codec)?; + + // Deserialize alarm_temp parameter + let alarm_temp = codec.read_float()?; + + let result = self + .service + .set_alarm(address, alarm_type, alarm_temp) + .await; + match result { + Ok(response) => { + let reply_info = MessageInfo::new( + MessageType::Reply, + ServiceId::Temp.as_u8(), + TempMethod::SetAlarm.as_u8(), + sequence, + ); + codec.start_write_message(&reply_info)?; + // Serialize response + codec.write_bool(response)?; // Return value + Ok(()) + } + Err(_e) => { + let reply_info = MessageInfo::new( + MessageType::Reply, + ServiceId::Temp.as_u8(), + TempMethod::SetAlarm.as_u8(), + sequence, + ); + codec.start_write_message(&reply_info)?; + // Send error response with default out parameters + codec.write_bool(bool::default())?; // Error return value + Ok(()) + } + } + } + } + + base_service.add_method(TempMethod::SetAlarm.as_u8(), SetAlarmHandler { service }); + } + + fn register_enable_alarm(base_service: &mut BaseService, service: Arc) { + struct EnableAlarmHandler { + service: Arc, + } + + #[async_trait] + impl MethodHandler for EnableAlarmHandler { + async fn handle(&self, sequence: u32, codec: &mut dyn Codec) -> ErpcResult<()> { + // Deserialize address parameter + let address = codec.read_uint8()?; + + // Deserialize alarm_type parameter + let alarm_type = AlarmType::read(codec)?; + + let result = self.service.enable_alarm(address, alarm_type).await; + match result { + Ok(response) => { + let reply_info = MessageInfo::new( + MessageType::Reply, + ServiceId::Temp.as_u8(), + TempMethod::EnableAlarm.as_u8(), + sequence, + ); + codec.start_write_message(&reply_info)?; + // Serialize response + codec.write_bool(response)?; // Return value + Ok(()) + } + Err(_e) => { + let reply_info = MessageInfo::new( + MessageType::Reply, + ServiceId::Temp.as_u8(), + TempMethod::EnableAlarm.as_u8(), + sequence, + ); + codec.start_write_message(&reply_info)?; + // Send error response with default out parameters + codec.write_bool(bool::default())?; // Error return value + Ok(()) + } + } + } + } + + base_service.add_method( + TempMethod::EnableAlarm.as_u8(), + EnableAlarmHandler { service }, + ); + } + + fn register_disable_alarm(base_service: &mut BaseService, service: Arc) { + struct DisableAlarmHandler { + service: Arc, + } + + #[async_trait] + impl MethodHandler for DisableAlarmHandler { + async fn handle(&self, sequence: u32, codec: &mut dyn Codec) -> ErpcResult<()> { + // Deserialize address parameter + let address = codec.read_uint8()?; + + // Deserialize alarm_type parameter + let alarm_type = AlarmType::read(codec)?; + + let result = self.service.disable_alarm(address, alarm_type).await; + match result { + Ok(response) => { + let reply_info = MessageInfo::new( + MessageType::Reply, + ServiceId::Temp.as_u8(), + TempMethod::DisableAlarm.as_u8(), + sequence, + ); + codec.start_write_message(&reply_info)?; + // Serialize response + codec.write_bool(response)?; // Return value + Ok(()) + } + Err(_e) => { + let reply_info = MessageInfo::new( + MessageType::Reply, + ServiceId::Temp.as_u8(), + TempMethod::DisableAlarm.as_u8(), + sequence, + ); + codec.start_write_message(&reply_info)?; + // Send error response with default out parameters + codec.write_bool(bool::default())?; // Error return value + Ok(()) + } + } + } + } + + base_service.add_method( + TempMethod::DisableAlarm.as_u8(), + DisableAlarmHandler { service }, + ); + } + + fn register_get_one_sensor(base_service: &mut BaseService, service: Arc) { + struct GetOneSensorHandler { + service: Arc, + } + + #[async_trait] + impl MethodHandler for GetOneSensorHandler { + async fn handle(&self, sequence: u32, codec: &mut dyn Codec) -> ErpcResult<()> { + // Deserialize address parameter + let address = codec.read_uint8()?; + + let result = self.service.get_one_sensor(address).await; + match result { + Ok(response) => { + let reply_info = MessageInfo::new( + MessageType::Reply, + ServiceId::Temp.as_u8(), + TempMethod::GetOneSensor.as_u8(), + sequence, + ); + codec.start_write_message(&reply_info)?; + // Serialize response + response.write(codec)?; // Return value + Ok(()) + } + Err(_e) => { + let reply_info = MessageInfo::new( + MessageType::Reply, + ServiceId::Temp.as_u8(), + TempMethod::GetOneSensor.as_u8(), + sequence, + ); + codec.start_write_message(&reply_info)?; + // Send error response with default out parameters + SensorInfo::default().write(codec)?; // Error return value + Ok(()) + } + } + } + } + + base_service.add_method( + TempMethod::GetOneSensor.as_u8(), + GetOneSensorHandler { service }, + ); + } + + fn register_get_all_sensors_b(base_service: &mut BaseService, service: Arc) { + struct GetAllSensorsBHandler { + service: Arc, + } + + #[async_trait] + impl MethodHandler for GetAllSensorsBHandler { + async fn handle(&self, sequence: u32, codec: &mut dyn Codec) -> ErpcResult<()> { + let result = self.service.get_all_sensors_b().await; + match result { + Ok(response) => { + let reply_info = MessageInfo::new( + MessageType::Reply, + ServiceId::Temp.as_u8(), + TempMethod::GetAllSensorsB.as_u8(), + sequence, + ); + codec.start_write_message(&reply_info)?; + // Serialize response + codec.start_write_list(response.len() as u32)?; + for item in &response { + item.write(codec)?; + } // Return value + Ok(()) + } + Err(_e) => { + let reply_info = MessageInfo::new( + MessageType::Reply, + ServiceId::Temp.as_u8(), + TempMethod::GetAllSensorsB.as_u8(), + sequence, + ); + codec.start_write_message(&reply_info)?; + // Send error response with default out parameters + codec.start_write_list(0)?; // Error return value + Ok(()) + } + } + } + } + + base_service.add_method( + TempMethod::GetAllSensorsB.as_u8(), + GetAllSensorsBHandler { service }, + ); + } + + fn register_save_settings(base_service: &mut BaseService, service: Arc) { + struct SaveSettingsHandler { + service: Arc, + } + + #[async_trait] + impl MethodHandler for SaveSettingsHandler { + async fn handle(&self, sequence: u32, codec: &mut dyn Codec) -> ErpcResult<()> { + let result = self.service.save_settings().await; + match result { + Ok(response) => { + let reply_info = MessageInfo::new( + MessageType::Reply, + ServiceId::Temp.as_u8(), + TempMethod::SaveSettings.as_u8(), + sequence, + ); + codec.start_write_message(&reply_info)?; + // Serialize response + codec.write_binary(&response)?; // Return value + Ok(()) + } + Err(_e) => { + let reply_info = MessageInfo::new( + MessageType::Reply, + ServiceId::Temp.as_u8(), + TempMethod::SaveSettings.as_u8(), + sequence, + ); + codec.start_write_message(&reply_info)?; + // Send error response with default out parameters + codec.write_binary(&Vec::::default())?; // Error return value + Ok(()) + } + } + } + } + + base_service.add_method( + TempMethod::SaveSettings.as_u8(), + SaveSettingsHandler { service }, + ); + } + + fn register_load_settings(base_service: &mut BaseService, service: Arc) { + struct LoadSettingsHandler { + service: Arc, + } + + #[async_trait] + impl MethodHandler for LoadSettingsHandler { + async fn handle(&self, sequence: u32, codec: &mut dyn Codec) -> ErpcResult<()> { + // Deserialize saved_state parameter + let saved_state = codec.read_binary()?; + + let result = self.service.load_settings(saved_state).await; + match result { + Ok(response) => { + let reply_info = MessageInfo::new( + MessageType::Reply, + ServiceId::Temp.as_u8(), + TempMethod::LoadSettings.as_u8(), + sequence, + ); + codec.start_write_message(&reply_info)?; + // Serialize response + codec.write_bool(response)?; // Return value + Ok(()) + } + Err(_e) => { + let reply_info = MessageInfo::new( + MessageType::Reply, + ServiceId::Temp.as_u8(), + TempMethod::LoadSettings.as_u8(), + sequence, + ); + codec.start_write_message(&reply_info)?; + // Send error response with default out parameters + codec.write_bool(bool::default())?; // Error return value + Ok(()) + } + } + } + } + + base_service.add_method( + TempMethod::LoadSettings.as_u8(), + LoadSettingsHandler { service }, + ); + } + + fn register_read_one_sensor(base_service: &mut BaseService, service: Arc) { + struct ReadOneSensorHandler { + service: Arc, + } + + #[async_trait] + impl MethodHandler for ReadOneSensorHandler { + async fn handle(&self, sequence: u32, codec: &mut dyn Codec) -> ErpcResult<()> { + // Deserialize address parameter + let address = codec.read_uint8()?; + + let result = self.service.read_one_sensor(address).await; + match result { + Ok(response) => { + let reply_info = MessageInfo::new( + MessageType::Reply, + ServiceId::Temp.as_u8(), + TempMethod::ReadOneSensor.as_u8(), + sequence, + ); + codec.start_write_message(&reply_info)?; + // Serialize response + codec.write_float(response)?; // Return value + Ok(()) + } + Err(_e) => { + let reply_info = MessageInfo::new( + MessageType::Reply, + ServiceId::Temp.as_u8(), + TempMethod::ReadOneSensor.as_u8(), + sequence, + ); + codec.start_write_message(&reply_info)?; + // Send error response with default out parameters + codec.write_float(f32::default())?; // Error return value + Ok(()) + } + } + } + } + + base_service.add_method( + TempMethod::ReadOneSensor.as_u8(), + ReadOneSensorHandler { service }, + ); + } + + fn register_read_sensors(base_service: &mut BaseService, service: Arc) { + struct ReadSensorsHandler { + service: Arc, + } + + #[async_trait] + impl MethodHandler for ReadSensorsHandler { + async fn handle(&self, sequence: u32, codec: &mut dyn Codec) -> ErpcResult<()> { + // Deserialize addresses parameter + let addresses = (|| -> ErpcResult> { + let list_len = codec.start_read_list()?; + let mut list = Vec::new(); + for _ in 0..list_len { + list.push(codec.read_uint8()?); + } + Ok(list) + })()?; + + let result = self.service.read_sensors(addresses).await; + match result { + Ok(response) => { + let reply_info = MessageInfo::new( + MessageType::Reply, + ServiceId::Temp.as_u8(), + TempMethod::ReadSensors.as_u8(), + sequence, + ); + codec.start_write_message(&reply_info)?; + // Serialize response + codec.write_bool(response)?; // Return value + Ok(()) + } + Err(_e) => { + let reply_info = MessageInfo::new( + MessageType::Reply, + ServiceId::Temp.as_u8(), + TempMethod::ReadSensors.as_u8(), + sequence, + ); + codec.start_write_message(&reply_info)?; + // Send error response with default out parameters + codec.write_bool(bool::default())?; // Error return value + Ok(()) + } + } + } + } + + base_service.add_method( + TempMethod::ReadSensors.as_u8(), + ReadSensorsHandler { service }, + ); + } + } + + /// Implementation of Service trait for TempServer + #[async_trait] + impl Service for TempServer + where + T: Temp, + { + fn service_id(&self) -> u8 { + self.base_service.service_id() + } + + async fn handle_invocation( + &self, + method_id: u8, + sequence: u32, + codec: &mut dyn Codec, + ) -> ErpcResult<()> { + self.base_service + .handle_invocation(method_id, sequence, codec) + .await + } + + fn supported_methods(&self) -> Vec { + self.base_service.supported_methods() + } + } + + /// Client implementation for Temp + pub struct TempClient<'a, T> + where + T: Transport, + { + client: &'a mut ClientManager, + } + + /// Temp client implementation + /// Matches eRPC IDL interface exactly with proper serialization + impl<'a, T> TempClient<'a, T> + where + T: Transport, + { + pub fn new(client_manager: &'a mut ClientManager) -> Self { + Self { + client: client_manager, + } + } + + /// []>]> + pub async fn add_sensor( + &mut self, + address: u8, + ) -> Result> { + // Serialize parameters to request_data + let mut request_codec = BasicCodec::new(); + + // Write address + request_codec.write_uint8(address)?; + + let request_data = request_codec.as_bytes().to_vec(); + + let response_data = self + .client + .perform_request( + ServiceId::Temp.as_u8(), + TempMethod::AddSensor.as_u8(), + false, + request_data, + ) + .await?; + // Deserialize response_data + if response_data.is_empty() { + return Err("Empty response data received".into()); + } + + let mut response_codec = BasicCodec::from_data(response_data); + + // Read return value + let result = (&mut response_codec).read_bool()?; + Ok(result) + } + + /// []>]> + pub async fn remove_sensor( + &mut self, + address: u8, + ) -> Result> { + // Serialize parameters to request_data + let mut request_codec = BasicCodec::new(); + + // Write address + request_codec.write_uint8(address)?; + + let request_data = request_codec.as_bytes().to_vec(); + + let response_data = self + .client + .perform_request( + ServiceId::Temp.as_u8(), + TempMethod::RemoveSensor.as_u8(), + false, + request_data, + ) + .await?; + // Deserialize response_data + if response_data.is_empty() { + return Err("Empty response data received".into()); + } + + let mut response_codec = BasicCodec::from_data(response_data); + + // Read return value + let result = (&mut response_codec).read_bool()?; + Ok(result) + } + + /// [, 1:]>]> + pub async fn set_interval( + &mut self, + address: u8, + interval: f32, + ) -> Result> { + // Serialize parameters to request_data + let mut request_codec = BasicCodec::new(); + + // Write address + request_codec.write_uint8(address)?; + // Write interval + request_codec.write_float(interval)?; + + let request_data = request_codec.as_bytes().to_vec(); + + let response_data = self + .client + .perform_request( + ServiceId::Temp.as_u8(), + TempMethod::SetInterval.as_u8(), + false, + request_data, + ) + .await?; + // Deserialize response_data + if response_data.is_empty() { + return Err("Empty response data received".into()); + } + + let mut response_codec = BasicCodec::from_data(response_data); + + // Read return value + let result = (&mut response_codec).read_bool()?; + Ok(result) + } + + /// [, 1:, 2:]>]> + pub async fn set_alarm( + &mut self, + address: u8, + alarm_type: AlarmType, + alarm_temp: f32, + ) -> Result> { + // Serialize parameters to request_data + let mut request_codec = BasicCodec::new(); + + // Write address + request_codec.write_uint8(address)?; + // Write alarm_type + alarm_type.write(&mut request_codec)?; + // Write alarm_temp + request_codec.write_float(alarm_temp)?; + + let request_data = request_codec.as_bytes().to_vec(); + + let response_data = self + .client + .perform_request( + ServiceId::Temp.as_u8(), + TempMethod::SetAlarm.as_u8(), + false, + request_data, + ) + .await?; + // Deserialize response_data + if response_data.is_empty() { + return Err("Empty response data received".into()); + } + + let mut response_codec = BasicCodec::from_data(response_data); + + // Read return value + let result = (&mut response_codec).read_bool()?; + Ok(result) + } + + /// [, 1:]>]> + pub async fn enable_alarm( + &mut self, + address: u8, + alarm_type: AlarmType, + ) -> Result> { + // Serialize parameters to request_data + let mut request_codec = BasicCodec::new(); + + // Write address + request_codec.write_uint8(address)?; + // Write alarm_type + alarm_type.write(&mut request_codec)?; + + let request_data = request_codec.as_bytes().to_vec(); + + let response_data = self + .client + .perform_request( + ServiceId::Temp.as_u8(), + TempMethod::EnableAlarm.as_u8(), + false, + request_data, + ) + .await?; + // Deserialize response_data + if response_data.is_empty() { + return Err("Empty response data received".into()); + } + + let mut response_codec = BasicCodec::from_data(response_data); + + // Read return value + let result = (&mut response_codec).read_bool()?; + Ok(result) + } + + /// [, 1:]>]> + pub async fn disable_alarm( + &mut self, + address: u8, + alarm_type: AlarmType, + ) -> Result> { + // Serialize parameters to request_data + let mut request_codec = BasicCodec::new(); + + // Write address + request_codec.write_uint8(address)?; + // Write alarm_type + alarm_type.write(&mut request_codec)?; + + let request_data = request_codec.as_bytes().to_vec(); + + let response_data = self + .client + .perform_request( + ServiceId::Temp.as_u8(), + TempMethod::DisableAlarm.as_u8(), + false, + request_data, + ) + .await?; + // Deserialize response_data + if response_data.is_empty() { + return Err("Empty response data received".into()); + } + + let mut response_codec = BasicCodec::from_data(response_data); + + // Read return value + let result = (&mut response_codec).read_bool()?; + Ok(result) + } + + /// []>]> + pub async fn get_one_sensor( + &mut self, + address: u8, + ) -> Result> { + // Serialize parameters to request_data + let mut request_codec = BasicCodec::new(); + + // Write address + request_codec.write_uint8(address)?; + + let request_data = request_codec.as_bytes().to_vec(); + + let response_data = self + .client + .perform_request( + ServiceId::Temp.as_u8(), + TempMethod::GetOneSensor.as_u8(), + false, + request_data, + ) + .await?; + // Deserialize response_data + if response_data.is_empty() { + return Err("Empty response data received".into()); + } + + let mut response_codec = BasicCodec::from_data(response_data); + + // Read return value + let result = SensorInfo::read((&mut response_codec))?; + Ok(result) + } + + /// []> + pub async fn get_all_sensors_b( + &mut self, + ) -> Result, Box> { + // Serialize parameters to request_data + let request_codec = BasicCodec::new(); + + // No parameters to serialize + + let request_data = request_codec.as_bytes().to_vec(); + + let response_data = self + .client + .perform_request( + ServiceId::Temp.as_u8(), + TempMethod::GetAllSensorsB.as_u8(), + false, + request_data, + ) + .await?; + // Deserialize response_data + if response_data.is_empty() { + return Err("Empty response data received".into()); + } + + let mut response_codec = BasicCodec::from_data(response_data); + + // Read return value + let result = (|| -> ErpcResult> { + let list_len = (&mut response_codec).start_read_list()?; + let mut list = Vec::new(); + for _ in 0..list_len { + list.push(SensorInfo::read((&mut response_codec))?); + } + Ok(list) + })()?; + Ok(result) + } + + /// []> + pub async fn save_settings( + &mut self, + ) -> Result, Box> { + // Serialize parameters to request_data + let request_codec = BasicCodec::new(); + + // No parameters to serialize + + let request_data = request_codec.as_bytes().to_vec(); + + let response_data = self + .client + .perform_request( + ServiceId::Temp.as_u8(), + TempMethod::SaveSettings.as_u8(), + false, + request_data, + ) + .await?; + // Deserialize response_data + if response_data.is_empty() { + return Err("Empty response data received".into()); + } + + let mut response_codec = BasicCodec::from_data(response_data); + + // Read return value + let result = (&mut response_codec).read_binary()?; + Ok(result) + } + + /// []>]> + pub async fn load_settings( + &mut self, + saved_state: Vec, + ) -> Result> { + // Serialize parameters to request_data + let mut request_codec = BasicCodec::new(); + + // Write saved_state + request_codec.write_binary(&saved_state)?; + + let request_data = request_codec.as_bytes().to_vec(); + + let response_data = self + .client + .perform_request( + ServiceId::Temp.as_u8(), + TempMethod::LoadSettings.as_u8(), + false, + request_data, + ) + .await?; + // Deserialize response_data + if response_data.is_empty() { + return Err("Empty response data received".into()); + } + + let mut response_codec = BasicCodec::from_data(response_data); + + // Read return value + let result = (&mut response_codec).read_bool()?; + Ok(result) + } + + /// []>]> + pub async fn read_one_sensor( + &mut self, + address: u8, + ) -> Result> { + // Serialize parameters to request_data + let mut request_codec = BasicCodec::new(); + + // Write address + request_codec.write_uint8(address)?; + + let request_data = request_codec.as_bytes().to_vec(); + + let response_data = self + .client + .perform_request( + ServiceId::Temp.as_u8(), + TempMethod::ReadOneSensor.as_u8(), + false, + request_data, + ) + .await?; + // Deserialize response_data + if response_data.is_empty() { + return Err("Empty response data received".into()); + } + + let mut response_codec = BasicCodec::from_data(response_data); + + // Read return value + let result = (&mut response_codec).read_float()?; + Ok(result) + } + + /// [, 1:]>]> + pub async fn read_sensors( + &mut self, + addresses: Vec, + ) -> Result> { + // Serialize parameters to request_data + let mut request_codec = BasicCodec::new(); + + // Write addresses + request_codec.start_write_list(addresses.len() as u32)?; + for item in &addresses { + request_codec.write_uint8(*item)?; + } + + let request_data = request_codec.as_bytes().to_vec(); + + let response_data = self + .client + .perform_request( + ServiceId::Temp.as_u8(), + TempMethod::ReadSensors.as_u8(), + false, + request_data, + ) + .await?; + // Deserialize response_data + if response_data.is_empty() { + return Err("Empty response data received".into()); + } + + let mut response_codec = BasicCodec::from_data(response_data); + + // Read return value + let result = (&mut response_codec).read_bool()?; + Ok(result) + } + } +} + +/// ======================================================================= +/// TempAsync +/// ======================================================================= + +pub mod temp_async_server { + use super::*; + use std::sync::Arc; + + /// Generated trait containing eRPC methods that should be implemented for use with TempAsyncServer. + #[async_trait] + pub trait TempAsync: Send + Sync + 'static { + /// [, 1:]>]> + async fn alarm_fired( + &self, + addr: u8, + temp: f32, + ) -> Result<(), Box>; + /// [, 1:]>]> + async fn read_results( + &self, + results: Vec, + ) -> Result<(), Box>; + } + + /// Server wrapper for TempAsync service + pub struct TempAsyncServer + where + T: TempAsync, + { + inner: Arc, + base_service: BaseService, + } + + impl TempAsyncServer + where + T: TempAsync, + { + /// Create a new TempAsyncServer with service implementation + pub fn new(inner: T) -> Self { + let inner_arc = Arc::new(inner); + let base_service = Self::create_base_service(&inner_arc); + Self { + inner: inner_arc, + base_service, + } + } + + /// Get a reference to the inner service implementation + pub fn get_ref(&self) -> &T { + &self.inner + } + + /// Get the inner service implementation + pub fn into_inner(self) -> Arc { + self.inner + } + + fn create_base_service(service: &Arc) -> BaseService { + let mut base_service = BaseService::new(ServiceId::TempAsync.as_u8()); + + // Register all method handlers + Self::register_alarm_fired(&mut base_service, Arc::clone(service)); + Self::register_read_results(&mut base_service, Arc::clone(service)); + + base_service + } + + fn register_alarm_fired(base_service: &mut BaseService, service: Arc) { + struct AlarmFiredHandler { + service: Arc, + } + + #[async_trait] + impl MethodHandler for AlarmFiredHandler { + async fn handle(&self, sequence: u32, codec: &mut dyn Codec) -> ErpcResult<()> { + // Deserialize addr parameter + let addr = codec.read_uint8()?; + + // Deserialize temp parameter + let temp = codec.read_float()?; + + let result = self.service.alarm_fired(addr, temp).await; + match result { + Ok(_) => { + let reply_info = MessageInfo::new( + MessageType::Reply, + ServiceId::TempAsync.as_u8(), + TempAsyncMethod::AlarmFired.as_u8(), + sequence, + ); + codec.start_write_message(&reply_info)?; + // Serialize response + Ok(()) + } + Err(_e) => { + let reply_info = MessageInfo::new( + MessageType::Reply, + ServiceId::TempAsync.as_u8(), + TempAsyncMethod::AlarmFired.as_u8(), + sequence, + ); + codec.start_write_message(&reply_info)?; + // Send error response with default out parameters + Ok(()) + } + } + } + } + + base_service.add_method( + TempAsyncMethod::AlarmFired.as_u8(), + AlarmFiredHandler { service }, + ); + } + + fn register_read_results(base_service: &mut BaseService, service: Arc) { + struct ReadResultsHandler { + service: Arc, + } + + #[async_trait] + impl MethodHandler for ReadResultsHandler { + async fn handle(&self, sequence: u32, codec: &mut dyn Codec) -> ErpcResult<()> { + // Deserialize results parameter + let results = (|| -> ErpcResult> { + let list_len = codec.start_read_list()?; + let mut list = Vec::new(); + for _ in 0..list_len { + list.push(SensorReadResult::read(codec)?); + } + Ok(list) + })()?; + + let _ = self.service.read_results(results).await; + Ok(()) + } + } + + base_service.add_method( + TempAsyncMethod::ReadResults.as_u8(), + ReadResultsHandler { service }, + ); + } + } + + /// Implementation of Service trait for TempAsyncServer + #[async_trait] + impl Service for TempAsyncServer + where + T: TempAsync, + { + fn service_id(&self) -> u8 { + self.base_service.service_id() + } + + async fn handle_invocation( + &self, + method_id: u8, + sequence: u32, + codec: &mut dyn Codec, + ) -> ErpcResult<()> { + self.base_service + .handle_invocation(method_id, sequence, codec) + .await + } + + fn supported_methods(&self) -> Vec { + self.base_service.supported_methods() + } + } + + /// Client implementation for TempAsync + pub struct TempAsyncClient<'a, T> + where + T: Transport, + { + client: &'a mut ClientManager, + } + + /// TempAsync client implementation + /// Matches eRPC IDL interface exactly with proper serialization + impl<'a, T> TempAsyncClient<'a, T> + where + T: Transport, + { + pub fn new(client_manager: &'a mut ClientManager) -> Self { + Self { + client: client_manager, + } + } + + /// [, 1:]>]> + pub async fn alarm_fired( + &mut self, + addr: u8, + temp: f32, + ) -> Result<(), Box> { + // Serialize parameters to request_data + let mut request_codec = BasicCodec::new(); + + // Write addr + request_codec.write_uint8(addr)?; + // Write temp + request_codec.write_float(temp)?; + + let request_data = request_codec.as_bytes().to_vec(); + + let response_data = self + .client + .perform_request( + ServiceId::TempAsync.as_u8(), + TempAsyncMethod::AlarmFired.as_u8(), + false, + request_data, + ) + .await?; + Ok(()) + } + + /// [, 1:]>]> + pub async fn read_results( + &mut self, + results: Vec, + ) -> Result<(), Box> { + // Serialize parameters to request_data + let mut request_codec = BasicCodec::new(); + + // Write results + request_codec.start_write_list(results.len() as u32)?; + for item in &results { + item.write(&mut request_codec)?; + } + + let request_data = request_codec.as_bytes().to_vec(); + + let _ = self + .client + .perform_request( + ServiceId::TempAsync.as_u8(), + TempAsyncMethod::ReadResults.as_u8(), + true, + request_data, + ) + .await?; + Ok(()) + } + } +}