diff --git a/Cargo.lock b/Cargo.lock index f43efff0..b738028a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -78,47 +78,48 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.13" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -126,9 +127,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.82" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "arrayref" @@ -136,21 +137,37 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + [[package]] name = "atm0s-media-server" version = "0.1.0" dependencies = [ "atm0s-sdn", "clap", + "convert-enum", + "derive_more", "local-ip-address", "log", + "maxminddb", + "media-server-gateway", "media-server-protocol", "media-server-runner", + "media-server-secure", + "num_enum", "poem", "poem-openapi", "prost", + "quinn", "rand", + "rcgen", + "rustls", "sans-io-runtime", + "serde", "tokio", "tracing-subscriber", ] @@ -158,7 +175,7 @@ dependencies = [ [[package]] name = "atm0s-sdn" version = "0.1.10" -source = "git+https://github.com/giangndm/8xFF-decentralized-sdn.git?rev=9200a1615def0ddffce8338b34afd24421f24269#9200a1615def0ddffce8338b34afd24421f24269" +source = "git+https://github.com/giangndm/8xFF-decentralized-sdn.git?rev=e3456db45912bdd461755088a5dde5e004b0f17a#e3456db45912bdd461755088a5dde5e004b0f17a" dependencies = [ "atm0s-sdn-identity", "atm0s-sdn-network", @@ -178,7 +195,7 @@ dependencies = [ [[package]] name = "atm0s-sdn-identity" version = "0.2.0" -source = "git+https://github.com/giangndm/8xFF-decentralized-sdn.git?rev=9200a1615def0ddffce8338b34afd24421f24269#9200a1615def0ddffce8338b34afd24421f24269" +source = "git+https://github.com/giangndm/8xFF-decentralized-sdn.git?rev=e3456db45912bdd461755088a5dde5e004b0f17a#e3456db45912bdd461755088a5dde5e004b0f17a" dependencies = [ "multiaddr", "rand", @@ -188,7 +205,7 @@ dependencies = [ [[package]] name = "atm0s-sdn-network" version = "0.3.1" -source = "git+https://github.com/giangndm/8xFF-decentralized-sdn.git?rev=9200a1615def0ddffce8338b34afd24421f24269#9200a1615def0ddffce8338b34afd24421f24269" +source = "git+https://github.com/giangndm/8xFF-decentralized-sdn.git?rev=e3456db45912bdd461755088a5dde5e004b0f17a#e3456db45912bdd461755088a5dde5e004b0f17a" dependencies = [ "aes-gcm", "atm0s-sdn-identity", @@ -215,7 +232,7 @@ dependencies = [ [[package]] name = "atm0s-sdn-router" version = "0.1.4" -source = "git+https://github.com/giangndm/8xFF-decentralized-sdn.git?rev=9200a1615def0ddffce8338b34afd24421f24269#9200a1615def0ddffce8338b34afd24421f24269" +source = "git+https://github.com/giangndm/8xFF-decentralized-sdn.git?rev=e3456db45912bdd461755088a5dde5e004b0f17a#e3456db45912bdd461755088a5dde5e004b0f17a" dependencies = [ "atm0s-sdn-identity", "atm0s-sdn-utils", @@ -227,17 +244,50 @@ dependencies = [ [[package]] name = "atm0s-sdn-utils" version = "0.1.1" -source = "git+https://github.com/giangndm/8xFF-decentralized-sdn.git?rev=9200a1615def0ddffce8338b34afd24421f24269#9200a1615def0ddffce8338b34afd24421f24269" +source = "git+https://github.com/giangndm/8xFF-decentralized-sdn.git?rev=e3456db45912bdd461755088a5dde5e004b0f17a#e3456db45912bdd461755088a5dde5e004b0f17a" dependencies = [ "log", "serde", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" -version = "1.2.0" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "aws-lc-rs" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "474d7cec9d0a1126fad1b224b767fcbf351c23b0309bb21ec210bcfd379926a5" +dependencies = [ + "aws-lc-sys", + "mirai-annotations", + "paste", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +checksum = "7505fc3cb7acbf42699a43a79dd9caa4ed9e99861dfbb837c5c0fb5a0a8d2980" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", + "libc", + "paste", +] [[package]] name = "backtrace" @@ -260,6 +310,12 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.21.7" @@ -268,9 +324,15 @@ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64" -version = "0.22.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bincode" @@ -282,10 +344,33 @@ dependencies = [ ] [[package]] -name = "bitflags" -version = "1.3.2" +name = "bindgen" +version = "0.69.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.66", + "which", +] + +[[package]] +name = "binstring" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "7e0d60973d9320722cb1206f412740e162a33b8547ea8d6be75d7cff237c7a85" [[package]] name = "bitflags" @@ -293,6 +378,17 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +[[package]] +name = "blake2b_simd" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23285ad32269793932e830392f2fe2f83e26488fd3ec778883a93c8323735780" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -311,6 +407,16 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "bstr" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -331,9 +437,29 @@ checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" -version = "1.0.95" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" +checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" +dependencies = [ + "jobserver", + "libc", + "once_cell", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] [[package]] name = "cfg-if" @@ -359,6 +485,28 @@ dependencies = [ "windows-targets 0.52.5", ] +[[package]] +name = "chrono-tz" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb" +dependencies = [ + "chrono", + "chrono-tz-build", + "phf", +] + +[[package]] +name = "chrono-tz-build" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1" +dependencies = [ + "parse-zoneinfo", + "phf", + "phf_codegen", +] + [[package]] name = "cipher" version = "0.4.4" @@ -369,6 +517,17 @@ dependencies = [ "inout", ] +[[package]] +name = "clang-sys" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a483f3cbf7cec2e153d424d0e92329d816becc6421389bd494375c6065921b9b" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "4.5.4" @@ -388,7 +547,7 @@ dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim 0.11.1", + "strsim", ] [[package]] @@ -400,7 +559,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.66", ] [[package]] @@ -409,11 +568,31 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +[[package]] +name = "cmake" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" +dependencies = [ + "cc", +] + +[[package]] +name = "coarsetime" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b3839cf01bb7960114be3ccf2340f541b6d0c81f8690b007b2b39f750f7e5d" +dependencies = [ + "libc", + "wasix", + "wasm-bindgen", +] + [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "combine" @@ -427,13 +606,25 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "constant_time_eq" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" + [[package]] name = "convert-enum" version = "0.1.0" @@ -457,7 +648,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" dependencies = [ "aes-gcm", - "base64 0.22.0", + "base64 0.22.1", "hkdf", "hmac", "percent-encoding", @@ -468,6 +659,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.6" @@ -507,11 +708,42 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] [[package]] name = "crypto-common" @@ -524,6 +756,12 @@ dependencies = [ "typenum", ] +[[package]] +name = "ct-codecs" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3b7eb4404b8195a9abb6356f4ac07d8ba267045c8d6d220ac4dc992e6cc75df" + [[package]] name = "ctr" version = "0.9.2" @@ -557,14 +795,14 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.66", ] [[package]] name = "darling" -version = "0.20.8" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" +checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" dependencies = [ "darling_core", "darling_macro", @@ -572,40 +810,40 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.8" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" +checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", - "strsim 0.10.0", - "syn 2.0.60", + "strsim", + "syn 2.0.66", ] [[package]] name = "darling_macro" -version = "0.20.8" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" +checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" dependencies = [ "darling_core", "quote", - "syn 2.0.60", + "syn 2.0.66", ] [[package]] name = "data-encoding" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" [[package]] name = "data-encoding-macro" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20c01c06f5f429efdf2bae21eb67c28b3df3cf85b7dd2d8ef09c0838dac5d33e" +checksum = "f1559b6cba622276d6d63706db152618eeb15b89b3e4041446b05876e352e639" dependencies = [ "data-encoding", "data-encoding-macro-internal", @@ -613,14 +851,25 @@ dependencies = [ [[package]] name = "data-encoding-macro-internal" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0047d07f2c89b17dd631c80450d69841a6b5d7fb17278cbc43d7e4cfcf2576f3" +checksum = "332d754c0af53bc87c108fed664d121ecf59207ec4196041f04d6ab9002ad33f" dependencies = [ "data-encoding", "syn 1.0.109", ] +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "deranged" version = "0.3.11" @@ -654,6 +903,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "deunicode" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339544cc9e2c4dc3fc7149fd630c5f22263a4fdf18a98afd0075784968b5cf00" + [[package]] name = "digest" version = "0.10.7" @@ -661,6 +916,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", "subtle", ] @@ -671,11 +927,62 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" +[[package]] +name = "dunce" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519-compact" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9b3460f44bea8cd47f45a0c70892f1eff856d97cd55358b2f73f663789f6190" +dependencies = [ + "ct-codecs", + "getrandom", +] + [[package]] name = "either" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] [[package]] name = "encoding_rs" @@ -694,9 +1001,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", @@ -704,15 +1011,25 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] [[package]] name = "fiat-crypto" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38793c55593b33412e3ae40c2c9781ffaa6f438f6f8c10f24e71846fbd7ae01e" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "fixedbitset" @@ -756,6 +1073,12 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futures-channel" version = "0.3.30" @@ -779,7 +1102,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.66", ] [[package]] @@ -826,17 +1149,20 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] name = "getrandom" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -855,17 +1181,58 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "globset" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata 0.4.6", + "regex-syntax 0.8.3", +] + +[[package]] +name = "globwalk" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" +dependencies = [ + "bitflags", + "ignore", + "walkdir", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + [[package]] name = "h2" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "816ec7294445779408f36fe57bc5b7fc1cf59664059096c65f905c1c61f58069" +checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" dependencies = [ + "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "futures-util", "http", "indexmap", "slab", @@ -885,9 +1252,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "headers" @@ -954,31 +1321,64 @@ dependencies = [ ] [[package]] -name = "http" -version = "1.1.0" +name = "hmac-sha1-compact" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +checksum = "dff9d405ec732fa3fcde87264e54a32a84956a377b3e3107de96e59b798c84a7" + +[[package]] +name = "hmac-sha256" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3688e69b38018fec1557254f64c8dc2cc8ec502890182f395dbb0aa997aa5735" dependencies = [ - "bytes", - "fnv", - "itoa", + "digest", ] [[package]] -name = "http-body" -version = "1.0.0" +name = "hmac-sha512" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +checksum = "e4ce1f4656bae589a3fab938f9f09bf58645b7ed01a2c5f8a3c238e01a4ef78a" dependencies = [ - "bytes", - "http", + "digest", ] [[package]] -name = "http-body-util" -version = "0.1.1" +name = "home" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" dependencies = [ "bytes", "futures-core", @@ -999,6 +1399,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "humansize" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" +dependencies = [ + "libm", +] + [[package]] name = "hyper" version = "1.3.1" @@ -1021,9 +1430,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +checksum = "3d8d52be92d09acc2e01dddb7fde3ad983fc6489c7db4837e605bc3fca4cb63e" dependencies = [ "bytes", "futures-util", @@ -1031,7 +1440,6 @@ dependencies = [ "http-body", "hyper", "pin-project-lite", - "socket2", "tokio", ] @@ -1074,6 +1482,22 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "ignore" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata 0.4.6", + "same-file", + "walkdir", + "winapi-util", +] + [[package]] name = "indexmap" version = "2.2.6" @@ -1093,6 +1517,21 @@ dependencies = [ "generic-array", ] +[[package]] +name = "ipnetwork" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e" +dependencies = [ + "serde", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + [[package]] name = "itertools" version = "0.12.1" @@ -1108,6 +1547,35 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "jni" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.69" @@ -1117,17 +1585,82 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jwt-simple" +version = "0.12.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "094661f5aad510abe2658bff20409e89046b753d9dc2d4007f5c100b6d982ba0" +dependencies = [ + "anyhow", + "binstring", + "blake2b_simd", + "coarsetime", + "ct-codecs", + "ed25519-compact", + "hmac-sha1-compact", + "hmac-sha256", + "hmac-sha512", + "k256", + "p256", + "p384", + "rand", + "serde", + "serde_json", + "superboring", + "thiserror", + "zeroize", +] + +[[package]] +name = "k256" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", + "signature", +] + [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin 0.5.2", +] + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "libloading" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +dependencies = [ + "cfg-if", + "windows-targets 0.52.5", +] + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "libp2p-identity" @@ -1146,9 +1679,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "local-ip-address" @@ -1164,9 +1697,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -1187,6 +1720,18 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "maxminddb" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6087e5d8ea14861bb7c7f573afbc7be3798d3ef0fae87ec4fd9a4de9a127c3c" +dependencies = [ + "ipnetwork", + "log", + "memchr", + "serde", +] + [[package]] name = "media-server-core" version = "0.1.0" @@ -1204,6 +1749,17 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "media-server-gateway" +version = "0.1.0" +dependencies = [ + "atm0s-sdn", + "log", + "media-server-protocol", + "prost", + "serde", +] + [[package]] name = "media-server-protocol" version = "0.1.0" @@ -1212,9 +1768,13 @@ dependencies = [ "convert-enum", "derivative", "derive_more", + "log", "prost", "prost-build", + "quinn", "serde", + "tera", + "tokio", ] [[package]] @@ -1225,13 +1785,24 @@ dependencies = [ "convert-enum", "log", "media-server-core", + "media-server-gateway", "media-server-protocol", + "media-server-secure", "num_enum", "rand", "sans-io-runtime", "transport-webrtc", ] +[[package]] +name = "media-server-secure" +version = "0.1.0" +dependencies = [ + "jwt-simple", + "media-server-protocol", + "serde", +] + [[package]] name = "media-server-utils" version = "0.1.0" @@ -1263,11 +1834,17 @@ dependencies = [ "unicase", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" dependencies = [ "adler", ] @@ -1283,6 +1860,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mirai-annotations" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1" + [[package]] name = "mockall" version = "0.12.1" @@ -1307,24 +1890,23 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.66", ] [[package]] name = "multer" -version = "3.0.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15d522be0a9c3e46fd2632e272d178f56387bdb5c9fbb3a36c649062e9b5219" +checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" dependencies = [ "bytes", "encoding_rs", "futures-util", "http", "httparse", - "log", "memchr", "mime", - "spin", + "spin 0.9.8", "tokio", "version_check", ] @@ -1406,12 +1988,22 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ - "bitflags 2.5.0", + "bitflags", "cfg-if", "cfg_aliases", "libc", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -1424,9 +2016,9 @@ dependencies = [ [[package]] name = "num" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3135b08af27d103b0a51f2ae0f8632117b7b185ccf931445affa8df530576a41" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" dependencies = [ "num-bigint", "num-complex", @@ -1438,20 +2030,36 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" dependencies = [ - "autocfg", "num-integer", "num-traits", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + [[package]] name = "num-complex" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23c6602fda94a57c990fe0df199a035d83576b496aa29f4e634a8ac6004e68a6" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ "num-traits", ] @@ -1473,9 +2081,9 @@ dependencies = [ [[package]] name = "num-iter" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", @@ -1484,11 +2092,10 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ - "autocfg", "num-bigint", "num-integer", "num-traits", @@ -1496,11 +2103,12 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -1531,7 +2139,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.66", ] [[package]] @@ -1561,7 +2169,7 @@ version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ - "bitflags 2.5.0", + "bitflags", "cfg-if", "foreign-types", "libc", @@ -1578,14 +2186,20 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.66", ] +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + [[package]] name = "openssl-src" -version = "300.2.3+3.2.1" +version = "300.3.0+3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cff92b6f71555b61bb9315f7c64da3ca43d87531622120fea0195fc761b4843" +checksum = "eba8804a1c5765b18c4b3f907e6897ebabeedebc9830e1a0046c4a4cf44663e1" dependencies = [ "cc", ] @@ -1609,11 +2223,35 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -1621,15 +2259,49 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.5", +] + +[[package]] +name = "parse-zoneinfo" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" +dependencies = [ + "regex", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pem" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +dependencies = [ + "base64 0.22.1", + "serde", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", ] [[package]] @@ -1638,16 +2310,99 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pest" +version = "2.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26293c9193fbca7b1a3bf9b79dc1e388e927e6cacaa78b4a3ab705a1d3d41459" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ec22af7d3fb470a85dd2ca96b7c577a1eb4ef6f1683a9fe9a8c16e136c04687" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "pest_meta" +version = "2.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a240022f37c361ec1878d646fc5b7d7c4d28d5946e1a80ad5a7a4f4ca0bdcd" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + [[package]] name = "petgraph" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", "indexmap", ] +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project-lite" version = "0.2.14" @@ -1660,6 +2415,27 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.30" @@ -1674,9 +2450,9 @@ checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7" [[package]] name = "poem" -version = "3.0.0" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b735eaaaa6bc7ed2dcbcab1d5373afe1f6d03a37d8695ba3c42101f733a8455" +checksum = "e88b6912ed1e8833d7c22c9c986c517f4518d7d37e3c04566d917c789aaea591" dependencies = [ "bytes", "chrono", @@ -1724,16 +2500,16 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.66", ] [[package]] name = "poem-openapi" -version = "5.0.0" +version = "5.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24375f20342eeffd8e5e4f55483ebcda2f3fcc301a2926b61edc761842528d06" +checksum = "b6445b50be2e26f142d4e554d15773fc1e7510b994083c9625a65eba0d3f4287" dependencies = [ - "base64 0.22.0", + "base64 0.22.1", "bytes", "derive_more", "futures-util", @@ -1748,16 +2524,15 @@ dependencies = [ "serde_json", "serde_urlencoded", "serde_yaml", - "static_assertions", "thiserror", "tokio", ] [[package]] name = "poem-openapi-derive" -version = "5.0.0" +version = "5.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c57d06358cf40434bb0e2f92c1ef578288e9ab8821d1d471a967efa74017b6" +checksum = "e890165626ff447a1ff3d6f2293e6ccacbf7fcbdd4c94086aa548de655735b03" dependencies = [ "darling", "http", @@ -1767,7 +2542,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.60", + "syn 2.0.66", "thiserror", ] @@ -1838,12 +2613,21 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.19" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ac2cf0f2e4f42b49f5ffd07dae8d746508ef7526c13940e5f524012ae6c6550" +checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2", - "syn 2.0.60", + "syn 2.0.66", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", ] [[package]] @@ -1857,18 +2641,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.81" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" +checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6" dependencies = [ "unicode-ident", ] [[package]] name = "prost" -version = "0.12.4" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f5d036824e4761737860779c906171497f6d55681139d8312388f8fe398922" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" dependencies = [ "bytes", "prost-derive", @@ -1876,9 +2660,9 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.12.4" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80b776a1b2dc779f5ee0641f8ade0125bc1298dd41a9a0c16d8bd57b42d222b1" +checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" dependencies = [ "bytes", "heck", @@ -1891,28 +2675,28 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.60", + "syn 2.0.66", "tempfile", ] [[package]] name = "prost-derive" -version = "0.12.4" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19de2de2a00075bf566bee3bd4db014b11587e84184d3f7a791bc17f1a8e9e48" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", "itertools", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.66", ] [[package]] name = "prost-types" -version = "0.12.4" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3235c33eb02c1f1e212abdbe34c78b264b038fb58ca612664343271e36e55ffe" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" dependencies = [ "prost", ] @@ -1936,6 +2720,54 @@ dependencies = [ "serde", ] +[[package]] +name = "quinn" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904e3d3ba178131798c6d9375db2b13b34337d489b089fc5ba0825a2ff1bee73" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e974563a4b1c2206bbc61191ca4da9c22e4308b4c455e8906751cc7828393f08" +dependencies = [ + "bytes", + "rand", + "ring", + "rustc-hash", + "rustls", + "rustls-platform-verifier", + "slab", + "thiserror", + "tinyvec", + "tracing", +] + +[[package]] +name = "quinn-udp" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4f0def2590301f4f667db5a77f9694fb004f82796dc1a8b1508fafa3d0e8b72" +dependencies = [ + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.52.0", +] + [[package]] name = "quote" version = "1.0.36" @@ -1975,13 +2807,26 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rcgen" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54077e1872c46788540de1ea3d7f4ccb1983d12f9aa909b234468676c1a36779" +dependencies = [ + "pem", + "ring", + "rustls-pki-types", + "time", + "yasna", +] + [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" dependencies = [ - "bitflags 1.3.2", + "bitflags", ] [[package]] @@ -2028,6 +2873,16 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "rfc7239" version = "0.1.1" @@ -2037,11 +2892,53 @@ dependencies = [ "uncased", ] +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rsa" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "sha2", + "signature", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc_version" @@ -2058,23 +2955,116 @@ version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.5.0", + "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys 0.52.0", ] +[[package]] +name = "rustls" +version = "0.23.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79adb16721f56eb2d843e67676896a61ce7a0fa622dc18d3e372477a029d2740" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64 0.22.1", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" + +[[package]] +name = "rustls-platform-verifier" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5f0d26fa1ce3c790f9590868f0109289a044acb954525f933e2aa3b871c157d" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-roots", + "winapi", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84e217e7fdc8466b5b35d30f8c0a30febd29173df4a3a0c2115d306b9c4117ad" + +[[package]] +name = "rustls-webpki" +version = "0.102.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "ryu" -version = "1.0.17" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "same-file" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] [[package]] name = "sans-io-runtime" version = "0.1.0" -source = "git+https://github.com/giangndm/sans-io-runtime.git?rev=e7ef60e0eef35c532c8544c472514ae831a8908f#e7ef60e0eef35c532c8544c472514ae831a8908f" +source = "git+https://github.com/giangndm/sans-io-runtime.git?rev=c781cef12b2a435b5e31a6ede69d301a23719452#c781cef12b2a435b5e31a6ede69d301a23719452" dependencies = [ "convert-enum", "derive_more", @@ -2086,6 +3076,15 @@ dependencies = [ "socket2", ] +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -2107,37 +3106,75 @@ dependencies = [ "thiserror", ] +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "num-bigint", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.200" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.200" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.66", ] [[package]] name = "serde_json" -version = "1.0.116" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ "itoa", "ryu", @@ -2194,9 +3231,9 @@ dependencies = [ [[package]] name = "sha1-asm" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ba6947745e7f86be3b8af00b7355857085dbdf8901393c89514510eb61f4e21" +checksum = "286acebaf8b67c1130aedffad26f594eff0c1292389158135327d2e23aed582b" dependencies = [ "cc", ] @@ -2221,6 +3258,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -2230,6 +3273,22 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "slab" version = "0.4.9" @@ -2239,6 +3298,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "slug" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bd94acec9c8da640005f8e135a39fc0372e74535e6b368b7a04b875f784c8c4" +dependencies = [ + "deunicode", + "wasm-bindgen", +] + [[package]] name = "smallmap" version = "1.4.2" @@ -2256,9 +3325,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", @@ -2270,12 +3339,28 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6734caf0b6f51addd5eeacca12fb39b2c6c14e8d4f3ac42f3a78955c0467458" +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -2307,12 +3392,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - [[package]] name = "strsim" version = "0.11.1" @@ -2325,6 +3404,19 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +[[package]] +name = "superboring" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbde97f499e51ef384f585dc8f8fb6a9c3a71b274b8d12469b516758e6540607" +dependencies = [ + "getrandom", + "hmac-sha256", + "hmac-sha512", + "rand", + "rsa", +] + [[package]] name = "syn" version = "1.0.109" @@ -2338,9 +3430,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.60" +version = "2.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" dependencies = [ "proc-macro2", "quote", @@ -2368,6 +3460,28 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "tera" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab9d851b45e865f178319da0abdbfe6acbc4328759ff18dafc3a41c16b4cd2ee" +dependencies = [ + "chrono", + "chrono-tz", + "globwalk", + "humansize", + "lazy_static", + "percent-encoding", + "pest", + "pest_derive", + "rand", + "regex", + "serde", + "serde_json", + "slug", + "unic-segment", +] + [[package]] name = "termtree" version = "0.4.1" @@ -2376,22 +3490,22 @@ checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" [[package]] name = "thiserror" -version = "1.0.59" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.59" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.66", ] [[package]] @@ -2477,7 +3591,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.66", ] [[package]] @@ -2493,23 +3607,22 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" [[package]] name = "toml_edit" @@ -2528,6 +3641,7 @@ version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -2541,7 +3655,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.66", ] [[package]] @@ -2591,6 +3705,7 @@ dependencies = [ "log", "media-server-core", "media-server-protocol", + "media-server-secure", "media-server-utils", "num_enum", "prost", @@ -2605,6 +3720,12 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + [[package]] name = "uncased" version = "0.9.10" @@ -2614,6 +3735,56 @@ dependencies = [ "version_check", ] +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-segment" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23" +dependencies = [ + "unic-ucd-segment", +] + +[[package]] +name = "unic-ucd-segment" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + [[package]] name = "unicase" version = "2.7.0" @@ -2666,6 +3837,12 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6889a77d49f1f013504cec6bf97a2c730394adedaeb1deb5ea08949a50541105" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.0" @@ -2701,12 +3878,31 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasix" +version = "0.12.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1fbb4ef9bbca0c1170e0b00dd28abc9e3b68669821600cad1caaed606583c6d" +dependencies = [ + "wasi", +] + [[package]] name = "wasm-bindgen" version = "0.2.92" @@ -2728,7 +3924,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.66", "wasm-bindgen-shared", ] @@ -2750,7 +3946,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.66", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2761,11 +3957,32 @@ version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +[[package]] +name = "webpki-roots" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + [[package]] name = "wildmatch" -version = "2.3.3" +version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "939e59c1bc731542357fdaad98b209ef78c8743d652bb61439d16b16a79eb025" +checksum = "3928939971918220fed093266b809d1ee4ec6c1a2d72692ff6876898f3b16c19" [[package]] name = "winapi" @@ -2783,6 +4000,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -2958,11 +4184,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" dependencies = [ "zeroize_derive", ] @@ -2975,5 +4210,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.66", ] diff --git a/Cargo.toml b/Cargo.toml index 4ca4729e..f236ace3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,11 +6,13 @@ members = [ "packages/media_core", "packages/media_runner", "packages/transport_webrtc", + "packages/media_secure", + "packages/media_gateway", ] [workspace.dependencies] -sans-io-runtime = { git = "https://github.com/giangndm/sans-io-runtime.git", rev = "e7ef60e0eef35c532c8544c472514ae831a8908f" } -atm0s-sdn = { git = "https://github.com/giangndm/8xFF-decentralized-sdn.git", rev = "9200a1615def0ddffce8338b34afd24421f24269" } +sans-io-runtime = { git = "https://github.com/giangndm/sans-io-runtime.git", rev = "c781cef12b2a435b5e31a6ede69d301a23719452" } +atm0s-sdn = { git = "https://github.com/giangndm/8xFF-decentralized-sdn.git", rev = "e3456db45912bdd461755088a5dde5e004b0f17a" } tracing-subscriber = { version = "0.3", features = ["env-filter", "std"] } convert-enum = "0.1" num_enum = "0.7" diff --git a/bin/Cargo.toml b/bin/Cargo.toml index d8978ac0..9efc801c 100644 --- a/bin/Cargo.toml +++ b/bin/Cargo.toml @@ -16,6 +16,24 @@ poem-openapi = { version = "5.0", features = ["swagger-ui"] } tokio = { version = "1.37", features = ["full"] } sans-io-runtime = { workspace = true } atm0s-sdn = { workspace = true } -media-server-protocol = { path = "../packages/protocol" } -media-server-runner = { path = "../packages/media_runner" } +media-server-protocol = { path = "../packages/protocol", features = ["quinn-rpc"] } +media-server-secure = { path = "../packages/media_secure" } +media-server-runner = { path = "../packages/media_runner", optional = true } +media-server-gateway = { path = "../packages/media_gateway", optional = true } local-ip-address = "0.6" +serde = { version = "1.0", features = ["derive"] } +quinn = { version = "0.11", optional = true } +rustls = { version = "0.23", optional = true } +convert-enum = { workspace = true } +num_enum = { workspace = true } +derive_more = { workspace = true } +rcgen = { version = "0.13", optional = true } +maxminddb = "0.24.0" + +[features] +default = ["gateway", "media", "connector", "cert_utils"] +gateway = ["media-server-gateway", "quinn_vnet"] +media = ["media-server-runner", "quinn_vnet"] +connector = ["quinn_vnet"] +cert_utils = ["rcgen", "rustls"] +quinn_vnet = ["rustls", "quinn"] diff --git a/bin/certs/cluster.cert b/bin/certs/cluster.cert new file mode 100644 index 00000000..4469aa6b Binary files /dev/null and b/bin/certs/cluster.cert differ diff --git a/bin/certs/cluster.key b/bin/certs/cluster.key new file mode 100644 index 00000000..20085fd1 Binary files /dev/null and b/bin/certs/cluster.key differ diff --git a/bin/gate_z0_n1.sh b/bin/gate_z0_n1.sh new file mode 100644 index 00000000..4e3dce24 --- /dev/null +++ b/bin/gate_z0_n1.sh @@ -0,0 +1,11 @@ +RUST_LOG=atm0s_sdn_network::features::socket=debug,info \ +RUST_BACKTRACE=1 \ +cargo run -- \ + --http-port 3000 \ + --node-id 0 \ + --sdn-port 10000 \ + --sdn-zone 0 \ + gateway \ + --lat 10 \ + --lon 20 \ + --geo-db "../maxminddb-data/GeoLite2-City.mmdb" diff --git a/bin/gate_z256_n1.sh b/bin/gate_z256_n1.sh new file mode 100644 index 00000000..5091df04 --- /dev/null +++ b/bin/gate_z256_n1.sh @@ -0,0 +1,12 @@ +RUST_LOG=atm0s_sdn_network::features::socket=debug,info \ +RUST_BACKTRACE=1 \ +cargo run -- \ + --http-port 4000 \ + --node-id 256 \ + --sdn-zone 256 \ + --sdn-port 11000 \ + --seeds 0@/ip4/127.0.0.1/udp/10000 \ + gateway \ + --lat 20 \ + --lon 30 \ + --geo-db "../maxminddb-data/GeoLite2-City.mmdb" diff --git a/bin/media_z0_n1.sh b/bin/media_z0_n1.sh new file mode 100644 index 00000000..93137d8c --- /dev/null +++ b/bin/media_z0_n1.sh @@ -0,0 +1,11 @@ +RUST_LOG=atm0s_sdn_network::features::socket=debug,info \ +RUST_BACKTRACE=1 \ +cargo run -- \ + --http-port 3001 \ + --node-id 1 \ + --sdn-port 10001 \ + --sdn-zone 0 \ + --seeds 0@/ip4/127.0.0.1/udp/10000 \ + media \ + --allow-private-ip \ + --enable-token-api diff --git a/bin/media_z0_n2.sh b/bin/media_z0_n2.sh new file mode 100644 index 00000000..925da6f9 --- /dev/null +++ b/bin/media_z0_n2.sh @@ -0,0 +1,11 @@ +RUST_LOG=info \ +RUST_BACKTRACE=1 \ +cargo run -- \ + --http-port 3002 \ + --node-id 2 \ + --sdn-port 10002 \ + --sdn-zone 0 \ + --seeds 0@/ip4/127.0.0.1/udp/10000 \ + media \ + --allow-private-ip \ + --enable-token-api diff --git a/bin/media_z256_n1.sh b/bin/media_z256_n1.sh new file mode 100644 index 00000000..4aecbff0 --- /dev/null +++ b/bin/media_z256_n1.sh @@ -0,0 +1,11 @@ +RUST_LOG=atm0s_sdn_network::features::socket=debug,info \ +RUST_BACKTRACE=1 \ +cargo run -- \ + --http-port 4001 \ + --node-id 257 \ + --sdn-port 11001 \ + --sdn-zone 256 \ + --seeds 256@/ip4/127.0.0.1/udp/11000 \ + media \ + --allow-private-ip \ + --enable-token-api diff --git a/bin/media_z256_n2.sh b/bin/media_z256_n2.sh new file mode 100644 index 00000000..84a6a474 --- /dev/null +++ b/bin/media_z256_n2.sh @@ -0,0 +1,11 @@ +RUST_LOG=info \ +RUST_BACKTRACE=1 \ +cargo run -- \ + --http-port 4002 \ + --node-id 258 \ + --sdn-port 11002 \ + --sdn-zone 256 \ + --seeds 256@/ip4/127.0.0.1/udp/11000 \ + media \ + --allow-private-ip \ + --enable-token-api diff --git a/bin/node1.sh b/bin/node1.sh deleted file mode 100644 index 452b9939..00000000 --- a/bin/node1.sh +++ /dev/null @@ -1 +0,0 @@ -RUST_LOG=info RUST_BACKTRACE=1 cargo run -- --http-port 3001 --node-id 0 --sdn-port 10001 media --allow-private-ip diff --git a/bin/node2.sh b/bin/node2.sh deleted file mode 100644 index 9d3992ee..00000000 --- a/bin/node2.sh +++ /dev/null @@ -1 +0,0 @@ -RUST_LOG=info RUST_BACKTRACE=1 cargo run -- --http-port 3002 --node-id 1 --sdn-port 10002 --seeds 0@/ip4/127.0.0.1/udp/10001 media --allow-private-ip diff --git a/bin/src/errors.rs b/bin/src/errors.rs new file mode 100644 index 00000000..38eacb73 --- /dev/null +++ b/bin/src/errors.rs @@ -0,0 +1,6 @@ +#[derive(num_enum::TryFromPrimitive, num_enum::IntoPrimitive, derive_more::Display)] +#[repr(u32)] +pub enum MediaServerError { + GatewayRpcError = 0x00020001, + InvalidConnId = 0x00020002, +} diff --git a/bin/src/http.rs b/bin/src/http.rs index 52397372..07de7460 100644 --- a/bin/src/http.rs +++ b/bin/src/http.rs @@ -1,7 +1,9 @@ use std::net::SocketAddr; +use std::sync::Arc; use media_server_protocol::endpoint::ClusterConnId; use media_server_protocol::transport::{RpcReq, RpcRes}; +use media_server_secure::{MediaEdgeSecure, MediaGatewaySecure}; use poem::endpoint::StaticFilesEndpoint; use poem::{listener::TcpListener, middleware::Cors, EndpointExt, Route, Server}; use poem_openapi::types::{ToJSON, Type}; @@ -9,6 +11,11 @@ use poem_openapi::OpenApiService; use poem_openapi::{types::ParseFromJSON, Object}; use tokio::sync::mpsc::Sender; +mod api_connector; +mod api_media; +mod api_token; +mod utils; + #[derive(Debug, Default, Object)] pub struct Response { pub status: bool, @@ -34,36 +41,58 @@ impl Rpc { } } -mod api_connector; -mod api_media; -mod utils; - -pub async fn run_gateway_http_server(sender: Sender, RpcRes>>) -> Result<(), Box> { - let api_service: OpenApiService<_, ()> = OpenApiService::new(api_media::MediaApis, "Media Gateway APIs", env!("CARGO_PKG_VERSION")).server("/"); - let ui = api_service.swagger_ui(); - let spec = api_service.spec(); +pub async fn run_gateway_http_server( + port: u16, + sender: Sender, RpcRes>>, + edge_secure: Arc, + gateway_secure: Arc, +) -> Result<(), Box> { + let token_service: OpenApiService<_, ()> = OpenApiService::new(api_token::TokenApis::::new(), "App APIs", env!("CARGO_PKG_VERSION")).server("/token/"); + let token_ui = token_service.swagger_ui(); + let token_spec = token_service.spec(); + let media_service: OpenApiService<_, ()> = OpenApiService::new(api_media::MediaApis::::new(), "Media Gateway APIs", env!("CARGO_PKG_VERSION")).server("/media/"); + let media_ui = media_service.swagger_ui(); + let media_spec = media_service.spec(); let route = Route::new() - .nest("/", api_service) - .nest("/ui", ui) - .at("/spec", poem::endpoint::make_sync(move |_| spec.clone())) - .with(Cors::new()) - .data(api_media::MediaServerCtx { sender }); + .nest("/samples", StaticFilesEndpoint::new("./public").index_file("index.html")) + .nest("/token/", token_service.data(api_token::TokenServerCtx { secure: gateway_secure })) + .nest("/token/ui", token_ui) + .at("/token/spec", poem::endpoint::make_sync(move |_| token_spec.clone())) + .nest("/", media_service.data(api_media::MediaServerCtx { sender, secure: edge_secure })) + .nest("/ui", media_ui) + .at("/spec", poem::endpoint::make_sync(move |_| media_spec.clone())) + .with(Cors::new()); - Server::new(TcpListener::bind("0.0.0.0:3000")).run(route).await?; + Server::new(TcpListener::bind(SocketAddr::new([0, 0, 0, 0].into(), port))).run(route).await?; Ok(()) } -pub async fn run_media_http_server(port: u16, sender: Sender, RpcRes>>) -> Result<(), Box> { - let api_service: OpenApiService<_, ()> = OpenApiService::new(api_media::MediaApis, "Media Server APIs", env!("CARGO_PKG_VERSION")).server("/"); - let ui = api_service.swagger_ui(); - let spec = api_service.spec(); - let route = Route::new() - .nest("/", api_service) +pub async fn run_media_http_server( + port: u16, + sender: Sender, RpcRes>>, + edge_secure: Arc, + gateway_secure: Option>, +) -> Result<(), Box> { + let mut route = Route::new(); + + if let Some(gateway_secure) = gateway_secure { + let token_service: OpenApiService<_, ()> = OpenApiService::new(api_token::TokenApis::::new(), "App APIs", env!("CARGO_PKG_VERSION")).server("/token/"); + let token_ui = token_service.swagger_ui(); + let token_spec = token_service.spec(); + route = route + .nest("/token/", token_service.data(api_token::TokenServerCtx { secure: gateway_secure })) + .nest("/token/ui", token_ui) + .at("/token/spec", poem::endpoint::make_sync(move |_| token_spec.clone())); + } + let media_service: OpenApiService<_, ()> = OpenApiService::new(api_media::MediaApis::::new(), "Media Gateway APIs", env!("CARGO_PKG_VERSION")).server("/media/"); + let media_ui = media_service.swagger_ui(); + let media_spec = media_service.spec(); + let route = route .nest("/samples", StaticFilesEndpoint::new("./public").index_file("index.html")) - .nest("/ui", ui) - .at("/spec", poem::endpoint::make_sync(move |_| spec.clone())) - .with(Cors::new()) - .data(api_media::MediaServerCtx { sender }); + .nest("/", media_service.data(api_media::MediaServerCtx { sender, secure: edge_secure })) + .nest("/ui", media_ui) + .at("/spec", poem::endpoint::make_sync(move |_| media_spec.clone())) + .with(Cors::new()); Server::new(TcpListener::bind(SocketAddr::new([0, 0, 0, 0].into(), port))).run(route).await?; Ok(()) diff --git a/bin/src/http/api_media.rs b/bin/src/http/api_media.rs index c42f48b8..5744f7ee 100644 --- a/bin/src/http/api_media.rs +++ b/bin/src/http/api_media.rs @@ -1,6 +1,9 @@ +use std::{marker::PhantomData, sync::Arc}; + use media_server_protocol::{ endpoint::ClusterConnId, protobuf::gateway::{ConnectRequest, ConnectResponse, RemoteIceRequest, RemoteIceResponse}, + tokens::{WebrtcToken, WhepToken, WhipToken}, transport::{ webrtc, whep::{self, WhepConnectReq, WhepDeleteReq, WhepRemoteIceReq}, @@ -8,50 +11,64 @@ use media_server_protocol::{ RpcReq, RpcRes, RpcResult, }, }; +use media_server_secure::MediaEdgeSecure; use poem::{http::StatusCode, web::Data, Result}; use poem_openapi::{ - auth::Bearer, param::Path, payload::{Json, PlainText, Response as HttpResponse}, - OpenApi, SecurityScheme, + OpenApi, }; +use rand::random; use super::{ - utils::{ApplicationSdp, ApplicationSdpPatch, CustomHttpResponse, Protobuf, RemoteIpAddr, UserAgent}, + utils::{ApplicationSdp, ApplicationSdpPatch, CustomHttpResponse, Protobuf, RemoteIpAddr, TokenAuthorization, UserAgent}, Rpc, }; -#[derive(SecurityScheme)] -#[oai(rename = "Token Authorization", ty = "bearer", key_in = "header", key_name = "Authorization")] -pub struct TokenAuthorization(pub Bearer); - -#[derive(Clone)] -pub struct MediaServerCtx { +pub struct MediaServerCtx { pub(crate) sender: tokio::sync::mpsc::Sender, RpcRes>>, + pub(crate) secure: Arc, } -pub struct MediaApis; +impl Clone for MediaServerCtx { + fn clone(&self) -> Self { + Self { + sender: self.sender.clone(), + secure: self.secure.clone(), + } + } +} + +pub struct MediaApis(PhantomData); + +impl MediaApis { + pub fn new() -> Self { + Self(Default::default()) + } +} #[OpenApi] -impl MediaApis { +impl MediaApis { /// connect whip endpoint #[oai(path = "/whip/endpoint", method = "post")] async fn whip_create( &self, - Data(data): Data<&MediaServerCtx>, + Data(ctx): Data<&MediaServerCtx>, UserAgent(user_agent): UserAgent, RemoteIpAddr(ip_addr): RemoteIpAddr, TokenAuthorization(token): TokenAuthorization, body: ApplicationSdp, ) -> Result>> { - log::info!("[MediaAPIs] create whip endpoint with token {}, ip {}, user_agent {}", token.token, ip_addr, user_agent); + let token = ctx.secure.decode_obj::("whip", &token.token).ok_or(poem::Error::from_status(StatusCode::BAD_REQUEST))?; + log::info!("[MediaAPIs] create whip endpoint with token {:?}, ip {}, user_agent {}", token, ip_addr, user_agent); let (req, rx) = Rpc::new(RpcReq::Whip(whip::RpcReq::Connect(WhipConnectReq { ip: ip_addr, sdp: body.0, - token: token.token, + room: token.room.into(), + peer: token.peer.into(), user_agent, }))); - data.sender.send(req).await.map_err(|_e| poem::Error::from_status(StatusCode::INTERNAL_SERVER_ERROR))?; + ctx.sender.send(req).await.map_err(|_e| poem::Error::from_status(StatusCode::INTERNAL_SERVER_ERROR))?; let res = rx.await.map_err(|_e| poem::Error::from_status(StatusCode::INTERNAL_SERVER_ERROR))?; match res { RpcRes::Whip(whip::RpcRes::Connect(res)) => match res { @@ -74,11 +91,11 @@ impl MediaApis { /// patch whip conn for trickle-ice #[oai(path = "/whip/conn/:conn_id", method = "patch")] - async fn conn_whip_patch(&self, Data(data): Data<&MediaServerCtx>, conn_id: Path, body: ApplicationSdpPatch) -> Result>> { + async fn conn_whip_patch(&self, Data(ctx): Data<&MediaServerCtx>, conn_id: Path, body: ApplicationSdpPatch) -> Result>> { let conn_id = conn_id.0.parse().map_err(|_e| poem::Error::from_status(StatusCode::BAD_REQUEST))?; log::info!("[MediaAPIs] patch whip endpoint with sdp {}", body.0); let (req, rx) = Rpc::new(RpcReq::Whip(whip::RpcReq::RemoteIce(WhipRemoteIceReq { conn_id, ice: body.0 }))); - data.sender.send(req).await.map_err(|_e| poem::Error::from_status(StatusCode::INTERNAL_SERVER_ERROR))?; + ctx.sender.send(req).await.map_err(|_e| poem::Error::from_status(StatusCode::INTERNAL_SERVER_ERROR))?; let res = rx.await.map_err(|_e| poem::Error::from_status(StatusCode::INTERNAL_SERVER_ERROR))?; //TODO process with ICE restart match res { @@ -98,18 +115,18 @@ impl MediaApis { /// post whip conn for action #[oai(path = "/api/whip/conn/:conn_id", method = "post")] - async fn conn_whip_post(&self, _ctx: Data<&MediaServerCtx>, _conn_id: Path, _body: Json) -> Result> { + async fn conn_whip_post(&self, _ctx: Data<&MediaServerCtx>, _conn_id: Path, _body: Json) -> Result> { // let conn_id = conn_id.0.parse().map_err(|_e| poem::Error::from_status(StatusCode::BAD_REQUEST))?; Err(poem::Error::from_string("Not supported", StatusCode::BAD_REQUEST)) } /// delete whip conn #[oai(path = "/whip/conn/:conn_id", method = "delete")] - async fn conn_whip_delete(&self, Data(data): Data<&MediaServerCtx>, conn_id: Path) -> Result> { + async fn conn_whip_delete(&self, Data(ctx): Data<&MediaServerCtx>, conn_id: Path) -> Result> { let conn_id = conn_id.0.parse().map_err(|_e| poem::Error::from_status(StatusCode::BAD_REQUEST))?; log::info!("[MediaAPIs] close whip endpoint conn {}", conn_id); let (req, rx) = Rpc::new(RpcReq::Whip(whip::RpcReq::Delete(WhipDeleteReq { conn_id }))); - data.sender.send(req).await.map_err(|_e| poem::Error::from_status(StatusCode::INTERNAL_SERVER_ERROR))?; + ctx.sender.send(req).await.map_err(|_e| poem::Error::from_status(StatusCode::INTERNAL_SERVER_ERROR))?; let res = rx.await.map_err(|_e| poem::Error::from_status(StatusCode::INTERNAL_SERVER_ERROR))?; match res { RpcRes::Whip(whip::RpcRes::Delete(res)) => match res { @@ -130,20 +147,22 @@ impl MediaApis { #[oai(path = "/whep/endpoint", method = "post")] async fn whep_create( &self, - Data(data): Data<&MediaServerCtx>, + Data(ctx): Data<&MediaServerCtx>, UserAgent(user_agent): UserAgent, RemoteIpAddr(ip_addr): RemoteIpAddr, TokenAuthorization(token): TokenAuthorization, body: ApplicationSdp, ) -> Result>> { - log::info!("[MediaAPIs] create whep endpoint with token {}, ip {}, user_agent {}", token.token, ip_addr, user_agent); + let token = ctx.secure.decode_obj::("whep", &token.token).ok_or(poem::Error::from_status(StatusCode::BAD_REQUEST))?; + log::info!("[MediaAPIs] create whep endpoint with token {:?}, ip {}, user_agent {}", token, ip_addr, user_agent); let (req, rx) = Rpc::new(RpcReq::Whep(whep::RpcReq::Connect(WhepConnectReq { ip: ip_addr, sdp: body.0, - token: token.token, + room: token.room.into(), + peer: token.peer.unwrap_or_else(|| format!("whep-{}", (random::()))).into(), user_agent, }))); - data.sender.send(req).await.map_err(|_e| poem::Error::from_status(StatusCode::INTERNAL_SERVER_ERROR))?; + ctx.sender.send(req).await.map_err(|_e| poem::Error::from_status(StatusCode::INTERNAL_SERVER_ERROR))?; let res = rx.await.map_err(|_e| poem::Error::from_status(StatusCode::INTERNAL_SERVER_ERROR))?; match res { RpcRes::Whep(whep::RpcRes::Connect(res)) => match res { @@ -166,11 +185,11 @@ impl MediaApis { /// patch whep conn for trickle-ice #[oai(path = "/whep/conn/:conn_id", method = "patch")] - async fn conn_whep_patch(&self, Data(data): Data<&MediaServerCtx>, conn_id: Path, body: ApplicationSdpPatch) -> Result>> { + async fn conn_whep_patch(&self, Data(ctx): Data<&MediaServerCtx>, conn_id: Path, body: ApplicationSdpPatch) -> Result>> { let conn_id = conn_id.0.parse().map_err(|_e| poem::Error::from_status(StatusCode::BAD_REQUEST))?; log::info!("[MediaAPIs] patch whep endpoint with sdp {}", body.0); let (req, rx) = Rpc::new(RpcReq::Whep(whep::RpcReq::RemoteIce(WhepRemoteIceReq { conn_id, ice: body.0 }))); - data.sender.send(req).await.map_err(|_e| poem::Error::from_status(StatusCode::INTERNAL_SERVER_ERROR))?; + ctx.sender.send(req).await.map_err(|_e| poem::Error::from_status(StatusCode::INTERNAL_SERVER_ERROR))?; let res = rx.await.map_err(|_e| poem::Error::from_status(StatusCode::INTERNAL_SERVER_ERROR))?; //TODO process with ICE restart match res { @@ -190,18 +209,18 @@ impl MediaApis { /// post whep conn for action #[oai(path = "/api/whep/conn/:conn_id", method = "post")] - async fn conn_whep_post(&self, _ctx: Data<&MediaServerCtx>, _conn_id: Path, _body: Json) -> Result> { + async fn conn_whep_post(&self, _ctx: Data<&MediaServerCtx>, _conn_id: Path, _body: Json) -> Result> { // let conn_id = conn_id.0.parse().map_err(|_e| poem::Error::from_status(StatusCode::BAD_REQUEST))?; Err(poem::Error::from_string("Not supported", StatusCode::BAD_REQUEST)) } /// delete whep conn #[oai(path = "/whep/conn/:conn_id", method = "delete")] - async fn conn_whep_delete(&self, Data(data): Data<&MediaServerCtx>, conn_id: Path) -> Result> { + async fn conn_whep_delete(&self, Data(ctx): Data<&MediaServerCtx>, conn_id: Path) -> Result> { let conn_id = conn_id.0.parse().map_err(|_e| poem::Error::from_status(StatusCode::BAD_REQUEST))?; log::info!("[MediaAPIs] close whep endpoint conn {}", conn_id); let (req, rx) = Rpc::new(RpcReq::Whep(whep::RpcReq::Delete(WhepDeleteReq { conn_id }))); - data.sender.send(req).await.map_err(|_e| poem::Error::from_status(StatusCode::INTERNAL_SERVER_ERROR))?; + ctx.sender.send(req).await.map_err(|_e| poem::Error::from_status(StatusCode::INTERNAL_SERVER_ERROR))?; let res = rx.await.map_err(|_e| poem::Error::from_status(StatusCode::INTERNAL_SERVER_ERROR))?; match res { RpcRes::Whep(whep::RpcRes::Delete(res)) => match res { @@ -222,15 +241,25 @@ impl MediaApis { #[oai(path = "/webrtc/connect", method = "post")] async fn webrtc_connect( &self, - Data(data): Data<&MediaServerCtx>, + Data(ctx): Data<&MediaServerCtx>, UserAgent(user_agent): UserAgent, RemoteIpAddr(ip_addr): RemoteIpAddr, TokenAuthorization(token): TokenAuthorization, connect: Protobuf, ) -> Result>> { - log::info!("[MediaAPIs] create webrtc with token {}, ip {}, user_agent {}, request {:?}", token.token, ip_addr, user_agent, connect); - let (req, rx) = Rpc::new(RpcReq::Webrtc(webrtc::RpcReq::Connect(ip_addr, token.token, user_agent, connect.0))); - data.sender.send(req).await.map_err(|_e| poem::Error::from_status(StatusCode::INTERNAL_SERVER_ERROR))?; + let token = ctx.secure.decode_obj::("webrtc", &token.token).ok_or(poem::Error::from_status(StatusCode::BAD_REQUEST))?; + log::info!("[MediaAPIs] create webrtc with token {:?}, ip {}, user_agent {}, request {:?}", token, ip_addr, user_agent, connect); + if let Some(join) = &connect.join { + if token.room != Some(join.room.clone()) { + return Err(poem::Error::from_string("Wrong room".to_string(), StatusCode::FORBIDDEN)); + } + + if token.peer != Some(join.peer.clone()) { + return Err(poem::Error::from_string("Wrong peer".to_string(), StatusCode::FORBIDDEN)); + } + } + let (req, rx) = Rpc::new(RpcReq::Webrtc(webrtc::RpcReq::Connect(ip_addr, user_agent, connect.0))); + ctx.sender.send(req).await.map_err(|_e| poem::Error::from_status(StatusCode::INTERNAL_SERVER_ERROR))?; let res = rx.await.map_err(|_e| poem::Error::from_status(StatusCode::INTERNAL_SERVER_ERROR))?; match res { RpcRes::Webrtc(webrtc::RpcRes::Connect(res)) => match res { @@ -253,11 +282,11 @@ impl MediaApis { /// patch webrtc conn for trickle-ice #[oai(path = "/webrtc/:conn_id/ice-candidate", method = "post")] - async fn webrtc_ice_candidate(&self, Data(data): Data<&MediaServerCtx>, conn_id: Path, body: Protobuf) -> Result>> { + async fn webrtc_ice_candidate(&self, Data(ctx): Data<&MediaServerCtx>, conn_id: Path, body: Protobuf) -> Result>> { let conn_id = conn_id.0.parse().map_err(|_e| poem::Error::from_status(StatusCode::BAD_REQUEST))?; log::info!("[MediaAPIs] on remote ice from webrtc conn {conn_id} with ice candidate {:?}", body.0); let (req, rx) = Rpc::new(RpcReq::Webrtc(webrtc::RpcReq::RemoteIce(conn_id, body.0))); - data.sender.send(req).await.map_err(|_e| poem::Error::from_status(StatusCode::INTERNAL_SERVER_ERROR))?; + ctx.sender.send(req).await.map_err(|_e| poem::Error::from_status(StatusCode::INTERNAL_SERVER_ERROR))?; let res = rx.await.map_err(|_e| poem::Error::from_status(StatusCode::INTERNAL_SERVER_ERROR))?; //TODO process with ICE restart match res { @@ -279,7 +308,7 @@ impl MediaApis { #[oai(path = "/webrtc/:conn_id/restart-ice", method = "post")] async fn webrtc_restart_ice( &self, - Data(data): Data<&MediaServerCtx>, + Data(ctx): Data<&MediaServerCtx>, UserAgent(user_agent): UserAgent, RemoteIpAddr(ip_addr): RemoteIpAddr, TokenAuthorization(token): TokenAuthorization, @@ -287,16 +316,19 @@ impl MediaApis { connect: Protobuf, ) -> Result>> { let conn_id2 = conn_id.0.parse().map_err(|_e| poem::Error::from_status(StatusCode::BAD_REQUEST))?; - log::info!( - "[MediaAPIs] restart_ice webrtc with token {}, ip {}, user_agent {}, conn {}, request {:?}", - token.token, - ip_addr, - user_agent, - conn_id.0, - connect - ); - let (req, rx) = Rpc::new(RpcReq::Webrtc(webrtc::RpcReq::RestartIce(conn_id2, ip_addr, token.token, user_agent, connect.0))); - data.sender.send(req).await.map_err(|_e| poem::Error::from_status(StatusCode::INTERNAL_SERVER_ERROR))?; + let token = ctx.secure.decode_obj::("webrtc", &token.token).ok_or(poem::Error::from_status(StatusCode::BAD_REQUEST))?; + if let Some(join) = &connect.join { + if token.room != Some(join.room.clone()) { + return Err(poem::Error::from_string("Wrong room".to_string(), StatusCode::FORBIDDEN)); + } + + if token.peer != Some(join.peer.clone()) { + return Err(poem::Error::from_string("Wrong peer".to_string(), StatusCode::FORBIDDEN)); + } + } + log::info!("[MediaAPIs] restart_ice webrtc, ip {}, user_agent {}, conn {}, request {:?}", ip_addr, user_agent, conn_id.0, connect); + let (req, rx) = Rpc::new(RpcReq::Webrtc(webrtc::RpcReq::RestartIce(conn_id2, ip_addr, user_agent, connect.0))); + ctx.sender.send(req).await.map_err(|_e| poem::Error::from_status(StatusCode::INTERNAL_SERVER_ERROR))?; let res = rx.await.map_err(|_e| poem::Error::from_status(StatusCode::INTERNAL_SERVER_ERROR))?; match res { RpcRes::Webrtc(webrtc::RpcRes::RestartIce(res)) => match res { diff --git a/bin/src/http/api_token.rs b/bin/src/http/api_token.rs new file mode 100644 index 00000000..04610879 --- /dev/null +++ b/bin/src/http/api_token.rs @@ -0,0 +1,129 @@ +use std::{marker::PhantomData, sync::Arc}; + +use super::{utils::TokenAuthorization, Response}; +use media_server_protocol::tokens::{WebrtcToken, WhepToken, WhipToken}; +use media_server_secure::MediaGatewaySecure; +use poem::{web::Data, Result}; +use poem_openapi::{payload::Json, OpenApi}; + +pub struct TokenServerCtx +where + S: MediaGatewaySecure + Send + Sync, +{ + pub(crate) secure: Arc, +} + +impl Clone for TokenServerCtx { + fn clone(&self) -> Self { + Self { secure: self.secure.clone() } + } +} + +#[derive(poem_openapi::Object)] +struct WhipTokenReq { + room: String, + peer: String, + ttl: u64, +} + +#[derive(poem_openapi::Object)] +struct WhipTokenRes { + token: String, +} + +#[derive(poem_openapi::Object)] +struct WhepTokenReq { + room: String, + peer: Option, + ttl: u64, +} + +#[derive(poem_openapi::Object)] +struct WhepTokenRes { + token: String, +} + +#[derive(poem_openapi::Object)] +struct WebrtcTokenReq { + room: Option, + peer: Option, + ttl: u64, +} + +#[derive(poem_openapi::Object)] +struct WebrtcTokenRes { + token: String, +} + +pub struct TokenApis(PhantomData); + +impl TokenApis { + pub fn new() -> Self { + Self(Default::default()) + } +} + +#[OpenApi] +impl TokenApis { + /// create whip session token + #[oai(path = "/whip", method = "post")] + async fn whip_token(&self, Data(ctx): Data<&TokenServerCtx>, body: Json, TokenAuthorization(token): TokenAuthorization) -> Result>> { + if ctx.secure.validate_app(&token.token) { + let body = body.0; + Ok(Json(Response { + status: true, + data: Some(WhipTokenRes { + token: ctx.secure.encode_obj("whip", WhipToken { room: body.room, peer: body.peer }, body.ttl), + }), + error: None, + })) + } else { + Ok(Json(Response { + status: false, + error: Some("APP_TOKEN_INVALID".to_string()), + data: None, + })) + } + } + + /// create whep session token + #[oai(path = "/whep", method = "post")] + async fn whep_token(&self, Data(ctx): Data<&TokenServerCtx>, body: Json, TokenAuthorization(token): TokenAuthorization) -> Json> { + if ctx.secure.validate_app(&token.token) { + let body = body.0; + Json(Response { + status: true, + data: Some(WhepTokenRes { + token: ctx.secure.encode_obj("whep", WhepToken { room: body.room, peer: body.peer }, body.ttl), + }), + error: None, + }) + } else { + Json(Response { + status: false, + error: Some("APP_TOKEN_INVALID".to_string()), + data: None, + }) + } + } + + #[oai(path = "/webrtc", method = "post")] + async fn webrtc_token(&self, Data(ctx): Data<&TokenServerCtx>, body: Json, TokenAuthorization(token): TokenAuthorization) -> Json> { + if ctx.secure.validate_app(&token.token) { + let body = body.0; + Json(Response { + status: true, + data: Some(WebrtcTokenRes { + token: ctx.secure.encode_obj("webrtc", WebrtcToken { room: body.room, peer: body.peer }, body.ttl), + }), + error: None, + }) + } else { + Json(Response { + status: false, + error: Some("APP_TOKEN_INVALID".to_string()), + data: None, + }) + } + } +} diff --git a/bin/src/http/utils/mod.rs b/bin/src/http/utils/mod.rs index bf56ff64..f86657a3 100644 --- a/bin/src/http/utils/mod.rs +++ b/bin/src/http/utils/mod.rs @@ -1,9 +1,11 @@ mod payload_protobuf; mod payload_sdp; mod remote_ip; +mod token; mod user_agent; pub use payload_protobuf::*; pub use payload_sdp::*; pub use remote_ip::*; +pub use token::*; pub use user_agent::*; diff --git a/bin/src/http/utils/token.rs b/bin/src/http/utils/token.rs new file mode 100644 index 00000000..26109365 --- /dev/null +++ b/bin/src/http/utils/token.rs @@ -0,0 +1,5 @@ +use poem_openapi::{auth::Bearer, OpenApi, SecurityScheme}; + +#[derive(SecurityScheme)] +#[oai(rename = "Token Authorization", ty = "bearer", key_in = "header", key_name = "Authorization")] +pub struct TokenAuthorization(pub Bearer); diff --git a/bin/src/lib.rs b/bin/src/lib.rs new file mode 100644 index 00000000..7f05225f --- /dev/null +++ b/bin/src/lib.rs @@ -0,0 +1,19 @@ +use std::net::SocketAddr; + +use atm0s_sdn::{NodeAddr, NodeId}; + +mod errors; +mod http; +#[cfg(feature = "quinn_vnet")] +mod quinn; +pub mod server; + +#[derive(Clone)] +pub struct NodeConfig { + pub node_id: NodeId, + pub secret: String, + pub seeds: Vec, + pub udp_port: u16, + pub zone: u32, + pub custom_addrs: Vec, +} diff --git a/bin/src/main.rs b/bin/src/main.rs index 73742963..f9edf569 100644 --- a/bin/src/main.rs +++ b/bin/src/main.rs @@ -1,21 +1,10 @@ +use std::net::SocketAddr; + +use atm0s_media_server::{server, NodeConfig}; use atm0s_sdn::{NodeAddr, NodeId}; use clap::Parser; -use rand::random; -use server::{run_media_connector, run_media_gateway, run_media_server, ServerType}; use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; -mod http; -mod server; - -#[derive(Clone)] -pub struct NodeConfig { - pub node_id: NodeId, - pub session: u64, - pub secret: String, - pub seeds: Vec, - pub udp_port: u16, -} - /// Scalable Media Server solution for WebRTC, RTMP, and SIP. #[derive(Parser, Debug)] #[command(version, about, long_about = None)] @@ -32,9 +21,13 @@ struct Args { #[arg(env, long, default_value_t = 0)] sdn_port: u16, - /// Sdn Zone - #[arg(env, long, default_value = "local")] - sdn_zone: String, + /// Custom Sdn addr + #[arg(env, long)] + sdn_custom_addrs: Vec, + + /// Sdn Zone, which is 32bit number with last 8bit is 0 + #[arg(env, long, default_value_t = 0)] + sdn_zone: u32, /// Current Node ID #[arg(env, long, default_value_t = 1)] @@ -53,7 +46,7 @@ struct Args { workers: usize, #[command(subcommand)] - server: ServerType, + server: server::ServerType, } #[tokio::main(flavor = "current_thread")] @@ -71,15 +64,26 @@ async fn main() { let workers = args.workers; let node = NodeConfig { node_id: args.node_id, - session: random(), secret: args.secret, seeds: args.seeds, udp_port: args.sdn_port, + zone: args.sdn_zone, + custom_addrs: args.sdn_custom_addrs, }; - match args.server { - ServerType::Gateway(args) => run_media_gateway(workers, args).await, - ServerType::Connector(args) => run_media_connector(workers, args).await, - ServerType::Media(args) => run_media_server(workers, http_port, node, args).await, - } + let local = tokio::task::LocalSet::new(); + local + .run_until(async move { + match args.server { + #[cfg(feature = "gateway")] + server::ServerType::Gateway(args) => server::run_media_gateway(workers, http_port, node, args).await, + #[cfg(feature = "connector")] + server::ServerType::Connector(args) => server::run_media_connector(workers, args).await, + #[cfg(feature = "media")] + server::ServerType::Media(args) => server::run_media_server(workers, http_port, node, args).await, + #[cfg(feature = "cert_utils")] + server::ServerType::Cert(args) => server::run_cert_utils(args).await, + } + }) + .await; } diff --git a/bin/src/quinn/builder.rs b/bin/src/quinn/builder.rs new file mode 100644 index 00000000..3868996a --- /dev/null +++ b/bin/src/quinn/builder.rs @@ -0,0 +1,95 @@ +use quinn::crypto::rustls::QuicClientConfig; +use quinn::{ClientConfig, Endpoint, EndpointConfig, ServerConfig, TokioRuntime, TransportConfig}; +use rustls::client::danger::ServerCertVerifier; +use rustls::pki_types::{CertificateDer, PrivatePkcs8KeyDer}; +use std::error::Error; +use std::sync::Arc; +use std::time::Duration; + +use super::vsocket::VirtualUdpSocket; + +pub fn make_quinn_server(socket: VirtualUdpSocket, priv_key: PrivatePkcs8KeyDer<'static>, cert: CertificateDer<'static>) -> Result> { + let server_config = configure_server(priv_key, cert)?; + let runtime = Arc::new(TokioRuntime); + let mut config = EndpointConfig::default(); + config.max_udp_payload_size(1500).expect("Should config quinn server max_size to 1500"); + Endpoint::new_with_abstract_socket(config, Some(server_config), Arc::new(socket), runtime).map_err(|e| e.into()) +} + +pub fn make_quinn_client(socket: VirtualUdpSocket, server_certs: &[CertificateDer]) -> Result> { + let runtime = Arc::new(TokioRuntime); + let mut config = EndpointConfig::default(); + //Note that client mtu size shoud be smaller than server's + config.max_udp_payload_size(1400).expect("Should config quinn client max_size to 1400"); + let mut endpoint = Endpoint::new_with_abstract_socket(config, None, Arc::new(socket), runtime)?; + endpoint.set_default_client_config(configure_client(server_certs)?); + Ok(endpoint) +} + +/// Returns default server configuration along with its certificate. +fn configure_server(priv_key: PrivatePkcs8KeyDer<'static>, cert: CertificateDer<'static>) -> Result> { + let cert_chain = vec![cert]; + + let mut server_config = ServerConfig::with_single_cert(cert_chain, priv_key.into())?; + let transport_config = Arc::get_mut(&mut server_config.transport).unwrap(); + transport_config.max_concurrent_uni_streams(0_u8.into()); + + Ok(server_config) +} + +fn configure_client(server_certs: &[CertificateDer]) -> Result> { + let mut config = if server_certs.is_empty() { + let provider = rustls::crypto::CryptoProvider::get_default().unwrap(); + ClientConfig::new(Arc::new(QuicClientConfig::try_from( + rustls::ClientConfig::builder() + .dangerous() + .with_custom_certificate_verifier(SkipServerVerification::new(provider.clone())) + .with_no_client_auth(), + )?)) + } else { + let mut certs = rustls::RootCertStore::empty(); + for cert in server_certs { + certs.add(cert.clone())?; + } + ClientConfig::with_root_certificates(Arc::new(certs))? + }; + + let mut transport = TransportConfig::default(); + transport.keep_alive_interval(Some(Duration::from_secs(3))); + config.transport_config(Arc::new(transport)); + Ok(config) +} + +#[derive(Debug)] +struct SkipServerVerification(Arc); + +impl SkipServerVerification { + fn new(provider: Arc) -> Arc { + Arc::new(Self(provider)) + } +} + +impl ServerCertVerifier for SkipServerVerification { + fn verify_tls12_signature(&self, message: &[u8], cert: &CertificateDer<'_>, dss: &rustls::DigitallySignedStruct) -> Result { + Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) + } + + fn verify_tls13_signature(&self, message: &[u8], cert: &CertificateDer<'_>, dss: &rustls::DigitallySignedStruct) -> Result { + Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) + } + + fn supported_verify_schemes(&self) -> Vec { + self.0.signature_verification_algorithms.supported_schemes() + } + + fn verify_server_cert( + &self, + end_entity: &CertificateDer<'_>, + intermediates: &[CertificateDer<'_>], + server_name: &rustls::pki_types::ServerName<'_>, + ocsp_response: &[u8], + now: rustls::pki_types::UnixTime, + ) -> Result { + Ok(rustls::client::danger::ServerCertVerified::assertion()) + } +} diff --git a/bin/src/quinn/mod.rs b/bin/src/quinn/mod.rs new file mode 100644 index 00000000..04ab220b --- /dev/null +++ b/bin/src/quinn/mod.rs @@ -0,0 +1,7 @@ +mod builder; +mod vnet; +mod vsocket; + +pub use builder::{make_quinn_client, make_quinn_server}; +pub use vnet::VirtualNetwork; +pub use vsocket::VirtualUdpSocket; diff --git a/bin/src/quinn/vnet.rs b/bin/src/quinn/vnet.rs new file mode 100644 index 00000000..86179b2d --- /dev/null +++ b/bin/src/quinn/vnet.rs @@ -0,0 +1,94 @@ +use std::collections::{HashMap, VecDeque}; + +use atm0s_sdn::{features::socket, NodeId}; +use sans_io_runtime::Buffer; +use tokio::{ + select, + sync::mpsc::{channel, unbounded_channel, Receiver, Sender, UnboundedReceiver, UnboundedSender}, +}; + +use super::vsocket::VirtualUdpSocket; + +#[derive(Debug)] +pub struct NetworkPkt { + pub local_port: u16, + pub remote: NodeId, + pub remote_port: u16, + pub data: Buffer, + pub meta: u8, +} + +pub struct VirtualNetwork { + node_id: NodeId, + in_rx: Receiver, + out_tx: Sender, + close_socket_tx: UnboundedSender, + close_socket_rx: UnboundedReceiver, + sockets: HashMap>, + ports: VecDeque, +} + +impl VirtualNetwork { + pub fn new(node_id: NodeId) -> (Self, Sender, Receiver) { + let (in_tx, in_rx) = channel(1000); + let (out_tx, out_rx) = channel(1000); + let (close_socket_tx, close_socket_rx) = unbounded_channel(); + + ( + Self { + node_id, + in_rx, + out_tx, + close_socket_rx, + close_socket_tx, + sockets: HashMap::new(), + ports: (0..60000).collect(), + }, + in_tx, + out_rx, + ) + } + + pub async fn udp_socket(&mut self, port: u16) -> Option { + //remove port from ports + let port = if port > 0 { + let index = self.ports.iter().position(|&x| x == port).expect("Should have port"); + self.ports.swap_remove_back(index); + port + } else { + self.ports.pop_front()? + }; + self.out_tx.send(socket::Control::Bind(port)).await.expect("Should send bind"); + let (tx, rx) = channel(100); + self.sockets.insert(port, tx); + Some(VirtualUdpSocket::new(self.node_id, port, self.out_tx.clone(), rx, self.close_socket_tx.clone())) + } + + pub async fn recv(&mut self) -> Option<()> { + select! { + port = self.close_socket_rx.recv() => { + let port = port.expect("Should have port"); + self.ports.push_back(port); + self.sockets.remove(&port); + self.out_tx.send(socket::Control::Unbind(port)).await.expect("Should send unbind"); + Some(()) + } + event = self.in_rx.recv() => { + let event = event?; + match event { + socket::Event::RecvFrom(local_port, remote, remote_port, data, meta) => { + let pkt = NetworkPkt { data, local_port, remote, remote_port, meta }; + if let Some(socket_tx) = self.sockets.get(&local_port) { + if let Err(e) = socket_tx.try_send(pkt) { + log::error!("Send to socket {} error {:?}", local_port, e); + } + } else { + log::warn!("No socket for port {}", local_port); + } + }, + } + Some(()) + } + } + } +} diff --git a/bin/src/quinn/vsocket.rs b/bin/src/quinn/vsocket.rs new file mode 100644 index 00000000..25809ac5 --- /dev/null +++ b/bin/src/quinn/vsocket.rs @@ -0,0 +1,130 @@ +use std::{ + fmt::Debug, + io::IoSliceMut, + net::{SocketAddr, SocketAddrV4}, + ops::DerefMut, + pin::Pin, + sync::{Arc, Mutex}, + task::{Context, Poll}, +}; + +use atm0s_sdn::features::socket; +use quinn::{ + udp::{EcnCodepoint, RecvMeta, Transmit}, + AsyncUdpSocket, UdpPoller, +}; +use tokio::sync::mpsc::{Receiver, Sender, UnboundedSender}; + +use super::vnet::NetworkPkt; + +#[derive(Debug)] +pub struct Poller {} + +impl UdpPoller for Poller { + fn poll_writable(self: Pin<&mut Self>, _cx: &mut Context) -> Poll> { + //TODO implement this for better performace + Poll::Ready(Ok(())) + } +} + +pub struct VirtualUdpSocket { + port: u16, + addr: SocketAddr, + rx: Mutex>, + tx: Sender, + close_socket_tx: UnboundedSender, +} + +impl VirtualUdpSocket { + pub fn new(node_id: u32, port: u16, tx: Sender, rx: Receiver, close_socket_tx: UnboundedSender) -> Self { + Self { + port, + addr: SocketAddr::V4(SocketAddrV4::new(node_id.into(), port)), + rx: Mutex::new(rx), + tx, + close_socket_tx, + } + } +} + +impl Debug for VirtualUdpSocket { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("VirtualUdpSocket").finish() + } +} + +impl AsyncUdpSocket for VirtualUdpSocket { + fn create_io_poller(self: Arc) -> Pin> { + Box::into_pin(Box::new(Poller {})) + } + + fn try_send(&self, transmit: &Transmit) -> std::io::Result<()> { + match transmit.destination { + SocketAddr::V4(addr) => { + log::debug!("{} sending {} bytes to {}", self.addr, transmit.contents.len(), addr); + if self.tx.capacity() > 0 + && self + .tx + .try_send(socket::Control::SendTo( + self.port, + u32::from_be_bytes(addr.ip().octets()), + addr.port(), + transmit.contents.to_vec().into(), + transmit.ecn.map(|x| x as u8).unwrap_or(0), + )) + .is_ok() + { + Ok(()) + } else { + //Err(std::io::ErrorKind::WouldBlock.into()) + //TODO avoid fake send success, need to implement awake mechanism + Ok(()) + } + } + _ => Err(std::io::ErrorKind::ConnectionRefused.into()), + } + } + + fn poll_recv(&self, cx: &mut Context, bufs: &mut [IoSliceMut<'_>], meta: &mut [RecvMeta]) -> Poll> { + let mut rx = self.rx.lock().expect("Should lock mutex"); + match rx.poll_recv(cx) { + std::task::Poll::Pending => std::task::Poll::Pending, + std::task::Poll::Ready(Some(pkt)) => { + let len = pkt.data.len(); + if len <= bufs[0].len() { + let addr = SocketAddr::V4(SocketAddrV4::new(pkt.remote.into(), pkt.remote_port)); + log::debug!("{} received {} bytes from {}", self.addr, len, addr); + bufs[0].deref_mut()[0..len].copy_from_slice(&pkt.data); + meta[0] = quinn::udp::RecvMeta { + addr, + len, + stride: len, + ecn: if pkt.meta == 0 { + None + } else { + EcnCodepoint::from_bits(pkt.meta) + }, + dst_ip: None, + }; + std::task::Poll::Ready(Ok(1)) + } else { + log::warn!("Buffer too small for packet {} vs {}, dropping", len, bufs[0].len()); + std::task::Poll::Pending + } + } + std::task::Poll::Ready(None) => std::task::Poll::Ready(Err(std::io::Error::new(std::io::ErrorKind::ConnectionAborted, "Socket closed"))), + } + } + + fn local_addr(&self) -> std::io::Result { + Ok(self.addr) + } +} + +impl Drop for VirtualUdpSocket { + fn drop(&mut self) { + if let Err(e) = self.close_socket_tx.send(self.port) { + log::error!("Failed to send close socket: {:?}", e); + } + } +} diff --git a/bin/src/server.rs b/bin/src/server.rs index 2910f019..65586f60 100644 --- a/bin/src/server.rs +++ b/bin/src/server.rs @@ -1,16 +1,31 @@ use clap::Subcommand; +#[cfg(feature = "cert_utils")] +mod cert; +#[cfg(feature = "connector")] mod connector; +#[cfg(feature = "gateway")] mod gateway; +#[cfg(feature = "media")] mod media; +#[cfg(feature = "cert_utils")] +pub use cert::run_cert_utils; +#[cfg(feature = "connector")] pub use connector::run_media_connector; +#[cfg(feature = "gateway")] pub use gateway::run_media_gateway; +#[cfg(feature = "media")] pub use media::run_media_server; #[derive(Debug, Subcommand)] pub enum ServerType { + #[cfg(feature = "gateway")] Gateway(gateway::Args), + #[cfg(feature = "connector")] Connector(connector::Args), + #[cfg(feature = "media")] Media(media::Args), + #[cfg(feature = "cert_utils")] + Cert(cert::Args), } diff --git a/bin/src/server/cert.rs b/bin/src/server/cert.rs new file mode 100644 index 00000000..20138071 --- /dev/null +++ b/bin/src/server/cert.rs @@ -0,0 +1,19 @@ +use std::time::{SystemTime, UNIX_EPOCH}; + +use clap::Parser; + +/// A Certs util for quic, which generate der cert and key based on domain +#[derive(Debug, Parser)] +pub struct Args { + /// Domains + #[arg(env, long)] + domains: Vec, +} + +pub async fn run_cert_utils(args: Args) { + let cert = rcgen::generate_simple_self_signed(args.domains).unwrap(); + let start = SystemTime::now(); + let since_the_epoch = start.duration_since(UNIX_EPOCH).expect("Time went backwards").as_millis(); + std::fs::write(format!("./certificate-{}.cert", since_the_epoch), cert.cert.der().to_vec()).unwrap(); + std::fs::write(format!("./certificate-{}.key", since_the_epoch), cert.key_pair.serialize_der().to_vec()).unwrap(); +} diff --git a/bin/src/server/gateway.rs b/bin/src/server/gateway.rs index 8e9842be..b730cca2 100644 --- a/bin/src/server/gateway.rs +++ b/bin/src/server/gateway.rs @@ -1,8 +1,378 @@ +use std::{sync::Arc, time::Duration}; + +use atm0s_sdn::{features::FeaturesEvent, secure::StaticKeyAuthorization, services::visualization, SdnBuilder, SdnControllerUtils, SdnExtOut, SdnOwner}; use clap::Parser; +use media_server_gateway::{store_service::GatewayStoreServiceBuilder, ServiceKind, STORE_SERVICE_ID}; +use media_server_protocol::{ + gateway::{generate_gateway_zone_tag, GATEWAY_RPC_PORT}, + protobuf::{ + cluster_gateway::{MediaEdgeServiceClient, MediaEdgeServiceServer}, + gateway::RemoteIceResponse, + }, + rpc::{ + node_vnet_addr, + quinn::{QuinnClient, QuinnServer}, + }, + transport::{webrtc, whep, whip, RpcError, RpcReq, RpcRes}, +}; +use media_server_secure::jwt::{MediaEdgeSecureJwt, MediaGatewaySecureJwt}; +use rustls::pki_types::{CertificateDer, PrivatePkcs8KeyDer}; + +use crate::{ + errors::MediaServerError, + http::run_gateway_http_server, + quinn::{make_quinn_client, make_quinn_server, VirtualNetwork}, + NodeConfig, +}; +use sans_io_runtime::{backend::PollingBackend, ErrorDebugger2}; + +use self::{dest_selector::build_dest_selector, ip_location::Ip2Location}; + +mod dest_selector; +mod ip_location; +mod rpc_handler; + +#[derive(Clone, Debug, convert_enum::From, convert_enum::TryInto)] +enum SC { + Visual(visualization::Control), + Gateway(media_server_gateway::store_service::Control), +} + +#[derive(Clone, Debug, convert_enum::From, convert_enum::TryInto)] +enum SE { + Visual(visualization::Event), + Gateway(media_server_gateway::store_service::Event), +} +type TC = (); +type TW = (); #[derive(Debug, Parser)] -pub struct Args {} +pub struct Args { + /// Location latude + #[arg(env, long, default_value_t = 0.0)] + lat: f32, + + /// Location longtude + #[arg(env, long, default_value_t = 0.0)] + lon: f32, + + /// GeoIp database + #[arg(env, long, default_value = "./maxminddb-data/GeoLite2-City.mmdb")] + geo_db: String, +} + +pub async fn run_media_gateway(workers: usize, http_port: Option, node: NodeConfig, args: Args) { + rustls::crypto::ring::default_provider().install_default().expect("should install ring as default"); + + let default_cluster_cert_buf = include_bytes!("../../certs/cluster.cert"); + let default_cluster_key_buf = include_bytes!("../../certs/cluster.key"); + let default_cluster_cert = CertificateDer::from(default_cluster_cert_buf.to_vec()); + let default_cluster_key = PrivatePkcs8KeyDer::from(default_cluster_key_buf.to_vec()); + + let edge_secure = Arc::new(MediaEdgeSecureJwt::from(node.secret.as_bytes())); + let gateway_secure = Arc::new(MediaGatewaySecureJwt::from(node.secret.as_bytes())); + let (req_tx, mut req_rx) = tokio::sync::mpsc::channel(1024); + if let Some(http_port) = http_port { + tokio::spawn(async move { + if let Err(e) = run_gateway_http_server(http_port, req_tx, edge_secure, gateway_secure).await { + log::error!("HTTP Error: {}", e); + } + }); + } + + let node_id = node.node_id; + + let mut builder = SdnBuilder::<(), SC, SE, TC, TW>::new(node_id, node.udp_port, node.custom_addrs); + + builder.set_authorization(StaticKeyAuthorization::new(&node.secret)); + builder.set_manual_discovery(vec!["gateway".to_string(), generate_gateway_zone_tag(node.zone)], vec!["gateway".to_string()]); + builder.add_service(Arc::new(GatewayStoreServiceBuilder::new(node.zone, args.lat, args.lon))); + + for seed in node.seeds { + builder.add_seed(seed); + } + + let mut controller = builder.build::>(workers); + let (selector, mut requester) = build_dest_selector(); + + // Ip location for routing client to closest gateway + let ip2location = Arc::new(Ip2Location::new(&args.geo_db)); + + // + // Vnet is a virtual udp layer for creating RPC handlers, we separate media server to 2 layer + // - async for business logic like proxy, logging handling + // - sync with sans-io style for media data + // + let (mut vnet, vnet_tx, mut vnet_rx) = VirtualNetwork::new(node.node_id); + + let media_rpc_socket = vnet.udp_socket(0).await.expect("Should open virtual port for gateway rpc"); + let media_rpc_client = MediaEdgeServiceClient::new(QuinnClient::new(make_quinn_client(media_rpc_socket, &vec![]).expect("Should create endpoint for media rpc client"))); + + let media_rpc_socket = vnet.udp_socket(GATEWAY_RPC_PORT).await.expect("Should open virtual port for gateway rpc"); + let mut media_rpc_server = MediaEdgeServiceServer::new( + QuinnServer::new(make_quinn_server(media_rpc_socket, default_cluster_key, default_cluster_cert.clone()).expect("Should create endpoint for media rpc server")), + rpc_handler::Ctx { + selector: selector.clone(), + client: media_rpc_client.clone(), + ip2location: ip2location.clone(), + }, + rpc_handler::MediaRpcHandlerImpl::default(), + ); + + tokio::task::spawn_local(async move { + media_rpc_server.run().await; + }); + + tokio::task::spawn_local(async move { while let Some(_) = vnet.recv().await {} }); + + loop { + if controller.process().is_none() { + break; + } + while let Ok(control) = vnet_rx.try_recv() { + controller.feature_control((), control.into()); + } + while let Some(out) = requester.recv() { + controller.service_control(STORE_SERVICE_ID.into(), (), out.into()); + } + while let Ok(req) = req_rx.try_recv() { + let res_tx = req.answer_tx; + let param = req.req; + let conn_part = param.get_conn_part(); + let selector = selector.clone(); + let client = media_rpc_client.clone(); + let ip2location = ip2location.clone(); + tokio::spawn(async move { + match param { + RpcReq::Whip(param) => match param { + whip::RpcReq::Connect(param) => { + if let Some(selected) = selector.select(ServiceKind::Webrtc, ip2location.get_location(¶m.ip)).await { + let sock_addr = node_vnet_addr(selected, GATEWAY_RPC_PORT); + log::info!("[Gateway] selected node {selected}"); + let rpc_req = param.into(); + let res = client.whip_connect(sock_addr, rpc_req).await; + log::info!("[Gateway] response from node {selected} => {:?}", res); + if let Some(res) = res { + res_tx + .send(RpcRes::Whip(whip::RpcRes::Connect(Ok(whip::WhipConnectRes { + sdp: res.sdp, + conn_id: res.conn.parse().unwrap(), + })))) + .print_err2("answer http request error"); + } else { + res_tx + .send(RpcRes::Whip(whip::RpcRes::Connect(Err(RpcError::new2(MediaServerError::GatewayRpcError))))) + .print_err2("answer http request error"); + } + } + } + whip::RpcReq::RemoteIce(req) => { + if let Some((node, _session)) = conn_part { + let rpc_req = media_server_protocol::protobuf::cluster_gateway::WhipRemoteIceRequest { + conn: req.conn_id.to_string(), + ice: req.ice, + }; + log::info!("[Gateway] selected node {node}"); + let sock_addr = node_vnet_addr(node, GATEWAY_RPC_PORT); + let res = client.whip_remote_ice(sock_addr, rpc_req).await; + if let Some(_res) = res { + res_tx + .send(RpcRes::Whip(whip::RpcRes::RemoteIce(Ok(whip::WhipRemoteIceRes {})))) + .print_err2("answer http request error"); + } else { + res_tx + .send(RpcRes::Whip(whip::RpcRes::RemoteIce(Err(RpcError::new2(MediaServerError::GatewayRpcError))))) + .print_err2("answer http request error"); + } + } else { + res_tx + .send(RpcRes::Whip(whip::RpcRes::RemoteIce(Err(RpcError::new2(MediaServerError::InvalidConnId))))) + .print_err2("answer http request error"); + } + } + whip::RpcReq::Delete(req) => { + if let Some((node, _session)) = conn_part { + let rpc_req = media_server_protocol::protobuf::cluster_gateway::WhipCloseRequest { conn: req.conn_id.to_string() }; + log::info!("[Gateway] selected node {node}"); + let sock_addr = node_vnet_addr(node, GATEWAY_RPC_PORT); + let res = client.whip_close(sock_addr, rpc_req).await; + if let Some(_res) = res { + res_tx.send(RpcRes::Whip(whip::RpcRes::Delete(Ok(whip::WhipDeleteRes {})))).print_err2("answer http request error"); + } else { + res_tx + .send(RpcRes::Whip(whip::RpcRes::Delete(Err(RpcError::new2(MediaServerError::GatewayRpcError))))) + .print_err2("answer http request error"); + } + } else { + res_tx + .send(RpcRes::Whip(whip::RpcRes::Delete(Err(RpcError::new2(MediaServerError::InvalidConnId))))) + .print_err2("answer http request error"); + } + } + }, + RpcReq::Whep(param) => match param { + whep::RpcReq::Connect(param) => { + if let Some(selected) = selector.select(ServiceKind::Webrtc, ip2location.get_location(¶m.ip)).await { + let sock_addr = node_vnet_addr(selected, GATEWAY_RPC_PORT); + log::info!("[Gateway] selected node {selected}"); + let rpc_req = param.into(); + let res = client.whep_connect(sock_addr, rpc_req).await; + log::info!("[Gateway] response from node {selected} => {:?}", res); + if let Some(res) = res { + res_tx + .send(RpcRes::Whep(whep::RpcRes::Connect(Ok(whep::WhepConnectRes { + sdp: res.sdp, + conn_id: res.conn.parse().unwrap(), + })))) + .print_err2("answer http request error"); + } else { + res_tx + .send(RpcRes::Whep(whep::RpcRes::Connect(Err(RpcError::new2(MediaServerError::GatewayRpcError))))) + .print_err2("answer http request error"); + } + } + } + whep::RpcReq::RemoteIce(req) => { + if let Some((node, _session)) = conn_part { + let rpc_req = media_server_protocol::protobuf::cluster_gateway::WhepRemoteIceRequest { + conn: req.conn_id.to_string(), + ice: req.ice, + }; + log::info!("[Gateway] selected node {node}"); + let sock_addr = node_vnet_addr(node, GATEWAY_RPC_PORT); + let res = client.whep_remote_ice(sock_addr, rpc_req).await; + if let Some(_res) = res { + res_tx + .send(RpcRes::Whep(whep::RpcRes::RemoteIce(Ok(whep::WhepRemoteIceRes {})))) + .print_err2("answer http request error"); + } else { + res_tx + .send(RpcRes::Whep(whep::RpcRes::RemoteIce(Err(RpcError::new2(MediaServerError::GatewayRpcError))))) + .print_err2("answer http request error"); + } + } else { + res_tx + .send(RpcRes::Whep(whep::RpcRes::RemoteIce(Err(RpcError::new2(MediaServerError::InvalidConnId))))) + .print_err2("answer http request error"); + } + } + whep::RpcReq::Delete(req) => { + if let Some((node, _session)) = conn_part { + let rpc_req = media_server_protocol::protobuf::cluster_gateway::WhepCloseRequest { conn: req.conn_id.to_string() }; + log::info!("[Gateway] selected node {node}"); + let sock_addr = node_vnet_addr(node, GATEWAY_RPC_PORT); + let res = client.whep_close(sock_addr, rpc_req).await; + if let Some(_res) = res { + res_tx.send(RpcRes::Whep(whep::RpcRes::Delete(Ok(whep::WhepDeleteRes {})))).print_err2("answer http request error"); + } else { + res_tx + .send(RpcRes::Whep(whep::RpcRes::Delete(Err(RpcError::new2(MediaServerError::GatewayRpcError))))) + .print_err2("answer http request error"); + } + } else { + res_tx + .send(RpcRes::Whep(whep::RpcRes::Delete(Err(RpcError::new2(MediaServerError::InvalidConnId))))) + .print_err2("answer http request error"); + } + } + }, + RpcReq::Webrtc(param) => match param { + webrtc::RpcReq::Connect(ip, user_agent, req) => { + if let Some(selected) = selector.select(ServiceKind::Webrtc, ip2location.get_location(&ip)).await { + let sock_addr = node_vnet_addr(selected, GATEWAY_RPC_PORT); + log::info!("[Gateway] selected node {selected}"); + let rpc_req = media_server_protocol::protobuf::cluster_gateway::WebrtcConnectRequest { + user_agent, + ip: ip.to_string(), + req: Some(req), + }; + let res = client.webrtc_connect(sock_addr, rpc_req).await; + log::info!("[Gateway] response from node {selected} => {:?}", res); + if let Some(res) = res { + let res = res.res.unwrap(); + res_tx + .send(RpcRes::Webrtc(webrtc::RpcRes::Connect(Ok((res.conn_id.parse().unwrap(), res))))) + .print_err2("answer http request error"); + } else { + res_tx + .send(RpcRes::Webrtc(webrtc::RpcRes::Connect(Err(RpcError::new2(MediaServerError::GatewayRpcError))))) + .print_err2("answer http request error"); + } + } + } + webrtc::RpcReq::RemoteIce(conn, ice) => { + if let Some((node, _session)) = conn_part { + let rpc_req = media_server_protocol::protobuf::cluster_gateway::WebrtcRemoteIceRequest { + conn: conn.to_string(), + candidates: ice.candidates, + }; + log::info!("[Gateway] selected node {node}"); + let sock_addr = node_vnet_addr(node, GATEWAY_RPC_PORT); + let res = client.webrtc_remote_ice(sock_addr, rpc_req).await; + if let Some(res) = res { + res_tx + .send(RpcRes::Webrtc(webrtc::RpcRes::RemoteIce(Ok(RemoteIceResponse { added: res.added })))) + .print_err2("answer http request error"); + } else { + res_tx + .send(RpcRes::Webrtc(webrtc::RpcRes::RemoteIce(Err(RpcError::new2(MediaServerError::GatewayRpcError))))) + .print_err2("answer http request error"); + } + } else { + res_tx + .send(RpcRes::Webrtc(webrtc::RpcRes::RemoteIce(Err(RpcError::new2(MediaServerError::InvalidConnId))))) + .print_err2("answer http request error"); + } + } + webrtc::RpcReq::RestartIce(conn, ip, user_agent, req) => { + //TODO how to handle media-node down? + if let Some((node, _session)) = conn_part { + let rpc_req = media_server_protocol::protobuf::cluster_gateway::WebrtcRestartIceRequest { + conn: conn.to_string(), + ip: ip.to_string(), + user_agent, + req: Some(req), + }; + log::info!("[Gateway] selected node {node}"); + let sock_addr = node_vnet_addr(node, GATEWAY_RPC_PORT); + let res = client.webrtc_restart_ice(sock_addr, rpc_req).await; + if let Some(res) = res { + let res = res.res.unwrap(); + res_tx + .send(RpcRes::Webrtc(webrtc::RpcRes::RestartIce(Ok((res.conn_id.parse().unwrap(), res))))) + .print_err2("answer http request error"); + } else { + res_tx + .send(RpcRes::Webrtc(webrtc::RpcRes::RestartIce(Err(RpcError::new2(MediaServerError::GatewayRpcError))))) + .print_err2("answer http request error"); + } + } else { + res_tx + .send(RpcRes::Webrtc(webrtc::RpcRes::RestartIce(Err(RpcError::new2(MediaServerError::InvalidConnId))))) + .print_err2("answer http request error"); + } + } + webrtc::RpcReq::Delete(_) => { + //TODO implement delete webrtc conn + } + }, + } + }); + } -pub async fn run_media_gateway(workers: usize, args: Args) { - println!("Running media gateway"); + while let Some(out) = controller.pop_event() { + match out { + SdnExtOut::ServicesEvent(_, _, SE::Gateway(event)) => { + requester.on_event(event); + } + SdnExtOut::FeaturesEvent(_, FeaturesEvent::Socket(event)) => { + if let Err(e) = vnet_tx.try_send(event) { + log::error!("[MediaEdge] forward Sdn SocketEvent error {:?}", e); + } + } + _ => {} + } + } + tokio::time::sleep(Duration::from_millis(10)).await; + } } diff --git a/bin/src/server/gateway/dest_selector.rs b/bin/src/server/gateway/dest_selector.rs new file mode 100644 index 00000000..696b618d --- /dev/null +++ b/bin/src/server/gateway/dest_selector.rs @@ -0,0 +1,65 @@ +use std::collections::HashMap; + +use media_server_gateway::ServiceKind; +use media_server_protocol::protobuf::cluster_gateway::ping_event::gateway_origin::Location; +use tokio::sync::{ + mpsc::{channel, Receiver, Sender}, + oneshot, +}; + +#[derive(Clone)] +pub struct GatewayDestSelector { + tx: Sender<(ServiceKind, Option<(f32, f32)>, oneshot::Sender>)>, +} + +impl GatewayDestSelector { + pub async fn select(&self, kind: ServiceKind, location: Option<(f32, f32)>) -> Option { + let (tx, rx) = oneshot::channel(); + self.tx.send((kind, location, tx)).await.ok()?; + rx.await.ok()? + } +} + +pub struct GatewayDestRequester { + rx: Receiver<(ServiceKind, Option<(f32, f32)>, oneshot::Sender>)>, + req_seed: u64, + reqs: HashMap>>, +} + +impl GatewayDestRequester { + pub fn on_event(&mut self, event: media_server_gateway::store_service::Event) { + match event { + media_server_gateway::store_service::Event::FindNodeRes(req_id, res) => { + if let Some(tx) = self.reqs.remove(&req_id) { + if let Err(_) = tx.send(res) { + log::error!("[GatewayDestRequester] answer for req_id {req_id} error"); + } + } + } + } + } + + pub fn recv(&mut self) -> Option { + let (kind, location, tx) = self.rx.try_recv().ok()?; + let req_id = self.req_seed; + self.req_seed += 1; + self.reqs.insert(req_id, tx); + Some(media_server_gateway::store_service::Control::FindNodeReq( + req_id, + kind, + location.map(|(lat, lon)| Location { lat, lon }), + )) + } +} + +pub fn build_dest_selector() -> (GatewayDestSelector, GatewayDestRequester) { + let (tx, rx) = channel(100); + ( + GatewayDestSelector { tx }, + GatewayDestRequester { + rx, + req_seed: 0, + reqs: HashMap::new(), + }, + ) +} diff --git a/bin/src/server/gateway/ip_location.rs b/bin/src/server/gateway/ip_location.rs new file mode 100644 index 00000000..1e4188af --- /dev/null +++ b/bin/src/server/gateway/ip_location.rs @@ -0,0 +1,30 @@ +use std::net::IpAddr; + +use maxminddb::Reader; + +pub struct Ip2Location { + city_reader: Reader>, +} + +impl Ip2Location { + pub fn new(database_city: &str) -> Self { + let city_reader = maxminddb::Reader::open_readfile(database_city).expect("Failed to open geoip database"); + Self { city_reader } + } + + pub fn get_location(&self, ip: &IpAddr) -> Option<(f32, f32)> { + match self.city_reader.lookup::(*ip) { + Ok(res) => { + let location = res.location?; + match (location.latitude, location.longitude) { + (Some(lat), Some(lon)) => Some((lat as f32, lon as f32)), + _ => None, + } + } + Err(err) => { + log::error!("cannot get location of ip {} {}", ip, err); + None + } + } + } +} diff --git a/bin/src/server/gateway/rpc_handler.rs b/bin/src/server/gateway/rpc_handler.rs new file mode 100644 index 00000000..39d72dd9 --- /dev/null +++ b/bin/src/server/gateway/rpc_handler.rs @@ -0,0 +1,103 @@ +use std::{net::SocketAddr, sync::Arc}; + +use media_server_gateway::ServiceKind; +use media_server_protocol::{ + endpoint::ClusterConnId, + gateway::GATEWAY_RPC_PORT, + protobuf::cluster_gateway::{ + MediaEdgeServiceClient, MediaEdgeServiceHandler, WebrtcConnectRequest, WebrtcConnectResponse, WebrtcRemoteIceRequest, WebrtcRemoteIceResponse, WebrtcRestartIceRequest, + WebrtcRestartIceResponse, WhepCloseRequest, WhepCloseResponse, WhepConnectRequest, WhepConnectResponse, WhepRemoteIceRequest, WhepRemoteIceResponse, WhipCloseRequest, WhipCloseResponse, + WhipConnectRequest, WhipConnectResponse, WhipRemoteIceRequest, WhipRemoteIceResponse, + }, + rpc::{ + node_vnet_addr, + quinn::{QuinnClient, QuinnStream}, + }, + transport::ConnLayer, +}; + +use super::{dest_selector::GatewayDestSelector, ip_location::Ip2Location}; + +#[derive(Clone)] +pub struct Ctx { + pub(crate) selector: GatewayDestSelector, + pub(crate) client: MediaEdgeServiceClient, + pub(crate) ip2location: Arc, +} + +#[derive(Default)] +pub struct MediaRpcHandlerImpl {} + +impl MediaEdgeServiceHandler for MediaRpcHandlerImpl { + async fn whip_connect(&self, ctx: &Ctx, req: WhipConnectRequest) -> Option { + log::info!("On whip_connect from other gateway"); + let location = req.ip.parse().ok().map(|ip| ctx.ip2location.get_location(&ip)).flatten(); + let dest = ctx.selector.select(ServiceKind::Webrtc, location).await?; + let dest_addr = node_vnet_addr(dest, GATEWAY_RPC_PORT); + ctx.client.whip_connect(dest_addr, req).await + } + + async fn whip_remote_ice(&self, ctx: &Ctx, req: WhipRemoteIceRequest) -> Option { + log::info!("On whip_remote_ice from other gateway"); + let conn: ClusterConnId = req.conn.parse().ok()?; + let (dest, _session) = conn.get_down_part(); + let dest_addr = node_vnet_addr(dest, GATEWAY_RPC_PORT); + ctx.client.whip_remote_ice(dest_addr, req).await + } + + async fn whip_close(&self, ctx: &Ctx, req: WhipCloseRequest) -> Option { + log::info!("On whip_close from other gateway"); + let conn: ClusterConnId = req.conn.parse().ok()?; + let (dest, _session) = conn.get_down_part(); + let dest_addr = node_vnet_addr(dest, GATEWAY_RPC_PORT); + ctx.client.whip_close(dest_addr, req).await + } + + async fn whep_connect(&self, ctx: &Ctx, req: WhepConnectRequest) -> Option { + log::info!("On whep_connect from other gateway"); + let location = req.ip.parse().ok().map(|ip| ctx.ip2location.get_location(&ip)).flatten(); + let dest = ctx.selector.select(ServiceKind::Webrtc, location).await?; + let dest_addr = node_vnet_addr(dest, GATEWAY_RPC_PORT); + ctx.client.whep_connect(dest_addr, req).await + } + + async fn whep_remote_ice(&self, ctx: &Ctx, req: WhepRemoteIceRequest) -> Option { + log::info!("On whep_remote_ice from other gateway"); + let conn: ClusterConnId = req.conn.parse().ok()?; + let (dest, _session) = conn.get_down_part(); + let dest_addr = node_vnet_addr(dest, GATEWAY_RPC_PORT); + ctx.client.whep_remote_ice(dest_addr, req).await + } + + async fn whep_close(&self, ctx: &Ctx, req: WhepCloseRequest) -> Option { + log::info!("On whep_close from other gateway"); + let conn: ClusterConnId = req.conn.parse().ok()?; + let (dest, _session) = conn.get_down_part(); + let dest_addr = node_vnet_addr(dest, GATEWAY_RPC_PORT); + ctx.client.whep_close(dest_addr, req).await + } + + async fn webrtc_connect(&self, ctx: &Ctx, req: WebrtcConnectRequest) -> Option { + log::info!("On webrtc_connect from other gateway"); + let location = req.ip.parse().ok().map(|ip| ctx.ip2location.get_location(&ip)).flatten(); + let dest = ctx.selector.select(ServiceKind::Webrtc, location).await?; + let dest_addr = node_vnet_addr(dest, GATEWAY_RPC_PORT); + ctx.client.webrtc_connect(dest_addr, req).await + } + + async fn webrtc_remote_ice(&self, ctx: &Ctx, req: WebrtcRemoteIceRequest) -> Option { + log::info!("On webrtc_remote_ice from other gateway"); + let conn: ClusterConnId = req.conn.parse().ok()?; + let (dest, _session) = conn.get_down_part(); + let dest_addr = node_vnet_addr(dest, GATEWAY_RPC_PORT); + ctx.client.webrtc_remote_ice(dest_addr, req).await + } + + async fn webrtc_restart_ice(&self, ctx: &Ctx, req: WebrtcRestartIceRequest) -> Option { + log::info!("On webrtc_restart_ice from other gateway"); + let conn: ClusterConnId = req.conn.parse().ok()?; + let (dest, _session) = conn.get_down_part(); + let dest_addr = node_vnet_addr(dest, GATEWAY_RPC_PORT); + ctx.client.webrtc_restart_ice(dest_addr, req).await + } +} diff --git a/bin/src/server/media.rs b/bin/src/server/media.rs index 79310a6e..68d29d46 100644 --- a/bin/src/server/media.rs +++ b/bin/src/server/media.rs @@ -1,22 +1,37 @@ use std::{ collections::HashMap, net::{IpAddr, SocketAddr, SocketAddrV4}, + sync::Arc, time::Duration, }; -use atm0s_sdn::SdnExtIn; +use atm0s_sdn::{features::FeaturesEvent, SdnExtIn, SdnExtOut}; use clap::Parser; +use media_server_protocol::{gateway::GATEWAY_RPC_PORT, protobuf::cluster_gateway::MediaEdgeServiceServer, rpc::quinn::QuinnServer}; use media_server_runner::MediaConfig; +use media_server_secure::jwt::{MediaEdgeSecureJwt, MediaGatewaySecureJwt}; +use rand::random; +use rustls::pki_types::{CertificateDer, PrivatePkcs8KeyDer}; use sans_io_runtime::{backend::PollingBackend, Controller}; -use crate::{http::run_media_http_server, server::media::runtime_worker::MediaRuntimeWorker, NodeConfig}; +use crate::{ + http::run_media_http_server, + quinn::{make_quinn_server, VirtualNetwork}, + server::media::runtime_worker::MediaRuntimeWorker, + NodeConfig, +}; +mod rpc_handler; mod runtime_worker; use runtime_worker::{ExtIn, ExtOut}; #[derive(Debug, Parser)] pub struct Args { + /// Enable token API or not, which allow generate token + #[arg(env, long)] + enable_token_api: bool, + /// Webrtc Ice Lite #[arg(env, long)] ice_lite: bool, @@ -35,17 +50,29 @@ pub struct Args { } pub async fn run_media_server(workers: usize, http_port: Option, node: NodeConfig, args: Args) { + rustls::crypto::ring::default_provider().install_default().expect("should install ring as default"); + + let default_cluster_cert_buf = include_bytes!("../../certs/cluster.cert"); + let default_cluster_key_buf = include_bytes!("../../certs/cluster.key"); + let default_cluster_cert = CertificateDer::from(default_cluster_cert_buf.to_vec()); + let default_cluster_key = PrivatePkcs8KeyDer::from(default_cluster_key_buf.to_vec()); + + let secure = Arc::new(MediaEdgeSecureJwt::from(node.secret.as_bytes())); + let secure2 = args.enable_token_api.then(|| Arc::new(MediaGatewaySecureJwt::from(node.secret.as_bytes()))); let (req_tx, mut req_rx) = tokio::sync::mpsc::channel(1024); + let req_tx2 = req_tx.clone(); if let Some(http_port) = http_port { + let secure = secure.clone(); tokio::spawn(async move { - if let Err(e) = run_media_http_server(http_port, req_tx).await { + if let Err(e) = run_media_http_server(http_port, req_tx2, secure, secure2).await { log::error!("HTTP Error: {}", e); } }); } let node_id = node.node_id; - let node_session = node.session; + let node_session = random(); + let mut webrtc_addrs = args.custom_ips.into_iter().map(|ip| SocketAddr::new(ip, args.media_port)).collect::>(); local_ip_address::local_ip().into_iter().for_each(|ip| { if let IpAddr::V4(ip) = ip { @@ -62,12 +89,14 @@ pub async fn run_media_server(workers: usize, http_port: Option, node: Node let cfg = runtime_worker::ICfg { controller: i == 0, node: node.clone(), + session: node_session, media: MediaConfig { webrtc_addrs: webrtc_addrs.clone(), ice_lite: args.ice_lite, + secure: secure.clone(), }, }; - controller.add_worker::<_, _, MediaRuntimeWorker, PollingBackend<_, 128, 512>>(Duration::from_millis(1), cfg, None); + controller.add_worker::<_, _, MediaRuntimeWorker<_>, PollingBackend<_, 128, 512>>(Duration::from_millis(1), cfg, None); } for seed in node.seeds { @@ -77,10 +106,32 @@ pub async fn run_media_server(workers: usize, http_port: Option, node: Node let mut req_id_seed = 0; let mut reqs = HashMap::new(); + // + // Vnet is a virtual udp layer for creating RPC handlers, we separate media server to 2 layer + // - async for business logic like proxy, logging handling + // - sync with sans-io style for media data + // + let (mut vnet, vnet_tx, mut vnet_rx) = VirtualNetwork::new(node.node_id); + let media_rpc_socket = vnet.udp_socket(GATEWAY_RPC_PORT).await.expect("Should open virtual port for gateway rpc"); + let mut media_rpc_server = MediaEdgeServiceServer::new( + QuinnServer::new(make_quinn_server(media_rpc_socket, default_cluster_key, default_cluster_cert).expect("Should create endpoint for media rpc server")), + rpc_handler::Ctx { req_tx }, + rpc_handler::MediaRpcHandlerImpl::default(), + ); + + tokio::task::spawn_local(async move { + media_rpc_server.run().await; + }); + + tokio::task::spawn_local(async move { while let Some(_) = vnet.recv().await {} }); + loop { if controller.process().is_none() { break; } + while let Ok(control) = vnet_rx.try_recv() { + controller.send_to_best(ExtIn::Sdn(SdnExtIn::FeaturesControl(0.into(), control.into()))); + } while let Ok(req) = req_rx.try_recv() { let req_id = req_id_seed; req_id_seed += 1; @@ -114,9 +165,14 @@ pub async fn run_media_server(workers: usize, http_port: Option, node: Node } } } - ExtOut::Sdn(_) => {} + ExtOut::Sdn(SdnExtOut::FeaturesEvent(_, FeaturesEvent::Socket(event))) => { + if let Err(e) = vnet_tx.try_send(event) { + log::error!("[MediaEdge] forward Sdn SocketEvent error {:?}", e); + } + } + _ => {} } } - tokio::time::sleep(Duration::from_millis(100)).await; + tokio::time::sleep(Duration::from_millis(10)).await; } } diff --git a/bin/src/server/media/rpc_handler.rs b/bin/src/server/media/rpc_handler.rs new file mode 100644 index 00000000..4a7d73f1 --- /dev/null +++ b/bin/src/server/media/rpc_handler.rs @@ -0,0 +1,161 @@ +//! +//! This file implement forward logic from quic_rpc to worker logic +//! + +use media_server_protocol::{ + endpoint::ClusterConnId, + protobuf::{ + cluster_gateway::{ + MediaEdgeServiceHandler, WebrtcConnectRequest, WebrtcConnectResponse, WebrtcRemoteIceRequest, WebrtcRemoteIceResponse, WebrtcRestartIceRequest, WebrtcRestartIceResponse, WhepCloseRequest, + WhepCloseResponse, WhepConnectRequest, WhepConnectResponse, WhepRemoteIceRequest, WhepRemoteIceResponse, WhipCloseRequest, WhipCloseResponse, WhipConnectRequest, WhipConnectResponse, + WhipRemoteIceRequest, WhipRemoteIceResponse, + }, + gateway::RemoteIceRequest, + }, + transport::{ + webrtc, + whep::{self, WhepDeleteReq, WhepRemoteIceReq}, + whip::{self, WhipDeleteReq, WhipRemoteIceReq}, + RpcReq, RpcRes, + }, +}; + +use crate::http::Rpc; + +#[derive(Clone)] +pub struct Ctx { + pub(crate) req_tx: tokio::sync::mpsc::Sender, RpcRes>>, +} + +#[derive(Default)] +pub struct MediaRpcHandlerImpl {} + +impl MediaEdgeServiceHandler for MediaRpcHandlerImpl { + /* Start of whip */ + async fn whip_connect(&self, ctx: &Ctx, req: WhipConnectRequest) -> Option { + let req = req.try_into().ok()?; + log::info!("On whip_connect from gateway"); + let (req, rx) = Rpc::new(RpcReq::Whip(whip::RpcReq::Connect(req))); + ctx.req_tx.send(req).await.ok()?; + let res = rx.await.ok()?; + match res { + RpcRes::Whip(whip::RpcRes::Connect(res)) => res.ok().map(|r| WhipConnectResponse { + sdp: r.sdp, + conn: r.conn_id.to_string(), + }), + _ => None, + } + } + + async fn whip_remote_ice(&self, ctx: &Ctx, req: WhipRemoteIceRequest) -> Option { + log::info!("On whip_remote_ice from gateway"); + let conn_id = req.conn.parse().ok()?; + let conn = req.conn.clone(); + let (req, rx) = Rpc::new(RpcReq::Whip(whip::RpcReq::RemoteIce(WhipRemoteIceReq { conn_id, ice: req.ice }))); + ctx.req_tx.send(req).await.ok()?; + let res = rx.await.ok()?; + //TODO process with ICE restart + match res { + RpcRes::Whip(whip::RpcRes::RemoteIce(res)) => res.ok().map(|r| WhipRemoteIceResponse { conn }), + _ => None, + } + } + + async fn whip_close(&self, ctx: &Ctx, req: WhipCloseRequest) -> Option { + log::info!("On whip_close from gateway"); + let conn_id = req.conn.parse().ok()?; + let conn = req.conn.clone(); + let (req, rx) = Rpc::new(RpcReq::Whip(whip::RpcReq::Delete(WhipDeleteReq { conn_id }))); + ctx.req_tx.send(req).await.ok()?; + let res = rx.await.ok()?; + //TODO process with ICE restart + match res { + RpcRes::Whip(whip::RpcRes::Delete(res)) => res.ok().map(|r| WhipCloseResponse { conn }), + _ => None, + } + } + + /* Start of whep */ + async fn whep_connect(&self, ctx: &Ctx, req: WhepConnectRequest) -> Option { + let req = req.try_into().ok()?; + log::info!("On whep_connect from gateway"); + let (req, rx) = Rpc::new(RpcReq::Whep(whep::RpcReq::Connect(req))); + ctx.req_tx.send(req).await.ok()?; + let res = rx.await.ok()?; + match res { + RpcRes::Whep(whep::RpcRes::Connect(res)) => res.ok().map(|r| WhepConnectResponse { + sdp: r.sdp, + conn: r.conn_id.to_string(), + }), + _ => None, + } + } + + async fn whep_remote_ice(&self, ctx: &Ctx, req: WhepRemoteIceRequest) -> Option { + log::info!("On whep_remote_ice from gateway"); + let conn_id = req.conn.parse().ok()?; + let conn = req.conn.clone(); + let (req, rx) = Rpc::new(RpcReq::Whep(whep::RpcReq::RemoteIce(WhepRemoteIceReq { conn_id, ice: req.ice }))); + ctx.req_tx.send(req).await.ok()?; + let res = rx.await.ok()?; + //TODO process with ICE restart + match res { + RpcRes::Whep(whep::RpcRes::RemoteIce(res)) => res.ok().map(|r| WhepRemoteIceResponse { conn }), + _ => None, + } + } + + async fn whep_close(&self, ctx: &Ctx, req: WhepCloseRequest) -> Option { + log::info!("On whep_close from gateway"); + let conn_id = req.conn.parse().ok()?; + let conn = req.conn.clone(); + let (req, rx) = Rpc::new(RpcReq::Whep(whep::RpcReq::Delete(WhepDeleteReq { conn_id }))); + ctx.req_tx.send(req).await.ok()?; + let res = rx.await.ok()?; + //TODO process with ICE restart + match res { + RpcRes::Whep(whep::RpcRes::Delete(res)) => res.ok().map(|r| WhepCloseResponse { conn }), + _ => None, + } + } + + /* Start of sdk */ + async fn webrtc_connect(&self, ctx: &Ctx, req: WebrtcConnectRequest) -> Option { + log::info!("On webrtc_connect from gateway"); + let (req, rx) = Rpc::new(RpcReq::Webrtc(webrtc::RpcReq::Connect(req.ip.parse().ok()?, req.user_agent, req.req?))); + ctx.req_tx.send(req).await.ok()?; + let res = rx.await.ok()?; + match res { + RpcRes::Webrtc(webrtc::RpcRes::Connect(res)) => res.ok().map(|(conn, mut r)| { + r.conn_id = conn.to_string(); + WebrtcConnectResponse { res: Some(r) } + }), + _ => None, + } + } + + async fn webrtc_remote_ice(&self, ctx: &Ctx, req: WebrtcRemoteIceRequest) -> Option { + log::info!("On webrtc_remote_ice from gateway"); + let (req, rx) = Rpc::new(RpcReq::Webrtc(webrtc::RpcReq::RemoteIce(req.conn.parse().ok()?, RemoteIceRequest { candidates: req.candidates }))); + ctx.req_tx.send(req).await.ok()?; + let res = rx.await.ok()?; + match res { + RpcRes::Webrtc(webrtc::RpcRes::RemoteIce(res)) => res.ok().map(|r| WebrtcRemoteIceResponse { added: r.added }), + _ => None, + } + } + + async fn webrtc_restart_ice(&self, ctx: &Ctx, req: WebrtcRestartIceRequest) -> Option { + log::info!("On webrtc_restart_ice from gateway"); + let (req, rx) = Rpc::new(RpcReq::Webrtc(webrtc::RpcReq::RestartIce(req.conn.parse().ok()?, req.ip.parse().ok()?, req.user_agent, req.req?))); + ctx.req_tx.send(req).await.ok()?; + let res = rx.await.ok()?; + match res { + RpcRes::Webrtc(webrtc::RpcRes::RestartIce(res)) => res.ok().map(|(conn, mut r)| { + r.conn_id = conn.to_string(); + WebrtcRestartIceResponse { res: Some(r) } + }), + _ => None, + } + } +} diff --git a/bin/src/server/media/runtime_worker.rs b/bin/src/server/media/runtime_worker.rs index 76312544..0811cb25 100644 --- a/bin/src/server/media/runtime_worker.rs +++ b/bin/src/server/media/runtime_worker.rs @@ -1,13 +1,11 @@ -use std::{collections::VecDeque, sync::Arc, time::Instant}; +use std::{collections::VecDeque, time::Instant}; + +use atm0s_sdn::{SdnExtIn, SdnExtOut, SdnWorkerBusEvent}; -use atm0s_sdn::{ - secure::{HandshakeBuilderXDA, StaticKeyAuthorization}, - services::visualization, - ControllerPlaneCfg, DataPlaneCfg, DataWorkerHistory, SdnExtIn, SdnExtOut, SdnWorkerBusEvent, -}; use media_server_protocol::transport::{RpcReq, RpcRes}; -use media_server_runner::{Input as WorkerInput, MediaConfig, MediaServerWorker, Output as WorkerOutput, Owner, SdnConfig, UserData, SC, SE, TC, TW}; -use rand::rngs::OsRng; +use media_server_runner::{Input as WorkerInput, MediaConfig, MediaServerWorker, Output as WorkerOutput, Owner, UserData, SC, SE, TC, TW}; +use media_server_secure::MediaEdgeSecure; +use rand::random; use sans_io_runtime::{BusChannelControl, BusControl, BusEvent, WorkerInner, WorkerInnerInput, WorkerInnerOutput}; use crate::NodeConfig; @@ -30,56 +28,43 @@ pub enum Channel { Worker(u16), } type Event = SdnWorkerBusEvent; -pub struct ICfg { +pub struct ICfg { pub controller: bool, pub node: NodeConfig, - pub media: MediaConfig, + pub session: u64, + pub media: MediaConfig, } type SCfg = (); type Input = WorkerInnerInput; type Output = WorkerInnerOutput; -pub struct MediaRuntimeWorker { +pub struct MediaRuntimeWorker { index: u16, - worker: MediaServerWorker, + worker: MediaServerWorker, queue: VecDeque, } -impl WorkerInner for MediaRuntimeWorker { - fn build(index: u16, cfg: ICfg) -> Self { - let sdn_config = SdnConfig { - node_id: cfg.node.node_id, - controller: if cfg.controller { - Some(ControllerPlaneCfg { - session: cfg.node.session, - authorization: Arc::new(StaticKeyAuthorization::new(&cfg.node.secret)), - handshake_builder: Arc::new(HandshakeBuilderXDA), - random: Box::new(OsRng::default()), - services: vec![Arc::new(visualization::VisualizationServiceBuilder::new(false))], - }) - } else { - None - }, - tick_ms: 1000, - data: DataPlaneCfg { - worker_id: 0, - services: vec![Arc::new(visualization::VisualizationServiceBuilder::new(false))], - history: Arc::new(DataWorkerHistory::default()), - }, - }; - +impl WorkerInner, SCfg> for MediaRuntimeWorker { + fn build(index: u16, cfg: ICfg) -> Self { let mut queue = VecDeque::from([Output::Bus(BusControl::Channel(Owner::Sdn, BusChannelControl::Subscribe(Channel::Worker(index))))]); - if sdn_config.controller.is_some() { + if cfg.controller { queue.push_back(Output::Bus(BusControl::Channel(Owner::Sdn, BusChannelControl::Subscribe(Channel::Controller)))); } - MediaRuntimeWorker { - index, - worker: MediaServerWorker::new(cfg.node.udp_port, sdn_config, cfg.media), - queue, - } + let worker = MediaServerWorker::new( + cfg.node.node_id, + random(), + &cfg.node.secret, + cfg.controller, + cfg.node.udp_port, + cfg.node.custom_addrs, + cfg.node.zone, + cfg.media, + ); + log::info!("creted worker"); + MediaRuntimeWorker { index, worker, queue } } fn worker_index(&self) -> u16 { @@ -115,7 +100,7 @@ impl WorkerInner for MediaRunt } } -impl MediaRuntimeWorker { +impl MediaRuntimeWorker { fn process_out(&mut self, out: WorkerOutput) -> Output { match out { WorkerOutput::ExtRpc(req_id, res) => Output::Ext(true, ExtOut::Rpc(req_id, self.index, res)), diff --git a/deny.toml b/deny.toml index c4a973db..9c5f42a7 100644 --- a/deny.toml +++ b/deny.toml @@ -29,7 +29,9 @@ targets = [ vulnerability = "deny" unmaintained = "warn" yanked = "deny" -ignore = [] +ignore = [ + "RUSTSEC-2023-0071" # a new version of rsa has not been released +] [bans] multiple-versions = "allow" @@ -64,4 +66,4 @@ allow = [ "Zlib", # https://tldrlegal.com/license/zlib-libpng-license-(zlib) "LGPL-2.0", # https://www.gnu.org/licenses/old-licenses/lgpl-2.0.en.html "X11", # https://en.wikipedia.org/wiki/MIT_License -] \ No newline at end of file +] diff --git a/packages/media_core/src/endpoint.rs b/packages/media_core/src/endpoint.rs index 3135216c..b8bf7609 100644 --- a/packages/media_core/src/endpoint.rs +++ b/packages/media_core/src/endpoint.rs @@ -10,7 +10,7 @@ use media_server_protocol::{ }; use sans_io_runtime::{ backend::{BackendIncoming, BackendOutgoing}, - return_if_some, TaskSwitcher, TaskSwitcherBranch, TaskSwitcherChild, + return_if_some, Task, TaskSwitcher, TaskSwitcherBranch, TaskSwitcherChild, }; use crate::{ @@ -201,13 +201,18 @@ impl, ExtIn, ExtOut> Endpoint { _tmp: PhantomData::default(), } } +} - pub fn on_tick(&mut self, now: Instant) { +impl, ExtIn, ExtOut> Task, EndpointOutput> for Endpoint +where + T::Time: From, +{ + fn on_tick(&mut self, now: Instant) { self.internal.input(&mut self.switcher).on_tick(now); self.transport.input(&mut self.switcher).on_tick(now); } - pub fn on_event(&mut self, now: Instant, input: EndpointInput) { + fn on_event(&mut self, now: Instant, input: EndpointInput) { match input { EndpointInput::Net(net) => { self.transport.input(&mut self.switcher).on_input(now, TransportInput::Net(net)); @@ -224,7 +229,7 @@ impl, ExtIn, ExtOut> Endpoint { } } - pub fn on_shutdown(&mut self, now: Instant) { + fn on_shutdown(&mut self, now: Instant) { self.transport.input(&mut self.switcher).on_input(now, TransportInput::Close); } } diff --git a/packages/media_gateway/Cargo.toml b/packages/media_gateway/Cargo.toml new file mode 100644 index 00000000..3e24b384 --- /dev/null +++ b/packages/media_gateway/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "media-server-gateway" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +log = { workspace = true } +serde = { version = "1.0.200", features = ["derive"] } +media-server-protocol = { path = "../protocol" } +atm0s-sdn = { workspace = true } +prost = { workspace = true } diff --git a/packages/media_gateway/src/agent_service.rs b/packages/media_gateway/src/agent_service.rs new file mode 100644 index 00000000..0ab06679 --- /dev/null +++ b/packages/media_gateway/src/agent_service.rs @@ -0,0 +1,156 @@ +use std::{collections::VecDeque, fmt::Debug}; + +use atm0s_sdn::{ + base::{NetOutgoingMeta, Service, ServiceBuilder, ServiceCtx, ServiceInput, ServiceOutput, ServiceSharedInput, ServiceWorker, ServiceWorkerCtx, ServiceWorkerInput, ServiceWorkerOutput}, + features::{data, FeaturesControl, FeaturesEvent}, + RouteRule, ServiceBroadcastLevel, +}; +use media_server_protocol::protobuf::{ + self, + cluster_gateway::{ + gateway_event, + ping_event::{MediaOrigin, Origin, ServiceStats}, + }, +}; +use prost::Message as _; + +use crate::{ServiceKind, AGENT_SERVICE_ID, AGENT_SERVICE_NAME, DATA_PORT, STORE_SERVICE_ID}; + +#[derive(Debug, Clone)] +pub enum Control { + Stats(Vec<(ServiceKind, u32)>), +} + +#[derive(Debug, Clone)] +pub enum Event {} + +pub struct GatewayAgentService { + output: Option>, + seq: u16, + _tmp: std::marker::PhantomData<(UserData, SC, SE, TC, TW)>, +} + +impl GatewayAgentService +where + SC: From + TryInto, + SE: From + TryInto, +{ + pub fn new() -> Self { + Self { + output: None, + seq: 0, + _tmp: std::marker::PhantomData, + } + } +} + +impl Service for GatewayAgentService +where + SC: From + TryInto, + SE: From + TryInto, +{ + fn service_id(&self) -> u8 { + AGENT_SERVICE_ID + } + + fn service_name(&self) -> &str { + AGENT_SERVICE_NAME + } + + fn on_shared_input<'a>(&mut self, _ctx: &ServiceCtx, _now: u64, input: ServiceSharedInput) { + match input { + ServiceSharedInput::Tick(_) => { + let rule = RouteRule::ToServices(STORE_SERVICE_ID, ServiceBroadcastLevel::Group, self.seq); + self.seq += 1; + let mut meta = NetOutgoingMeta::secure(); + meta.source = true; + let data = protobuf::cluster_gateway::GatewayEvent { + event: Some(gateway_event::Event::Ping(protobuf::cluster_gateway::PingEvent { + cpu: 0, //TODO + memory: 0, //TODO + disk: 0, //TODO + webrtc: Some(ServiceStats { active: true, live: 0, max: 100 }), + origin: Some(Origin::Media(MediaOrigin {})), + })), + } + .encode_to_vec(); + log::info!("[GatewayAgent] broadcast ping to zone gateways"); + self.output = Some(ServiceOutput::FeatureControl(data::Control::DataSendRule(DATA_PORT, rule, meta, data.into()).into())); + } + ServiceSharedInput::Connection(_) => {} + } + } + + fn on_input(&mut self, _ctx: &ServiceCtx, _now: u64, _input: ServiceInput) {} + + fn pop_output2(&mut self, _now: u64) -> Option> { + self.output.take() + } +} + +pub struct GatewayAgentServiceWorker { + queue: VecDeque>, +} + +impl ServiceWorker for GatewayAgentServiceWorker { + fn service_id(&self) -> u8 { + AGENT_SERVICE_ID + } + + fn service_name(&self) -> &str { + AGENT_SERVICE_NAME + } + + fn on_tick(&mut self, _ctx: &ServiceWorkerCtx, _now: u64, _tick_count: u64) {} + + fn on_input(&mut self, _ctx: &ServiceWorkerCtx, _now: u64, input: ServiceWorkerInput) { + match input { + ServiceWorkerInput::Control(owner, control) => self.queue.push_back(ServiceWorkerOutput::ForwardControlToController(owner, control)), + ServiceWorkerInput::FromController(_) => {} + ServiceWorkerInput::FeatureEvent(event) => self.queue.push_back(ServiceWorkerOutput::ForwardFeatureEventToController(event)), + } + } + + fn pop_output2(&mut self, _now: u64) -> Option> { + self.queue.pop_front() + } +} + +pub struct GatewayAgentServiceBuilder { + _tmp: std::marker::PhantomData<(UserData, SC, SE, TC, TW)>, +} + +impl GatewayAgentServiceBuilder { + pub fn new() -> Self { + Self { _tmp: std::marker::PhantomData } + } +} + +impl ServiceBuilder for GatewayAgentServiceBuilder +where + UserData: 'static + Debug + Send + Sync + Copy + Eq, + SC: 'static + Debug + Send + Sync + From + TryInto, + SE: 'static + Debug + Send + Sync + From + TryInto, + TC: 'static + Debug + Send + Sync, + TW: 'static + Debug + Send + Sync, +{ + fn service_id(&self) -> u8 { + AGENT_SERVICE_ID + } + + fn service_name(&self) -> &str { + AGENT_SERVICE_NAME + } + + fn discoverable(&self) -> bool { + false + } + + fn create(&self) -> Box> { + Box::new(GatewayAgentService::new()) + } + + fn create_worker(&self) -> Box> { + Box::new(GatewayAgentServiceWorker { queue: Default::default() }) + } +} diff --git a/packages/media_gateway/src/lib.rs b/packages/media_gateway/src/lib.rs new file mode 100644 index 00000000..efc08678 --- /dev/null +++ b/packages/media_gateway/src/lib.rs @@ -0,0 +1,16 @@ +pub mod agent_service; +mod store; +pub mod store_service; + +#[derive(Debug, Clone)] +pub enum ServiceKind { + Webrtc, +} + +pub const DATA_PORT: u16 = 10001; + +pub const STORE_SERVICE_ID: u8 = 101; +pub const STORE_SERVICE_NAME: &str = "gateway_store"; + +pub const AGENT_SERVICE_ID: u8 = 102; +pub const AGENT_SERVICE_NAME: &str = "gateway_agent"; diff --git a/packages/media_gateway/src/store.rs b/packages/media_gateway/src/store.rs new file mode 100644 index 00000000..26f8649d --- /dev/null +++ b/packages/media_gateway/src/store.rs @@ -0,0 +1,111 @@ +use media_server_protocol::protobuf::cluster_gateway::ping_event::{gateway_origin::Location, GatewayOrigin, Origin, ServiceStats}; + +use crate::ServiceKind; + +use self::service::ServiceStore; + +mod service; + +#[derive(Debug)] +pub struct PingEvent { + pub cpu: u8, + pub memory: u8, + pub disk: u8, + pub origin: Origin, + pub webrtc: Option, +} + +pub struct GatewayStore { + zone: u32, + location: Location, + webrtc: ServiceStore, + output: Option, +} + +impl GatewayStore { + pub fn new(zone: u32, location: Location) -> Self { + Self { + webrtc: ServiceStore::new(ServiceKind::Webrtc, location.clone()), + zone, + location, + output: None, + } + } + + pub fn on_tick(&mut self, now: u64) { + self.webrtc.on_tick(now); + + let ping = PingEvent { + cpu: 0, //TODO + memory: 0, //TODO + disk: 0, //TODO + origin: Origin::Gateway(GatewayOrigin { + zone: self.zone, + location: Some(self.location.clone()), + }), + webrtc: self.webrtc.local_stats(), + }; + + log::trace!("[GatewayStore] create ping event for broadcast {:?}", ping); + self.output = Some(ping); + } + + pub fn on_ping(&mut self, now: u64, from: u32, ping: PingEvent) { + log::debug!("[GatewayStore] on ping from {from} data {:?}", ping); + let node_usage = node_usage(&ping, 80, 90); + let webrtc_usage = webrtc_usage(&ping, 80, 90); + match ping.origin { + Origin::Media(_) => match (node_usage, webrtc_usage, ping.webrtc) { + (Some(_node), Some(webrtc), Some(stats)) => self.webrtc.on_node_ping(now, from, webrtc, stats), + _ => self.webrtc.remove_node(from), + }, + Origin::Gateway(gateway) => { + if gateway.zone == self.zone { + //Reject stats from same zone + return; + } + match (node_usage, webrtc_usage, gateway.location, ping.webrtc) { + (Some(node), Some(webrtc), Some(location), Some(stats)) => self.webrtc.on_gateway_ping(now, gateway.zone, from, node, location, webrtc, stats), + _ => self.webrtc.remove_gateway(gateway.zone, from), + } + } + } + } + + pub fn best_for(&self, kind: ServiceKind, location: Option) -> Option { + let node = match kind { + ServiceKind::Webrtc => self.webrtc.best_for(location.clone()), + }; + log::debug!("[GatewayStore] query best {:?} for {:?} got {:?}", kind, location, node); + node + } + + pub fn pop_output(&mut self) -> Option { + self.output.take() + } +} + +fn node_usage(ping: &PingEvent, max_memory: u8, max_disk: u8) -> Option { + if ping.memory as u8 >= max_memory { + return None; + } + + if ping.disk as u8 >= max_disk { + return None; + } + + Some(ping.cpu) +} + +fn webrtc_usage(ping: &PingEvent, max_memory: u8, max_disk: u8) -> Option { + if ping.memory as u8 >= max_memory { + return None; + } + + if ping.disk as u8 >= max_disk { + return None; + } + + let webrtc = ping.webrtc.as_ref()?; + webrtc.active.then(|| (ping.cpu as u8).max(((webrtc.live * 100) / webrtc.max) as u8)) +} diff --git a/packages/media_gateway/src/store/service.rs b/packages/media_gateway/src/store/service.rs new file mode 100644 index 00000000..561e48d7 --- /dev/null +++ b/packages/media_gateway/src/store/service.rs @@ -0,0 +1,215 @@ +use media_server_protocol::protobuf::cluster_gateway::ping_event::{gateway_origin::Location, ServiceStats}; + +use crate::ServiceKind; + +const PING_TIMEOUT: u64 = 5000; //timeout after 5s not ping + +/// This is for node inside same zone +struct NodeSource { + node: u32, + usage: u8, + stats: ServiceStats, + last_updated: u64, +} + +/// This is for other cluser +struct ZoneSource { + zone: u32, + usage: u8, + location: Location, + last_updated: u64, + gateways: Vec, +} + +pub struct ServiceStore { + kind: ServiceKind, + location: Location, + local_sources: Vec, + zone_sources: Vec, +} + +impl ServiceStore { + pub fn new(kind: ServiceKind, location: Location) -> Self { + log::info!("[ServiceStore {:?}] create new in {:?}", kind, location); + Self { + kind, + location, + local_sources: vec![], + zone_sources: vec![], + } + } + + pub fn on_tick(&mut self, now: u64) { + self.local_sources.retain(|s| s.last_updated + PING_TIMEOUT > now); + for z in self.zone_sources.iter_mut() { + z.gateways.retain(|s| s.last_updated + PING_TIMEOUT > now); + } + self.zone_sources.retain(|s| !s.gateways.is_empty()); + } + + pub fn on_node_ping(&mut self, now: u64, node: u32, usage: u8, stats: ServiceStats) { + if let Some(s) = self.local_sources.iter_mut().find(|s| s.node == node) { + s.usage = usage; + s.last_updated = now; + } else { + log::info!("[ServiceStore {:?}] new node {} usage {}, stats {:?}", self.kind, node, usage, stats); + self.local_sources.push(NodeSource { + node, + usage, + last_updated: now, + stats, + }); + } + self.local_sources.sort(); + } + + pub fn remove_node(&mut self, node: u32) { + if let Some((index, _)) = self.local_sources.iter_mut().enumerate().find(|(_i, s)| s.node == node) { + let node = self.local_sources.remove(index); + log::info!("[ServiceStore {:?}] remove node {} usage {}, stats {:?}", self.kind, node.node, node.usage, node.stats); + } + } + + pub fn on_gateway_ping(&mut self, now: u64, zone: u32, gateway: u32, gateway_usage: u8, location: Location, usage: u8, stats: ServiceStats) { + if let Some(z) = self.zone_sources.iter_mut().find(|s| s.zone == zone) { + z.usage = usage; + z.last_updated = now; + if let Some(g) = z.gateways.iter_mut().find(|g| g.node == gateway) { + g.usage = gateway_usage; + g.last_updated = now; + } else { + log::info!( + "[ServiceStore {:?}] zone {zone} at {:?} add new gateway {gateway} gateway usage {gateway_usage}, stats {:?}", + self.kind, + z.location, + stats + ); + z.gateways.push(NodeSource { + node: gateway, + usage: gateway_usage, + last_updated: now, + stats, + }); + } + z.gateways.sort(); + } else { + log::info!( + "[ServiceStore {:?}] new zone {zone} at {:?} usage {usage}, gateway {gateway} gateway usage {gateway_usage}, stats {:?}", + self.kind, + location, + stats + ); + self.zone_sources.push(ZoneSource { + zone, + usage, + location, + last_updated: now, + gateways: vec![NodeSource { + node: gateway, + usage: gateway_usage, + last_updated: now, + stats, + }], + }); + } + self.local_sources.sort(); + } + + pub fn remove_gateway(&mut self, zone: u32, gateway: u32) { + if let Some((index, z)) = self.zone_sources.iter_mut().enumerate().find(|(i, z)| z.zone == zone) { + if let Some((g_index, g)) = z.gateways.iter_mut().enumerate().find(|(i, g)| g.node == gateway) { + let g = z.gateways.remove(g_index); + log::info!( + "[ServiceStore {:?}] zone {zone} at {:?} remove gateway {} gateway usage {}, stats {:?}", + self.kind, + z.location, + g.node, + g.usage, + g.stats, + ); + } + if z.gateways.is_empty() { + let zone = self.zone_sources.remove(index); + log::info!("[ServiceStore {:?}] remove zone {} at {:?}", self.kind, zone.zone, zone.location,); + } + } + } + + pub fn best_for(&self, location: Option) -> Option { + let location = location.unwrap_or_else(|| self.location.clone()); + let mut min_dis = distance(&self.location, &location); + let mut min_node = self.local_sources.first().map(|s| s.node); + + for z in self.zone_sources.iter() { + let dis = distance(&location, &z.location); + if min_node.is_none() || min_dis > dis { + min_dis = dis; + min_node = z.gateways.first().map(|s| s.node); + } + } + + log::info!("[ServiceStore {:?}] query best node for {:?} got min_dis {min_dis} min_node {:?}", self.kind, location, min_node); + min_node + } + + pub fn local_stats(&self) -> Option { + if self.local_sources.is_empty() { + return None; + } + + let mut stats = ServiceStats { active: false, max: 0, live: 0 }; + for n in self.local_sources.iter() { + if n.stats.active { + stats.active = true; + } + stats.live = n.stats.live; + stats.max = n.stats.max; + } + + Some(stats) + } +} + +impl Eq for NodeSource {} +impl PartialEq for NodeSource { + fn eq(&self, other: &Self) -> bool { + self.node.eq(&other.node) + } +} + +impl Ord for NodeSource { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.usage.cmp(&other.usage) + } +} + +impl PartialOrd for NodeSource { + fn partial_cmp(&self, other: &Self) -> Option { + self.usage.partial_cmp(&other.usage) + } +} + +impl Eq for ZoneSource {} +impl PartialEq for ZoneSource { + fn eq(&self, other: &Self) -> bool { + self.zone.eq(&other.zone) + } +} + +impl Ord for ZoneSource { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.usage.cmp(&other.usage) + } +} + +impl PartialOrd for ZoneSource { + fn partial_cmp(&self, other: &Self) -> Option { + self.usage.partial_cmp(&other.usage) + } +} + +/// Calculate distance between two nodes. +fn distance(node1: &Location, node2: &Location) -> f32 { + //TODO make it more accuracy + ((node1.lat - node2.lat).powi(2) + (node1.lon - node2.lon).powi(2)).sqrt() +} diff --git a/packages/media_gateway/src/store_service.rs b/packages/media_gateway/src/store_service.rs new file mode 100644 index 00000000..e180e7ad --- /dev/null +++ b/packages/media_gateway/src/store_service.rs @@ -0,0 +1,219 @@ +use std::{collections::VecDeque, fmt::Debug}; + +use atm0s_sdn::{ + base::{ + NetIncomingMeta, NetOutgoingMeta, Service, ServiceBuilder, ServiceCtx, ServiceInput, ServiceOutput, ServiceSharedInput, ServiceWorker, ServiceWorkerCtx, ServiceWorkerInput, + ServiceWorkerOutput, + }, + features::{data, FeaturesControl, FeaturesEvent}, + RouteRule, ServiceBroadcastLevel, +}; +use media_server_protocol::protobuf::{ + self, + cluster_gateway::{gateway_event, ping_event::gateway_origin::Location}, +}; +use prost::Message as _; + +use crate::{ + store::{GatewayStore, PingEvent}, + ServiceKind, DATA_PORT, STORE_SERVICE_ID, STORE_SERVICE_NAME, +}; + +#[derive(Debug, Clone)] +pub enum Control { + FindNodeReq(u64, ServiceKind, Option), +} + +#[derive(Debug, Clone)] +pub enum Event { + FindNodeRes(u64, Option), +} + +pub struct GatewayStoreService { + queue: VecDeque>, + store: GatewayStore, + seq: u16, + _tmp: std::marker::PhantomData<(UserData, SC, SE, TC, TW)>, +} + +impl GatewayStoreService +where + SC: From + TryInto, + SE: From + TryInto, +{ + pub fn new(zone: u32, lat: f32, lon: f32) -> Self { + Self { + store: GatewayStore::new(zone, Location { lat, lon }), + queue: VecDeque::from([ServiceOutput::FeatureControl(data::Control::DataListen(DATA_PORT).into())]), + seq: 0, + _tmp: std::marker::PhantomData, + } + } + + fn handle_event(&mut self, now: u64, _port: u16, meta: NetIncomingMeta, data: &[u8]) -> Option<()> { + let from = meta.source?; + let req = protobuf::cluster_gateway::GatewayEvent::decode(data).ok()?; + let event = req.event?; + match event { + gateway_event::Event::Ping(ping) => { + let origin = ping.origin?; + self.store.on_ping( + now, + from, + PingEvent { + cpu: ping.cpu as u8, + memory: ping.memory as u8, + disk: ping.disk as u8, + origin, + webrtc: ping.webrtc, + }, + ) + } + } + + Some(()) + } +} + +impl Service for GatewayStoreService +where + SC: From + TryInto + Debug, + SE: From + TryInto, + TC: Debug, +{ + fn service_id(&self) -> u8 { + STORE_SERVICE_ID + } + + fn service_name(&self) -> &str { + STORE_SERVICE_NAME + } + + fn on_shared_input<'a>(&mut self, _ctx: &ServiceCtx, now: u64, input: ServiceSharedInput) { + match input { + ServiceSharedInput::Tick(_) => { + self.store.on_tick(now); + if let Some(ping) = self.store.pop_output() { + let rule = RouteRule::ToServices(STORE_SERVICE_ID, ServiceBroadcastLevel::Global, self.seq); + self.seq += 1; + let mut meta = NetOutgoingMeta::secure(); + meta.source = true; + let data = protobuf::cluster_gateway::GatewayEvent { + event: Some(gateway_event::Event::Ping(protobuf::cluster_gateway::PingEvent { + cpu: ping.cpu as u32, + memory: ping.memory as u32, + disk: ping.disk as u32, + webrtc: ping.webrtc, + origin: Some(ping.origin), + })), + } + .encode_to_vec(); + self.queue + .push_back(ServiceOutput::FeatureControl(data::Control::DataSendRule(DATA_PORT, rule, meta, data.into()).into())); + } + } + ServiceSharedInput::Connection(_) => {} + } + } + + fn on_input(&mut self, _ctx: &ServiceCtx, now: u64, input: ServiceInput) { + match input { + ServiceInput::FeatureEvent(FeaturesEvent::Data(data::Event::Recv(port, meta, data))) => { + self.handle_event(now, port, meta, &data); + } + ServiceInput::Control(actor, control) => { + if let Ok(control) = control.try_into() { + match control { + Control::FindNodeReq(req_id, kind, location) => { + let out = self.store.best_for(kind, location); + self.queue.push_back(ServiceOutput::Event(actor, Event::FindNodeRes(req_id, out).into())); + } + } + } + } + _ => {} + } + } + + fn pop_output2(&mut self, _now: u64) -> Option> { + self.queue.pop_front() + } +} + +pub struct GatewayStoreServiceWorker { + queue: VecDeque>, +} + +impl ServiceWorker for GatewayStoreServiceWorker { + fn service_id(&self) -> u8 { + STORE_SERVICE_ID + } + + fn service_name(&self) -> &str { + STORE_SERVICE_NAME + } + + fn on_tick(&mut self, _ctx: &ServiceWorkerCtx, _now: u64, _tick_count: u64) {} + + fn on_input(&mut self, _ctx: &ServiceWorkerCtx, _now: u64, input: ServiceWorkerInput) { + match input { + ServiceWorkerInput::Control(owner, control) => self.queue.push_back(ServiceWorkerOutput::ForwardControlToController(owner, control)), + ServiceWorkerInput::FromController(_) => {} + ServiceWorkerInput::FeatureEvent(event) => { + log::info!("forward event to controller"); + self.queue.push_back(ServiceWorkerOutput::ForwardFeatureEventToController(event)) + } + } + } + + fn pop_output2(&mut self, _now: u64) -> Option> { + self.queue.pop_front() + } +} + +pub struct GatewayStoreServiceBuilder { + _tmp: std::marker::PhantomData<(UserData, SC, SE, TC, TW)>, + zone: u32, + lat: f32, + lon: f32, +} + +impl GatewayStoreServiceBuilder { + pub fn new(zone: u32, lat: f32, lon: f32) -> Self { + Self { + zone, + lat, + lon, + _tmp: std::marker::PhantomData, + } + } +} + +impl ServiceBuilder for GatewayStoreServiceBuilder +where + UserData: 'static + Debug + Send + Sync + Copy + Eq, + SC: 'static + Debug + Send + Sync + From + TryInto, + SE: 'static + Debug + Send + Sync + From + TryInto, + TC: 'static + Debug + Send + Sync, + TW: 'static + Debug + Send + Sync, +{ + fn service_id(&self) -> u8 { + STORE_SERVICE_ID + } + + fn service_name(&self) -> &str { + STORE_SERVICE_NAME + } + + fn discoverable(&self) -> bool { + true + } + + fn create(&self) -> Box> { + Box::new(GatewayStoreService::new(self.zone, self.lat, self.lon)) + } + + fn create_worker(&self) -> Box> { + Box::new(GatewayStoreServiceWorker { queue: Default::default() }) + } +} diff --git a/packages/media_runner/Cargo.toml b/packages/media_runner/Cargo.toml index a2e929e5..019c1c29 100644 --- a/packages/media_runner/Cargo.toml +++ b/packages/media_runner/Cargo.toml @@ -11,6 +11,8 @@ log = { workspace = true } num_enum = { workspace = true } convert-enum = { workspace = true } media-server-protocol = { path = "../protocol" } +media-server-secure = { path = "../media_secure" } +media-server-gateway = { path = "../media_gateway" } media-server-core = { path = "../media_core" } sans-io-runtime = { workspace = true, default-features = false } diff --git a/packages/media_runner/src/worker.rs b/packages/media_runner/src/worker.rs index 1c4c2376..849a9337 100644 --- a/packages/media_runner/src/worker.rs +++ b/packages/media_runner/src/worker.rs @@ -1,8 +1,15 @@ -use std::{net::SocketAddr, time::Instant}; +use std::{net::SocketAddr, sync::Arc, time::Instant}; -use atm0s_sdn::{services::visualization, NetInput, NetOutput, SdnExtIn, SdnExtOut, SdnWorker, SdnWorkerBusEvent, SdnWorkerCfg, SdnWorkerInput, SdnWorkerOutput, TimePivot}; +use atm0s_sdn::{ + generate_node_addr, + secure::{HandshakeBuilderXDA, StaticKeyAuthorization}, + services::{manual_discovery, visualization}, + ControllerPlaneCfg, DataPlaneCfg, DataWorkerHistory, NetInput, NetOutput, SdnExtIn, SdnExtOut, SdnWorker, SdnWorkerBusEvent, SdnWorkerCfg, SdnWorkerInput, SdnWorkerOutput, TimePivot, +}; use media_server_core::cluster::{self, MediaCluster}; +use media_server_gateway::agent_service::GatewayAgentServiceBuilder; use media_server_protocol::{ + gateway::generate_gateway_zone_tag, protobuf::gateway::{ConnectResponse, RemoteIceResponse}, transport::{ webrtc, @@ -11,7 +18,8 @@ use media_server_protocol::{ RpcReq, RpcRes, }, }; -use rand::random; +use media_server_secure::MediaEdgeSecure; +use rand::{random, rngs::OsRng}; use sans_io_runtime::{ backend::{BackendIncoming, BackendOutgoing}, collections::DynamicDeque, @@ -19,9 +27,10 @@ use sans_io_runtime::{ }; use transport_webrtc::{GroupInput, MediaWorkerWebrtc, VariantParams, WebrtcOwner}; -pub struct MediaConfig { +pub struct MediaConfig { pub ice_lite: bool, pub webrtc_addrs: Vec, + pub secure: Arc, } pub type SdnConfig = SdnWorkerCfg; @@ -34,8 +43,17 @@ pub enum Owner { //for sdn pub type UserData = cluster::ClusterRoomHash; -pub type SC = visualization::Control; -pub type SE = visualization::Event; +#[derive(Clone, Debug, convert_enum::From, convert_enum::TryInto)] +pub enum SC { + Visual(visualization::Control), + Gateway(media_server_gateway::agent_service::Control), +} + +#[derive(Clone, Debug, convert_enum::From, convert_enum::TryInto)] +pub enum SE { + Visual(visualization::Event), + Gateway(media_server_gateway::agent_service::Event), +} pub type TC = (); pub type TW = (); @@ -67,27 +85,58 @@ enum MediaClusterOwner { Webrtc(WebrtcOwner), } -pub struct MediaServerWorker { +pub struct MediaServerWorker { sdn_slot: usize, sdn_worker: TaskSwitcherBranch, SdnWorkerOutput>, media_cluster: TaskSwitcherBranch, cluster::Output>, - media_webrtc: TaskSwitcherBranch, + media_webrtc: TaskSwitcherBranch, transport_webrtc::GroupOutput>, switcher: TaskSwitcher, queue: DynamicDeque, timer: TimePivot, + secure: Arc, } -impl MediaServerWorker { - pub fn new(udp_port: u16, sdn: SdnConfig, media: MediaConfig) -> Self { - let sdn_udp_addr = SocketAddr::from(([0, 0, 0, 0], udp_port)); +impl MediaServerWorker { + pub fn new(node_id: u32, session: u64, secret: &str, controller: bool, sdn_udp: u16, sdn_custom_addrs: Vec, sdn_zone: u32, media: MediaConfig) -> Self { + let secure = media.secure.clone(); //TODO why need this? + let sdn_udp_addr = SocketAddr::from(([0, 0, 0, 0], sdn_udp)); + + let node_addr = generate_node_addr(node_id, sdn_udp, sdn_custom_addrs); + + let visualization = Arc::new(visualization::VisualizationServiceBuilder::new(false)); + let discovery = Arc::new(manual_discovery::ManualDiscoveryServiceBuilder::new(node_addr, vec![], vec![generate_gateway_zone_tag(sdn_zone)])); + let gateway = Arc::new(GatewayAgentServiceBuilder::new()); + + let sdn_config = SdnConfig { + node_id, + controller: if controller { + Some(ControllerPlaneCfg { + session, + authorization: Arc::new(StaticKeyAuthorization::new(&secret)), + handshake_builder: Arc::new(HandshakeBuilderXDA), + random: Box::new(OsRng::default()), + services: vec![visualization.clone(), discovery.clone(), gateway.clone()], + }) + } else { + None + }, + tick_ms: 1000, + data: DataPlaneCfg { + worker_id: 0, + services: vec![visualization, discovery, gateway], + history: Arc::new(DataWorkerHistory::default()), + }, + }; + Self { sdn_slot: 1, //TODO dont use this hack, must to wait to bind success to network - sdn_worker: TaskSwitcherBranch::new(SdnWorker::new(sdn), TaskType::Sdn), + sdn_worker: TaskSwitcherBranch::new(SdnWorker::new(sdn_config), TaskType::Sdn), media_cluster: TaskSwitcherBranch::default(TaskType::MediaCluster), - media_webrtc: TaskSwitcherBranch::new(MediaWorkerWebrtc::new(media.webrtc_addrs, media.ice_lite), TaskType::MediaWebrtc), + media_webrtc: TaskSwitcherBranch::new(MediaWorkerWebrtc::new(media.webrtc_addrs, media.ice_lite, media.secure), TaskType::MediaWebrtc), switcher: TaskSwitcher::new(3), queue: DynamicDeque::from([Output::Net(Owner::Sdn, BackendOutgoing::UdpListen { addr: sdn_udp_addr, reuse: true })]), timer: TimePivot::build(), + secure, } } @@ -101,6 +150,7 @@ impl MediaServerWorker { self.sdn_worker.input(s).on_tick(now_ms); self.media_cluster.input(s).on_tick(now); self.media_webrtc.input(s).on_tick(now); + //TODO collect node stats then send to GatewayAgent service } pub fn on_event(&mut self, now: Instant, input: Input) { @@ -170,7 +220,7 @@ impl MediaServerWorker { } } -impl MediaServerWorker { +impl MediaServerWorker { fn output_sdn(&mut self, now: Instant, out: SdnWorkerOutput) -> Output { match out { SdnWorkerOutput::Ext(out) => Output::ExtSdn(out), @@ -247,16 +297,12 @@ impl MediaServerWorker { } } -impl MediaServerWorker { +impl MediaServerWorker { fn process_rpc(&mut self, now: Instant, req_id: u64, req: RpcReq) { log::info!("[MediaServerWorker] incoming rpc req {req_id}"); match req { RpcReq::Whip(req) => match req { - whip::RpcReq::Connect(req) => match self - .media_webrtc - .input(&mut self.switcher) - .spawn(transport_webrtc::VariantParams::Whip(req.token.into(), "publisher".to_string().into()), &req.sdp) - { + whip::RpcReq::Connect(req) => match self.media_webrtc.input(&mut self.switcher).spawn(transport_webrtc::VariantParams::Whip(req.room, req.peer), &req.sdp) { Ok((_ice_lite, sdp, conn_id)) => self.queue.push_back(Output::ExtRpc(req_id, RpcRes::Whip(whip::RpcRes::Connect(Ok(WhipConnectRes { conn_id, sdp }))))), Err(e) => self.queue.push_back(Output::ExtRpc(req_id, RpcRes::Whip(whip::RpcRes::Connect(Err(e))))), }, @@ -279,7 +325,7 @@ impl MediaServerWorker { match self .media_webrtc .input(&mut self.switcher) - .spawn(transport_webrtc::VariantParams::Whep(req.token.into(), peer_id.into()), &req.sdp) + .spawn(transport_webrtc::VariantParams::Whep(req.room, peer_id.into()), &req.sdp) { Ok((_ice_lite, sdp, conn_id)) => self.queue.push_back(Output::ExtRpc(req_id, RpcRes::Whep(whep::RpcRes::Connect(Ok(WhepConnectRes { conn_id, sdp }))))), Err(e) => self.queue.push_back(Output::ExtRpc(req_id, RpcRes::Whep(whep::RpcRes::Connect(Err(e))))), @@ -299,7 +345,11 @@ impl MediaServerWorker { } }, RpcReq::Webrtc(req) => match req { - webrtc::RpcReq::Connect(ip, token, user_agent, req) => match self.media_webrtc.input(&mut self.switcher).spawn(VariantParams::Webrtc(ip, token, user_agent, req.clone()), &req.sdp) { + webrtc::RpcReq::Connect(ip, user_agent, req) => match self + .media_webrtc + .input(&mut self.switcher) + .spawn(VariantParams::Webrtc(ip, user_agent, req.clone(), self.secure.clone()), &req.sdp) + { Ok((ice_lite, sdp, conn_id)) => self.queue.push_back(Output::ExtRpc( req_id, RpcRes::Webrtc(webrtc::RpcRes::Connect(Ok(( @@ -320,11 +370,11 @@ impl MediaServerWorker { GroupInput::Ext(conn.into(), transport_webrtc::ExtIn::RemoteIce(req_id, transport_webrtc::Variant::Webrtc, ice.candidates)), ); } - webrtc::RpcReq::RestartIce(conn, ip, token, user_agent, req) => { + webrtc::RpcReq::RestartIce(conn, ip, user_agent, req) => { log::info!("on rpc request {req_id}, webrtc::RpcReq::RestartIce"); self.media_webrtc.input(&mut self.switcher).on_event( now, - GroupInput::Ext(conn.into(), transport_webrtc::ExtIn::RestartIce(req_id, transport_webrtc::Variant::Webrtc, ip, token, user_agent, req)), + GroupInput::Ext(conn.into(), transport_webrtc::ExtIn::RestartIce(req_id, transport_webrtc::Variant::Webrtc, ip, user_agent, req)), ); } webrtc::RpcReq::Delete(_) => todo!(), diff --git a/packages/media_secure/Cargo.toml b/packages/media_secure/Cargo.toml new file mode 100644 index 00000000..13f6eea1 --- /dev/null +++ b/packages/media_secure/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "media-server-secure" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +jwt-simple = { version = "0.12", optional = true, default-features=false, features=["pure-rust"] } +media-server-protocol = { path = "../protocol" } +serde = { version = "1.0", features = ["derive"] } + +[features] +default = ["jwt-secure"] +jwt-secure = ["jwt-simple"] diff --git a/packages/media_secure/src/jwt.rs b/packages/media_secure/src/jwt.rs new file mode 100644 index 00000000..999b34d6 --- /dev/null +++ b/packages/media_secure/src/jwt.rs @@ -0,0 +1,61 @@ +use crate::{MediaEdgeSecure, MediaGatewaySecure}; +use jwt_simple::prelude::*; +use serde::{de::DeserializeOwned, Serialize}; + +const HEADER_CONN: &'static str = "C"; + +pub struct MediaEdgeSecureJwt { + key: HS256Key, +} + +impl From<&[u8]> for MediaEdgeSecureJwt { + fn from(key: &[u8]) -> Self { + Self { key: HS256Key::from_bytes(key) } + } +} + +impl MediaEdgeSecure for MediaEdgeSecureJwt { + fn decode_obj(&self, _type: &'static str, token: &str) -> Option { + let mut options = VerificationOptions::default(); + options.allowed_issuers = Some(HashSet::from_strings(&[_type])); + let claims = self.key.verify_token::(token, Some(options)).ok()?; + Some(claims.custom) + } + + fn encode_conn_id(&self, conn: C, ttl_seconds: u64) -> String { + let claims = Claims::with_custom_claims(conn, Duration::from_secs(ttl_seconds)).with_issuer("conn"); + self.key.authenticate(claims).expect("Should create jwt") + } + + fn decode_conn_id(&self, token: &str) -> Option { + let mut options = VerificationOptions::default(); + options.allowed_issuers = Some(HashSet::from_strings(&["conn"])); + let claims = self.key.verify_token::(token, Some(options)).ok()?; + Some(claims.custom) + } +} + +pub struct MediaGatewaySecureJwt { + key_str: String, + key: HS256Key, +} + +impl From<&[u8]> for MediaGatewaySecureJwt { + fn from(key: &[u8]) -> Self { + Self { + key_str: String::from_utf8_lossy(key).to_string(), + key: HS256Key::from_bytes(key), + } + } +} + +impl MediaGatewaySecure for MediaGatewaySecureJwt { + fn validate_app(&self, token: &str) -> bool { + self.key_str.eq(token) + } + + fn encode_obj(&self, _type: &'static str, ob: O, ttl_seconds: u64) -> String { + let claims = Claims::with_custom_claims(ob, Duration::from_secs(ttl_seconds)).with_issuer(_type); + self.key.authenticate(claims).expect("Should create jwt") + } +} diff --git a/packages/media_secure/src/lib.rs b/packages/media_secure/src/lib.rs new file mode 100644 index 00000000..bb778ebf --- /dev/null +++ b/packages/media_secure/src/lib.rs @@ -0,0 +1,17 @@ +use serde::{de::DeserializeOwned, Serialize}; + +#[cfg(feature = "jwt-secure")] +pub mod jwt; + +/// This interface is for validating and generating data in each edge node like media-node +pub trait MediaEdgeSecure { + fn decode_obj(&self, _type: &'static str, data: &str) -> Option; + fn encode_conn_id(&self, conn: C, ttl_ms: u64) -> String; + fn decode_conn_id(&self, data: &str) -> Option; +} + +/// This interface for generate signed data for gateway, like connect token +pub trait MediaGatewaySecure { + fn validate_app(&self, token: &str) -> bool; + fn encode_obj(&self, _type: &'static str, ob: O, ttl_ms: u64) -> String; +} diff --git a/packages/protocol/Cargo.toml b/packages/protocol/Cargo.toml index be78f9e4..0aa802c8 100644 --- a/packages/protocol/Cargo.toml +++ b/packages/protocol/Cargo.toml @@ -7,14 +7,21 @@ edition = "2021" [dependencies] bincode = "1.3.3" +log = { workspace = true } convert-enum = { workspace = true } derivative = { workspace = true } derive_more = { workspace = true } prost = { workspace = true } serde = { version = "1.0.200", features = ["derive"] } +quinn = { version = "0.11", optional = true } +tokio = { version = "1", features = ["rt"] } [build-dependencies] prost-build = "0.12" +tera = "1" +serde = { version = "1.0.200", features = ["derive"] } [features] +default = [] build-protobuf = [] +quinn-rpc = ["quinn"] diff --git a/packages/protocol/build.rs b/packages/protocol/build.rs index 3d97c56d..382dc667 100644 --- a/packages/protocol/build.rs +++ b/packages/protocol/build.rs @@ -1,13 +1,76 @@ +#[cfg(feature = "build-protobuf")] +use prost_build::{Config, ServiceGenerator}; +#[cfg(feature = "build-protobuf")] +use std::fmt::Write; use std::io::Result; - -#[cfg(any(feature = "build-protobuf"))] -use prost_build::Config; +#[cfg(feature = "build-protobuf")] +use tera::{Context, Tera}; fn main() -> Result<()> { #[cfg(feature = "build-protobuf")] Config::new() + .service_generator(Box::new(GenericRpcGenerator)) .out_dir("src/protobuf") .include_file("mod.rs") - .compile_protos(&["./proto/shared.proto", "./proto/conn.proto", "./proto/features.proto", "./proto/gateway.proto"], &["./proto"])?; + .compile_protos( + &[ + "./proto/shared.proto", + "./proto/conn.proto", + "./proto/features.proto", + "./proto/gateway.proto", + "./proto/cluster_gateway.proto", + ], + &["./proto"], + )?; Ok(()) } + +#[cfg(feature = "build-protobuf")] +struct GenericRpcGenerator; + +#[cfg(feature = "build-protobuf")] +impl ServiceGenerator for GenericRpcGenerator { + fn generate(&mut self, service: prost_build::Service, buf: &mut String) { + #[derive(serde::Serialize, Debug)] + struct MethodInfo { + name: String, + input: String, + output: String, + } + + let methods = service + .methods + .into_iter() + .map(|m| MethodInfo { + name: m.name, + input: m.input_type, + output: m.output_type, + }) + .collect::>(); + + println!("{:?}", methods); + + let mut tera = Tera::default(); + tera.add_raw_template("service", include_str!("./build_templates/service.teml")) + .expect("Should include service template"); + let mut context = Context::new(); + context.insert("service", &service.name); + context.insert("methods", &methods); + tera.render_to("service", &context, StringWriter(buf)).expect("Should success to write"); + } +} + +#[cfg(feature = "build-protobuf")] +struct StringWriter<'a>(&'a mut String); + +#[cfg(feature = "build-protobuf")] +impl<'a> std::io::Write for StringWriter<'a> { + fn write(&mut self, buf: &[u8]) -> Result { + self.0.write_str(String::from_utf8_lossy(buf).to_string().as_str()).expect("Should write ok"); + Ok(buf.len()) + } + + fn flush(&mut self) -> Result<()> { + Ok(()) + } +} diff --git a/packages/protocol/build_templates/service.teml b/packages/protocol/build_templates/service.teml new file mode 100644 index 00000000..2e75ed06 --- /dev/null +++ b/packages/protocol/build_templates/service.teml @@ -0,0 +1,95 @@ +#[allow(async_fn_in_trait)] +pub trait {{ service }}ServiceHandler { +{% for method in methods %} + async fn {{ method.name }}(&self, ctx: &CTX, req: {{ method.input }}) -> Option<{{ method.output }}>; +{% endfor %} +} + +pub struct {{ service }}ServiceClient, S: crate::rpc::RpcStream> { + client: C, + _tmp: std::marker::PhantomData<(D, S)>, +} + +impl, S: crate::rpc::RpcStream> Clone for {{ service }}ServiceClient { + fn clone(&self) -> Self { + Self { + client: self.client.clone(), + _tmp: Default::default(), + } + } +} + +impl, S: crate::rpc::RpcStream> {{ service }}ServiceClient { + pub fn new(client: C) -> Self { + Self { + client, + _tmp: Default::default(), + } + } + +{% for method in methods %} + pub async fn {{ method.name }}(&self, dest: D, req: {{ method.input }}) -> Option<{{ method.output }}> { + use prost::Message; + + let mut stream = self.client.connect(dest, "{{ method.name }}.service").await?; + let out_buf = req.encode_to_vec(); + stream.write(&out_buf).await?; + let in_buf = stream.read().await?; + {{ method.output }}::decode(in_buf.as_slice()).ok() + } +{% endfor %} +} + +pub struct {{ service }}ServiceServer, Sr: crate::rpc::RpcServer, S: crate::rpc::RpcStream> { + ctx: std::sync::Arc, + handler: std::sync::Arc, + server: Sr, + _tmp: std::marker::PhantomData, +} + +impl, Sr: crate::rpc::RpcServer, S: 'static + crate::rpc::RpcStream> {{ service }}ServiceServer { + pub fn new(server: Sr, ctx: CTX, handler: H) -> Self { + Self { + ctx: std::sync::Arc::new(ctx), + handler: std::sync::Arc::new(handler), + server, + _tmp: Default::default(), + } + } + + pub async fn run(&mut self) { + let local = tokio::task::LocalSet::new(); + local + .run_until(async move { + self.run_local().await; + }) + .await; + } + + async fn run_local(&mut self) { + use prost::Message; + + while let Some((domain, mut stream)) = self.server.accept().await { + let ctx = self.ctx.clone(); + let handler = self.handler.clone(); + match domain.as_str() { +{% for method in methods %} + "{{ method.name }}.service" => { + tokio::task::spawn_local(async move { + if let Some(in_buf) = stream.read().await { + if let Ok(req) = {{ method.input }}::decode(in_buf.as_slice()) { + if let Some(res) = handler.{{ method.name }}(&ctx, req).await { + let out_buf = res.encode_to_vec(); + stream.write(&out_buf).await; + stream.close().await; + } + } + } + }); + } +{% endfor %} + _ => {} + } + } + } +} diff --git a/packages/protocol/proto/cluster_gateway.proto b/packages/protocol/proto/cluster_gateway.proto new file mode 100644 index 00000000..7fe0b422 --- /dev/null +++ b/packages/protocol/proto/cluster_gateway.proto @@ -0,0 +1,153 @@ +syntax = "proto3"; + +import "gateway.proto"; + +package cluster_gateway; + +message GatewayEvent { + oneof event { + PingEvent ping = 1; + } +} + +message PingEvent { + message MediaOrigin { + + } + + message GatewayOrigin { + message Location { + float lat = 1; + float lon = 2; + } + + uint32 zone = 1; + Location location= 3; + } + + message ServiceStats { + uint32 live = 1; + uint32 max = 2; + bool active = 3; + } + + oneof origin { + MediaOrigin media = 1; + GatewayOrigin gateway = 2; + } + + uint32 cpu = 3; + uint32 memory = 4; + uint32 disk = 5; + + ServiceStats webrtc = 6; +} + +message Empty {} + +service MediaEdge { + rpc WhipConnect (WhipConnectRequest) returns (WhipConnectResponse); + rpc WhipRemoteIce (WhipRemoteIceRequest) returns (WhipRemoteIceResponse); + rpc WhipClose (WhipCloseRequest) returns (WhipCloseResponse); + + rpc WhepConnect (WhepConnectRequest) returns (WhepConnectResponse); + rpc WhepRemoteIce (WhepRemoteIceRequest) returns (WhepRemoteIceResponse); + rpc WhepClose (WhepCloseRequest) returns (WhepCloseResponse); + + rpc WebrtcConnect (WebrtcConnectRequest) returns (WebrtcConnectResponse); + rpc WebrtcRemoteIce (WebrtcRemoteIceRequest) returns (WebrtcRemoteIceResponse); + rpc WebrtcRestartIce (WebrtcRestartIceRequest) returns (WebrtcRestartIceResponse); +} + +//For whip +message WhipConnectRequest { + string user_agent = 1; + string ip = 2; + string sdp = 3; + string room = 4; + string peer = 5; +} + +message WhipConnectResponse { + string conn = 1; + string sdp = 2; +} + +message WhipRemoteIceRequest { + string conn = 1; + string ice = 2; +} + +message WhipRemoteIceResponse { + string conn = 1; +} + +message WhipCloseRequest { + string conn = 1; +} + +message WhipCloseResponse { + string conn = 1; +} + +//For whep +message WhepConnectRequest { + string user_agent = 1; + string ip = 2; + string sdp = 3; + string room = 4; + string peer = 5; +} + +message WhepConnectResponse { + string conn = 1; + string sdp = 2; +} + +message WhepRemoteIceRequest { + string conn = 1; + string ice = 2; +} + +message WhepRemoteIceResponse { + string conn = 1; +} + +message WhepCloseRequest { + string conn = 1; +} + +message WhepCloseResponse { + string conn = 1; +} + +//For SDK +message WebrtcConnectRequest { + string user_agent = 1; + string ip = 2; + gateway.ConnectRequest req = 3; +} + +message WebrtcConnectResponse { + gateway.ConnectResponse res = 1; +} + +message WebrtcRemoteIceRequest { + string conn = 1; + repeated string candidates = 2; +} + +message WebrtcRemoteIceResponse { + uint32 added = 1; +} + +message WebrtcRestartIceRequest { + string conn = 1; + string user_agent = 2; + string ip = 3; + gateway.ConnectRequest req = 4; +} + +message WebrtcRestartIceResponse { + gateway.ConnectResponse res = 1; +} diff --git a/packages/protocol/src/endpoint.rs b/packages/protocol/src/endpoint.rs index f0d36a66..9d9dffe6 100644 --- a/packages/protocol/src/endpoint.rs +++ b/packages/protocol/src/endpoint.rs @@ -45,6 +45,10 @@ impl ConnLayer for ClusterConnId { fn up(self, _param: Self::UpParam) -> Self::Up { panic!("should not happen") } + + fn get_down_part(&self) -> Self::DownRes { + (self.node, self.node_session) + } } #[derive(Clone, Copy, PartialEq, Eq, Debug)] @@ -86,6 +90,10 @@ impl ConnLayer for ServerConnId { server_conn: self, } } + + fn get_down_part(&self) -> Self::DownRes { + self.worker + } } impl ConnLayer for usize { @@ -101,6 +109,10 @@ impl ConnLayer for usize { fn up(self, param: Self::UpParam) -> Self::Up { ServerConnId { index: self, worker: param } } + + fn get_down_part(&self) -> Self::DownRes { + () + } } #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/packages/protocol/src/gateway.rs b/packages/protocol/src/gateway.rs new file mode 100644 index 00000000..9546a173 --- /dev/null +++ b/packages/protocol/src/gateway.rs @@ -0,0 +1,5 @@ +pub fn generate_gateway_zone_tag(zone: u32) -> String { + format!("gateway-zone-{}", zone) +} + +pub const GATEWAY_RPC_PORT: u16 = 10000; diff --git a/packages/protocol/src/lib.rs b/packages/protocol/src/lib.rs index ce0fa6d3..c8dde912 100644 --- a/packages/protocol/src/lib.rs +++ b/packages/protocol/src/lib.rs @@ -1,4 +1,7 @@ pub mod endpoint; +pub mod gateway; pub mod media; pub mod protobuf; +pub mod rpc; +pub mod tokens; pub mod transport; diff --git a/packages/protocol/src/protobuf/cluster_gateway.rs b/packages/protocol/src/protobuf/cluster_gateway.rs new file mode 100644 index 00000000..a776ee70 --- /dev/null +++ b/packages/protocol/src/protobuf/cluster_gateway.rs @@ -0,0 +1,595 @@ +// This file is @generated by prost-build. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GatewayEvent { + #[prost(oneof = "gateway_event::Event", tags = "1")] + pub event: ::core::option::Option, +} +/// Nested message and enum types in `GatewayEvent`. +pub mod gateway_event { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Event { + #[prost(message, tag = "1")] + Ping(super::PingEvent), + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PingEvent { + #[prost(uint32, tag = "3")] + pub cpu: u32, + #[prost(uint32, tag = "4")] + pub memory: u32, + #[prost(uint32, tag = "5")] + pub disk: u32, + #[prost(message, optional, tag = "6")] + pub webrtc: ::core::option::Option, + #[prost(oneof = "ping_event::Origin", tags = "1, 2")] + pub origin: ::core::option::Option, +} +/// Nested message and enum types in `PingEvent`. +pub mod ping_event { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct MediaOrigin {} + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct GatewayOrigin { + #[prost(uint32, tag = "1")] + pub zone: u32, + #[prost(message, optional, tag = "3")] + pub location: ::core::option::Option, + } + /// Nested message and enum types in `GatewayOrigin`. + pub mod gateway_origin { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Location { + #[prost(float, tag = "1")] + pub lat: f32, + #[prost(float, tag = "2")] + pub lon: f32, + } + } + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct ServiceStats { + #[prost(uint32, tag = "1")] + pub live: u32, + #[prost(uint32, tag = "2")] + pub max: u32, + #[prost(bool, tag = "3")] + pub active: bool, + } + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Origin { + #[prost(message, tag = "1")] + Media(MediaOrigin), + #[prost(message, tag = "2")] + Gateway(GatewayOrigin), + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Empty {} +/// For whip +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct WhipConnectRequest { + #[prost(string, tag = "1")] + pub user_agent: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub ip: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub sdp: ::prost::alloc::string::String, + #[prost(string, tag = "4")] + pub room: ::prost::alloc::string::String, + #[prost(string, tag = "5")] + pub peer: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct WhipConnectResponse { + #[prost(string, tag = "1")] + pub conn: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub sdp: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct WhipRemoteIceRequest { + #[prost(string, tag = "1")] + pub conn: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub ice: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct WhipRemoteIceResponse { + #[prost(string, tag = "1")] + pub conn: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct WhipCloseRequest { + #[prost(string, tag = "1")] + pub conn: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct WhipCloseResponse { + #[prost(string, tag = "1")] + pub conn: ::prost::alloc::string::String, +} +/// For whep +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct WhepConnectRequest { + #[prost(string, tag = "1")] + pub user_agent: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub ip: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub sdp: ::prost::alloc::string::String, + #[prost(string, tag = "4")] + pub room: ::prost::alloc::string::String, + #[prost(string, tag = "5")] + pub peer: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct WhepConnectResponse { + #[prost(string, tag = "1")] + pub conn: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub sdp: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct WhepRemoteIceRequest { + #[prost(string, tag = "1")] + pub conn: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub ice: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct WhepRemoteIceResponse { + #[prost(string, tag = "1")] + pub conn: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct WhepCloseRequest { + #[prost(string, tag = "1")] + pub conn: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct WhepCloseResponse { + #[prost(string, tag = "1")] + pub conn: ::prost::alloc::string::String, +} +/// For SDK +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct WebrtcConnectRequest { + #[prost(string, tag = "1")] + pub user_agent: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub ip: ::prost::alloc::string::String, + #[prost(message, optional, tag = "3")] + pub req: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct WebrtcConnectResponse { + #[prost(message, optional, tag = "1")] + pub res: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct WebrtcRemoteIceRequest { + #[prost(string, tag = "1")] + pub conn: ::prost::alloc::string::String, + #[prost(string, repeated, tag = "2")] + pub candidates: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct WebrtcRemoteIceResponse { + #[prost(uint32, tag = "1")] + pub added: u32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct WebrtcRestartIceRequest { + #[prost(string, tag = "1")] + pub conn: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub user_agent: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub ip: ::prost::alloc::string::String, + #[prost(message, optional, tag = "4")] + pub req: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct WebrtcRestartIceResponse { + #[prost(message, optional, tag = "1")] + pub res: ::core::option::Option, +} +#[allow(async_fn_in_trait)] +pub trait MediaEdgeServiceHandler { + async fn whip_connect( + &self, + ctx: &CTX, + req: WhipConnectRequest, + ) -> Option; + async fn whip_remote_ice( + &self, + ctx: &CTX, + req: WhipRemoteIceRequest, + ) -> Option; + async fn whip_close( + &self, + ctx: &CTX, + req: WhipCloseRequest, + ) -> Option; + async fn whep_connect( + &self, + ctx: &CTX, + req: WhepConnectRequest, + ) -> Option; + async fn whep_remote_ice( + &self, + ctx: &CTX, + req: WhepRemoteIceRequest, + ) -> Option; + async fn whep_close( + &self, + ctx: &CTX, + req: WhepCloseRequest, + ) -> Option; + async fn webrtc_connect( + &self, + ctx: &CTX, + req: WebrtcConnectRequest, + ) -> Option; + async fn webrtc_remote_ice( + &self, + ctx: &CTX, + req: WebrtcRemoteIceRequest, + ) -> Option; + async fn webrtc_restart_ice( + &self, + ctx: &CTX, + req: WebrtcRestartIceRequest, + ) -> Option; +} +pub struct MediaEdgeServiceClient< + D, + C: crate::rpc::RpcClient, + S: crate::rpc::RpcStream, +> { + client: C, + _tmp: std::marker::PhantomData<(D, S)>, +} +impl, S: crate::rpc::RpcStream> Clone +for MediaEdgeServiceClient { + fn clone(&self) -> Self { + Self { + client: self.client.clone(), + _tmp: Default::default(), + } + } +} +impl< + D, + C: crate::rpc::RpcClient, + S: crate::rpc::RpcStream, +> MediaEdgeServiceClient { + pub fn new(client: C) -> Self { + Self { + client, + _tmp: Default::default(), + } + } + pub async fn whip_connect( + &self, + dest: D, + req: WhipConnectRequest, + ) -> Option { + use prost::Message; + let mut stream = self.client.connect(dest, "whip_connect.service").await?; + let out_buf = req.encode_to_vec(); + stream.write(&out_buf).await?; + let in_buf = stream.read().await?; + WhipConnectResponse::decode(in_buf.as_slice()).ok() + } + pub async fn whip_remote_ice( + &self, + dest: D, + req: WhipRemoteIceRequest, + ) -> Option { + use prost::Message; + let mut stream = self.client.connect(dest, "whip_remote_ice.service").await?; + let out_buf = req.encode_to_vec(); + stream.write(&out_buf).await?; + let in_buf = stream.read().await?; + WhipRemoteIceResponse::decode(in_buf.as_slice()).ok() + } + pub async fn whip_close( + &self, + dest: D, + req: WhipCloseRequest, + ) -> Option { + use prost::Message; + let mut stream = self.client.connect(dest, "whip_close.service").await?; + let out_buf = req.encode_to_vec(); + stream.write(&out_buf).await?; + let in_buf = stream.read().await?; + WhipCloseResponse::decode(in_buf.as_slice()).ok() + } + pub async fn whep_connect( + &self, + dest: D, + req: WhepConnectRequest, + ) -> Option { + use prost::Message; + let mut stream = self.client.connect(dest, "whep_connect.service").await?; + let out_buf = req.encode_to_vec(); + stream.write(&out_buf).await?; + let in_buf = stream.read().await?; + WhepConnectResponse::decode(in_buf.as_slice()).ok() + } + pub async fn whep_remote_ice( + &self, + dest: D, + req: WhepRemoteIceRequest, + ) -> Option { + use prost::Message; + let mut stream = self.client.connect(dest, "whep_remote_ice.service").await?; + let out_buf = req.encode_to_vec(); + stream.write(&out_buf).await?; + let in_buf = stream.read().await?; + WhepRemoteIceResponse::decode(in_buf.as_slice()).ok() + } + pub async fn whep_close( + &self, + dest: D, + req: WhepCloseRequest, + ) -> Option { + use prost::Message; + let mut stream = self.client.connect(dest, "whep_close.service").await?; + let out_buf = req.encode_to_vec(); + stream.write(&out_buf).await?; + let in_buf = stream.read().await?; + WhepCloseResponse::decode(in_buf.as_slice()).ok() + } + pub async fn webrtc_connect( + &self, + dest: D, + req: WebrtcConnectRequest, + ) -> Option { + use prost::Message; + let mut stream = self.client.connect(dest, "webrtc_connect.service").await?; + let out_buf = req.encode_to_vec(); + stream.write(&out_buf).await?; + let in_buf = stream.read().await?; + WebrtcConnectResponse::decode(in_buf.as_slice()).ok() + } + pub async fn webrtc_remote_ice( + &self, + dest: D, + req: WebrtcRemoteIceRequest, + ) -> Option { + use prost::Message; + let mut stream = self.client.connect(dest, "webrtc_remote_ice.service").await?; + let out_buf = req.encode_to_vec(); + stream.write(&out_buf).await?; + let in_buf = stream.read().await?; + WebrtcRemoteIceResponse::decode(in_buf.as_slice()).ok() + } + pub async fn webrtc_restart_ice( + &self, + dest: D, + req: WebrtcRestartIceRequest, + ) -> Option { + use prost::Message; + let mut stream = self.client.connect(dest, "webrtc_restart_ice.service").await?; + let out_buf = req.encode_to_vec(); + stream.write(&out_buf).await?; + let in_buf = stream.read().await?; + WebrtcRestartIceResponse::decode(in_buf.as_slice()).ok() + } +} +pub struct MediaEdgeServiceServer< + CTX, + H: MediaEdgeServiceHandler, + Sr: crate::rpc::RpcServer, + S: crate::rpc::RpcStream, +> { + ctx: std::sync::Arc, + handler: std::sync::Arc, + server: Sr, + _tmp: std::marker::PhantomData, +} +impl< + CTX: 'static + Clone, + H: 'static + MediaEdgeServiceHandler, + Sr: crate::rpc::RpcServer, + S: 'static + crate::rpc::RpcStream, +> MediaEdgeServiceServer { + pub fn new(server: Sr, ctx: CTX, handler: H) -> Self { + Self { + ctx: std::sync::Arc::new(ctx), + handler: std::sync::Arc::new(handler), + server, + _tmp: Default::default(), + } + } + pub async fn run(&mut self) { + let local = tokio::task::LocalSet::new(); + local + .run_until(async move { + self.run_local().await; + }) + .await; + } + async fn run_local(&mut self) { + use prost::Message; + while let Some((domain, mut stream)) = self.server.accept().await { + let ctx = self.ctx.clone(); + let handler = self.handler.clone(); + match domain.as_str() { + "whip_connect.service" => { + tokio::task::spawn_local(async move { + if let Some(in_buf) = stream.read().await { + if let Ok(req) = WhipConnectRequest::decode( + in_buf.as_slice(), + ) { + if let Some(res) = handler.whip_connect(&ctx, req).await { + let out_buf = res.encode_to_vec(); + stream.write(&out_buf).await; + stream.close().await; + } + } + } + }); + } + "whip_remote_ice.service" => { + tokio::task::spawn_local(async move { + if let Some(in_buf) = stream.read().await { + if let Ok(req) = WhipRemoteIceRequest::decode( + in_buf.as_slice(), + ) { + if let Some(res) = handler.whip_remote_ice(&ctx, req).await + { + let out_buf = res.encode_to_vec(); + stream.write(&out_buf).await; + stream.close().await; + } + } + } + }); + } + "whip_close.service" => { + tokio::task::spawn_local(async move { + if let Some(in_buf) = stream.read().await { + if let Ok(req) = WhipCloseRequest::decode( + in_buf.as_slice(), + ) { + if let Some(res) = handler.whip_close(&ctx, req).await { + let out_buf = res.encode_to_vec(); + stream.write(&out_buf).await; + stream.close().await; + } + } + } + }); + } + "whep_connect.service" => { + tokio::task::spawn_local(async move { + if let Some(in_buf) = stream.read().await { + if let Ok(req) = WhepConnectRequest::decode( + in_buf.as_slice(), + ) { + if let Some(res) = handler.whep_connect(&ctx, req).await { + let out_buf = res.encode_to_vec(); + stream.write(&out_buf).await; + stream.close().await; + } + } + } + }); + } + "whep_remote_ice.service" => { + tokio::task::spawn_local(async move { + if let Some(in_buf) = stream.read().await { + if let Ok(req) = WhepRemoteIceRequest::decode( + in_buf.as_slice(), + ) { + if let Some(res) = handler.whep_remote_ice(&ctx, req).await + { + let out_buf = res.encode_to_vec(); + stream.write(&out_buf).await; + stream.close().await; + } + } + } + }); + } + "whep_close.service" => { + tokio::task::spawn_local(async move { + if let Some(in_buf) = stream.read().await { + if let Ok(req) = WhepCloseRequest::decode( + in_buf.as_slice(), + ) { + if let Some(res) = handler.whep_close(&ctx, req).await { + let out_buf = res.encode_to_vec(); + stream.write(&out_buf).await; + stream.close().await; + } + } + } + }); + } + "webrtc_connect.service" => { + tokio::task::spawn_local(async move { + if let Some(in_buf) = stream.read().await { + if let Ok(req) = WebrtcConnectRequest::decode( + in_buf.as_slice(), + ) { + if let Some(res) = handler.webrtc_connect(&ctx, req).await { + let out_buf = res.encode_to_vec(); + stream.write(&out_buf).await; + stream.close().await; + } + } + } + }); + } + "webrtc_remote_ice.service" => { + tokio::task::spawn_local(async move { + if let Some(in_buf) = stream.read().await { + if let Ok(req) = WebrtcRemoteIceRequest::decode( + in_buf.as_slice(), + ) { + if let Some(res) = handler + .webrtc_remote_ice(&ctx, req) + .await + { + let out_buf = res.encode_to_vec(); + stream.write(&out_buf).await; + stream.close().await; + } + } + } + }); + } + "webrtc_restart_ice.service" => { + tokio::task::spawn_local(async move { + if let Some(in_buf) = stream.read().await { + if let Ok(req) = WebrtcRestartIceRequest::decode( + in_buf.as_slice(), + ) { + if let Some(res) = handler + .webrtc_restart_ice(&ctx, req) + .await + { + let out_buf = res.encode_to_vec(); + stream.write(&out_buf).await; + stream.close().await; + } + } + } + }); + } + _ => {} + } + } + } +} diff --git a/packages/protocol/src/protobuf/mod.rs b/packages/protocol/src/protobuf/mod.rs index a312513f..de7f7f0d 100644 --- a/packages/protocol/src/protobuf/mod.rs +++ b/packages/protocol/src/protobuf/mod.rs @@ -1,4 +1,7 @@ // This file is @generated by prost-build. +pub mod cluster_gateway { + include!("cluster_gateway.rs"); +} pub mod conn { include!("conn.rs"); } diff --git a/packages/protocol/src/rpc.rs b/packages/protocol/src/rpc.rs new file mode 100644 index 00000000..71d02d9f --- /dev/null +++ b/packages/protocol/src/rpc.rs @@ -0,0 +1,25 @@ +use std::net::{SocketAddr, SocketAddrV4}; + +#[cfg(feature = "quinn-rpc")] +pub mod quinn; + +#[allow(async_fn_in_trait)] +pub trait RpcStream { + async fn read(&mut self) -> Option>; + async fn write(&mut self, buf: &[u8]) -> Option<()>; + async fn close(&mut self); +} + +#[allow(async_fn_in_trait)] +pub trait RpcClient: Clone { + async fn connect(&self, dest: D, server_name: &str) -> Option; +} + +#[allow(async_fn_in_trait)] +pub trait RpcServer { + async fn accept(&mut self) -> Option<(String, S)>; +} + +pub fn node_vnet_addr(node_id: u32, port: u16) -> SocketAddr { + SocketAddr::V4(SocketAddrV4::new(node_id.into(), port)) +} diff --git a/packages/protocol/src/rpc/quinn.rs b/packages/protocol/src/rpc/quinn.rs new file mode 100644 index 00000000..ab7fdeb6 --- /dev/null +++ b/packages/protocol/src/rpc/quinn.rs @@ -0,0 +1,138 @@ +use std::net::SocketAddr; + +use quinn::{crypto::rustls::HandshakeData, Connection, Endpoint, Incoming, RecvStream, SendStream}; +use tokio::{ + io::{AsyncReadExt, AsyncWriteExt}, + sync::mpsc::{channel, Receiver, Sender}, + task::JoinHandle, +}; + +use super::{RpcClient, RpcServer, RpcStream}; + +pub struct QuinnServer { + rx: Receiver<(String, QuinnStream)>, + task: JoinHandle>, +} + +impl QuinnServer { + pub fn new(endpoint: Endpoint) -> Self { + let (tx, rx) = channel(10); + let task = tokio::spawn(Self::run_endpoint(endpoint, tx)); + Self { rx, task } + } + + async fn run_endpoint(endpoint: Endpoint, tx: Sender<(String, QuinnStream)>) -> Option<()> { + while let Some(incoming) = endpoint.accept().await { + let fx = Self::run_incoming(incoming, tx.clone()); + tokio::spawn(async move { + if let Err(e) = fx.await { + log::error!("Quinn Incoming error {:?}", e); + } + }); + } + Some(()) + } + + async fn run_incoming(incoming: Incoming, tx: Sender<(String, QuinnStream)>) -> Result<(), Box> { + let conn = incoming.await?; + let handshake = conn + .handshake_data() + .ok_or("MISSING_HANDSHAKE_DATA".to_string())? + .downcast::() + .map_err(|_| "MISSING_HANDSHAKE_DATA".to_string())?; + let server_name = handshake.server_name.ok_or("MISSING_SERVER_NAME".to_string())?; + let (send, recv) = conn.accept_bi().await?; + tx.send(( + server_name, + QuinnStream { + conn, + send, + recv, + buf: Vec::with_capacity(1500), + buf_goal: None, + }, + )) + .await + .map_err(|e| e.into()) + } +} + +impl Drop for QuinnServer { + fn drop(&mut self) { + self.task.abort(); + } +} + +impl RpcServer for QuinnServer { + async fn accept(&mut self) -> Option<(String, QuinnStream)> { + self.rx.recv().await + } +} + +#[derive(Clone)] +pub struct QuinnClient { + endpoint: Endpoint, +} + +impl QuinnClient { + pub fn new(endpoint: Endpoint) -> Self { + Self { endpoint } + } +} + +impl RpcClient for QuinnClient { + async fn connect(&self, dest: SocketAddr, server_name: &str) -> Option { + let conn = self.endpoint.connect(dest, server_name).ok()?.await.ok()?; + let (send, recv) = conn.open_bi().await.ok()?; + Some(QuinnStream { + conn, + send, + recv, + buf: Vec::with_capacity(1500), + buf_goal: None, + }) + } +} + +pub struct QuinnStream { + conn: Connection, + send: SendStream, + recv: RecvStream, + buf: Vec, + buf_goal: Option, +} + +impl QuinnStream {} + +impl RpcStream for QuinnStream { + async fn read(&mut self) -> Option> { + loop { + if let Some(buf_goal) = self.buf_goal { + let max_len = buf_goal - self.buf.len(); + if let Some(chunk) = self.recv.read_chunk(max_len, true).await.ok()? { + log::debug!("Got frame part {}/{buf_goal}", chunk.bytes.len()); + self.buf.extend_from_slice(&chunk.bytes); + if self.buf.len() == buf_goal { + self.buf_goal = None; + let res = std::mem::replace(&mut self.buf, Vec::with_capacity(1500)); + return Some(res); + } + } + } else { + let len = self.recv.read_u32().await.ok()?; + self.buf_goal = Some(len as usize); + log::debug!("Got frame len {}", len); + } + } + } + + async fn write(&mut self, buf: &[u8]) -> Option<()> { + log::debug!("Write frame len {}", buf.len()); + self.send.write_u32(buf.len() as u32).await.ok()?; + self.send.write_all(buf).await.ok() + } + + async fn close(&mut self) { + self.conn.closed().await; + } +} diff --git a/packages/protocol/src/tokens.rs b/packages/protocol/src/tokens.rs new file mode 100644 index 00000000..5ad12a70 --- /dev/null +++ b/packages/protocol/src/tokens.rs @@ -0,0 +1,19 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct WhipToken { + pub room: String, + pub peer: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct WhepToken { + pub room: String, + pub peer: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct WebrtcToken { + pub room: Option, + pub peer: Option, +} diff --git a/packages/protocol/src/transport.rs b/packages/protocol/src/transport.rs index 831f7e30..78782fd3 100644 --- a/packages/protocol/src/transport.rs +++ b/packages/protocol/src/transport.rs @@ -14,6 +14,7 @@ pub trait ConnLayer { fn down(self) -> (Self::Down, Self::DownRes); fn up(self, param: Self::UpParam) -> Self::Up; + fn get_down_part(&self) -> Self::DownRes; } #[derive(Debug, Clone, convert_enum::From, convert_enum::TryInto)] @@ -40,6 +41,14 @@ impl RpcReq { } } } + + pub fn get_conn_part(&self) -> Option { + match self { + Self::Whip(req) => req.get_down_part(), + Self::Whep(req) => req.get_down_part(), + Self::Webrtc(req) => req.get_down_part(), + } + } } #[derive(Debug, Clone, convert_enum::From, convert_enum::TryInto)] diff --git a/packages/protocol/src/transport/webrtc.rs b/packages/protocol/src/transport/webrtc.rs index 837597c4..c7512bf6 100644 --- a/packages/protocol/src/transport/webrtc.rs +++ b/packages/protocol/src/transport/webrtc.rs @@ -5,24 +5,24 @@ use crate::protobuf::gateway::{ConnectRequest, ConnectResponse, RemoteIceRequest #[derive(Debug, Clone)] pub enum RpcReq { - /// Ip, Token, Agent, Req - Connect(IpAddr, String, String, ConnectRequest), + /// Ip, Agent, Req + Connect(IpAddr, String, ConnectRequest), RemoteIce(Conn, RemoteIceRequest), - RestartIce(Conn, IpAddr, String, String, ConnectRequest), + RestartIce(Conn, IpAddr, String, ConnectRequest), Delete(Conn), } impl RpcReq { pub fn down(self) -> (RpcReq, Option) { match self { - RpcReq::Connect(ip_addr, token, user_agent, req) => (RpcReq::Connect(ip_addr, token, user_agent, req), None), + RpcReq::Connect(ip_addr, user_agent, req) => (RpcReq::Connect(ip_addr, user_agent, req), None), RpcReq::RemoteIce(conn, req) => { let (down, layer) = conn.down(); (RpcReq::RemoteIce(down, req), Some(layer)) } - RpcReq::RestartIce(conn, ip_addr, token, user_agent, req) => { + RpcReq::RestartIce(conn, ip_addr, user_agent, req) => { let (down, layer) = conn.down(); - (RpcReq::RestartIce(down, ip_addr, token, user_agent, req), Some(layer)) + (RpcReq::RestartIce(down, ip_addr, user_agent, req), Some(layer)) } RpcReq::Delete(conn) => { let (down, layer) = conn.down(); @@ -30,6 +30,15 @@ impl RpcReq { } } } + + pub fn get_down_part(&self) -> Option { + match self { + RpcReq::Connect(..) => None, + RpcReq::RemoteIce(conn, ..) => Some(conn.get_down_part()), + RpcReq::RestartIce(conn, ..) => Some(conn.get_down_part()), + RpcReq::Delete(conn, ..) => Some(conn.get_down_part()), + } + } } #[derive(Debug, Clone)] diff --git a/packages/protocol/src/transport/whep.rs b/packages/protocol/src/transport/whep.rs index 19d2c9e1..6b9ec573 100644 --- a/packages/protocol/src/transport/whep.rs +++ b/packages/protocol/src/transport/whep.rs @@ -1,11 +1,17 @@ use std::net::IpAddr; +use crate::{ + endpoint::{PeerId, RoomId}, + protobuf, +}; + use super::{ConnLayer, RpcResult}; #[derive(Debug, Clone)] pub struct WhepConnectReq { pub sdp: String, - pub token: String, + pub room: RoomId, + pub peer: PeerId, pub ip: IpAddr, pub user_agent: String, } @@ -54,6 +60,14 @@ impl RpcReq { } } } + + pub fn get_down_part(&self) -> Option { + match self { + RpcReq::Connect(req) => None, + RpcReq::RemoteIce(req) => Some(req.conn_id.get_down_part()), + RpcReq::Delete(req) => Some(req.conn_id.get_down_part()), + } + } } #[derive(Debug, Clone, convert_enum::From, convert_enum::TryInto)] @@ -76,3 +90,28 @@ impl RpcRes { } } } + +impl TryFrom for WhepConnectReq { + type Error = (); + fn try_from(value: protobuf::cluster_gateway::WhepConnectRequest) -> Result { + Ok(Self { + sdp: value.sdp, + room: value.room.into(), + peer: value.peer.into(), + ip: value.ip.parse().map_err(|_| ())?, + user_agent: value.user_agent, + }) + } +} + +impl Into for WhepConnectReq { + fn into(self) -> protobuf::cluster_gateway::WhepConnectRequest { + protobuf::cluster_gateway::WhepConnectRequest { + user_agent: self.user_agent, + ip: self.ip.to_string(), + sdp: self.sdp, + room: self.room.0, + peer: self.peer.0, + } + } +} diff --git a/packages/protocol/src/transport/whip.rs b/packages/protocol/src/transport/whip.rs index 9e49dd48..8158efff 100644 --- a/packages/protocol/src/transport/whip.rs +++ b/packages/protocol/src/transport/whip.rs @@ -1,11 +1,17 @@ use std::net::IpAddr; +use crate::{ + endpoint::{PeerId, RoomId}, + protobuf, +}; + use super::{ConnLayer, RpcResult}; #[derive(Debug, Clone)] pub struct WhipConnectReq { pub sdp: String, - pub token: String, + pub room: RoomId, + pub peer: PeerId, pub ip: IpAddr, pub user_agent: String, } @@ -54,6 +60,14 @@ impl RpcReq { } } } + + pub fn get_down_part(&self) -> Option { + match self { + RpcReq::Connect(req) => None, + RpcReq::RemoteIce(req) => Some(req.conn_id.get_down_part()), + RpcReq::Delete(req) => Some(req.conn_id.get_down_part()), + } + } } #[derive(Debug, Clone, convert_enum::From, convert_enum::TryInto)] @@ -76,3 +90,28 @@ impl RpcRes { } } } + +impl TryFrom for WhipConnectReq { + type Error = (); + fn try_from(value: protobuf::cluster_gateway::WhipConnectRequest) -> Result { + Ok(Self { + sdp: value.sdp, + room: value.room.into(), + peer: value.peer.into(), + ip: value.ip.parse().map_err(|_| ())?, + user_agent: value.user_agent, + }) + } +} + +impl Into for WhipConnectReq { + fn into(self) -> protobuf::cluster_gateway::WhipConnectRequest { + protobuf::cluster_gateway::WhipConnectRequest { + user_agent: self.user_agent, + ip: self.ip.to_string(), + sdp: self.sdp, + room: self.room.0, + peer: self.peer.0, + } + } +} diff --git a/packages/transport_webrtc/Cargo.toml b/packages/transport_webrtc/Cargo.toml index e29b0ac4..df4baf49 100644 --- a/packages/transport_webrtc/Cargo.toml +++ b/packages/transport_webrtc/Cargo.toml @@ -14,5 +14,6 @@ sans-io-runtime = { workspace = true, default-features = false } prost = { workspace = true } media-server-utils = { path = "../media_utils" } media-server-protocol = { path = "../protocol" } +media-server-secure = { path = "../media_secure" } media-server-core = { path = "../media_core" } str0m = { git = "https://github.com/algesten/str0m.git", rev = "d826257b06ac4a5562301eaa1fbac4ab3a6c04a2" } diff --git a/packages/transport_webrtc/src/lib.rs b/packages/transport_webrtc/src/lib.rs index 77ecec02..be422f0d 100644 --- a/packages/transport_webrtc/src/lib.rs +++ b/packages/transport_webrtc/src/lib.rs @@ -16,4 +16,6 @@ pub enum WebrtcError { RpcTrackNotAttached = 0x2004, RpcTrackAlreadyAttached = 0x2005, RpcEndpointNotFound = 0x2006, + RpcTokenInvalid = 0x2007, + RpcTokenRoomPeerNotMatch = 0x2008, } diff --git a/packages/transport_webrtc/src/transport.rs b/packages/transport_webrtc/src/transport.rs index 4fd3dc8f..bb67e018 100644 --- a/packages/transport_webrtc/src/transport.rs +++ b/packages/transport_webrtc/src/transport.rs @@ -1,6 +1,8 @@ use std::{ + marker::PhantomData, net::{IpAddr, SocketAddr}, ops::Deref, + sync::Arc, time::{Duration, Instant}, }; @@ -14,6 +16,7 @@ use media_server_protocol::{ protobuf::gateway::ConnectRequest, transport::{RpcError, RpcResult}, }; +use media_server_secure::MediaEdgeSecure; use media_server_utils::{RtpSeqExtend, Small2dMap}; use sans_io_runtime::{ backend::{BackendIncoming, BackendOutgoing}, @@ -39,10 +42,10 @@ mod webrtc; mod whep; mod whip; -pub enum VariantParams { +pub enum VariantParams { Whip(RoomId, PeerId), Whep(RoomId, PeerId), - Webrtc(IpAddr, String, String, ConnectRequest), + Webrtc(IpAddr, String, ConnectRequest, Arc), } #[derive(Debug, PartialEq, Eq)] @@ -54,7 +57,7 @@ pub enum Variant { pub enum ExtIn { RemoteIce(u64, Variant, Vec), - RestartIce(u64, Variant, IpAddr, String, String, ConnectRequest), + RestartIce(u64, Variant, IpAddr, String, ConnectRequest), } #[derive(Debug, PartialEq, Eq)] @@ -96,7 +99,7 @@ trait TransportWebrtcInternal { fn pop_output(&mut self, now: Instant) -> Option; } -pub struct TransportWebrtc { +pub struct TransportWebrtc { next_tick: Option, rtc: Rtc, rtc_ice_lite: bool, @@ -105,10 +108,11 @@ pub struct TransportWebrtc { local_convert: LocalMediaConvert, seq_extends: smallmap::Map, queue: DynamicDeque, 4>, + _tmp: PhantomData, } -impl TransportWebrtc { - pub fn new(variant: VariantParams, offer: &str, dtls_cert: DtlsCert, local_addrs: Vec<(SocketAddr, usize)>, rtc_ice_lite: bool) -> RpcResult<(Self, String, String)> { +impl TransportWebrtc { + pub fn new(variant: VariantParams, offer: &str, dtls_cert: DtlsCert, local_addrs: Vec<(SocketAddr, usize)>, rtc_ice_lite: bool) -> RpcResult<(Self, String, String)> { let offer = SdpOffer::from_sdp_string(offer).map_err(|_e| RpcError::new2(WebrtcError::InvalidSdp))?; let rtc_config = Rtc::builder() .set_rtp_mode(true) @@ -131,7 +135,7 @@ impl TransportWebrtc { let mut internal: Box = match variant { VariantParams::Whip(room, peer) => Box::new(whip::TransportWebrtcWhip::new(room, peer)), VariantParams::Whep(room, peer) => Box::new(whep::TransportWebrtcWhep::new(room, peer)), - VariantParams::Webrtc(_ip, _token, _user_agent, req) => { + VariantParams::Webrtc(_ip, _user_agent, req, secure) => { rtc.direct_api().create_data_channel(ChannelConfig { label: "data".to_string(), negotiated: Some(1000), @@ -140,7 +144,7 @@ impl TransportWebrtc { //we need to start sctp as client side for handling restart-ice in new server //if not, datachannel will not connect successful after reconnect to new server rtc.direct_api().start_sctp(true); - Box::new(webrtc::TransportWebrtcSdk::new(req)) + Box::new(webrtc::TransportWebrtcSdk::new(req, secure)) } }; @@ -165,6 +169,7 @@ impl TransportWebrtc { local_convert, seq_extends: Default::default(), queue: Default::default(), + _tmp: Default::default(), }, ice_ufrag, answer.to_sdp_string(), @@ -240,7 +245,7 @@ impl TransportWebrtc { } } -impl Transport for TransportWebrtc { +impl Transport for TransportWebrtc { fn on_tick(&mut self, now: Instant) { if let Some(next_tick) = self.next_tick { if next_tick <= now { @@ -289,7 +294,7 @@ impl Transport for TransportWebrtc { } self.queue.push_back(TransportOutput::Ext(ExtOut::RemoteIce(req_id, variant, Ok(success_count))).into()); } - ExtIn::RestartIce(req_id, variant, _ip, _useragent, _token, req) => { + ExtIn::RestartIce(req_id, variant, _ip, _useragent, req) => { if let Ok(offer) = SdpOffer::from_sdp_string(&req.sdp) { if let Ok(answer) = self.rtc.sdp_api().accept_offer(offer) { self.internal.on_codec_config(self.rtc.codec_config()); @@ -314,7 +319,7 @@ impl Transport for TransportWebrtc { } } -impl TaskSwitcherChild> for TransportWebrtc { +impl TaskSwitcherChild> for TransportWebrtc { type Time = Instant; fn pop_output(&mut self, now: Instant) -> Option> { diff --git a/packages/transport_webrtc/src/transport/webrtc.rs b/packages/transport_webrtc/src/transport/webrtc.rs index 674178a7..91087e62 100644 --- a/packages/transport_webrtc/src/transport/webrtc.rs +++ b/packages/transport_webrtc/src/transport/webrtc.rs @@ -1,4 +1,7 @@ -use std::time::{Duration, Instant}; +use std::{ + sync::Arc, + time::{Duration, Instant}, +}; use media_server_core::{ endpoint::{EndpointEvent, EndpointLocalTrackEvent, EndpointLocalTrackReq, EndpointRemoteTrackReq, EndpointReq, EndpointReqId, EndpointRes}, @@ -20,8 +23,10 @@ use media_server_protocol::{ gateway::ConnectRequest, shared::{sender::Status as ProtoSenderStatus, Kind}, }, + tokens::WebrtcToken, transport::{RpcError, RpcResult}, }; +use media_server_secure::MediaEdgeSecure; use prost::Message; use sans_io_runtime::{collections::DynamicDeque, return_if_err, return_if_none}; use str0m::{ @@ -58,7 +63,7 @@ enum TransportWebrtcError { Timeout, } -pub struct TransportWebrtcSdk { +pub struct TransportWebrtcSdk { join: Option<(RoomId, PeerId, Option, RoomInfoPublish, RoomInfoSubscribe)>, state: State, queue: DynamicDeque, @@ -68,10 +73,11 @@ pub struct TransportWebrtcSdk { remote_tracks: Vec, media_convert: RemoteMediaConvert, bwe_state: BweState, + secure: Arc, } -impl TransportWebrtcSdk { - pub fn new(req: ConnectRequest) -> Self { +impl TransportWebrtcSdk { + pub fn new(req: ConnectRequest, secure: Arc) -> Self { let tracks = req.tracks.unwrap_or_default(); Self { join: req @@ -85,6 +91,7 @@ impl TransportWebrtcSdk { event_seq: 0, media_convert: RemoteMediaConvert::default(), bwe_state: BweState::default(), + secure, } } @@ -130,7 +137,7 @@ impl TransportWebrtcSdk { } } -impl TransportWebrtcInternal for TransportWebrtcSdk { +impl TransportWebrtcInternal for TransportWebrtcSdk { fn on_codec_config(&mut self, cfg: &CodecConfig) { self.media_convert.set_config(cfg); } @@ -335,7 +342,10 @@ impl TransportWebrtcInternal for TransportWebrtcSdk { ))); } } - Str0mEvent::ChannelData(data) => self.on_str0m_channel_data(data), + Str0mEvent::ChannelData(data) => { + let event = return_if_err!(ClientEvent::decode(data.data.as_slice())); + self.on_str0m_channel_event(event); + } Str0mEvent::ChannelClose(_channel) => { log::info!("[TransportWebrtcSdk] channel closed, leave room {:?}", self.join); self.state = State::Disconnected; @@ -418,7 +428,7 @@ impl TransportWebrtcInternal for TransportWebrtcSdk { } } -impl TransportWebrtcSdk { +impl TransportWebrtcSdk { fn on_str0m_state(&mut self, now: Instant, state: IceConnectionState) { log::info!("[TransportWebrtcSdk] str0m state changed {:?}", state); @@ -486,8 +496,7 @@ impl TransportWebrtcSdk { } } - fn on_str0m_channel_data(&mut self, data: ChannelData) { - let event = return_if_err!(ClientEvent::decode(data.data.as_slice())); + fn on_str0m_channel_event(&mut self, event: ClientEvent) { log::info!("[TransportWebrtcSdk] on client event {:?}", event); match return_if_none!(event.event) { protobuf::conn::client_event::Event::Request(req) => match req.request { @@ -516,20 +525,28 @@ impl TransportWebrtcSdk { } ///This is for handling rpc from client -impl TransportWebrtcSdk { +impl TransportWebrtcSdk { fn on_session_req(&mut self, req_id: u32, req: protobuf::conn::request::session::Request) { let build_req = |req: EndpointReq| InternalOutput::TransportOutput(TransportOutput::RpcReq(req_id.into(), req)); match req { protobuf::conn::request::session::Request::Join(req) => { let info = req.info.unwrap_or_default(); let meta = PeerMeta { metadata: info.metadata }; - self.queue.push_back(build_req(EndpointReq::JoinRoom( - info.room.into(), - info.peer.into(), - meta, - info.publish.unwrap_or_default().into(), - info.subscribe.unwrap_or_default().into(), - ))); + if let Some(token) = self.secure.decode_obj::("webrtc", &req.token) { + if token.room == Some(info.room.clone()) && token.peer == Some(info.peer.clone()) { + self.queue.push_back(build_req(EndpointReq::JoinRoom( + info.room.into(), + info.peer.into(), + meta, + info.publish.unwrap_or_default().into(), + info.subscribe.unwrap_or_default().into(), + ))); + } else { + self.send_rpc_res_err(req_id, RpcError::new2(WebrtcError::RpcTokenRoomPeerNotMatch)); + } + } else { + self.send_rpc_res_err(req_id, RpcError::new2(WebrtcError::RpcTokenInvalid)); + } } protobuf::conn::request::session::Request::Leave(_req) => self.queue.push_back(build_req(EndpointReq::LeaveRoom)), protobuf::conn::request::session::Request::Sdp(req) => { @@ -639,7 +656,7 @@ impl TransportWebrtcSdk { #[cfg(test)] mod tests { - use std::time::Instant; + use std::{sync::Arc, time::Instant}; use media_server_core::{ endpoint::EndpointReq, @@ -647,7 +664,15 @@ mod tests { }; use media_server_protocol::{ endpoint::{PeerMeta, RoomInfoPublish, RoomInfoSubscribe}, - protobuf::{gateway, shared}, + protobuf::{ + conn::{self, client_event, ClientEvent}, + gateway, shared, + }, + tokens::WebrtcToken, + }; + use media_server_secure::{ + jwt::{MediaEdgeSecureJwt, MediaGatewaySecureJwt}, + MediaGatewaySecure, }; use str0m::channel::ChannelId; @@ -676,7 +701,8 @@ mod tests { let channel_id = create_channel_id(); let now = Instant::now(); - let mut transport = TransportWebrtcSdk::new(req); + let secure_jwt = Arc::new(MediaEdgeSecureJwt::from(b"1234".as_slice())); + let mut transport = TransportWebrtcSdk::new(req, secure_jwt.clone()); assert_eq!(transport.pop_output(now), None); transport.on_str0m_event(now, str0m::Event::ChannelOpen(channel_id, "data".to_string())); @@ -709,7 +735,9 @@ mod tests { let channel_id = create_channel_id(); let now = Instant::now(); - let mut transport = TransportWebrtcSdk::new(req); + let gateway_jwt = MediaGatewaySecureJwt::from(b"1234".as_slice()); + let secure_jwt = Arc::new(MediaEdgeSecureJwt::from(b"1234".as_slice())); + let mut transport = TransportWebrtcSdk::new(req, secure_jwt.clone()); assert_eq!(transport.pop_output(now), None); transport.on_str0m_event(now, str0m::Event::ChannelOpen(channel_id, "data".to_string())); @@ -718,6 +746,48 @@ mod tests { Some(InternalOutput::TransportOutput(TransportOutput::Event(TransportEvent::State(TransportState::Connected)))) ); assert_eq!(transport.pop_output(now), None); + + let token = gateway_jwt.encode_obj( + "webrtc", + WebrtcToken { + room: Some("demo".to_string()), + peer: Some("peer1".to_string()), + }, + 10000, + ); + transport.on_str0m_channel_event(ClientEvent { + seq: 0, + event: Some(client_event::Event::Request(conn::Request { + req_id: 1, + request: Some(conn::request::Request::Session(conn::request::Session { + request: Some(conn::request::session::Request::Join(conn::request::session::RoomJoin { + info: Some(shared::RoomJoin { + room: "demo".to_string(), + peer: "peer1".to_string(), + metadata: None, + publish: None, + subscribe: None, + }), + token: token.clone(), + })), + })), + })), + }); + + assert_eq!( + transport.pop_output(now), + Some(InternalOutput::TransportOutput(TransportOutput::RpcReq( + 1.into(), + EndpointReq::JoinRoom( + "demo".to_string().into(), + "peer1".to_string().into(), + PeerMeta { metadata: None }, + RoomInfoPublish { peer: false, tracks: false }, + RoomInfoSubscribe { peers: false, tracks: false } + ) + ))) + ); + assert_eq!(transport.pop_output(now), None); } //TODO test remote track non-source diff --git a/packages/transport_webrtc/src/worker.rs b/packages/transport_webrtc/src/worker.rs index 8bbec8b9..eff88b92 100644 --- a/packages/transport_webrtc/src/worker.rs +++ b/packages/transport_webrtc/src/worker.rs @@ -1,13 +1,14 @@ -use std::{collections::VecDeque, net::SocketAddr, time::Instant}; +use std::{collections::VecDeque, net::SocketAddr, sync::Arc, time::Instant}; use media_server_core::{ cluster::{ClusterEndpointControl, ClusterEndpointEvent, ClusterRoomHash}, endpoint::{Endpoint, EndpointCfg, EndpointInput, EndpointOutput}, }; use media_server_protocol::transport::{RpcError, RpcResult}; +use media_server_secure::MediaEdgeSecure; use sans_io_runtime::{ backend::{BackendIncoming, BackendOutgoing}, - group_owner_type, group_task, return_if_none, return_if_some, TaskSwitcher, TaskSwitcherChild, + group_owner_type, return_if_none, return_if_some, TaskGroup, TaskSwitcherChild, }; use str0m::change::DtlsCert; @@ -17,7 +18,6 @@ use crate::{ WebrtcError, }; -group_task!(Endpoints, Endpoint, EndpointInput, EndpointOutput); group_owner_type!(WebrtcOwner); pub enum GroupInput { @@ -36,28 +36,30 @@ pub enum GroupOutput { Continue, } -pub struct MediaWorkerWebrtc { +pub struct MediaWorkerWebrtc { ice_lite: bool, shared_port: SharedUdpPort, dtls_cert: DtlsCert, - endpoints: Endpoints, + endpoints: TaskGroup, EndpointOutput, Endpoint, ExtIn, ExtOut>, 16>, addrs: Vec<(SocketAddr, usize)>, queue: VecDeque, + secure: Arc, } -impl MediaWorkerWebrtc { - pub fn new(addrs: Vec, ice_lite: bool) -> Self { +impl MediaWorkerWebrtc { + pub fn new(addrs: Vec, ice_lite: bool, secure: Arc) -> Self { Self { ice_lite, shared_port: SharedUdpPort::default(), dtls_cert: DtlsCert::new_openssl(), - endpoints: Endpoints::default(), + endpoints: TaskGroup::default(), addrs: vec![], queue: VecDeque::from(addrs.iter().map(|addr| GroupOutput::Net(BackendOutgoing::UdpListen { addr: *addr, reuse: false })).collect::>()), + secure, } } - pub fn spawn(&mut self, variant: VariantParams, offer: &str) -> RpcResult<(bool, String, usize)> { + pub fn spawn(&mut self, variant: VariantParams, offer: &str) -> RpcResult<(bool, String, usize)> { let cfg = match &variant { VariantParams::Whip(_, _) => EndpointCfg { max_ingress_bitrate: 2_500_000, @@ -94,7 +96,7 @@ impl MediaWorkerWebrtc { } } -impl MediaWorkerWebrtc { +impl MediaWorkerWebrtc { pub fn tasks(&self) -> usize { self.endpoints.tasks() } @@ -119,7 +121,7 @@ impl MediaWorkerWebrtc { } GroupInput::Ext(owner, ext) => { log::info!("[MediaWorkerWebrtc] on ext to owner {:?}", owner); - if let Some(&Some(_)) = self.endpoints.tasks.get(owner.index()) { + if self.endpoints.has_task(owner.index()) { self.endpoints.on_event(now, owner.index(), EndpointInput::Ext(ext)); } else { match ext { @@ -127,8 +129,9 @@ impl MediaWorkerWebrtc { self.queue .push_back(GroupOutput::Ext(owner, ExtOut::RemoteIce(req_id, variant, Err(RpcError::new2(WebrtcError::RpcEndpointNotFound))))); } - ExtIn::RestartIce(req_id, variant, remote, useragent, token, req) => { - if let Ok((ice_lite, sdp, index)) = self.spawn(VariantParams::Webrtc(remote, useragent, token, req.clone()), &req.sdp) { + ExtIn::RestartIce(req_id, variant, remote, useragent, req) => { + let sdp = req.sdp.clone(); + if let Ok((ice_lite, sdp, index)) = self.spawn(VariantParams::Webrtc(remote, useragent, req, self.secure.clone()), &sdp) { self.queue.push_back(GroupOutput::Ext(index.into(), ExtOut::RestartIce(req_id, variant, Ok((ice_lite, sdp))))); } else { self.queue @@ -149,7 +152,7 @@ impl MediaWorkerWebrtc { } } -impl TaskSwitcherChild for MediaWorkerWebrtc { +impl TaskSwitcherChild for MediaWorkerWebrtc { type Time = Instant; fn pop_output(&mut self, now: Instant) -> Option { return_if_some!(self.queue.pop_front());