initial commit
This commit is contained in:
commit
3163644c62
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/target
|
||||
db.json
|
506
Cargo.lock
generated
Normal file
506
Cargo.lock
generated
Normal file
@ -0,0 +1,506 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.68"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.67"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c041d3eab048880cb0b86b256447da3f18859a163c3b8d8893f4e6368abe6393"
|
||||
dependencies = [
|
||||
"bytemuck_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck_derive"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aca418a974d83d40a0c1f0c5cba6ff4bc28d8df099109ca459a2118d40b6322"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
|
||||
|
||||
[[package]]
|
||||
name = "centralex"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytemuck",
|
||||
"rand",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dec7af912d60cdbd3677c1af9352ebae6fb8394d165568a2234df0fa00f87793"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.139"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
|
||||
dependencies = [
|
||||
"adler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.30.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b8c786513eb403643f2a88c244c2aaa270ef2153f55094587d0c48a3cf22a83"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
|
||||
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"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.152"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.152"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.91"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.24.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "597a12a59981d9e3c38d216785b0c37399f6e415e8d0712047620f189371b0bb"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"bytes",
|
||||
"libc",
|
||||
"memchr",
|
||||
"mio",
|
||||
"num_cpus",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "1.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
|
14
Cargo.toml
Normal file
14
Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "centralex"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = { version = "1.0.68", features = ["backtrace"] }
|
||||
bytemuck = { version = "1.13.0", features = ["derive"] }
|
||||
rand = "0.8.5"
|
||||
serde = { version = "1.0.152", features = ["derive"] }
|
||||
serde_json = "1.0.91"
|
||||
tokio = { version = "1.24.2", features = ["full"] }
|
446
src/main.rs
Normal file
446
src/main.rs
Normal file
@ -0,0 +1,446 @@
|
||||
#![feature(generic_const_exprs)]
|
||||
#![allow(unused)]
|
||||
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet, HashMap, HashSet},
|
||||
fmt::Debug,
|
||||
fs::File,
|
||||
future::Future,
|
||||
io::{BufReader, BufWriter},
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||
ops::Range,
|
||||
path::{Path, PathBuf},
|
||||
sync::{Arc, Mutex},
|
||||
time::{Duration, SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use anyhow::bail;
|
||||
use packets::{reject_static, Header, Packet, RemConnect};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::{
|
||||
io::{AsyncReadExt, AsyncWriteExt},
|
||||
net::{TcpListener, TcpSocket, TcpStream},
|
||||
select,
|
||||
task::JoinHandle,
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use crate::packets::dyn_ip_update;
|
||||
|
||||
const AUTH_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
const CALL_ACK_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
const PING_INTERVAL: Duration = Duration::from_secs(15);
|
||||
const TIMEOUT_DELAY: Duration = Duration::from_secs(35);
|
||||
const PORT_TIMEOUT: Duration = Duration::from_secs(60);
|
||||
const PORT_RETRY_TIME: Duration = Duration::from_secs(60); // 10 *
|
||||
|
||||
const BIND_IP: &str = "0.0.0.0";
|
||||
|
||||
mod packets;
|
||||
|
||||
type Port = u16;
|
||||
type Number = u32;
|
||||
type UnixTimestamp = u64;
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
struct Config {
|
||||
allowed_ports: AllowedPorts,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
fn load(db: &Path) -> std::io::Result<Self> {
|
||||
println!("loading config");
|
||||
Ok(serde_json::from_reader(BufReader::new(File::open(db)?))?)
|
||||
}
|
||||
|
||||
fn load_or_default(db: &Path) -> std::io::Result<Self> {
|
||||
match Self::load(db) {
|
||||
Ok(db) => Ok(db),
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(Self::default()),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
struct PortHandler {
|
||||
#[serde(skip)]
|
||||
last_update: Option<Instant>,
|
||||
|
||||
#[serde(skip)]
|
||||
port_guards: HashMap<Port, PortGuard>,
|
||||
|
||||
allowed_ports: AllowedPorts,
|
||||
|
||||
free_ports: HashSet<Port>,
|
||||
errored_ports: BTreeSet<(UnixTimestamp, Port)>,
|
||||
allocated_ports: HashMap<Number, Port>,
|
||||
port_status: HashMap<Port, PortStatus>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct PortStatus {}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
|
||||
struct AllowedPorts(Vec<Range<u16>>);
|
||||
|
||||
impl AllowedPorts {
|
||||
fn is_allowed(&self, port: Port) -> bool {
|
||||
self.0.iter().any(|range| range.contains(&port))
|
||||
}
|
||||
}
|
||||
|
||||
impl PortHandler {
|
||||
fn register_update(&mut self) {
|
||||
self.last_update = Some(Instant::now());
|
||||
}
|
||||
|
||||
fn store(&self, db: &Path) -> anyhow::Result<()> {
|
||||
println!("storing database");
|
||||
serde_json::to_writer(BufWriter::new(File::create(db)?), self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load(db: &Path) -> std::io::Result<Self> {
|
||||
println!("loading database");
|
||||
Ok(serde_json::from_reader(BufReader::new(File::open(db)?))?)
|
||||
}
|
||||
|
||||
fn load_or_default(db: &Path) -> std::io::Result<Self> {
|
||||
match Self::load(db) {
|
||||
Ok(db) => Ok(db),
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(Self::default()),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
fn update_allowed_ports(&mut self, allowed_ports: &AllowedPorts) {
|
||||
self.register_update();
|
||||
|
||||
self.allowed_ports = allowed_ports.clone();
|
||||
|
||||
self.free_ports.clear();
|
||||
self.free_ports
|
||||
.extend(self.allowed_ports.0.iter().cloned().flatten());
|
||||
|
||||
self.free_ports.shrink_to_fit(); // we are at the maximum number of ports we'll ever reach
|
||||
|
||||
self.errored_ports
|
||||
.retain(|(_, port)| self.allowed_ports.is_allowed(*port));
|
||||
|
||||
self.allocated_ports
|
||||
.retain(|_, port| self.allowed_ports.is_allowed(*port));
|
||||
|
||||
self.free_ports.retain(|port| {
|
||||
self.allocated_ports
|
||||
.iter()
|
||||
.find(|(_, allocated_port)| *allocated_port == port)
|
||||
.is_none()
|
||||
&& self
|
||||
.errored_ports
|
||||
.iter()
|
||||
.find(|(_, errored_port)| errored_port == port)
|
||||
.is_none()
|
||||
});
|
||||
}
|
||||
|
||||
fn start_port_guard<'fut, Fut, Func>(&mut self, port: Port, listener: TcpListener, f: Func)
|
||||
where
|
||||
Fut: Future<Output = ()> + Send + 'fut,
|
||||
Func: FnOnce(&'_ mut TcpListener) -> Fut + Send + 'static,
|
||||
{
|
||||
assert!(self
|
||||
.port_guards
|
||||
.insert(port, PortGuard::start(listener, f))
|
||||
.is_none());
|
||||
}
|
||||
|
||||
fn start_rejector(&mut self, port: Port, listener: TcpListener, packet: Packet) {
|
||||
assert!(self
|
||||
.port_guards
|
||||
.insert(
|
||||
port,
|
||||
PortGuard::start(listener, move |listener: &mut TcpListener| async move {
|
||||
loop {
|
||||
if let Ok((mut socket, _)) = listener.accept().await {
|
||||
let (_, mut writer) = socket.split();
|
||||
let _ = packet.send(&mut writer).await;
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
.is_none());
|
||||
}
|
||||
}
|
||||
|
||||
struct PortGuard {
|
||||
listener: Arc<tokio::sync::Mutex<TcpListener>>,
|
||||
handle: JoinHandle<()>,
|
||||
}
|
||||
|
||||
impl Debug for PortGuard {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("PortGuard").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl PortGuard {
|
||||
fn start<'fut, Fut>(
|
||||
listener: TcpListener,
|
||||
f: impl FnOnce(&mut TcpListener) -> Fut + Send + 'static,
|
||||
) -> Self
|
||||
where
|
||||
Fut: Future<Output = ()> + Send + 'fut,
|
||||
{
|
||||
let mut listener = Arc::new(tokio::sync::Mutex::new(listener));
|
||||
|
||||
let handle = {
|
||||
let listener = listener.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut lock = listener.lock().await;
|
||||
f(&mut *lock).await;
|
||||
})
|
||||
};
|
||||
|
||||
Self { listener, handle }
|
||||
}
|
||||
|
||||
async fn stop(mut self) -> TcpListener {
|
||||
self.handle.abort();
|
||||
let _ = self.handle.await;
|
||||
Arc::try_unwrap(self.listener).unwrap().into_inner()
|
||||
}
|
||||
}
|
||||
|
||||
impl PortHandler {
|
||||
fn allocate_port_for_number(&mut self, number: Number) -> Option<Port> {
|
||||
if let Some(port) = self.allocated_ports.get(&number) {
|
||||
return Some(*port);
|
||||
}
|
||||
|
||||
let port = if let Some(&port) = self.free_ports.iter().next() {
|
||||
self.register_update();
|
||||
self.free_ports.remove(&port);
|
||||
port
|
||||
} else {
|
||||
self.try_recover_port()?
|
||||
};
|
||||
|
||||
assert!(self.allocated_ports.insert(number, port).is_none());
|
||||
Some(port)
|
||||
}
|
||||
|
||||
fn try_recover_port(&mut self) -> Option<Port> {
|
||||
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
|
||||
|
||||
let mut recovered_port = None;
|
||||
|
||||
self.errored_ports = std::mem::take(&mut self.errored_ports)
|
||||
.into_iter()
|
||||
.filter_map(|(mut timestamp, mut port)| {
|
||||
if recovered_port.is_none()
|
||||
&& now.saturating_sub(Duration::from_secs(timestamp)) >= PORT_RETRY_TIME
|
||||
{
|
||||
println!(
|
||||
" trying port: {port} at -{:?}",
|
||||
Duration::from_secs(now.as_secs())
|
||||
.saturating_sub(Duration::from_secs(timestamp))
|
||||
);
|
||||
|
||||
match std::net::TcpListener::bind((BIND_IP, port)) {
|
||||
Ok(_) => {
|
||||
recovered_port = Some((timestamp, port));
|
||||
return None;
|
||||
}
|
||||
Err(_) => timestamp = now.as_secs(),
|
||||
}
|
||||
} else {
|
||||
println!(
|
||||
"skipped port: {port} at -{:?}",
|
||||
Duration::from_secs(now.as_secs())
|
||||
.saturating_sub(Duration::from_secs(timestamp))
|
||||
);
|
||||
}
|
||||
|
||||
Some((timestamp, port))
|
||||
})
|
||||
.collect();
|
||||
|
||||
if let Some((_, port)) = recovered_port {
|
||||
println!("recovered_port: {port}");
|
||||
return Some(port);
|
||||
}
|
||||
|
||||
None // TODO
|
||||
}
|
||||
|
||||
fn mark_port_error(&mut self, number: Number, port: Port) {
|
||||
self.register_update();
|
||||
|
||||
self.errored_ports.insert((
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs(),
|
||||
port,
|
||||
));
|
||||
|
||||
self.allocated_ports.remove(&number);
|
||||
self.free_ports.remove(&port);
|
||||
}
|
||||
|
||||
fn open_port(&mut self, port: Port) -> Option<TcpListener> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn close_port_for(&mut self, number: Number, listener: TcpListener) -> anyhow::Result<()> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
async fn connection_handler(
|
||||
port_handler: Arc<Mutex<PortHandler>>,
|
||||
stream: &mut TcpStream,
|
||||
) -> anyhow::Result<()> {
|
||||
let (mut reader, mut writer) = stream.split();
|
||||
|
||||
let mut packet = Packet::recv(&mut reader).await?;
|
||||
|
||||
let RemConnect { number, pin } = packet.as_rem_connect()?;
|
||||
|
||||
let (port, listener) = loop {
|
||||
let port = port_handler
|
||||
.lock()
|
||||
.unwrap()
|
||||
.allocate_port_for_number(number);
|
||||
|
||||
println!("allocated port: {:?}", port);
|
||||
|
||||
let Some(port) = port else {
|
||||
writer.write_all(&reject_static(b"oop")).await?;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let ip = dyn_ip_update(number, pin, port).await?;
|
||||
|
||||
let listener = TcpListener::bind((BIND_IP, port)).await;
|
||||
|
||||
let listener = match listener {
|
||||
Ok(listener) => break (port, listener),
|
||||
Err(err) => {
|
||||
port_handler.lock().unwrap().mark_port_error(number, port);
|
||||
// tokio::time::sleep(Duration::from_millis(300)).await;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Foo {
|
||||
Caller { stream: TcpStream, addr: SocketAddr },
|
||||
Packet { packet: Packet },
|
||||
}
|
||||
|
||||
let result = select! {
|
||||
kind = Packet::peek_packet_kind(&mut reader) => {
|
||||
packet.recv_into(&mut reader).await?;
|
||||
Foo::Packet { packet }
|
||||
},
|
||||
caller = listener.accept() => {
|
||||
let (stream, addr) = caller?;
|
||||
Foo::Caller { stream, addr }
|
||||
},
|
||||
};
|
||||
|
||||
dbg!(&result);
|
||||
|
||||
match result {
|
||||
Foo::Caller { stream, addr } => todo!(),
|
||||
Foo::Packet { mut packet } => {
|
||||
match packet.kind() {
|
||||
packets::PacketKind::End => {
|
||||
packet.header = Header { kind: 3, length: 0 };
|
||||
packet.data.clear();
|
||||
}
|
||||
packets::PacketKind::Reject => {}
|
||||
|
||||
kind => bail!("unexpected packet: {kind:?}"),
|
||||
}
|
||||
port_handler
|
||||
.lock()
|
||||
.unwrap()
|
||||
.start_rejector(port, listener, packet);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let config = Config::load_or_default("config.json".as_ref())?;
|
||||
|
||||
if config.allowed_ports.0.is_empty() {
|
||||
panic!("no allowed ports");
|
||||
}
|
||||
|
||||
let db_path = PathBuf::from("db.json");
|
||||
|
||||
let mut port_handler = PortHandler::load_or_default(&db_path)?;
|
||||
port_handler.update_allowed_ports(&config.allowed_ports);
|
||||
|
||||
let port_handler = Arc::new(Mutex::new(port_handler));
|
||||
|
||||
{
|
||||
let port_handler = port_handler.clone();
|
||||
tokio::spawn(async move {
|
||||
let mut last_store = None;
|
||||
loop {
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
|
||||
let port_handler = port_handler.lock().unwrap();
|
||||
|
||||
if let Some(last_update) = port_handler.last_update {
|
||||
let should_store = last_store
|
||||
.map(|last_store| last_update > last_store)
|
||||
.unwrap_or(true);
|
||||
|
||||
if should_store {
|
||||
last_store = Some(last_update);
|
||||
port_handler.store(&db_path).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let listener = TcpListener::bind(("127.0.0.1", 11812)).await?;
|
||||
|
||||
while let Ok((mut stream, addr)) = listener.accept().await {
|
||||
println!("connection from {addr}");
|
||||
|
||||
let port_handler = port_handler.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
if let Err(err) = connection_handler(port_handler, &mut stream).await {
|
||||
println!("client at {addr} had an error: {err}");
|
||||
|
||||
let mut packet = Packet::default();
|
||||
|
||||
packet.data.extend_from_slice(err.to_string().as_bytes());
|
||||
packet.data.truncate(0xfe);
|
||||
packet.data.push(0);
|
||||
packet.header = Header {
|
||||
kind: 0xff,
|
||||
length: packet.data.len() as u8,
|
||||
};
|
||||
|
||||
let (_, mut writer) = stream.split();
|
||||
let _ = packet.send(&mut writer).await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
210
src/packets.rs
Normal file
210
src/packets.rs
Normal file
@ -0,0 +1,210 @@
|
||||
use std::{ffi::CString, mem::discriminant};
|
||||
|
||||
use anyhow::bail;
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use tokio::{
|
||||
io::{AsyncReadExt, AsyncWriteExt},
|
||||
net::tcp::{ReadHalf, WriteHalf},
|
||||
};
|
||||
|
||||
pub const fn reject_static<const N: usize>(message: &[u8; N]) -> [u8; N + 2] {
|
||||
let mut pkg = [0u8; N + 2];
|
||||
pkg[0] = 4;
|
||||
pkg[1] = message.len() as u8;
|
||||
let mut i = 0;
|
||||
while i < message.len() {
|
||||
pkg[i + 2] = message[i];
|
||||
i += 1;
|
||||
}
|
||||
pkg
|
||||
}
|
||||
|
||||
pub const REJECT_OCC: &[u8; 6] = b"\x04\x04occ\x00";
|
||||
pub const REJECT_NC: &[u8; 5] = b"\x04\x03nc\x00";
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum PacketKind {
|
||||
Unknown(u8),
|
||||
DynIpUpdate = 0x01,
|
||||
DynIpUpdateResponse = 0x02,
|
||||
End = 0x03,
|
||||
Reject = 0x04,
|
||||
RemConnect = 0x81,
|
||||
RemConfirm = 0x82,
|
||||
RemCall = 0x83,
|
||||
RemAck = 0x84,
|
||||
Error = 0xff,
|
||||
}
|
||||
|
||||
impl PacketKind {
|
||||
fn from_u8(raw: u8) -> Self {
|
||||
use PacketKind::*;
|
||||
|
||||
match raw {
|
||||
0x01 => DynIpUpdate,
|
||||
0x02 => DynIpUpdateResponse,
|
||||
0x03 => End,
|
||||
0x04 => Reject,
|
||||
0x81 => RemConnect,
|
||||
0x82 => RemConfirm,
|
||||
0x83 => RemCall,
|
||||
0x84 => RemAck,
|
||||
0xff => Error,
|
||||
kind => Unknown(kind),
|
||||
}
|
||||
}
|
||||
|
||||
fn kind(&self) -> u8 {
|
||||
use PacketKind::*;
|
||||
|
||||
match self {
|
||||
Unknown(value) => *value,
|
||||
DynIpUpdate => 0x01,
|
||||
DynIpUpdateResponse => 0x02,
|
||||
End => 0x03,
|
||||
Reject => 0x04,
|
||||
RemConnect => 0x81,
|
||||
RemConfirm => 0x82,
|
||||
RemCall => 0x83,
|
||||
RemAck => 0x84,
|
||||
Error => 0xff,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct Header {
|
||||
pub kind: u8,
|
||||
pub length: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Packet {
|
||||
pub header: Header,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct RemConnect {
|
||||
pub number: u32,
|
||||
pub pin: u16,
|
||||
}
|
||||
|
||||
impl Packet {
|
||||
pub async fn peek_packet_kind(stream: &mut ReadHalf<'_>) -> std::io::Result<PacketKind> {
|
||||
Self::peek_packet_kind_raw(stream)
|
||||
.await
|
||||
.map(PacketKind::from_u8)
|
||||
}
|
||||
|
||||
pub async fn peek_packet_kind_raw(stream: &mut ReadHalf<'_>) -> std::io::Result<u8> {
|
||||
let mut kind = 0;
|
||||
let n = stream.peek(std::slice::from_mut(&mut kind)).await?;
|
||||
|
||||
if n == 1 {
|
||||
Ok(kind)
|
||||
} else {
|
||||
Err(std::io::ErrorKind::UnexpectedEof.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn recv(stream: &mut ReadHalf<'_>) -> std::io::Result<Packet> {
|
||||
let mut packet = Packet::default();
|
||||
packet.recv_into(stream).await?;
|
||||
Ok(packet)
|
||||
}
|
||||
|
||||
pub async fn recv_into(&mut self, stream: &mut ReadHalf<'_>) -> std::io::Result<()> {
|
||||
let header_bytes = bytemuck::bytes_of_mut(&mut self.header);
|
||||
|
||||
stream.read_exact(header_bytes).await?;
|
||||
|
||||
self.data.resize(self.header.length as usize, 0);
|
||||
|
||||
stream.read_exact(&mut self.data).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn send(&self, stream: &mut WriteHalf<'_>) -> std::io::Result<()> {
|
||||
stream.write_all(bytemuck::bytes_of(&self.header)).await?;
|
||||
stream.write_all(&self.data).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> PacketKind {
|
||||
PacketKind::from_u8(self.header.kind)
|
||||
}
|
||||
|
||||
pub fn as_rem_connect(&self) -> anyhow::Result<RemConnect> {
|
||||
if self.kind() != PacketKind::RemConnect {
|
||||
bail!("Unexpected Packet: {:?} expected RemConnect", self.kind());
|
||||
}
|
||||
|
||||
if self.data.len() < 6 {
|
||||
bail!(
|
||||
"Too little data for RemConnect. Need at least 6 Bytes got {}",
|
||||
self.data.len()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(RemConnect {
|
||||
number: u32::from_le_bytes(self.data[..4].try_into()?),
|
||||
pin: u16::from_le_bytes(self.data[4..6].try_into()?),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn dyn_ip_update(number: u32, pin: u16, port: u16) -> anyhow::Result<std::net::Ipv4Addr> {
|
||||
let mut packet = Packet::default();
|
||||
packet.header = Header {
|
||||
kind: PacketKind::DynIpUpdate.kind(),
|
||||
length: 8,
|
||||
};
|
||||
|
||||
packet.data.clear();
|
||||
packet.data.reserve(packet.header.length as usize);
|
||||
packet.data.extend_from_slice(&number.to_le_bytes());
|
||||
packet.data.extend_from_slice(&pin.to_le_bytes());
|
||||
packet.data.extend_from_slice(&port.to_le_bytes());
|
||||
|
||||
let mut socket = tokio::net::TcpStream::connect(("127.0.0.1", 11811)).await?;
|
||||
|
||||
let (mut reader, mut writer) = socket.split();
|
||||
|
||||
packet.send(&mut writer).await?;
|
||||
|
||||
packet.recv_into(&mut reader).await?;
|
||||
|
||||
match packet.kind() {
|
||||
PacketKind::DynIpUpdateResponse => Ok(<[u8; 4]>::try_from(packet.data)
|
||||
.map_err(|err| {
|
||||
anyhow::anyhow!(
|
||||
"too little data for ip address. Need 4 bytes got {}",
|
||||
err.len()
|
||||
)
|
||||
})?
|
||||
.into()),
|
||||
PacketKind::Error => {
|
||||
let first_zero = packet
|
||||
.data
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(|(i, x)| (*x == 0).then_some(i));
|
||||
|
||||
bail!(
|
||||
"{}",
|
||||
std::str::from_utf8(
|
||||
first_zero
|
||||
.map(|i| &packet.data[..i])
|
||||
.unwrap_or(&packet.data),
|
||||
)?
|
||||
)
|
||||
}
|
||||
|
||||
_ => bail!("server returned unexpected packet"),
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user