diff --git a/src/lib_ccx/networking.c b/src/lib_ccx/networking.c index 4cafdb7fd..2ecd25e0e 100644 --- a/src/lib_ccx/networking.c +++ b/src/lib_ccx/networking.c @@ -45,6 +45,24 @@ const char *srv_pwd; unsigned char *srv_header; size_t srv_header_len; +#ifndef DISABLE_RUST +extern void ccxr_connect_to_srv(const char *addr, const char *port, const char *cc_desc, const char *pwd); +extern void ccxr_net_send_header(const unsigned char *data, size_t len); +extern int ccxr_net_send_cc(const unsigned char *data, int length, void *private_data, struct cc_subtitle *sub); +extern void ccxr_net_check_conn(); +extern void ccxr_net_send_epg( + const char *start, + const char *stop, + const char *title, + const char *desc, + const char *lang, + const char *category); +extern int ccxr_net_tcp_read(int socket, void *buffer, size_t length); +extern int ccxr_net_udp_read(int socket, void *buffer, size_t length, const char *src_str, const char *addr_str); +extern int ccxr_start_tcp_srv(const char *port, const char *pwd); +extern int ccxr_start_udp_srv(const char *src, const char *addr, unsigned port); +#endif + /* * Established connection to specified address. * Returns socked id @@ -84,6 +102,9 @@ int set_nonblocking(int fd); void connect_to_srv(const char *addr, const char *port, const char *cc_desc, const char *pwd) { +#ifndef DISABLE_RUST + return ccxr_connect_to_srv(addr, port, cc_desc, pwd); +#endif if (NULL == addr) { mprint("Server address is not set\n"); @@ -115,6 +136,9 @@ void connect_to_srv(const char *addr, const char *port, const char *cc_desc, con void net_send_header(const unsigned char *data, size_t len) { +#ifndef DISABLE_RUST + return ccxr_net_send_header(data, len); +#endif assert(srv_sd > 0); #if DEBUG_OUT @@ -141,6 +165,9 @@ void net_send_header(const unsigned char *data, size_t len) int net_send_cc(const unsigned char *data, int len, void *private_data, struct cc_subtitle *sub) { +#ifndef DISABLE_RUST + return ccxr_net_send_cc(data, len, private_data, sub); +#endif assert(srv_sd > 0); #if DEBUG_OUT @@ -160,6 +187,9 @@ int net_send_cc(const unsigned char *data, int len, void *private_data, struct c void net_check_conn() { +#ifndef DISABLE_RUST + return ccxr_net_check_conn(); +#endif time_t now; static time_t last_ping = 0; char c = 0; @@ -221,6 +251,9 @@ void net_send_epg( const char *lang, const char *category) { +#ifndef DISABLE_RUST + return ccxr_net_send_epg(start, stop, title, desc, lang, category); +#endif size_t st; size_t sp; size_t t; @@ -301,6 +334,9 @@ void net_send_epg( int net_tcp_read(int socket, void *buffer, size_t length) { +#ifndef DISABLE_RUST + return ccxr_net_tcp_read(socket, buffer, length); +#endif assert(buffer != NULL); assert(length > 0); @@ -333,6 +369,9 @@ int net_tcp_read(int socket, void *buffer, size_t length) int net_udp_read(int socket, void *buffer, size_t length, const char *src_str, const char *addr_str) { +#ifndef DISABLE_RUST + return ccxr_net_udp_read(socket, buffer, length, src_str, addr_str); +#endif assert(buffer != NULL); assert(length > 0); @@ -519,6 +558,9 @@ int tcp_connect(const char *host, const char *port) int start_tcp_srv(const char *port, const char *pwd) { +#ifndef DISABLE_RUST + return ccxr_start_tcp_srv(port, pwd); +#endif if (NULL == port) port = DFT_PORT; @@ -974,6 +1016,10 @@ ssize_t read_byte(int fd, char *ch) int start_upd_srv(const char *src_str, const char *addr_str, unsigned port) { +#ifndef DISABLE_RUST + return ccxr_start_udp_srv(src_str, addr_str, port); +#endif + init_sockets(); in_addr_t src; diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index 4f96e52d6..506b46540 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.14" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", @@ -28,36 +28,36 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.3" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -127,7 +127,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.66", + "syn 2.0.76", "which", ] @@ -145,9 +145,9 @@ checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "camino" -version = "1.1.7" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" [[package]] name = "ccx_rust" @@ -189,9 +189,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clang-sys" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", @@ -200,9 +200,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.4" +version = "4.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" dependencies = [ "clap_builder", "clap_derive", @@ -210,9 +210,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.2" +version = "4.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" dependencies = [ "anstream", "anstyle", @@ -222,33 +222,36 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.4" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.76", ] [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "colorchoice" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "convert_case" -version = "0.4.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] [[package]] name = "deranged" @@ -261,15 +264,24 @@ dependencies = [ [[package]] name = "derive_more" -version = "0.99.18" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "convert_case", "proc-macro2", "quote", - "rustc_version", - "syn 2.0.66", + "syn 2.0.76", + "unicode-xid", ] [[package]] @@ -280,9 +292,9 @@ checksum = "74c57ab96715773d9cb9789b38eb7cbf04b3c6f5624a9d98f51761603376767c" [[package]] name = "either" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "env_logger" @@ -297,6 +309,12 @@ dependencies = [ "termcolor", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.3.9" @@ -304,7 +322,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -331,6 +349,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "heck" version = "0.4.1" @@ -358,7 +382,7 @@ version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -387,11 +411,21 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "indexmap" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "is_terminal_polyfill" -version = "1.70.0" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" @@ -437,6 +471,8 @@ version = "0.1.0" dependencies = [ "bitflags 2.6.0", "derive_more", + "num_enum", + "socket2", "strum 0.26.3", "strum_macros 0.26.4", "thiserror", @@ -446,15 +482,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.155" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "libloading" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", "windows-targets", @@ -468,15 +504,15 @@ checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "minimal-lexical" @@ -518,6 +554,27 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_enum" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.76", +] + [[package]] name = "once_cell" version = "1.19.0" @@ -596,7 +653,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.76", ] [[package]] @@ -622,28 +679,37 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "prettyplease" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" +checksum = "a909e6e8053fa1a5ad670f5816c7d93029ee1fa8898718490544a6b0d5d38b3e" dependencies = [ "proc-macro2", - "syn 2.0.66", + "syn 2.0.76", +] + +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit", ] [[package]] name = "proc-macro2" -version = "1.0.83" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -665,9 +731,9 @@ checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" [[package]] name = "regex" -version = "1.10.4" +version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", @@ -677,9 +743,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", @@ -688,9 +754,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "rsmpeg" @@ -710,15 +776,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver", -] - [[package]] name = "rustix" version = "0.38.34" @@ -729,7 +786,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -752,30 +809,24 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "semver" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" - [[package]] name = "serde" -version = "1.0.202" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" +checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.202" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" +checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.76", ] [[package]] @@ -790,6 +841,16 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "strsim" version = "0.11.1" @@ -818,7 +879,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.66", + "syn 2.0.76", ] [[package]] @@ -831,7 +892,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.66", + "syn 2.0.76", ] [[package]] @@ -847,9 +908,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.66" +version = "2.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" dependencies = [ "proc-macro2", "quote", @@ -879,22 +940,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.76", ] [[package]] @@ -952,6 +1013,23 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" + +[[package]] +name = "toml_edit" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + [[package]] name = "unicode-bidi" version = "0.3.15" @@ -973,6 +1051,18 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unicode-xid" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" + [[package]] name = "url" version = "2.5.2" @@ -986,9 +1076,9 @@ dependencies = [ [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "vcpkg" @@ -1030,7 +1120,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -1048,11 +1138,20 @@ dependencies = [ "windows-targets", ] +[[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.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -1066,48 +1165,57 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] diff --git a/src/rust/lib_ccxr/Cargo.lock b/src/rust/lib_ccxr/Cargo.lock index 4fd82ee25..c8f067a5a 100644 --- a/src/rust/lib_ccxr/Cargo.lock +++ b/src/rust/lib_ccxr/Cargo.lock @@ -10,9 +10,12 @@ checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "convert_case" -version = "0.4.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] [[package]] name = "deranged" @@ -25,17 +28,32 @@ dependencies = [ [[package]] name = "derive_more" -version = "0.99.18" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "convert_case", "proc-macro2", "quote", - "rustc_version", "syn", + "unicode-xid", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -45,6 +63,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "heck" version = "0.5.0" @@ -61,6 +85,16 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "indexmap" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "itoa" version = "1.0.11" @@ -73,6 +107,8 @@ version = "0.1.0" dependencies = [ "bitflags", "derive_more", + "num_enum", + "socket2", "strum", "strum_macros", "thiserror", @@ -80,12 +116,45 @@ dependencies = [ "url", ] +[[package]] +name = "libc" +version = "0.2.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num_enum" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -98,6 +167,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.86" @@ -109,54 +187,49 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver", -] - [[package]] name = "rustversion" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" -[[package]] -name = "semver" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" - [[package]] name = "serde" -version = "1.0.205" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33aedb1a7135da52b7c21791455563facbbcc43d0f0f66165b42c21b3dfb150" +checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.205" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692d6f5ac90220161d6774db30c662202721e64aed9058d2c394f451261420c1" +checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "strum" version = "0.26.3" @@ -178,9 +251,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.72" +version = "2.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" dependencies = [ "proc-macro2", "quote", @@ -253,6 +326,23 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" + +[[package]] +name = "toml_edit" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + [[package]] name = "unicode-bidi" version = "0.3.15" @@ -274,6 +364,18 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unicode-xid" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" + [[package]] name = "url" version = "2.5.2" @@ -284,3 +386,85 @@ dependencies = [ "idna", "percent-encoding", ] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +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 = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] diff --git a/src/rust/lib_ccxr/Cargo.toml b/src/rust/lib_ccxr/Cargo.toml index 7911005e4..4693b3ea1 100644 --- a/src/rust/lib_ccxr/Cargo.toml +++ b/src/rust/lib_ccxr/Cargo.toml @@ -6,10 +6,12 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +num_enum = "0.7.3" +socket2 = "0.5.7" bitflags = "2.6.0" thiserror = "1.0.61" time = { version = "0.3.36", features = ["macros", "formatting"] } -derive_more = "0.99.18" +derive_more = { version = "1.0.0", features = ["full"] } url = "2.5.2" strum = "0.26.3" strum_macros = "0.26.4" diff --git a/src/rust/lib_ccxr/src/lib.rs b/src/rust/lib_ccxr/src/lib.rs index 15c1b3e68..8c8897272 100644 --- a/src/rust/lib_ccxr/src/lib.rs +++ b/src/rust/lib_ccxr/src/lib.rs @@ -1,4 +1,5 @@ pub mod common; pub mod hardsubx; +pub mod net; pub mod time; pub mod util; diff --git a/src/rust/lib_ccxr/src/net/block.rs b/src/rust/lib_ccxr/src/net/block.rs new file mode 100644 index 000000000..7b5f5f526 --- /dev/null +++ b/src/rust/lib_ccxr/src/net/block.rs @@ -0,0 +1,358 @@ +use super::NetError; +use crate::time::units::Timestamp; + +use num_enum::{IntoPrimitive, TryFromPrimitive}; + +use std::borrow::Cow; +use std::fmt::{self, Display, Formatter}; +use std::io::{self, Write}; + +/// Default port to be used when port number is not specified for TCP. +pub const DEFAULT_TCP_PORT: u16 = 2048; + +/// The amount of time to wait for a response before reseting the connection. +pub const NO_RESPONSE_INTERVAL: Timestamp = Timestamp::from_millis(20_000); + +/// The time interval between sending ping messages. +pub const PING_INTERVAL: Timestamp = Timestamp::from_millis(3_000); + +/// The size of the `length` section of the [`Block`]'s byte format. +/// +/// See [`BlockStream`] for more information. +pub const LEN_SIZE: usize = 10; + +/// The sequence of bytes used to denote the end of a [`Block`] in its byte format. +/// +/// See [`BlockStream`] for more information. +pub const END_MARKER: &str = "\r\n"; + +/// Represents the different kinds of commands that could be sent or received in a block. +#[derive(Copy, Clone, Debug, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)] +#[repr(u8)] +pub enum Command { + Ok = 1, + + /// Used to send password just after making a TCP connection. + Password = 2, + BinMode = 3, + + /// Used to send description just after making a TCP connection. + CcDesc = 4, + BinHeader = 5, + BinData = 6, + + /// Used to send EPG metadata across network. + EpgData = 7, + Error = 51, + UnknownCommand = 52, + WrongPassword = 53, + ConnLimit = 54, + + /// Used to send ping messages to check network connectivity. + Ping = 55, +} + +/// Represents the smallest unit of data that could be sent or received from network. +/// +/// A [`Block`] consists of a [`Command`] and some binary data associated with it. The kind of +/// block is denoted by its [`Command`]. The binary data has different formats or information based +/// on the kind of [`Command`]. +/// +/// Any subtitle data, metadata, ping, etc, that needs to be sent by network goes through in the +/// form of a [`Block`]. See [`BlockStream`] for more information on how a [`Block`] is sent or +/// received. +pub struct Block<'a> { + command: Command, + data: Cow<'a, [u8]>, +} + +impl<'a> Block<'a> { + fn new(command: Command, data: &'a [u8]) -> Block<'a> { + Block { + command, + data: Cow::from(data), + } + } + + fn new_owned(command: Command, data: Vec) -> Block<'a> { + Block { + command, + data: Cow::from(data), + } + } + + /// Returns the kind of [`Block`] denoted by its [`Command`]. + pub fn command(&self) -> Command { + self.command + } + + /// Returns the associated data of [`Block`]. + pub fn data(&self) -> &[u8] { + &self.data + } + + /// Create a new [`Ping`](Command::Ping) Block. + pub fn ping() -> Block<'a> { + Block::new_owned(Command::Ping, vec![]) + } + + /// Create a new [`BinHeader`](Command::BinHeader) Block along with `header` data. + pub fn bin_header(header: &'a [u8]) -> Block<'a> { + Block::new(Command::BinHeader, header) + } + + /// Create a new [`BinData`](Command::BinData) Block along with `data`. + pub fn bin_data(data: &'a [u8]) -> Block<'a> { + Block::new(Command::BinData, data) + } + + /// Create a new [`Password`](Command::Password) Block along with `password` data. + /// + /// The data of the returned [`Block`] will consist of `password` encoded as UTF-8 bytes which + /// is not nul-terminated. + /// + /// # Examples + /// ``` + /// # use lib_ccxr::net::Block; + /// let b = Block::password("A"); + /// assert_eq!(b.data(), &[b'A']); + /// ``` + pub fn password(password: &'a str) -> Block<'a> { + Block::new(Command::Password, password.as_bytes()) + } + + /// Create a new [`CcDesc`](Command::CcDesc) Block along with `desc` data. + /// + /// The data of the returned [`Block`] will consist of `desc` encoded as UTF-8 bytes which is + /// not nul-terminated. + /// + /// # Examples + /// ``` + /// # use lib_ccxr::net::Block; + /// let b = Block::cc_desc("Teletext"); + /// assert_eq!(b.data(), &[b'T', b'e', b'l', b'e', b't', b'e', b'x', b't']); + /// ``` + pub fn cc_desc(desc: &'a str) -> Block<'a> { + Block::new(Command::CcDesc, desc.as_bytes()) + } + + /// Create a new [`EpgData`](Command::EpgData) Block along with the related metadata used in + /// EPG. + /// + /// All the parameters are encoded as UTF-8 bytes which are nul-terminated. If a parameter is + /// [`None`], then it is considered to be equivalent to an empty String. All these + /// nul-terminated UTF-8 bytes are placed one after the other in the order of `start`, `stop`, + /// `title`, `desc`, `lang`, `category` with nul character acting as the seperator between + /// these sections. + /// + /// # Examples + /// ``` + /// # use lib_ccxr::net::Block; + /// let b = Block::epg_data("A", "B", Some("C"), None, Some("D"), None); + /// assert_eq!(b.data(), &[b'A', b'\0', b'B', b'\0', b'C', b'\0', b'\0', b'D', b'\0', b'\0']); + /// ``` + pub fn epg_data( + start: &str, + stop: &str, + title: Option<&str>, + desc: Option<&str>, + lang: Option<&str>, + category: Option<&str>, + ) -> Block<'a> { + let title = title.unwrap_or(""); + let desc = desc.unwrap_or(""); + let lang = lang.unwrap_or(""); + let category = category.unwrap_or(""); + + // Plus 1 to accomodate space for the nul character + let start_len = start.len() + 1; + let stop_len = stop.len() + 1; + let title_len = title.len() + 1; + let desc_len = desc.len() + 1; + let lang_len = lang.len() + 1; + let category_len = category.len() + 1; + + let total_len = start_len + stop_len + title_len + desc_len + lang_len + category_len; + let mut data = Vec::with_capacity(total_len); + + data.extend_from_slice(start.as_bytes()); + data.extend_from_slice("\0".as_bytes()); + data.extend_from_slice(stop.as_bytes()); + data.extend_from_slice("\0".as_bytes()); + data.extend_from_slice(title.as_bytes()); + data.extend_from_slice("\0".as_bytes()); + data.extend_from_slice(desc.as_bytes()); + data.extend_from_slice("\0".as_bytes()); + data.extend_from_slice(lang.as_bytes()); + data.extend_from_slice("\0".as_bytes()); + data.extend_from_slice(category.as_bytes()); + data.extend_from_slice("\0".as_bytes()); + + Block::new_owned(Command::EpgData, data) + } +} + +/// The [`BlockStream`] trait allows for sending and receiving [`Block`]s across the network. +/// +/// The only two implementers of [`BlockStream`] are [`SendTarget`] and [`RecvSource`] which are +/// used for sending and receiving blocks respectively. +/// +/// This trait provides an abstraction over the different interfaces of [`TcpStream`] and +/// [`UdpSocket`]. The implementers only need to implement the functionality to send and receive +/// bytes through network by [`BlockStream::send`] and [`BlockStream::recv`]. The functionality to +/// send and receive [`Block`] will be automatically made available by [`BlockStream::send_block`] +/// and [`BlockStream::recv_block`]. +/// +/// A [`Block`] is sent or received across the network using a byte format. Since a [`Block`] +/// consists of `command` and variable sized `data`, it is encoded in the following way. +/// +/// | Section | Length | Description | +/// |------------|--------------|---------------------------------------------------------------------------| +/// | command | 1 | The `command` enconded as its corresponding byte value. | +/// | length | [`LEN_SIZE`] | The length of `data` encoded as nul-terminated string form of the number. | +/// | data | length | The associated `data` bytes whose meaning is dependent on `command`. | +/// | end_marker | 2 | This value is always [`END_MARKER`], signifying end of current block. | +/// +/// The only exception to the above format is a [`Ping`] [`Block`] which is encoded only with its 1-byte +/// command section. It does not have length, data or end_marker sections. +/// +/// [`SendTarget`]: super::SendTarget +/// [`RecvSource`]: super::RecvSource +/// [`TcpStream`]: std::net::TcpStream +/// [`UdpSocket`]: std::net::UdpSocket +/// [`Ping`]: Command::Ping +pub trait BlockStream { + /// Send the bytes in `buf` across the network. + fn send(&mut self, buf: &[u8]) -> io::Result; + + /// Receive the bytes from network and place them in `buf`. + fn recv(&mut self, buf: &mut [u8]) -> io::Result; + + /// Send a [`Block`] across the network. + /// + /// It returns a [`NetError`] if some transmission failure ocurred, or else it will return a bool + /// that indicates the status of this connection. It will be `false` if the connection shutdown + /// correctly. + fn send_block(&mut self, block: &Block<'_>) -> Result { + let Block { command, data } = block; + + if self.send(&[(*command).into()])? != 1 { + return Ok(false); + } + + if *command == Command::Ping { + return Ok(true); + } + + let mut length_part = [0; LEN_SIZE]; + let _ = write!(length_part.as_mut_slice(), "{}", data.len()); + if self.send(&length_part)? != LEN_SIZE { + return Ok(false); + } + + if data.len() > 0 && self.send(data)? != data.len() { + return Ok(false); + } + + if self.send(END_MARKER.as_bytes())? != END_MARKER.len() { + return Ok(false); + } + + #[cfg(feature = "debug_out")] + eprintln!("block = {}", block); + + Ok(true) + } + + /// Receive a [`Block`] from the network. + /// + /// It returns a [`NetError`] if some transmission failure ocurred or byte format is violated. + /// It will return a [`None`] if the connection has shutdown down correctly. + fn recv_block<'a>(&mut self) -> Result>, NetError> { + let mut command_byte = [0_u8; 1]; + if self.recv(&mut command_byte)? != 1 { + return Ok(None); + } + + let command: Command = command_byte[0] + .try_into() + .map_err(|_| NetError::InvalidBytes { + location: "command", + })?; + + if command == Command::Ping { + return Ok(Some(Block::ping())); + } + + let mut length_bytes = [0u8; LEN_SIZE]; + if self.recv(&mut length_bytes)? != LEN_SIZE { + return Ok(None); + } + let end = length_bytes + .iter() + .position(|&x| x == b'\0') + .unwrap_or(LEN_SIZE); + let length: usize = String::from_utf8_lossy(&length_bytes[0..end]) + .parse() + .map_err(|_| NetError::InvalidBytes { location: "length" })?; + + let mut data = vec![0u8; length]; + if self.recv(&mut data)? != length { + return Ok(None); + } + + let mut end_marker = [0u8; END_MARKER.len()]; + if self.recv(&mut end_marker)? != END_MARKER.len() { + return Ok(None); + } + if end_marker != END_MARKER.as_bytes() { + return Err(NetError::InvalidBytes { + location: "end_marker", + }); + } + + let block = Block::new_owned(command, data); + + #[cfg(feature = "debug_out")] + eprintln!("{}", block); + + Ok(Some(block)) + } +} + +impl Display for Block<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let _ = write!(f, "[Rust] {} {} ", self.command, self.data.len()); + + if self.command != Command::BinHeader && self.command != Command::BinData { + let _ = write!(f, "{} ", &*String::from_utf8_lossy(&self.data)); + } + + let _ = write!(f, "\\r\\n"); + + Ok(()) + } +} + +impl Display for Command { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let message = match self { + Command::Ok => "OK", + Command::Password => "PASSWORD", + Command::BinMode => "BIN_MODE", + Command::CcDesc => "CC_DESC", + Command::BinHeader => "BIN_HEADER", + Command::BinData => "BIN_DATA", + Command::EpgData => "EPG_DATA", + Command::Error => "ERROR", + Command::UnknownCommand => "UNKNOWN_COMMAND", + Command::WrongPassword => "WRONG_PASSWORD", + Command::ConnLimit => "CONN_LIMIT", + Command::Ping => "PING", + }; + + let _ = write!(f, "{}", message); + + Ok(()) + } +} diff --git a/src/rust/lib_ccxr/src/net/c_functions.rs b/src/rust/lib_ccxr/src/net/c_functions.rs new file mode 100644 index 000000000..375b253eb --- /dev/null +++ b/src/rust/lib_ccxr/src/net/c_functions.rs @@ -0,0 +1,98 @@ +//! Provides pure Rust equivalent functions for some functions in `networking.c`. + +use crate::net::*; + +use std::sync::RwLock; + +static TARGET: RwLock> = RwLock::new(None); +static SOURCE: RwLock> = RwLock::new(None); + +/// Rust equivalent for `connect_to_srv` function in C. Uses Rust-native types as input and output. +pub fn connect_to_srv( + addr: &'static str, + port: Option, + cc_desc: Option<&'static str>, + pwd: Option<&'static str>, +) { + let mut send_target = TARGET.write().expect("Unable to use TARGET"); + *send_target = Some(SendTarget::new(SendTargetConfig { + target_addr: addr, + port, + password: pwd, + description: cc_desc, + })); +} + +/// Rust equivalent for `net_send_header` function in C. Uses Rust-native types as input and output. +pub fn net_send_header(data: &[u8]) { + let mut send_target = TARGET.write().unwrap(); + send_target.as_mut().unwrap().send_header(data); +} + +/// Rust equivalent for `net_send_cc` function in C. Uses Rust-native types as input and output. +pub fn net_send_cc(data: &[u8]) -> bool { + let mut send_target = TARGET.write().unwrap(); + send_target.as_mut().unwrap().send_cc(data) +} + +/// Rust equivalent for `net_check_conn` function in C. Uses Rust-native types as input and output. +pub fn net_check_conn() { + let mut send_target = TARGET.write().unwrap(); + send_target.as_mut().unwrap().check_connection(); +} + +/// Rust equivalent for `net_send_epg` function in C. Uses Rust-native types as input and output. +pub fn net_send_epg( + start: &str, + stop: &str, + title: Option<&str>, + desc: Option<&str>, + lang: Option<&str>, + category: Option<&str>, +) { + let mut send_target = TARGET.write().unwrap(); + send_target + .as_mut() + .unwrap() + .send_epg_data(start, stop, title, desc, lang, category); +} + +/// Rust equivalent for `net_tcp_read` function in C. Uses Rust-native types as input and output. +pub fn net_tcp_read(buffer: &mut [u8]) -> Option { + let mut recv_source = SOURCE.write().unwrap(); + if let Ok(b) = recv_source.as_mut().unwrap().recv_header_or_cc() { + if let Some(block) = b { + buffer[..block.data().len()].copy_from_slice(block.data()); + Some(block.data().len()) + } else { + Some(0) + } + } else { + None + } +} + +/// Rust equivalent for `net_udp_read` function in C. Uses Rust-native types as input and output. +pub fn net_udp_read(buffer: &mut [u8]) -> Option { + let mut recv_source = SOURCE.write().unwrap(); + recv_source.as_mut().unwrap().recv(buffer).ok() +} + +/// Rust equivalent for `start_tcp_srv` function in C. Uses Rust-native types as input and output. +pub fn start_tcp_srv(port: Option, pwd: Option<&'static str>) { + let mut recv_source = SOURCE.write().unwrap(); + *recv_source = Some(RecvSource::new(RecvSourceConfig::Tcp { + port, + password: pwd, + })); +} + +/// Rust equivalent for `start_udp_srv` function in C. Uses Rust-native types as input and output. +pub fn start_udp_srv(src: Option<&'static str>, addr: Option<&'static str>, port: u16) { + let mut recv_source = SOURCE.write().unwrap(); + *recv_source = Some(RecvSource::new(RecvSourceConfig::Udp { + source: src, + address: addr, + port, + })); +} diff --git a/src/rust/lib_ccxr/src/net/mod.rs b/src/rust/lib_ccxr/src/net/mod.rs new file mode 100644 index 000000000..0d8697b84 --- /dev/null +++ b/src/rust/lib_ccxr/src/net/mod.rs @@ -0,0 +1,54 @@ +//! A module for sending and receiving subtitle data across the network. +//! +//! The [`SendTarget`] struct provides methods to send data to the network. It can be constructed +//! from [`SendTargetConfig`]. +//! +//! The [`RecvSource`] struct provides methods to receive data from the network. It can be +//! constructed from [`RecvSourceConfig`]. +//! +//! Any data to be sent across the network in the stored in the form of a [`Block`]. The +//! [`BlockStream`] can encode a [`Block`] as a byte sequence using a custom byte format which can +//! then be sent or received using standard networking primitives. See [`BlockStream`] to know more +//! about the custom byte format. +//! +//! # Conversion Guide +//! +//! | From | To | +//! |--------------------------------|-----------------------------------| +//! | `connect_to_srv` | [`SendTarget::new`] | +//! | `net_send_header` | [`SendTarget::send_header`] | +//! | `net_send_cc` | [`SendTarget::send_cc`] | +//! | `net_send_epg` | [`SendTarget::send_epg_data`] | +//! | `net_check_conn` | [`SendTarget::check_connection`] | +//! | `start_tcp_srv` | [`RecvSource::new`] | +//! | `start_udp_srv` | [`RecvSource::new`] | +//! | `net_tcp_read` | [`RecvSource::recv_header_or_cc`] | +//! | `net_udp_read` | [`RecvSource::send`] | +//! | `read_block` | [`BlockStream::recv_block`] | +//! | `write_block` | [`BlockStream::send_block`] | +//! | Commands in Protocol Constants | [`Command`] | +//! | `DFT_PORT` | [`DEFAULT_TCP_PORT`] | +//! | `NO_RESPONCE_INTERVAL` | [`NO_RESPONSE_INTERVAL`] | +//! | `PING_INTERVAL` | [`PING_INTERVAL`] | + +mod block; +pub mod c_functions; +mod source; +mod target; + +pub use crate::net::{block::*, source::*, target::*}; + +/// A collective [`Error`](std::error::Error) type that encompasses all the possible error cases +/// when sending, receiving or parsing data during networking operations. +#[derive(thiserror::Error, Debug)] +pub enum NetError { + /// An Error ocurred within std giving a [`io::Error`] + #[error(transparent)] + IoError(#[from] std::io::Error), + + /// The received bytes do not follow a [`Block`]'s byte format. + /// + /// See [`BlockStream`] for more information. + #[error("invalid bytes while parsing {location}")] + InvalidBytes { location: &'static str }, +} diff --git a/src/rust/lib_ccxr/src/net/source.rs b/src/rust/lib_ccxr/src/net/source.rs new file mode 100644 index 000000000..9205bd7d0 --- /dev/null +++ b/src/rust/lib_ccxr/src/net/source.rs @@ -0,0 +1,312 @@ +use super::{Block, BlockStream, Command, NetError, DEFAULT_TCP_PORT, PING_INTERVAL}; +use crate::time::units::Timestamp; +use crate::util::log::{fatal, info, ExitCause}; + +use socket2::{Domain, Socket, Type}; + +use std::io; +use std::io::{Read, Write}; +use std::net::{ + IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, TcpListener, TcpStream, ToSocketAddrs, + UdpSocket, +}; + +/// An enum of configuration parameters to construct [`RecvSource`]. +#[derive(Copy, Clone, Debug)] +pub enum RecvSourceConfig<'a> { + Tcp { + /// The port number where TCP socket will be bound. + /// + /// If no port number is provided then [`DEFAULT_TCP_PORT`] will be used instead. + port: Option, + + /// The password of receiving server. + /// + /// The password sent from client will be compared to this and only allow furthur + /// communication if the passwords match. + password: Option<&'a str>, + }, + Udp { + /// Source's IP address or hostname if trying to open a multicast SSM channel. + source: Option<&'a str>, + + /// The IP address or hostname where UDP socket will be bound. + address: Option<&'a str>, + + /// The port number where UDP socket will be bound. + port: u16, + }, +} + +enum SourceSocket { + Tcp(TcpStream), + Udp { + socket: UdpSocket, + source: Option, + address: Ipv4Addr, + }, +} + +/// A struct used for receiving subtitle data from the network. +/// +/// Even though it exposes methods from [`BlockStream`], it is recommended to not use them. +/// Instead use the methods provided directly by [`RecvSource`] like +/// [`RecvSource::recv_header_or_cc`]. +/// +/// Note: To create a [`RecvSource`], one must first construct a [`RecvSourceConfig`]. +/// +/// ```no_run +/// # use lib_ccxr::net::{RecvSource, RecvSourceConfig}; +/// let config = RecvSourceConfig::Tcp { +/// port: None, +/// password: Some("12345678"), +/// }; +/// let mut recv_source = RecvSource::new(config); +/// +/// // Once recv_source is constructed, we can use it to receive data. +/// let block = recv_source.recv_header_or_cc().unwrap(); +/// ``` +pub struct RecvSource { + socket: SourceSocket, + last_ping: Timestamp, +} + +impl BlockStream for RecvSource { + fn send(&mut self, buf: &[u8]) -> io::Result { + match &mut self.socket { + SourceSocket::Tcp(stream) => stream.write(buf), + SourceSocket::Udp { socket, .. } => socket.send(buf), + } + } + + fn recv(&mut self, buf: &mut [u8]) -> io::Result { + match &mut self.socket { + SourceSocket::Tcp(stream) => stream.read(buf), + SourceSocket::Udp { + socket, + source, + address, + } => { + if cfg!(target_os = "windows") { + socket.recv(buf) + } else { + let should_check_source = address.is_multicast() && source.is_some(); + if should_check_source { + loop { + if let Ok((size, src_addr)) = socket.recv_from(buf) { + if src_addr.ip() == source.unwrap() { + return Ok(size); + } + } + } + } else { + socket.recv(buf) + } + } + } + } + } +} + +impl RecvSource { + /// Create a new [`RecvSource`] from the configuration parameters of [`RecvSourceConfig`]. + /// + /// Note: This method attempts to create a server. It does not return a [`Result`]. When + /// it is unable to start a server, it crashes instantly by calling [`fatal!`]. + /// + /// This method will continously block until a connection with a client is made. + pub fn new(config: RecvSourceConfig) -> RecvSource { + match config { + RecvSourceConfig::Tcp { port, password } => { + let port = port.unwrap_or(DEFAULT_TCP_PORT); + + info!( + "\n\r----------------------------------------------------------------------\n" + ); + info!("Binding to {}\n", port); + + let addresses = [ + SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port), + SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port), + ]; + + let listener = TcpListener::bind(addresses.as_slice()).unwrap_or_else( + |_| fatal!(cause = ExitCause::Failure; "Unable to start server\n"), + ); + + if let Some(pwd) = &password { + info!("Password: {}\n", pwd); + } + + info!("Waiting for connections\n"); + + loop { + if let Ok((socket, _)) = listener.accept() { + let mut source = RecvSource { + socket: SourceSocket::Tcp(socket), + last_ping: Timestamp::from_millis(0), + }; + if check_password(&mut source, password) { + return source; + } + + info!("Connection closed\n"); + drop(source); + } + } + } + RecvSourceConfig::Udp { + source, + address, + port, + } => { + let address = address + .map(|x| { + for s in (x, 0).to_socket_addrs().unwrap() { + if let SocketAddr::V4(sv4) = s { + return *sv4.ip(); + } + } + fatal!(cause = ExitCause::Failure; "Could not resolve udp address") + }) + .unwrap_or(Ipv4Addr::UNSPECIFIED); + + let source = source.map(|x| { + for s in (x, 0).to_socket_addrs().unwrap() { + if let SocketAddr::V4(sv4) = s { + return *sv4.ip(); + } + } + fatal!(cause = ExitCause::Failure; "Could not resolve udp source") + }); + + let socket = Socket::new(Domain::IPV4, Type::DGRAM, None).unwrap_or_else( + |_| fatal!(cause = ExitCause::Failure; "Socket creation error"), + ); + + if address.is_multicast() { + socket.set_reuse_address(true).unwrap_or_else(|_| { + info!("Cannot not set reuse address\n"); + }); + } + + let binding_address = if cfg!(target_os = "windows") && address.is_multicast() { + Ipv4Addr::UNSPECIFIED + } else { + address + }; + + socket + .bind(&SocketAddrV4::new(binding_address, port).into()) + .unwrap_or_else(|_| fatal!(cause = ExitCause::Bug; "Socket bind error")); + + if address.is_multicast() { + if let Some(src) = source { + socket.join_ssm_v4(&src, &address, &Ipv4Addr::UNSPECIFIED) + } else { + socket.join_multicast_v4(&address, &Ipv4Addr::UNSPECIFIED) + } + .unwrap_or_else( + |_| fatal!(cause = ExitCause::Bug; "Cannot join multicast group"), + ); + } + + info!( + "\n\r----------------------------------------------------------------------\n" + ); + if address == Ipv4Addr::UNSPECIFIED { + info!("\rReading from UDP socket {}\n", port); + } else if let Some(src) = source { + info!("\rReading from UDP socket {}@{}:{}\n", src, address, port); + } else { + info!("\rReading from UDP socket {}:{}\n", address, port); + } + + RecvSource { + socket: SourceSocket::Udp { + socket: socket.into(), + address, + source, + }, + last_ping: Timestamp::from_millis(0), + } + } + } + } + + /// Receive a [`BinHeader`] or [`BinData`] [`Block`]. + /// + /// Note that this method will continously block until it receives a + /// [`BinHeader`] or [`BinData`] [`Block`]. + /// + /// It returns a [`NetError`] if some transmission failure ocurred or byte format is violated. + /// It will return a [`None`] if the connection has shutdown down correctly. + /// + /// [`BinHeader`]: Command::BinHeader + /// [`BinData`]: Command::BinData + pub fn recv_header_or_cc<'a>(&mut self) -> Result>, NetError> { + let now = Timestamp::now(); + if self.last_ping.millis() == 0 { + self.last_ping = now; + } + + if now - self.last_ping > PING_INTERVAL { + self.last_ping = now; + if self.send_ping().is_err() { + fatal!(cause = ExitCause::Failure; "Unable to send keep-alive packet to client\n"); + } + } + + loop { + if let Some(block) = self.recv_block()? { + if block.command() == Command::BinHeader || block.command() == Command::BinData { + return Ok(Some(block)); + } + } else { + return Ok(None); + } + } + } + + /// Send a [`Ping`](Command::Ping) [`Block`]. + /// + /// It returns a [`NetError`] if some transmission failure ocurred, or else it will return a bool + /// that indicates the status of this connection. It will be `false` if the connection shutdown + /// correctly. + fn send_ping(&mut self) -> Result { + self.send_block(&Block::ping()) + } +} + +/// Check if the received password matches with the current password. +/// +/// This methods attempts to read a [`Password`](Command::Password) [`Block`] from `socket`. Any +/// form of error in this operation results in `false`. +/// +/// If `password` is [`None`], then no checking is done and results in `true`. +fn check_password(socket: &mut RecvSource, password: Option<&str>) -> bool { + let block = match socket.recv_block() { + Ok(Some(b)) => b, + _ => return false, + }; + + let pwd = match password { + Some(p) => p, + None => return true, + }; + + if block.command() == Command::Password && String::from_utf8_lossy(block.data()) == *pwd { + true + } else { + #[cfg(feature = "debug_out")] + { + eprintln!("[C] Wrong password"); + eprintln!("[S] PASSWORD"); + } + // TODO: Check if the below portion is really required, since the receiver is not even + // listening for a Password block + let _ = socket.send_block(&Block::password("")); + + false + } +} diff --git a/src/rust/lib_ccxr/src/net/target.rs b/src/rust/lib_ccxr/src/net/target.rs new file mode 100644 index 000000000..eb84b4ac8 --- /dev/null +++ b/src/rust/lib_ccxr/src/net/target.rs @@ -0,0 +1,293 @@ +use super::{ + block::{Block, BlockStream, Command, DEFAULT_TCP_PORT, NO_RESPONSE_INTERVAL, PING_INTERVAL}, + NetError, +}; +use crate::time::units::Timestamp; +use crate::util::log::{fatal, info, ExitCause}; + +use std::io; +use std::io::{Read, Write}; +use std::net::TcpStream; + +/// A struct of configuration parameters to construct [`SendTarget`]. +#[derive(Copy, Clone, Debug)] +pub struct SendTargetConfig<'a> { + /// Target's IP address or hostname. + pub target_addr: &'a str, + + /// Target's port number. + /// + /// If no port number is provided then [`DEFAULT_TCP_PORT`] will be used instead. + pub port: Option, + + /// Password to be sent after establishing connection. + pub password: Option<&'a str>, + + /// Description to sent after establishing connection. + pub description: Option<&'a str>, +} + +/// A struct used for sending subtitle data across the network. +/// +/// Even though it exposes methods from [`BlockStream`], it is recommended to not use them. +/// Instead use the methods provided directly by [`SendTarget`] like [`SendTarget::send_header`], +/// [`SendTarget::send_cc`], etc. +/// +/// To create a [`SendTarget`], one must first construct a [`SendTargetConfig`]. +/// +/// ```no_run +/// # use lib_ccxr::net::{SendTarget, SendTargetConfig}; +/// let config = SendTargetConfig { +/// target_addr: "192.168.60.133", +/// port: None, +/// password: Some("12345678"), +/// description: None, +/// }; +/// let mut send_target = SendTarget::new(config); +/// +/// // Once send_target is constructed, we can use it to send different kinds of data. +/// # let header = &[0u8; 1]; +/// send_target.send_header(header); +/// # let cc = &[0u8; 1]; +/// send_target.send_cc(cc); +/// ``` +pub struct SendTarget<'a> { + stream: Option, + config: SendTargetConfig<'a>, + header_data: Option>, + last_ping: Timestamp, + last_send_ping: Timestamp, +} + +impl BlockStream for SendTarget<'_> { + fn send(&mut self, buf: &[u8]) -> io::Result { + self.stream.as_mut().unwrap().write(buf) + } + + fn recv(&mut self, buf: &mut [u8]) -> io::Result { + self.stream.as_mut().unwrap().read(buf) + } +} + +impl<'a> SendTarget<'a> { + /// Create a new [`SendTarget`] from the configuration parameters of [`SendTargetConfig`]. + /// + /// Note: This method attempts to connect to a server. It does not return a [`Result`]. When + /// it is unable to connect to a server, it crashes instantly by calling [`fatal!`]. + pub fn new(config: SendTargetConfig<'a>) -> SendTarget<'a> { + if config.target_addr.is_empty() { + info!("Server address is not set\n"); + fatal!( + cause = ExitCause::Failure; + "Unable to connect, address passed is null\n" + ); + } + + let tcp_stream = TcpStream::connect(( + config.target_addr, + config.port.unwrap_or(DEFAULT_TCP_PORT), + )) + .unwrap_or_else( + |_| fatal!(cause = ExitCause::Failure; "Unable to connect (tcp connection error).\n"), + ); + + tcp_stream.set_nonblocking(true).unwrap_or_else( + |_| fatal!(cause = ExitCause::Failure; "Unable to connect (set nonblocking).\n"), + ); + + let mut send_target = SendTarget { + stream: Some(tcp_stream), + config, + header_data: None, + last_ping: Timestamp::from_millis(0), + last_send_ping: Timestamp::from_millis(0), + }; + + send_target.send_password().unwrap_or_else( + |_| fatal!(cause = ExitCause::Failure; "Unable to connect (sending password).\n"), + ); + + send_target.send_description().unwrap_or_else( + |_| fatal!(cause = ExitCause::Failure; "Unable to connect (sending cc_desc).\n"), + ); + + info!( + "Connected to {}:{}\n", + send_target.config.target_addr, + send_target.config.port.unwrap_or(DEFAULT_TCP_PORT) + ); + + send_target + } + + /// Consumes the [`SendTarget`] only returning its internal stream. + /// + /// Note: Crashes if `self.stream` is not set. + fn into_stream(self) -> TcpStream { + self.stream.expect("TcpStream must be set") + } + + /// Send a [`BinHeader`](Command::BinHeader) [`Block`] returning if the operation was successful. + pub fn send_header(&mut self, data: &[u8]) -> bool { + #[cfg(feature = "debug_out")] + { + eprintln!("Sending header (len = {}): ", data.len()); + eprintln!( + "File created by {:02X} version {:02X}{:02X}", + data[3], data[4], data[5] + ); + eprintln!("File format revision: {:02X}{:02X}", data[6], data[7]); + } + + if let Ok(true) = self.send_block(&Block::bin_header(data)) { + } else { + println!("Can't send BIN header"); + return false; + } + + if self.header_data.is_none() { + self.header_data = Some(data.into()) + } + + true + } + + /// Send a [`BinData`](Command::BinData) [`Block`] returning if the operation was successful. + pub fn send_cc(&mut self, data: &[u8]) -> bool { + #[cfg(feature = "debug_out")] + { + eprintln!("[C] Sending {} bytes", data.len()); + } + + if let Ok(true) = self.send_block(&Block::bin_data(data)) { + } else { + println!("Can't send BIN data"); + return false; + } + + true + } + + /// Send a [`EpgData`](Command::EpgData) [`Block`] returning if the operation was successful. + pub fn send_epg_data( + &mut self, + start: &str, + stop: &str, + title: Option<&str>, + desc: Option<&str>, + lang: Option<&str>, + category: Option<&str>, + ) -> bool { + let block = Block::epg_data(start, stop, title, desc, lang, category); + + #[cfg(feature = "debug_out")] + { + eprintln!("[C] Sending EPG: {} bytes", block.data().len()) + } + + if let Ok(true) = self.send_block(&block) { + } else { + eprintln!("Can't send EPG data"); + return false; + } + + true + } + + /// Send a [`Ping`](Command::Ping) [`Block`]. + /// + /// It returns a [`NetError`] if some transmission failure ocurred, or else it will return a bool + /// that indicates the status of this connection. It will be `false` if the connection shutdown + /// correctly. + fn send_ping(&mut self) -> Result { + self.send_block(&Block::ping()) + } + + /// Send a [`Password`](Command::Password) [`Block`]. + /// + /// It returns a [`NetError`] if some transmission failure ocurred, or else it will return a bool + /// that indicates the status of this connection. It will be `false` if the connection shutdown + /// correctly. + fn send_password(&mut self) -> Result { + let password = self.config.password.unwrap_or(""); + self.send_block(&Block::password(password)) + } + + /// Send a [`CcDesc`](Command::CcDesc) [`Block`]. + /// + /// It returns a [`NetError`] if some transmission failure ocurred, or else it will return a bool + /// that indicates the status of this connection. It will be `false` if the connection shutdown + /// correctly. + fn send_description(&mut self) -> Result { + let description = self.config.description.unwrap_or(""); + self.send_block(&Block::cc_desc(description)) + } + + /// Check the connection health and reset connection if necessary. + /// + /// This method determines the connection health by comparing the time since last [`Ping`] + /// [`Block`] was received with [`NO_RESPONSE_INTERVAL`]. If it exceeds the + /// [`NO_RESPONSE_INTERVAL`], the connection is reset. + /// + /// This method also sends timely [`Ping`] [`Block`]s back to the server based on the + /// [`PING_INTERVAL`]. This method will crash instantly with [`fatal!`] if it is unable to send + /// data. + /// + /// [`Ping`]: Command::Ping + pub fn check_connection(&mut self) { + let now = Timestamp::now(); + + if self.last_ping.millis() == 0 { + self.last_ping = now; + } + + loop { + if self + .recv_block() + .ok() + .flatten() + .map(|x| x.command() == Command::Ping) + .unwrap_or(false) + { + #[cfg(feature = "debug_out")] + { + eprintln!("[S] Received PING"); + } + self.last_ping = now; + } else { + break; + } + } + + if now - self.last_ping > NO_RESPONSE_INTERVAL { + eprintln!( + "[S] No PING received from the server in {} sec, reconnecting", + NO_RESPONSE_INTERVAL.seconds() + ); + + self.stream + .take() + .unwrap() + .shutdown(std::net::Shutdown::Both) + .expect("Unable to shutdown the TCP Server"); + + self.stream = Some(SendTarget::new(self.config).into_stream()); + + // `self.header_data` is only temporarily taken, since it will be refilled inside + // `send_header` function. + if let Some(header_data) = self.header_data.take() { + self.send_header(header_data.as_slice()); + } + + self.last_ping = now; + } + + if now - self.last_send_ping >= PING_INTERVAL { + if self.send_ping().is_err() { + fatal!(cause = ExitCause::Failure; "Unable to send data\n"); + } + + self.last_send_ping = now; + } + } +} diff --git a/src/rust/src/libccxr_exports/mod.rs b/src/rust/src/libccxr_exports/mod.rs index de1e4b43c..f8545fc23 100644 --- a/src/rust/src/libccxr_exports/mod.rs +++ b/src/rust/src/libccxr_exports/mod.rs @@ -1,5 +1,6 @@ //! Provides C-FFI functions that are direct equivalent of functions available in C. +pub mod net; pub mod time; use crate::ccx_s_options; diff --git a/src/rust/src/libccxr_exports/net.rs b/src/rust/src/libccxr_exports/net.rs new file mode 100644 index 000000000..b32e1339f --- /dev/null +++ b/src/rust/src/libccxr_exports/net.rs @@ -0,0 +1,286 @@ +use crate::bindings::*; + +use std::convert::TryInto; +use std::ffi::CStr; +use std::os::raw::{c_char, c_int, c_uchar, c_uint, c_void}; + +use lib_ccxr::net::c_functions::*; + +/// Rust equivalent for `connect_to_srv` function in C. Uses C-native types as input and output. +/// +/// # Safety +/// +/// `addr` must not be null. All the strings must end with a nul character. +#[no_mangle] +pub unsafe extern "C" fn ccxr_connect_to_srv( + addr: *const c_char, + port: *const c_char, + cc_desc: *const c_char, + pwd: *const c_char, +) { + let addr = CStr::from_ptr(addr) + .to_str() + .expect("Unable to convert Cstr to &str"); + + let port = if !port.is_null() { + Some( + CStr::from_ptr(port) + .to_str() + .expect("Unable to convert Cstr to &str") + .parse() + .expect("Unable to parse into u16"), + ) + } else { + None + }; + + let cc_desc = if !cc_desc.is_null() { + Some( + CStr::from_ptr(cc_desc) + .to_str() + .expect("Unable to convert Cstr to &str"), + ) + } else { + None + }; + + let pwd = if !pwd.is_null() { + Some( + CStr::from_ptr(pwd) + .to_str() + .expect("Unable to convert Cstr to &str"), + ) + } else { + None + }; + + connect_to_srv(addr, port, cc_desc, pwd); +} + +/// Rust equivalent for `net_send_header` function in C. Uses C-native types as input and output. +/// +/// # Safety +/// +/// `data` must not be null and should have a length of `len`. +/// [`ccxr_connect_to_srv`] or `connect_to_srv` must have been called before this function. +#[no_mangle] +pub unsafe extern "C" fn ccxr_net_send_header(data: *const c_uchar, len: usize) { + let buffer = std::slice::from_raw_parts(data, len); + net_send_header(buffer); +} + +/// Rust equivalent for `net_send_cc` function in C. Uses C-native types as input and output. +/// +/// # Safety +/// +/// `data` must not be null and should have a length of `len`. +/// [`ccxr_connect_to_srv`] or `connect_to_srv` must have been called before this function. +#[no_mangle] +pub unsafe extern "C" fn ccxr_net_send_cc( + data: *const c_uchar, + len: usize, + _private_data: *const c_void, + _sub: *const cc_subtitle, +) -> c_int { + let buffer = std::slice::from_raw_parts(data, len); + if net_send_cc(buffer) { + 1 + } else { + -1 + } +} + +/// Rust equivalent for `net_check_conn` function in C. Uses C-native types as input and output. +/// +/// # Safety +/// +/// [`ccxr_connect_to_srv`] or `connect_to_srv` must have been called before this function. +#[no_mangle] +pub unsafe extern "C" fn ccxr_net_check_conn() { + net_check_conn() +} + +/// Rust equivalent for `net_send_epg` function in C. Uses C-native types as input and output. +/// +/// # Safety +/// +/// `start` and `stop` must not be null. All the strings must end with a nul character. +/// [`ccxr_connect_to_srv`] or `connect_to_srv` must have been called before this function. +#[no_mangle] +pub unsafe extern "C" fn ccxr_net_send_epg( + start: *const c_char, + stop: *const c_char, + title: *const c_char, + desc: *const c_char, + lang: *const c_char, + category: *const c_char, +) { + let start = CStr::from_ptr(start) + .to_str() + .expect("Unable to convert Cstr to &str"); + let stop = CStr::from_ptr(stop) + .to_str() + .expect("Unable to convert Cstr to &str"); + + let title = if !title.is_null() { + Some( + CStr::from_ptr(title) + .to_str() + .expect("Unable to convert Cstr to &str"), + ) + } else { + None + }; + + let desc = if !desc.is_null() { + Some( + CStr::from_ptr(desc) + .to_str() + .expect("Unable to convert Cstr to &str"), + ) + } else { + None + }; + + let lang = if !lang.is_null() { + Some( + CStr::from_ptr(lang) + .to_str() + .expect("Unable to convert Cstr to &str"), + ) + } else { + None + }; + + let category = if !category.is_null() { + Some( + CStr::from_ptr(category) + .to_str() + .expect("Unable to convert Cstr to &str"), + ) + } else { + None + }; + + net_send_epg(start, stop, title, desc, lang, category) +} + +/// Rust equivalent for `net_tcp_read` function in C. Uses C-native types as input and output. +/// +/// # Safety +/// +/// `buffer` should not be null. it should be of size `length`. +/// [`ccxr_start_tcp_srv`] or `start_tcp_srv` must have been called before this function. +#[no_mangle] +pub unsafe extern "C" fn ccxr_net_tcp_read( + _socket: c_int, + buffer: *mut c_void, + length: usize, +) -> c_int { + let buffer = std::slice::from_raw_parts_mut(buffer as *mut u8, length); + let ans = net_tcp_read(buffer); + match ans { + Some(x) => x.try_into().unwrap(), + None => -1, + } +} + +/// Rust equivalent for `net_udp_read` function in C. Uses C-native types as input and output. +/// +/// # Safety +/// +/// `buffer` should not be null. it should be of size `length`. +/// [`ccxr_start_udp_srv`] or `start_udp_srv` must have been called before this function. +#[no_mangle] +pub unsafe extern "C" fn ccxr_net_udp_read( + _socket: c_int, + buffer: *mut c_void, + length: usize, + _src_str: *const c_char, + _addr_str: *const c_char, +) -> c_int { + let buffer = std::slice::from_raw_parts_mut(buffer as *mut u8, length); + let ans = net_udp_read(buffer); + match ans { + Some(x) => x.try_into().unwrap(), + None => -1, + } +} + +/// Rust equivalent for `start_tcp_srv` function in C. Uses C-native types as input and output. +/// +/// Note that this function always returns 1 as an fd, since it will not be used anyway. +/// +/// # Safety +/// +/// `port` should be a numerical 16-bit value. All the strings must end with a nul character. +/// The output file desciptor should not be used. +#[no_mangle] +pub unsafe extern "C" fn ccxr_start_tcp_srv(port: *const c_char, pwd: *const c_char) -> c_int { + let port = if !port.is_null() { + Some( + CStr::from_ptr(port) + .to_str() + .expect("Unable to convert Cstr to &str") + .parse() + .expect("Unable to parse into u16"), + ) + } else { + None + }; + + let pwd = if !pwd.is_null() { + Some( + CStr::from_ptr(pwd) + .to_str() + .expect("Unable to convert Cstr to &str"), + ) + } else { + None + }; + + start_tcp_srv(port, pwd); + + 1 +} + +/// Rust equivalent for `start_udp_srv` function in C. Uses C-native types as input and output. +/// +/// Note that this function always returns 1 as an fd, since it will not be used anyway. +/// +/// # Safety +/// +/// `port` should be a 16-bit value. All the strings must end with a nul character. +/// The output file desciptor should not be used. +#[no_mangle] +pub unsafe extern "C" fn ccxr_start_udp_srv( + src: *const c_char, + addr: *const c_char, + port: c_uint, +) -> c_int { + let src = if !src.is_null() { + Some( + CStr::from_ptr(src) + .to_str() + .expect("Unable to convert Cstr to &str"), + ) + } else { + None + }; + + let addr = if !addr.is_null() { + Some( + CStr::from_ptr(addr) + .to_str() + .expect("Unable to convert Cstr to &str"), + ) + } else { + None + }; + + let port = port.try_into().unwrap(); + + start_udp_srv(src, addr, port); + + 1 +}