updated README with instructions for docker and docker-compose versions, added docker-compose config for running both the control panel and server with a shared log volume, reduced size of control panel image, use tuptime because uptime isn't installed anyway

asyncversion
Anton Franzluebbers 2022-11-06 19:11:59 -05:00
parent 5200ba7723
commit 5f52900ded
12 changed files with 1959 additions and 18 deletions

View File

@ -1,6 +1,6 @@
# VelNetServerRust
# VelNet Server
This basic, single-file relay server is designed to be used for network games, and is similar to Photon Realtime in design. It is written in Rust, with a single-threaded, non-blocking design and does not rely on any network frameworks (pure TCP/UDP). A Unity/C# client implementation can be found in our VelNetUnity repository.
This basic, single-file relay server is designed to be used for network games, and is similar to Photon Realtime in design. It is written in Rust, with a single-threaded, non-blocking design and does not rely on any network frameworks (pure TCP/UDP). A Unity/C# client implementation can be found in our [VelNetUnity](https://github.com/velaboratory/VelNetUnity) repository.
Like Photon, there is no built-in persistence of rooms or data. Rooms are created when the first client joins and destroyed when the last client leaves.
@ -12,12 +12,45 @@ The server supports both TCP and UDP transports.
## Running
1. Get a linoox server (also runs fine on windows & osx, but the instructions below are for linux)
### Option 1: Pull from Docker Hub
```sh
docker run -p 5000:5000 velaboratory/velnet
```
or
```sh
docker run -p 5050:5000 --name velnet velaboratory/velnet
```
To run on a different port and change the name of the container.
### Option 2: Use docker-compose
Runs both the control panel and the server.
```sh
docker compose up -d
```
to run, and
```sh
docker compose stop
```
to stop.
This builds the images from the local data in the folder, and doesn't pull anything from Docker Hub.
### Option 3: Run Rust natively
1. Get a linoox server (also runs fine on Windows & OSX, but the instructions below are for Linux)
2. Clone this repo
3. Edit config.json to an open port on your firewall
3. Edit `config.json` to an open port on your firewall
4. Modify the `user` field in `control-panel/config.json` to be your username.
5. Install rust through using rustup: https://rustup.rs/
6. Install: `sudo ./install.sh`
7. Run server: `sudo systemctl start velnet`
8. Run control panel: `sudo systemctl start velnet-control-panel`
9. Install tuptime: `cargo install tuptime`
9. Install onefetch: `cargo install onefetch`

41
control-panel/Dockerfile Normal file
View File

@ -0,0 +1,41 @@
FROM rust:1.64 as build
# 1. Create a new empty shell project
RUN USER=root cargo new --bin velnet_control_panel
WORKDIR /velnet_control_panel
# 2. Copy our manifests
COPY ./Cargo.lock ./Cargo.lock
COPY ./Cargo.toml ./Cargo.toml
# 3. Build only the dependencies to cache them
RUN cargo build --release && rm src/*.rs
# 4. Now that the dependency is built, copy your source code
COPY ./src ./src
# 5. Build for release.
RUN rm ./target/release/deps/velnet_control_panel*
RUN cargo build --release
# our final base
FROM rust:1.64-slim
WORKDIR /velnet_control_panel
# RUN apt-get update && apt-get install -y extra-runtime-dependencies && rm -rf /var/lib/apt/lists/*
RUN apt update && apt install -y tuptime
# copy the build artifact from the build stage
COPY --from=build /velnet_control_panel/target/release/velnet_control_panel .
# Copy the config files and helper scripts
COPY static static
COPY config.json .
COPY onefetch_file.sh .
COPY git_pull.sh .
COPY compile_server.sh .
EXPOSE 8080
# run
CMD ["./velnet_control_panel"]

View File

@ -1,7 +1,7 @@
{
"port": 8080,
"user": "ntsfranz",
"server_log_file": "../server.log",
"server_log_file": "../logs/server.log",
"control_panel_log_file": "control_panel.log",
"server_dir": "../",
"handlebars_dev_mode": true

View File

@ -0,0 +1,4 @@
docker stop velnet_control_panel
docker build -t velaboratory/velnet_control_panel .
docker rm velnet_control_panel
docker run -d -p 8080:8080 --name velnet_control_panel velaboratory/velnet_control_panel

View File

@ -49,24 +49,24 @@ async fn index(hb: web::Data<Handlebars<'_>>) -> HttpResponse {
// let restarts_log = lines_from_file(config.restarts_log_file);
let uptime = Command::new("uptime")
let uptime = Command::new("tuptime")
.output()
.expect("failed to execute process");
let _onefetch = Command::new("sh")
.arg("onefetch_file.sh")
.arg(config.user)
.output()
.expect("failed");
// let _onefetch = Command::new("sh")
// .arg("onefetch_file.sh")
// .arg(config.user)
// .output()
// .expect("failed");
let onefetch = fs::read_to_string("onefetch.out").unwrap();
// let onefetch = fs::read_to_string("onefetch.out").unwrap();
let data = json!({
"log_output": &log_file[(cmp::max((log_file.len() as i64) - 1000, 0) as usize)..],
// "restarts_output": &restarts_log[(cmp::max((restarts_log.len() as i64) - 1000, 0) as usize)..],
"uptime": format!("{}", String::from_utf8_lossy(&uptime.stdout)),
//"onefetch": format!("{}", String::from_utf8_lossy(&onefetch.stdout))
"onefetch": onefetch.trim_end()
"onefetch": ""
});
let body = hb.render("index", &data).unwrap();

View File

@ -18,7 +18,7 @@
<style>
.bottom-scroller {
height: 40em;
max-height: 40em;
overflow: auto;
display: flex;
flex-direction: column-reverse;
@ -45,17 +45,16 @@
</section>
<section>
{{!-- <section>
<div class="container">
<div class="block">
<button class="button" id="restart-button">Restart Server</button>
<button class="button" id="pull-button">Git Pull</button>
<button class="button" id="compile-button">Compile</button>
</div>
<div class="block">Uptime: {{uptime}}</div>
<pre style="font-size: 0.8em;">{{onefetch}}</pre>
<pre style="font-size: 0.8em;">{{uptime}}</pre>
</div>
</section>
</section> --}}
<section class="section">
<div class="container">

19
docker-compose.yaml Normal file
View File

@ -0,0 +1,19 @@
version: "3.9"
services:
control-panel:
build: control-panel
ports:
- "8080:8080"
volumes:
- logs-volume:/logs
server:
build: server
ports:
- "5000:5000/tcp"
- "5000:5000/udp"
volumes:
- logs-volume:/logs
volumes:
logs-volume:

798
server/Cargo.lock generated Normal file
View File

@ -0,0 +1,798 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "async-attributes"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "async-channel"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319"
dependencies = [
"concurrent-queue",
"event-listener",
"futures-core",
]
[[package]]
name = "async-executor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965"
dependencies = [
"async-task",
"concurrent-queue",
"fastrand",
"futures-lite",
"once_cell",
"slab",
]
[[package]]
name = "async-global-executor"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c026b7e44f1316b567ee750fea85103f87fcb80792b860e979f221259796ca0a"
dependencies = [
"async-channel",
"async-executor",
"async-io",
"async-mutex",
"blocking",
"futures-lite",
"num_cpus",
"once_cell",
]
[[package]]
name = "async-io"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a811e6a479f2439f0c04038796b5cfb3d2ad56c230e0f2d3f7b04d68cfee607b"
dependencies = [
"concurrent-queue",
"futures-lite",
"libc",
"log",
"once_cell",
"parking",
"polling",
"slab",
"socket2",
"waker-fn",
"winapi",
]
[[package]]
name = "async-lock"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6"
dependencies = [
"event-listener",
]
[[package]]
name = "async-mutex"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e"
dependencies = [
"event-listener",
]
[[package]]
name = "async-notify"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8356f653934a654817bceada97a857ef8d68ab8992753d23ed8e8ccd5fc8fa31"
dependencies = [
"futures-channel",
"futures-util",
]
[[package]]
name = "async-std"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8056f1455169ab86dd47b47391e4ab0cbd25410a70e9fe675544f49bafaf952"
dependencies = [
"async-attributes",
"async-channel",
"async-global-executor",
"async-io",
"async-lock",
"crossbeam-utils",
"futures-channel",
"futures-core",
"futures-io",
"futures-lite",
"gloo-timers",
"kv-log-macro",
"log",
"memchr",
"num_cpus",
"once_cell",
"pin-project-lite",
"pin-utils",
"slab",
"wasm-bindgen-futures",
]
[[package]]
name = "async-task"
version = "4.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30696a84d817107fc028e049980e09d5e140e8da8f1caeb17e8e950658a3cea9"
[[package]]
name = "atomic-waker"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a"
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "blocking"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "046e47d4b2d391b1f6f8b407b1deb8dee56c1852ccd868becf2710f601b5f427"
dependencies = [
"async-channel",
"async-task",
"atomic-waker",
"fastrand",
"futures-lite",
"once_cell",
]
[[package]]
name = "bumpalo"
version = "3.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899"
[[package]]
name = "cache-padded"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c"
[[package]]
name = "cc"
version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
dependencies = [
"libc",
"num-integer",
"num-traits",
"time 0.1.44",
"winapi",
]
[[package]]
name = "concurrent-queue"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3"
dependencies = [
"cache-padded",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6"
dependencies = [
"cfg-if",
"lazy_static",
]
[[package]]
name = "ctor"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "event-listener"
version = "2.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71"
[[package]]
name = "fastrand"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf"
dependencies = [
"instant",
]
[[package]]
name = "futures"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
[[package]]
name = "futures-executor"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b"
[[package]]
name = "futures-lite"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48"
dependencies = [
"fastrand",
"futures-core",
"futures-io",
"memchr",
"parking",
"pin-project-lite",
"waker-fn",
]
[[package]]
name = "futures-macro"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures-sink"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868"
[[package]]
name = "futures-task"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a"
[[package]]
name = "futures-util"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
name = "gloo-timers"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d12a7f4e95cfe710f1d624fb1210b7d961a5fb05c4fd942f4feab06e61f590e"
dependencies = [
"futures-channel",
"futures-core",
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
[[package]]
name = "itoa"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
[[package]]
name = "js-sys"
version = "0.3.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "kv-log-macro"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f"
dependencies = [
"log",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c"
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
"value-bag",
]
[[package]]
name = "memchr"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "num-integer"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "num_threads"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
dependencies = [
"libc",
]
[[package]]
name = "once_cell"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
[[package]]
name = "parking"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72"
[[package]]
name = "pin-project-lite"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "polling"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259"
dependencies = [
"cfg-if",
"libc",
"log",
"wepoll-ffi",
"winapi",
]
[[package]]
name = "proc-macro2"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
dependencies = [
"proc-macro2",
]
[[package]]
name = "ryu"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
[[package]]
name = "serde"
version = "1.0.136"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.136"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "simplelog"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48dfff04aade74dd495b007c831cd6f4e0cee19c344dd9dc0884c0289b70a786"
dependencies = [
"log",
"termcolor",
"time 0.3.11",
]
[[package]]
name = "slab"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5"
[[package]]
name = "socket2"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "syn"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "termcolor"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
dependencies = [
"winapi-util",
]
[[package]]
name = "time"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
dependencies = [
"libc",
"wasi",
"winapi",
]
[[package]]
name = "time"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217"
dependencies = [
"itoa",
"libc",
"num_threads",
"time-macros",
]
[[package]]
name = "time-macros"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792"
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "value-bag"
version = "1.0.0-alpha.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79923f7731dc61ebfba3633098bf3ac533bbd35ccd8c57e7088d9a5eebe0263f"
dependencies = [
"ctor",
"version_check",
]
[[package]]
name = "velnet_server"
version = "0.2.0"
dependencies = [
"async-notify",
"async-std",
"chrono",
"futures",
"log",
"serde",
"serde_json",
"simplelog",
]
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "waker-fn"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasm-bindgen"
version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca"
dependencies = [
"bumpalo",
"lazy_static",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395"
dependencies = [
"cfg-if",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2"
[[package]]
name = "web-sys"
version = "0.3.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "wepoll-ffi"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb"
dependencies = [
"cc",
]
[[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-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

16
server/Cargo.toml Normal file
View File

@ -0,0 +1,16 @@
[package]
name = "velnet_server"
version = "0.2.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
chrono = "*"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
async-std = { version = "1.6", features = ["attributes"]}
futures = "*"
async-notify = "*"
simplelog = "^0.12.0"
log = "*"

32
server/Dockerfile Normal file
View File

@ -0,0 +1,32 @@
FROM rust:1.64 as build
# 1. Create a new empty shell project
RUN USER=root cargo new --bin velnet_server
WORKDIR /velnet_server
# 2. Copy our manifests
COPY ./Cargo.lock ./Cargo.lock
COPY ./Cargo.toml ./Cargo.toml
# 3. Build only the dependencies to cache them
RUN cargo build --release && rm src/*.rs
# 4. Now that the dependency is built, copy your source code
COPY ./src ./src
# 5. Build for release.
RUN rm ./target/release/deps/velnet_server*
RUN cargo build --release
# our final base
FROM debian:buster-slim
# copy the build artifact from the build stage
COPY --from=build /velnet_server/target/release/velnet_server .
COPY config.json .
EXPOSE 5000/tcp
EXPOSE 5000/udp
# run
CMD ["./velnet_server"]

6
server/config.json Normal file
View File

@ -0,0 +1,6 @@
{
"port": 5000,
"tcp_timeout": 30,
"log_file": "logs/server.log"
}

993
server/src/main.rs Normal file
View File

@ -0,0 +1,993 @@
use async_std::prelude::*;
use async_std::future;
use async_std::net::TcpListener;
use async_std::net::TcpStream;
use async_std::net::UdpSocket;
use async_notify::Notify;
use futures::stream::StreamExt;
use futures::join;
use futures::select;
use futures::pin_mut;
use futures::future::FutureExt;
use async_std::net::{IpAddr, SocketAddr};
use std::collections::HashMap;
use std::rc::{Rc};
use std::cell::{RefCell};
use std::fs;
use chrono::Local;
use std::time::Duration;
use serde::{Serialize, Deserialize};
use std::error::Error;
use std::fs::OpenOptions;
use std::io::BufReader;
use std::ptr;
use simplelog::*;
enum ToClientTCPMessageType {
LoggedIn = 0,
RoomList = 1,
PlayerJoined = 2,
DataMessage = 3,
MasterMessage = 4,
YouJoined = 5,
PlayerLeft = 6,
YouLeft = 7,
RoomData = 8,
}
enum FromClientTCPMessageType {
LogIn = 0,
GetRooms = 1,
JoinRoom = 2,
SendMessageOthersUnbuffered = 3,
SendMessageAllUnbuffered = 4,
SendMessageGroupUnbuffered = 5,
CreateGroup = 6,
SendMessageOthersBuffered = 7,
SendMessageAllBuffered = 8,
GetRoomData = 9,
}
enum ToClientUDPMessageType {
Connected = 0,
DataMessage = ToClientTCPMessageType::DataMessage as isize,
}
enum FromClientUDPMessageType {
Connect = 0,
SendMessageOthersUnbuffered = FromClientTCPMessageType::SendMessageOthersUnbuffered as isize,
SendMessageAllUnbuffered = FromClientTCPMessageType::SendMessageAllUnbuffered as isize,
SendMessageGroupUnbuffered = FromClientTCPMessageType::SendMessageGroupUnbuffered as isize,
}
struct Client {
logged_in: bool,
id: u32,
username: String,
room_name: String,
application: String,
groups: HashMap<String, Vec<Rc<RefCell<Client>>>>,
ip: IpAddr,
udp_port: u16,
message_queue: Vec<u8>,
message_queue_udp: Vec<Vec<u8>>,
notify: Rc<Notify>,
notify_udp: Rc<Notify>,
is_master: bool,
}
struct Room {
name: String,
clients: HashMap<u32, Rc<RefCell<Client>>>,
master_client: Rc<RefCell<Client>>,
}
#[derive(Serialize, Deserialize)]
struct VelNetConfig {
port: u16,
tcp_timeout: u64,
log_file: String,
}
#[async_std::main]
async fn main() {
let config = read_config_file().unwrap();
let f = OpenOptions::new()
.write(true)
.append(true)
.create(true)
.open(config.log_file)
.unwrap();
CombinedLogger::init(
vec![
TermLogger::new(LevelFilter::Info, Config::default(), TerminalMode::Mixed, ColorChoice::Auto),
WriteLogger::new(LevelFilter::Debug, Config::default(), f),
]
).unwrap();
log::info!("----------------------");
log::info!("VelNet Server Starting");
// read the config file
let foo = fs::read_to_string("config.json").unwrap();
let config: VelNetConfig = serde_json::from_str(&foo).unwrap();
log::info!("Running on port: {}", config.port);
let tcp_listener = TcpListener::bind(format!("0.0.0.0:{}", config.port)).await.unwrap();
let udp_socket = Rc::new(RefCell::new(UdpSocket::bind(format!("0.0.0.0:{}", config.port)).await.unwrap()));
let clients: Rc<RefCell<HashMap<u32, Rc<RefCell<Client>>>>> = Rc::new(RefCell::new(HashMap::new()));
let rooms: Rc<RefCell<HashMap<String, Rc<RefCell<Room>>>>> = Rc::new(RefCell::new(HashMap::new()));
let last_client_id = Rc::new(RefCell::new(0));
let tcp_future = tcp_listener
.incoming()
.for_each_concurrent(None, |tcpstream| process_client(tcpstream.unwrap(), udp_socket.clone(), clients.clone(), rooms.clone(), last_client_id.clone(), &config));
let udp_future = process_udp(udp_socket.clone(), clients.clone(), rooms.clone());
join!(tcp_future,udp_future);
}
fn read_config_file() -> Result<VelNetConfig, Box<dyn Error>> {
// Open the file in read-only mode with buffer.
let file = fs::File::open("config.json")?;
let reader = BufReader::new(file);
let config = serde_json::from_reader(reader)?;
Ok(config)
}
async fn process_client(socket: TcpStream, udp_socket: Rc<RefCell<UdpSocket>>, clients: Rc<RefCell<HashMap<u32, Rc<RefCell<Client>>>>>, rooms: Rc<RefCell<HashMap<String, Rc<RefCell<Room>>>>>, last_client_id: Rc<RefCell<u32>>, config: &VelNetConfig) {
log::info!("Started TCP");
match socket.set_nodelay(true) {
Ok(_) => {}
Err(_) => {
log::error!("Could not set no delay");
return;
}
}
//socket.set_read_timeout(Some(time::Duration::new(config.tcp_timeout,0))).unwrap();
//socket.set_write_timeout(Some(time::Duration::new(config.tcp_timeout,0))).unwrap();
let my_id;
{
let mut reference = last_client_id.borrow_mut();
*reference = *reference + 1;
my_id = *reference;
}
let client_notify = Rc::new(Notify::new());
let client_notify_udp = Rc::new(Notify::new());
let ip;
match socket.peer_addr() {
Ok(p) => ip = p.ip(),
Err(_) => { return; }
}
let client = Rc::new(RefCell::new(Client {
id: my_id,
username: String::from(""),
logged_in: false,
room_name: String::from(""),
application: String::from(""),
groups: HashMap::new(),
ip: ip,
udp_port: 0 as u16,
message_queue: vec![],
message_queue_udp: vec![],
notify: client_notify.clone(),
notify_udp: client_notify_udp.clone(),
is_master: false,
}));
log::info!("Spawned client handler = {}", my_id);
{
let temp_client = client.clone();
let mut clients_temp = clients.borrow_mut();
clients_temp.insert(my_id, temp_client);
}
let read_async = client_read(client.clone(), socket.clone(), clients.clone(), rooms.clone(), config.tcp_timeout * 1000).fuse();
let write_async = client_write(client.clone(), socket, client_notify.clone(), config.tcp_timeout * 1000).fuse();
let write_async_udp = client_write_udp(client.clone(), udp_socket.clone(), client_notify_udp.clone()).fuse();
pin_mut!(read_async,write_async,write_async_udp); //not sure why this is necessary, since select
select! { //
() = read_async => log::debug!("read async ended"),
() = write_async => log::debug!("write async ended"),
() = write_async_udp => log::debug!("write async udp ended")
}
{
client_leave_room(client.clone(), false, rooms.clone());
let mut clients_temp = clients.borrow_mut();
clients_temp.remove(&client.borrow().id);
}
{
log::info!("Client {} left", client.borrow().id);
}
}
async fn read_timeout(mut socket: &TcpStream, buf: &mut [u8], duration: u64) -> Result<usize, Box<dyn Error>> {
//this is a read exact function. The buffer passed should be the exact size wanted
let num_to_read = buf.len();
let mut num_read = 0;
while num_read < num_to_read {
match future::timeout(Duration::from_millis(duration), socket.read(&mut buf[num_read..])).await {
Ok(r) => {
match r {
Ok(n) if n == 0 => {
return Err(format!("{}", "no bytes read"))?;
}
Ok(n) => {
num_read += n;
}
Err(e) => { return Err(format!("{}", e.to_string()))?; }
}
}
Err(e) => { return Err(format!("{}", e.to_string()))?; }
}
}
return Ok(num_read);
}
async fn client_read(client: Rc<RefCell<Client>>, mut socket: TcpStream, clients: Rc<RefCell<HashMap<u32, Rc<RefCell<Client>>>>>, rooms: Rc<RefCell<HashMap<String, Rc<RefCell<Room>>>>>, duration: u64) {
let mut buf = [0; 1];
loop {
match read_timeout(&mut socket, &mut buf, duration).await {
Ok(_) => {
let t = buf[0];
if t == FromClientTCPMessageType::LogIn as u8 { //[0:u8][username.length():u8][username:shortstring][password.length():u8][password:shortstring]
match read_login_message(socket.clone(), client.clone(), duration).await {
Ok(_) => (),
Err(_) => {
log::error!("failed to read from socket");
return;
}
};
} else if t == FromClientTCPMessageType::GetRooms as u8 {//[1:u8]
match read_rooms_message(socket.clone(), client.clone(), rooms.clone()).await {
Ok(_) => (),
Err(_) => {
log::error!("failed to read from socket");
return;
}
};
} else if t == FromClientTCPMessageType::GetRoomData as u8 {
match read_roomdata_message(socket.clone(), client.clone(), rooms.clone(), duration).await {
Ok(_) => (),
Err(_) => {
log::error!("failed to read from socket");
return;
}
};
} else if t == FromClientTCPMessageType::JoinRoom as u8 {//[2:u8][roomname.length():u8][roomname:shortstring]
match read_join_message(socket.clone(), client.clone(), rooms.clone(), duration).await {
Ok(_) => (),
Err(_) => {
log::error!("failed to read from socket");
return;
}
};
} else if t == FromClientTCPMessageType::SendMessageOthersUnbuffered as u8 ||
t == FromClientTCPMessageType::SendMessageAllUnbuffered as u8 ||
t == FromClientTCPMessageType::SendMessageGroupUnbuffered as u8 ||
t == FromClientTCPMessageType::SendMessageOthersBuffered as u8 ||
t == FromClientTCPMessageType::SendMessageAllBuffered as u8 { //others,all,group[t:u8][message.length():i32][message:u8array]
match read_send_message(socket.clone(), client.clone(), rooms.clone(), t, duration).await {
Ok(_) => (),
Err(_) => {
log::error!("failed to read from socket");
return;
}
};
} else if t == FromClientTCPMessageType::CreateGroup as u8 { //[t:u8][list.lengthbytes:i32][clients:i32array]
match read_group_message(socket.clone(), client.clone(), clients.clone(), duration).await {
Ok(_) => (),
Err(_) => {
log::error!("failed to read from socket");
return;
}
};
} else {
//die...not correct protocol
log::error!("Incorrect protocol, killing");
return;
}
}
Err(e) => {
log::error!("failed to read from socket; err = {:?}", e);
//remove the client
return;
}
};
}
}
async fn write_timeout(mut socket: &TcpStream, buf: &[u8], duration: u64) -> Result<usize, Box<dyn Error>> {
return match future::timeout(Duration::from_millis(duration), socket.write(buf)).await {
Ok(r) => {
match r {
Ok(n) => {
Ok(n)
}
Err(e) => { Err(format!("{}", e.to_string()))? }
}
}
Err(e) => { Err(format!("{}", e.to_string()))? }
};
}
async fn client_write(client: Rc<RefCell<Client>>, mut socket: TcpStream, notify: Rc<Notify>, duration: u64) {
//wait on messages in my queue
loop {
notify.notified().await; //there is something to write
let mut to_write = vec![];
{
let client_ref = client.borrow();
to_write.extend_from_slice(&client_ref.message_queue); //must do this so that the borrow ends
}
{
let mut client_ref = client.borrow_mut();
client_ref.message_queue.clear();
}
match write_timeout(&mut socket, &to_write, duration).await {
Ok(_) => (),
Err(_) => {
log::error!("failed to write to the tcp socket");
return;
}
}
}
}
async fn client_write_udp(client: Rc<RefCell<Client>>, socket: Rc<RefCell<UdpSocket>>, notify: Rc<Notify>) {
loop {
notify.notified().await; //there is something to write
let ip;
let port;
let mut messages = vec![];
{
let mut client_ref = client.borrow_mut();
ip = client_ref.ip;
port = client_ref.udp_port;
for msg in client_ref.message_queue_udp.iter() {
messages.push(msg.clone());
}
client_ref.message_queue_udp.clear();
}
for msg in messages.iter() {
let socket = socket.borrow();
match socket.send_to(&msg, SocketAddr::new(ip, port)).await {
Ok(_) => (),
Err(_) => {
log::error!("failed to write to the udp socket");
return;
}
}
}
}
}
async fn process_udp(socket: Rc<RefCell<UdpSocket>>, clients: Rc<RefCell<HashMap<u32, Rc<RefCell<Client>>>>>, rooms: Rc<RefCell<HashMap<String, Rc<RefCell<Room>>>>>) {
let mut buf = [0u8; 1024];
loop {
let socket = socket.borrow();
let res = socket.recv_from(&mut buf).await;
match res {
Ok(_) => (),
Err(_) => continue
}
let (packet_size, addr) = res.unwrap();
let t = buf[0];
if packet_size >= 5 {
//get the client id, which has to be sent with every udp message, because you don't know where udp messages are coming from
let client_id_bytes = [buf[1], buf[2], buf[3], buf[4]];
let client_id = u32::from_be_bytes(client_id_bytes);
if t == FromClientUDPMessageType::Connect as u8 { //1 byte, 0. Nothing else. This is just to establish the udp port, Echos back the same thing sent
//connect message, respond back
let clients = clients.borrow();
if clients.contains_key(&client_id) {
let mut client = clients.get(&client_id).unwrap().borrow_mut();
client.udp_port = addr.port(); //set the udp port to send data to
client.message_queue_udp.push(vec![0]);
client.notify_udp.notify();
}
} else if t == FromClientUDPMessageType::SendMessageOthersUnbuffered as u8 { //[3:u8][from:i32][contents:u8array] note that it must fit into the packet of 1024 bytes
let clients = clients.borrow();
if clients.contains_key(&client_id) {
let client = clients.get(&client_id).unwrap().borrow();
let rooms_ref = rooms.borrow();
if client.room_name != "" {
let room = rooms_ref[&client.room_name].borrow();
buf[0] = ToClientUDPMessageType::DataMessage as u8; //technically unecessary, unless we change this number
for (_k, v) in room.clients.iter() {
if *_k != client_id {
let mut msg = vec![];
let mut v_ref = v.borrow_mut();
msg.extend_from_slice(&buf[0..packet_size]);
v_ref.message_queue_udp.push(msg);
v_ref.notify_udp.notify();
}
}
}
}
} else if t == FromClientUDPMessageType::SendMessageAllUnbuffered as u8 { //see above
let clients = clients.borrow();
if clients.contains_key(&client_id) {
let mut client = clients.get(&client_id).unwrap().borrow_mut();
let rooms_ref = rooms.borrow();
if client.room_name != "" {
let room = rooms_ref[&client.room_name].borrow();
buf[0] = ToClientUDPMessageType::DataMessage as u8; //technically unecessary, unless we change this number
for (_k, v) in room.clients.iter() {
if *_k != client_id {
let mut msg = vec![];
let mut v_ref = v.borrow_mut();
msg.extend_from_slice(&buf[0..packet_size]);
v_ref.message_queue_udp.push(msg);
v_ref.notify_udp.notify();
} else {
let mut msg = vec![];
msg.extend_from_slice(&buf[0..packet_size]);
client.message_queue_udp.push(msg);
client.notify_udp.notify();
}
}
}
}
} else if t == FromClientUDPMessageType::SendMessageGroupUnbuffered as u8 { //[5:byte][from:i32][group.length():u8][message:u8array]
//this one is a little different, because we don't send the group in the message, so we have to formulate another message (like a 3 message)
//send a message to a group
//read the group name
let group_name_size = buf[5];
let message_vec = buf[6..packet_size].to_vec();
let (group_name_bytes, message_bytes) = message_vec.split_at(group_name_size as usize);
let res = String::from_utf8(group_name_bytes.to_vec());
match res {
Ok(_) => {}
Err(_) => {
log::error!("Could not convert group name");
return;
}
}
let group_name = res.unwrap();
let clients = clients.borrow();
let mut message_to_send = vec![];
message_to_send.push(ToClientUDPMessageType::DataMessage as u8);
message_to_send.extend([buf[1], buf[2], buf[3], buf[4]]);
message_to_send.extend(message_bytes);
let mut send_to_client = false;
if clients.contains_key(&client_id) {
{
{
let client = clients.get(&client_id).unwrap().borrow();
if client.groups.contains_key(&group_name) {
let clients = client.groups.get(&group_name).unwrap();
//we need to form a new message without the group name
for v in clients.iter() {
let mut skip_client = false;
{
let v_ref = v.borrow();
if v_ref.id == client.id {
skip_client = true;
send_to_client = true;
}
}
if !skip_client {
let mut v_ref = v.borrow_mut();
v_ref.message_queue_udp.push(message_to_send.clone());
v_ref.notify_udp.notify();
}
}
}
}
if send_to_client {
let mut client = clients.get(&client_id).unwrap().borrow_mut();
client.message_queue_udp.push(message_to_send.clone());
client.notify_udp.notify();
}
}
}
}
}
}
}
//this is in response to someone asking to login (this is where usernames and passwords would be processed, in theory)
async fn read_login_message(mut stream: TcpStream, client: Rc<RefCell<Client>>, duration: u64) -> Result<(), Box<dyn Error>> {
//byte,shortstring,byte,shortstring
let username = read_short_string(&mut stream, duration).await?;
let application = read_short_string(&mut stream, duration).await?;
log::info!("{}: Got application {} and userid {}", Local::now().format("%Y-%m-%d %H:%M:%S"), application, username);
{
let mut client = client.borrow_mut();
client.username = username;
client.application = application;
client.logged_in = true;
}
{
let mut client = client.borrow_mut();
let mut write_buf = vec![];
write_buf.push(ToClientTCPMessageType::LoggedIn as u8);
write_buf.extend_from_slice(&(client.id).to_be_bytes()); //send the client the id
client.message_queue.extend(write_buf);
client.notify.notify();
}
return Ok(());
}
async fn read_rooms_message(mut _stream: TcpStream, client: Rc<RefCell<Client>>, rooms: Rc<RefCell<HashMap<String, Rc<RefCell<Room>>>>>) -> Result<(), Box<dyn Error>> {
let mut write_buf = vec![];
write_buf.push(ToClientTCPMessageType::RoomList as u8);
//first we need to get the room names
let rooms = rooms.borrow();
let mut client = client.borrow_mut();
let mut rooms_vec = vec![];
for (k, v) in rooms.iter() {
if !k.starts_with(&client.application.to_string()) {
continue;
}
let mut iter = k.chars();
iter.by_ref().nth(client.application.len());
let application_stripped_room = iter.as_str();
let room_string = format!("{}:{}", application_stripped_room, v.borrow().clients.len());
rooms_vec.push(room_string);
}
let rooms_message = rooms_vec.join(",");
let message_bytes = rooms_message.as_bytes();
let message_len = message_bytes.len() as u32;
write_buf.extend_from_slice(&(message_len).to_be_bytes());
write_buf.extend_from_slice(message_bytes);
client.message_queue.extend_from_slice(&write_buf);
client.notify.notify();
return Ok(());
}
async fn read_join_message(mut stream: TcpStream, client: Rc<RefCell<Client>>, rooms: Rc<RefCell<HashMap<String, Rc<RefCell<Room>>>>>, duration: u64) -> Result<(), Box<dyn Error>> {
//byte,shortstring
let short_room_name = read_short_string(&mut stream, duration).await?;
let extended_room_name;
let mut leave_room = false;
{
let client_ref = client.borrow();
extended_room_name = format!("{}_{}", client_ref.application, short_room_name);
if client_ref.room_name != "" {
leave_room = true;
}
}
if leave_room {
//todo
client_leave_room(client.clone(), true, rooms.clone());
}
let mut client_ref = client.borrow_mut();
let mut rooms_ref = rooms.borrow_mut();
if short_room_name.trim() == "" || short_room_name == "-1" {
return Ok(());
}
if !rooms_ref.contains_key(&extended_room_name) { //new room, must create it
let map: HashMap<u32, Rc<RefCell<Client>>> = HashMap::new();
let r = Rc::new(RefCell::new(Room {
name: extended_room_name.to_string(),
clients: map,
master_client: client.clone(), //client is the master, since they joined first
}));
client_ref.is_master = true;
rooms_ref.insert(String::from(&extended_room_name), r);
log::info!("{}: {}: New room {} created", Local::now().format("%Y-%m-%d %H:%M:%S"), client_ref.application, &extended_room_name);
} else {
client_ref.is_master = false;
}
//the room is guaranteed to exist now, so this call can't fail
let mut room_to_join = rooms_ref[&extended_room_name].borrow_mut();
room_to_join.clients.insert(client_ref.id, client.clone());
log::info!("{}: {}: Client {} joined {}", Local::now().format("%Y-%m-%d %H:%M:%S"), client_ref.application, client_ref.id, &extended_room_name);
client_ref.room_name = extended_room_name; //we create an option and assign it back to the room
//send a join message to everyone in the room (except the client)
for (_k, v) in room_to_join.clients.iter() {
if *_k != client_ref.id {
send_client_join_message(v, client_ref.id, &short_room_name);
}
}
//send a join message to the client that has all of the ids in the room
let mut ids_in_room = vec![];
for (_k, _v) in room_to_join.clients.iter() {
ids_in_room.push(*_k);
}
send_you_joined_message(&mut *client_ref, ids_in_room, &short_room_name);
if client_ref.is_master {
let temp = client_ref.id;
send_client_master_message(&mut *client_ref, temp);
} else {
send_client_master_message(&mut *client_ref, room_to_join.master_client.borrow().id);
}
return Ok(());
}
async fn read_roomdata_message(mut stream: TcpStream, client: Rc<RefCell<Client>>, rooms: Rc<RefCell<HashMap<String, Rc<RefCell<Room>>>>>, duration: u64) -> Result<(), Box<dyn Error>> {
//type, room_name
//will respond with type, numclients u32, id1 u32, name_len u8, name_bytes ...
//read the room name and append the client application
let short_room_name = read_short_string(&mut stream, duration).await?;
let mut client_ref = client.borrow_mut();
let room_name = format!("{}_{}", client_ref.application, short_room_name);
//we need to access the rooms list
let rooms_ref = rooms.borrow();
if rooms_ref.contains_key(&room_name) {
let room = rooms_ref.get(&room_name).unwrap();
//form and send the message
let mut write_buf = vec![];
write_buf.push(ToClientTCPMessageType::RoomData as u8);
let roomname_bytes = short_room_name.as_bytes();
write_buf.push(roomname_bytes.len() as u8);
write_buf.extend_from_slice(&roomname_bytes);
let clients = &room.borrow().clients;
write_buf.extend_from_slice(&(clients.len() as u32).to_be_bytes());
for (_k, c) in clients.iter() {
write_buf.extend_from_slice(&(_k).to_be_bytes());
if *_k != client_ref.id {
let c_ref = c.borrow();
let username_bytes = c_ref.username.as_bytes();
write_buf.push(username_bytes.len() as u8);
write_buf.extend_from_slice(&username_bytes);
} else {
let username_bytes = client_ref.username.as_bytes();
write_buf.push(username_bytes.len() as u8);
write_buf.extend_from_slice(&username_bytes);
}
}
client_ref.message_queue.extend_from_slice(&write_buf);
client_ref.notify.notify();
}
return Ok(());
}
async fn read_send_message(mut stream: TcpStream, client: Rc<RefCell<Client>>, rooms: Rc<RefCell<HashMap<String, Rc<RefCell<Room>>>>>, message_type: u8, duration: u64) -> Result<(), Box<dyn Error>> {
//4 byte length, array
//this is a message for everyone in the room (maybe)
let to_send = read_vec(&mut stream, duration).await?;
if message_type == FromClientTCPMessageType::SendMessageOthersUnbuffered as u8 {
send_room_message(client, &to_send, rooms.clone(), false, false);
} else if message_type == FromClientTCPMessageType::SendMessageAllUnbuffered as u8 {
send_room_message(client, &to_send, rooms.clone(), true, false);
} else if message_type == FromClientTCPMessageType::SendMessageOthersBuffered as u8 { //ordered
send_room_message(client, &to_send, rooms.clone(), false, true);
} else if message_type == FromClientTCPMessageType::SendMessageAllBuffered as u8 { //ordered
send_room_message(client, &to_send, rooms.clone(), true, true);
} else if message_type == FromClientTCPMessageType::SendMessageGroupUnbuffered as u8 {
let group = read_short_string(&mut stream, duration).await?;
send_group_message(client, &to_send, &group);
}
return Ok(());
}
async fn read_group_message(mut stream: TcpStream, client: Rc<RefCell<Client>>, clients: Rc<RefCell<HashMap<u32, Rc<RefCell<Client>>>>>, duration: u64) -> Result<(), Box<dyn Error>> {
let group = read_short_string(&mut stream, duration).await?;
let id_bytes = read_vec(&mut stream, duration).await?;
let num = id_bytes.len();
let mut client_ref = client.borrow_mut();
let clients_ref = clients.borrow();
let mut group_clients = vec![];
let mut i = 0;
loop {
if i >= num {
break;
}
let mut slice = [0u8; 4];
slice[0] = id_bytes[i];
slice[1] = id_bytes[i + 1];
slice[2] = id_bytes[i + 2];
slice[3] = id_bytes[i + 3]; //probably a better way to do this
let id = u32::from_be_bytes(slice);
match clients_ref.get(&id) {
Some(client) => { group_clients.push(client.clone()); }
None => () //not there, so don't add it
}
i = i + 4;
}
//delete the group if it exists
if client_ref.groups.contains_key(&group) {
client_ref.groups.remove(&group); //ensures the client references go away
}
client_ref.groups.insert(group.clone(), group_clients);
return Ok(());
}
fn client_leave_room(client: Rc<RefCell<Client>>, send_to_client: bool, rooms: Rc<RefCell<HashMap<String, Rc<RefCell<Room>>>>>) {
//first remove the client from the room they are in
{
let mut client_ref = client.borrow_mut();
let roomname = String::from(client_ref.room_name.clone());
if roomname == "" { //client not in room, leave
return;
}
let mut new_master_id = 0;
{
log::info!("{}: {}: Client {} in room, leaving", Local::now().format("%Y-%m-%d %H:%M:%S"), client_ref.application, client_ref.id);
}
let mut destroy_room = false;
{
let rooms_ref = rooms.borrow_mut();
let mut room_ref = rooms_ref.get(&roomname).unwrap().borrow_mut();
for (_k, v) in room_ref.clients.iter() {
if *_k != client_ref.id {
send_client_left_message(v, client_ref.id, &roomname);
}
}
if send_to_client && client_ref.room_name != "" {
send_you_left_message(&mut *client_ref, &roomname);
}
room_ref.clients.remove(&client_ref.id); //remove the client from that list in the room
if room_ref.clients.len() == 0 {
destroy_room = true;
}
}
//if the room is empty, destroy it as well
let mut rooms_ref = rooms.borrow_mut();
if destroy_room {
rooms_ref.remove(&roomname);
log::info!("{}: {}: Destroyed room {}", Local::now().format("%Y-%m-%d %H:%M:%S"), client_ref.application, &roomname)
} else if client_ref.is_master { //we need to change the master!
let mut room_ref = rooms_ref.get(&roomname).unwrap().borrow_mut();
for (_k, v) in room_ref.clients.iter() {
if *_k != client_ref.id {
new_master_id = v.borrow().id;
break;
}
}
log::info!("{}: {}: Changing master to {}", Local::now().format("%Y-%m-%d %H:%M:%S"), client_ref.application, new_master_id);
for (_k, v) in room_ref.clients.iter() {
let mut c = v.borrow_mut();
send_client_master_message(&mut *c, new_master_id);
}
room_ref.master_client = room_ref.clients.get(&new_master_id).unwrap().clone();
room_ref.master_client.borrow_mut().is_master = true;
client_ref.is_master = false;
}
}
let mut client_ref = client.borrow_mut();
client_ref.room_name = String::from("");
}
fn send_you_left_message(client_ref: &mut Client, room: &str) {
let mut write_buf = vec![];
write_buf.push(ToClientTCPMessageType::YouLeft as u8);
write_buf.push(room.as_bytes().len() as u8);
write_buf.extend_from_slice(room.as_bytes());
client_ref.message_queue.extend_from_slice(&write_buf);
client_ref.notify.notify();
}
fn send_client_left_message(to: &Rc<RefCell<Client>>, from: u32, room: &str) {
let mut write_buf = vec![];
write_buf.push(ToClientTCPMessageType::PlayerLeft as u8);
write_buf.extend_from_slice(&(from).to_be_bytes()); //send everyone that the client id left the room
write_buf.push(room.as_bytes().len() as u8);
write_buf.extend_from_slice(room.as_bytes());
let mut client_ref = to.borrow_mut();
client_ref.message_queue.extend_from_slice(&write_buf);
client_ref.notify.notify();
}
fn send_client_join_message(to: &Rc<RefCell<Client>>, from: u32, room: &str) {
//2u8, person_id_u32, room_name_len_u8, room_name_bytes
let mut write_buf = vec![];
write_buf.push(ToClientTCPMessageType::PlayerJoined as u8);
write_buf.extend_from_slice(&(from).to_be_bytes()); //send everyone that the client id joined the room
write_buf.push(room.as_bytes().len() as u8);
write_buf.extend_from_slice(room.as_bytes());
let mut client_ref = to.borrow_mut();
client_ref.message_queue.extend_from_slice(&write_buf);
client_ref.notify.notify();
}
fn send_you_joined_message(client_ref: &mut Client, in_room: Vec<u32>, room: &str) {
//you_joined_u8, ids_len_u32, id_list_array_u32, room_name_len_u8, room_name_bytes
let mut write_buf = vec![];
write_buf.push(ToClientTCPMessageType::YouJoined as u8);
write_buf.extend_from_slice(&(in_room.len() as u32).to_be_bytes());
for id in in_room {
write_buf.extend_from_slice(&(id).to_be_bytes());
}
write_buf.push(room.as_bytes().len() as u8);
write_buf.extend_from_slice(room.as_bytes());
client_ref.message_queue.extend_from_slice(&write_buf);
client_ref.notify.notify();
}
fn send_client_master_message(client_ref: &mut Client, master_id: u32) {
//2u8, person_id_u32, room_name_len_u8, room_name_bytes
let mut write_buf = vec![];
write_buf.push(ToClientTCPMessageType::MasterMessage as u8);
write_buf.extend_from_slice(&(master_id).to_be_bytes()); //send everyone that the client id joined the room
client_ref.message_queue.extend_from_slice(&write_buf);
client_ref.notify.notify();
}
fn send_room_message(sender: Rc<RefCell<Client>>, message: &Vec<u8>, rooms: Rc<RefCell<HashMap<String, Rc<RefCell<Room>>>>>, include_sender: bool, _ordered: bool) {
//this message is 3u8, sender_id_u32, message_len_u32, message_bytes
let mut write_buf = vec![];
let mut sender_ref = sender.borrow_mut();
write_buf.push(ToClientTCPMessageType::DataMessage as u8);
write_buf.extend_from_slice(&sender_ref.id.to_be_bytes());
write_buf.extend_from_slice(&(message.len() as u32).to_be_bytes());
write_buf.extend_from_slice(message);
if sender_ref.room_name == "" {
return;
}
if include_sender {
sender_ref.message_queue.extend_from_slice(&write_buf);
sender_ref.notify.notify();
}
let rooms_ref = rooms.borrow();
let room_ref = rooms_ref[&sender_ref.room_name].borrow();
for (_k, v) in room_ref.clients.iter() {
if *_k != sender_ref.id {
let mut temp_mut = v.borrow_mut();
temp_mut.message_queue.extend_from_slice(&write_buf);
temp_mut.notify.notify();
}
}
}
fn send_group_message(sender: Rc<RefCell<Client>>, message: &Vec<u8>, group: &String) {
let mut write_buf = vec![];
let mut sender_ref = sender.borrow_mut();
write_buf.push(ToClientTCPMessageType::DataMessage as u8);
write_buf.extend_from_slice(&sender_ref.id.to_be_bytes());
write_buf.extend_from_slice(&(message.len() as u32).to_be_bytes());
write_buf.extend_from_slice(message);
//get the list of client ids for this group
let mut send_to_client = false;
if sender_ref.groups.contains_key(group) {
let group = sender_ref.groups.get(group).unwrap();
for c in group {
if ptr::eq(sender.as_ref(), c.as_ref()) {
send_to_client = true;
} else {
let mut temp_mut = c.borrow_mut();
temp_mut.message_queue.extend_from_slice(&write_buf);
temp_mut.notify.notify();
}
}
}
if send_to_client {
sender_ref.message_queue.extend_from_slice(&write_buf);
sender_ref.notify.notify();
}
}
async fn read_u8(stream: &mut TcpStream, duration: u64) -> Result<u8, Box<dyn Error>> {
let mut buf = [0; 1];
read_timeout(stream, &mut buf, duration).await?;
return Ok(buf[0]);
}
async fn read_u32(stream: &mut TcpStream, duration: u64) -> Result<u32, Box<dyn Error>> {
let mut buf: [u8; 4] = [0; 4];
read_timeout(stream, &mut buf, duration).await?;
let size = u32::from_be_bytes(buf);
return Ok(size);
}
async fn _read_string(stream: &mut TcpStream, duration: u64) -> Result<String, Box<dyn Error>> {
let size = read_u32(stream, duration).await?;
let mut string_bytes = vec![0; size as usize];
read_timeout(stream, &mut string_bytes, duration).await?;
return Ok(String::from_utf8(string_bytes)?);
}
async fn read_short_string(stream: &mut TcpStream, duration: u64) -> Result<String, Box<dyn Error>> {
let size = read_u8(stream, duration).await?;
let mut string_bytes = vec![0; size as usize];
read_timeout(stream, &mut string_bytes, duration).await?;
return Ok(String::from_utf8(string_bytes)?);
}
async fn read_vec(stream: &mut TcpStream, duration: u64) -> Result<Vec<u8>, Box<dyn Error>> {
let message_size = read_u32(stream, duration).await?;
let mut message = vec![0u8; message_size as usize];
read_timeout(stream, &mut message, duration).await?;
return Ok(message);
}