basic broadcasting works!
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
Michael Zhang 2023-02-11 10:38:28 -06:00
parent 2205d60f62
commit dbc9a2d7a1
15 changed files with 1260 additions and 16 deletions

709
Cargo.lock generated
View file

@ -35,12 +35,90 @@ dependencies = [
"backtrace",
]
[[package]]
name = "async-trait"
version = "0.1.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "axum"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5694b64066a2459918d8074c2ce0d5a88f409431994c2356617c8ae0c4721fc"
dependencies = [
"async-trait",
"axum-core",
"axum-macros",
"base64 0.20.0",
"bitflags",
"bytes",
"futures-util",
"headers",
"http",
"http-body",
"hyper",
"itoa",
"matchit",
"memchr",
"mime",
"percent-encoding",
"pin-project-lite",
"rustversion",
"serde",
"serde_json",
"serde_path_to_error",
"serde_urlencoded",
"sha1",
"sync_wrapper",
"tokio",
"tokio-tungstenite",
"tower",
"tower-http",
"tower-layer",
"tower-service",
]
[[package]]
name = "axum-core"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cae3e661676ffbacb30f1a824089a8c9150e71017f7e1e38f2aa32009188d34"
dependencies = [
"async-trait",
"bytes",
"futures-util",
"http",
"http-body",
"mime",
"rustversion",
"tower-layer",
"tower-service",
]
[[package]]
name = "axum-macros"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dbcf61bed07d554bd5c225cd07bc41b793eab63e79c6f0ceac7e1aed2f1c670"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "backtrace"
version = "0.3.67"
@ -56,18 +134,45 @@ dependencies = [
"rustc-demangle",
]
[[package]]
name = "base64"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "base64"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "block-buffer"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e"
dependencies = [
"generic-array",
]
[[package]]
name = "bumpalo"
version = "3.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "bytes"
version = "1.3.0"
@ -154,6 +259,15 @@ version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]]
name = "cpufeatures"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
dependencies = [
"libc",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.6"
@ -173,6 +287,16 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "cxx"
version = "1.0.86"
@ -252,6 +376,19 @@ dependencies = [
"syn",
]
[[package]]
name = "dashmap"
version = "5.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc"
dependencies = [
"cfg-if",
"hashbrown",
"lock_api",
"once_cell",
"parking_lot_core",
]
[[package]]
name = "derive_builder"
version = "0.12.0"
@ -283,6 +420,16 @@ dependencies = [
"syn",
]
[[package]]
name = "digest"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "errno"
version = "0.2.8"
@ -310,6 +457,15 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "form_urlencoded"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
dependencies = [
"percent-encoding",
]
[[package]]
name = "futures"
version = "0.3.25"
@ -399,12 +555,83 @@ dependencies = [
"slab",
]
[[package]]
name = "generic-array"
version = "0.14.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "getrandom"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
dependencies = [
"cfg-if",
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
]
[[package]]
name = "gimli"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dec7af912d60cdbd3677c1af9352ebae6fb8394d165568a2234df0fa00f87793"
[[package]]
name = "h2"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4"
dependencies = [
"bytes",
"fnv",
"futures-core",
"futures-sink",
"futures-util",
"http",
"indexmap",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "headers"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584"
dependencies = [
"base64 0.13.1",
"bitflags",
"bytes",
"headers-core",
"http",
"httpdate",
"mime",
"sha1",
]
[[package]]
name = "headers-core"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429"
dependencies = [
"http",
]
[[package]]
name = "heck"
version = "0.4.0"
@ -420,6 +647,70 @@ dependencies = [
"libc",
]
[[package]]
name = "http"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]]
name = "http-body"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
dependencies = [
"bytes",
"http",
"pin-project-lite",
]
[[package]]
name = "http-range-header"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29"
[[package]]
name = "httparse"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
[[package]]
name = "httpdate"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
[[package]]
name = "hyper"
version = "0.14.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c"
dependencies = [
"bytes",
"futures-channel",
"futures-core",
"futures-util",
"h2",
"http",
"http-body",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
"socket2",
"tokio",
"tower-service",
"tracing",
"want",
]
[[package]]
name = "iana-time-zone"
version = "0.1.53"
@ -450,6 +741,26 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
dependencies = [
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "indexmap"
version = "1.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
dependencies = [
"autocfg",
"hashbrown",
]
[[package]]
name = "io-lifetimes"
version = "1.0.3"
@ -519,24 +830,32 @@ name = "liveterm"
version = "0.1.0"
dependencies = [
"anyhow",
"axum",
"chrono",
"clap",
"dashmap",
"derive_builder",
"futures",
"lazy_static",
"libc",
"log",
"nix",
"parking_lot",
"rand",
"serde",
"serde_bytes",
"serde_derive",
"serde_json",
"signal-hook",
"termios",
"tokio",
"tokio-tungstenite",
"tokio-util",
"tracing",
"tracing-appender",
"tracing-subscriber",
"tungstenite",
"url",
]
[[package]]
@ -558,6 +877,12 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "matchit"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40"
[[package]]
name = "memchr"
version = "2.5.0"
@ -573,6 +898,12 @@ dependencies = [
"autocfg",
]
[[package]]
name = "mime"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
[[package]]
name = "miniz_oxide"
version = "0.6.2"
@ -697,6 +1028,32 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "percent-encoding"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
[[package]]
name = "pin-project"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "pin-project-lite"
version = "0.2.9"
@ -709,6 +1066,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
@ -751,6 +1114,36 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
@ -760,6 +1153,21 @@ dependencies = [
"bitflags",
]
[[package]]
name = "ring"
version = "0.16.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
dependencies = [
"cc",
"libc",
"once_cell",
"spin",
"untrusted",
"web-sys",
"winapi",
]
[[package]]
name = "rustc-demangle"
version = "0.1.21"
@ -780,6 +1188,24 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "rustls"
version = "0.20.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f"
dependencies = [
"log",
"ring",
"sct",
"webpki",
]
[[package]]
name = "rustversion"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70"
[[package]]
name = "ryu"
version = "1.0.12"
@ -798,12 +1224,31 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2"
[[package]]
name = "sct"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "serde"
version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
[[package]]
name = "serde_bytes"
version = "0.11.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "416bda436f9aab92e02c8e10d49a15ddd339cea90b6e340fe51ed97abb548294"
dependencies = [
"serde",
]
[[package]]
name = "serde_derive"
version = "1.0.152"
@ -826,6 +1271,38 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_path_to_error"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26b04f22b563c91331a10074bda3dd5492e3cc39d56bd557e91c0af42b6c7341"
dependencies = [
"serde",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
"form_urlencoded",
"itoa",
"ryu",
"serde",
]
[[package]]
name = "sha1"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "sharded-slab"
version = "0.1.4"
@ -879,6 +1356,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "spin"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "static_assertions"
version = "1.1.0"
@ -902,6 +1385,12 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "sync_wrapper"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
[[package]]
name = "termcolor"
version = "1.1.3"
@ -920,6 +1409,26 @@ dependencies = [
"libc",
]
[[package]]
name = "thiserror"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "thread_local"
version = "1.1.4"
@ -967,6 +1476,21 @@ dependencies = [
"time-core",
]
[[package]]
name = "tinyvec"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.24.1"
@ -998,6 +1522,33 @@ dependencies = [
"syn",
]
[[package]]
name = "tokio-rustls"
version = "0.23.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59"
dependencies = [
"rustls",
"tokio",
"webpki",
]
[[package]]
name = "tokio-tungstenite"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd"
dependencies = [
"futures-util",
"log",
"rustls",
"tokio",
"tokio-rustls",
"tungstenite",
"webpki",
"webpki-roots",
]
[[package]]
name = "tokio-util"
version = "0.7.4"
@ -1012,6 +1563,53 @@ dependencies = [
"tracing",
]
[[package]]
name = "tower"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
dependencies = [
"futures-core",
"futures-util",
"pin-project",
"pin-project-lite",
"tokio",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "tower-http"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858"
dependencies = [
"bitflags",
"bytes",
"futures-core",
"futures-util",
"http",
"http-body",
"http-range-header",
"pin-project-lite",
"tower",
"tower-layer",
"tower-service",
]
[[package]]
name = "tower-layer"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
[[package]]
name = "tower-service"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
[[package]]
name = "tracing"
version = "0.1.37"
@ -1019,6 +1617,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
dependencies = [
"cfg-if",
"log",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
@ -1081,18 +1680,89 @@ dependencies = [
"tracing-log",
]
[[package]]
name = "try-lock"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
[[package]]
name = "tungstenite"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788"
dependencies = [
"base64 0.13.1",
"byteorder",
"bytes",
"http",
"httparse",
"log",
"rand",
"rustls",
"sha1",
"thiserror",
"url",
"utf-8",
"webpki",
]
[[package]]
name = "typenum"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
[[package]]
name = "unicode-bidi"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58"
[[package]]
name = "unicode-ident"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
[[package]]
name = "unicode-normalization"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-width"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "untrusted"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "url"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
]
[[package]]
name = "utf-8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "valuable"
version = "0.1.0"
@ -1105,6 +1775,16 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "want"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
dependencies = [
"log",
"try-lock",
]
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
@ -1171,6 +1851,35 @@ version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
[[package]]
name = "web-sys"
version = "0.3.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "webpki"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "webpki-roots"
version = "0.22.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87"
dependencies = [
"webpki",
]
[[package]]
name = "winapi"
version = "0.3.9"

View file

@ -6,21 +6,29 @@ edition = "2021"
[dependencies]
anyhow = { version = "1.0.68", features = ["backtrace"] }
axum = { version = "0.6.4", features = ["ws", "http2", "macros", "headers"] }
chrono = "0.4.23"
clap = { version = "4.0.32", features = ["derive"] }
dashmap = "5.4.0"
derive_builder = "0.12.0"
futures = "0.3.25"
lazy_static = "1.4.0"
libc = "0.2.139"
log = "0.4.17"
nix = "0.26.1"
parking_lot = "0.12.1"
rand = "0.8.5"
serde = "1.0.152"
serde_bytes = "0.11.9"
serde_derive = "1.0.152"
serde_json = "1.0.91"
signal-hook = "0.3.14"
termios = "0.3.3"
tokio = { version = "1.24.1", features = ["full"] }
tokio-tungstenite = { version = "0.18.0", features = ["rustls-tls-webpki-roots"] }
tokio-util = { version = "0.7.4", features = ["codec"] }
tracing = "0.1.37"
tracing-appender = "0.2.2"
tracing-subscriber = "0.3.16"
tungstenite = "0.18.0"
url = "2.3.1"

View file

@ -10,8 +10,9 @@ use serde::{
},
ser::{Serialize, SerializeSeq, Serializer},
};
use serde_bytes::ByteBuf;
#[derive(Debug, Builder, Serialize)]
#[derive(Clone, Debug, Builder, Serialize, Deserialize)]
pub struct Header {
#[builder(setter(skip))]
pub version: Version,
@ -34,7 +35,7 @@ pub struct Header {
pub theme: Option<Theme>,
}
#[derive(Debug, Clone, Copy, Serialize)]
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct Version(u32);
impl Default for Version {
@ -43,7 +44,7 @@ impl Default for Version {
}
}
#[derive(Debug, Clone, Serialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Theme {
fg: String,
bg: String,
@ -118,13 +119,13 @@ impl<'d> Deserialize<'d> for Event {
// Must first go through &[u8] then to Vec<u8> because serde_json treats
// &[u8] specially when it comes to deserializing from binary strings
let data = {
let data: &'de [u8] = match seq.next_element()? {
let data: ByteBuf = match seq.next_element()? {
Some(v) => v,
None => {
return Err(A::Error::invalid_length(2, &"an array of length 3"))
}
};
data.to_vec()
data.into_vec()
};
let event_kind = match io {

View file

@ -1,5 +1,4 @@
pub mod raw_term;
pub mod recorder;
mod terminal;
pub mod stream;
pub mod terminal;

View file

@ -8,6 +8,9 @@ pub struct RawTerm(RawFd, Termios);
impl RawTerm {
/// Put the terminal into raw mode.
///
/// I have no idea why these work yet, these are just copied from Python's
/// pty.py library
pub fn init(fd: RawFd) -> Result<Self> {
use nix::sys::termios::*;
let saved_mode = tcgetattr(fd)?;

View file

@ -20,7 +20,7 @@ pub struct RecordOpts {
output_file: PathBuf,
}
pub fn record(opts: RecordOpts) -> Result<()> {
pub fn record_main(opts: RecordOpts) -> Result<()> {
let mut file = File::create(&opts.output_file)?;
let mut command = Command::new("zsh");

108
src/client/stream.rs Normal file
View file

@ -0,0 +1,108 @@
use std::sync::mpsc;
use std::thread;
use std::{collections::HashMap, process::Command};
use anyhow::{Context, Result};
use futures::{SinkExt, StreamExt};
use tokio::runtime::Runtime;
use tokio_tungstenite::connect_async;
use tungstenite::Message as WsMessage;
use url::Url;
use crate::asciicast::HeaderBuilder;
use crate::client::terminal::Terminal;
use crate::message::Message;
#[derive(Debug, Parser)]
pub struct StreamOpts {}
pub fn stream_main(opts: StreamOpts) -> Result<()> {
let runtime = Runtime::new()?;
runtime.block_on(stream_async_main(opts))
}
async fn stream_async_main(opts: StreamOpts) -> Result<()> {
println!("Hellosu {opts:?}");
// Open a new websocket connection to the server
let url = Url::parse(&"ws://localhost:8200/broadcast").unwrap();
let (ws, _) = connect_async(url).await.context("Failed to connect")?;
println!("WebSocket handshake has been successfully completed");
let (mut write, mut read) = ws.split();
let mut command = Command::new("zsh");
command.env("TERM", "xterm-256color");
// Write header
// TODO: Clean this up
let header = {
let command_str = format!("{command:?}");
let env = command
.get_envs()
.into_iter()
.filter_map(|(a, b)| b.map(|b| (a, b)))
.map(|(a, b)| {
(
a.to_string_lossy().to_string(),
b.to_string_lossy().to_string(),
)
})
.collect::<HashMap<_, _>>();
HeaderBuilder::default()
.width(30)
.height(30)
.command(command_str)
.env(env)
.build()?
};
// Step 1. Send header
{
let message = Message::AsciicastHeader(header);
let ws_message = WsMessage::Text(serde_json::to_string(&message).unwrap());
write.send(ws_message).await?;
}
// STep 2. Wait for hellosu
{
let msg = match read.next().await {
Some(v) => v?,
None => bail!("wtf bro"),
};
let msg: Message = match msg {
WsMessage::Text(v) => serde_json::from_str(&v)?,
_ => bail!("dont send me other Shit!!!"),
};
let server_hello = match msg {
Message::ServerHello(v) => v,
_ => bail!("DONT SEND ME OTHER SHIT"),
};
println!("URL: {:?}", server_hello.url);
}
// Step 3. Produuuuuuce
let (tx, rx) = mpsc::channel();
let (pty, rxt) = Terminal::setup(command)?;
thread::spawn(move || {
for event in rxt.into_iter() {
let message = Message::AsciicastEvent(event);
let ws_message =
WsMessage::Text(serde_json::to_string(&message).unwrap());
tx.send(ws_message);
}
});
tokio::spawn(async move {
for ws_message in rx.into_iter() {
write.send(ws_message).await;
}
});
pty.wait_until_complete()?;
Ok(())
}

View file

@ -114,14 +114,17 @@ impl Terminal {
// Set up recording function
let start_time = Instant::now();
let record = |now: Instant, output: bool, data: &[u8]| -> Result<()> {
let elapsed = (now - start_time).as_secs_f64();
let mut last_frame_time = start_time;
let mut record = |now: Instant, output: bool, data: &[u8]| -> Result<()> {
let elapsed = (now - last_frame_time).as_secs_f64();
let event_kind = (match output {
true => EventKind::Output,
false => EventKind::Input,
})(data.to_vec());
let event = Event(elapsed, event_kind);
self.event_tx.send(event)?;
last_frame_time = now;
Ok(())
};

View file

@ -11,3 +11,5 @@ extern crate tracing;
pub mod asciicast;
pub mod client;
pub mod server;
pub mod message;

View file

@ -1,6 +1,12 @@
use anyhow::Result;
use clap::{ArgAction, Parser};
use liveterm::client::recorder::{record, RecordOpts};
use liveterm::{
client::{
recorder::{record_main, RecordOpts},
stream::{stream_main, StreamOpts},
},
server::{server_main, ServerOpts},
};
use tracing::Level;
#[derive(Parser)]
@ -15,12 +21,16 @@ struct Opt {
#[derive(Parser)]
enum Subcommand {
/// Record terminal session.
#[structopt(name = "rec")]
#[structopt(name = "record", alias = "rec")]
Record(RecordOpts),
/// Stream to the server
#[structopt(name = "stream")]
Stream(StreamOpts),
/// Run the server.
#[structopt(name = "server")]
Server,
Server(ServerOpts),
}
fn main() -> Result<()> {
@ -29,10 +39,16 @@ fn main() -> Result<()> {
match opt.subcommand {
Subcommand::Record(opts) => {
record(opts)?;
record_main(opts)?;
}
Subcommand::Server => {}
Subcommand::Stream(opts) => {
stream_main(opts)?;
}
Subcommand::Server(opts) => {
server_main(opts)?;
}
}
Ok(())
}

38
src/message.rs Normal file
View file

@ -0,0 +1,38 @@
use crate::asciicast::{Event, Header, HeaderBuilder, EventKind};
/// Message used by Liveterm for broadcasts
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum Message {
ServerHello(ServerHello),
AsciicastHeader(Header),
AsciicastEvent(Event),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ServerHello {
pub url: String,
}
#[test]
fn test() {
let msg = Message::AsciicastHeader(
HeaderBuilder::default()
.width(30)
.height(30)
.build()
.unwrap(),
);
println!("{}", serde_json::to_string(&msg).unwrap());
}
#[test]
fn test2() {
const msg: &'static str = "{\"AsciicastEvent\":[0.058985141,\"o\",\"\\u001b[1m\\u001b[3m%\\u001b[23m\\u001b[1m\\u001b[0m \\r \\r\"]}";
let msg2: Message = serde_json::from_str(&msg).unwrap();
if let Message::AsciicastEvent(ref evt) = msg2 {
if let EventKind::Output(ref d) = evt.1 {
// println!("==> {} <==", String::from_utf8(d.to_vec()).unwrap());
}
}
println!("{msg2:?}");
}

162
src/server/broadcast.rs Normal file
View file

@ -0,0 +1,162 @@
use std::sync::atomic::{AtomicUsize, Ordering};
use anyhow::{Context, Result};
use axum::{
extract::{
ws::{Message as WsMessage, WebSocket},
WebSocketUpgrade,
},
response::Response,
};
use dashmap::{mapref::entry::Entry, DashMap};
use futures::{future::BoxFuture, FutureExt};
use rand::{prelude::Distribution, Rng};
use serde::Deserialize;
use tokio::sync::broadcast::{self, Receiver, Sender};
use crate::{
asciicast::{Event, Header},
message::{Message, ServerHello},
};
// TODO: Figure out a real data structure to use here
lazy_static::lazy_static! {
pub static ref BROADCASTS: DashMap<String, Sender<Message>> = DashMap::new();
pub static ref LEN: AtomicUsize = AtomicUsize::new(3);
}
const CAPACITY: usize = 1024;
pub async fn broadcast(ws: WebSocketUpgrade) -> Response {
// Generate channels
// TODO: Handle lag situation, possibly need to choose different channel
// implementation?
let (tx, _) = broadcast::channel(CAPACITY);
// Allocate new room
let mut rng = rand::thread_rng();
let mut ct = 0;
let room_id = loop {
let len = LEN.load(Ordering::Relaxed);
let room_id = (&mut rng)
.sample_iter(RoomIdDistribution)
.take(len)
.collect::<Vec<_>>();
let room_id = unsafe {
// Distribution guarantees UTF-8-safety
String::from_utf8_unchecked(room_id)
};
let _entry = match BROADCASTS.entry(room_id.clone()) {
Entry::Occupied(_) => {
ct += 1;
// Generous amount for now
if ct > 10 {
LEN.fetch_add(1, Ordering::Relaxed);
ct = 0;
}
continue;
}
Entry::Vacant(entry) => {
entry.insert(tx.clone());
break room_id;
}
};
};
ws.on_upgrade(handle_websocket(room_id, tx))
}
fn deserialize_message<'d, T>(msg: &'d WsMessage) -> Result<T>
where
T: Deserialize<'d>,
{
Ok(match msg {
WsMessage::Text(data) => serde_json::from_str(data)?,
WsMessage::Binary(data) => serde_json::from_slice(data)?,
_ => bail!("for now, protocol does not allow for other message types"),
})
}
async fn handle_websocket_inner(
room_id: String,
tx: Sender<Message>,
mut socket: WebSocket,
) -> Result<()> {
// Step 1. Read the header
let msg: Message = match socket.recv().await {
Some(v) => deserialize_message(&v?)?,
None => bail!("send a header please..."),
};
let header: Header = match msg {
Message::AsciicastHeader(header) => header,
_ => bail!("please send header omg!!!"),
};
// Step 2. Respond with Hellosu
let server_hello = Message::ServerHello(ServerHello {
url: format!("http://192.168.0.133:8200/watch/{room_id}"),
});
socket
.send(WsMessage::Text(serde_json::to_string(&server_hello)?))
.await?;
// Step 3. Consoooome
while let Some(msg) = socket.recv().await {
// TODO: Consume header
let msg = msg.context("could not read event message")?;
trace!(?msg, "parsed event message");
let msg: Message = match &msg {
WsMessage::Text(data) => serde_json::from_str(data),
WsMessage::Binary(data) => serde_json::from_slice(data),
_ => continue,
}
.context("could not parse event message from json")?;
// Ignoring this for now cus it'll error if there's no receivers
tx.send(msg);
}
Ok(())
}
fn handle_websocket<'a>(
room_id: String,
tx: Sender<Message>,
) -> impl FnOnce(WebSocket) -> BoxFuture<'a, ()> {
move |socket: WebSocket| {
async {
match handle_websocket_inner(room_id, tx, socket).await {
Ok(_) => {}
Err(err) => {
eprintln!("Fuck! {err:?}");
eprintln!("{err}");
}
}
}
.boxed()
}
}
// Cribbed from https://rust-random.github.io/rand/rand/distributions/struct.Alphanumeric.html
struct RoomIdDistribution;
impl Distribution<u8> for RoomIdDistribution {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> u8 {
const RANGE: u32 = 26 + 26 + 10;
const ALLOWED_CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
abcdefghijklmnopqrstuvwxyz\
0123456789\
-";
loop {
let var = rng.next_u32() >> (32 - 6);
if var < RANGE {
return ALLOWED_CHARS[var as usize];
}
}
}
}

34
src/server/error.rs Normal file
View file

@ -0,0 +1,34 @@
// Cribbed from https://github.com/tokio-rs/axum/blob/main/examples/anyhow-error-response/src/main.rs
use axum::{
http::StatusCode,
response::{IntoResponse, Response},
};
pub type Result<T, E = AppError> = std::result::Result<T, E>;
/// Make our own error that wraps `anyhow::Error`.
pub struct AppError(anyhow::Error);
// Tell axum how to convert `AppError` into a response.
impl IntoResponse for AppError {
fn into_response(self) -> Response {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Something went wrong: {}", self.0),
)
.into_response()
}
}
// This enables using `?` on functions that return `Result<_, anyhow::Error>` to
// turn them into `Result<_, AppError>`. That way you don't need to do that
// manually.
impl<E> From<E> for AppError
where
E: Into<anyhow::Error>,
{
fn from(err: E) -> Self {
Self(err.into())
}
}

59
src/server/listen.rs Normal file
View file

@ -0,0 +1,59 @@
use anyhow::{Context, Result};
use axum::{
extract::{
ws::{Message as WsMessage, WebSocket},
Path, WebSocketUpgrade,
},
http::StatusCode,
response::Response,
};
use futures::{future::BoxFuture, FutureExt};
use tokio::sync::broadcast::Receiver;
use crate::message::Message;
use super::broadcast::BROADCASTS;
pub async fn listen(
Path((room_id,)): Path<(String,)>,
ws: WebSocketUpgrade,
) -> Result<Response, StatusCode> {
let rx = match BROADCASTS.get(&room_id) {
Some(v) => (*v).subscribe(),
None => return Err(StatusCode::NOT_FOUND),
};
Ok(ws.on_upgrade(handle_websocket(rx)))
}
async fn handle_ws_page_inner(
mut rx: Receiver<Message>,
mut socket: WebSocket,
) -> Result<()> {
info!("Started WS connection from website.");
while let Ok(message) = rx.recv().await {
let ws_message = WsMessage::Text(serde_json::to_string(&message)?);
info!("[DM->Web] Received WS message from dashmap, forwarding {ws_message:?}");
socket.send(ws_message).await?;
}
Ok(())
}
fn handle_websocket<'a>(
rx: Receiver<Message>,
) -> impl FnOnce(WebSocket) -> BoxFuture<'a, ()> {
move |socket: WebSocket| {
async {
match handle_ws_page_inner(rx, socket).await {
Ok(_) => {}
Err(err) => {
eprintln!("Fuck! {err:?}");
eprintln!("{err}");
}
}
}
.boxed()
}
}

102
src/server/mod.rs Normal file
View file

@ -0,0 +1,102 @@
pub mod broadcast;
pub mod error;
pub mod listen;
use std::net::SocketAddr;
use anyhow::Result;
use axum::{
extract::{Path, WebSocketUpgrade},
http::Response,
response::Html,
routing::{get, post},
Router,
};
use tokio::runtime::Runtime;
use crate::server::{broadcast::broadcast, listen::listen};
#[derive(Debug, Parser)]
pub struct ServerOpts {
/// Address `host:port' to bind to
#[clap(long = "bind-addr")]
bind_addr: Option<String>,
}
pub fn server_main(opts: ServerOpts) -> Result<()> {
let runtime = Runtime::new()?;
runtime.block_on(server_async_main(opts))
}
async fn server_async_main(opts: ServerOpts) -> Result<()> {
println!("Hellosu {opts:?}");
// build our application with a single route
let app = Router::new()
.route("/watch/:room_id", get(watch_page))
.route("/ws/:room_id", get(listen))
.route("/broadcast", get(broadcast));
// run it with hyper on localhost:8200
let addr: SocketAddr = opts
.bind_addr
.unwrap_or_else(|| String::from("127.0.0.1:8200"))
.parse()?;
println!("Listening on {addr:?}...");
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await?;
Ok(())
}
async fn watch_page(Path((room_id,)): Path<(String,)>) -> Html<String> {
let html = format!(
r#"
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="https://unpkg.com/xterm@5.1.0/css/xterm.css" integrity="sha384-3K5+KOWDosY1TkoDWxPkGjncHOU5L57iOnkapqUQ7lPzlNYDE05ADJlUh4iRvtqu" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Iosevka/6.0.0/iosevka/iosevka.min.css" integrity="sha512-3hU20586NsplKRzjf2jQN5vTRTI2EsXObrHDOBLGdkiRkneg699BlmBXWGHHFHADCF3TOk2BREsVy7qTkmvQqQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
</head>
<body>
<div id="terminal"></div>
<script src="https://unpkg.com/xterm@5.1.0/lib/xterm.js" integrity="sha384-ggxzZEYHgGhnf1GQFOr/yGjYsaR4nC7xAKLMxCPTDrd0/Ru8e4IjexJE7b+r1v+2" crossorigin="anonymous"></script>
<script>
let term = new Terminal({{
fontWeight: "400",
fontFamily: "Iosevka Web",
letterSpacing: 0,
}});
term.open(document.getElementById('terminal'));
term.write('Hello from \x1B[1;3;31mxterm.js\x1B[0m $ ');
let roomId = "{room_id}";
let ws = new WebSocket(`ws://${{location.host}}/ws/${{roomId}}`);
ws.addEventListener('open', (_) => {{
}});
ws.addEventListener('message', (msg) => {{
console.log('message', msg);
let msgdata = JSON.parse(msg.data);
if ("AsciicastEvent" in msgdata) {{
let [elapsed, type, data] = msgdata["AsciicastEvent"];
if (type === "o") {{
console.log("writing in", elapsed, "sec", data);
setTimeout(() => {{
term.write(data);
}}, elapsed * 1000)
}}
}}
}});
</script>
</body>
</html>
"#,
);
Html(html)
}