feat: implement client

This commit is contained in:
2026-02-12 13:29:15 -08:00
parent 0d0a548a46
commit 5aa6b98742
23 changed files with 645 additions and 164 deletions

346
Cargo.lock generated
View File

@@ -40,7 +40,7 @@ dependencies = [
"foldhash", "foldhash",
"futures-core", "futures-core",
"h2", "h2",
"http", "http 0.2.12",
"httparse", "httparse",
"httpdate", "httpdate",
"itoa", "itoa",
@@ -76,7 +76,7 @@ checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8"
dependencies = [ dependencies = [
"bytestring", "bytestring",
"cfg-if", "cfg-if",
"http", "http 0.2.12",
"regex", "regex",
"regex-lite", "regex-lite",
"serde", "serde",
@@ -250,6 +250,28 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "aws-lc-rs"
version = "1.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256"
dependencies = [
"aws-lc-sys",
"zeroize",
]
[[package]]
name = "aws-lc-sys"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c34dda4df7017c8db52132f0f8a2e0f8161649d15723ed63fc00c82d0f2081a"
dependencies = [
"cc",
"cmake",
"dunce",
"fs_extra",
]
[[package]] [[package]]
name = "backon" name = "backon"
version = "1.6.0" version = "1.6.0"
@@ -340,6 +362,33 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "client"
version = "0.1.0"
dependencies = [
"console",
"futures",
"futures-util",
"registry",
"serde",
"serde_json",
"thiserror",
"thiserror-ext",
"tokio",
"tokio-tungstenite",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "cmake"
version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "combine" name = "combine"
version = "4.6.7" version = "4.6.7"
@@ -387,6 +436,22 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "core-foundation"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.17" version = "0.2.17"
@@ -415,6 +480,12 @@ dependencies = [
"typenum", "typenum",
] ]
[[package]]
name = "data-encoding"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea"
[[package]] [[package]]
name = "deranged" name = "deranged"
version = "0.5.5" version = "0.5.5"
@@ -468,6 +539,12 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "dunce"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
[[package]] [[package]]
name = "either" name = "either"
version = "1.15.0" version = "1.15.0"
@@ -548,6 +625,12 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "fs_extra"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
[[package]] [[package]]
name = "futures" name = "futures"
version = "0.3.31" version = "0.3.31"
@@ -647,6 +730,17 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "getrandom"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.3.4" version = "0.3.4"
@@ -670,7 +764,7 @@ dependencies = [
"futures-core", "futures-core",
"futures-sink", "futures-sink",
"futures-util", "futures-util",
"http", "http 0.2.12",
"indexmap", "indexmap",
"slab", "slab",
"tokio", "tokio",
@@ -695,6 +789,16 @@ dependencies = [
"itoa", "itoa",
] ]
[[package]]
name = "http"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
dependencies = [
"bytes",
"itoa",
]
[[package]] [[package]]
name = "httparse" name = "httparse"
version = "1.10.1" version = "1.10.1"
@@ -846,7 +950,7 @@ version = "0.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
dependencies = [ dependencies = [
"getrandom", "getrandom 0.3.4",
"libc", "libc",
] ]
@@ -874,9 +978,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.180" version = "0.2.181"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5"
[[package]] [[package]]
name = "litemap" name = "litemap"
@@ -1014,6 +1118,12 @@ version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "openssl-probe"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.12.5" version = "0.12.5"
@@ -1155,7 +1265,7 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
dependencies = [ dependencies = [
"getrandom", "getrandom 0.3.4",
] ]
[[package]] [[package]]
@@ -1229,6 +1339,43 @@ version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c"
[[package]]
name = "registry"
version = "0.1.0"
dependencies = [
"actix-web",
"actix-ws",
"base64",
"console",
"futures",
"futures-util",
"ipnetwork",
"redis",
"rustls",
"serde",
"serde_json",
"thiserror",
"thiserror-ext",
"tokio",
"tracing",
"tracing-actix-web",
"tracing-subscriber",
]
[[package]]
name = "ring"
version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
"cfg-if",
"getrandom 0.2.17",
"libc",
"untrusted",
"windows-sys 0.52.0",
]
[[package]] [[package]]
name = "rustc_version" name = "rustc_version"
version = "0.4.1" version = "0.4.1"
@@ -1238,6 +1385,54 @@ dependencies = [
"semver", "semver",
] ]
[[package]]
name = "rustls"
version = "0.23.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b"
dependencies = [
"aws-lc-rs",
"log",
"once_cell",
"rustls-pki-types",
"rustls-webpki",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-native-certs"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63"
dependencies = [
"openssl-probe",
"rustls-pki-types",
"schannel",
"security-framework",
]
[[package]]
name = "rustls-pki-types"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
dependencies = [
"zeroize",
]
[[package]]
name = "rustls-webpki"
version = "0.103.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53"
dependencies = [
"aws-lc-rs",
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.22" version = "1.0.22"
@@ -1246,9 +1441,18 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.22" version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
[[package]]
name = "schannel"
version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1"
dependencies = [
"windows-sys 0.61.2",
]
[[package]] [[package]]
name = "scopeguard" name = "scopeguard"
@@ -1256,6 +1460,29 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "security-framework"
version = "3.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef"
dependencies = [
"bitflags",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]] [[package]]
name = "semver" name = "semver"
version = "1.0.27" version = "1.0.27"
@@ -1403,6 +1630,12 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.114" version = "2.0.114"
@@ -1545,6 +1778,32 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "tokio-rustls"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
dependencies = [
"rustls",
"tokio",
]
[[package]]
name = "tokio-tungstenite"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857"
dependencies = [
"futures-util",
"log",
"rustls",
"rustls-native-certs",
"rustls-pki-types",
"tokio",
"tokio-rustls",
"tungstenite",
]
[[package]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.7.18" version = "0.7.18"
@@ -1633,6 +1892,25 @@ dependencies = [
"tracing-log", "tracing-log",
] ]
[[package]]
name = "tungstenite"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442"
dependencies = [
"bytes",
"data-encoding",
"http 1.4.0",
"httparse",
"log",
"rand",
"rustls",
"rustls-pki-types",
"sha1",
"thiserror",
"utf-8",
]
[[package]] [[package]]
name = "typenum" name = "typenum"
version = "1.19.0" version = "1.19.0"
@@ -1641,9 +1919,9 @@ checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.22" version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e"
[[package]] [[package]]
name = "unicode-segmentation" name = "unicode-segmentation"
@@ -1663,6 +1941,12 @@ version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]] [[package]]
name = "url" name = "url"
version = "2.5.8" version = "2.5.8"
@@ -1675,6 +1959,12 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "utf-8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]] [[package]]
name = "utf8_iter" name = "utf8_iter"
version = "1.0.4" version = "1.0.4"
@@ -1687,7 +1977,7 @@ version = "1.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f"
dependencies = [ dependencies = [
"getrandom", "getrandom 0.3.4",
"js-sys", "js-sys",
"wasm-bindgen", "wasm-bindgen",
] ]
@@ -1764,28 +2054,6 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "wg-mesh"
version = "0.1.0"
dependencies = [
"actix-web",
"actix-ws",
"base64",
"console",
"futures",
"futures-util",
"ipnetwork",
"redis",
"serde",
"serde_json",
"thiserror",
"thiserror-ext",
"tokio",
"tracing",
"tracing-actix-web",
"tracing-subscriber",
]
[[package]] [[package]]
name = "windows-link" name = "windows-link"
version = "0.2.1" version = "0.2.1"
@@ -2030,6 +2298,12 @@ dependencies = [
"synstructure", "synstructure",
] ]
[[package]]
name = "zeroize"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
[[package]] [[package]]
name = "zerotrie" name = "zerotrie"
version = "0.2.3" version = "0.2.3"
@@ -2065,9 +2339,9 @@ dependencies = [
[[package]] [[package]]
name = "zmij" name = "zmij"
version = "1.0.19" version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" checksum = "4de98dfa5d5b7fef4ee834d0073d560c9ca7b6c46a71d058c48db7960f8cfaf7"
[[package]] [[package]]
name = "zstd" name = "zstd"

View File

@@ -1,30 +1,5 @@
[package] [workspace]
name = "wg-mesh" members = ["registry", "client"]
version = "0.1.0" resolver = "3"
edition = "2024"
[dependencies] [workspace.dependencies]
actix-web = "4.12.1"
actix-ws = "0.3.1"
base64 = "0.22.1"
console = "0.16.2"
futures = "0.3.31"
futures-util = "0.3.31"
ipnetwork = { version = "0.21.1", features = ["serde"] }
redis = { version = "=1.0.2", features = ["connection-manager", "tokio-comp"] }
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.149"
thiserror = "2.0.18"
thiserror-ext = "0.3.0"
tokio = { version = "1.49.0", features = ["macros", "sync"] }
tracing = "0.1.44"
tracing-actix-web = "0.7.21"
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
[[bin]]
name = "registry"
path = "registry/main.rs"
[[bin]]
name = "client"
path = "client/main.rs"

View File

@@ -4,3 +4,4 @@ need_stdout = true
background = false background = false
on_change_strategy = "kill_then_restart" on_change_strategy = "kill_then_restart"
kill = ["kill", "-s", "INT"] kill = ["kill", "-s", "INT"]
watch = ["registry/", "client/", "Cargo.toml"]

18
client/Cargo.toml Normal file
View File

@@ -0,0 +1,18 @@
[package]
name = "client"
version = "0.1.0"
edition = "2024"
[dependencies]
console = "0.16.2"
futures = "0.3.31"
futures-util = "0.3.31"
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.149"
thiserror = "2.0.18"
thiserror-ext = "0.3.0"
tokio = { version = "1.49.0", features = ["macros", "rt-multi-thread"] }
tokio-tungstenite = { version = "0.28.0", features = ["rustls-tls-native-roots"] }
tracing = "0.1.44"
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
registry = { path = "../registry" }

View File

@@ -1 +0,0 @@
fn main() {}

20
client/src/error.rs Normal file
View File

@@ -0,0 +1,20 @@
use thiserror::Error;
use thiserror_ext::{Box, Construct};
use tokio_tungstenite::tungstenite;
#[derive(Error, Debug, Box, Construct)]
#[thiserror_ext(newtype(name = Error))]
pub enum ErrorKind {
#[error("error connecting to websocket")]
WsConnect {
url: String,
#[source]
source: tungstenite::Error,
},
#[error("error reading websocket msg")]
WsRead(#[source] tungstenite::Error),
#[error("error deserializing json")]
DeserializeJson(#[source] serde_json::Error),
}
pub type Result<T> = core::result::Result<T, Error>;

63
client/src/main.rs Normal file
View File

@@ -0,0 +1,63 @@
use console::style;
use futures::StreamExt;
use registry::PeerMessage;
use thiserror_ext::AsReport;
use tokio_tungstenite::tungstenite::Message;
use tracing::level_filters::LevelFilter;
use tracing_subscriber::{
EnvFilter, fmt::format::FmtSpan, layer::SubscriberExt, util::SubscriberInitExt,
};
use crate::error::Error;
mod error;
async fn run() -> crate::error::Result<()> {
let url = get_api_from_env();
let (ws_stream, response) = tokio_tungstenite::connect_async(&url)
.await
.map_err(|e| Error::ws_connect(e, &url))?;
let (_, mut read) = ws_stream.split();
tracing::info!("connected, response: {:?}", response.status());
while let Some(msg) = read.next().await {
match msg.map_err(|e| Error::ws_read(e))? {
Message::Text(text) => {
let server_msg: PeerMessage =
serde_json::from_str(&text).map_err(|e| Error::deserialize_json(e))?;
match server_msg {
PeerMessage::HydratePeers { peers } => {}
PeerMessage::PeerUpdate { peer } => {}
}
}
_ => {}
}
}
Ok(())
}
#[tokio::main]
async fn main() {
let tracing_env_filter = EnvFilter::builder()
.with_default_directive(LevelFilter::INFO.into())
.from_env_lossy();
tracing_subscriber::registry()
.with(tracing_env_filter)
.with(
tracing_subscriber::fmt::layer()
.compact()
.with_span_events(FmtSpan::CLOSE),
)
.init();
if let Err(e) = run().await {
eprintln!("{}: {}", style("error").red(), e.as_report());
std::process::exit(1)
}
}
fn get_api_from_env() -> String {
return "ws://localhost:8080/ws/peers".to_string();
}

27
registry/Cargo.toml Normal file
View File

@@ -0,0 +1,27 @@
[package]
name = "registry"
version = "0.1.0"
edition = "2024"
[dependencies]
actix-web = "4.12.1"
actix-ws = "0.3.1"
base64 = "0.22.1"
ipnetwork = { version = "0.21.1", features = ["serde"] }
redis = { version = "=1.0.2", features = ["connection-manager", "tokio-comp"] }
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.149"
console = "0.16.2"
futures = "0.3.31"
futures-util = "0.3.31"
rustls = { version = "0.23.36", features = ["aws-lc-rs"] }
tracing = "0.1.44"
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
tracing-actix-web = "0.7.21"
tokio = { version = "1.49.0", features = ["macros", "rt-multi-thread", "sync"] }
thiserror = "2.0.18"
thiserror-ext = "0.3.0"
[lib]
name = "registry"
path = "src/lib.rs"

View File

@@ -1,88 +1,88 @@
use crate::{AppState, error::Error, storage::StorageImpl}; use crate::{AppState, error::Error, storage::StorageImpl};
use actix_web::{HttpRequest, HttpResponse, rt, web}; use actix_web::{HttpRequest, HttpResponse, rt, web};
use actix_ws::AggregatedMessage; use actix_ws::AggregatedMessage;
use futures_util::StreamExt; use futures_util::StreamExt;
use tokio::sync::broadcast::error::RecvError; +use tokio::sync::broadcast::error::RecvError;
pub async fn peers( pub async fn peers(
req: HttpRequest, req: HttpRequest,
stream: web::Payload, stream: web::Payload,
app_state: web::Data<AppState>, app_state: web::Data<AppState>,
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, Error> {
let (res, mut session, msg_stream) = let (res, mut session, msg_stream) =
actix_ws::handle(&req, stream).map_err(|e| Error::ws(e))?; actix_ws::handle(&req, stream).map_err(|e| Error::ws(e))?;
let mut msg_stream = msg_stream.aggregate_continuations(); let mut msg_stream = msg_stream.aggregate_continuations();
let mut peer_rx = app_state.peer_updates.subscribe(); let mut peer_rx = app_state.peer_updates.subscribe();
match app_state.storage.get_peers().await { match app_state.storage.get_peers().await {
Ok(initial_peers) => { Ok(initial_peers) => {
let json = serde_json::to_string(&initial_peers).unwrap_or_else(|_| "[]".to_string()); let json = serde_json::to_string(&initial_peers).unwrap_or_else(|_| "[]".to_string());
if session.text(json).await.is_err() { if session.text(json).await.is_err() {
return Ok(res); return Ok(res);
} }
tracing::info!( tracing::info!(
"sent initial peer list ({} peers) to new client", + "sent initial peer list ({} peers) to new client",
initial_peers.len() initial_peers.len()
); );
} }
Err(e) => { Err(e) => {
tracing::warn!("failed to fetch initial peers: {:?}", e); tracing::warn!("failed to fetch initial peers: {:?}", e);
session.close(None).await.ok(); + session.close(None).await.ok();
return Ok(res); return Ok(res);
} }
} }
rt::spawn(async move { rt::spawn(async move {
loop { loop {
tokio::select! { tokio::select! {
msg = msg_stream.next() => { msg = msg_stream.next() => {
match msg { match msg {
Some(Ok(AggregatedMessage::Ping(data))) => { Some(Ok(AggregatedMessage::Ping(data))) => {
if session.pong(&data).await.is_err() { if session.pong(&data).await.is_err() {
break; break;
} }
} }
Some(Ok(AggregatedMessage::Pong(_))) => {} Some(Ok(AggregatedMessage::Pong(_))) => {}
Some(Ok(AggregatedMessage::Close(_))) => { Some(Ok(AggregatedMessage::Close(_))) => {
break; break;
} }
Some(Ok(AggregatedMessage::Text(_))) => { Some(Ok(AggregatedMessage::Text(_))) => {
} }
Some(Ok(AggregatedMessage::Binary(_))) => { Some(Ok(AggregatedMessage::Binary(_))) => {
} }
Some(Err(_)) => { Some(Err(_)) => {
break; break;
} }
None => { None => {
break; break;
} }
} }
} }
update = peer_rx.recv() => { update = peer_rx.recv() => {
match update { match update {
Ok(peer_update) => { Ok(peer_update) => {
let json = serde_json::to_string(&peer_update.peer) let json = serde_json::to_string(&peer_update.peer)
.unwrap_or_else(|_| "{}".to_string()); .unwrap_or_else(|_| "{}".to_string());
if session.text(json).await.is_err() { if session.text(json).await.is_err() {
break; break;
} }
tracing::info!("sent peer update to client: {}", peer_update.peer.public_key); + tracing::info!("sent peer update to client: {}", peer_update.peer.public_key);
} }
Err(RecvError::Lagged(n)) => { + Err(RecvError::Lagged(n)) => {
tracing::warn!("client lagged, missed {} updates", n); + tracing::warn!("client lagged, missed {} updates", n);
} }
Err(RecvError::Closed) => { + Err(RecvError::Closed) => {
break; break;
} }
} }
} }
} }
} }
session.close(None).await.ok(); + session.close(None).await.ok();
tracing::info!("client disconnected"); + tracing::info!("client disconnected");
}); });
Ok(res) Ok(res)
} }

View File

@@ -2,9 +2,9 @@ use crate::{
AppState, PeerUpdate, AppState, PeerUpdate,
error::Result, error::Result,
storage::{RegisterRequest, StorageImpl}, storage::{RegisterRequest, StorageImpl},
utils::Peer,
}; };
use actix_web::{HttpResponse, web}; use actix_web::{HttpResponse, web};
use registry::Peer;
pub async fn register_peer( pub async fn register_peer(
app_state: web::Data<AppState>, app_state: web::Data<AppState>,

View File

@@ -0,0 +1,92 @@
use crate::{AppState, error::Error, storage::StorageImpl};
use actix_web::{HttpRequest, HttpResponse, rt, web};
use actix_ws::AggregatedMessage;
use futures_util::StreamExt;
use registry::PeerMessage;
pub async fn peers(
req: HttpRequest,
stream: web::Payload,
app_state: web::Data<AppState>,
) -> Result<HttpResponse, Error> {
let (res, mut session, msg_stream) =
actix_ws::handle(&req, stream).map_err(|e| Error::ws(e))?;
let mut msg_stream = msg_stream.aggregate_continuations();
let mut peer_rx = app_state.peer_updates.subscribe();
match app_state.storage.get_peers().await {
Ok(initial_peers) => {
let msg = PeerMessage::HydratePeers {
peers: initial_peers.clone(),
};
let json = serde_json::to_string(&msg)
.unwrap_or_else(|_| r#"{"type":"HydratePeers","peers":[]}"#.to_string());
if session.text(json).await.is_err() {
return Ok(res);
}
tracing::info!(
"sent initial peer list ({} peers) to new WebSocket client",
initial_peers.len()
);
}
Err(e) => {
tracing::warn!("failed to fetch initial peers: {:?}", e);
session.close(None).await.ok();
return Ok(res);
}
}
rt::spawn(async move {
loop {
tokio::select! {
msg = msg_stream.next() => {
match msg {
Some(Ok(AggregatedMessage::Ping(data))) => {
if session.pong(&data).await.is_err() {
break;
}
}
Some(Ok(AggregatedMessage::Pong(_))) => {}
Some(Ok(AggregatedMessage::Close(_))) => {
break;
}
Some(Ok(AggregatedMessage::Text(_))) => {
}
Some(Ok(AggregatedMessage::Binary(_))) => {
}
Some(Err(_)) => {
break;
}
None => {
break;
}
}
}
update = peer_rx.recv() => {
match update {
Ok(peer_update) => {
let json = serde_json::to_string(&peer_update.peer)
.unwrap_or_else(|_| "{}".to_string());
if session.text(json).await.is_err() {
break;
}
tracing::info!("sent peer update to WebSocket client: {}", peer_update.peer.public_key);
}
Err(tokio::sync::broadcast::error::RecvError::Lagged(n)) => {
tracing::warn!("WebSocket client lagged, missed {} updates", n);
}
Err(tokio::sync::broadcast::error::RecvError::Closed) => {
break;
}
}
}
}
}
session.close(None).await.ok();
tracing::info!("WebSocket client disconnected");
});
Ok(res)
}

View File

@@ -37,6 +37,7 @@ pub enum ErrorKind {
impl ResponseError for Error { impl ResponseError for Error {
fn error_response(&self) -> actix_web::HttpResponse<actix_web::body::BoxBody> { fn error_response(&self) -> actix_web::HttpResponse<actix_web::body::BoxBody> {
match self.inner() { match self.inner() {
ErrorKind::Ws(e) => e.error_response(),
_ => HttpResponse::InternalServerError().finish(), _ => HttpResponse::InternalServerError().finish(),
} }
} }

4
registry/src/lib.rs Normal file
View File

@@ -0,0 +1,4 @@
mod types;
mod utils;
pub use types::peer_message::*;

View File

@@ -1,10 +1,12 @@
mod endpoints; mod endpoints;
mod error; mod error;
mod storage; mod storage;
mod types;
mod utils; mod utils;
use actix_web::{App, HttpServer, web}; use actix_web::{App, HttpServer, web};
use console::style; use console::style;
use registry::Peer;
use thiserror_ext::AsReport; use thiserror_ext::AsReport;
use tokio::sync::broadcast; use tokio::sync::broadcast;
use tracing::level_filters::LevelFilter; use tracing::level_filters::LevelFilter;
@@ -13,7 +15,6 @@ use tracing_subscriber::{
}; };
use crate::storage::{Storage, get_storage_from_env}; use crate::storage::{Storage, get_storage_from_env};
use crate::utils::Peer;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct PeerUpdate { pub struct PeerUpdate {

View File

@@ -1,10 +1,8 @@
use crate::{ use crate::error::{Error, Result};
error::{Error, Result},
utils::Peer,
};
mod valkey; mod valkey;
use registry::Peer;
pub use valkey::RegisterRequest; pub use valkey::RegisterRequest;
pub enum Storage { pub enum Storage {

View File

@@ -3,10 +3,11 @@ use std::net::IpAddr;
use ipnetwork::IpNetwork; use ipnetwork::IpNetwork;
use redis::AsyncTypedCommands; use redis::AsyncTypedCommands;
use registry::Peer;
use serde::Deserialize; use serde::Deserialize;
use crate::error::Result; use crate::error::Result;
use crate::utils::{Peer, WireguardPublicKey}; use crate::utils::WireguardPublicKey;
use crate::{error::Error, storage::StorageImpl}; use crate::{error::Error, storage::StorageImpl};
pub struct ValkeyStorage { pub struct ValkeyStorage {

View File

@@ -0,0 +1 @@
pub mod peer_message;

View File

@@ -1,6 +1,7 @@
use ipnetwork::IpNetwork;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use ipnetwork::IpNetwork;
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Peer { pub struct Peer {
pub public_key: String, pub public_key: String,
@@ -8,3 +9,10 @@ pub struct Peer {
pub port: String, pub port: String,
pub allowed_ips: Vec<IpNetwork>, pub allowed_ips: Vec<IpNetwork>,
} }
#[derive(Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum PeerMessage {
HydratePeers { peers: Vec<Peer> },
PeerUpdate { peer: Peer },
}

View File

@@ -1,5 +1,3 @@
mod peer;
mod wg; mod wg;
pub use peer::Peer;
pub use wg::WireguardPublicKey; pub use wg::WireguardPublicKey;