diff --git a/.cargo/audit.toml b/.cargo/audit.toml new file mode 100644 index 0000000..13fb85a --- /dev/null +++ b/.cargo/audit.toml @@ -0,0 +1,9 @@ +[output] +deny = ["unmaintained", "unsound", "yanked"] +quiet = false + +[advisories] +ignore = [ + # Ignore advisory for time; the issue depends on modifying the environment across threads. It's not impossible but it's very unlikely in our use cases. + "RUSTSEC-2020-0071", +] diff --git a/.dockerignore b/.dockerignore index 8339eb5..c98fc97 100644 --- a/.dockerignore +++ b/.dockerignore @@ -11,3 +11,4 @@ LICENSE README.md backend/fuzz backend/Dockerfile +frontend/Dockerfile diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3da2095..0bbfa88 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,7 @@ jobs: override: true profile: minimal components: rustfmt, clippy + - run: shellcheck **/*.sh - name: Environment run: rustup --version && cargo --version - name: Build diff --git a/BUILDING.md b/BUILDING.md index 061cadc..c87ca66 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -1,15 +1,19 @@ -# Building the Calling Backend +# Building ## For Development & Debugging cargo run --bin calling_backend -You can specify a variety of command line arguments. See the [config.rs file](/src/config.rs) file for -more details or run: +or - cargo run --bin calling_backend -- --help + cargo run --bin calling_frontend -An example for debugging would be: +You must specify a variety of command line arguments. See [backend config.rs file](/backend/src/config.rs) or +[frontend config.rs file](/frontend/src/config.rs) for more details or run either with the `--help` option. + +## Debugging with the Backend + +An example for debugging the backend would be: cargo run --bin calling_backend -- --binding-ip 192.168.1.100 --ice-candidate-ip 192.168.1.100 --diagnostics-interval-secs 1 @@ -42,44 +46,20 @@ to instruct the compiler to optimize for the CPU that is performing the build it ## For Deployment -Signal uses the provided Dockerfile to build images for deployment. This uses a multi-stage process, -creating a stage for building, the binary for delivery, and a runnable image for testing. +Signal uses Docker files to build images for deployment. This uses a multi-stage process, +creating a stage for building and a runnable image. ### Building the Docker Images -Images currently run on AWS EC2 instances supporting the Intel Skylake architecture. When building -the images, we can target that specific CPU (or choose any other that matches the platform where the -container will be run): +When building the images, we can target that specific CPU (or choose any other that matches the platform where the +container will be run, such as the Intel Skylake architecture): docker build -f backend/Dockerfile --build-arg rust_flags=-Ctarget-cpu=skylake -t signal-calling-backend . +or + + docker build -f frontend/Dockerfile --build-arg rust_flags=-Ctarget-cpu=skylake -t signal-calling-frontend . + The ```build-arg``` can also be omitted to maintain maximum compatibility. _Note: At the time of this writing, the skylake-avx512 target is not compatible with some dependencies._ - -### Deploying the Docker Image - -The deployment is specific to the type of service or registry being used. For testing, the -image can be saved and copied somewhere for running. To save: - - docker save signal-calling-backend:latest | gzip > signal-calling-backend-latest.tar.gz - -### Running the Docker Container - -To run the container, the following docker command can be used: - - docker run -d --rm -p 8080:8080 -p 10000:10000/udp signal-calling-backend:latest - -- ```-d``` runs the container in detached mode (can be omitted for easier testing) -- ```--rm``` will clean up the container when it is stopped -- ```-p 8080:8080``` connects the TCP port 8080 to the same one on the host -- ```-p 10000:10000/udp``` connects the UDP port 10000 to the same one on the host - -### Binary Deployment - -The docker file can also be used to obtain a binary file: - - docker build -f backend/Dockerfile --build-arg rust_flags=-Ctarget-cpu=skylake -t signal-calling-backend --target export-stage -o bin . - -This will build the calling_backend binary executable for Linux and copy it to the ./bin directory of -the host. The command will stop at the export-stage and not create the runnable docker image. diff --git a/Cargo.lock b/Cargo.lock index 1279780..64c46e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -39,27 +39,18 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.18" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" dependencies = [ "memchr", ] -[[package]] -name = "ansi_term" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" -dependencies = [ - "winapi", -] - [[package]] name = "anyhow" -version = "1.0.62" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1485d4d2cc45e7b201ee3767015c96faa5904387c9d87c6efdd0fb511f12d305" +checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" [[package]] name = "async-stream" @@ -112,9 +103,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "aws-config" -version = "0.46.0" +version = "0.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11a8c971b0cb0484fc9436a291a44503b95141edc36ce7a6af6b6d7a06a02ab0" +checksum = "b309b2154d224728d845a958c580834f24213037ed61b195da80c0b0fc7469fa" dependencies = [ "aws-http", "aws-sdk-sso", @@ -131,6 +122,7 @@ dependencies = [ "http", "hyper", "ring", + "time", "tokio", "tower", "tracing", @@ -139,11 +131,12 @@ dependencies = [ [[package]] name = "aws-endpoint" -version = "0.46.0" +version = "0.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bc956f415dda77215372e5bc751a2463d1f9a1ec34edf3edc6c0ff67e5c8e43" +checksum = "76f35c8f5877ad60db4f0d9dcdfbcb2233a8cc539f9e568df39ee0581ec62e89" dependencies = [ "aws-smithy-http", + "aws-smithy-types", "aws-types", "http", "regex", @@ -152,9 +145,9 @@ dependencies = [ [[package]] name = "aws-http" -version = "0.46.0" +version = "0.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a0d98a1d606aa24554e604f220878db4aa3b525b72f88798524497cc3867fc6" +checksum = "2f5422c9632d887968ccb66e2871a6d190d6104e276034912bee72ef58a5d890" dependencies = [ "aws-smithy-http", "aws-smithy-types", @@ -170,9 +163,9 @@ dependencies = [ [[package]] name = "aws-sdk-dynamodb" -version = "0.16.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c0797758933ee45a1021af0ed0745e05b82a3a57e3dde572eb7b3407b5da50e" +checksum = "0d757928810bbc52fe8bc3005689fb2465d4d62c280054693ec6d0162ce33bbc" dependencies = [ "aws-endpoint", "aws-http", @@ -193,9 +186,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "0.16.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baa0c66fab12976065403cf4cafacffe76afa91d0da335d195af379d4223d235" +checksum = "e2cc8b50281e1350d0b5c7207c2ce53c6721186ad196472caff4f20fa4b42e96" dependencies = [ "aws-endpoint", "aws-http", @@ -215,9 +208,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "0.16.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "048037cdfd7f42fb29b5f969c7f639b4b7eac00e8f911e4eac4f89fb7b3a0500" +checksum = "d6179f13c9fbab3226860f377354dece860e34ff129b69c7c1b0fa828d1e9c76" dependencies = [ "aws-endpoint", "aws-http", @@ -237,9 +230,9 @@ dependencies = [ [[package]] name = "aws-sig-auth" -version = "0.46.0" +version = "0.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8386fc0d218dbf2011f65bd8300d21ba98603fd150b962f61239be8b02d1fc6" +checksum = "b16f4d70c9c865af392eb40cacfe2bec3fa18f651fbdf49919cfc1dda13b189e" dependencies = [ "aws-sigv4", "aws-smithy-http", @@ -250,9 +243,9 @@ dependencies = [ [[package]] name = "aws-sigv4" -version = "0.46.0" +version = "0.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd866926c2c4978210bcb01d7d1b431c794f0c23ca9ee1e420204b018836b5fb" +checksum = "8d33790cecae42b999d197074c8a19e9b96b9e346284a6f93989e7489c9fa0f5" dependencies = [ "aws-smithy-http", "form_urlencoded", @@ -268,9 +261,9 @@ dependencies = [ [[package]] name = "aws-smithy-async" -version = "0.46.0" +version = "0.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb59cfdd21143006c01b9ca4dc4a9190b8c50c2ef831f9eb36f54f69efa42f1" +checksum = "bc604f278bae64bbd15854baa9c46ed69a56dfb0669d04aab80974749f2d6599" dependencies = [ "futures-util", "pin-project-lite", @@ -280,9 +273,9 @@ dependencies = [ [[package]] name = "aws-smithy-client" -version = "0.46.0" +version = "0.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44243329ba8618474c3b7f396de281f175ae172dd515b3d35648671a3cf51871" +checksum = "ec39585f8274fa543ad5c63cc09cbd435666be16b2cf99e4e07be5cf798bc050" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -303,9 +296,9 @@ dependencies = [ [[package]] name = "aws-smithy-http" -version = "0.46.0" +version = "0.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fba78f69a5bbe7ac1826389304c67b789032d813574e78f9a2d450634277f833" +checksum = "014a0ef5c4508fc2f6a9d3925c214725af19f020ea388db48e20196cc4cc9d6d" dependencies = [ "aws-smithy-types", "bytes", @@ -318,15 +311,15 @@ dependencies = [ "percent-encoding", "pin-project-lite", "tokio", - "tokio-util 0.7.3", + "tokio-util", "tracing", ] [[package]] name = "aws-smithy-http-tower" -version = "0.46.0" +version = "0.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff8a512d68350561e901626baa08af9491cfbd54596201b84b4da846a59e4da3" +checksum = "deecb478dc3cc40203e0e97ac0fb92947e0719754bbafd0026bdc49318e2fd03" dependencies = [ "aws-smithy-http", "bytes", @@ -339,18 +332,18 @@ dependencies = [ [[package]] name = "aws-smithy-json" -version = "0.46.0" +version = "0.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31b7633698853aae80bd8b26866531420138eca91ea4620735d20b0537c93c2e" +checksum = "6593456af93c4a39724f7dc9d239833102ab96c1d1e94c35ea79f0e55f9fd54c" dependencies = [ "aws-smithy-types", ] [[package]] name = "aws-smithy-query" -version = "0.46.0" +version = "0.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a94b5a8cc94a85ccbff89eb7bc80dc135ede02847a73d68c04ac2a3e4cf6b7" +checksum = "b803460b71645dfa9f6be47c4f00f91632f01e5bb01f9dc43890cd6cba983f08" dependencies = [ "aws-smithy-types", "urlencoding", @@ -358,9 +351,9 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "0.46.0" +version = "0.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d230d281653de22fb0e9c7c74d18d724a39d7148e2165b1e760060064c4967c0" +checksum = "e93b0c93a3b963da946a0b8ef3853a7252298eb75cdbfb21dad60f5ed0ded861" dependencies = [ "itoa", "num-integer", @@ -370,18 +363,18 @@ dependencies = [ [[package]] name = "aws-smithy-xml" -version = "0.46.0" +version = "0.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4aacaf6c0fa549ebe5d9daa96233b8635965721367ee7c69effc8d8078842df3" +checksum = "36b9efb4855b4acb29961a776d45680f3cbdd7c4783cbbae078da54c342575dd" dependencies = [ "xmlparser", ] [[package]] name = "aws-types" -version = "0.46.0" +version = "0.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb54f097516352475a0159c9355f8b4737c54044538a4d9aca4d376ef2361ccc" +checksum = "93f3f349b39781849261db1c727369923bb97007cf7bd0deb3a6e9e461c8d38f" dependencies = [ "aws-smithy-async", "aws-smithy-client", @@ -395,9 +388,9 @@ dependencies = [ [[package]] name = "axum" -version = "0.5.15" +version = "0.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de18bc5f2e9df8f52da03856bf40e29b747de5a84e43aefff90e3dc4a21529b" +checksum = "c9e3356844c4d6a6d6467b8da2cffb4a2820be256f50a3a386c9d152bab31043" dependencies = [ "async-trait", "axum-core", @@ -427,9 +420,9 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4f44a0e6200e9d11a1cdc989e4b358f6e3d354fbf48478f345a17f4e43f8635" +checksum = "d9f0c0a60006f2a293d82d571f635042a72edf927539b7685bd62d361963839b" dependencies = [ "async-trait", "bytes", @@ -437,6 +430,8 @@ dependencies = [ "http", "http-body", "mime", + "tower-layer", + "tower-service", ] [[package]] @@ -453,9 +448,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "block-buffer" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" dependencies = [ "generic-array", ] @@ -480,9 +475,9 @@ checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" [[package]] name = "bytes-utils" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1934a3ef9cac8efde4966a92781e77713e1ba329f1d42e446c7d7eba340d8ef1" +checksum = "e47d3a8076e283f3acd27400535992edb3ba4b5bb72f8891ad8fbe7932a7d4b9" dependencies = [ "bytes", "either", @@ -490,7 +485,7 @@ dependencies = [ [[package]] name = "calling_backend" -version = "1.4.3" +version = "1.5.0" dependencies = [ "aes", "aes-gcm", @@ -499,6 +494,7 @@ dependencies = [ "base64", "byteorder", "calling_common", + "clap", "crc", "env_logger", "futures", @@ -518,14 +514,13 @@ dependencies = [ "psutil", "rand", "rand_core 0.5.1", - "rand_core 0.6.3", + "rand_core 0.6.4", "rand_distr", "scopeguard", "serde", "serde_json", "sha-1", "sha2", - "structopt", "thiserror", "tokio", "unzip3", @@ -550,18 +545,19 @@ dependencies = [ [[package]] name = "calling_frontend" -version = "0.3.2" +version = "0.4.0" dependencies = [ "anyhow", "async-trait", "aws-config", "aws-sdk-dynamodb", + "aws-smithy-async", "aws-smithy-types", "aws-types", "axum", "base64", "calling_common", - "clap 3.2.17", + "clap", "env_logger", "futures", "hex", @@ -579,14 +575,12 @@ dependencies = [ "serde", "serde_dynamo", "serde_json", - "serde_yaml", "sha2", "subtle", "thiserror", "tokio", "tokio-stream", "tower", - "yaml-rust", ] [[package]] @@ -613,24 +607,9 @@ dependencies = [ [[package]] name = "clap" -version = "2.34.0" +version = "3.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" -dependencies = [ - "ansi_term", - "atty", - "bitflags", - "strsim 0.8.0", - "textwrap 0.11.0", - "unicode-width", - "vec_map", -] - -[[package]] -name = "clap" -version = "3.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29e724a68d9319343bb3328c9cc2dfde263f4b3142ee1059a9980580171c954b" +checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750" dependencies = [ "atty", "bitflags", @@ -638,18 +617,18 @@ dependencies = [ "clap_lex", "indexmap", "once_cell", - "strsim 0.10.0", + "strsim", "termcolor", - "textwrap 0.15.0", + "textwrap", ] [[package]] name = "clap_derive" -version = "3.2.17" +version = "3.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13547f7012c01ab4a0e8f8967730ada8f9fdf419e8b6c792788f39cf4e46eefa" +checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" dependencies = [ - "heck 0.4.0", + "heck", "proc-macro-error", "proc-macro2", "quote", @@ -683,9 +662,9 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "cpufeatures" -version = "0.2.2" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" dependencies = [ "libc", ] @@ -712,7 +691,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", - "rand_core 0.6.3", + "rand_core 0.6.4", "typenum", ] @@ -784,9 +763,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" dependencies = [ "block-buffer", "crypto-common", @@ -807,9 +786,9 @@ checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "env_logger" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +checksum = "c90bf5f19754d10198ccb95b70664fc925bd1fc090a0fd9a6ebc54acc8cd6272" dependencies = [ "atty", "humantime", @@ -863,11 +842,10 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" dependencies = [ - "matches", "percent-encoding", ] @@ -879,9 +857,9 @@ checksum = "85dcb89d2b10c5f6133de2efd8c11959ce9dbb46a2f7a4cab208c4eeda6ce1ab" [[package]] name = "futures" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab30e97ab6aacfe635fad58f22c2bb06c8b685f7421eb1e064a729e2a5f481fa" +checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c" dependencies = [ "futures-channel", "futures-core", @@ -894,9 +872,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bfc52cbddcfd745bf1740338492bb0bd83d76c67b445f91c5fb29fae29ecaa1" +checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" dependencies = [ "futures-core", "futures-sink", @@ -904,15 +882,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2acedae88d38235936c3922476b10fced7b2b68136f5e3c03c2d5be348a1115" +checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" [[package]] name = "futures-executor" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d11aa21b5b587a64682c0094c2bdd4df0076c5324961a40cc3abd7f37930528" +checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab" dependencies = [ "futures-core", "futures-task", @@ -921,15 +899,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93a66fc6d035a26a3ae255a6d2bca35eda63ae4c5512bef54449113f7a1228e5" +checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68" [[package]] name = "futures-macro" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0db9cce532b0eae2ccf2766ab246f114b56b9cf6d445e00c2549fbc100ca045d" +checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17" dependencies = [ "proc-macro2", "quote", @@ -938,21 +916,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca0bae1fe9752cf7fd9b0064c674ae63f97b37bc714d745cbde0afb7ec4e6765" +checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56" [[package]] name = "futures-task" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "842fc63b931f4056a24d59de13fb1272134ce261816e063e634ad0c15cdc5306" +checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" [[package]] name = "futures-util" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0828a5471e340229c11c77ca80017937ce3c58cb788a17e5f1c2d5c485a9577" +checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" dependencies = [ "futures-channel", "futures-core", @@ -1025,7 +1003,7 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.7.3", + "tokio-util", "tracing", ] @@ -1037,9 +1015,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "headers" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cff78e5788be1e0ab65b04d306b2ed5092c815ec97ec70f4ebd5aee158aa55d" +checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" dependencies = [ "base64", "bitflags", @@ -1048,7 +1026,7 @@ dependencies = [ "http", "httpdate", "mime", - "sha-1", + "sha1", ] [[package]] @@ -1060,15 +1038,6 @@ dependencies = [ "http", ] -[[package]] -name = "heck" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "heck" version = "0.4.0" @@ -1114,7 +1083,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.3", + "digest 0.10.5", ] [[package]] @@ -1147,9 +1116,9 @@ checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" [[package]] name = "httparse" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" @@ -1234,9 +1203,9 @@ dependencies = [ [[package]] name = "itertools" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] @@ -1249,9 +1218,9 @@ checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" [[package]] name = "js-sys" -version = "0.3.59" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" dependencies = [ "wasm-bindgen", ] @@ -1264,9 +1233,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.132" +version = "0.2.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" +checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966" [[package]] name = "libm" @@ -1274,17 +1243,11 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "292a948cd991e376cf75541fe5b97a1081d713c618b4f1b9500f8844e49eb565" -[[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" - [[package]] name = "lock_api" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" dependencies = [ "autocfg", "scopeguard", @@ -1308,12 +1271,6 @@ dependencies = [ "libc", ] -[[package]] -name = "matches" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" - [[package]] name = "matchit" version = "0.5.0" @@ -1485,9 +1442,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.13.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" [[package]] name = "opaque-debug" @@ -1532,9 +1489,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "petgraph" @@ -1652,9 +1609,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.43" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" +checksum = "3edcd08cf4fea98d1ae6c9ddd3b8ccb1acac7c3693d62625969a7daa04a2ae36" dependencies = [ "unicode-ident", ] @@ -1676,7 +1633,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f835c582e6bd972ba8347313300219fed5bfa52caf175298d860b61ff6069bb" dependencies = [ "bytes", - "heck 0.4.0", + "heck", "itertools", "lazy_static", "log", @@ -1743,7 +1700,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -1753,7 +1710,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -1767,9 +1724,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom 0.2.7", ] @@ -1868,6 +1825,15 @@ dependencies = [ "security-framework", ] +[[package]] +name = "rustls-pemfile" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" +dependencies = [ + "base64", +] + [[package]] name = "ryu" version = "1.0.11" @@ -1908,9 +1874,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.6.1" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" +checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" dependencies = [ "bitflags", "core-foundation", @@ -1931,24 +1897,24 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711" +checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" [[package]] name = "serde" -version = "1.0.143" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553" +checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.143" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391" +checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" dependencies = [ "proc-macro2", "quote", @@ -1957,9 +1923,9 @@ dependencies = [ [[package]] name = "serde_dynamo" -version = "4.0.4" +version = "4.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb17a242663eb9ad2793cca00badeed14197d8db7727186e1c8858841a68b9aa" +checksum = "ad9e01501a6fcea651a0500601d6186bc0dff830751774f4340235f445d1d429" dependencies = [ "aws-sdk-dynamodb", "serde", @@ -1967,9 +1933,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.83" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7" +checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" dependencies = [ "itoa", "ryu", @@ -1988,19 +1954,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_yaml" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f50845f68d5c693aac7d72a25415ddd21cb8182c04eafe447b73af55a05f9e1b" -dependencies = [ - "indexmap", - "itoa", - "ryu", - "serde", - "unsafe-libyaml", -] - [[package]] name = "sha-1" version = "0.10.0" @@ -2009,18 +1962,29 @@ checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.3", + "digest 0.10.5", +] + +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.5", ] [[package]] name = "sha2" -version = "0.10.2" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.3", + "digest 0.10.5", ] [[package]] @@ -2049,9 +2013,9 @@ checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" [[package]] name = "socket2" -version = "0.4.4" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" dependencies = [ "libc", "winapi", @@ -2072,42 +2036,12 @@ dependencies = [ "lock_api", ] -[[package]] -name = "strsim" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" - [[package]] name = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" -[[package]] -name = "structopt" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" -dependencies = [ - "clap 2.34.0", - "lazy_static", - "structopt-derive", -] - -[[package]] -name = "structopt-derive" -version = "0.4.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" -dependencies = [ - "heck 0.3.3", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "subtle" version = "2.4.1" @@ -2116,9 +2050,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.99" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" +checksum = "e90cde112c4b9690b8cbe810cba9ddd8bc1d7472e2cae317b69e9438c1cba7d2" dependencies = [ "proc-macro2", "quote", @@ -2174,33 +2108,24 @@ checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" [[package]] name = "textwrap" -version = "0.11.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width", -] - -[[package]] -name = "textwrap" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" +checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16" [[package]] name = "thiserror" -version = "1.0.32" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.32" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" dependencies = [ "proc-macro2", "quote", @@ -2209,9 +2134,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db76ff9fa4b1458b3c7f077f3ff9887394058460d21e634355b273aaf11eea45" +checksum = "3c3f9a28b618c3a6b9251b6908e9c99e04b9e5c02e6581ccbb67d59c34ef7f9b" dependencies = [ "libc", "num_threads", @@ -2219,9 +2144,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.20.1" +version = "1.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581" +checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" dependencies = [ "autocfg", "bytes", @@ -2229,7 +2154,6 @@ dependencies = [ "memchr", "mio", "num_cpus", - "once_cell", "parking_lot", "pin-project-lite", "signal-hook-registry", @@ -2262,9 +2186,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9" +checksum = "f6edf2d6bc038a43d31353570e27270603f4648d18f5ed10c0e179abe43255af" dependencies = [ "futures-core", "pin-project-lite", @@ -2273,23 +2197,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.6.10" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "log", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" dependencies = [ "bytes", "futures-core", @@ -2402,27 +2312,15 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" - -[[package]] -name = "unicode-segmentation" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" - -[[package]] -name = "unicode-width" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" +checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" [[package]] name = "unicode-xid" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "universal-hash" @@ -2434,12 +2332,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "unsafe-libyaml" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "931179334a56395bcf64ba5e0ff56781381c1a5832178280c7d7f91d1679aeb0" - [[package]] name = "untrusted" version = "0.7.1" @@ -2454,15 +2346,9 @@ checksum = "99c0ec316ab08201476c032feb2f94a5c8ece5b209765c1fbc4430dd6e931ad6" [[package]] name = "urlencoding" -version = "2.1.0" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b90931029ab9b034b300b797048cf23723400aa757e8a2bfb9d748102f9821" - -[[package]] -name = "vec_map" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9" [[package]] name = "version_check" @@ -2482,9 +2368,9 @@ dependencies = [ [[package]] name = "warp" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cef4e1e9114a4b7f1ac799f16ce71c14de5778500c5450ec6b7b920c55b587e" +checksum = "ed7b8be92646fc3d18b06147664ebc5f48d222686cb11a8755e561a735aacc6d" dependencies = [ "bytes", "futures-channel", @@ -2497,13 +2383,14 @@ dependencies = [ "mime_guess", "percent-encoding", "pin-project", + "rustls-pemfile", "scoped-tls", "serde", "serde_json", "serde_urlencoded", "tokio", "tokio-stream", - "tokio-util 0.6.10", + "tokio-util", "tower-service", "tracing", ] @@ -2522,9 +2409,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.82" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2532,9 +2419,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.82" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" dependencies = [ "bumpalo", "log", @@ -2547,9 +2434,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.82" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2557,9 +2444,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.82" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" dependencies = [ "proc-macro2", "quote", @@ -2570,15 +2457,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.82" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" [[package]] name = "web-sys" -version = "0.3.59" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed055ab27f941423197eb86b2035720b1a3ce40504df082cac2ecc6ed73335a1" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" dependencies = [ "js-sys", "wasm-bindgen", @@ -2596,13 +2483,13 @@ dependencies = [ [[package]] name = "which" -version = "4.2.5" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" +checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" dependencies = [ "either", - "lazy_static", "libc", + "once_cell", ] [[package]] @@ -2696,15 +2583,6 @@ version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "114ba2b24d2167ef6d67d7d04c8cc86522b87f490025f39f0303b7db5bf5e3d8" -[[package]] -name = "yaml-rust" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" -dependencies = [ - "linked-hash-map", -] - [[package]] name = "zeroize" version = "1.5.7" diff --git a/README.md b/README.md index 7ef9275..395242f 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,10 @@ Media forwarding server for group calls. Forwards media from 1 to N devices. +## Frontend + +Signaling server for group calls that helps direct client requests to appropriate backends. + # Thanks We thank WebRTC for the "googcc" congestion control algorithm (see googcc.rs for more details). diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 0496efa..2945218 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -5,7 +5,7 @@ [package] name = "calling_backend" -version = "1.4.3" +version = "1.5.0" authors = ["Calling Team "] edition = "2021" description = "Media forwarding server for group calls." @@ -22,7 +22,7 @@ scopeguard = "1.1" # For logging and command line operations log = "0.4" env_logger = "0.9" -structopt = "0.3" +clap = { version = "3.0", features = ["derive"] } # For runtime and threading tokio = { version = "1", features = ["full"] } diff --git a/backend/Dockerfile b/backend/Dockerfile index 3f96dbb..40d0fc3 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -3,10 +3,19 @@ # SPDX-License-Identifier: AGPL-3.0-only # -# Use the current rust environment for building. -FROM rust:1.58.1-buster AS build-stage +ARG debian_ver=bullseye + +FROM debian:${debian_ver} AS build-stage + +# Update system packages. RUN apt-get update \ - && apt-get install -y --no-install-recommends --no-install-suggests protobuf-compiler + && apt-get upgrade -y \ + && apt-get install -y --no-install-recommends --no-install-suggests curl build-essential ca-certificates protobuf-compiler \ + && update-ca-certificates + +# Install Rust. +RUN curl https://sh.rustup.rs -sSf | sh -s -- -y +ENV PATH="/root/.cargo/bin:${PATH}" # Take in a build argument to specify RUSTFLAGS environment, usually a target-cpu. ARG rust_flags @@ -30,19 +39,26 @@ RUN cargo build --bin calling_backend --release COPY . . RUN cargo build --bin calling_backend --release -# Export the calling_backend executable if the '-o' option is specified. -FROM scratch AS export-stage +# Create a minimal container to deploy and run the calling backend. +FROM debian:${debian_ver}-slim AS run-stage -COPY --from=build-stage /usr/src/calling-service/target/release/calling_backend calling_backend +# Update system packages. +RUN apt-get update \ + && apt-get upgrade -y \ + # Install curl for ip detection. + && apt-get install -y --no-install-recommends --no-install-suggests curl \ + # Install jq for parsing gcp metadata. + && apt-get install -y --no-install-recommends --no-install-suggests jq \ + # Cleanup unnecessary stuff. + && rm -rf /var/lib/apt/lists/* -# Create a minimal container to deploy and run the calling_backend. -FROM debian:buster-slim AS run-stage +COPY --from=build-stage /usr/src/calling-service/target/release/calling_backend /usr/local/bin/ +COPY backend/docker-entrypoint.sh /usr/local/bin/ + +USER nobody:nogroup # Expose http and udp server access ports to this container. EXPOSE 8080 EXPOSE 10000/udp -COPY --from=build-stage /usr/src/calling-service/target/release/calling_backend . -USER 1000 - -ENTRYPOINT ["./calling_backend"] +ENTRYPOINT ["docker-entrypoint.sh"] diff --git a/backend/docker-entrypoint.sh b/backend/docker-entrypoint.sh new file mode 100755 index 0000000..932bd48 --- /dev/null +++ b/backend/docker-entrypoint.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +# +# +# Copyright 2022 Signal Messenger, LLC +# SPDX-License-Identifier: AGPL-3.0-only +# +# + +if [[ -z "${EXTERNAL_IP}" ]]; then + EXTERNAL_IP="$(curl -Ss "http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/access-configs/0/external-ip" -H "Metadata-Flavor: Google")" + if [[ -z "${EXTERNAL_IP}" ]]; then + echo "Error: EXTERNAL_IP not defined!" + exit 1 + fi +fi + +if [[ -z "${INTERNAL_IP}" ]]; then + INTERNAL_IP="$(curl -Ss "http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/ip" -H "Metadata-Flavor: Google")" + if [[ -z "${INTERNAL_IP}" ]]; then + echo "Error: INTERNAL_IP not defined!" + exit 1 + fi +fi + +set -- calling_backend \ + --ice-candidate-ip "$EXTERNAL_IP" \ + --signaling-ip "$INTERNAL_IP" \ + "$@" + +"$@" diff --git a/backend/fuzz/README.md b/backend/fuzz/README.md index 401ae0a..4a77c64 100644 --- a/backend/fuzz/README.md +++ b/backend/fuzz/README.md @@ -1,7 +1,7 @@ This directory contains fuzz targets used with `cargo fuzz`. ``` -// In the top-level source directory +// In the backend source directory cargo install cargo-fuzz cargo fuzz list cargo +nightly fuzz run diff --git a/backend/src/call.rs b/backend/src/call.rs index 095fe9b..d410a1f 100644 --- a/backend/src/call.rs +++ b/backend/src/call.rs @@ -18,7 +18,11 @@ use log::*; use prost::Message; use thiserror::Error; -use crate::{audio, protos, rtp::{self, VideoRotation}, vp8}; +use crate::{ + audio, protos, + rtp::{self, VideoRotation}, + vp8, +}; pub const CLIENT_SERVER_DATA_SSRC: rtp::Ssrc = 1; pub const CLIENT_SERVER_DATA_PAYLOAD_TYPE: rtp::PayloadType = 101; diff --git a/backend/src/config.rs b/backend/src/config.rs index fcdbf5e..4f07f63 100644 --- a/backend/src/config.rs +++ b/backend/src/config.rs @@ -7,68 +7,67 @@ use std::net::SocketAddr; -use serde::Deserialize; -use structopt::StructOpt; +use clap; /// General configuration options, set by command line arguments or /// falls back to default or environment variables (in some cases). -#[derive(Default, StructOpt, Debug, Clone)] -#[structopt(name = "calling_backend")] +#[derive(Default, clap::Parser, Debug, Clone)] +#[clap(name = "calling_backend")] pub struct Config { /// The IP address to bind to for all servers. - #[structopt(long, default_value = "0.0.0.0")] + #[clap(long, default_value = "0.0.0.0")] pub binding_ip: String, /// The IP address to share for for ICE candidates. Clients will connect /// to the calling backend using this IP. - #[structopt(long, env = "ICE_CANDIDATE_IP")] + #[clap(long)] pub ice_candidate_ip: Option, /// The port to use for ICE candidates. Clients will connect to the /// calling backend using this port. - #[structopt(long, default_value = "10000")] + #[clap(long, default_value = "10000")] pub ice_candidate_port: u16, /// The IP address to share for direct access to the signaling_server. If /// defined, then the signaling_server will be used, otherwise the /// http_server will be used for testing. - #[structopt(long, env = "SIGNALING_IP")] + #[clap(long)] pub signaling_ip: Option, /// The port to use for the signaling interface. - #[structopt(long, default_value = "8080")] + #[clap(long, default_value = "8080")] pub signaling_port: u16, /// Maximum clients per call, if using the http_server for testing. - #[structopt(long, default_value = "8")] + #[clap(long, default_value = "8")] pub max_clients_per_call: u32, /// The initial bitrate target for sending. In a 16-person call with /// each base layer at 50kbps you'd need 800kbps to send them all. - #[structopt(long, default_value = "800")] + #[clap(long, default_value = "800")] pub initial_target_send_rate_kbps: u64, /// The min target send rate for sending. /// This affects the congestion controller (googcc). - #[structopt(long, default_value = "100")] + #[clap(long, default_value = "100")] pub min_target_send_rate_kbps: u64, /// The max target send rate for sending. /// This affects the congestion controller (googcc) /// and indirectly the maximum that any client can receive /// no matter how much the client requests. - #[structopt(long, default_value = "30000")] + #[clap(long, default_value = "30000")] pub max_target_send_rate_kbps: u64, /// If the client doesn't request a max send rate, /// use this as the max send rate. /// Affects the allocation of the target send rate, /// not the calculation of the of the target send rate. - #[structopt(long, default_value = "5000")] + #[clap(long, default_value = "5000")] pub default_requested_max_send_rate_kbps: u64, /// Timer tick period for operating on the Sfu state (ms). - #[structopt(long, default_value = "100")] + #[clap(long, default_value = "100")] pub tick_interval_ms: u64, /// How quickly we want to drain each outgoing queue. @@ -76,57 +75,49 @@ pub struct Config { /// It will push out other, lower-priority, streams to prioritize draining. /// The lower the value here, the higher the rate and the /// higher priority put on draining the outgoing queue. - #[structopt(long, default_value = "500")] + #[clap(long, default_value = "500")] pub outgoing_queue_drain_ms: u64, /// Optional interval used to post diagnostics to the log. If not defined /// then no periodic information about calls will be posted to the log. - #[structopt(long, env = "DIAGNOSTICS_INTERVAL_SECS")] + #[clap(long)] pub diagnostics_interval_secs: Option, /// Interval for sending active speaker messages (ms). The amount of time /// to wait between sending messages to the clients to remind them of the /// current active speaker for the call. Using milliseconds in case sub- /// second resolution is needed. - #[structopt(long, default_value = "1000")] + #[clap(long, default_value = "1000")] pub active_speaker_message_interval_ms: u64, /// Inactivity check interval (seconds). The amount of time to wait between /// iterating structures for inactive calls and clients. - #[structopt(long, default_value = "5")] + #[clap(long, default_value = "5")] pub inactivity_check_interval_secs: u64, /// Amount of time to wait before dropping a call or client due to inactivity (seconds). - #[structopt(long, default_value = "30")] + #[clap(long, default_value = "30")] pub inactivity_timeout_secs: u64, - #[structopt(flatten)] + #[clap(flatten)] pub metrics: MetricsOptions, } -#[derive(StructOpt, Clone, Debug, Default)] +#[derive(clap::Parser, Clone, Debug, Default)] pub struct MetricsOptions { /// Host and port of Datadog StatsD agent. Typically 127.0.0.1:8125. - #[structopt(long)] + #[clap(long)] pub datadog: Option, /// Region appears as a tag in metrics and logging. - #[structopt(long = "metrics-region", default_value = "unspecified")] + #[clap(long = "metrics-region", default_value = "unspecified")] pub region: String, /// Deployment version appears as a tag in metrics and in logging if specified. - #[structopt(long = "metrics-version")] + #[clap(long = "metrics-version")] pub version: Option, } -/// Deployment configuration options, used to set sensitive information -/// at runtime from a configuration file. -#[derive(Debug, Deserialize)] -pub struct DeploymentConfig { - #[serde(rename = "authenticationKey")] - pub authentication_key: String, -} - /// Returns the public address of the server for media/UDP as per configuration. pub fn get_server_media_address(config: &'static Config) -> SocketAddr { let ip = config diff --git a/backend/src/main.rs b/backend/src/main.rs index f4777d8..faf8412 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -15,9 +15,9 @@ use calling_backend::{ config, http_server, metrics_server, sfu::Sfu, signaling_server, udp_server, }; use calling_common::{DataRate, Duration, Instant}; +use clap::Parser; use env_logger::Env; use parking_lot::Mutex; -use structopt::StructOpt; use tokio::{ runtime, signal::unix::{signal, SignalKind}, @@ -26,7 +26,7 @@ use tokio::{ lazy_static! { // Load the config and treat it as a read-only static value. - static ref CONFIG: config::Config = config::Config::from_args(); + static ref CONFIG: config::Config = config::Config::parse(); } #[rustfmt::skip] diff --git a/frontend/Cargo.toml b/frontend/Cargo.toml index 5d1e5c3..70001de 100644 --- a/frontend/Cargo.toml +++ b/frontend/Cargo.toml @@ -5,7 +5,7 @@ [package] name = "calling_frontend" -version = "0.3.2" +version = "0.4.0" authors = ["Calling Team "] edition = "2021" description = "Frontend server for group calls." @@ -22,7 +22,6 @@ thiserror = "1.0" log = "0.4" env_logger = "0.9" clap = { version = "3.0", features = ["derive"] } -yaml-rust = "0.4" # For runtime and threading tokio = { version = "1", features = ["rt-multi-thread", "signal", "macros"] } @@ -35,8 +34,6 @@ base64 = "0.13" hex = { version = "0.4", features = ["serde"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -serde_yaml = "0.9" -serde_dynamo = { version = "4", features = ["aws-sdk-dynamodb+0_16"] } # For common and authentication rand = "0.8" @@ -49,13 +46,15 @@ hyper = { version = "0.14", features = ["full"] } axum = { version = "0.5", features = ["headers"] } tower = "0.4" mime = "0.3" +http = "0.2" # For storage access to DynamoDB -aws-types = { version = "0.46", features = ["hardcoded-credentials"] } -aws-smithy-types = "0.46" -aws-config = "0.46" -aws-sdk-dynamodb = "0.16" -http = "0.2" +aws-types = { version = "0.49", features = ["hardcoded-credentials"] } +aws-smithy-types = "0.49" +aws-smithy-async = "0.49" +aws-config = "0.49" +aws-sdk-dynamodb = "0.19" +serde_dynamo = { version = "4", features = ["aws-sdk-dynamodb+0_19"] } # For metrics parking_lot = "0.12" diff --git a/frontend/Dockerfile b/frontend/Dockerfile index f9ed9bb..19ca9ce 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -3,10 +3,14 @@ # SPDX-License-Identifier: AGPL-3.0-only # -# Use Debian stretch for now to ensure GLIBC < 2.28. -FROM debian:stretch-20220316 AS build-stage +ARG debian_ver=bullseye + +FROM debian:${debian_ver} AS build-stage + +# Update system packages. RUN apt-get update \ - && apt-get install -y --no-install-recommends --no-install-suggests build-essential curl ca-certificates \ + && apt-get upgrade -y \ + && apt-get install -y --no-install-recommends --no-install-suggests curl build-essential ca-certificates protobuf-compiler \ && update-ca-certificates # Install Rust. @@ -35,22 +39,27 @@ RUN cargo build --bin calling_frontend --release COPY . . RUN cargo build --bin calling_frontend --release -# Export the calling_frontend executable if the '-o' option is specified. -FROM scratch AS export-stage - -COPY --from=build-stage /usr/src/calling-service/target/release/calling_frontend calling_frontend - -# Create a minimal container to deploy and run the calling_frontend. -FROM debian:stretch-slim AS run-stage +# Create a minimal container to deploy and run the calling frontend. +FROM debian:${debian_ver}-slim AS run-stage +# Update system packages. RUN apt-get update \ + && apt-get upgrade -y \ && apt-get install -y --no-install-recommends --no-install-suggests ca-certificates \ - && update-ca-certificates + && update-ca-certificates \ + # Install curl for ip detection. + && apt-get install -y --no-install-recommends --no-install-suggests curl \ + # Install jq for parsing gcp metadata. + && apt-get install -y --no-install-recommends --no-install-suggests jq \ + # Cleanup unnecessary stuff. + && rm -rf /var/lib/apt/lists/* + +COPY --from=build-stage /usr/src/calling-service/target/release/calling_frontend /usr/local/bin/ +COPY frontend/docker-entrypoint.sh /usr/local/bin/ + +USER nobody:nogroup # Expose http server access ports to this container. -EXPOSE 8090 +EXPOSE 8080 -COPY --from=build-stage /usr/src/calling-service/target/release/calling_frontend . -USER 1000 - -ENTRYPOINT ["./calling_frontend"] +ENTRYPOINT ["docker-entrypoint.sh"] diff --git a/frontend/docker-entrypoint.sh b/frontend/docker-entrypoint.sh new file mode 100755 index 0000000..0d2b95d --- /dev/null +++ b/frontend/docker-entrypoint.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +# +# +# Copyright 2022 Signal Messenger, LLC +# SPDX-License-Identifier: AGPL-3.0-only +# +# + +if [[ -z "${REGION}" ]]; then + ZONE="$(curl -Ss "http://metadata.google.internal/computeMetadata/v1/instance/zone" -H "Metadata-Flavor: Google")" + REGION=$(echo "$ZONE" | awk -F/ '{ print $NF }' | awk -F- '{OFS="-"; NF--; print $0}') + if [[ -z "${REGION}" ]]; then + echo "Error: REGION not defined!" + exit 1 + fi +fi + +if [[ -z "${CALLING_AUTH_KEY}" ]]; then + if [[ -z "${SECRET_PROJECT}" ]]; then + echo "Error: SECRET_PROJECT not defined but needed to get calling-auth-key!" + exit 1 + fi + if [[ -z "${AUTH_SECRET_NAME}" ]]; then + echo "Error: AUTH_SECRET_NAME not defined but needed to get calling-auth-key!" + exit 1 + fi + TOKEN="$(curl -Ss "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token" -H "Metadata-Flavor: Google" | jq '.access_token')" + CALLING_AUTH_KEY="$(curl -Ss "https://secretmanager.googleapis.com/v1/projects/$SECRET_PROJECT/secrets/$AUTH_SECRET_NAME/versions/latest:access" -H "Metadata-Flavor: Google" -H "authorization: Bearer $TOKEN" | jq -r '.payload.data' | base64 --decode)" + + if [[ -z "${CALLING_AUTH_KEY}" ]]; then + echo "Error: CALLING_AUTH_KEY not defined!" + exit 1 + fi +fi + +set -- calling_frontend \ + --region "$REGION" \ + --authentication-key "$CALLING_AUTH_KEY" \ + "$@" + +"$@" diff --git a/frontend/src/api.rs b/frontend/src/api.rs index 8e66ba4..6f561e5 100644 --- a/frontend/src/api.rs +++ b/frontend/src/api.rs @@ -3,7 +3,6 @@ // SPDX-License-Identifier: AGPL-3.0-only // -mod v1; mod v2; use std::{ @@ -20,7 +19,7 @@ use axum::{ handler::Handler, middleware::{self, Next}, response::IntoResponse, - routing::{delete, get}, + routing::get, Extension, Router, }; use http::{header, Request, StatusCode}; @@ -117,14 +116,11 @@ async fn metrics( let latency = start.elapsed(); - let version; - if path.starts_with("/v1/") { - version = "v1"; - } else if path.starts_with("/v2/") { - version = "v2"; + let version = if path.starts_with("/v2/") { + "v2" } else { - version = "unknown"; - } + "unknown" + }; let mut api_metrics = frontend.api_metrics.lock(); @@ -253,14 +249,6 @@ fn app(frontend: Arc) -> Router { let health_route = Router::new().route("/health", get(get_health)); let routes = Router::new() - .route( - "/v1/conference/participants", - get(v1::get_participants).put(v1::join), - ) - .route( - "/v1/conference/participants/:endpoint_id", - delete(v1::leave), - ) .route( "/v2/conference/participants", get(v2::get_participants).put(v2::join), @@ -279,8 +267,6 @@ fn app(frontend: Arc) -> Router { } pub async fn start(frontend: Arc, ender_rx: Receiver<()>) -> Result<()> { - // TODO: Fix if address is already in use, be sure it brings down the whole server. - let addr = SocketAddr::new( IpAddr::from_str(&frontend.config.server_ip)?, frontend.config.server_port, diff --git a/frontend/src/api/v1.rs b/frontend/src/api/v1.rs deleted file mode 100644 index 72cb437..0000000 --- a/frontend/src/api/v1.rs +++ /dev/null @@ -1,1091 +0,0 @@ -// -// Copyright 2022 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only -// - -use std::{str, sync::Arc}; - -use anyhow::Result; -use axum::{ - extract::{OriginalUri, Path}, - response::{IntoResponse, Redirect}, - Extension, Json, -}; -use http::StatusCode; -use log::*; -use serde::{Deserialize, Serialize}; - -use crate::{ - authenticator::UserAuthorization, - frontend::{Frontend, JoinRequestWrapper, UserId}, -}; - -#[derive(Deserialize, Serialize, Debug)] -pub struct ParticipantsResponse { - #[serde(rename = "conferenceId")] - pub call_id: String, - #[serde(rename = "maxConferenceSize")] - pub max_devices: u32, - pub participants: Vec, - pub creator: String, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq)] -pub struct Candidate { - pub port: u16, - pub ip: String, - #[serde(rename = "type")] - pub candidate_type: String, -} - -#[derive(Deserialize, Serialize, Debug)] -pub struct Fingerprint { - pub fingerprint: String, - pub hash: String, -} - -#[derive(Deserialize, Serialize, Debug)] -pub struct Transport { - pub candidates: Vec, - #[serde(rename = "dhePublicKey")] - // Note: In the original v1, dhe_pub_key was optional, but now required because - // DTLS is no longer used. - pub dhe_public_key: String, - #[serde(rename = "hkdfExtraInfo")] - pub hkdf_extra_info: Option, - pub fingerprints: Option>, - pub ufrag: String, - pub pwd: String, -} - -#[derive(Deserialize, Serialize, Debug)] -pub struct PayloadParameters { - pub minptime: Option, - pub useinbandfec: Option, -} - -#[derive(Deserialize, Serialize, Debug)] -pub struct RtcpFbs { - #[serde(rename = "type")] - pub fbs_type: String, - pub subtype: Option, -} - -#[derive(Deserialize, Serialize, Debug)] -pub struct PayloadType { - pub id: u8, - pub name: String, - pub clockrate: u32, - pub channels: u32, - pub parameters: Option, - #[serde(rename = "rtcp-fbs")] - pub rtcp_fbs: Option>, -} - -#[derive(Deserialize, Serialize, Debug)] -pub struct HeaderExtension { - pub id: u32, - pub uri: String, -} - -#[derive(Deserialize, Serialize, Debug)] -pub struct SsrcGroup { - pub semantics: String, - pub sources: Vec, -} - -#[derive(Deserialize, Serialize, Debug)] -pub struct JoinRequest { - pub transport: Transport, - #[serde(rename = "audioPayloadType")] - pub audio_payload_type: PayloadType, - #[serde(rename = "videoPayloadType")] - pub video_payload_type: PayloadType, - #[serde(rename = "dataPayloadType")] - pub data_payload_type: PayloadType, - #[serde(rename = "audioHeaderExtensions")] - pub audio_header_extensions: Vec, - #[serde(rename = "videoHeaderExtensions")] - pub video_header_extensions: Vec, - #[serde(rename = "audioSsrcs")] - pub audio_ssrcs: Vec, - #[serde(rename = "audioSsrcGroups")] - pub audio_ssrc_groups: Vec, - #[serde(rename = "dataSsrcs")] - pub data_ssrcs: Vec, - #[serde(rename = "dataSsrcGroups")] - pub data_ssrc_groups: Vec, - #[serde(rename = "videoSsrcs")] - pub video_ssrcs: Vec, - #[serde(rename = "videoSsrcGroups")] - pub video_ssrc_groups: Vec, -} - -#[derive(Deserialize, Serialize, Debug)] -pub struct JoinResponse { - #[serde(rename = "endpointId")] - pub leave_request_token: String, - #[serde(rename = "opaqueUserId")] - pub opaque_user_id: UserId, - #[serde(rename = "ssrcPrefix")] - pub demux_id: u32, - pub transport: Transport, -} - -#[derive(Deserialize, Serialize, Debug, Clone)] -pub struct SfuParticipant { - #[serde(rename = "endpointId")] - pub client_id: String, - #[serde(rename = "ssrcPrefix")] - pub demux_id: u32, -} - -fn temporary_redirect(uri: &str) -> Result { - if http::HeaderValue::try_from(uri).is_ok() { - Ok(Redirect::temporary(uri).into_response()) - } else { - Err(StatusCode::INTERNAL_SERVER_ERROR) - } -} - -pub async fn get_participants( - Extension(frontend): Extension>, - Extension(user_authorization): Extension, - OriginalUri(original_uri): OriginalUri, -) -> Result { - trace!("get_participants:"); - - let call = frontend - .get_call_record(&user_authorization.group_id) - .await?; - - if let Some(redirect_uri) = frontend.get_redirect_uri(&call.backend_region, &original_uri) { - return temporary_redirect(&redirect_uri); - } - - let participants = frontend - .get_client_ids_in_call(&call) - .await? - .into_iter() - .map(|client_id| { - Ok(SfuParticipant { - demux_id: Frontend::get_demux_id_from_endpoint_id(&client_id)?.as_u32(), - client_id, - }) - }) - .collect::>>() - .map_err(|err| { - error!( - "get_participants: could not generate participants list: {}", - err - ); - StatusCode::INTERNAL_SERVER_ERROR - })?; - - Ok(Json(ParticipantsResponse { - call_id: call.call_id, - max_devices: frontend.config.max_clients_per_call, - participants, - creator: call.creator, - }) - .into_response()) -} - -pub async fn join( - Extension(frontend): Extension>, - Extension(user_authorization): Extension, - Json(request): Json, - OriginalUri(original_uri): OriginalUri, -) -> Result { - trace!("join:"); - - // Do some simple request verification. - if request.transport.dhe_public_key.is_empty() { - warn!("join: dhe_public_key is empty"); - return Err(StatusCode::BAD_REQUEST); - } - - let call = frontend - .get_or_create_call_record(&user_authorization) - .await?; - - if let Some(redirect_uri) = frontend.get_redirect_uri(&call.backend_region, &original_uri) { - return temporary_redirect(&redirect_uri); - } - - let response = frontend - .join_client_to_call( - &user_authorization.user_id, - &call, - JoinRequestWrapper { - ice_ufrag: request.transport.ufrag, - dhe_public_key: request.transport.dhe_public_key, - hkdf_extra_info: request.transport.hkdf_extra_info, - }, - ) - .await?; - - // Build the most minimal response possible (the client ignores everything else). - let candidate = Candidate { - port: response.port, - ip: response.ip, - candidate_type: "host".to_string(), - }; - - let candidates = vec![candidate]; - - let transport = Transport { - candidates, - dhe_public_key: response.dhe_public_key, - hkdf_extra_info: None, - fingerprints: None, - ufrag: response.ice_ufrag, - pwd: response.ice_pwd, - }; - - Ok(Json(JoinResponse { - demux_id: response.demux_id, - transport, - opaque_user_id: user_authorization.user_id, - leave_request_token: response.client_id, - }) - .into_response()) -} - -pub async fn leave( - Extension(_frontend): Extension>, - Extension(_user_authorization): Extension, - Path(_endpoint_id): Path, - OriginalUri(_original_uri): OriginalUri, -) -> Result { - trace!("leave:"); - - // A no-op since "leave via RTP" was released in December, 2021. - Ok(StatusCode::NO_CONTENT) -} - -#[cfg(test)] -mod api_server_v1_tests { - use super::*; - - use std::str; - use std::time::SystemTime; - - use hex::{FromHex, ToHex}; - use hmac::Mac; - use http::{header, Request}; - use hyper::Body; - use lazy_static::lazy_static; - use mockall::predicate::*; - use tower::ServiceExt; - - use crate::{ - api::app, - authenticator::{Authenticator, HmacSha256, GV2_AUTH_MATCH_LIMIT}, - backend::{self, MockBackend}, - config, - frontend::{DemuxId, FrontendIdGenerator, GroupId, MockIdGenerator}, - storage::{CallRecord, MockStorage}, - }; - - const AUTH_KEY: &str = "f00f0014fe091de31827e8d686969fad65013238aadd25ef8629eb8a9e5ef69b"; - - const USER_ID_1: &str = "1111111111111111"; - const USER_ID_2: &str = "2222222222222222"; - const GROUP_ID_1: &str = "aaaaaaaaaaaaaaaa"; - const CALL_ID_1: &str = "a1a1a1a1"; - const ENDPOINT_ID_1: &str = "1111111111111111-123456"; - const DEMUX_ID_1: u32 = 1070920496; - const ENDPOINT_ID_2: &str = "2222222222222222-987654"; - const DEMUX_ID_2: u32 = 1778901216; - const LOCAL_REGION: &str = "us-west-1"; - const ALT_REGION: &str = "ap-northeast-2"; - const REDIRECTED_URL: &str = "https://ap-northeast-2.test.com/v1/conference/participants"; - const CLIENT_ICE_UFRAG: &str = "client-ufrag"; - const CLIENT_DHE_PUBLIC_KEY: &str = "f924028e9b8021b77eb97b36f1d43e63"; - const BACKEND_ICE_UFRAG: &str = "backend-ufrag"; - const BACKEND_ICE_PWD: &str = "backend-password"; - const BACKEND_DHE_PUBLIC_KEY: &str = "24c41251f82b1f3481cce4bdaab8976a"; - - lazy_static! { - static ref CONFIG: config::Config = { - let mut config = config::default_test_config(); - config.authentication_key = AUTH_KEY.to_string(); - config.region = LOCAL_REGION.to_string(); - config.regional_url_template = "https://.test.com".to_string(); - config - }; - } - - fn generate_signed_v2_password( - user_id_hex: &str, - group_id_hex: &str, - timestamp: u64, - permission: &str, - key: &[u8; 32], - ) -> String { - // Format the credentials string. - let credentials = format!( - "2:{}:{}:{}:{}", - user_id_hex, group_id_hex, timestamp, permission - ); - - // Get the MAC for the credentials. - let mut hmac = HmacSha256::new_from_slice(key).unwrap(); - hmac.update(credentials.as_bytes()); - let mac = hmac.finalize().into_bytes(); - let mac = &mac[..GV2_AUTH_MATCH_LIMIT]; - - // Append the MAC to the credentials. - format!("{}:{}", credentials, mac.encode_hex::()) - } - - fn create_authorization_header(user_id: &str, permission: &str) -> String { - let timestamp = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap(); - - let password = generate_signed_v2_password( - user_id, - GROUP_ID_1, - timestamp.as_secs(), - permission, - &<[u8; 32]>::from_hex(AUTH_KEY).unwrap(), - ); - - // Append the username to the password, encode in base64, and prefix - // with 'Basic' for a valid authorization_header. - format!( - "Basic {}", - base64::encode(format!("{}:{}", user_id, password)) - ) - } - - fn create_authorization_header_for_user(user_id: &str) -> String { - create_authorization_header(user_id, "1") - } - - fn create_authorization_header_for_user_no_permission(user_id: &str) -> String { - create_authorization_header(user_id, "0") - } - - fn create_call_record(backend_region: &str) -> CallRecord { - CallRecord { - group_id: GROUP_ID_1.into(), - call_id: CALL_ID_1.to_string(), - backend_ip: "127.0.0.1".to_string(), - backend_region: backend_region.to_string(), - creator: USER_ID_1.to_string(), - } - } - - fn create_join_request() -> JoinRequest { - JoinRequest { - transport: Transport { - candidates: vec![], - dhe_public_key: CLIENT_DHE_PUBLIC_KEY.to_string(), - hkdf_extra_info: None, - fingerprints: None, - ufrag: CLIENT_ICE_UFRAG.to_string(), - pwd: "unused".to_string(), - }, - audio_payload_type: PayloadType { - id: 102, - name: "audio".to_string(), - clockrate: 48000, - channels: 2, - parameters: None, - rtcp_fbs: None, - }, - video_payload_type: PayloadType { - id: 108, - name: "video".to_string(), - clockrate: 90000, - channels: 0, - parameters: None, - rtcp_fbs: None, - }, - data_payload_type: PayloadType { - id: 101, - name: "data".to_string(), - clockrate: 90000, - channels: 0, - parameters: None, - rtcp_fbs: None, - }, - audio_header_extensions: vec![ - HeaderExtension { - id: 1, - uri: "random 1".to_string(), - }, - HeaderExtension { - id: 2, - uri: "random 2".to_string(), - }, - HeaderExtension { - id: 3, - uri: "random 3".to_string(), - }, - ], - video_header_extensions: vec![ - HeaderExtension { - id: 1, - uri: "random 1".to_string(), - }, - HeaderExtension { - id: 2, - uri: "random 2".to_string(), - }, - HeaderExtension { - id: 3, - uri: "random 3".to_string(), - }, - ], - audio_ssrcs: vec![1], - audio_ssrc_groups: vec![], - data_ssrcs: vec![1], - data_ssrc_groups: vec![], - video_ssrcs: vec![1, 2, 3, 4, 5, 6], - video_ssrc_groups: vec![ - SsrcGroup { - semantics: "SIM".to_string(), - sources: vec![1], - }, - SsrcGroup { - semantics: "FID".to_string(), - sources: vec![2], - }, - SsrcGroup { - semantics: "FID".to_string(), - sources: vec![3], - }, - SsrcGroup { - semantics: "FID".to_string(), - sources: vec![4], - }, - ], - } - } - - fn create_clients_response_two_calls() -> backend::ClientsResponse { - let client_ids = vec![ENDPOINT_ID_1.to_string(), ENDPOINT_ID_2.to_string()]; - - backend::ClientsResponse { client_ids } - } - - fn create_mocked_storage_unused() -> Box { - Box::new(MockStorage::new()) - } - - fn create_mocked_storage_no_call() -> Box { - let mut storage = Box::new(MockStorage::new()); - storage - .expect_get_call_record() - // group_id: &GroupId - .with(eq(GroupId::from(GROUP_ID_1))) - .once() - // Result> - .returning(|_| Ok(None)); - storage - } - - fn create_mocked_storage_with_call_for_region(region: String) -> Box { - let mut storage = Box::new(MockStorage::new()); - storage - .expect_get_call_record() - // group_id: &GroupId - .with(eq(GroupId::from(GROUP_ID_1))) - .once() - // Result> - .returning(move |_| Ok(Some(create_call_record(®ion)))); - storage - } - - fn create_mocked_backend_unused() -> Box { - Box::new(MockBackend::new()) - } - - fn create_mocked_backend_two_calls() -> Box { - let mut backend = Box::new(MockBackend::new()); - backend - .expect_get_clients() - // backend_address: &BackendAddress, call_id: &str, - .with( - eq(backend::Address::try_from("127.0.0.1").unwrap()), - eq(CALL_ID_1), - ) - .once() - // Result - .returning(|_, _| Ok(create_clients_response_two_calls())); - backend - } - - fn create_frontend( - config: &'static config::Config, - storage: Box, - backend: Box, - ) -> Arc { - Arc::new(Frontend { - config, - authenticator: Authenticator::from_hex_key(AUTH_KEY).unwrap(), - storage, - backend, - id_generator: Box::new(FrontendIdGenerator), - api_metrics: Default::default(), - }) - } - - fn create_frontend_with_id_generator( - config: &'static config::Config, - storage: Box, - backend: Box, - id_generator: Box, - ) -> Arc { - Arc::new(Frontend { - config, - authenticator: Authenticator::from_hex_key(AUTH_KEY).unwrap(), - storage, - backend, - id_generator, - api_metrics: Default::default(), - }) - } - - /// Invoke the "GET /v1/conference/participants" in the case where there is no call. - #[tokio::test] - async fn test_get_with_no_call() { - let config = &CONFIG; - - // Create mocked dependencies with expectations. - let storage = create_mocked_storage_no_call(); - let backend = create_mocked_backend_unused(); - - let frontend = create_frontend(config, storage, backend); - - // Create an axum application. - let app = app(frontend); - - // Create the request. - let request = Request::builder() - .method(http::Method::GET) - .uri("/v1/conference/participants") - .header(header::USER_AGENT, "test/user/agent") - .header( - header::AUTHORIZATION, - create_authorization_header_for_user(USER_ID_1), - ) - .body(Body::empty()) - .unwrap(); - - // Submit the request. - let response = app.oneshot(request).await.unwrap(); - assert_eq!(response.status(), StatusCode::NOT_FOUND); - } - - /// Invoke the "GET /v1/conference/participants" in the case where there is a call - /// with two participants. - #[tokio::test] - async fn test_get_with_call() { - let config = &CONFIG; - - // Create mocked dependencies with expectations. - let storage = create_mocked_storage_with_call_for_region(config.region.to_string()); - let backend = create_mocked_backend_two_calls(); - - let frontend = create_frontend(config, storage, backend); - - // Create an axum application. - let app = app(frontend); - - // Create the request. - let request = Request::builder() - .method(http::Method::GET) - .uri("/v1/conference/participants") - .header(header::USER_AGENT, "test/user/agent") - .header( - header::AUTHORIZATION, - create_authorization_header_for_user(USER_ID_1), - ) - .body(Body::empty()) - .unwrap(); - - // Submit the request. - let response = app.oneshot(request).await.unwrap(); - assert_eq!(response.status(), StatusCode::OK); - - let body = hyper::body::to_bytes(response.into_body()).await.unwrap(); - let participants_response: ParticipantsResponse = serde_json::from_slice(&body).unwrap(); - - assert_eq!(participants_response.call_id, CALL_ID_1); - assert_eq!( - participants_response.max_devices, - config.max_clients_per_call - ); - assert_eq!(participants_response.creator, USER_ID_1); - assert_eq!(participants_response.participants.len(), 2); - - assert_eq!( - participants_response.participants[0].client_id, - ENDPOINT_ID_1 - ); - assert_eq!(participants_response.participants[0].demux_id, DEMUX_ID_1); - assert_eq!( - participants_response.participants[1].client_id, - ENDPOINT_ID_2 - ); - assert_eq!(participants_response.participants[1].demux_id, DEMUX_ID_2); - } - - /// Invoke the "GET /v1/conference/participants" in the case where the call is in a - /// different region. - #[tokio::test] - async fn test_get_with_call_in_different_region() { - let config = &CONFIG; - - // Create mocked dependencies with expectations. - let storage = create_mocked_storage_with_call_for_region(ALT_REGION.to_string()); - let backend = create_mocked_backend_unused(); - - let frontend = create_frontend(config, storage, backend); - - // Create an axum application. - let app = app(frontend); - - // Create the request. - let request = Request::builder() - .method(http::Method::GET) - .uri("/v1/conference/participants") - .header(header::USER_AGENT, "test/user/agent") - .header( - header::AUTHORIZATION, - create_authorization_header_for_user(USER_ID_1), - ) - .body(Body::empty()) - .unwrap(); - - // Submit the request. - let response = app.oneshot(request).await.unwrap(); - assert_eq!(response.status(), StatusCode::TEMPORARY_REDIRECT); - assert_eq!( - response - .headers() - .get("Location") - .unwrap() - .to_str() - .unwrap(), - REDIRECTED_URL - ); - } - - /// Invoke the "PUT /v1/conference/participants" to join in the case where there is no call yet. - #[tokio::test] - async fn test_join_with_no_call() { - let config = &CONFIG; - - // Create mocked dependencies with expectations. - let mut storage = create_mocked_storage_no_call(); - let mut backend = Box::new(MockBackend::new()); - let mut id_generator = Box::new(MockIdGenerator::new()); - - // Create additional expectations. - backend - .expect_get_info() - .once() - // Result - .returning(|| { - Ok(backend::InfoResponse { - backend_direct_ip: "127.0.0.1".to_string(), - }) - }); - - id_generator - .expect_get_random_call_id() - .with(eq(16)) - .once() - .returning(|_| CALL_ID_1.to_string()); - - let expected_call_record = create_call_record(&config.region); - - storage - .expect_get_or_add_call_record() - // call: &CallRecord - .with(eq(expected_call_record)) - .once() - // Result, StorageError> - .returning(move |_| Ok(Some(create_call_record(&config.region)))); - - id_generator - .expect_get_random_demux_id_and_endpoint_id() - // user_id: &str - .with(eq(USER_ID_1)) - .once() - // Result<(DemuxId, String), FrontendError> - .returning(|_| Ok((DEMUX_ID_1.try_into().unwrap(), ENDPOINT_ID_1.to_string()))); - - let expected_demux_id: DemuxId = DEMUX_ID_1.try_into().unwrap(); - - backend - .expect_join() - // backend_address: &BackendAddress, call_id: &str, demux_id: DemuxId, join_request: &JoinRequest, - .with( - eq(backend::Address::try_from("127.0.0.1").unwrap()), - eq(CALL_ID_1), - eq(expected_demux_id), - eq(backend::JoinRequest { - client_id: ENDPOINT_ID_1.to_string(), - ice_ufrag: CLIENT_ICE_UFRAG.to_string(), - dhe_public_key: Some(CLIENT_DHE_PUBLIC_KEY.to_string()), - hkdf_extra_info: None, - }), - ) - .once() - // Result - .returning(|_, _, _, _| { - Ok(backend::JoinResponse { - ip: "127.0.0.1".to_string(), - port: 8080, - ice_ufrag: BACKEND_ICE_UFRAG.to_string(), - ice_pwd: BACKEND_ICE_PWD.to_string(), - dhe_public_key: Some(BACKEND_DHE_PUBLIC_KEY.to_string()), - }) - }); - - let frontend = create_frontend_with_id_generator(config, storage, backend, id_generator); - - // Create an axum application. - let app = app(frontend); - - // Create the request. - let join_request = create_join_request(); - - let request = Request::builder() - .method(http::Method::PUT) - .uri("/v1/conference/participants") - .header(header::USER_AGENT, "test/user/agent") - .header(header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref()) - .header( - header::AUTHORIZATION, - create_authorization_header_for_user(USER_ID_1), - ) - .body(Body::from(serde_json::to_vec(&join_request).unwrap())) - .unwrap(); - - // Submit the request. - let response = app.oneshot(request).await.unwrap(); - assert_eq!(response.status(), StatusCode::OK); - - let body = hyper::body::to_bytes(response.into_body()).await.unwrap(); - let join_response: JoinResponse = serde_json::from_slice(&body).unwrap(); - assert_eq!(join_response.demux_id, DEMUX_ID_1); - assert_eq!(join_response.leave_request_token, ENDPOINT_ID_1.to_string()); - assert_eq!(join_response.opaque_user_id, USER_ID_1.to_string()); - assert!(join_response.transport.candidates.contains(&Candidate { - port: 8080, - ip: "127.0.0.1".to_string(), - candidate_type: "host".to_string() - })); - assert_eq!(join_response.transport.ufrag, BACKEND_ICE_UFRAG.to_string()); - assert_eq!(join_response.transport.pwd, BACKEND_ICE_PWD.to_string()); - assert_eq!( - join_response.transport.dhe_public_key, - BACKEND_DHE_PUBLIC_KEY.to_string() - ); - } - - /// Invoke the "PUT /v1/conference/participants" to join in the case where there is a call. - #[tokio::test] - async fn test_join_with_call() { - let config = &CONFIG; - - // Create mocked dependencies with expectations. - let storage = create_mocked_storage_with_call_for_region(config.region.to_string()); - let mut backend = Box::new(MockBackend::new()); - let mut id_generator = Box::new(MockIdGenerator::new()); - - // Create additional expectations. - id_generator - .expect_get_random_demux_id_and_endpoint_id() - // user_id: &str - .with(eq(USER_ID_2)) - .once() - // Result<(DemuxId, String), FrontendError> - .returning(|_| Ok((DEMUX_ID_2.try_into().unwrap(), ENDPOINT_ID_2.to_string()))); - - let expected_demux_id: DemuxId = DEMUX_ID_2.try_into().unwrap(); - - backend - .expect_join() - // backend_address: &BackendAddress, call_id: &str, demux_id: DemuxId, join_request: &JoinRequest, - .with( - eq(backend::Address::try_from("127.0.0.1").unwrap()), - eq(CALL_ID_1), - eq(expected_demux_id), - eq(backend::JoinRequest { - client_id: ENDPOINT_ID_2.to_string(), - ice_ufrag: CLIENT_ICE_UFRAG.to_string(), - dhe_public_key: Some(CLIENT_DHE_PUBLIC_KEY.to_string()), - hkdf_extra_info: None, - }), - ) - .once() - // Result - .returning(|_, _, _, _| { - Ok(backend::JoinResponse { - ip: "127.0.0.1".to_string(), - port: 8080, - ice_ufrag: BACKEND_ICE_UFRAG.to_string(), - ice_pwd: BACKEND_ICE_PWD.to_string(), - dhe_public_key: Some(BACKEND_DHE_PUBLIC_KEY.to_string()), - }) - }); - - let frontend = create_frontend_with_id_generator(config, storage, backend, id_generator); - - // Create an axum application. - let app = app(frontend); - - // Create the request. - let join_request = create_join_request(); - - let request = Request::builder() - .method(http::Method::PUT) - .uri("/v1/conference/participants") - .header(header::USER_AGENT, "test/user/agent") - .header(header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref()) - .header( - header::AUTHORIZATION, - create_authorization_header_for_user(USER_ID_2), - ) - .body(Body::from(serde_json::to_vec(&join_request).unwrap())) - .unwrap(); - - // Submit the request. - let response = app.oneshot(request).await.unwrap(); - assert_eq!(response.status(), StatusCode::OK); - - let body = hyper::body::to_bytes(response.into_body()).await.unwrap(); - let join_response: JoinResponse = serde_json::from_slice(&body).unwrap(); - assert_eq!(join_response.demux_id, DEMUX_ID_2); - assert_eq!(join_response.leave_request_token, ENDPOINT_ID_2.to_string()); - assert_eq!(join_response.opaque_user_id, USER_ID_2.to_string()); - assert!(join_response.transport.candidates.contains(&Candidate { - port: 8080, - ip: "127.0.0.1".to_string(), - candidate_type: "host".to_string() - })); - assert_eq!(join_response.transport.ufrag, BACKEND_ICE_UFRAG.to_string()); - assert_eq!(join_response.transport.pwd, BACKEND_ICE_PWD.to_string()); - assert_eq!( - join_response.transport.dhe_public_key, - BACKEND_DHE_PUBLIC_KEY.to_string() - ); - } - - /// Invoke the "PUT /v1/conference/participants" to join in the case where the call is - /// in a different region. - #[tokio::test] - async fn test_join_with_call_in_different_region() { - let config = &CONFIG; - - // Create mocked dependencies with expectations. - let storage = create_mocked_storage_with_call_for_region(ALT_REGION.to_string()); - let backend = create_mocked_backend_unused(); - - let frontend = create_frontend(config, storage, backend); - - // Create an axum application. - let app = app(frontend); - - // Create the request. - let join_request = create_join_request(); - - let request = Request::builder() - .method(http::Method::PUT) - .uri("/v1/conference/participants") - .header(header::USER_AGENT, "test/user/agent") - .header(header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref()) - .header( - header::AUTHORIZATION, - create_authorization_header_for_user(USER_ID_2), - ) - .body(Body::from(serde_json::to_vec(&join_request).unwrap())) - .unwrap(); - - // Submit the request. - let response = app.oneshot(request).await.unwrap(); - assert_eq!(response.status(), StatusCode::TEMPORARY_REDIRECT); - assert_eq!( - response - .headers() - .get("Location") - .unwrap() - .to_str() - .unwrap(), - REDIRECTED_URL - ); - } - - /// Invoke the "PUT /v1/conference/participants" to join with an empty DHE public key. - #[tokio::test] - async fn test_join_with_empty_dhe_public_key() { - let config = &CONFIG; - - // Create mocked dependencies with expectations. - let storage = create_mocked_storage_unused(); - let backend = create_mocked_backend_unused(); - - let frontend = create_frontend(config, storage, backend); - - // Create an axum application. - let app = app(frontend); - - // Create the request. - let mut join_request = create_join_request(); - join_request.transport.dhe_public_key = "".to_string(); - - let request = Request::builder() - .method(http::Method::PUT) - .uri("/v1/conference/participants") - .header(header::USER_AGENT, "test/user/agent") - .header(header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref()) - .header( - header::AUTHORIZATION, - create_authorization_header_for_user(USER_ID_1), - ) - .body(Body::from(serde_json::to_vec(&join_request).unwrap())) - .unwrap(); - - // Submit the request. - let response = app.oneshot(request).await.unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); - } - - /// Invoke the "PUT /v1/conference/participants" to join in the case where there is no call yet - /// but the user has no permission to create a call. - #[tokio::test] - async fn test_join_with_no_call_no_create_permission() { - let config = &CONFIG; - - // Create mocked dependencies with expectations. - let storage = create_mocked_storage_no_call(); - let backend = create_mocked_backend_unused(); - - let frontend = create_frontend(config, storage, backend); - - // Create an axum application. - let app = app(frontend); - - // Create the request. - let join_request = create_join_request(); - - let request = Request::builder() - .method(http::Method::PUT) - .uri("/v1/conference/participants") - .header(header::USER_AGENT, "test/user/agent") - .header(header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref()) - .header( - header::AUTHORIZATION, - create_authorization_header_for_user_no_permission(USER_ID_1), - ) - .body(Body::from(serde_json::to_vec(&join_request).unwrap())) - .unwrap(); - - // Submit the request. - let response = app.oneshot(request).await.unwrap(); - assert_eq!(response.status(), StatusCode::FORBIDDEN); - } - - /// Invoke the "PUT /v1/conference/participants" to join in the case where the request is - /// missing the authorization header. - #[tokio::test] - async fn test_join_with_no_authorization_header() { - let config = &CONFIG; - - // Create mocked dependencies with expectations. - let storage = create_mocked_storage_unused(); - let backend = create_mocked_backend_unused(); - - let frontend = create_frontend(config, storage, backend); - - // Create an axum application. - let app = app(frontend); - - // Create the request. - let join_request = create_join_request(); - - let request = Request::builder() - .method(http::Method::PUT) - .uri("/v1/conference/participants") - .header(header::USER_AGENT, "test/user/agent") - .header(header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref()) - .body(Body::from(serde_json::to_vec(&join_request).unwrap())) - .unwrap(); - - // Submit the request. - let response = app.oneshot(request).await.unwrap(); - assert_eq!(response.status(), StatusCode::UNAUTHORIZED); - } - - /// Invoke the "PUT /v1/conference/participants" to join in the case where the authorization - /// header is empty. - #[tokio::test] - async fn test_join_with_empty_authorization_header() { - let config = &CONFIG; - - // Create mocked dependencies with expectations. - let storage = create_mocked_storage_unused(); - let backend = create_mocked_backend_unused(); - - let frontend = create_frontend(config, storage, backend); - - // Create an axum application. - let app = app(frontend); - - // Create the request. - let join_request = create_join_request(); - - let request = Request::builder() - .method(http::Method::PUT) - .uri("/v1/conference/participants") - .header(header::USER_AGENT, "test/user/agent") - .header(header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref()) - .header(header::AUTHORIZATION, "") - .body(Body::from(serde_json::to_vec(&join_request).unwrap())) - .unwrap(); - - // Submit the request. - let response = app.oneshot(request).await.unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); - } - - /// Invoke the "DELETE /v1/conference/participants" to leave a call. - #[tokio::test] - async fn test_leave() { - let config = &CONFIG; - - // Create mocked dependencies with expectations. - let storage = create_mocked_storage_unused(); - let backend = create_mocked_backend_unused(); - - let frontend = create_frontend(config, storage, backend); - - // Create an axum application. - let app = app(frontend); - - let request = Request::builder() - .method(http::Method::DELETE) - .uri(format!("/v1/conference/participants/{}", ENDPOINT_ID_1)) - .header(header::USER_AGENT, "test/user/agent") - .header(header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref()) - .header( - header::AUTHORIZATION, - create_authorization_header_for_user(USER_ID_2), - ) - .body(Body::empty()) - .unwrap(); - - // Submit the request. - let response = app.oneshot(request).await.unwrap(); - assert_eq!(response.status(), StatusCode::NO_CONTENT); - } -} diff --git a/frontend/src/authenticator.rs b/frontend/src/authenticator.rs index 398ea3f..e476272 100644 --- a/frontend/src/authenticator.rs +++ b/frontend/src/authenticator.rs @@ -195,7 +195,6 @@ mod authenticator_tests { use super::*; use env_logger::Env; use hex::ToHex; - //use hmac::digest::generic_array::GenericArray; const AUTH_KEY_1: &str = "f00f0014fe091de31827e8d686969fad65013238aadd25ef8629eb8a9e5ef69b"; const AUTH_KEY_2: &str = "f00f0072f8ee256b9ba24255897230342cc83b76a3964d6288a7ac8ae4e8e9ca"; diff --git a/frontend/src/backend.rs b/frontend/src/backend.rs index 6f9d1fb..eb8a129 100644 --- a/frontend/src/backend.rs +++ b/frontend/src/backend.rs @@ -117,6 +117,8 @@ pub trait Backend: Sync + Send { pub struct BackendHttpClient { http_client: HttpClient, + /// URL used when invoking the get_info() API so that the request goes through + /// a load balancer. base_url: String, } @@ -124,16 +126,9 @@ impl BackendHttpClient { pub fn from_config(config: &'static config::Config) -> Self { let client = HttpClient::builder().build_http(); - // Build the base_url string for the backend client. This is used when invoking - // the get_info() API so that the request goes through the load balancer for the region. - let base_url = config - .calling_server_url_template - .replace("", &config.region) - .replace("", &config.calling_server_version); - Self { http_client: client, - base_url, + base_url: config.calling_server_url.clone(), } } } diff --git a/frontend/src/config.rs b/frontend/src/config.rs index a2df585..7a00853 100644 --- a/frontend/src/config.rs +++ b/frontend/src/config.rs @@ -4,125 +4,77 @@ // use clap; -use serde::Deserialize; /// Configuration options from command line arguments. #[derive(Default, clap::Parser, Debug, Clone)] #[clap(name = "calling_frontend")] -pub struct ArgConfig { +pub struct Config { /// The IP address to bind to for the server. #[clap(long, default_value = "0.0.0.0")] pub server_ip: String, /// The port to use to access the server. - #[clap(long, default_value = "8090")] + #[clap(long, default_value = "8080")] pub server_port: u16, - /// Region of the frontend. Appears as a tag in metrics and logging. + /// GCP region of the frontend. Appears as a tag in metrics and logging. #[clap(long)] pub region: String, + /// The authentication key to use when validating API requests. + #[clap(long)] + pub authentication_key: String, + /// Deployment version of the frontend. Appears as a tag in metrics and in logging. #[clap(long)] pub version: String, - /// A version string suitable to be used in the calling_server_url_template. - /// Examples: "21", "staging.21" - #[clap(long)] - pub calling_server_version: String, - - /// The path to a yaml file with further configuration settings. - #[clap(long)] - pub yaml: String, -} - -/// Configuration options from a yaml file. -#[derive(Default, Deserialize)] -pub struct YamlConfig { /// Maximum clients per call. + #[clap(long)] pub max_clients_per_call: u32, /// Interval for removing ended calls from the database. + #[clap(long)] pub cleanup_interval_ms: u64, - /// The authentication key to use when validating API requests. - pub authentication_key: String, - /// A URL template string that provides a region-specific address of the server and /// used for redirects. /// '' will be substituted with the current region. /// Example: "https://sfu..voip.signal.org" + #[clap(long)] pub regional_url_template: String, - /// A URL template string that provides a specific address to a calling server. - /// '' will be substituted with the current region. - /// '' will be substituted with the current deployment version. - /// Example: `http://cs...voip.signal.org` - pub calling_server_url_template: String, + /// The URL of the calling server to access for the backend. + #[clap(long)] + pub calling_server_url: String, - /// The key used for accessing storage, such as the AWS_ACCESS_KEY_ID. - pub storage_key: String, + /// Interval for fetching a new identity token for storage support via DynamodDB. + #[clap(long, default_value = "600000")] + pub identity_fetcher_interval_ms: u64, - /// The password used for accessing storage, such as the AWS_SECRET_ACCESS_KEY. - pub storage_password: String, + /// Where to fetch identity tokens from for storage support via DynamodDB. + #[clap(long)] + pub identity_token_url: Option, /// The name of the table that provides the list of calls being tracked. + #[clap(long)] pub storage_table: String, - /// The region in which the server resides. + /// The AWS region in which the DynamoDB server resides. + #[clap(long)] pub storage_region: String, - /// The storage endpoint used only for testing. Typically something like http://dynamodb:8000. + /// The storage endpoint used only for testing. Typically something like "http://dynamodb:8000". /// Do not specify anything for production. + #[clap(long)] pub storage_endpoint: Option, /// IP and port of Datadog StatsD agent. Typically 127.0.0.1:8125. If not /// present, metrics will be disabled. + #[clap(long)] pub metrics_datadog_host: Option, } -pub struct Config { - pub server_ip: String, - pub server_port: u16, - pub region: String, - pub version: String, - pub calling_server_version: String, - pub max_clients_per_call: u32, - pub cleanup_interval_ms: u64, - pub authentication_key: String, - pub regional_url_template: String, - pub calling_server_url_template: String, - pub storage_key: String, - pub storage_password: String, - pub storage_table: String, - pub storage_region: String, - pub storage_endpoint: Option, - pub metrics_datadog_host: Option, -} - -impl Config { - pub fn merge(arg_config: ArgConfig, yaml_config: YamlConfig) -> Config { - Config { - server_ip: arg_config.server_ip, - server_port: arg_config.server_port, - region: arg_config.region, - version: arg_config.version, - calling_server_version: arg_config.calling_server_version, - max_clients_per_call: yaml_config.max_clients_per_call, - cleanup_interval_ms: yaml_config.cleanup_interval_ms, - authentication_key: yaml_config.authentication_key, - regional_url_template: yaml_config.regional_url_template, - calling_server_url_template: yaml_config.calling_server_url_template, - storage_key: yaml_config.storage_key, - storage_password: yaml_config.storage_password, - storage_table: yaml_config.storage_table, - storage_region: yaml_config.storage_region, - storage_endpoint: yaml_config.storage_endpoint, - metrics_datadog_host: yaml_config.metrics_datadog_host, - } - } -} - #[cfg(test)] pub fn default_test_config() -> Config { Config { @@ -130,18 +82,17 @@ pub fn default_test_config() -> Config { server_port: 8080, max_clients_per_call: 8, cleanup_interval_ms: 5000, + identity_fetcher_interval_ms: 1000 * 60 * 10, + identity_token_url: None, authentication_key: "f00f0014fe091de31827e8d686969fad65013238aadd25ef8629eb8a9e5ef69b" .to_string(), - region: "us-west-1".to_string(), + region: "us-west1".to_string(), version: "1".to_string(), regional_url_template: "".to_string(), - calling_server_url_template: "http://127.0.0.1:8080".to_string(), - calling_server_version: "1".to_string(), - storage_key: "DUMMYKEY".to_string(), - storage_password: "DUMMYSECRET".to_string(), - storage_table: "Conferences".to_string(), - storage_region: "us-east-2".to_string(), - storage_endpoint: Some("http://127.0.0.1:8000".to_string()), + calling_server_url: "http://127.0.0.1:8080".to_string(), + storage_table: "CallRecords".to_string(), + storage_region: "us-east-1".to_string(), + storage_endpoint: Some("localhost:9010".to_string()), metrics_datadog_host: None, } } diff --git a/frontend/src/frontend.rs b/frontend/src/frontend.rs index ed3daff..22fd095 100644 --- a/frontend/src/frontend.rs +++ b/frontend/src/frontend.rs @@ -33,6 +33,12 @@ pub type UserId = String; #[derive(Clone, Deserialize, Serialize, Eq, PartialEq)] pub struct GroupId(String); +impl From for GroupId { + fn from(group_id_string: String) -> Self { + Self(group_id_string) + } +} + impl From<&str> for GroupId { fn from(group_id: &str) -> Self { Self(group_id.to_string()) @@ -121,8 +127,6 @@ pub struct JoinResponseWrapper { pub ice_ufrag: String, pub ice_pwd: String, pub dhe_public_key: String, - // Needed for the v1 api. - pub client_id: String, } #[derive(thiserror::Error, Debug, Eq, PartialEq)] @@ -382,7 +386,6 @@ impl Frontend { ice_ufrag: backend_join_response.ice_ufrag, ice_pwd: backend_join_response.ice_pwd, dhe_public_key: backend_dhe_public_key, - client_id, }) } diff --git a/frontend/src/main.rs b/frontend/src/main.rs index d5224da..11abac3 100644 --- a/frontend/src/main.rs +++ b/frontend/src/main.rs @@ -9,7 +9,7 @@ extern crate lazy_static; #[macro_use] extern crate log; -use std::{fs::File, sync::Arc}; +use std::sync::Arc; use anyhow::Result; use calling_common::Duration; @@ -28,14 +28,7 @@ use tokio::{ lazy_static! { // Load the config and treat it as a read-only static value. - static ref CONFIG: config::Config = { - let arg_config = config::ArgConfig::parse(); - - let f = File::open(&arg_config.yaml).expect("yaml file can be opened"); - let yaml_config = serde_yaml::from_reader(f).expect("yaml file can be parsed"); - - config::Config::merge(arg_config, yaml_config) - }; + static ref CONFIG: config::Config = config::Config::parse(); } #[rustfmt::skip] @@ -48,10 +41,9 @@ fn print_config(config: &'static config::Config) { info!(" {:38}{}", "region:", config.region); info!(" {:38}{}", "version:", config.version); info!(" {:38}{}", "regional_url_template:", config.regional_url_template); - info!(" {:38}{}", "calling_server_url_template:", config.calling_server_url_template); - info!(" {:38}{}", "calling_server_version_param:", config.calling_server_version); + info!(" {:38}{}", "calling_server_url:", config.calling_server_url); info!(" {:38}{}", "storage_table:", config.storage_table); - info!(" {:38}{}", "storage_region:", config.storage_region); + info!(" {:38}{:?}", "identity_url:", config.identity_token_url); info!(" {:38}{:?}", "storage_endpoint:", config.storage_endpoint); info!(" {:38}{}", "metrics_datadog:", match &config.metrics_datadog_host { @@ -120,23 +112,26 @@ fn main() -> Result<()> { // for each core on the system. let threaded_rt = runtime::Runtime::new()?; - // Create an authenticator. - let authenticator = Authenticator::from_hex_key(&config.authentication_key)?; - let (api_ender_tx, api_ender_rx) = oneshot::channel(); let (cleaner_ender_tx, cleaner_ender_rx) = oneshot::channel(); let (metrics_ender_tx, metrics_ender_rx) = oneshot::channel(); + let (identity_fetcher_ender_tx, identity_fetcher_ender_rx) = oneshot::channel(); let (signal_canceller_tx, signal_canceller_rx) = mpsc::channel(1); let signal_canceller_tx_clone_for_cleaner = signal_canceller_tx.clone(); let signal_canceller_tx_clone_for_metrics = signal_canceller_tx.clone(); + let signal_canceller_tx_clone_for_identity_fetcher = signal_canceller_tx.clone(); + + // Create frontend entities that might fail. + let authenticator = Authenticator::from_hex_key(&config.authentication_key)?; + let (storage, identity_fetcher) = threaded_rt.block_on(DynamoDb::new(config))?; threaded_rt.block_on(async { // Create the shared Frontend state. let frontend: Arc = Arc::new(Frontend { config, authenticator, - storage: Box::new(DynamoDb::new(config).await), + storage: Box::new(storage), backend: Box::new(BackendHttpClient::from_config(config)), id_generator: Box::new(FrontendIdGenerator), api_metrics: Mutex::new(Default::default()), @@ -163,6 +158,14 @@ fn main() -> Result<()> { let _ = signal_canceller_tx_clone_for_metrics.send(()).await; }); + // Start the identity token fetcher. + let fetcher_handle = tokio::spawn(async move { + let _ = identity_fetcher.start(identity_fetcher_ender_rx).await; + let _ = signal_canceller_tx_clone_for_identity_fetcher + .send(()) + .await; + }); + // Wait for any signals to be detected, or cancel due to one of the // servers not being able to be started (the channel is buffered). wait_for_signal(signal_canceller_rx).await; @@ -171,9 +174,10 @@ fn main() -> Result<()> { let _ = api_ender_tx.send(()); let _ = cleaner_ender_tx.send(()); let _ = metrics_ender_tx.send(()); + let _ = identity_fetcher_ender_tx.send(()); // Wait for the servers to exit. - let _ = tokio::join!(api_handle, cleaner_handle, metrics_handle,); + let _ = tokio::join!(api_handle, cleaner_handle, metrics_handle, fetcher_handle); }); info!("shutting down the runtime"); diff --git a/frontend/src/storage.rs b/frontend/src/storage.rs index 92fca57..6b42648 100644 --- a/frontend/src/storage.rs +++ b/frontend/src/storage.rs @@ -3,19 +3,25 @@ // SPDX-License-Identifier: AGPL-3.0-only // -use anyhow::{Context, Result}; +use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use aws_sdk_dynamodb::{ model::{AttributeValue, Select}, types::SdkError, Client, Config, Endpoint, }; +use aws_smithy_async::rt::sleep::default_async_sleep; use aws_smithy_types::retry::RetryConfigBuilder; use aws_types::{region::Region, Credentials}; +use calling_common::Duration; use http::Uri; +use hyper::client::HttpConnector; +use hyper::{Body, Method, Request}; use log::*; use serde::{Deserialize, Serialize}; use serde_dynamo::{from_item, to_item}; +use std::{env, path::PathBuf}; +use tokio::{io::AsyncWriteExt, sync::oneshot::Receiver}; #[cfg(test)] use mockall::{automock, predicate::*}; @@ -23,6 +29,7 @@ use mockall::{automock, predicate::*}; use crate::{ config, frontend::{GroupId, UserId}, + metrics::Timer, }; const GROUP_CONFERENCE_ID_STRING: &str = "groupConferenceId"; @@ -85,49 +92,68 @@ pub struct DynamoDb { } impl DynamoDb { - pub async fn new(config: &'static config::Config) -> Self { + pub async fn new(config: &'static config::Config) -> Result<(Self, IdentityFetcher)> { + let sleep_impl = + default_async_sleep().ok_or_else(|| anyhow!("failed to create sleep_impl"))?; + + let identity_fetcher; + let client = match &config.storage_endpoint { - None => { + Some(endpoint) => { + const KEY: &str = "DUMMY_KEY"; + const PASSWORD: &str = "DUMMY_PASSWORD"; + + info!("Using endpoint for DynamodDB testing: {}", endpoint); + + // Create an identity fetcher with a dummy token path, which isn't used + // for testing. + identity_fetcher = IdentityFetcher::new(config, "/tmp/token"); + + let aws_config = Config::builder() + .credentials_provider(Credentials::from_keys(KEY, PASSWORD, None)) + .endpoint_resolver(Endpoint::immutable(Uri::from_static(endpoint))) + .sleep_impl(sleep_impl) + .region(Region::new(&config.storage_region)) + .build(); + Client::from_conf(aws_config) + } + _ => { info!( "Using region for DynamodDB access: {}", config.storage_region.as_str() ); + // Get the location of the identity token file from the environment variable, + // the same location that the client will try to get it from for credentials. + let identity_token_path = env::var("AWS_WEB_IDENTITY_TOKEN_FILE")?; + identity_fetcher = IdentityFetcher::new(config, &identity_token_path); + + // Fetch an identity token once before connecting for the first time. + identity_fetcher.fetch_token().await?; + let retry_config = RetryConfigBuilder::new() .max_attempts(4) .initial_backoff(std::time::Duration::from_millis(100)) .build(); - let aws_config = Config::builder() - .credentials_provider(Credentials::from_keys( - &config.storage_key, - &config.storage_password, - None, - )) + let aws_config = aws_config::from_env() + .sleep_impl(sleep_impl) .retry_config(retry_config) .region(Region::new(&config.storage_region)) - .build(); - Client::from_conf(aws_config) - } - Some(endpoint) => { - info!("Using endpoint for DynamodDB testing: {}", endpoint); - let aws_config = Config::builder() - .credentials_provider(Credentials::from_keys( - &config.storage_key, - &config.storage_password, - None, - )) - .endpoint_resolver(Endpoint::immutable(Uri::from_static(endpoint))) - .region(Region::new(&config.storage_region)) - .build(); - Client::from_conf(aws_config) + .load() + .await; + + Client::new(&aws_config) } }; - Self { - client, - table_name: config.storage_table.to_string(), - } + Ok(( + Self { + client, + table_name: config.storage_table.to_string(), + }, + identity_fetcher, + )) } } @@ -177,8 +203,6 @@ impl Storage for DynamoDb { Err(SdkError::ServiceError { err: e, raw: _ }) if e.is_conditional_check_failed_exception() => { - // TODO: This log replicates behavior of the old server, remove if not useful. - info!("Conditional check failed, call now already exists"); Ok(self .get_call_record(&call.group_id) .await @@ -220,8 +244,6 @@ impl Storage for DynamoDb { Err(SdkError::ServiceError { err: e, raw: _ }) if e.is_conditional_check_failed_exception() => { - // TODO: This log replicates behavior of the old server, remove if not useful. - info!("Item already removed or replaced: {:.6}", call_id); Ok(()) } Err(err) => Err(StorageError::UnexpectedError(err.into())), @@ -259,3 +281,79 @@ impl Storage for DynamoDb { Ok(vec![]) } } + +/// Supports the DynamoDB storage implementation by periodically refreshing an identity +/// token file at the location given by `identity_token_path`. +pub struct IdentityFetcher { + client: hyper::Client, + fetch_interval: Duration, + identity_token_path: PathBuf, + identity_token_url: Option, +} + +impl IdentityFetcher { + fn new(config: &'static config::Config, identity_token_path: &str) -> Self { + IdentityFetcher { + client: hyper::client::Client::builder().build_http(), + fetch_interval: Duration::from_millis(config.identity_fetcher_interval_ms), + identity_token_path: PathBuf::from(identity_token_path), + identity_token_url: config.identity_token_url.to_owned(), + } + } + + async fn fetch_token(&self) -> Result<()> { + if let Some(url) = &self.identity_token_url { + let request = Request::builder() + .method(Method::GET) + .uri(url) + .header("Metadata-Flavor", "Google") + .body(Body::empty())?; + + debug!("Fetching identity token from {}", url); + + let body = self.client.request(request).await?; + let body = hyper::body::to_bytes(body).await?; + let temp_name = self.identity_token_path.with_extension("bak"); + let mut temp_file = tokio::fs::File::create(&temp_name).await?; + temp_file.write_all(&body).await?; + tokio::fs::rename(temp_name, &self.identity_token_path).await?; + + debug!( + "Successfully wrote identity token to {:?}", + &self.identity_token_path + ); + } + Ok(()) + } + + pub async fn start(self, ender_rx: Receiver<()>) -> Result<()> { + // Periodically fetch a new web identity from GCP. + let fetcher_handle = tokio::spawn(async move { + loop { + // Use sleep() instead of interval() so that we never wait *less* than one + // interval to do the next tick. + tokio::time::sleep(self.fetch_interval.into()).await; + + let timer = start_timer_us!("calling.frontend.identity_fetcher.timed"); + + let result = &self.fetch_token().await; + if let Err(e) = result { + event!("calling.frontend.identity_fetcher.error"); + error!("Failed to fetch identity token : {:?}", e); + } + timer.stop(); + } + }); + + info!("fetcher ready"); + + // Wait for any task to complete and cancel the rest. + tokio::select!( + _ = fetcher_handle => {}, + _ = ender_rx => {}, + ); + + info!("fetcher shutdown"); + Ok(()) + } +}