Compare commits

..

25 Commits

Author SHA1 Message Date
kjjohnsen 8d32426962
Update README.md 2023-01-22 14:41:17 -05:00
Anton Franzluebbers 5f52900ded 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 2022-11-06 19:11:59 -05:00
Anton Franzluebbers 5200ba7723 docker support, move server to sibling folder 2022-09-27 19:12:37 -04:00
Anton Franzluebbers 7a615c4489
Update README.md 2022-07-10 15:53:07 -04:00
Anton Franzluebbers 4c44c3531b control panel works more now, added install script to add services and compile 2022-07-10 15:16:44 -04:00
Kyle Johnsen 04cb9e29d9 fixed master changing bug 2022-05-25 14:57:44 -04:00
Anton Franzluebbers 95dece7fed added checking if log files exist first, limit length of log file output in control panel 2022-03-26 16:12:09 -04:00
Anton Franzluebbers ec86134356 Merge branch 'asyncversion' of github.com:velaboratory/VelNetServerRust into asyncversion 2022-03-26 19:06:52 +00:00
Anton Franzluebbers 8c334ec751 minor control panel changes from server 2022-03-26 19:06:19 +00:00
Kyle Johnsen 2f894d7729 fixed potential tcp errors that may occur when packets are split 2022-03-20 16:29:28 -04:00
Kyle Johnsen e64ddafeb6 removed some potential failure paths, server should never crash 2022-03-20 15:36:37 -04:00
Kyle Johnsen 3caa73304f mistake of commiting without testing 2022-03-18 16:34:46 -04:00
Kyle Johnsen 85cc1d5a39 moved timeout to config 2022-03-18 16:33:35 -04:00
Kyle Johnsen 792054749f important change, added 5s timeout on all connections. Must send heartbeat 2022-03-18 08:59:41 -04:00
Kyle Johnsen aa792c736c accidentally sending messages twice 2022-03-16 00:30:59 -04:00
Kyle Johnsen 5fa0855b9b bug fix 2022-03-15 13:27:09 -04:00
Kyle Johnsen 62b5ab9303 Merge branch 'asyncversion' of github.com:velaboratory/VelNetServerRust into asyncversion 2022-03-14 15:07:48 -04:00
Kyle Johnsen 983308aabe untested hotfix 2022-03-14 15:07:24 -04:00
kjjohnsen 49d7628ad5
Update README.md 2022-03-13 10:28:35 -04:00
Kyle Johnsen 3a03ceb8ce fixed another unwrap bug 2022-03-12 21:54:52 -05:00
Kyle Johnsen b8f65ecf9d fixed crash condition 2022-03-07 13:08:19 -05:00
Kyle Johnsen 5f42d33040 added 'proper' error handling 2022-03-07 09:49:32 -05:00
Kyle Johnsen 84096c2d98 removed some debug messages 2022-03-07 02:30:47 -05:00
Kyle Johnsen 317d2a84c9 test scene fully working, should be drop in compatible 2022-03-07 02:24:16 -05:00
Kyle Johnsen 8336412fb3 async version 2022-03-07 02:09:04 -05:00
36 changed files with 2679 additions and 1242 deletions

7
.gitignore vendored
View File

@ -1,8 +1,11 @@
/target target/
.vscode .vscode
main main
main.exe main.exe
main.pdb main.pdb
.idea/ .idea/
restarts.log restarts.log
nohup.out nohup.out
server.log
control_panel.log
onefetch.out

173
Cargo.lock generated
View File

@ -1,173 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "VelNetServerRust"
version = "0.1.0"
dependencies = [
"chrono",
"serde",
"serde_json",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[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",
"winapi",
]
[[package]]
name = "itoa"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
[[package]]
name = "libc"
version = "0.2.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c"
[[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 = "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 = "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 = "time"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
dependencies = [
"libc",
"wasi",
"winapi",
]
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[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"

View File

@ -1,23 +1,56 @@
# 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](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.
The only game logic implemented by the server is that of a "master client", which is an easier way to negotiate a leader in a room that can perform room level operations.
The "group" functionality is used to specify specific clients to communicate with. Note, these client ids can bridge across rooms.
The server supports both TCP and UDP transports.
## Running ## Running
1. Get a linoox server ### Option 1: Pull from Docker Hub
```sh
docker run -p 5000:5000 -p 5000:5000/udp velaboratory/velnet
```
or
```sh
docker run -p 5050:5000 -p 5050:5000/udp --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 2. Clone this repo
3. Install rust: `sudo apt install cargo` 3. Edit `config.json` to an open port on your firewall
5. Build: `cargo build --release` 4. Modify the `user` field in `control-panel/config.json` to be your username.
6. Run: `sudo ./target/release/VelNetServerRust` 5. Install rust through using rustup: https://rustup.rs/
7. Or run in the background so that it doesn't quit when you leave ssh: `nohup sudo ./target/release/VelNetServerRust`. You'll have to install `nohup` with apt. 6. Install: `sudo ./install.sh`
7. Run server: `sudo systemctl start velnet`
8. Run control panel: `sudo systemctl start velnet-control-panel`
## Running with control panel server 9. Install tuptime: `cargo install tuptime`
9. Install onefetch: `cargo install onefetch`
You don't need to do both of these steps. The control panel runs the other server.
1. Get a linoox server
2. Clone this repo
3. Install rust: `sudo apt install cargo`
3. Switch to control panel: `cd control-panel`
5. Build: `cargo build --release`
6. `touch ../nohup.out ../restarts.log`
6. Run the web server in the background so that it doesn't quit when you leave ssh: `nohup sudo ./target/release/control-panel`. You'll have to install `nohup` with apt.

View File

@ -1,5 +0,0 @@
{
"port":5000,
"tcp_timeout":30,
"tcp_send_buffer":100000
}

View File

@ -1,7 +0,0 @@
/target
.vscode
main
main.exe
main.pdb
.idea/
onefetch.out

240
control-panel/Cargo.lock generated
View File

@ -19,6 +19,29 @@ dependencies = [
"tokio-util 0.7.0", "tokio-util 0.7.0",
] ]
[[package]]
name = "actix-files"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e04dcf7654254676d434b0285e2298d577ed4826f67f536e7a39bb0f64721164"
dependencies = [
"actix-http",
"actix-service",
"actix-utils",
"actix-web",
"askama_escape",
"bitflags",
"bytes",
"derive_more",
"futures-core",
"http-range",
"log",
"mime",
"mime_guess",
"percent-encoding",
"pin-project-lite",
]
[[package]] [[package]]
name = "actix-http" name = "actix-http"
version = "3.0.0" version = "3.0.0"
@ -62,8 +85,8 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "465a6172cf69b960917811022d8f29bc0b7fa1398bc4f78b3c466673db1213b6" checksum = "465a6172cf69b960917811022d8f29bc0b7fa1398bc4f78b3c466673db1213b6"
dependencies = [ dependencies = [
"quote", "quote 1.0.15",
"syn", "syn 1.0.86",
] ]
[[package]] [[package]]
@ -176,9 +199,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7525bedf54704abb1d469e88d7e7e9226df73778798a69cea5022d53b2ae91bc" checksum = "7525bedf54704abb1d469e88d7e7e9226df73778798a69cea5022d53b2ae91bc"
dependencies = [ dependencies = [
"actix-router", "actix-router",
"proc-macro2", "proc-macro2 1.0.36",
"quote", "quote 1.0.15",
"syn", "syn 1.0.86",
] ]
[[package]] [[package]]
@ -222,6 +245,12 @@ dependencies = [
"alloc-no-stdlib", "alloc-no-stdlib",
] ]
[[package]]
name = "askama_escape"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341"
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.1.0" version = "1.1.0"
@ -333,16 +362,6 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "control-panel"
version = "0.1.0"
dependencies = [
"actix-web",
"handlebars",
"serde",
"serde_json",
]
[[package]] [[package]]
name = "convert_case" name = "convert_case"
version = "0.4.0" version = "0.4.0"
@ -388,6 +407,17 @@ dependencies = [
"typenum", "typenum",
] ]
[[package]]
name = "default-env"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f753eb82d29277e79efc625e84aecacfd4851ee50e05a8573a4740239a77bfd3"
dependencies = [
"proc-macro2 0.4.30",
"quote 0.6.13",
"syn 0.15.44",
]
[[package]] [[package]]
name = "derive_more" name = "derive_more"
version = "0.99.17" version = "0.99.17"
@ -395,10 +425,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
dependencies = [ dependencies = [
"convert_case", "convert_case",
"proc-macro2", "proc-macro2 1.0.36",
"quote", "quote 1.0.15",
"rustc_version", "rustc_version",
"syn", "syn 1.0.86",
] ]
[[package]] [[package]]
@ -420,6 +450,12 @@ dependencies = [
"crypto-common", "crypto-common",
] ]
[[package]]
name = "either"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be"
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
version = "0.8.30" version = "0.8.30"
@ -569,6 +605,12 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
[[package]]
name = "heck"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.1.19" version = "0.1.19"
@ -589,6 +631,12 @@ dependencies = [
"itoa", "itoa",
] ]
[[package]]
name = "http-range"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573"
[[package]] [[package]]
name = "httparse" name = "httparse"
version = "1.6.0" version = "1.6.0"
@ -622,6 +670,15 @@ dependencies = [
"hashbrown", "hashbrown",
] ]
[[package]]
name = "itertools"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3"
dependencies = [
"either",
]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.1" version = "1.0.1"
@ -715,6 +772,16 @@ version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
[[package]]
name = "mime_guess"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
dependencies = [
"mime",
"unicase",
]
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.4.4" version = "0.4.4"
@ -849,9 +916,9 @@ checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55"
dependencies = [ dependencies = [
"pest", "pest",
"pest_meta", "pest_meta",
"proc-macro2", "proc-macro2 1.0.36",
"quote", "quote 1.0.15",
"syn", "syn 1.0.86",
] ]
[[package]] [[package]]
@ -883,13 +950,22 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
[[package]]
name = "proc-macro2"
version = "0.4.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
dependencies = [
"unicode-xid 0.1.0",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.36" version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
dependencies = [ dependencies = [
"unicode-xid", "unicode-xid 0.2.2",
] ]
[[package]] [[package]]
@ -898,13 +974,22 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quote"
version = "0.6.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
dependencies = [
"proc-macro2 0.4.30",
]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.15" version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2 1.0.36",
] ]
[[package]] [[package]]
@ -972,6 +1057,12 @@ dependencies = [
"semver", "semver",
] ]
[[package]]
name = "rustversion"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0a5f7c728f5d284929a1cccb5bc19884422bfe6ef4d6c409da2c41838983fcf"
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.9" version = "1.0.9"
@ -1014,9 +1105,9 @@ version = "1.0.136"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2 1.0.36",
"quote", "quote 1.0.15",
"syn", "syn 1.0.86",
] ]
[[package]] [[package]]
@ -1074,6 +1165,17 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "simplelog"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48dfff04aade74dd495b007c831cd6f4e0cee19c344dd9dc0884c0289b70a786"
dependencies = [
"log",
"termcolor",
"time",
]
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.5" version = "0.4.5"
@ -1096,15 +1198,66 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "strum"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
[[package]]
name = "strum_macros"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4faebde00e8ff94316c01800f9054fd2ba77d30d9e922541913051d1d978918b"
dependencies = [
"heck",
"proc-macro2 1.0.36",
"quote 1.0.15",
"rustversion",
"syn 1.0.86",
]
[[package]]
name = "syn"
version = "0.15.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
dependencies = [
"proc-macro2 0.4.30",
"quote 0.6.13",
"unicode-xid 0.1.0",
]
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.86" version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2 1.0.36",
"quote", "quote 1.0.15",
"unicode-xid", "unicode-xid 0.2.2",
]
[[package]]
name = "systemctl"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3134cbab79000bee5ed4d71331d2c9071db897f8369e3e3089b6be85cc781d1"
dependencies = [
"default-env",
"itertools",
"strum",
"strum_macros",
]
[[package]]
name = "termcolor"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
dependencies = [
"winapi-util",
] ]
[[package]] [[package]]
@ -1218,6 +1371,15 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
[[package]]
name = "unicase"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
dependencies = [
"version_check",
]
[[package]] [[package]]
name = "unicode-bidi" name = "unicode-bidi"
version = "0.3.7" version = "0.3.7"
@ -1233,6 +1395,12 @@ dependencies = [
"tinyvec", "tinyvec",
] ]
[[package]]
name = "unicode-xid"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
[[package]] [[package]]
name = "unicode-xid" name = "unicode-xid"
version = "0.2.2" version = "0.2.2"
@ -1251,6 +1419,20 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "velnet_control_panel"
version = "0.2.0"
dependencies = [
"actix-files",
"actix-web",
"handlebars",
"log",
"serde",
"serde_json",
"simplelog",
"systemctl",
]
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.4" version = "0.9.4"

View File

@ -1,12 +1,16 @@
[package] [package]
name = "control-panel" name = "velnet_control_panel"
version = "0.1.0" version = "0.2.0"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
actix-web = "4" actix-web = "4.1.0"
handlebars = { version = "4.2.1", features = ["dir_source"] } handlebars = { version = "4.2.1", features = ["dir_source"] }
serde_json = "1.0" serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
simplelog = "^0.12.0"
log = "*"
systemctl = "0.1.7"
actix-files = "0.6.1"

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

@ -0,0 +1,4 @@
#!/bin/bash
cd ..
sudo -u "$1" bash -c "~/.cargo/bin/cargo build --release"

View File

@ -1,5 +1,8 @@
{ {
"port": 8080, "port": 8080,
"log_file": "/home/ntsfranz/Documents/VelNetServerRust/nohup.out", "user": "ntsfranz",
"server_dir": "/home/ntsfranz/Documents/VelNetServerRust/" "server_log_file": "../logs/server.log",
} "control_panel_log_file": "control_panel.log",
"server_dir": "../",
"handlebars_dev_mode": true
}

View File

@ -0,0 +1 @@
sudo -u "$1" bash -c "git pull"

View File

@ -1 +1,3 @@
sudo -u ubuntu bash -c "onefetch > onefetch.out" #!/bin/bash
sudo -u "$1" bash -c "~/.cargo/bin/onefetch > onefetch.out"

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

@ -9,57 +9,64 @@ use serde_json::json;
use std::io; use std::io;
use std::io::{prelude::*, BufReader}; use std::io::{prelude::*, BufReader};
use std::fs; use std::fs;
use std::fs::File; use std::fs::{File, OpenOptions};
use std::path::Path; use std::path::Path;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use std::process::{Command, Stdio}; use std::process::{Command};
use std::env; use std::cmp;
use std::error::Error;
use simplelog;
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
struct Config { struct ControlPanelConfig {
port: u16, port: u16,
log_file: String, user: String,
server_dir: String server_log_file: String,
control_panel_log_file: String,
server_dir: String,
handlebars_dev_mode: bool,
} }
fn lines_from_file(filename: impl AsRef<Path>) -> Vec<String> { fn lines_from_file(filename: impl AsRef<Path>) -> Vec<String> {
let file = File::open(filename).expect("no such file"); if filename.as_ref().is_file() {
let buf = BufReader::new(file); let file = File::open(filename).expect("no such file");
buf.lines() let buf = BufReader::new(file);
.map(|l| l.expect("Could not parse line")) buf.lines()
.collect() .map(|l| l.expect("Could not parse line"))
.collect()
} else {
vec![]
}
} }
#[get("/")] #[get("/")]
async fn index(hb: web::Data<Handlebars<'_>>) -> HttpResponse { async fn index(hb: web::Data<Handlebars<'_>>) -> HttpResponse {
let config = read_config_file().unwrap();
//read the config file
let file = fs::read_to_string("config.json").unwrap();
let config: Config = serde_json::from_str(&file).unwrap();
//read the log file //read the log file
let log_file = lines_from_file(config.log_file); let log_file = lines_from_file(config.server_log_file);
let restarts_log = lines_from_file("../restarts.log");
let uptime = Command::new("uptime") // let restarts_log = lines_from_file(config.restarts_log_file);
let uptime = Command::new("tuptime")
.output() .output()
.expect("failed to execute process"); .expect("failed to execute process");
let _onefetch = Command::new("sh") // let _onefetch = Command::new("sh")
.arg("onefetch_file.sh") // .arg("onefetch_file.sh")
.output() // .arg(config.user)
.expect("failed"); // .output()
// .expect("failed");
let onefetch = fs::read_to_string("onefetch.out").unwrap();
// let onefetch = fs::read_to_string("onefetch.out").unwrap();
let data = json!({ let data = json!({
"log_output": log_file, "log_output": &log_file[(cmp::max((log_file.len() as i64) - 1000, 0) as usize)..],
"restarts_output": restarts_log, // "restarts_output": &restarts_log[(cmp::max((restarts_log.len() as i64) - 1000, 0) as usize)..],
"uptime": format!("{}", String::from_utf8_lossy(&uptime.stdout)), "uptime": format!("{}", String::from_utf8_lossy(&uptime.stdout)),
//"onefetch": format!("{}", String::from_utf8_lossy(&onefetch.stdout)) //"onefetch": format!("{}", String::from_utf8_lossy(&onefetch.stdout))
"onefetch": onefetch "onefetch": ""
}); });
let body = hb.render("index", &data).unwrap(); let body = hb.render("index", &data).unwrap();
@ -70,23 +77,33 @@ async fn index(hb: web::Data<Handlebars<'_>>) -> HttpResponse {
async fn restart_server() -> HttpResponse { async fn restart_server() -> HttpResponse {
// Restart the process // Restart the process
let _output = Command::new("sh") // let _output = Command::new("sh")
.arg("../run.sh") // .arg("../restart_server.sh")
.stdin(Stdio::null()) // .stdin(Stdio::null())
//.stdout(Stdio::null()) // .spawn();
//.stderr(Stdio::null()) // let _output = Command::new("systemd")
.spawn(); // .arg("restart")
// .arg("velnet")
// .stdin(Stdio::null())
// .spawn();
HttpResponse::Ok().body("DONE") systemctl::restart("velnet.service").unwrap();
let ret = systemctl::status("velnet.service").unwrap();
HttpResponse::Ok().body(ret)
} }
#[get("/git_pull")] #[get("/git_pull")]
async fn git_pull() -> HttpResponse { async fn git_pull() -> HttpResponse {
let output = Command::new("git") //read the config file
.arg("pull") let config = read_config_file().unwrap();
let output = Command::new("sh")
.arg("git_pull.sh")
.arg(config.user)
.output() .output()
.expect("failed to execute process"); .expect("failed to execute process");
@ -98,39 +115,63 @@ async fn git_pull() -> HttpResponse {
async fn compile() -> HttpResponse { async fn compile() -> HttpResponse {
//read the config file //read the config file
let file = fs::read_to_string("config.json").unwrap(); let config = read_config_file().unwrap();
let config: Config = serde_json::from_str(&file).unwrap();
let orig_dir = std::env::current_dir().unwrap(); log::debug!("before");
let root = Path::new(&config.server_dir); let output = Command::new("sh")
let _new_dir = env::set_current_dir(&root); .arg("compile_server.sh")
.arg(config.user)
print!("before");
let output = Command::new("cargo")
.arg("build")
.arg("--release")
.output() .output()
.expect("failed to execute process"); .expect("failed to execute process");
print!("after"); log::debug!("after");
let _new_dir = env::set_current_dir(orig_dir);
HttpResponse::Ok().body(output.stdout) HttpResponse::Ok().body(output.stdout)
} }
fn read_config_file() -> Result<ControlPanelConfig, Box<dyn Error>> {
// Open the file in read-only mode with buffer.
let file = File::open("config.json")?;
let reader = BufReader::new(file);
let config = serde_json::from_reader(reader)?;
Ok(config)
}
#[actix_web::main] #[actix_web::main]
async fn main() -> io::Result<()> { async fn main() -> io::Result<()> {
//read the config file
let config: ControlPanelConfig = read_config_file().unwrap();
let f = OpenOptions::new()
.write(true)
.append(true)
.create(true)
.open(config.control_panel_log_file)
.unwrap();
simplelog::CombinedLogger::init(
vec![
simplelog::TermLogger::new(simplelog::LevelFilter::Info, simplelog::Config::default(), simplelog::TerminalMode::Mixed, simplelog::ColorChoice::Auto),
simplelog::WriteLogger::new(simplelog::LevelFilter::Debug, simplelog::Config::default(), f),
]
).unwrap();
log::info!("Starting control panel server.");
let mut handlebars = Handlebars::new(); let mut handlebars = Handlebars::new();
handlebars.set_dev_mode(true); handlebars.set_dev_mode(config.handlebars_dev_mode);
handlebars.register_templates_directory(".hbs", "./static/templates").unwrap(); handlebars.register_templates_directory(".hbs", "./static/templates").unwrap();
let handlebars_ref = web::Data::new(handlebars); let handlebars_ref = web::Data::new(handlebars);
log::info!("http://127.0.0.1:{}", config.port);
HttpServer::new(move || { HttpServer::new(move || {
App::new() App::new()
.wrap(error_handlers()) .wrap(error_handlers())
@ -139,10 +180,11 @@ async fn main() -> io::Result<()> {
.service(restart_server) .service(restart_server)
.service(git_pull) .service(git_pull)
.service(compile) .service(compile)
.service(actix_files::Files::new("/static", "./static"))
}) })
.bind(("127.0.0.1", 8080))? .bind(("0.0.0.0", config.port))?
.run() .run()
.await .await
} }
// Custom error handlers, to return HTML responses when an error occurs. // Custom error handlers, to return HTML responses when an error occurs.

View File

@ -0,0 +1,223 @@
use actix_web::body::BoxBody;
use actix_web::dev::ServiceResponse;
use actix_web::http::header::ContentType;
use actix_web::http::StatusCode;
use actix_web::middleware::{ErrorHandlerResponse, ErrorHandlers};
use actix_web::{get, web, App, HttpResponse, HttpServer, Result};
use handlebars::Handlebars;
use serde_json::json;
use std::io;
use std::io::{prelude::*, BufReader};
use std::fs;
use std::fs::File;
use std::path::Path;
use serde::{Serialize, Deserialize};
use std::process::{Command, Stdio};
use std::env;
use std::cmp;
#[derive(Serialize, Deserialize)]
struct Config {
port: u16,
log_file: String,
server_dir: String,
}
fn lines_from_file(filename: impl AsRef<Path>) -> Vec<String> {
let file = File::open(filename).expect("no such file");
let buf = BufReader::new(file);
buf.lines()
.map(|l| l.expect("Could not parse line"))
.collect()
}
#[get("/")]
async fn index(hb: web::Data<Handlebars<'_>>) -> HttpResponse {
//read the config file
let file = fs::read_to_string("config.json").unwrap();
let config: Config = serde_json::from_str(&file).unwrap();
//read the log file
let log_file = lines_from_file(config.log_file);
let restarts_log = lines_from_file("../restarts.log");
let uptime = Command::new("uptime")
.output()
.expect("failed to execute process");
let _onefetch = Command::new("sh")
.arg("onefetch_file.sh")
.output()
.expect("failed");
let onefetch = fs::read_to_string("onefetch.out").unwrap();
let data = json!({
"log_output": log_file,
"restarts_output": restarts_log,
"uptime": format!("{}", String::from_utf8_lossy(&uptime.stdout)),
//"onefetch": format!("{}", String::from_utf8_lossy(&onefetch.stdout))
"onefetch": onefetch
});
let body = hb.render("index", &data).unwrap();
HttpResponse::Ok().body(body)
}
#[get("/nohup.out")]
async fn nohup() -> HttpResponse {
//read the config file
let file = fs::read_to_string("config.json").unwrap();
let config: Config = serde_json::from_str(&file).unwrap();
//read the log file
let log_file = lines_from_file(config.log_file);
// get the last 100 lines
let all_lines = log_file.as_slice()[cmp::max(log_file.len()-100, 0)..].to_vec().join("\n");
HttpResponse::Ok().body(all_lines)
}
#[get("/restarts.log2")]
async fn restarts() -> HttpResponse {
//read the log file
let log_file = lines_from_file("../restarts.log");
// get the last 100 lines
let all_lines = log_file.as_slice()[cmp::max(log_file.len()-100, 0)..].to_vec().join("\n");
HttpResponse::Ok().body(all_lines)
}
#[get("/restart_server")]
async fn restart_server() -> HttpResponse {
// Restart the process
let _output = Command::new("sh")
.arg("../run.sh")
.stdin(Stdio::null())
//.stdout(Stdio::null())
//.stderr(Stdio::null())
.spawn();
HttpResponse::Ok().body("DONE")
}
#[get("/git_pull")]
async fn git_pull() -> HttpResponse {
let output = Command::new("git")
.arg("pull")
.output()
.expect("failed to execute process");
HttpResponse::Ok().body(output.stdout)
}
#[get("/compile")]
async fn compile() -> HttpResponse {
//read the config file
let file = fs::read_to_string("config.json").unwrap();
let config: Config = serde_json::from_str(&file).unwrap();
let orig_dir = std::env::current_dir().unwrap();
let root = Path::new(&config.server_dir);
let _new_dir = env::set_current_dir(&root);
print!("before");
let output = Command::new("cargo")
.arg("build")
.arg("--release")
.output()
.expect("failed to execute process");
print!("after");
let _new_dir = env::set_current_dir(orig_dir);
HttpResponse::Ok().body(output.stdout)
}
#[actix_web::main]
async fn main() -> io::Result<()> {
let mut handlebars = Handlebars::new();
handlebars.set_dev_mode(true);
handlebars.register_templates_directory(".hbs", "./static/templates").unwrap();
let handlebars_ref = web::Data::new(handlebars);
HttpServer::new(move || {
App::new()
.wrap(error_handlers())
.app_data(handlebars_ref.clone())
.service(index)
.service(restart_server)
.service(git_pull)
.service(compile)
.service(nohup)
.service(restarts)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
// Custom error handlers, to return HTML responses when an error occurs.
fn error_handlers() -> ErrorHandlers<BoxBody> {
ErrorHandlers::new().handler(StatusCode::NOT_FOUND, not_found)
}
// Error handler for a 404 Page not found error.
fn not_found<B>(res: ServiceResponse<B>) -> Result<ErrorHandlerResponse<BoxBody>> {
let response = get_error_response(&res, "Page not found");
Ok(ErrorHandlerResponse::Response(ServiceResponse::new(
res.into_parts().0,
response.map_into_left_body(),
)))
}
// Generic error handler.
fn get_error_response<B>(res: &ServiceResponse<B>, error: &str) -> HttpResponse<BoxBody> {
let request = res.request();
// Provide a fallback to a simple plain text response in case an error occurs during the
// rendering of the error page.
let fallback = |e: &str| {
HttpResponse::build(res.status())
.content_type(ContentType::plaintext())
.body(e.to_string())
};
let hb = request
.app_data::<web::Data<Handlebars>>()
.map(|t| t.get_ref());
match hb {
Some(hb) => {
let data = json!({
"error": error,
"status_code": res.status().as_str()
});
let body = hb.render("error", &data);
match body {
Ok(body) => HttpResponse::build(res.status())
.content_type(ContentType::html())
.body(body),
Err(_) => fallback(error),
}
}
None => fallback(error),
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/mstile-150x150.png"/>
<TileColor>#b91d47</TileColor>
</tile>
</msapplication>
</browserconfig>

Binary file not shown.

After

Width:  |  Height:  |  Size: 815 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,20 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="300.000000pt" height="300.000000pt" viewBox="0 0 300.000000 300.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.14, written by Peter Selinger 2001-2017
</metadata>
<g transform="translate(0.000000,300.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M605 1500 l600 -600 97 0 98 0 0 600 0 600 -100 0 -100 0 0 -452 0
-453 -453 453 -452 452 -145 0 -145 0 600 -600z"/>
<path d="M1500 2000 l0 -100 300 0 300 0 0 100 0 100 -300 0 -300 0 0 -100z"/>
<path d="M2200 1500 l0 -600 398 2 397 3 3 98 3 97 -301 0 -300 0 -2 498 -3
497 -97 3 -98 3 0 -601z"/>
<path d="M1500 1500 l0 -100 300 0 300 0 0 100 0 100 -300 0 -300 0 0 -100z"/>
<path d="M1500 1000 l0 -100 300 0 300 0 0 100 0 100 -300 0 -300 0 0 -100z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 977 B

View File

@ -0,0 +1,19 @@
{
"name": "",
"short_name": "",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-256x256.png",
"sizes": "256x256",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

View File

@ -1,15 +1,24 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="apple-touch-icon" sizes="180x180" href="/static/favicons/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/static/favicons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/static/favicons/favicon-16x16.png">
<link rel="manifest" href="/static/favicons/site.webmanifest">
<link rel="mask-icon" href="/static/favicons/safari-pinned-tab.svg" color="#5bbad5">
<meta name="msapplication-TileColor" content="#b91d47">
<meta name="theme-color" content="#ffffff">
<title>VelNet Control Panel</title> <title>VelNet Control Panel</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css">
<style> <style>
.bottom-scroller { .bottom-scroller {
height: 30em; max-height: 40em;
overflow: auto; overflow: auto;
display: flex; display: flex;
flex-direction: column-reverse; flex-direction: column-reverse;
@ -22,97 +31,80 @@
</head> </head>
<body> <body>
<section class="section"> <section class="section">
<div class="container"> <div class="container">
<img src="https://vel.engr.uga.edu/wp-content/uploads/elementor/thumbs/4dLogo-1-oaxk7d2mcrutzo0dhqah6qlgpp2c1tvihad1dchrc0.png" style="float: right;"> <img src="https://vel.engr.uga.edu/wp-content/uploads/elementor/thumbs/4dLogo-1-oaxk7d2mcrutzo0dhqah6qlgpp2c1tvihad1dchrc0.png"
<h1 class="title"> style="float: right;" alt="vel logo">
VelNet Control Panel <h1 class="title">
</h1> VelNet Control Panel
<p class="subtitle"> </h1>
Log output and utilites for <strong>VelNet</strong> <p class="subtitle">
</p> Log output and utilities for <strong>VelNet</strong>
</div> </p>
</div>
</section> </section>
<section> {{!-- <section>
<div class="container"> <div class="container">
<div class="block"> <div class="block">
<button class="button" id="restart-button">Restart Server</button> <button class="button" id="restart-button">Restart Server</button>
<button class="button" id="pull-button">Git Pull</button> <button class="button" id="pull-button">Git Pull</button>
<button class="button" id="compile-button">Compile</button> <button class="button" id="compile-button">Compile</button>
</div>
<pre style="font-size: 0.8em;">{{uptime}}</pre>
</div>
</section> --}}
<section class="section">
<div class="container">
<nav class="panel">
<p class="panel-heading">
Server Log
<code>server.log</code>
<!-- <label for="debug_checkbox">DEBUG</label> -->
<!-- <input id="debug_checkbox" type="checkbox"> -->
</p>
<div class="content bottom-scroller">
<code class="log-output">
{{#each log_output}}
<div class="panel-block">
{{this}}
</div>
{{/each}}
</code>
</div> </div>
<div class="block">Uptime: {{uptime}}</div>
<pre>{{onefetch}}</pre>
</div>
</section>
<section class="section"> </nav>
<div class="container">
<nav class="panel">
<p class="panel-heading">
Server Log
<code>nohup.out</code>
</p>
<div class="content bottom-scroller"> </div>
<code class="log-output">
<div>
{{#each log_output}}
<div class="panel-block">
{{this}}
</div>
{{/each}}
</div>
</code>
</div>
</nav> </section>
<nav class="panel"> <script>
<p class="panel-heading"> "use strict";
Server Restarts document.getElementById('restart-button').addEventListener('click', _ => {
<code>restarts.log</code> fetch('/restart_server').then(_ => {
</p> setTimeout(location.reload(), 1000);
<div class="content bottom-scroller">
<code class="log-output">
<div>
{{#each restarts_output}}
<div class="panel-block">
{{this}}
</div>
{{/each}}
</div>
</code>
</div>
</nav>
</div>
</section>
<script>
"use strict";
document.getElementById('restart-button').addEventListener('click', c=> {
fetch('/restart_server').then(r=> {
setTimeout(location.reload(), 1000);
});
}); });
document.getElementById('pull-button').addEventListener('click', c=> {
fetch('/git_pull').then(r=> { });
location.reload(); document.getElementById('pull-button').addEventListener('click', _ => {
}); fetch('/git_pull').then(_ => {
location.reload();
}); });
document.getElementById('compile-button').addEventListener('click', c=> {
fetch('/compile').then(r=> { });
location.reload(); document.getElementById('compile-button').addEventListener('click', _ => {
}); fetch('/compile').then(_ => {
location.reload();
}); });
</script>
});
</script>
</body> </body>
</html> </html>

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:

35
install.sh Executable file
View File

@ -0,0 +1,35 @@
#!/bin/bash
echo "Building VelNet Server..."
cargo build --release
(
cd control-panel || exit
echo "Building VelNet Control Panel..."
cargo build --release
)
echo "Copying systemd configs..."
(
sudo cp velnet.service /etc/systemd/system/
service="/etc/systemd/system/velnet.service"
wd="$(pwd)"
exe="$(pwd)/target/release/velnet_server"
sudo sed -i -e "s+WorkingDirectory=+WorkingDirectory=$wd+g" "$service"
sudo sed -i -e "s+ExecStart=+ExecStart=$exe+g" "$service"
systemctl enable velnet
systemctl restart velnet
)
(
cd control-panel || exit
sudo cp ../velnet.service /etc/systemd/system/velnet-control-panel.service
service="/etc/systemd/system/velnet-control-panel.service"
wd="$(pwd)"
exe="$(pwd)/target/release/velnet_control_panel"
sudo sed -i -e "s+WorkingDirectory=+WorkingDirectory=$wd+g" "$service"
sudo sed -i -e "s+ExecStart=+ExecStart=$exe+g" "$service"
systemctl enable velnet-control-panel
systemctl restart velnet-control-panel
)
echo "'velnet' and 'velnet-control-panel' systemd services enabled."

15
run.sh
View File

@ -1,15 +0,0 @@
#!/bin/bash
BASEDIR=$(dirname "$0")
echo $(date +"%Y-%m-%dT%T.%3N%z") >> $BASEDIR/restarts.log
echo "Before: " >> $BASEDIR/restarts.log
pgrep -f "VelNetServerRust -v2" >> $BASEDIR/restarts.log
#pgrep -f "VelNetServerRust -v2" | xargs sudo kill >> $BASEDIRE/restarts.log
#kill $(pgrep -f "VelNetServerRust -v2")
pkill -f "VelNetServerRust -v2"
echo "\nAfter: " >> $BASEDIR/restarts.log
pgrep -f "VelNetServerRust -v2" >> $BASEDIR/restarts.log
echo "\n"
(cd $BASEDIR && nohup ./target/release/VelNetServerRust -v2 >> nohup.out &)
# nohup "$BASEDIR/target/release/VelNetServerRust" &
echo "Starting..." >> $BASEDIR/restarts.log

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"

View File

@ -1,6 +1,6 @@
[package] [package]
name = "VelNetServerRust" name = "velnet_server"
version = "0.1.0" version = "0.2.0"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -9,3 +9,8 @@ edition = "2021"
chrono = "*" chrono = "*"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" 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);
}

View File

@ -1,846 +0,0 @@
extern crate chrono;
extern crate serde;
extern crate serde_json;
use std::io::prelude::*;
use std::thread;
use std::net::{TcpListener, TcpStream,UdpSocket,IpAddr,SocketAddr};
use std::collections::HashMap;
use std::sync::{Arc,RwLock};
use std::sync::mpsc;
use std::sync::mpsc::{SyncSender,Receiver};
use chrono::Local;
use std::fs;
use std::time;
use serde::{Serialize, Deserialize};
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,
SendMesssageOthersUnbuffered = FromClientTCPMessageType::SendMessageOthersUnbuffered as isize,
SendMessageAllUnbuffered = FromClientTCPMessageType::SendMessageAllUnbuffered as isize,
SendMessageGroupUnbuffered = FromClientTCPMessageType::SendMessageGroupUnbuffered as isize
}
struct Client {
logged_in: Arc<RwLock<bool>>,
id: u32,
username: Arc<RwLock<String>>,
application: Arc<RwLock<String>>,
room: Arc<RwLock<Option<Arc<Room>>>>,
sender: SyncSender<Vec<u8>>,
rooms_mutex: Arc<RwLock<HashMap<String,Arc<Room>>>>,
clients_mutex: Arc<RwLock<HashMap<u32,Arc<Client>>>>,
groups: Arc<RwLock<HashMap<String,Vec<Arc<Client>>>>>,
ip: Arc<RwLock<IpAddr>>,
port: Arc<RwLock<u16>>
}
struct Room {
name: String,
clients: RwLock<HashMap<u32,Arc<Client>>>,
master_client: Arc<RwLock<Arc<Client>>>
}
#[derive(Serialize, Deserialize)]
struct Config {
port: u16,
tcp_timeout: u64,
tcp_send_buffer: usize
}
fn read_u8(stream: &mut TcpStream) -> u8 {
let mut buf = [0; 1];
stream.read_exact(&mut buf).unwrap();
return buf[0];
}
fn read_u32(stream: &mut TcpStream) -> u32 {
let mut buf:[u8;4] = [0; 4];
stream.read_exact(&mut buf).unwrap();
let size = u32::from_be_bytes(buf);
return size;
}
fn _read_string(stream: &mut TcpStream) -> String {
let size = read_u32(stream);
let mut string_bytes = vec![0;size as usize];
stream.read_exact(&mut string_bytes).unwrap();
return String::from_utf8(string_bytes).unwrap();
}
fn read_short_string(stream: &mut TcpStream) -> String {
let size = read_u8(stream);
let mut string_bytes = vec![0;size as usize];
stream.read_exact(&mut string_bytes).unwrap();
return String::from_utf8(string_bytes).unwrap();
}
fn read_vec(stream: &mut TcpStream) -> Vec<u8> {
let message_size = read_u32(stream);
let mut message = vec![0u8;message_size as usize];
stream.read_exact(&mut message).unwrap();
return message;
}
//this is in response to someone asking to login (this is where usernames and passwords would be processed, in theory)
fn read_login_message(stream: &mut TcpStream, client: &Arc<Client>) {
//byte,shortstring,byte,shortstring
let username = read_short_string(stream);
let application = read_short_string(stream);
println!("{}: Got application {} and userid {}",Local::now().format("%Y-%m-%d %H:%M:%S"),application,username);
let mut client_user = client.username.write().unwrap();
*client_user = username;
let mut client_application = client.application.write().unwrap();
*client_application = application;
let mut client_loggedin = client.logged_in.write().unwrap();
*client_loggedin = true;
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.sender.try_send(write_buf).unwrap();
}
//this is in response to a request for rooms.
fn read_rooms_message(_stream: &mut TcpStream, client: &Arc<Client>){
let mut write_buf = vec![];
write_buf.push(ToClientTCPMessageType::RoomList as u8);
//first we need to get the room names
let rooms = client.rooms_mutex.read().unwrap();
let mut rooms_vec = vec![];
for (k,v) in rooms.iter() {
let app_name = client.application.read().unwrap();
if !k.starts_with(&app_name.to_string()) {
continue;
}
let clients = v.clients.read().unwrap();
let mut iter = k.chars();
iter.by_ref().nth(app_name.len());
let application_stripped_room = iter.as_str();
let room_string = format!("{}:{}",application_stripped_room,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.sender.try_send(write_buf).unwrap();
}
fn read_roomdata_message(stream: &mut TcpStream, client: &Arc<Client>){
//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(stream);
let application = client.application.read().unwrap().to_string();
let room_name = format!("{}_{}", application, short_room_name);
//we need to access the rooms list
let rooms = client.rooms_mutex.read().unwrap();
if rooms.contains_key(&room_name) {
let room = rooms.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.clients.read().unwrap();
write_buf.extend_from_slice(&(clients.len() as u32).to_be_bytes());
for (_k,c) in clients.iter() {
//write out the client id (u32) and the username (short string)
write_buf.extend_from_slice(&(c.id).to_be_bytes());
let username = c.username.read().unwrap();
let username_bytes = username.as_bytes();
write_buf.push(username_bytes.len() as u8);
write_buf.extend_from_slice(&username_bytes);
}
client.sender.try_send(write_buf).unwrap();
}
}
fn send_client_master_message(to: &Arc<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
let res = to.sender.try_send(write_buf);
match res {
Ok(_) => (),
Err(_) => ()
}
}
fn send_client_join_message(to: &Arc<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 res = to.sender.try_send(write_buf);
match res {
Ok(_) => (),
Err(_) => ()
}
}
fn send_you_joined_message(to: &Arc<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());
let res = to.sender.try_send(write_buf);
match res {
Ok(_) => (),
Err(_) => ()
}
}
fn send_you_left_message(to: &Arc<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());
let res = to.sender.try_send(write_buf);
match res {
Ok(_) => (),
Err(_) => ()
}
}
fn send_client_left_message(to: &Arc<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 res = to.sender.try_send(write_buf);
match res {
Ok(_) => (),
Err(_) => ()
}
}
//helper function, because clients leave room in multiple places
fn client_leave_room(client: &Arc<Client>, send_to_client: bool){
//first remove the client from the room they are in
{
let room = client.room.read().unwrap(); //I need to get the room, because I'll be modifying the clients in it
if room.is_some(){
{
let mut change_master = false;
let mut new_master_id = 0;
{
println!("{}: {}: Client {} in room, leaving",Local::now().format("%Y-%m-%d %H:%M:%S"), client.application.read().unwrap().to_string(),client.id);
}
let room = room.as_ref().unwrap();
//may have to choose a new master
{
let clients = room.clients.read().unwrap();
let master_client = room.master_client.read().unwrap();
if master_client.id == client.id {
//change the master
change_master = true;
}
for (_k,v) in clients.iter() {
if !send_to_client && v.id == client.id{
continue;
}else if v.id == client.id {
send_you_left_message(v, &room.name);
}else{
send_client_left_message(v, client.id, &room.name);
}
}
}
{
let mut clients = room.clients.write().unwrap();
clients.remove(&client.id); //remove the client from that list in the room
}
let clients = room.clients.read().unwrap();
//if the room is empty, destroy it as well
if clients.len() == 0 {
let mut rooms = client.rooms_mutex.write().unwrap();
rooms.remove(&room.name);
{
println!("{}: {}: Destroyed room {}",Local::now().format("%Y-%m-%d %H:%M:%S"), client.application.read().unwrap().to_string(), &room.name)
}
}else if change_master{
for (_k,v) in clients.iter() {
if v.id != client.id {
new_master_id = v.id;
break;
}
}
{
println!("{}: {}: Changing master to {}",Local::now().format("%Y-%m-%d %H:%M:%S"),client.application.read().unwrap().to_string(), new_master_id);
}
for (_k,v) in clients.iter() {
send_client_master_message(&v, new_master_id);
}
{
let mut master_client = room.master_client.write().unwrap();
*master_client = clients.get(&new_master_id).unwrap().clone();
}
}
}
}
}
{
let mut room = client.room.write().unwrap();
*room = Option::None;
}
}
fn read_join_message(stream: &mut TcpStream, client: &Arc<Client>){
//byte,shortstring
let short_room_name = read_short_string(stream);
let application = client.application.read().unwrap().to_string();
let extended_room_name = format!("{}_{}", application, short_room_name);
//if the client is in a room, leave it
let mut leave_room = false;
{
let room = client.room.read().unwrap(); //must release this mutex before calling into a function that uses it
if room.as_ref().is_some(){
leave_room = true;
}
}
if leave_room {
client_leave_room(client, true);
}
if short_room_name.trim() == "" || short_room_name == "-1" {
return;
}
//join room_name
{
{
let mut rooms = client.rooms_mutex.write().unwrap();
if !rooms.contains_key(&extended_room_name) { //new room, must create it
let map: HashMap<u32, Arc<Client>> = HashMap::new();
let r = Arc::new(Room {
name: extended_room_name.to_string(),
clients: RwLock::new(map),
master_client: Arc::new(RwLock::new(client.clone())) //client is the master, since they joined first
});
rooms.insert(String::from(&extended_room_name),r);
println!("{}: {}: New room {} created",Local::now().format("%Y-%m-%d %H:%M:%S"), application,&extended_room_name);
}
//the room is guaranteed to exist now, so this call can't fail
let room_to_join = &rooms[&extended_room_name];
let mut clients = room_to_join.clients.write().unwrap();
clients.insert(client.id,client.clone());
println!("{}: {}: Client {} joined {}",Local::now().format("%Y-%m-%d %H:%M:%S"), application, client.id,&extended_room_name);
let mut room = client.room.write().unwrap();
*room = Some(room_to_join.clone()); //we create an option and assign it back to the room
}
//once the client is in the room, it can't suddenly die, so we can release the write lock, and can use the extended_room name without issue
{
let rooms = client.rooms_mutex.read().unwrap();
let clients = rooms[&extended_room_name].clients.read().unwrap(); //only need a read lock now
//send a join message to everyone in the room (except the client)
for (_k,v) in clients.iter() {
if v.id != client.id {
send_client_join_message(v, client.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 clients.iter() {
ids_in_room.push(v.id);
}
send_you_joined_message(client, ids_in_room, &short_room_name);
}
let room = client.room.read().unwrap();
//tell the client who the master is
let master_client = room.as_ref().unwrap().master_client.read().unwrap();
send_client_master_message(client, master_client.id);
}
}
// function send_message_to_clients_dictionary(clients: message: &Vec<u8>, include_sender: bool){
// }
fn send_room_message(sender: &Arc<Client>, message: &Vec<u8>, include_sender: bool, ordered: bool){
//this message is 3u8, sender_id_u32, message_len_u32, message_bytes
let mut write_buf = vec![];
write_buf.push(ToClientTCPMessageType::DataMessage as u8);
write_buf.extend_from_slice(&sender.id.to_be_bytes());
write_buf.extend_from_slice(&(message.len() as u32).to_be_bytes());
write_buf.extend_from_slice(message);
//println!("sending {} bytes from {}",message.len(),sender.id);
{
if !ordered {
let room = sender.room.read().unwrap();
if room.is_some() {
let clients = room.as_ref().unwrap().clients.read().unwrap();
for (_k,v) in clients.iter(){
if !include_sender && v.id == sender.id {
continue;
}
match v.sender.try_send(write_buf.clone()){
Ok(_) => (),
Err(x) => println!("{}: {}: Error sending to client {}: {}",Local::now().format("%Y-%m-%d %H:%M:%S"), v.application.read().unwrap().to_string(),v.id,x)
} //this sometimes fails.
}
}
}else{ //I'm bad at rust, so I don't know how else to do this other than repeat the code above because the types are so different
let room = sender.room.write().unwrap();
if room.is_some() {
let clients = room.as_ref().unwrap().clients.read().unwrap();
for (_k,v) in clients.iter(){
if !include_sender && v.id == sender.id {
continue;
}
match v.sender.try_send(write_buf.clone()){
Ok(_) => (),
Err(x) => println!("{}: {}: Error sending to client {}: {}",Local::now().format("%Y-%m-%d %H:%M:%S"),v.application.read().unwrap().to_string(),v.id,x)
} //this sometimes fails.
}
}
}
}
}
fn send_group_message(sender: &Arc<Client>, message: &Vec<u8>, group: &String){
let mut write_buf = vec![];
write_buf.push(ToClientTCPMessageType::DataMessage as u8);
write_buf.extend_from_slice(&sender.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 groups = sender.groups.read().unwrap();
if groups.contains_key(group) {
let group = groups.get(group).unwrap();
for c in group {
//there may be a leftover when a client leaves...will fix itself
match c.sender.try_send(write_buf.clone()) {
Ok(_) => (),
Err(_) => ()
}
}
}
}
fn read_send_message(stream: &mut TcpStream, client: &Arc<Client>, message_type: u8){
//4 byte length, array
//this is a message for everyone in the room (maybe)
let to_send = read_vec(stream);
if message_type == FromClientTCPMessageType::SendMessageOthersUnbuffered as u8 {
send_room_message(client,&to_send,false,false);
}else if message_type == FromClientTCPMessageType::SendMessageAllUnbuffered as u8 {
send_room_message(client,&to_send,true,false);
} else if message_type == FromClientTCPMessageType::SendMessageOthersBuffered as u8 { //ordered
send_room_message(client,&to_send,false,true);
}else if message_type == FromClientTCPMessageType::SendMessageAllBuffered as u8 { //ordered
send_room_message(client,&to_send,true,true);
}else if message_type == FromClientTCPMessageType::SendMessageGroupUnbuffered as u8 {
let group = read_short_string(stream);
send_group_message(client,&to_send, &group);
}
}
fn read_group_message(stream: &mut TcpStream, client: &Arc<Client>){
let group = read_short_string(stream);
let id_bytes = read_vec(stream);
let num = id_bytes.len();
let mut groups = client.groups.write().unwrap();
let clients = client.clients_mutex.read().unwrap();
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.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 groups.contains_key(&group) {
groups.remove(&group); //ensures the client references go away
}
groups.insert(group.clone(),group_clients);
}
fn client_read_thread(mut stream: TcpStream, mut client: Arc<Client>) {
let mut read_buf:[u8;1] = [0; 1];
//messages come through as a 1 byte type identifier, that can be one of 0 (login) 1 (get rooms), 2 (join/leave room) 3 (send message to room), 4 (send message to room including me), 5 (send message to group), 6 (establish group)
loop {
//read exactly 1 byte
stream.read_exact(&mut read_buf).unwrap();
//println!("Got a message {}",read_buf[0]);
let t = read_buf[0];
if t == FromClientTCPMessageType::LogIn as u8 { //[0:u8][username.length():u8][username:shortstring][password.length():u8][password:shortstring]
read_login_message(&mut stream, &mut client);
} else if t == FromClientTCPMessageType::GetRooms as u8 {//[1:u8]
read_rooms_message(&mut stream, &mut client);
} else if t == FromClientTCPMessageType::GetRoomData as u8 {
read_roomdata_message(&mut stream, &mut client);
} else if t == FromClientTCPMessageType::JoinRoom as u8 {//[2:u8][roomname.length():u8][roomname:shortstring]
read_join_message(&mut stream, &mut client);
} 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]
read_send_message(&mut stream, &client, t);
} else if t == FromClientTCPMessageType::CreateGroup as u8 { //[t:u8][list.lengthbytes:i32][clients:i32array]
read_group_message(&mut stream, &client);
} else {
//die...not correct protocol
println!("Incorrect protocol, killing");
return;
}
std::io::stdout().flush().unwrap();
}
}
fn client_write_thread(mut stream: TcpStream, rx: Receiver<Vec<u8>> ) {
//wait on messages in my queue
loop {
let m = rx.recv().unwrap();
//println!("Sending a message {}",m.len());
if m.len() == 1{
break;
}
stream.write(&m).unwrap();
}
}
fn handle_client(stream: TcpStream, client_id: u32, clients_mutex: Arc<RwLock<HashMap<u32,Arc<Client>>>>, rooms_mutex: Arc<RwLock<HashMap<String,Arc<Room>>>>,tcp_timeout: u64,tcp_send_buffer:usize){
stream.set_nodelay(true).unwrap();
stream.set_read_timeout(Some(time::Duration::new(tcp_timeout,0))).unwrap();
stream.set_write_timeout(Some(time::Duration::new(tcp_timeout,0))).unwrap();
println!("{}: Accepted new connection, assigned client id {}",Local::now().format("%Y-%m-%d %H:%M:%S"),client_id);
let (tx, rx) = mpsc::sync_channel(tcp_send_buffer); //the server is very fast. However, if the clients throttle the sending, this buffer may overflow, and when it does, data is lost
//create a new client structure and add it to the list of clients
let client = Arc::new(Client{
id: client_id,
username: Arc::new(RwLock::new(String::from(""))),
logged_in: Arc::new(RwLock::new(false)),
room: Arc::new(RwLock::new(Option::None)),
application: Arc::new(RwLock::new(String::from(""))),
sender: tx,
rooms_mutex: rooms_mutex.clone(),
clients_mutex: clients_mutex.clone(),
groups: Arc::new(RwLock::new(HashMap::new())),
ip: Arc::new(RwLock::new(stream.peer_addr().unwrap().ip())),
port: Arc::new(RwLock::new(0))
});
{
let mut clients = clients_mutex.write().unwrap();
clients.insert(client_id, client.clone());
}
let read_clone = stream.try_clone().expect("clone failed");
let read_client = client.clone();
let read_handle = thread::spawn(move ||{client_read_thread(read_clone, read_client)});
let write_handle = thread::spawn(move ||{client_write_thread(stream, rx)});
//handle writing to the thread as needed
match read_handle.join(){
Ok(_)=>(),
Err(_)=>()
}
match client.sender.try_send(vec![0]){ //force send thread to exit
Ok(_)=>(),
Err(_)=>()
}
match write_handle.join() {
Ok(_)=>(),
Err(_)=>()
}
println!("{}: {}: Client {} left",Local::now().format("%Y-%m-%d %H:%M:%S"),client.application.read().unwrap().to_string(),client_id);
//now we can kill the client.
{
//make sure we remove the client from all rooms
client_leave_room(&client, false);
let mut clients = clients_mutex.write().unwrap();
clients.remove(&client_id);
}
}
fn tcp_listen(client_mutex: Arc<RwLock<HashMap<u32, Arc<Client>>>>, room_mutex: Arc<RwLock<HashMap<String,Arc<Room>>>>,port:u16,tcp_timeout:u64,tcp_send_buffer:usize){
println!("{}: Started TCP Listener",Local::now().format("%Y-%m-%d %H:%M:%S"));
let listener = TcpListener::bind(format!("0.0.0.0:{}",port)).expect("could not bind port");
let mut next_client_id = 0;
// accept connections and process them serially
for stream in listener.incoming() {
let client_mutex = Arc::clone(&client_mutex);
let room_mutex = Arc::clone(&room_mutex);
thread::spawn(move || {handle_client(stream.unwrap(), next_client_id, client_mutex, room_mutex,tcp_timeout,tcp_send_buffer)});
next_client_id+=1;
}
println!("{}: Ended TCP Listener",Local::now().format("%Y-%m-%d %H:%M:%S"));
}
fn udp_listen(client_mutex: Arc<RwLock<HashMap<u32, Arc<Client>>>>, _room_mutex: Arc<RwLock<HashMap<String,Arc<Room>>>>,port:u16){
let mut buf = [0u8;1024];
let s = UdpSocket::bind(format!("0.0.0.0:{}",port)).unwrap();
println!("{}: UDP Thread Started",Local::now().format("%Y-%m-%d %H:%M:%S"));
loop {
let res = s.recv_from(&mut buf);
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 = client_mutex.read().unwrap();
if clients.contains_key(&client_id){
let client = clients.get(&client_id).unwrap();
let mut port = client.port.write().unwrap();
*port = addr.port(); //set the udp port to send data to
buf[0] = ToClientUDPMessageType::Connected as u8;
match s.send_to(&buf,addr) {
Ok(_)=>(),
Err(_)=>()
}
}
}
} else if t == FromClientUDPMessageType::SendMesssageOthersUnbuffered as u8 { //[3:u8][from:i32][contents:u8array] note that it must fit into the packet of 1024 bytes
{
let clients = client_mutex.read().unwrap();
if clients.contains_key(&client_id){
let client = clients.get(&client_id).unwrap();
let room_option = client.room.read().unwrap();
let room = room_option.as_ref().unwrap();
let room_clients = room.clients.read().unwrap(); //we finally got to the room!
buf[0] = ToClientUDPMessageType::DataMessage as u8; //technically unecessary, unless we change this number
for (_k,v) in room_clients.iter() {
if v.id != client_id{
let ip = v.ip.read().unwrap();
let port = v.port.read().unwrap();
match s.send_to(&buf,SocketAddr::new(*ip, *port)) {
Ok(_)=> (),
Err(_) => ()
}
}
}
}
}
} else if t == FromClientUDPMessageType::SendMessageAllUnbuffered as u8 { //see above
{
let clients = client_mutex.read().unwrap();
if clients.contains_key(&client_id){
let client = clients.get(&client_id).unwrap();
let room_option = client.room.read().unwrap();
let room = room_option.as_ref().unwrap();
let room_clients = room.clients.read().unwrap(); //we finally got to the room!
buf[0] = ToClientUDPMessageType::DataMessage as u8; //messages are always 3s, even though this came in as 4
for (_k,v) in room_clients.iter() {
let ip = v.ip.read().unwrap();
let port = v.port.read().unwrap();
match s.send_to(&buf,SocketAddr::new(*ip, *port)) {
Ok(_)=> (),
Err(_) => ()
}
}
}
}
} 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 group_name = String::from_utf8(group_name_bytes.to_vec()).unwrap();
let clients = client_mutex.read().unwrap();
if clients.contains_key(&client_id){
let client = clients.get(&client_id).unwrap();
let groups = client.groups.read().unwrap();
if groups.contains_key(&group_name) {
let clients = groups.get(&group_name).unwrap();
//we need to form a new message without the group name
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);
for v in clients.iter() {
let ip = v.ip.read().unwrap();
let port = v.port.read().unwrap();
match s.send_to(&message_to_send,SocketAddr::new(*ip, *port)) {
Ok(_)=> (),
Err(_) => ()
}
}
}
}
}
}
}
println!("{}: UDP Thread Ended",Local::now().format("%Y-%m-%d %H:%M:%S"));
}
fn main() {
println!("{}: VelNet Server Starting",Local::now().format("%Y-%m-%d %H:%M:%S"));
//read the config file
let foo = fs::read_to_string("config.txt").unwrap();
let config: Config = serde_json::from_str(&foo).unwrap();
println!("{}",config.port);
let clients: HashMap<u32, Arc<Client>> = HashMap::new();
let rooms: HashMap<String, Arc<Room>> = HashMap::new();
let client_mutex = Arc::new(RwLock::new(clients));
let room_mutex = Arc::new(RwLock::new(rooms));
//start the UDP thread
let udp_clients = Arc::clone(&client_mutex);
let udp_rooms = Arc::clone(&room_mutex);
let udp_handle = thread::spawn(move ||{udp_listen(udp_clients, udp_rooms, config.port);});
//start the TCP thread
tcp_listen(client_mutex, room_mutex,config.port,config.tcp_timeout,config.tcp_send_buffer);
udp_handle.join().unwrap();
println!("{}: VelNet Ended", Local::now().format("%Y-%m-%d %H:%M:%S"));
}

13
velnet.service Normal file
View File

@ -0,0 +1,13 @@
[Unit]
Description=VelNet Server
Requires=network.target
After=network.target
[Service]
User=root
Group=root
WorkingDirectory=
ExecStart=
[Install]
WantedBy=multi-user.target