Compare commits
206 Commits
lifetime-c
...
master
| Author | SHA1 | Date |
|---|---|---|
|
|
af011a48fe | |
|
|
f27a12b01f | |
|
|
3e475656f0 | |
|
|
bf80015145 | |
|
|
b8c83e7853 | |
|
|
25582d2e55 | |
|
|
313b115537 | |
|
|
c1e4a22099 | |
|
|
0bfbd9c747 | |
|
|
e6905cb6b2 | |
|
|
ddb886df03 | |
|
|
596dd0db05 | |
|
|
2caff23ff2 | |
|
|
f43ceb07df | |
|
|
5aa4f8caf6 | |
|
|
0572d0d0e9 | |
|
|
cf16367d51 | |
|
|
8f3d1549ca | |
|
|
53ff1858f6 | |
|
|
d6c4baf556 | |
|
|
f3ab1f37da | |
|
|
30d9e5ba2d | |
|
|
7d749d0de0 | |
|
|
436975e3c5 | |
|
|
a5eff0bd32 | |
|
|
bb5fbdc43b | |
|
|
b53a63fd54 | |
|
|
19832f0aa1 | |
|
|
232effad14 | |
|
|
a8c37225ae | |
|
|
22a0d68255 | |
|
|
cd81b1e250 | |
|
|
db72977d2f | |
|
|
3ff3e531a1 | |
|
|
5b9d91e44e | |
|
|
1966f80bc6 | |
|
|
546e566c9b | |
|
|
965231cde9 | |
|
|
042f7e1007 | |
|
|
b1be92ed02 | |
|
|
b1b8107ce7 | |
|
|
e92096b6ad | |
|
|
a493eddd50 | |
|
|
194e954733 | |
|
|
f00d8cc7f7 | |
|
|
5f6ecdd839 | |
|
|
14154ebd4e | |
|
|
5b7258d3ab | |
|
|
9b5cb18693 | |
|
|
b66fdd62b3 | |
|
|
71fe467ca2 | |
|
|
6c89f34151 | |
|
|
ca4b0259b9 | |
|
|
585926ebed | |
|
|
5e49056992 | |
|
|
962293b32f | |
|
|
4c7c68c2a5 | |
|
|
7731e18d8b | |
|
|
f4bd223d12 | |
|
|
9de6969eb8 | |
|
|
3591318270 | |
|
|
e082971df0 | |
|
|
1ecccf6d67 | |
|
|
96450d5da2 | |
|
|
8df5ac848b | |
|
|
87a2a6b6da | |
|
|
f8a1364645 | |
|
|
b0ed33b5c7 | |
|
|
d1fce2060c | |
|
|
9a22addbb9 | |
|
|
3fb7c345ba | |
|
|
5b60f9282d | |
|
|
13dbf4050f | |
|
|
9b5912f9eb | |
|
|
28694107d4 | |
|
|
0dc93ba9ce | |
|
|
ccb29581fb | |
|
|
cae3a81cfb | |
|
|
b98ce05d32 | |
|
|
72f0ca0139 | |
|
|
1faacb9223 | |
|
|
285015f3f7 | |
|
|
0b36d04387 | |
|
|
157f76838b | |
|
|
a7de2887d4 | |
|
|
ef0ee61965 | |
|
|
864866d65b | |
|
|
8bd9303f5c | |
|
|
abb4bfaa3e | |
|
|
fd8346727b | |
|
|
1ac65001b4 | |
|
|
5243a6ce8e | |
|
|
b3174cc488 | |
|
|
4deabcd1fc | |
|
|
ff97058aa9 | |
|
|
b5194fce23 | |
|
|
89bf41861f | |
|
|
ab23ff5a99 | |
|
|
face6f935d | |
|
|
8831413e00 | |
|
|
f2c87bacb3 | |
|
|
e8fc561894 | |
|
|
5473b2255d | |
|
|
d74ed68206 | |
|
|
4501887b14 | |
|
|
c233138ca6 | |
|
|
8aa499ab03 | |
|
|
659a26b6a8 | |
|
|
143848f05c | |
|
|
800aedd9d8 | |
|
|
e5b439bb57 | |
|
|
49d3afd11f | |
|
|
638b1ac969 | |
|
|
ad2c83c01c | |
|
|
749ddac360 | |
|
|
b3de7670f3 | |
|
|
c4d3270026 | |
|
|
4d6a11e2be | |
|
|
28622307af | |
|
|
0c28bfa7f2 | |
|
|
48b7edd532 | |
|
|
7663a3f4ab | |
|
|
52fcd65a73 | |
|
|
acddcd17a8 | |
|
|
f3910e9515 | |
|
|
dc54a5d952 | |
|
|
b8440e8290 | |
|
|
81c5949de3 | |
|
|
573a625a04 | |
|
|
135a497f18 | |
|
|
c49bfb281e | |
|
|
c7edea3984 | |
|
|
68b3ebf74b | |
|
|
b393223bfa | |
|
|
399b9b8c59 | |
|
|
f24a4f72df | |
|
|
b33c250d08 | |
|
|
ca3e559353 | |
|
|
c1236bdcd3 | |
|
|
ea98cc7688 | |
|
|
2837ab52e1 | |
|
|
b4a49918fc | |
|
|
5e1ba2e110 | |
|
|
dbf49a65b2 | |
|
|
1a6b344b58 | |
|
|
be167055e8 | |
|
|
b1ad04ed50 | |
|
|
593fbc96ed | |
|
|
dfcc3fbedc | |
|
|
5057cec283 | |
|
|
82498ca72d | |
|
|
0b824efdb8 | |
|
|
7a25bd8b6e | |
|
|
1f3745c0b1 | |
|
|
fa57f25d8a | |
|
|
03ab7290c5 | |
|
|
ef86b89042 | |
|
|
a1474d01de | |
|
|
6d3ffc5ae5 | |
|
|
164542a56b | |
|
|
20afff2931 | |
|
|
22ed3db6bb | |
|
|
46a6b30681 | |
|
|
d56a7209c2 | |
|
|
f05323361c | |
|
|
743433ab57 | |
|
|
55e4935105 | |
|
|
a63fb3019c | |
|
|
d26b2676fb | |
|
|
8c2fbe17a1 | |
|
|
fc6e87c8bf | |
|
|
5ced20099a | |
|
|
d91c9bfd3b | |
|
|
7edc209fc8 | |
|
|
951ddeb754 | |
|
|
1e4b84787a | |
|
|
3c065781ea | |
|
|
72d297eca6 | |
|
|
28a8ec2302 | |
|
|
646163b3e3 | |
|
|
0ab49de27f | |
|
|
6f660517da | |
|
|
bd268d5a96 | |
|
|
4377b26d5f | |
|
|
4cd27856ba | |
|
|
d34c9580bd | |
|
|
9dff749432 | |
|
|
cfe48835ac | |
|
|
52e97bbaad | |
|
|
21cb46e4fb | |
|
|
ac9a337f35 | |
|
|
f399386906 | |
|
|
2827ab5c5e | |
|
|
700c093c6b | |
|
|
0e6d141704 | |
|
|
f7b827b15d | |
|
|
240f8fa671 | |
|
|
73b732fbe0 | |
|
|
7ed9e469fc | |
|
|
f4b9bdbb51 | |
|
|
9d234b2393 | |
|
|
6df560c525 | |
|
|
0677a95b61 | |
|
|
0903a07cc0 | |
|
|
79e6e425dc | |
|
|
1ebde0f0ed |
|
|
@ -0,0 +1,7 @@
|
|||
.gitignore
|
||||
|
||||
/target
|
||||
/docs
|
||||
/.vscode
|
||||
/.idea
|
||||
/.github
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
name: Publish server image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['master']
|
||||
|
||||
# Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds.
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
# There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu.
|
||||
jobs:
|
||||
build-and-push-image:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job.
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here.
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels.
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
|
||||
# This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages.
|
||||
# It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository.
|
||||
# It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step.
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
name: Check and test server
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install protobuffer compiler
|
||||
run: sudo apt-get install -y protobuf-compiler
|
||||
- uses: actions/checkout@v4
|
||||
- name: check
|
||||
run: cargo check --verbose
|
||||
- name: test
|
||||
run: cargo test --verbose
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
name: Push to AWS
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
Push:
|
||||
name: Push
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v1
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
aws-region: eu-north-1
|
||||
|
||||
|
||||
- name: Login to Amazon ECR
|
||||
id: login-ecr
|
||||
uses: aws-actions/amazon-ecr-login@v1
|
||||
|
||||
- name: Get commit hash
|
||||
id: get-commit-hash
|
||||
run: echo "::set-output name=commit-hash::$(git rev-parse --short HEAD)"
|
||||
|
||||
- name: Get timestamp
|
||||
id: get-timestamp
|
||||
run: echo "::set-output name=timestamp::$(date +'%Y-%m-%d-%H-%M')"
|
||||
|
||||
- name: Build, tag, and push the image to Amazon ECR
|
||||
id: build-image
|
||||
env:
|
||||
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
|
||||
ECR_REPOSITORY: ${{ secrets.REPO_NAME }}
|
||||
IMAGE_TAG: ${{ steps.get-commit-hash.outputs.commit-hash }}-${{ steps.get-timestamp.outputs.timestamp }}
|
||||
run: |
|
||||
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
|
||||
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
|
||||
|
|
@ -11,4 +11,10 @@ Cargo.lock
|
|||
.DS_Store
|
||||
.idea
|
||||
|
||||
*.properties
|
||||
*.properties
|
||||
.vscode/launch.json
|
||||
*.cer
|
||||
*.pem
|
||||
.vscode/settings.json
|
||||
*.dylib
|
||||
config_file.toml
|
||||
|
|
|
|||
41
Cargo.toml
41
Cargo.toml
|
|
@ -1,23 +1,26 @@
|
|||
[package]
|
||||
name = "rust-chat-server"
|
||||
version = "0.1.0"
|
||||
authors = ["Mitchell <mitchellhardie1@gmail.com>"]
|
||||
edition = "2018"
|
||||
[workspace]
|
||||
members = [
|
||||
'foundation',
|
||||
'server',
|
||||
'protocol',
|
||||
'client',
|
||||
]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[workspace.dependencies]
|
||||
# common data types
|
||||
uuid = {version = "1.1.2", features = ["serde", "v4"]}
|
||||
|
||||
[dependencies]
|
||||
regex = "1"
|
||||
crossbeam = "0.7"
|
||||
parking_lot = "0.10"
|
||||
crossbeam-channel = "0.4"
|
||||
crossbeam-utils = "0.7"
|
||||
crossbeam-queue = "0.2"
|
||||
dashmap = "3.11.4"
|
||||
async-std = "1.6.2"
|
||||
# maths
|
||||
rand = "0.8.5"
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 0
|
||||
# serialisation
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
# async tokio
|
||||
tokio = { version = "1.9.0", features = ["full"] }
|
||||
|
||||
# protobuf
|
||||
bytes = "1.6.0"
|
||||
prost = "0.12"
|
||||
prost-build = { version = "0.12" }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
# First stage: build the server file.
|
||||
FROM rust:alpine AS build
|
||||
|
||||
# Build dependencies
|
||||
RUN apk add musl-dev
|
||||
RUN apk add openssl-dev
|
||||
RUN apk add protobuf
|
||||
|
||||
COPY . .
|
||||
RUN cargo build --release --bin server
|
||||
|
||||
FROM alpine:latest AS exec
|
||||
|
||||
RUN apk add openssl-dev
|
||||
|
||||
COPY --from=build ./target/release/server /server/server
|
||||
|
||||
EXPOSE 5600/tcp
|
||||
EXPOSE 6500/tcp
|
||||
|
||||
CMD ["/server/server"]
|
||||
28
LICENSE
28
LICENSE
|
|
@ -42,7 +42,7 @@ know their rights.
|
|||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
that there is No warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
|
@ -98,14 +98,14 @@ public, and in some countries other activities as well.
|
|||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
a computer network, with No transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
tells the user that there is No warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
work under this License, and how to View a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
|
|
@ -202,7 +202,7 @@ non-permissive terms added in accord with section 7 apply to the code;
|
|||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
You may charge any price or No price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
|
@ -223,7 +223,7 @@ terms of section 4, provided that you also meet all of these conditions:
|
|||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
regardless of how they are packaged. This License gives No
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
|
|
@ -258,13 +258,13 @@ in one of these ways:
|
|||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
Model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
medium customarily used for software interchange, for a price No
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
Corresponding Source from a network server at No charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
|
|
@ -274,7 +274,7 @@ in one of these ways:
|
|||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
Corresponding Source in the same way through the same place at No
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
|
|
@ -287,7 +287,7 @@ in one of these ways:
|
|||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
Source of the work are being offered to the general public at No
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
|
|
@ -312,7 +312,7 @@ procedures, authorization keys, or other information required to install
|
|||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
code is in No case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
|
|
@ -337,7 +337,7 @@ protocols for communication across the network.
|
|||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
source code form), and must require No special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
|
@ -582,7 +582,7 @@ public statement of acceptance of a version permanently authorizes you
|
|||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
permissions. However, No additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
|
|
|
|||
33
README.md
33
README.md
|
|
@ -1 +1,32 @@
|
|||
# rust-chat-server
|
||||
# Rust-chat-server
|
||||
|
||||
A Chat server writen in rust to allow communication between peers.
|
||||
|
||||
---
|
||||
|
||||
## Features:
|
||||
- implemented:
|
||||
- json based API.
|
||||
- Server introspection.
|
||||
- Peer discovery.
|
||||
- sending messages to connected clients.
|
||||
-
|
||||
- todo:
|
||||
- Encryption to server.
|
||||
- server to server meshing.
|
||||
- asynchronous client managment instead of threaded approach.
|
||||
|
||||
## Goals:
|
||||
- Learn the rust programming lanaguage.
|
||||
- Ownership: how that affects normal programming styles.
|
||||
- Borrowing and references: how this affects shared state.
|
||||
- Lifetimes: how this affects data retention and sharing.
|
||||
- Learn how to create networked programs.
|
||||
- Application level protocol: how to get two programs to communicate via TCP sockets.
|
||||
- Socket handling: Discovering ways to handle multiple socket connections without affecting performance.
|
||||
- Learn common encryption protocols.
|
||||
- Adding support for encrypted sockets.
|
||||
- Pros and cons of symetric and asymetric encryption.
|
||||
- resolving common encryption flaws
|
||||
|
||||
> Questions: For questions please add a issue with the question label. It will eventually be responded to
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
[package]
|
||||
name = "client"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
uuid.workspace = true
|
||||
tokio.workspace = true
|
||||
cursive = "0.20.0"
|
||||
rand.workspace = true
|
||||
|
||||
bytes.workspace = true
|
||||
prost.workspace = true
|
||||
|
||||
foundation = { path = '../foundation' }
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
println!("Please dont use this");
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# Chat Kit
|
||||
|
||||
This is a self hosted, distributed shat system.
|
||||
It derives a lot of ideas from Discord, IRC and RCS.
|
||||
|
||||
This repository contains a couple of crates.
|
||||
- Protocol: The protocol message structures
|
||||
- Foundation: Shared structures and functions utilised in the server and client crate.
|
||||
- Server: The server that accepts client connections and manages state between them.
|
||||
- Client: A basic terminal client, used for testing and will be unstable.
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
[package]
|
||||
name = "foundation"
|
||||
version = "0.1.0"
|
||||
authors = ["Mitchell <mitchellhardie1@gmail.com>","michael-bailey <mickyb18a@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[lib]
|
||||
|
||||
[dependencies]
|
||||
chrono = {version = "0.4", features = ["serde", "rustc-serialize"] }
|
||||
async-trait = "0.1.52"
|
||||
regex = "1"
|
||||
crossbeam = "0.8.0"
|
||||
crossbeam-channel = "0.5.0"
|
||||
crossbeam-queue = "0.3.1"
|
||||
rayon = "1.2.0"
|
||||
zeroize = "1.1.0"
|
||||
log = "0.4"
|
||||
url = "2.2.0"
|
||||
futures = "0.3.16"
|
||||
serde_json = "1.0"
|
||||
openssl = "0.10"
|
||||
uuid = {version = "1.1.2", features = ["serde", "v4"]}
|
||||
tokio = { version = "1.9.0", features = ["full"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
prost.workspace = true
|
||||
protocol = { path = '../protocol' }
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
use tokio::task::JoinHandle;
|
||||
|
||||
use crate::client::server_writer_connection::ServerWriterConnection;
|
||||
|
||||
pub mod network_connection;
|
||||
pub mod server_reader_connection;
|
||||
pub mod server_writer_connection;
|
||||
|
||||
pub enum NetworkState {
|
||||
Disconnected,
|
||||
Connection {
|
||||
reader_handle: JoinHandle<()>,
|
||||
writer: ServerWriterConnection,
|
||||
},
|
||||
}
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
use std::{io, net::SocketAddr};
|
||||
|
||||
use protocol::prelude::{
|
||||
network_client_message,
|
||||
network_server_message,
|
||||
Connect,
|
||||
GetInfo,
|
||||
Info,
|
||||
NetworkClientMessage,
|
||||
NetworkServerMessage,
|
||||
Request,
|
||||
};
|
||||
use tokio::{io::split, net::TcpStream};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
client::{
|
||||
server_reader_connection::ServerReaderConnection,
|
||||
server_writer_connection::ServerWriterConnection,
|
||||
},
|
||||
networking::protobuf::{read_message, write_message},
|
||||
};
|
||||
|
||||
/// # NetworkConnection
|
||||
/// encapsulates the state of the network connection
|
||||
/// will connect to a server and ensure it is usinghte protobuf protocol
|
||||
///
|
||||
/// you can then either get info or connect to the server
|
||||
pub struct NetworkConnection {
|
||||
pub(super) stream: TcpStream,
|
||||
}
|
||||
|
||||
impl NetworkConnection {
|
||||
pub async fn connect(address: SocketAddr) -> io::Result<Self> {
|
||||
let mut stream = TcpStream::connect(address).await.unwrap();
|
||||
|
||||
let msg =
|
||||
read_message::<NetworkServerMessage, TcpStream>(&mut stream).await?;
|
||||
|
||||
let NetworkServerMessage {
|
||||
message: Some(network_server_message::Message::Request(Request {})),
|
||||
} = msg
|
||||
else {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"Received invalid start message from server",
|
||||
));
|
||||
};
|
||||
|
||||
Ok(Self { stream })
|
||||
}
|
||||
|
||||
/// Will consume the connection, and fetch the servers info.
|
||||
pub async fn send_get_info(mut self) -> io::Result<Info> {
|
||||
_ = write_message(
|
||||
&mut self.stream,
|
||||
NetworkClientMessage {
|
||||
message: Some(network_client_message::Message::GetInfo(GetInfo {})),
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
let message =
|
||||
read_message::<NetworkServerMessage, TcpStream>(&mut self.stream).await?;
|
||||
|
||||
let NetworkServerMessage {
|
||||
message: Some(network_server_message::Message::GotInfo(msg)),
|
||||
} = message
|
||||
else {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"sent for info got different message back",
|
||||
));
|
||||
};
|
||||
|
||||
Ok(msg)
|
||||
}
|
||||
|
||||
/// consumes this struct and returns a tuple of the sernding and receiving ahlfs of teh connected conneciton
|
||||
pub async fn send_connect(
|
||||
mut self,
|
||||
uuid: Uuid,
|
||||
username: String,
|
||||
) -> io::Result<(ServerWriterConnection, ServerReaderConnection)> {
|
||||
_ = write_message(
|
||||
&mut self.stream,
|
||||
NetworkClientMessage {
|
||||
message: Some(network_client_message::Message::Connect(Connect {
|
||||
username,
|
||||
uuid: uuid.to_string(),
|
||||
})),
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
let message =
|
||||
read_message::<NetworkServerMessage, TcpStream>(&mut self.stream).await?;
|
||||
|
||||
let NetworkServerMessage {
|
||||
message: Some(network_server_message::Message::Connected(_)),
|
||||
} = message
|
||||
else {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"sent connect got different message back or failed to connect",
|
||||
));
|
||||
};
|
||||
|
||||
Ok(self.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NetworkConnection>
|
||||
for (ServerWriterConnection, ServerReaderConnection)
|
||||
{
|
||||
fn from(value: NetworkConnection) -> Self {
|
||||
let (read_half, write_half) = split(value.stream);
|
||||
(
|
||||
ServerWriterConnection::new(write_half),
|
||||
ServerReaderConnection::new(read_half),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
use std::io;
|
||||
|
||||
use protocol::prelude::ConnectedServerMessage;
|
||||
use tokio::{io::ReadHalf, net::TcpStream};
|
||||
|
||||
use crate::networking::protobuf::read_message;
|
||||
|
||||
pub struct ServerReaderConnection {
|
||||
reader: ReadHalf<TcpStream>,
|
||||
}
|
||||
|
||||
impl ServerReaderConnection {
|
||||
pub(crate) fn new(read_half: ReadHalf<TcpStream>) -> Self {
|
||||
Self { reader: read_half }
|
||||
}
|
||||
|
||||
// move to other one
|
||||
pub async fn get_message(&mut self) -> io::Result<ConnectedServerMessage> {
|
||||
let message = read_message::<ConnectedServerMessage, ReadHalf<TcpStream>>(
|
||||
&mut self.reader,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
Ok(message)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
use tokio::{io::WriteHalf, net::TcpStream};
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct ServerWriterConnection {
|
||||
writer: WriteHalf<TcpStream>,
|
||||
}
|
||||
|
||||
impl ServerWriterConnection {
|
||||
pub(crate) fn new(writer: WriteHalf<TcpStream>) -> Self {
|
||||
Self { writer }
|
||||
}
|
||||
|
||||
pub async fn request_clients(&mut self) {}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
pub mod client;
|
||||
pub mod messages;
|
||||
pub mod models;
|
||||
pub mod networking;
|
||||
pub mod prelude;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
/**
|
||||
* # ClientDetails.
|
||||
* This defines the fileds a client would want to send when connecitng
|
||||
* uuid: the unique id of the user.
|
||||
* username: the users user name.
|
||||
* address: the ip address of the connected user.
|
||||
* public_key: the public key used when sending messages to the user.
|
||||
*/
|
||||
#[derive(Deserialize, Serialize, Debug, Clone, Default)]
|
||||
pub struct ClientDetails {
|
||||
pub uuid: Uuid,
|
||||
pub username: String,
|
||||
pub address: String,
|
||||
pub public_key: Option<Vec<u8>>,
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{models::message::Message, ClientDetails};
|
||||
|
||||
/// This enum defined the message that the server will receive from a client
|
||||
/// This uses the serde library to transform to and from json.
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum ClientStreamIn {
|
||||
GetClients,
|
||||
GetMessages,
|
||||
|
||||
SendMessage { to: Uuid, content: String },
|
||||
SendGlobalMessage { content: String },
|
||||
|
||||
Disconnect,
|
||||
}
|
||||
|
||||
/// This enum defined the message that the server will send to a client
|
||||
/// This uses the serde library to transform to and from json.
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum ClientStreamOut {
|
||||
Connected,
|
||||
|
||||
// get reequest messages
|
||||
ConnectedClients { clients: Vec<ClientDetails> },
|
||||
GlobalChatMessages { messages: Vec<Message> },
|
||||
|
||||
// event messges
|
||||
UserMessage { from: Uuid, content: String },
|
||||
GlobalMessage { from: Uuid, content: String },
|
||||
|
||||
ClientConnected { id: Uuid, username: String },
|
||||
ClientRemoved { id: Uuid },
|
||||
|
||||
Disconnected,
|
||||
|
||||
// error cases
|
||||
Error { msg: String },
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
pub mod client;
|
||||
pub mod network;
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Message the server will receive from a socket
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum NetworkSockIn {
|
||||
Info,
|
||||
Connect {
|
||||
uuid: Uuid,
|
||||
username: String,
|
||||
address: String,
|
||||
},
|
||||
}
|
||||
|
||||
/// Message the server will send through a socket
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum NetworkSockOut {
|
||||
Request,
|
||||
|
||||
GotInfo {
|
||||
server_name: String,
|
||||
server_owner: String,
|
||||
},
|
||||
Connected,
|
||||
|
||||
Error,
|
||||
}
|
||||
|
||||
impl PartialEq for NetworkSockOut {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(NetworkSockOut::Request, NetworkSockOut::Request) => true,
|
||||
(
|
||||
NetworkSockOut::GotInfo {
|
||||
server_name,
|
||||
server_owner,
|
||||
},
|
||||
NetworkSockOut::GotInfo {
|
||||
server_owner: owner_other,
|
||||
server_name: name_other,
|
||||
},
|
||||
) => server_name == name_other && server_owner == owner_other,
|
||||
(NetworkSockOut::Connected, NetworkSockOut::Connected) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
use chrono::{DateTime, Local};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Message {
|
||||
pub id: Uuid,
|
||||
pub from: Uuid,
|
||||
pub content: String,
|
||||
pub time: DateTime<Local>,
|
||||
}
|
||||
|
||||
impl Message {
|
||||
pub fn new(from: Uuid, content: String) -> Self {
|
||||
Self {
|
||||
id: Uuid::new_v4(),
|
||||
from,
|
||||
content,
|
||||
time: Local::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
pub mod message;
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
use std::io;
|
||||
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
||||
|
||||
pub async fn write_message<S, M>(stream: &mut S, message: M)
|
||||
where
|
||||
S: AsyncWrite + AsyncWriteExt + Unpin,
|
||||
M: Serialize,
|
||||
{
|
||||
let mut message = serde_json::to_string(&message).unwrap();
|
||||
message.push('\n');
|
||||
_ = stream.write(message.as_bytes()).await;
|
||||
}
|
||||
|
||||
// todo: Handle error properly
|
||||
pub async fn read_message<S, M>(stream: &mut S) -> io::Result<M>
|
||||
where
|
||||
S: AsyncRead + AsyncReadExt + Unpin,
|
||||
M: DeserializeOwned,
|
||||
{
|
||||
let string = read_line(stream).await?;
|
||||
Ok(serde_json::from_str(&string).unwrap())
|
||||
}
|
||||
|
||||
#[allow(clippy::redundant_guards, clippy::needless_range_loop)]
|
||||
async fn read_line<S>(stream: &mut S) -> Result<String, std::io::Error>
|
||||
where
|
||||
S: AsyncRead + AsyncReadExt + Unpin,
|
||||
{
|
||||
let mut buf = vec![0; 1024];
|
||||
let mut newline_found = false;
|
||||
let mut result = Vec::new();
|
||||
loop {
|
||||
let n = match stream.read(&mut buf).await {
|
||||
Ok(n) if n == 0 => return Ok(String::from_utf8(result).unwrap()),
|
||||
Ok(n) => n,
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
for i in 0..n {
|
||||
if buf[i] == b'\n' {
|
||||
newline_found = true;
|
||||
break;
|
||||
}
|
||||
result.push(buf[i]);
|
||||
}
|
||||
if newline_found {
|
||||
return Ok(String::from_utf8(result).unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
pub mod json;
|
||||
pub mod protobuf;
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
use std::io::{self, ErrorKind};
|
||||
|
||||
use prost::{
|
||||
bytes::{BufMut, Bytes, BytesMut},
|
||||
Message,
|
||||
};
|
||||
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
||||
|
||||
pub async fn write_message<T, S>(stream: &mut S, message: T) -> io::Result<()>
|
||||
where
|
||||
T: Message + Default,
|
||||
S: AsyncWrite + AsyncWriteExt + Unpin,
|
||||
{
|
||||
let message = encode_message::<T>(&message)?;
|
||||
stream.write_all(&message).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn encode_message<T>(msg: &T) -> io::Result<Bytes>
|
||||
where
|
||||
T: Message,
|
||||
{
|
||||
let length = msg.encoded_len();
|
||||
let mut buffer = BytesMut::with_capacity(4 + length);
|
||||
buffer.put_u32(length as u32);
|
||||
let encode_result = msg.encode(&mut buffer);
|
||||
if let Err(err) = encode_result {
|
||||
return Err(io::Error::new(
|
||||
ErrorKind::InvalidInput,
|
||||
format!("message encoding failed: {:?}", err),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(buffer.into())
|
||||
}
|
||||
|
||||
pub async fn read_message<T, S>(stream: &mut S) -> io::Result<T>
|
||||
where
|
||||
T: Message + Default,
|
||||
S: AsyncRead + AsyncReadExt + Unpin,
|
||||
{
|
||||
let size = stream.read_u32().await?;
|
||||
|
||||
let mut buffer = BytesMut::with_capacity(size as usize);
|
||||
unsafe { buffer.set_len(size as usize) };
|
||||
|
||||
stream.read_exact(&mut buffer).await?;
|
||||
|
||||
let message = decode_message::<T>(buffer.into())?;
|
||||
|
||||
Ok(message)
|
||||
}
|
||||
|
||||
pub fn decode_message<T>(buffer: Bytes) -> io::Result<T>
|
||||
where
|
||||
T: Message + Default,
|
||||
{
|
||||
let msg_result = T::decode(buffer);
|
||||
match msg_result {
|
||||
Ok(msg) => Ok(msg),
|
||||
Err(err) => Err(io::Error::new(
|
||||
ErrorKind::InvalidInput,
|
||||
format!("message decoding failed: {:?}", err),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
pub use protocol::prelude::*;
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
[package]
|
||||
name = "protocol"
|
||||
version = "0.1.0"
|
||||
authors = ["michael-bailey <mickyb18a@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.4"
|
||||
uuid = {version = "1.1.2", features = ["serde", "v4"]}
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
crossbeam = "0.8.0"
|
||||
crossbeam-channel = "0.5.0"
|
||||
zeroize = "1.1.0"
|
||||
tokio = { version = "1.9.0", features = ["full"] }
|
||||
futures = "0.3.16"
|
||||
async-trait = "0.1.52"
|
||||
toml = "0.8.8"
|
||||
|
||||
# prost setup
|
||||
bytes.workspace = true
|
||||
prost.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
prost-build.workspace = true
|
||||
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
use std::io::Result;
|
||||
|
||||
// Use this in build.rs
|
||||
fn main() -> Result<()> {
|
||||
prost_build::compile_protos(
|
||||
&["src/proto/network.proto", "src/proto/connected.proto"],
|
||||
&["src/proto"],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
mod proto;
|
||||
|
||||
pub mod prelude {
|
||||
pub use super::proto::*;
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package chatkit.messages;
|
||||
|
||||
// messages from the client when connected.
|
||||
message ConnectedClientMessage {
|
||||
oneof message {
|
||||
GetClients get_clients = 1;
|
||||
GetGlobalMessages get_global_message = 2;
|
||||
SendGlobalMessage send_global_message = 3;
|
||||
SendPrivateMessage send_private_message = 4;
|
||||
Disconnect disconnect = 5;
|
||||
}
|
||||
}
|
||||
|
||||
message GetClients {}
|
||||
message GetGlobalMessages {}
|
||||
|
||||
message SendGlobalMessage {
|
||||
string content = 1;
|
||||
}
|
||||
|
||||
message SendPrivateMessage {
|
||||
string uuid = 1;
|
||||
string to = 2;
|
||||
string content = 3;
|
||||
}
|
||||
|
||||
message Disconnect {}
|
||||
|
||||
|
||||
// messages from the Server when connected.
|
||||
message ConnectedServerMessage {
|
||||
oneof message {
|
||||
ConnectedClients connected_clients = 1;
|
||||
GlobalMessages global_messages = 2;
|
||||
PrivateMessage private_message = 3;
|
||||
Disconnected disconnected = 4;
|
||||
GlobalMessage global_message = 5;
|
||||
ClientConnected client_connected = 6;
|
||||
ClientDisconnected client_disconnected = 7;
|
||||
}
|
||||
}
|
||||
|
||||
message ConnectedClients {
|
||||
repeated ClientDetails clients = 1;
|
||||
}
|
||||
|
||||
message ClientConnected {
|
||||
ClientDetails details = 1;
|
||||
}
|
||||
|
||||
message ClientDisconnected {
|
||||
string uuid = 1;
|
||||
}
|
||||
|
||||
message ClientDetails {
|
||||
string uuid = 1;
|
||||
string name = 2;
|
||||
string address = 3;
|
||||
}
|
||||
|
||||
message GlobalMessages {
|
||||
repeated GlobalMessage messages = 1;
|
||||
}
|
||||
|
||||
message GlobalMessage {
|
||||
string uuid = 1;
|
||||
string from = 2;
|
||||
string content = 3;
|
||||
}
|
||||
|
||||
message PrivateMessage {
|
||||
string uuid = 1;
|
||||
string from = 2;
|
||||
string content = 3;
|
||||
}
|
||||
|
||||
message Disconnected {
|
||||
string reason = 1;
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
include!(concat!(env!("OUT_DIR"), "/chatkit.messages.rs"));
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package chatkit.messages;
|
||||
|
||||
// Network messages sent from the client.
|
||||
message NetworkClientMessage {
|
||||
oneof message {
|
||||
GetInfo get_info = 1;
|
||||
Connect connect = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message GetInfo {}
|
||||
|
||||
message Connect {
|
||||
string username = 1;
|
||||
string uuid = 2;
|
||||
}
|
||||
|
||||
// Network messages sent from the server.
|
||||
message NetworkServerMessage {
|
||||
oneof message {
|
||||
Request request = 1;
|
||||
Info got_info = 2;
|
||||
Connected connected = 3;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
message Request {}
|
||||
|
||||
message Info {
|
||||
string server_name = 1;
|
||||
string owner = 2;
|
||||
}
|
||||
|
||||
message Connected {}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
max_width = 80
|
||||
hard_tabs = true
|
||||
tab_spaces = 2
|
||||
imports_layout = "HorizontalVertical"
|
||||
imports_granularity = "Crate"
|
||||
merge_imports = true
|
||||
reorder_imports = true
|
||||
group_imports = "StdExternalCrate"
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
[package]
|
||||
name = "server"
|
||||
version = "0.1.0"
|
||||
authors = ["michael-bailey <mickyb18a@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
# [lib]
|
||||
# name = "serverlib"
|
||||
# path = "src/lib.rs"
|
||||
|
||||
# [[bin]]
|
||||
# name = "server"
|
||||
# path = "src/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "server"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.4"
|
||||
clap = {version = "4.4.8", features = ["derive"]}
|
||||
uuid.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
crossbeam = "0.8.0"
|
||||
crossbeam-channel = "0.5.0"
|
||||
zeroize = "1.1.0"
|
||||
openssl = "0.10.33"
|
||||
tokio.workspace = true
|
||||
futures = "0.3.16"
|
||||
async-trait = "0.1.80"
|
||||
actix = "0.13"
|
||||
rhai = {version = "1.7.0"}
|
||||
mlua = { version = "0.9.2", features=["lua54", "async", "serde", "macros", "vendored"] }
|
||||
libloading = "0.8.1"
|
||||
toml = "0.8.8"
|
||||
|
||||
tokio-stream = "0.1.9"
|
||||
|
||||
# protobuf
|
||||
bytes.workspace = true
|
||||
prost.workspace = true
|
||||
|
||||
foundation = {path = '../foundation'}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
use foundation::prelude::GlobalMessage;
|
||||
|
||||
pub struct ChatManager {
|
||||
messages: Vec<GlobalMessage>,
|
||||
}
|
||||
|
||||
impl ChatManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
messages: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_message(&mut self, message: GlobalMessage) {
|
||||
println!("[ChatManager] added new global message {:?}", message);
|
||||
self.messages.push(message);
|
||||
}
|
||||
|
||||
pub fn get_messages(&mut self) -> Vec<GlobalMessage> {
|
||||
println!("[ChatManager] got all messages");
|
||||
self.messages.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ChatManager {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
use std::net::SocketAddr;
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct ClientInfo {
|
||||
uuid: Uuid,
|
||||
username: String,
|
||||
addr: SocketAddr,
|
||||
}
|
||||
|
||||
impl ClientInfo {
|
||||
pub fn new(uuid: Uuid, username: String, addr: SocketAddr) -> Self {
|
||||
Self {
|
||||
uuid,
|
||||
username,
|
||||
addr,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_uuid(&self) -> Uuid {
|
||||
self.uuid
|
||||
}
|
||||
|
||||
pub fn get_username(&self) -> String {
|
||||
self.username.clone()
|
||||
}
|
||||
|
||||
pub fn get_addr(&self) -> SocketAddr {
|
||||
self.addr
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
use foundation::prelude::{ClientDetails, GlobalMessage, PrivateMessage};
|
||||
use tokio::{sync::mpsc::UnboundedSender, task::JoinHandle};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
connection::{
|
||||
client_info::ClientInfo,
|
||||
connection_manager::ConnectionManagerMessage,
|
||||
},
|
||||
network::{ClientWriter, NetworkConnection},
|
||||
};
|
||||
|
||||
pub struct ClientThread {
|
||||
read_task: JoinHandle<()>,
|
||||
writer: Box<dyn ClientWriter>,
|
||||
}
|
||||
|
||||
impl ClientThread {
|
||||
pub async fn new_run(
|
||||
uuid: Uuid,
|
||||
conn: Box<dyn NetworkConnection>,
|
||||
connection_manager_sender: UnboundedSender<ConnectionManagerMessage>,
|
||||
) -> Self {
|
||||
println!("[ClientThread] creating thread");
|
||||
let (writer, reader) = conn.send_connected(uuid).await;
|
||||
|
||||
println!("[ClientThread] creating tasks");
|
||||
ClientThread {
|
||||
read_task: reader.start_run(uuid, connection_manager_sender.clone()),
|
||||
writer,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send_clients(&mut self, clients: Vec<ClientDetails>) {
|
||||
self.writer.send_clients(clients).await
|
||||
}
|
||||
|
||||
pub async fn send_client_joined(&mut self, details: ClientDetails) {
|
||||
self.writer.send_client_joined(details).await;
|
||||
}
|
||||
pub async fn send_client_left(&mut self, uuid: Uuid) {
|
||||
self.writer.send_client_left(uuid).await
|
||||
}
|
||||
|
||||
// todo: link this in with message storage
|
||||
pub(crate) async fn send_global_message(&mut self, message: GlobalMessage) {
|
||||
self.writer.send_global_message(message).await;
|
||||
}
|
||||
|
||||
pub(crate) async fn send_global_messages(
|
||||
&mut self,
|
||||
messages: Vec<GlobalMessage>,
|
||||
) {
|
||||
self.writer.send_global_messages(messages).await;
|
||||
}
|
||||
|
||||
pub(crate) async fn send_disconnected(&mut self) {
|
||||
self.writer.send_disconnect().await
|
||||
}
|
||||
|
||||
pub(crate) async fn send_private_message(
|
||||
&mut self,
|
||||
from: Uuid,
|
||||
uuid: Uuid,
|
||||
content: String,
|
||||
) {
|
||||
self
|
||||
.writer
|
||||
.send_private_message(PrivateMessage {
|
||||
uuid: uuid.to_string(),
|
||||
from: from.to_string(),
|
||||
content,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ClientThread {
|
||||
fn drop(&mut self) {
|
||||
self.read_task.abort();
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ClientMessage {
|
||||
SendClients(Vec<ClientInfo>),
|
||||
SendGlobalMessages(Vec<GlobalMessage>),
|
||||
}
|
||||
|
|
@ -0,0 +1,252 @@
|
|||
use std::{collections::HashMap, net::SocketAddr};
|
||||
|
||||
use foundation::prelude::{ClientDetails, GlobalMessage};
|
||||
use tokio::sync::{
|
||||
mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
|
||||
Mutex,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
connection::{client_info::ClientInfo, client_thread::ClientThread},
|
||||
network::NetworkConnection,
|
||||
server_va::ServerMessages,
|
||||
};
|
||||
|
||||
pub struct ConnectionManager {
|
||||
receiver: Mutex<UnboundedReceiver<ConnectionManagerMessage>>,
|
||||
sender: UnboundedSender<ConnectionManagerMessage>,
|
||||
server_sender: UnboundedSender<ServerMessages>,
|
||||
client_map: HashMap<Uuid, ClientInfo>,
|
||||
client_tasks_map: HashMap<Uuid, ClientThread>,
|
||||
}
|
||||
|
||||
impl ConnectionManager {
|
||||
pub fn new(server_sender: UnboundedSender<ServerMessages>) -> Self {
|
||||
let (tx, rx) = unbounded_channel();
|
||||
Self {
|
||||
client_map: HashMap::new(),
|
||||
client_tasks_map: HashMap::new(),
|
||||
server_sender,
|
||||
receiver: Mutex::new(rx),
|
||||
sender: tx,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(&mut self) {
|
||||
loop {
|
||||
let mut lock = self.receiver.lock().await;
|
||||
let msg = lock.recv().await;
|
||||
drop(lock);
|
||||
|
||||
match msg {
|
||||
Some(ConnectionManagerMessage::AddClient {
|
||||
conn,
|
||||
uuid,
|
||||
username,
|
||||
addr,
|
||||
}) => self.add_client(conn, uuid, username, addr).await,
|
||||
|
||||
Some(ConnectionManagerMessage::Disconnected { uuid }) => {
|
||||
self.remove_client(uuid).await
|
||||
}
|
||||
Some(ConnectionManagerMessage::BroadcastGlobalMessage {
|
||||
from,
|
||||
content,
|
||||
}) => {
|
||||
self.broadcast_global_message(from, content).await;
|
||||
}
|
||||
Some(ConnectionManagerMessage::SendClientsTo { uuid }) => {
|
||||
self.send_clients_to(uuid).await;
|
||||
}
|
||||
Some(ConnectionManagerMessage::SendGlobalMessages { uuid }) => {
|
||||
self.send_global_messages(uuid).await;
|
||||
}
|
||||
|
||||
Some(ConnectionManagerMessage::SendGlobalMessagesTo {
|
||||
uuid,
|
||||
messages,
|
||||
}) => {
|
||||
self.send_global_messages_to(uuid, messages).await;
|
||||
}
|
||||
|
||||
Some(ConnectionManagerMessage::SendPrivateMessage {
|
||||
uuid,
|
||||
from,
|
||||
to,
|
||||
content,
|
||||
}) => {
|
||||
self.send_private_message(to, from, uuid, content).await;
|
||||
}
|
||||
Some(ConnectionManagerMessage::Disconnect { uuid }) => {
|
||||
self.disconnect(uuid).await
|
||||
}
|
||||
None => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn add_client(
|
||||
&mut self,
|
||||
conn: Box<dyn NetworkConnection>,
|
||||
uuid: Uuid,
|
||||
username: String,
|
||||
addr: SocketAddr,
|
||||
) {
|
||||
println!("[ConnectionManager] adding new client");
|
||||
let store = ClientInfo::new(uuid, username.clone(), addr);
|
||||
self.client_map.insert(uuid, store);
|
||||
println!("[ConnectionManager] added client info to map");
|
||||
|
||||
let thread = ClientThread::new_run(uuid, conn, self.sender.clone()).await;
|
||||
self.client_tasks_map.insert(uuid, thread);
|
||||
println!("[ConnectionManager] created running thread for new clinet");
|
||||
|
||||
for c in self.client_tasks_map.iter_mut() {
|
||||
c.1
|
||||
.send_client_joined(ClientDetails {
|
||||
uuid: uuid.to_string(),
|
||||
name: username.clone(),
|
||||
address: addr.to_string(),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn remove_client(&mut self, uuid: Uuid) {
|
||||
println!("[ConnectionManager] removing {}", uuid);
|
||||
self.client_map.remove(&uuid);
|
||||
self.client_tasks_map.remove(&uuid);
|
||||
|
||||
for c in self.client_tasks_map.iter_mut() {
|
||||
c.1.send_client_left(uuid).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_clients_to(&mut self, uuid: Uuid) {
|
||||
let clients = self
|
||||
.client_map
|
||||
.values()
|
||||
.cloned()
|
||||
.map(|c| foundation::prelude::ClientDetails {
|
||||
uuid: c.get_uuid().to_string(),
|
||||
name: c.get_username(),
|
||||
address: c.get_addr().to_string(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
let t = self.client_tasks_map.get_mut(&uuid);
|
||||
let Some(t) = t else {
|
||||
return;
|
||||
};
|
||||
|
||||
println!("[ConnectionManager] sending client list to {:?}", clients);
|
||||
|
||||
t.send_clients(clients).await;
|
||||
}
|
||||
|
||||
async fn broadcast_global_message(&mut self, from: Uuid, content: String) {
|
||||
let message = GlobalMessage {
|
||||
uuid: Uuid::new_v4().to_string(),
|
||||
from: from.to_string(),
|
||||
content,
|
||||
};
|
||||
_ = self
|
||||
.server_sender
|
||||
.send(ServerMessages::AddGlobalMessage(message.clone()));
|
||||
for c in self.client_tasks_map.iter_mut() {
|
||||
c.1.send_global_message(message.clone()).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_global_messages(&mut self, uuid: Uuid) {
|
||||
_ = self
|
||||
.server_sender
|
||||
.send(ServerMessages::SendGlobalMessages(uuid));
|
||||
}
|
||||
|
||||
async fn send_global_messages_to(
|
||||
&mut self,
|
||||
uuid: Uuid,
|
||||
messages: Vec<GlobalMessage>,
|
||||
) {
|
||||
let t = self.client_tasks_map.get_mut(&uuid);
|
||||
let Some(t) = t else {
|
||||
return;
|
||||
};
|
||||
|
||||
t.send_global_messages(messages).await;
|
||||
}
|
||||
|
||||
async fn send_private_message(
|
||||
&mut self,
|
||||
to: Uuid,
|
||||
from: Uuid,
|
||||
uuid: Uuid,
|
||||
content: String,
|
||||
) {
|
||||
let t = self.client_tasks_map.get_mut(&to);
|
||||
let Some(t) = t else {
|
||||
return;
|
||||
};
|
||||
|
||||
t.send_private_message(from, uuid, content).await
|
||||
}
|
||||
|
||||
async fn disconnect(&mut self, uuid: Uuid) {
|
||||
let t = self.client_tasks_map.get_mut(&uuid);
|
||||
let Some(t) = t else {
|
||||
return;
|
||||
};
|
||||
|
||||
t.send_disconnected().await;
|
||||
}
|
||||
|
||||
pub fn get_sender(&self) -> UnboundedSender<ConnectionManagerMessage> {
|
||||
self.sender.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ConnectionManagerMessage {
|
||||
// server messages
|
||||
AddClient {
|
||||
conn: Box<dyn NetworkConnection + 'static>,
|
||||
uuid: Uuid,
|
||||
username: String,
|
||||
addr: SocketAddr,
|
||||
},
|
||||
|
||||
// client thread messages
|
||||
SendClientsTo {
|
||||
uuid: Uuid,
|
||||
},
|
||||
|
||||
SendGlobalMessages {
|
||||
uuid: Uuid,
|
||||
},
|
||||
|
||||
SendGlobalMessagesTo {
|
||||
uuid: Uuid,
|
||||
messages: Vec<GlobalMessage>,
|
||||
},
|
||||
|
||||
BroadcastGlobalMessage {
|
||||
from: Uuid,
|
||||
content: String,
|
||||
},
|
||||
|
||||
SendPrivateMessage {
|
||||
uuid: Uuid,
|
||||
from: Uuid,
|
||||
to: Uuid,
|
||||
content: String,
|
||||
},
|
||||
|
||||
Disconnect {
|
||||
uuid: Uuid,
|
||||
},
|
||||
|
||||
Disconnected {
|
||||
uuid: Uuid,
|
||||
},
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
pub mod client_info;
|
||||
pub mod client_thread;
|
||||
pub mod connection_manager;
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
//! This is the main module of the actix server.
|
||||
//! It starts the server and sleeps for the remainder of the program
|
||||
|
||||
pub mod network;
|
||||
|
||||
pub mod chat;
|
||||
pub mod connection;
|
||||
pub mod os_signal_manager;
|
||||
pub mod server_va;
|
||||
|
||||
use crate::server_va::Server;
|
||||
|
||||
/// The main function
|
||||
#[actix::main()]
|
||||
async fn main() {
|
||||
// creating listeners
|
||||
|
||||
Server::default().run().await;
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
use std::{io, net::SocketAddr};
|
||||
|
||||
use foundation::{
|
||||
messages::client::ClientStreamIn,
|
||||
networking::json::read_message,
|
||||
};
|
||||
use tokio::{io::ReadHalf, net::TcpStream, sync::mpsc::UnboundedSender};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
connection::connection_manager::ConnectionManagerMessage,
|
||||
network::ClientReader,
|
||||
};
|
||||
|
||||
pub struct JSONClientReader {
|
||||
reader: ReadHalf<TcpStream>,
|
||||
addr: SocketAddr,
|
||||
uuid: Uuid,
|
||||
}
|
||||
|
||||
impl JSONClientReader {
|
||||
pub fn new(
|
||||
reader: ReadHalf<TcpStream>,
|
||||
addr: SocketAddr,
|
||||
uuid: Uuid,
|
||||
) -> Self {
|
||||
Self { reader, addr, uuid }
|
||||
}
|
||||
|
||||
// move to other one
|
||||
pub async fn get_message(&mut self) -> io::Result<ClientStreamIn> {
|
||||
read_message::<ReadHalf<TcpStream>, ClientStreamIn>(&mut self.reader).await
|
||||
}
|
||||
|
||||
pub fn handle_message(
|
||||
&self,
|
||||
msg: ClientStreamIn,
|
||||
channel: &UnboundedSender<ConnectionManagerMessage>,
|
||||
) {
|
||||
println!("[JSONClientReader:{}] got message", self.addr);
|
||||
|
||||
let uuid = self.uuid;
|
||||
|
||||
_ = match msg {
|
||||
ClientStreamIn::GetClients => {
|
||||
channel.send(ConnectionManagerMessage::SendClientsTo { uuid })
|
||||
}
|
||||
ClientStreamIn::GetMessages => {
|
||||
channel.send(ConnectionManagerMessage::SendGlobalMessages { uuid })
|
||||
}
|
||||
ClientStreamIn::SendMessage { to, content } => {
|
||||
channel.send(ConnectionManagerMessage::SendPrivateMessage {
|
||||
uuid: Uuid::new_v4(),
|
||||
from: uuid,
|
||||
to,
|
||||
content,
|
||||
})
|
||||
}
|
||||
ClientStreamIn::SendGlobalMessage { content } => {
|
||||
channel.send(ConnectionManagerMessage::BroadcastGlobalMessage {
|
||||
from: uuid,
|
||||
content,
|
||||
})
|
||||
}
|
||||
ClientStreamIn::Disconnect => {
|
||||
channel.send(ConnectionManagerMessage::Disconnect { uuid })
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientReader for JSONClientReader {
|
||||
fn start_run(
|
||||
mut self: Box<Self>,
|
||||
uuid: Uuid,
|
||||
channel: UnboundedSender<ConnectionManagerMessage>,
|
||||
) -> tokio::task::JoinHandle<()> {
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
let msg = self.get_message().await;
|
||||
|
||||
let Ok(msg) = msg else {
|
||||
let error = msg.unwrap_err();
|
||||
println!(
|
||||
"[JSONClientReader:{}] errored with '{}' disconnecting",
|
||||
self.addr, error
|
||||
);
|
||||
|
||||
_ = channel.send(ConnectionManagerMessage::Disconnected { uuid });
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
self.handle_message(msg, &channel);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
use std::net::SocketAddr;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use chrono::Local;
|
||||
use foundation::{
|
||||
messages::client::ClientStreamOut,
|
||||
models::message::Message,
|
||||
networking::json::write_message,
|
||||
prelude::{GlobalMessage, PrivateMessage},
|
||||
ClientDetails,
|
||||
};
|
||||
use tokio::{io::WriteHalf, net::TcpStream};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::network::ClientWriter;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct JSONClientWriter {
|
||||
writer: WriteHalf<TcpStream>,
|
||||
addr: SocketAddr,
|
||||
uuid: Uuid,
|
||||
}
|
||||
|
||||
impl JSONClientWriter {
|
||||
pub fn new(
|
||||
writer: WriteHalf<TcpStream>,
|
||||
addr: SocketAddr,
|
||||
uuid: Uuid,
|
||||
) -> Self {
|
||||
Self { writer, addr, uuid }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ClientWriter for JSONClientWriter {
|
||||
async fn send_clients(
|
||||
&mut self,
|
||||
clients: Vec<foundation::prelude::ClientDetails>,
|
||||
) {
|
||||
let message = ClientStreamOut::ConnectedClients {
|
||||
clients: clients
|
||||
.into_iter()
|
||||
.map(|c| ClientDetails {
|
||||
uuid: c.uuid.parse().unwrap(),
|
||||
username: c.name,
|
||||
address: c.address,
|
||||
public_key: None,
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
println!("[JSONClientWriter:{}] sending clients", self.addr);
|
||||
write_message(&mut self.writer, message).await;
|
||||
}
|
||||
|
||||
async fn send_client_joined(
|
||||
&mut self,
|
||||
details: foundation::prelude::ClientDetails,
|
||||
) {
|
||||
let message = ClientStreamOut::ClientConnected {
|
||||
id: details.uuid.parse().unwrap(),
|
||||
username: details.name,
|
||||
};
|
||||
println!(
|
||||
"[JSONClientReader:{}] sending client connected message",
|
||||
self.addr
|
||||
);
|
||||
write_message(&mut self.writer, message).await;
|
||||
}
|
||||
|
||||
async fn send_client_left(&mut self, uuid: Uuid) {
|
||||
let message = ClientStreamOut::ClientRemoved { id: uuid };
|
||||
println!(
|
||||
"[JSONClientReader:{}] sending client connected message",
|
||||
self.addr
|
||||
);
|
||||
write_message(&mut self.writer, message).await;
|
||||
}
|
||||
|
||||
async fn send_global_messages(&mut self, messages: Vec<GlobalMessage>) {
|
||||
let message = ClientStreamOut::GlobalChatMessages {
|
||||
messages: messages
|
||||
.into_iter()
|
||||
.map(|m| Message {
|
||||
id: m.uuid.parse().unwrap(),
|
||||
from: m.from.parse().unwrap(),
|
||||
content: m.content,
|
||||
time: Local::now(),
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
println!("[JSONClientWriter:{}] sending global messages", self.addr);
|
||||
write_message(&mut self.writer, message).await;
|
||||
}
|
||||
|
||||
async fn send_private_message(&mut self, message: PrivateMessage) {
|
||||
let message = ClientStreamOut::UserMessage {
|
||||
from: message.from.parse().unwrap(),
|
||||
content: message.content,
|
||||
};
|
||||
println!("[JSONClientWriter:{}] sending private message", self.addr);
|
||||
write_message(&mut self.writer, message).await;
|
||||
}
|
||||
|
||||
async fn send_global_message(&mut self, message: GlobalMessage) {
|
||||
let message = ClientStreamOut::GlobalMessage {
|
||||
from: message.from.parse().unwrap(),
|
||||
content: message.content,
|
||||
};
|
||||
write_message(&mut self.writer, message).await;
|
||||
}
|
||||
|
||||
async fn send_disconnect(&mut self) {
|
||||
let message = ClientStreamOut::Disconnected;
|
||||
println!("[JSONClientWriter:{}] sending disconnect", self.addr);
|
||||
write_message(&mut self.writer, message).await;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
use async_trait::async_trait;
|
||||
use tokio::{net::TcpListener, sync::mpsc::UnboundedSender, task::JoinHandle};
|
||||
|
||||
use crate::{
|
||||
network::{ConnectionType, NetworkListener},
|
||||
server_va::ServerMessages,
|
||||
};
|
||||
|
||||
/// # Listener Manager
|
||||
/// This stores and awaits for connections from listeners.
|
||||
/// When a connection is received, it is passed to the server
|
||||
pub struct JSONListener {
|
||||
listener: TcpListener,
|
||||
sender: UnboundedSender<ServerMessages>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl NetworkListener for JSONListener {
|
||||
/// Binds listeners and stores them in the ListenerManager
|
||||
async fn new(sender: UnboundedSender<ServerMessages>) -> Self {
|
||||
let address = "0.0.0.0:5600";
|
||||
|
||||
println!("[JSONListener] setting up listeners");
|
||||
let listener = TcpListener::bind(address)
|
||||
.await
|
||||
.expect("[JSONListener] failed to bind to 0.0.0.0:5600");
|
||||
|
||||
Self { listener, sender }
|
||||
}
|
||||
|
||||
async fn run(&self) {
|
||||
loop {
|
||||
println!("[JSONListener] waiting for connection");
|
||||
let accept_protobuf = self.listener.accept().await;
|
||||
|
||||
let Ok((stream, addr)) = accept_protobuf else {
|
||||
println!("[JSONListener] accept failed");
|
||||
continue;
|
||||
};
|
||||
|
||||
let msg = ServerMessages::NewConnection(ConnectionType::JsonConnection(
|
||||
stream, addr,
|
||||
));
|
||||
println!("[JSONListener] passing message to server");
|
||||
_ = self.sender.send(msg);
|
||||
}
|
||||
}
|
||||
|
||||
fn start_run(sender: UnboundedSender<ServerMessages>) -> JoinHandle<()> {
|
||||
tokio::spawn(async move {
|
||||
JSONListener::new(sender).await.run().await;
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
use std::{io, net::SocketAddr};
|
||||
|
||||
use foundation::{
|
||||
messages::network::{NetworkSockIn, NetworkSockOut},
|
||||
networking::json::{read_message, write_message},
|
||||
};
|
||||
use tokio::{io::split, net::TcpStream};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::network::{
|
||||
json::{
|
||||
json_client_reader::JSONClientReader,
|
||||
json_client_writer::JSONClientWriter,
|
||||
},
|
||||
ClientReader,
|
||||
ClientWriter,
|
||||
NetworkConnection,
|
||||
ServerRequest,
|
||||
};
|
||||
|
||||
pub struct JSONNetworkConnection {
|
||||
pub(super) stream: TcpStream,
|
||||
pub(super) addr: SocketAddr,
|
||||
}
|
||||
|
||||
impl JSONNetworkConnection {
|
||||
pub fn new(stream: TcpStream, addr: SocketAddr) -> Self {
|
||||
Self { stream, addr }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl NetworkConnection for JSONNetworkConnection {
|
||||
async fn get_request(&mut self) -> io::Result<ServerRequest> {
|
||||
println!("[JSONNetworkConnection] sending request");
|
||||
|
||||
write_message(&mut self.stream, NetworkSockOut::Request).await;
|
||||
|
||||
println!("[JSONNetworkConnection] waiting for response");
|
||||
|
||||
let request =
|
||||
read_message::<TcpStream, NetworkSockIn>(&mut self.stream).await?;
|
||||
|
||||
println!("[JSONNetworkConnection] returning request");
|
||||
|
||||
match request {
|
||||
NetworkSockIn::Info => Ok(ServerRequest::GetInfo),
|
||||
NetworkSockIn::Connect {
|
||||
uuid,
|
||||
username,
|
||||
address: _,
|
||||
} => Ok(ServerRequest::Connect {
|
||||
username,
|
||||
uuid,
|
||||
addr: self.addr,
|
||||
}),
|
||||
// _ => Ok(ServerRequest::Ignore),
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_info(mut self: Box<Self>, name: String, owner: String) {
|
||||
println!("[JSONNetworkConnection] Sending info to client");
|
||||
write_message(
|
||||
&mut self.stream,
|
||||
NetworkSockOut::GotInfo {
|
||||
server_name: name,
|
||||
server_owner: owner,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
println!("[JSONNetworkConnection] droping connection");
|
||||
}
|
||||
|
||||
async fn send_connected(
|
||||
mut self: Box<Self>,
|
||||
uuid: Uuid,
|
||||
) -> (Box<dyn ClientWriter>, Box<dyn ClientReader>) {
|
||||
write_message(&mut self.stream, NetworkSockOut::Connected).await;
|
||||
|
||||
let (read, write) = split(self.stream);
|
||||
|
||||
let writer = Box::new(JSONClientWriter::new(write, self.addr, uuid));
|
||||
let reader = Box::new(JSONClientReader::new(read, self.addr, uuid));
|
||||
(writer, reader)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
pub mod json_client_reader;
|
||||
pub mod json_client_writer;
|
||||
pub mod json_listener;
|
||||
pub mod json_network_connection;
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
use std::{io, net::SocketAddr};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use foundation::prelude::{ClientDetails, GlobalMessage, PrivateMessage};
|
||||
use tokio::{net::TcpStream, sync::mpsc::UnboundedSender, task::JoinHandle};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
connection::connection_manager::ConnectionManagerMessage,
|
||||
server_va::ServerMessages,
|
||||
};
|
||||
|
||||
pub mod json;
|
||||
pub mod protobuf;
|
||||
|
||||
pub enum ConnectionType {
|
||||
ProtobufConnection(TcpStream, SocketAddr),
|
||||
JsonConnection(TcpStream, SocketAddr),
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait NetworkListener {
|
||||
async fn new(channel: UnboundedSender<ServerMessages>) -> Self;
|
||||
async fn run(&self);
|
||||
fn start_run(sender: UnboundedSender<ServerMessages>) -> JoinHandle<()>;
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait NetworkConnection: Send {
|
||||
async fn get_request(&mut self) -> io::Result<ServerRequest>;
|
||||
async fn send_info(self: Box<Self>, name: String, owner: String);
|
||||
async fn send_connected(
|
||||
self: Box<Self>,
|
||||
uuid: Uuid,
|
||||
) -> (Box<dyn ClientWriter>, Box<dyn ClientReader>);
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait ClientReader: Send {
|
||||
fn start_run(
|
||||
self: Box<Self>,
|
||||
uuid: Uuid,
|
||||
channel: UnboundedSender<ConnectionManagerMessage>,
|
||||
) -> JoinHandle<()>;
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait ClientWriter: Send {
|
||||
async fn send_clients(&mut self, clients: Vec<ClientDetails>);
|
||||
async fn send_global_messages(&mut self, messages: Vec<GlobalMessage>);
|
||||
async fn send_global_message(&mut self, message: GlobalMessage);
|
||||
async fn send_private_message(&mut self, message: PrivateMessage);
|
||||
async fn send_disconnect(&mut self);
|
||||
async fn send_client_joined(&mut self, details: ClientDetails);
|
||||
async fn send_client_left(&mut self, uuid: Uuid);
|
||||
}
|
||||
|
||||
pub enum ServerRequest {
|
||||
GetInfo,
|
||||
Connect {
|
||||
username: String,
|
||||
uuid: uuid::Uuid,
|
||||
addr: SocketAddr,
|
||||
},
|
||||
Ignore,
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
pub mod protobuf_client_reader;
|
||||
pub mod protobuf_client_writer;
|
||||
pub mod protobuf_listener;
|
||||
pub mod protobuf_network_connection;
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
use std::{io, net::SocketAddr};
|
||||
|
||||
use foundation::{
|
||||
networking::protobuf::read_message,
|
||||
prelude::{
|
||||
connected_client_message,
|
||||
ConnectedClientMessage,
|
||||
Disconnect,
|
||||
GetClients,
|
||||
GetGlobalMessages,
|
||||
SendGlobalMessage,
|
||||
SendPrivateMessage,
|
||||
},
|
||||
};
|
||||
use tokio::{io::ReadHalf, net::TcpStream, sync::mpsc::UnboundedSender};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
connection::connection_manager::ConnectionManagerMessage,
|
||||
network::ClientReader,
|
||||
};
|
||||
|
||||
pub struct ProtobufClientReader {
|
||||
reader: ReadHalf<TcpStream>,
|
||||
addr: SocketAddr,
|
||||
uuid: Uuid,
|
||||
}
|
||||
|
||||
impl ProtobufClientReader {
|
||||
pub fn new(
|
||||
reader: ReadHalf<TcpStream>,
|
||||
addr: SocketAddr,
|
||||
uuid: Uuid,
|
||||
) -> Self {
|
||||
Self { reader, addr, uuid }
|
||||
}
|
||||
|
||||
// move to other one
|
||||
pub async fn get_message(&mut self) -> io::Result<ConnectedClientMessage> {
|
||||
read_message::<ConnectedClientMessage, ReadHalf<TcpStream>>(
|
||||
&mut self.reader,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn handle_message(
|
||||
&self,
|
||||
|
||||
msg: ConnectedClientMessage,
|
||||
channel: &UnboundedSender<ConnectionManagerMessage>,
|
||||
) {
|
||||
use connected_client_message::Message;
|
||||
|
||||
println!("[ProtobufClientReader:{}] got message", self.addr);
|
||||
|
||||
let uuid = self.uuid;
|
||||
|
||||
_ = match msg {
|
||||
ConnectedClientMessage {
|
||||
message: Some(Message::GetClients(GetClients {})),
|
||||
} => channel.send(ConnectionManagerMessage::SendClientsTo { uuid }),
|
||||
ConnectedClientMessage {
|
||||
message: Some(Message::GetGlobalMessage(GetGlobalMessages {})),
|
||||
} => channel.send(ConnectionManagerMessage::SendGlobalMessages { uuid }),
|
||||
ConnectedClientMessage {
|
||||
message:
|
||||
Some(Message::SendPrivateMessage(SendPrivateMessage {
|
||||
uuid: message_uuid,
|
||||
to,
|
||||
content,
|
||||
})),
|
||||
} => channel.send(ConnectionManagerMessage::SendPrivateMessage {
|
||||
uuid: message_uuid.parse().unwrap(),
|
||||
from: uuid,
|
||||
to: to.parse().unwrap(),
|
||||
content,
|
||||
}),
|
||||
ConnectedClientMessage {
|
||||
message: Some(Message::SendGlobalMessage(SendGlobalMessage { content })),
|
||||
} => channel.send(ConnectionManagerMessage::BroadcastGlobalMessage {
|
||||
from: uuid,
|
||||
content,
|
||||
}),
|
||||
ConnectedClientMessage {
|
||||
message: Some(Message::Disconnect(Disconnect {})),
|
||||
} => channel.send(ConnectionManagerMessage::Disconnect { uuid }),
|
||||
ConnectedClientMessage { message: None } => unimplemented!(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientReader for ProtobufClientReader {
|
||||
fn start_run(
|
||||
mut self: Box<Self>,
|
||||
uuid: Uuid,
|
||||
channel: UnboundedSender<ConnectionManagerMessage>,
|
||||
) -> tokio::task::JoinHandle<()> {
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
let msg = self.get_message().await;
|
||||
|
||||
let Ok(msg) = msg else {
|
||||
let error = msg.unwrap_err();
|
||||
println!(
|
||||
"[ProtobufClientReader:{}] errored with '{}' disconnecting",
|
||||
self.addr, error
|
||||
);
|
||||
|
||||
_ = channel.send(ConnectionManagerMessage::Disconnected { uuid });
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
self.handle_message(msg, &channel);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,175 @@
|
|||
use std::net::SocketAddr;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use foundation::{
|
||||
networking::protobuf::write_message,
|
||||
prelude::{
|
||||
connected_server_message,
|
||||
ClientConnected,
|
||||
ClientDetails,
|
||||
ClientDisconnected,
|
||||
ConnectedClients,
|
||||
ConnectedServerMessage,
|
||||
Disconnected,
|
||||
GlobalMessage,
|
||||
GlobalMessages,
|
||||
PrivateMessage,
|
||||
},
|
||||
};
|
||||
use tokio::{io::WriteHalf, net::TcpStream};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::network::ClientWriter;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct ProtobufClientWriter {
|
||||
writer: WriteHalf<TcpStream>,
|
||||
addr: SocketAddr,
|
||||
uuid: Uuid,
|
||||
}
|
||||
|
||||
impl ProtobufClientWriter {
|
||||
pub fn new(
|
||||
writer: WriteHalf<TcpStream>,
|
||||
addr: SocketAddr,
|
||||
uuid: Uuid,
|
||||
) -> Self {
|
||||
Self { writer, addr, uuid }
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn send_clients(&mut self, clients: Vec<ClientDetails>) {
|
||||
let message = ConnectedServerMessage {
|
||||
message: Some(connected_server_message::Message::ConnectedClients(
|
||||
ConnectedClients { clients },
|
||||
)),
|
||||
};
|
||||
println!("[ProtobufClientWriter:{}] sending clients", self.addr);
|
||||
write_message(&mut self.writer, message).await.unwrap();
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn send_global_messages(&mut self, messages: Vec<GlobalMessage>) {
|
||||
let message = ConnectedServerMessage {
|
||||
message: Some(connected_server_message::Message::GlobalMessages(
|
||||
GlobalMessages { messages },
|
||||
)),
|
||||
};
|
||||
println!(
|
||||
"[ProtobufClientWriter:{}] sending global messages",
|
||||
self.addr
|
||||
);
|
||||
write_message(&mut self.writer, message).await.unwrap();
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn send_private_message(&mut self, message: PrivateMessage) {
|
||||
let message = ConnectedServerMessage {
|
||||
message: Some(connected_server_message::Message::PrivateMessage(message)),
|
||||
};
|
||||
println!(
|
||||
"[ProtobufClientWriter:{}] sending private message",
|
||||
self.addr
|
||||
);
|
||||
write_message(&mut self.writer, message).await.unwrap();
|
||||
}
|
||||
#[deprecated]
|
||||
pub async fn send_disconnect(&mut self) {
|
||||
let message = ConnectedServerMessage {
|
||||
message: Some(connected_server_message::Message::Disconnected(
|
||||
Disconnected {
|
||||
reason: "shutting down".into(),
|
||||
},
|
||||
)),
|
||||
};
|
||||
println!("[ProtobufClientWriter:{}] sending disconnect", self.addr);
|
||||
write_message(&mut self.writer, message).await.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ClientWriter for ProtobufClientWriter {
|
||||
async fn send_clients(&mut self, clients: Vec<ClientDetails>) {
|
||||
let message = ConnectedServerMessage {
|
||||
message: Some(connected_server_message::Message::ConnectedClients(
|
||||
ConnectedClients { clients },
|
||||
)),
|
||||
};
|
||||
println!("[ProtobufClientWriter:{}] sending clients", self.addr);
|
||||
write_message(&mut self.writer, message).await.unwrap();
|
||||
}
|
||||
|
||||
async fn send_client_joined(&mut self, details: ClientDetails) {
|
||||
let message = ConnectedServerMessage {
|
||||
message: Some(connected_server_message::Message::ClientConnected(
|
||||
ClientConnected {
|
||||
details: Some(details),
|
||||
},
|
||||
)),
|
||||
};
|
||||
println!(
|
||||
"[ProtobufClientWriter:{}] sending client connected message",
|
||||
self.addr
|
||||
);
|
||||
write_message(&mut self.writer, message).await.unwrap();
|
||||
}
|
||||
|
||||
async fn send_client_left(&mut self, uuid: Uuid) {
|
||||
let message = ConnectedServerMessage {
|
||||
message: Some(connected_server_message::Message::ClientDisconnected(
|
||||
ClientDisconnected {
|
||||
uuid: uuid.to_string(),
|
||||
},
|
||||
)),
|
||||
};
|
||||
println!(
|
||||
"[ProtobufClientWriter:{}] sending client connected message",
|
||||
self.addr
|
||||
);
|
||||
write_message(&mut self.writer, message).await.unwrap();
|
||||
}
|
||||
|
||||
async fn send_global_messages(&mut self, messages: Vec<GlobalMessage>) {
|
||||
let message = ConnectedServerMessage {
|
||||
message: Some(connected_server_message::Message::GlobalMessages(
|
||||
GlobalMessages { messages },
|
||||
)),
|
||||
};
|
||||
println!(
|
||||
"[ProtobufClientWriter:{}] sending global messages",
|
||||
self.addr
|
||||
);
|
||||
write_message(&mut self.writer, message).await.unwrap();
|
||||
}
|
||||
|
||||
async fn send_global_message(&mut self, message: GlobalMessage) {
|
||||
let message = ConnectedServerMessage {
|
||||
message: Some(connected_server_message::Message::GlobalMessage(message)),
|
||||
};
|
||||
println!("[ProtobufClientWriter:{}] sending disconnect", self.addr);
|
||||
write_message(&mut self.writer, message).await.unwrap();
|
||||
}
|
||||
|
||||
async fn send_private_message(&mut self, message: PrivateMessage) {
|
||||
let message = ConnectedServerMessage {
|
||||
message: Some(connected_server_message::Message::PrivateMessage(message)),
|
||||
};
|
||||
println!(
|
||||
"[ProtobufClientWriter:{}] sending private message",
|
||||
self.addr
|
||||
);
|
||||
write_message(&mut self.writer, message).await.unwrap();
|
||||
}
|
||||
|
||||
async fn send_disconnect(&mut self) {
|
||||
let message = ConnectedServerMessage {
|
||||
message: Some(connected_server_message::Message::Disconnected(
|
||||
Disconnected {
|
||||
reason: "shutting down".into(),
|
||||
},
|
||||
)),
|
||||
};
|
||||
println!("[ProtobufClientWriter:{}] sending disconnect", self.addr);
|
||||
write_message(&mut self.writer, message).await.unwrap();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
use async_trait::async_trait;
|
||||
use tokio::{net::TcpListener, sync::mpsc::UnboundedSender, task::JoinHandle};
|
||||
|
||||
use crate::{
|
||||
network::{ConnectionType, NetworkListener},
|
||||
server_va::ServerMessages,
|
||||
};
|
||||
|
||||
/// # Listener Manager
|
||||
/// This stores and awaits for connections from listeners.
|
||||
/// When a connection is received, it is passed to the server
|
||||
pub struct ProtobufListener {
|
||||
protobuf_listener: TcpListener,
|
||||
sender: UnboundedSender<ServerMessages>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl NetworkListener for ProtobufListener {
|
||||
/// Binds listeners and stores them in the ListenerManager
|
||||
async fn new(channel: UnboundedSender<ServerMessages>) -> Self {
|
||||
println!("[ProtobufListener] setting up listeners");
|
||||
let protobuf_listener = TcpListener::bind("0.0.0.0:6500")
|
||||
.await
|
||||
.expect("[ProtobufListener] failed to bind to 0.0.0.0:6500");
|
||||
|
||||
Self {
|
||||
protobuf_listener,
|
||||
sender: channel,
|
||||
}
|
||||
}
|
||||
|
||||
async fn run(&self) {
|
||||
loop {
|
||||
println!("[ProtobufListener] waiting for connection");
|
||||
let accept_protobuf = self.protobuf_listener.accept().await;
|
||||
let Ok((stream, addr)) = accept_protobuf else {
|
||||
println!("[ProtobufListener] accept failed");
|
||||
continue;
|
||||
};
|
||||
|
||||
let msg = ServerMessages::NewConnection(
|
||||
ConnectionType::ProtobufConnection(stream, addr),
|
||||
);
|
||||
|
||||
println!("[ProtobufListener] passing message to server");
|
||||
_ = self.sender.send(msg);
|
||||
}
|
||||
}
|
||||
|
||||
fn start_run(sender: UnboundedSender<ServerMessages>) -> JoinHandle<()> {
|
||||
tokio::spawn(async move {
|
||||
ProtobufListener::new(sender).await.run().await;
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
use std::{io, net::SocketAddr};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use foundation::{
|
||||
networking::protobuf::{read_message, write_message},
|
||||
prelude::{
|
||||
network_client_message,
|
||||
network_server_message,
|
||||
Connect,
|
||||
Connected,
|
||||
GetInfo,
|
||||
Info,
|
||||
NetworkClientMessage,
|
||||
NetworkServerMessage,
|
||||
Request,
|
||||
},
|
||||
};
|
||||
use tokio::{io::split, net::TcpStream};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::network::{
|
||||
protobuf::{
|
||||
protobuf_client_reader::ProtobufClientReader,
|
||||
protobuf_client_writer::ProtobufClientWriter,
|
||||
},
|
||||
ClientReader,
|
||||
ClientWriter,
|
||||
NetworkConnection,
|
||||
ServerRequest,
|
||||
};
|
||||
|
||||
pub struct ProtobufNetworkConnection {
|
||||
pub(super) stream: TcpStream,
|
||||
pub(super) addr: SocketAddr,
|
||||
}
|
||||
|
||||
impl ProtobufNetworkConnection {
|
||||
pub fn new(stream: TcpStream, addr: SocketAddr) -> Self {
|
||||
Self { stream, addr }
|
||||
}
|
||||
|
||||
pub async fn get_request(&mut self) -> io::Result<ServerRequest> {
|
||||
let message = NetworkServerMessage {
|
||||
message: Some(network_server_message::Message::Request(Request {})),
|
||||
};
|
||||
|
||||
println!("[ProtobufNetworkConnection] sending request");
|
||||
write_message(&mut self.stream, message).await.unwrap();
|
||||
|
||||
println!("[ProtobufNetworkConnection] waiting for response");
|
||||
let request =
|
||||
read_message::<NetworkClientMessage, TcpStream>(&mut self.stream)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
println!("[ProtobufNetworkConnection] returning request");
|
||||
match request {
|
||||
NetworkClientMessage {
|
||||
message: Some(network_client_message::Message::GetInfo(GetInfo {})),
|
||||
} => Ok(ServerRequest::GetInfo),
|
||||
NetworkClientMessage {
|
||||
message:
|
||||
Some(network_client_message::Message::Connect(Connect {
|
||||
username,
|
||||
uuid,
|
||||
})),
|
||||
} => Ok(ServerRequest::Connect {
|
||||
username,
|
||||
uuid: uuid.parse().unwrap(),
|
||||
addr: self.addr,
|
||||
}),
|
||||
_ => Ok(ServerRequest::Ignore),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send_info(mut self, name: String, owner: String) {
|
||||
let message = NetworkServerMessage {
|
||||
message: Some(network_server_message::Message::GotInfo(Info {
|
||||
server_name: name,
|
||||
owner,
|
||||
})),
|
||||
};
|
||||
println!("[ProtobufNetworkConnection] Sending info to client");
|
||||
write_message(&mut self.stream, message).await.unwrap();
|
||||
println!("[ProtobufNetworkConnection] droping connection");
|
||||
}
|
||||
|
||||
pub async fn send_connected(
|
||||
mut self,
|
||||
uuid: Uuid,
|
||||
) -> (ProtobufClientWriter, ProtobufClientReader) {
|
||||
let message = NetworkServerMessage {
|
||||
message: Some(network_server_message::Message::Connected(Connected {})),
|
||||
};
|
||||
|
||||
write_message(&mut self.stream, message).await.unwrap();
|
||||
|
||||
self.into(uuid)
|
||||
}
|
||||
|
||||
fn into(self, uuid: Uuid) -> (ProtobufClientWriter, ProtobufClientReader) {
|
||||
let (read, write) = split(self.stream);
|
||||
|
||||
let writer = ProtobufClientWriter::new(write, self.addr, uuid);
|
||||
let reader = ProtobufClientReader::new(read, self.addr, uuid);
|
||||
(writer, reader)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl NetworkConnection for ProtobufNetworkConnection {
|
||||
async fn get_request(&mut self) -> io::Result<ServerRequest> {
|
||||
let message = NetworkServerMessage {
|
||||
message: Some(network_server_message::Message::Request(Request {})),
|
||||
};
|
||||
|
||||
println!("[ProtobufNetworkConnection] sending request");
|
||||
write_message(&mut self.stream, message).await.unwrap();
|
||||
|
||||
println!("[ProtobufNetworkConnection] waiting for response");
|
||||
let request =
|
||||
read_message::<NetworkClientMessage, TcpStream>(&mut self.stream)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
println!("[ProtobufNetworkConnection] returning request");
|
||||
match request {
|
||||
NetworkClientMessage {
|
||||
message: Some(network_client_message::Message::GetInfo(GetInfo {})),
|
||||
} => Ok(ServerRequest::GetInfo),
|
||||
NetworkClientMessage {
|
||||
message:
|
||||
Some(network_client_message::Message::Connect(Connect {
|
||||
username,
|
||||
uuid,
|
||||
})),
|
||||
} => Ok(ServerRequest::Connect {
|
||||
username,
|
||||
uuid: uuid.parse().unwrap(),
|
||||
addr: self.addr,
|
||||
}),
|
||||
_ => Ok(ServerRequest::Ignore),
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_info(mut self: Box<Self>, name: String, owner: String) {
|
||||
let message = NetworkServerMessage {
|
||||
message: Some(network_server_message::Message::GotInfo(Info {
|
||||
server_name: name,
|
||||
owner,
|
||||
})),
|
||||
};
|
||||
println!("[ProtobufNetworkConnection] Sending info to client");
|
||||
write_message(&mut self.stream, message).await.unwrap();
|
||||
println!("[ProtobufNetworkConnection] droping connection");
|
||||
}
|
||||
|
||||
async fn send_connected(
|
||||
mut self: Box<Self>,
|
||||
uuid: Uuid,
|
||||
) -> (Box<dyn ClientWriter>, Box<dyn ClientReader>) {
|
||||
let message = NetworkServerMessage {
|
||||
message: Some(network_server_message::Message::Connected(Connected {})),
|
||||
};
|
||||
|
||||
write_message(&mut self.stream, message).await.unwrap();
|
||||
|
||||
let (read, write) = split(self.stream);
|
||||
|
||||
let writer = Box::new(ProtobufClientWriter::new(write, self.addr, uuid));
|
||||
let reader = Box::new(ProtobufClientReader::new(read, self.addr, uuid));
|
||||
(writer, reader)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
use tokio::sync::mpsc::UnboundedSender;
|
||||
|
||||
use crate::server_va::ServerMessages;
|
||||
|
||||
pub struct OSSignalManager {
|
||||
server_channel: UnboundedSender<ServerMessages>,
|
||||
}
|
||||
|
||||
impl OSSignalManager {
|
||||
pub fn new(channel: UnboundedSender<ServerMessages>) -> Self {
|
||||
Self {
|
||||
server_channel: channel,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(&self) {
|
||||
loop {
|
||||
println!("[OSSignalManager] waiting for ctrl+c");
|
||||
tokio::signal::ctrl_c().await.unwrap();
|
||||
println!("[OSSignalManager] ctrl+c received, closing down server");
|
||||
self
|
||||
.server_channel
|
||||
.send(ServerMessages::Exit)
|
||||
.expect("[OSSignalManager] server channel closed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
use crate::plugin::WeakPluginInterface;
|
||||
use foundation::event::Event;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::plugin::plugin_details::PluginDetails;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// # Plugin
|
||||
/// Type alias for plugin objects.
|
||||
pub type Plugin<T> = Arc<dyn IPlugin<T>>;
|
||||
|
||||
/// # GetPluginFn
|
||||
/// This defines the type for getting the plugin struct from a
|
||||
pub type GetPluginFn<T> = fn() -> Plugin<T>;
|
||||
|
||||
/// # Plugin
|
||||
/// This trait defines an interface for plugins to implement.
|
||||
///
|
||||
/// ## Methods
|
||||
/// - details: This returns the details about the plugin.
|
||||
/// - init: Defines the initialisation routine for the plugin.
|
||||
/// - run: defines a routine to be ran like a thread by the plugin manager.
|
||||
/// - deinit: Defines the deinitalisation routine for the plugin
|
||||
#[async_trait::async_trait]
|
||||
pub trait IPlugin<T>: Send + Sync + Debug {
|
||||
fn details(&self) -> PluginDetails;
|
||||
fn on_event(&self, event: Event<T>);
|
||||
|
||||
fn set_interface(&self, interface: WeakPluginInterface<T>);
|
||||
|
||||
fn init(&self);
|
||||
async fn run(&self);
|
||||
fn deinit(&self);
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
mod plugin;
|
||||
mod plugin_details;
|
||||
mod plugin_entry;
|
||||
mod plugin_interface;
|
||||
mod plugin_manager;
|
||||
mod plugin_permissions;
|
||||
|
||||
pub use plugin::{IPlugin, Plugin};
|
||||
pub use plugin_details::PluginDetails;
|
||||
pub(crate) use plugin_interface::PluginInterface;
|
||||
pub use plugin_interface::WeakPluginInterface;
|
||||
pub(crate) use plugin_manager::{PluginManager, PluginManagerMessage};
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
use crate::plugin::plugin_interface::IPluginInterface;
|
||||
use crate::plugin::PluginInterface;
|
||||
use foundation::event::Event;
|
||||
|
||||
use crate::event_type::EventType;
|
||||
|
||||
use foundation::event::EventResult;
|
||||
use foundation::event::IResponder;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Weak;
|
||||
|
||||
use futures::channel::oneshot::Receiver;
|
||||
|
||||
use crate::plugin::plugin::Plugin;
|
||||
use crate::plugin::plugin_entry::PluginExecutionState::{Paused, Running, Stopped};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::time::sleep;
|
||||
|
||||
pub(crate) type PluginEntryObj = Arc<PluginEntry>;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub enum PluginPermission {
|
||||
Read,
|
||||
Write,
|
||||
ReadWrite,
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub(crate) enum PluginExecutionState {
|
||||
Running,
|
||||
Paused,
|
||||
Stopped,
|
||||
}
|
||||
|
||||
/// # PluginEntry
|
||||
/// a wrapper for plugins loaded into the server.
|
||||
/// Used to provide an api for the plugin to use.
|
||||
/// Also acts as gatekeeper to server data with permissions.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct PluginEntry<T>
|
||||
where
|
||||
T: Sync + Send,
|
||||
{
|
||||
server_permission: PluginPermission,
|
||||
network_permission: PluginPermission,
|
||||
client_manager_permission: PluginPermission,
|
||||
client_permission: PluginPermission,
|
||||
|
||||
state: Arc<Mutex<PluginExecutionState>>,
|
||||
|
||||
plugin: Plugin<EventType<'static>>,
|
||||
}
|
||||
|
||||
impl<T> PluginEntry<T>
|
||||
where
|
||||
T: Sync + Send,
|
||||
{
|
||||
pub fn new(plugin: Plugin<EventType>) -> Arc<PluginEntry<T>> {
|
||||
let entry = Arc::new(PluginEntry {
|
||||
server_permission: PluginPermission::None,
|
||||
network_permission: PluginPermission::None,
|
||||
client_manager_permission: PluginPermission::None,
|
||||
client_permission: PluginPermission::None,
|
||||
|
||||
state: Arc::new(Mutex::new(Stopped)),
|
||||
|
||||
plugin: plugin.clone(),
|
||||
});
|
||||
|
||||
let entry_ref = entry.clone() as PluginInterface<T>;
|
||||
|
||||
plugin.set_interface(Arc::downgrade(&entry_ref));
|
||||
entry
|
||||
}
|
||||
|
||||
pub(crate) async fn getState(&self) -> PluginExecutionState {
|
||||
*self.state.lock().await
|
||||
}
|
||||
|
||||
pub fn start(&self) {
|
||||
let cont = self.plugin.clone();
|
||||
let state = self.state.clone();
|
||||
tokio::spawn(async move {
|
||||
let local_state = state.clone();
|
||||
let mut lock = local_state.lock().await;
|
||||
match *lock {
|
||||
Running => (),
|
||||
Paused => {
|
||||
*lock = Running;
|
||||
}
|
||||
Stopped => {
|
||||
tokio::spawn(async move {
|
||||
cont.init();
|
||||
let mut lock = state.lock().await;
|
||||
*lock = Running;
|
||||
loop {
|
||||
match *lock {
|
||||
Running => cont.run().await,
|
||||
Paused => sleep(Duration::new(1, 0)).await,
|
||||
Stopped => break,
|
||||
}
|
||||
}
|
||||
cont.deinit()
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn pause(&self) {
|
||||
let state = self.state.clone();
|
||||
tokio::spawn(async move {
|
||||
let mut lock = state.lock().await;
|
||||
match *lock {
|
||||
Running => {
|
||||
*lock = Paused;
|
||||
}
|
||||
Paused => (),
|
||||
Stopped => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn stop(&self) {
|
||||
let state = self.state.clone();
|
||||
tokio::spawn(async move {
|
||||
let mut lock = state.lock().await;
|
||||
match *lock {
|
||||
Running => {
|
||||
*lock = Stopped;
|
||||
}
|
||||
Paused => {
|
||||
*lock = Stopped;
|
||||
}
|
||||
Stopped => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IPluginInterface<T> for PluginEntry {
|
||||
fn send_event(&self, _event: Event<EventType>) -> Receiver<EventResult> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl IResponder<EventType<'_>> for PluginEntry {
|
||||
fn on_event(&self, event: Event<EventType>) {
|
||||
use EventType::{ClientAdded, Custom, NewConnection};
|
||||
use PluginPermission::{None, Read, ReadWrite, Write};
|
||||
|
||||
match (
|
||||
&event.r#type,
|
||||
&self.network_permission,
|
||||
&self.client_manager_permission,
|
||||
&self.client_permission,
|
||||
&self.server_permission,
|
||||
) {
|
||||
(NewConnection, Read | ReadWrite, _, _, _) => self.plugin.on_event(event),
|
||||
(ClientAdded(id), _, Read | ReadWrite, _, _) => self.plugin.on_event(event),
|
||||
(Custom("ping"), _, _, _, _) => println!("[PluginEntry:on_event] Ping!"),
|
||||
_ => println!("[PluginEntry:on_event] not handled"),
|
||||
};
|
||||
}
|
||||
fn get_next(&self) -> Option<Weak<dyn IResponder<EventType>>> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
use foundation::event::Event;
|
||||
use foundation::event::EventResult;
|
||||
use foundation::event::IResponder;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Weak;
|
||||
|
||||
use futures::channel::oneshot::Receiver;
|
||||
|
||||
pub type WeakPluginInterface<T>
|
||||
where
|
||||
T: Sync + Send,
|
||||
= Weak<dyn IPluginInterface<T>>;
|
||||
|
||||
pub(crate) type PluginInterface<T>
|
||||
where
|
||||
T: Sync + Send,
|
||||
= Arc<dyn IPluginInterface<T>>;
|
||||
|
||||
pub trait IPluginInterface<T>: IResponder<T> + Send + Sync + Debug
|
||||
where
|
||||
T: Sync + Send,
|
||||
{
|
||||
fn send_event(&self, event: Event<T>) -> Receiver<EventResult>;
|
||||
}
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
use std::fs::Metadata;
|
||||
use std::{io::Error, mem, sync::Arc};
|
||||
|
||||
use libloading::Library;
|
||||
|
||||
use tokio::fs::{create_dir, read_dir, DirEntry};
|
||||
use tokio::sync::mpsc::Sender;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use futures::future::join_all;
|
||||
|
||||
use crate::plugin::plugin::GetPluginFn;
|
||||
use crate::plugin::plugin_entry::{PluginEntry, PluginEntryObj};
|
||||
|
||||
pub enum PluginManagerMessage {
|
||||
None,
|
||||
}
|
||||
|
||||
/// # PluginManager
|
||||
/// This struct handles the loading and unloading of plugins in the server
|
||||
///
|
||||
/// ## Attributes
|
||||
/// - plugins: A [Vec] of all loaded plugins
|
||||
/// - server_channel: A [Sender]
|
||||
pub struct PluginManager<Out: 'static>
|
||||
where
|
||||
Out: From<PluginManagerMessage> + Send,
|
||||
{
|
||||
#[allow(dead_code)]
|
||||
plugins: Mutex<Vec<PluginEntryObj>>,
|
||||
|
||||
#[allow(dead_code)]
|
||||
server_channel: Mutex<Sender<Out>>,
|
||||
}
|
||||
|
||||
impl<Out: 'static> PluginManager<Out>
|
||||
where
|
||||
Out: From<PluginManagerMessage> + Send,
|
||||
{
|
||||
/// Creates a new plugin manager with sender.
|
||||
pub fn new(channel: Sender<Out>) -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
plugins: Mutex::new(Vec::new()),
|
||||
server_channel: Mutex::new(channel),
|
||||
})
|
||||
}
|
||||
|
||||
/// Starts loading plugins from the plugins directory.
|
||||
/// If this directory isn't found then create it get created.
|
||||
pub async fn load(&self) -> Result<(), Error> {
|
||||
println!("[PluginManager]: loading plugins");
|
||||
println!(
|
||||
"[PluginManager]: from: {}",
|
||||
std::env::current_dir().unwrap().to_string_lossy()
|
||||
);
|
||||
|
||||
if let Ok(mut plugins) = read_dir("./plugins").await {
|
||||
// Todo: - make this concurrent
|
||||
let mut plugin_vec = vec![];
|
||||
while let Some(next) = plugins.next_entry().await? {
|
||||
println!("{:?}", next);
|
||||
plugin_vec.push(next)
|
||||
}
|
||||
|
||||
// get all entries by extension
|
||||
let entries: Vec<DirEntry> = plugin_vec
|
||||
.into_iter()
|
||||
.filter(|item| item.path().extension().unwrap_or_default() == "dylib")
|
||||
.collect();
|
||||
|
||||
// get entry metadata
|
||||
#[allow(clippy::needless_collect)] // This is a false positive. Collect is needed here
|
||||
let metadata: Vec<Metadata> = join_all(entries.iter().map(|item| item.metadata()))
|
||||
.await
|
||||
.into_iter()
|
||||
.filter_map(|item| item.ok())
|
||||
.collect();
|
||||
|
||||
// convert correct ones to plugins
|
||||
let plugins: Vec<PluginEntryObj> = entries
|
||||
.into_iter()
|
||||
.zip(metadata.into_iter())
|
||||
.filter(|(_item, meta)| meta.is_file())
|
||||
.map(|item| item.0)
|
||||
.map(|item| unsafe {
|
||||
let lib = Library::new(item.path()).unwrap();
|
||||
let plugin_fn = lib.get::<GetPluginFn<()>>("get_plugin".as_ref()).unwrap();
|
||||
PluginEntry::new(plugin_fn())
|
||||
})
|
||||
.collect();
|
||||
|
||||
println!("[PluginManager:load] got plugins: {:?}", plugins);
|
||||
|
||||
let mut self_vec = self.plugins.lock().await;
|
||||
let _ = mem::replace(&mut *self_vec, plugins);
|
||||
} else {
|
||||
create_dir("./plugins").await?;
|
||||
}
|
||||
|
||||
self
|
||||
.plugins
|
||||
.lock()
|
||||
.await
|
||||
.iter()
|
||||
.for_each(|item| item.start());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
use foundation::prelude::GlobalMessage;
|
||||
use tokio::{
|
||||
sync::{
|
||||
mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
|
||||
Mutex,
|
||||
},
|
||||
task::JoinHandle,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
chat::ChatManager,
|
||||
connection::connection_manager::{
|
||||
ConnectionManager,
|
||||
ConnectionManagerMessage,
|
||||
},
|
||||
network::{
|
||||
json::{
|
||||
json_listener::JSONListener,
|
||||
json_network_connection::JSONNetworkConnection,
|
||||
},
|
||||
protobuf::{
|
||||
protobuf_listener::ProtobufListener,
|
||||
protobuf_network_connection::ProtobufNetworkConnection,
|
||||
},
|
||||
ConnectionType,
|
||||
NetworkConnection,
|
||||
NetworkListener,
|
||||
ServerRequest,
|
||||
},
|
||||
os_signal_manager::OSSignalManager,
|
||||
};
|
||||
|
||||
/// # Server
|
||||
/// Manages communication between components in the server
|
||||
/// Main functions being the handling of new connections, and setting them up.
|
||||
pub struct Server {
|
||||
connection_manager_sender: UnboundedSender<ConnectionManagerMessage>,
|
||||
|
||||
chat_manager: ChatManager,
|
||||
|
||||
connection_manager_task: JoinHandle<()>,
|
||||
listener_task: JoinHandle<()>,
|
||||
json_listener_task: JoinHandle<()>,
|
||||
|
||||
os_event_manager_task: JoinHandle<()>,
|
||||
|
||||
receiver: Mutex<UnboundedReceiver<ServerMessages>>,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
/// Loops the future, reading messages from the servers channel.
|
||||
/// if exit is received, deconstructs all sub-tasks and exits the loop.
|
||||
pub async fn run(&mut self) {
|
||||
loop {
|
||||
let mut lock = self.receiver.lock().await;
|
||||
let msg = lock.recv().await;
|
||||
drop(lock);
|
||||
|
||||
match msg {
|
||||
Some(ServerMessages::Exit) | None => {
|
||||
println!("[Server] Shutting down");
|
||||
self.shutdown();
|
||||
return;
|
||||
}
|
||||
Some(ServerMessages::NewConnection(
|
||||
ConnectionType::ProtobufConnection(stream, addr),
|
||||
)) => {
|
||||
let conn = Box::new(ProtobufNetworkConnection::new(stream, addr));
|
||||
println!("[Server] New protobuf connection");
|
||||
self.handle_protobuf_connection(conn).await;
|
||||
}
|
||||
Some(ServerMessages::NewConnection(
|
||||
ConnectionType::JsonConnection(stream, addr),
|
||||
)) => {
|
||||
let conn = Box::new(JSONNetworkConnection::new(stream, addr));
|
||||
println!("[Server] New json connection");
|
||||
self.handle_protobuf_connection(conn).await;
|
||||
}
|
||||
Some(ServerMessages::SendGlobalMessages(uuid)) => {
|
||||
let messages = self.chat_manager.get_messages();
|
||||
println!("[Server] Sending Global Messages");
|
||||
_ = self.connection_manager_sender.send(
|
||||
ConnectionManagerMessage::SendGlobalMessagesTo { uuid, messages },
|
||||
);
|
||||
}
|
||||
Some(ServerMessages::AddGlobalMessage(message)) => {
|
||||
self.chat_manager.add_message(message);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_protobuf_connection(
|
||||
&self,
|
||||
mut conn: Box<dyn NetworkConnection>,
|
||||
) {
|
||||
println!("[Server] Getting request");
|
||||
let req = conn.get_request().await;
|
||||
|
||||
let Ok(req) = req else {
|
||||
println!("[Server] Got invalid request");
|
||||
return;
|
||||
};
|
||||
|
||||
match req {
|
||||
ServerRequest::GetInfo => {
|
||||
conn
|
||||
.send_info("test server".into(), "mickyb18a@gmail.com".into())
|
||||
.await
|
||||
}
|
||||
ServerRequest::Connect {
|
||||
username,
|
||||
uuid,
|
||||
addr,
|
||||
} => {
|
||||
println!("[Server] sending connectionn and info to conneciton manager");
|
||||
_ = self.connection_manager_sender.send(
|
||||
ConnectionManagerMessage::AddClient {
|
||||
conn,
|
||||
uuid,
|
||||
username,
|
||||
addr,
|
||||
},
|
||||
);
|
||||
}
|
||||
ServerRequest::Ignore => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn shutdown(&self) {
|
||||
self.os_event_manager_task.abort();
|
||||
self.connection_manager_task.abort();
|
||||
self.json_listener_task.abort();
|
||||
self.listener_task.abort();
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Server {
|
||||
fn default() -> Self {
|
||||
let (tx, rx) = unbounded_channel();
|
||||
let tx1 = tx.clone();
|
||||
let tx2 = tx.clone();
|
||||
let tx3 = tx.clone();
|
||||
let tx4 = tx.clone();
|
||||
|
||||
let os_event_manager_task = tokio::spawn(async move {
|
||||
OSSignalManager::new(tx1).run().await;
|
||||
});
|
||||
|
||||
let listener_task = ProtobufListener::start_run(tx2);
|
||||
let json_listener_task = JSONListener::start_run(tx3);
|
||||
|
||||
let mut connection_manager = ConnectionManager::new(tx4);
|
||||
let connection_manager_sender = connection_manager.get_sender();
|
||||
let connection_manager_task = tokio::spawn(async move {
|
||||
connection_manager.run().await;
|
||||
});
|
||||
|
||||
let chat_manager = ChatManager::new();
|
||||
|
||||
Self {
|
||||
chat_manager,
|
||||
|
||||
os_event_manager_task,
|
||||
connection_manager_task,
|
||||
connection_manager_sender,
|
||||
|
||||
json_listener_task,
|
||||
receiver: Mutex::new(rx),
|
||||
listener_task,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// # ServerMessage
|
||||
/// enum describing all messages that the server can handle
|
||||
pub enum ServerMessages {
|
||||
Exit,
|
||||
AddGlobalMessage(GlobalMessage),
|
||||
SendGlobalMessages(Uuid),
|
||||
NewConnection(ConnectionType),
|
||||
}
|
||||
102
src/lib.rs
102
src/lib.rs
|
|
@ -1,102 +0,0 @@
|
|||
use std::thread;
|
||||
use crossbeam::{unbounded , Sender, Receiver};
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
enum Message {
|
||||
NewJob(Job),
|
||||
Terminate,
|
||||
}
|
||||
|
||||
pub struct ThreadPool{
|
||||
workers: Vec<Worker>,
|
||||
sender: Sender<Message>,
|
||||
}
|
||||
|
||||
type Job = Box<dyn FnOnce() + Send + 'static>;
|
||||
|
||||
impl ThreadPool{
|
||||
/// Create a new ThreadPool.
|
||||
///
|
||||
/// The size is the number of threads in the pool.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// The `new` function will panic if the size is zero.
|
||||
pub fn new(size: usize) -> ThreadPool {
|
||||
assert!(size > 0);
|
||||
|
||||
let (sender, receiver) = unbounded();
|
||||
|
||||
let receiver = Arc::new(Mutex::new(receiver));
|
||||
|
||||
let mut workers = Vec::with_capacity(size);
|
||||
|
||||
for id in 0..size {
|
||||
// create some threads and store them in the vector
|
||||
workers.push(Worker::new(id, Arc::clone(&receiver)));
|
||||
}
|
||||
|
||||
ThreadPool {
|
||||
workers,
|
||||
sender,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute<F>(&self, f: F) where F: FnOnce() + Send + 'static {
|
||||
let job = Box::new(f);
|
||||
|
||||
self.sender.send(Message::NewJob(job)).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
struct Worker {
|
||||
id: usize,
|
||||
thread: Option<thread::JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl Worker {
|
||||
fn new(id: usize, receiver: Arc<Mutex<Receiver<Message>>>) -> Worker {
|
||||
let thread = thread::spawn(move || {
|
||||
loop{
|
||||
let message = receiver.lock().unwrap().recv().unwrap();
|
||||
|
||||
match message {
|
||||
Message::NewJob(job) => {
|
||||
println!("Worker {} got a job; executing.", id);
|
||||
job();
|
||||
},
|
||||
Message::Terminate => {
|
||||
println!("Worker {} was told to terminate.", id);
|
||||
break;
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Worker {
|
||||
id,
|
||||
thread: Some(thread),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ThreadPool {
|
||||
fn drop(&mut self) {
|
||||
println!("Sending terminate message to all workers.");
|
||||
|
||||
for _ in &mut self.workers {
|
||||
self.sender.send(Message::Terminate).unwrap();
|
||||
}
|
||||
|
||||
println!("Shutting down all workers.");
|
||||
|
||||
for worker in &mut self.workers {
|
||||
println!("Shutting down worker {}", worker.id);
|
||||
|
||||
if let Some(thread) = worker.thread.take() {
|
||||
thread.join().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/main.rs
18
src/main.rs
|
|
@ -1,18 +0,0 @@
|
|||
mod server;
|
||||
// mod server_v2;
|
||||
|
||||
use crate::server::client::client_profile::Client;
|
||||
use crate::server::server_profile::Server;
|
||||
use std::net::{TcpStream, TcpListener};
|
||||
use rust_chat_server::ThreadPool;
|
||||
use std::sync::{Arc, Barrier, Mutex};
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn main(){
|
||||
let server_name = String::from("Server-01");
|
||||
let server_address = String::from("0.0.0.0:6000");
|
||||
let server_owner = String::from("noreply@email.com");
|
||||
|
||||
let server = Server::new(&server_name, &server_address, &server_owner);
|
||||
server.start();
|
||||
}
|
||||
|
|
@ -1,189 +0,0 @@
|
|||
extern crate regex;
|
||||
|
||||
use crate::server::server_profile::Server;
|
||||
use crate::server::commands::{Commands};
|
||||
|
||||
use std::net::{Shutdown, TcpStream};
|
||||
use std::sync::Arc;
|
||||
use parking_lot::FairMutex;
|
||||
use std::collections::HashMap;
|
||||
use dashmap::DashMap;
|
||||
use std::io::prelude::*;
|
||||
use std::time::Duration;
|
||||
use regex::Regex;
|
||||
use crossbeam::{channel, Sender, Receiver, TryRecvError};
|
||||
use crossbeam_channel::unbounded;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Client<'client_lifetime> {
|
||||
connected: bool,
|
||||
stream: Arc<TcpStream>,
|
||||
uuid: String,
|
||||
username: String,
|
||||
address: String,
|
||||
server: &'client_lifetime Server<'client_lifetime>,
|
||||
tx_channel: Sender<Commands>,
|
||||
rx_channel: Receiver<Commands>,
|
||||
}
|
||||
|
||||
impl<'a> Client<'a> {
|
||||
pub fn new(server: &'a Server<'a>, stream: Arc<TcpStream>, uuid: &String, username: &String, address: &String) -> Client<'a>{
|
||||
let (tx_channel, rx_channel): (Sender<Commands>, Receiver<Commands>) = unbounded();
|
||||
|
||||
Client {
|
||||
connected: true,
|
||||
stream,
|
||||
uuid: uuid.to_string(),
|
||||
username: username.to_string(),
|
||||
address: address.to_string(),
|
||||
server,
|
||||
tx_channel,
|
||||
rx_channel,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_stream(&self) -> &TcpStream{
|
||||
&self.stream
|
||||
}
|
||||
|
||||
pub fn get_transmitter(&self) -> &Sender<Commands>{
|
||||
&self.tx_channel
|
||||
}
|
||||
|
||||
pub fn get_uuid(&self) -> &String{
|
||||
&self.uuid
|
||||
}
|
||||
|
||||
pub fn get_username(&self) -> &String{
|
||||
&self.username
|
||||
}
|
||||
|
||||
pub fn get_address(&self) -> &String{
|
||||
&self.address
|
||||
}
|
||||
|
||||
pub fn handle_connection(&self){
|
||||
self.stream.set_read_timeout(Some(Duration::from_millis(3000))).unwrap();
|
||||
let mut buffer = [0; 1024];
|
||||
|
||||
while self.connected {
|
||||
match self.rx_channel.try_recv() {
|
||||
/*command is on the channel*/
|
||||
|
||||
Ok(command) => {
|
||||
let a = command.clone();
|
||||
match command {
|
||||
|
||||
Commands::Info(Some(params)) => {
|
||||
self.get_stream().write_all(a.to_string().as_bytes());
|
||||
/* why not do this?
|
||||
*
|
||||
* self.transmit_data(a.to_string().as_str());
|
||||
*/
|
||||
},
|
||||
Commands::Disconnect(None) => {
|
||||
|
||||
},
|
||||
Commands::ClientRemove(Some(params)) => {},
|
||||
Commands::Client(Some(params)) => {
|
||||
self.transmit_data(a.to_string().as_str());
|
||||
todo!()
|
||||
/*
|
||||
* a success message needs to be read and confirmed
|
||||
*/
|
||||
},
|
||||
Commands::Success(data) => {},
|
||||
_ => {},
|
||||
}
|
||||
},
|
||||
/*sender disconnected*/
|
||||
Err(TryRecvError::Disconnected) => {},
|
||||
/*no data available yet*/
|
||||
Err(TryRecvError::Empty) => {},
|
||||
}
|
||||
|
||||
match self.stream.peek(&mut buffer){
|
||||
Ok(_) => {
|
||||
self.get_stream().read(&mut buffer).unwrap();
|
||||
|
||||
let incoming_message = String::from(String::from_utf8_lossy(&buffer));
|
||||
let command = Commands::from(incoming_message.clone());
|
||||
|
||||
println!("Request: {}", &incoming_message);
|
||||
|
||||
match command {
|
||||
Commands::Connect(Some(params)) => todo!(),
|
||||
_ => todo!(),
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
println!("no data peeked");
|
||||
},
|
||||
}
|
||||
}
|
||||
println!("---thread exit---");
|
||||
}
|
||||
|
||||
// deprecated
|
||||
/*
|
||||
pub fn connect(&self, server: &Server, connected_clients: &Arc<Mutex<HashMap<String, Client>>>, data: &HashMap<String, String>){
|
||||
let mut clients_hashmap = connected_clients.lock().unwrap();
|
||||
let uuid = self.get_uuid().to_string();
|
||||
clients_hashmap.insert(uuid, self.clone());
|
||||
std::mem::drop(clients_hashmap);
|
||||
|
||||
let new_client = Commands::Client(data.clone());
|
||||
server.update_all_clients(&new_client);
|
||||
|
||||
self.transmit_success(&String::from(""));
|
||||
}
|
||||
*/
|
||||
|
||||
pub fn disconnect(&mut self){
|
||||
self.stream.shutdown(Shutdown::Both).expect("shutdown call failed");
|
||||
self.connected = false;
|
||||
}
|
||||
|
||||
pub fn transmit_data(&self, data: &str){
|
||||
println!("Transmitting...");
|
||||
println!("data: {}", data);
|
||||
|
||||
self.get_stream().write(data.to_string().as_bytes()).unwrap();
|
||||
self.get_stream().flush().unwrap();
|
||||
}
|
||||
|
||||
// deprecated
|
||||
pub fn confirm_success(&self, buffer: &mut [u8; 1024], data: &String){
|
||||
let success_regex = Regex::new(r###"!success:"###).unwrap();
|
||||
|
||||
let _ = match self.get_stream().read(&mut *buffer) {
|
||||
Err(error) => self.transmit_error(&String::from("")),
|
||||
Ok(success) => {
|
||||
let incoming_message = String::from_utf8_lossy(&buffer[..]);
|
||||
if success_regex.is_match(&incoming_message){
|
||||
println!("success");
|
||||
}else{
|
||||
self.transmit_error(&String::from(""));
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn transmit_success(&self, data: &String){
|
||||
let mut success_message = "!success:".to_string();
|
||||
if !data.is_empty(){
|
||||
success_message.push_str(&" ".to_string());
|
||||
success_message.push_str(&data.to_string());
|
||||
}
|
||||
self.transmit_data(&success_message);
|
||||
}
|
||||
|
||||
fn transmit_error(&self, data: &String){
|
||||
let mut error_message = "!error:".to_string();
|
||||
if !data.is_empty(){
|
||||
error_message.push_str(&" ".to_string());
|
||||
error_message.push_str(&data.to_string());
|
||||
}
|
||||
self.transmit_data(&error_message);
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
pub mod client_profile;
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
use crate::server::client::client_profile::Client;
|
||||
|
||||
use std::sync::Mutex;
|
||||
use std::sync::Arc;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub fn get_client_data(clients_ref: &Arc<Mutex<HashMap<String, Client>>>, data: &HashMap<String, String>) -> String{
|
||||
let clients_hashmap = clients_ref.lock().unwrap();
|
||||
let uuid = data.get("uuid").unwrap();
|
||||
println!("uuid: {}", uuid);
|
||||
|
||||
for (key, value) in clients_hashmap.iter(){
|
||||
println!("{}",key);
|
||||
}
|
||||
let client = clients_hashmap.get(uuid);
|
||||
match client{
|
||||
Some(data) => {
|
||||
let mut message = String::from("!success:");
|
||||
message.push_str(&" uuid:".to_string());
|
||||
message.push_str(&data.get_uuid().to_string());
|
||||
message.push_str(&" host:".to_string());
|
||||
message.push_str(&data.get_address().to_string());
|
||||
message.push_str(&" username:".to_string());
|
||||
message.push_str(&data.get_username().to_string());
|
||||
message
|
||||
},
|
||||
None => String::from("client not online"),
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
use crate::server::client::client_profile::Client;
|
||||
|
||||
pub fn format_client_data(uuid: &String, client: &Client) -> String{
|
||||
["!client: username:",client.get_username(), " uuid:", uuid, " host:\"", client.get_address(), "\""].concat()
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
use crate::server::client::client_profile::Client;
|
||||
|
||||
use std::sync::Mutex;
|
||||
use std::sync::Arc;
|
||||
use std::collections::HashMap;
|
||||
use dashmap::DashMap;
|
||||
|
||||
pub fn add_client(clients_ref: &Arc<Mutex<HashMap<String, Client>>>, client: &Client){
|
||||
let mut clients_hashmap = clients_ref.lock().unwrap();
|
||||
let uuid = client.get_uuid().to_string();
|
||||
//clients_hashmap.insert(uuid, client.clone());
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
use crate::server::client::client_profile::Client;
|
||||
|
||||
use std::sync::Mutex;
|
||||
use std::sync::Arc;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub fn remove_client(clients_ref: &Arc<Mutex<HashMap<String, Client>>>, client: &Client){
|
||||
let mut clients_hashmap = clients_ref.lock().unwrap();
|
||||
clients_hashmap.remove(client.get_uuid()).unwrap();
|
||||
}
|
||||
|
|
@ -1,239 +0,0 @@
|
|||
mod request;
|
||||
mod info;
|
||||
mod success;
|
||||
mod error;
|
||||
mod connect;
|
||||
mod disconnect;
|
||||
mod client_update;
|
||||
mod client_info;
|
||||
mod client;
|
||||
mod test;
|
||||
mod message;
|
||||
|
||||
use crate::server::client::client_profile::Client;
|
||||
use crate::server::server_profile::Server;
|
||||
|
||||
use std::string::ToString;
|
||||
|
||||
use parking_lot::FairMutex;
|
||||
use std::sync::Mutex;
|
||||
use std::sync::Arc;
|
||||
use std::collections::HashMap;
|
||||
use dashmap::DashMap;
|
||||
use std::borrow::Borrow;
|
||||
use regex::Regex;
|
||||
use std::ops::Index;
|
||||
|
||||
|
||||
|
||||
/*
|
||||
impl ClientCommands{
|
||||
pub fn execute(&self, client: &mut Client, server: &Server, buffer: &mut [u8; 1024], connected_clients: &Arc<Mutex<HashMap<String, Client>>>){
|
||||
let stream = client.get_stream();
|
||||
match &*self{
|
||||
ClientCommands::Info => {
|
||||
let server_details = server.get_info();
|
||||
|
||||
client.transmit_success(&server_details);
|
||||
},
|
||||
ClientCommands::Connect(data) => {
|
||||
connect::add_client(connected_clients, client);
|
||||
|
||||
let new_client = ServerCommands::Client(data.clone());
|
||||
server.update_all_clients(&new_client);
|
||||
|
||||
client.transmit_success(&String::from(""));
|
||||
},
|
||||
ClientCommands::Disconnect => {
|
||||
disconnect::remove_client(connected_clients, client);
|
||||
|
||||
let mut data: HashMap<String, String> = HashMap::new();
|
||||
data.insert("uuid".to_string(), client.get_uuid().to_string());
|
||||
|
||||
let old_client = ServerCommands::ClientRemove(data);
|
||||
server.update_all_clients(&old_client);
|
||||
|
||||
client.transmit_success(&String::from(""));
|
||||
client.disconnect();
|
||||
println!("disconnected!");
|
||||
},
|
||||
ClientCommands::ClientUpdate => {
|
||||
let clients_hashmap = connected_clients.lock().unwrap();
|
||||
for (key, value) in clients_hashmap.iter(){
|
||||
let formatted_data = client_update::format_client_data(&key, &value);
|
||||
client.transmit_data(&formatted_data);
|
||||
|
||||
client.confirm_success(buffer, &formatted_data);
|
||||
}
|
||||
client.transmit_success(&String::from(""));
|
||||
client.confirm_success(buffer, &String::from("!success:"));
|
||||
},
|
||||
ClientCommands::ClientInfo(data) => {
|
||||
let requested_data = client_info::get_client_data(connected_clients, &data);
|
||||
client.transmit_data(&requested_data);
|
||||
},
|
||||
ClientCommands::Unknown => {
|
||||
println!("Unknown Command");
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ServerCommands{
|
||||
pub fn execute(&self, client: &mut Client, buffer: &mut [u8; 1024]){
|
||||
match &*self{
|
||||
ServerCommands::Client(data) => {
|
||||
let mut message = String::from("");
|
||||
message.push_str(&"!client: name:");
|
||||
message.push_str(&data.get("name").unwrap());
|
||||
message.push_str(&" host:");
|
||||
message.push_str(&data.get("host").unwrap());
|
||||
message.push_str(&" uuid:");
|
||||
message.push_str(&data.get("uuid").unwrap());
|
||||
|
||||
client.transmit_data(&message);
|
||||
|
||||
client.confirm_success(buffer, &message);
|
||||
},
|
||||
ServerCommands::ClientRemove(data) => {
|
||||
let mut message = String::from("");
|
||||
message.push_str(&"!client: uuid:");
|
||||
message.push_str(&data.get("uuid").unwrap());
|
||||
|
||||
client.transmit_data(&message);
|
||||
|
||||
client.confirm_success(buffer, &message);
|
||||
},
|
||||
ServerCommands::Unknown => {
|
||||
println!("Unknown Command!");
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// MARK: - commands_v2 electric boogaloo
|
||||
#[derive(Clone)]
|
||||
pub enum Commands {
|
||||
Request(Option<HashMap<String, String>>),
|
||||
Info(Option<HashMap<String, String>>),
|
||||
|
||||
Connect(Option<HashMap<String, String>>),
|
||||
Disconnect(Option<HashMap<String, String>>),
|
||||
|
||||
ClientUpdate(Option<HashMap<String, String>>),
|
||||
ClientInfo(Option<HashMap<String, String>>),
|
||||
ClientRemove(Option<HashMap<String, String>>),
|
||||
Client(Option<HashMap<String, String>>),
|
||||
|
||||
Success(Option<HashMap<String, String>>),
|
||||
Error(Option<HashMap<String, String>>),
|
||||
}
|
||||
|
||||
impl ToString for Commands {
|
||||
|
||||
fn to_string(&self) -> std::string::String {
|
||||
let mut out_string = String::new();
|
||||
|
||||
let (command, parameters) = match self {
|
||||
Commands::Info(arguments) => { ("!info:", arguments) },
|
||||
Commands::Connect(arguments) => { ("!connect:", arguments) },
|
||||
Commands::Disconnect(arguments) => { ("!disconnect:", arguments) },
|
||||
Commands::ClientUpdate(arguments) => { ("!clientUpdate:", arguments) },
|
||||
Commands::ClientInfo(arguments) => { ("!clientInfo:", arguments) },
|
||||
Commands::Error(arguments) => { ("!error:", arguments) },
|
||||
_ => { ("!error:", &None) }
|
||||
};
|
||||
|
||||
out_string.push_str(command);
|
||||
|
||||
if parameters.is_some() {
|
||||
let hash_map = parameters.borrow().as_ref().unwrap();
|
||||
for (k, v) in hash_map.iter() {
|
||||
out_string.push_str(" ");
|
||||
out_string.push_str(k.as_str());
|
||||
out_string.push_str(":");
|
||||
out_string.push_str(v.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
out_string
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Commands {
|
||||
fn from(data: &str) -> Self {
|
||||
let regex = Regex::new(r###"(\?|!)([a-zA-z0-9]*):|([a-zA-z]*):([a-zA-Z0-9\-\+\[\]{}_=/]+|("(.*?)")+)"###).unwrap();
|
||||
let mut iter = regex.find_iter(data);
|
||||
let command = iter.next().unwrap().as_str();
|
||||
|
||||
println!("command: {:?}", command);
|
||||
|
||||
let mut map: HashMap<String, String> = HashMap::new();
|
||||
|
||||
for i in iter {
|
||||
let parameter = i.as_str().to_string();
|
||||
let mut parts:Vec<&str> = parameter.split(":").collect();
|
||||
|
||||
map.insert(parts.index(0).to_string(), parts.index(1).to_string());
|
||||
}
|
||||
|
||||
let params = if map.capacity() > 1 {Some(map)} else { None };
|
||||
|
||||
match command {
|
||||
"!request:" => Commands::Request(params),
|
||||
"!info:" => Commands::Info(params),
|
||||
|
||||
"!connect:" => Commands::Connect(params),
|
||||
"!disconnect:" => Commands::Disconnect(params),
|
||||
|
||||
"!clientUpdate:" => Commands::ClientUpdate(params),
|
||||
"!clientInfo:" => Commands::ClientInfo(params),
|
||||
"!client:" => Commands::Client(params),
|
||||
"!clientRemove:" => Commands::ClientRemove(params),
|
||||
|
||||
"!success:" => Commands::Success(params),
|
||||
"!error:" => Commands::Error(params),
|
||||
|
||||
_ => Commands::Error(params),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Commands {
|
||||
fn from(data: String) -> Self {
|
||||
Commands::from(data.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[u8]> for Commands {
|
||||
fn from(data: &[u8]) -> Self {
|
||||
let incoming_message = String::from(String::from_utf8_lossy(data)).as_str();
|
||||
Commands::from(incoming_message)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_commands_v2 {
|
||||
use super::Commands;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
fn test_creation_from_string() {
|
||||
let command_result = Commands::from("!connect: name:bop host:127.0.0.1 uuid:123456-1234-1234-123456");
|
||||
()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_string() {
|
||||
|
||||
let mut a: HashMap<String, String> = HashMap::new();
|
||||
a.insert("name".to_string(), "michael".to_string());
|
||||
a.insert("host".to_string(), "127.0.0.1".to_string());
|
||||
a.insert("uuid".to_string(), "123456-1234-1234-123456".to_string());
|
||||
|
||||
let command = Commands::Connect(Some(a));
|
||||
|
||||
println!("{:?}", command.to_string())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
pub mod client;
|
||||
pub mod commands;
|
||||
pub mod server_profile;
|
||||
|
|
@ -1,165 +0,0 @@
|
|||
extern crate regex;
|
||||
|
||||
use crate::server::client::client_profile::Client;
|
||||
use crate::server::commands::{Commands};
|
||||
|
||||
use rust_chat_server::ThreadPool;
|
||||
use std::collections::VecDeque;
|
||||
use std::net::{TcpStream, TcpListener};
|
||||
use std::sync::{Arc, Barrier, Mutex };
|
||||
use crossbeam_channel::{unbounded, Sender, Receiver};
|
||||
use parking_lot::FairMutex;
|
||||
use std::collections::HashMap;
|
||||
use dashmap::DashMap;
|
||||
use std::io::prelude::*;
|
||||
use regex::Regex;
|
||||
|
||||
pub struct Server<'server_lifetime> {
|
||||
name: String,
|
||||
address: String,
|
||||
author: String,
|
||||
connected_clients: Arc<Mutex<HashMap<String,&'server_lifetime Client<'server_lifetime>>>>,
|
||||
thread_pool: ThreadPool,
|
||||
}
|
||||
|
||||
// MARK: - server implemetation
|
||||
impl<'server_lifetime> Server<'server_lifetime> {
|
||||
pub fn new(name: &String, address: &String, author: &String) -> Server<'server_lifetime> {
|
||||
Server{
|
||||
name: name.to_string(),
|
||||
address: address.to_string(),
|
||||
author: author.to_string(),
|
||||
connected_clients: Arc::new(Mutex::new(HashMap::new())),
|
||||
thread_pool: ThreadPool::new(16)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_address(&self) -> &String{
|
||||
&self.address
|
||||
}
|
||||
|
||||
pub fn start(&'server_lifetime self) {
|
||||
let listener = TcpListener::bind(self.get_address()).unwrap();
|
||||
let mut buffer = [0; 1024];
|
||||
|
||||
loop {
|
||||
if let Ok((mut stream, addr)) = listener.accept() {
|
||||
println!("Server: new connection, {}", addr);
|
||||
|
||||
let request = Commands::Request(None);
|
||||
request.to_string();
|
||||
self.transmit_data(&stream, &*request.to_string().as_str());
|
||||
|
||||
stream.read(&mut buffer).unwrap();
|
||||
|
||||
let incoming_message = String::from(String::from_utf8_lossy(&buffer));
|
||||
let command = Commands::from(incoming_message);
|
||||
match command {
|
||||
Commands::Connect(Some(data)) => {
|
||||
let uuid = data.get("uuid").unwrap();
|
||||
let username = data.get("name").unwrap();
|
||||
let address = data.get("host").unwrap();
|
||||
|
||||
let stream = Arc::new(stream);
|
||||
let mut client = Client::new(self, stream, &uuid, &username, &address);
|
||||
|
||||
let mut clients_hashmap = self.connected_clients.lock().unwrap();
|
||||
|
||||
clients_hashmap.insert(uuid.to_string(), &client);
|
||||
|
||||
self.thread_pool.execute(move || {
|
||||
client.handle_connection();
|
||||
});
|
||||
},
|
||||
Commands::Info(None) => {
|
||||
let mut params: HashMap<String, String> = HashMap::new();
|
||||
params.insert(String::from("name"), self.name.clone());
|
||||
params.insert(String::from("owner"), self.author.clone());
|
||||
|
||||
let command = Commands::Info(Some(params));
|
||||
|
||||
self.transmit_data(&stream, command.to_string().as_str());
|
||||
},
|
||||
_ => {
|
||||
println!("Invalid command!");
|
||||
self.transmit_data(&stream, Commands::Error(None).to_string().as_str());
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_info(&self, tx: Sender<Commands>) {
|
||||
let mut params: HashMap<String, String> = HashMap::new();
|
||||
params.insert(String::from("name"), self.name.clone());
|
||||
params.insert(String::from("owner"), self.author.clone());
|
||||
|
||||
let command = Commands::Info(Some(params));
|
||||
tx.send(command).unwrap();
|
||||
}
|
||||
|
||||
pub fn update_all_clients(&self, command: Commands){
|
||||
let clients = self.connected_clients.lock().unwrap();
|
||||
for client in clients.values(){
|
||||
let tx = client.get_transmitter();
|
||||
tx.send(command.clone()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn transmit_data(&self, mut stream: &TcpStream, data: &str){
|
||||
println!("Transmitting...");
|
||||
println!("data: {}",data);
|
||||
|
||||
stream.write(data.to_string().as_bytes()).unwrap();
|
||||
stream.flush().unwrap();
|
||||
}
|
||||
|
||||
//deprecated
|
||||
/*
|
||||
pub fn tokenize(&self, incoming_message: &str) -> Result<ClientCommands, &'static str>{
|
||||
let command_regex = Regex::new(r###"(\?|!)([a-zA-z0-9]*):|([a-zA-z]*):([a-zA-Z0-9\-\+\[\]{}_=/]+|("(.*?)")+)"###).unwrap();
|
||||
|
||||
if command_regex.is_match(incoming_message){
|
||||
let command = self.match_command(&incoming_message.to_string());
|
||||
let command = match command{
|
||||
ClientCommands::Connect(mut addons) => {
|
||||
self.regex_data(&command_regex, &incoming_message.replace("!connect: ", ""), &mut addons);
|
||||
ClientCommands::Connect(addons)
|
||||
},
|
||||
ClientCommands::ClientInfo(mut addons) => {
|
||||
self.regex_data(&command_regex, &incoming_message.replace("!clientInfo: ", ""), &mut addons);
|
||||
ClientCommands::ClientInfo(addons)
|
||||
},
|
||||
_ => {
|
||||
println!("no addons");
|
||||
command
|
||||
},
|
||||
};
|
||||
Ok(command)
|
||||
} else {
|
||||
Err("data did not match regex!")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn match_command(&self, command: &String) -> ClientCommands{
|
||||
match command{
|
||||
_ if command.starts_with("!info:") => ClientCommands::Info,
|
||||
_ if command.starts_with("!connect:") => ClientCommands::Connect(HashMap::new()),
|
||||
_ if command.starts_with("!disconnect:") => ClientCommands::Disconnect,
|
||||
_ if command.starts_with("!clientUpdate:") => ClientCommands::ClientUpdate,
|
||||
_ if command.starts_with("!clientInfo:") => ClientCommands::ClientInfo(HashMap::new()),
|
||||
_ => ClientCommands::Unknown,
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
fn regex_data(&self, command_regex: &Regex, data: &str, command_addons: &mut HashMap<String, String>){
|
||||
for figure in command_regex.find_iter(data){
|
||||
let segment = figure.as_str().to_string();
|
||||
let contents: Vec<&str> = segment.split(":").collect();
|
||||
println!("key: {}, value: {}", contents[0].to_string(), contents[1].to_string());
|
||||
command_addons.insert(contents[0].to_string(), contents[1].to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
use std::string::ToString;
|
||||
use std::sync::{Arc, Mutex, Weak};
|
||||
use std::net::TcpStream;
|
||||
use crate::server_v2::Serverv2;
|
||||
use std::sync::mpsc::{Receiver, Sender, channel, TryRecvError};
|
||||
use crate::server_v2::commands_v2::Commandsv2;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ClientV2 {
|
||||
pub uuid: String,
|
||||
pub username: String,
|
||||
pub address: String,
|
||||
|
||||
stream: Arc<Mutex<TcpStream>>,
|
||||
server_reference: Weak<Serverv2>,
|
||||
|
||||
tx: Sender<Commandsv2>,
|
||||
rx: Receiver<Commandsv2>,
|
||||
}
|
||||
|
||||
impl ClientV2 {
|
||||
pub fn new(stream: Arc<Mutex<TcpStream>>, server: Arc<Serverv2>, uuid: &String, username: &String, address: &String) -> ClientV2 {
|
||||
|
||||
let (tx, rx) = channel();
|
||||
|
||||
ClientV2 {
|
||||
stream: stream,
|
||||
server_reference: Arc::downgrade(&server),
|
||||
|
||||
tx,
|
||||
rx,
|
||||
|
||||
uuid: uuid.to_string(),
|
||||
username: username.to_string(),
|
||||
address: address.to_string(),
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&self) {
|
||||
|
||||
|
||||
|
||||
loop {
|
||||
match self.rx.try_recv() {
|
||||
Ok(Command) => {
|
||||
|
||||
}
|
||||
Err(TryRecvError::Empty) => { }
|
||||
Err(TryRecvError::Disconnected) => {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_tx(&self) -> Sender<Commandsv2> {
|
||||
self.tx.clone()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,107 +0,0 @@
|
|||
use std::collections::HashMap;
|
||||
use std::borrow::Borrow;
|
||||
use regex::Regex;
|
||||
use std::ops::Index;
|
||||
|
||||
pub enum Commands {
|
||||
Request(Option<HashMap<String, String>>),
|
||||
Info(Option<HashMap<String, String>>),
|
||||
|
||||
Connect(Option<HashMap<String, String>>),
|
||||
Disconnect(Option<HashMap<String, String>>),
|
||||
|
||||
ClientUpdate(Option<HashMap<String, String>>),
|
||||
ClientInfo(Option<HashMap<String, String>>),
|
||||
ClientRemove(Option<HashMap<String, String>>),
|
||||
Client(Option<HashMap<String, String>>),
|
||||
|
||||
Success(Option<HashMap<String, String>>),
|
||||
Error(Option<HashMap<String, String>>),
|
||||
}
|
||||
|
||||
impl Commands {
|
||||
pub fn to_String(&self) -> String {
|
||||
|
||||
let mut out_string = String::new();
|
||||
|
||||
let (command, parameters) = match self {
|
||||
Commands::Info(arguments) => { ("!info:", arguments) },
|
||||
Commands::Connect(arguments) => { ("!connect:", arguments) },
|
||||
Commands::Disconnect(arguments) => { ("!disconnect:", arguments) },
|
||||
Commands::ClientUpdate(arguments) => { ("!clientUpdate:", arguments) },
|
||||
Commands::ClientInfo(arguments) => { ("!clientInfo:", arguments) },
|
||||
Commands::Error(arguments) => { ("!error:", arguments) },
|
||||
_ => { ("!error:", &None) }
|
||||
};
|
||||
|
||||
out_string.push_str(command);
|
||||
|
||||
if parameters.is_some() {
|
||||
let hash_map = parameters.borrow().as_ref().unwrap();
|
||||
for (k, v) in hash_map.iter() {
|
||||
out_string.push_str(" ");
|
||||
out_string.push_str(k.as_str());
|
||||
out_string.push_str(":");
|
||||
out_string.push_str(v.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
out_string
|
||||
}
|
||||
|
||||
pub fn from_string(data: &str) -> Result<Commandsv2, &'static str> {
|
||||
let regex = Regex::new(r###"(\?|!)([a-zA-z0-9]*):|([a-zA-z]*):([a-zA-Z0-9\-\+\[\]{}_=/]+|("(.*?)")+)"###).unwrap();
|
||||
let mut iter = regex.find_iter(data);
|
||||
let command = iter.next().unwrap().as_str();
|
||||
|
||||
println!("command: {:?}", command);
|
||||
|
||||
let mut map: HashMap<String, String> = HashMap::new();
|
||||
|
||||
for i in iter {
|
||||
let parameter = i.as_str().to_string();
|
||||
let mut parts:Vec<&str> = parameter.split(":").collect();
|
||||
|
||||
map.insert(parts.index(0).to_string(), parts.index(1).to_string());
|
||||
}
|
||||
|
||||
let params = if map.capacity() > 1 {Some(map)} else { None };
|
||||
|
||||
match command {
|
||||
"!info:" => Ok(Commands::Info(params)),
|
||||
"!connect:" => Ok(Commands::Connect(params)),
|
||||
"!clientInfo:" => Ok(Commands::ClientInfo(params)),
|
||||
"!clientUpdate:" => Ok(Commands::ClientUpdate(params)),
|
||||
"!disconnect:" => Ok(Commands::Disconnect(params)),
|
||||
"!error:" => Ok(Commands::Error(params)),
|
||||
_ => { Err("NOT IMPLEMENTED") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_commands_v2 {
|
||||
use crate::server_v2::commands_v2::Commandsv2;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
fn test_creation_from_string() {
|
||||
let command_result = Commandsv2::from_string("!connect: name:bop host:127.0.0.1 uuid:123456-1234-1234-123456");
|
||||
assert!(command_result.is_ok(), true);
|
||||
let command = command_result.unwrap_or(Commandsv2::Error(None));
|
||||
()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_string() {
|
||||
|
||||
let mut a: HashMap<String, String> = HashMap::new();
|
||||
a.insert("name".to_string(), "michael".to_string());
|
||||
a.insert("host".to_string(), "127.0.0.1".to_string());
|
||||
a.insert("uuid".to_string(), "123456-1234-1234-123456".to_string());
|
||||
|
||||
let command = Commandsv2::Connect(Some(a));
|
||||
|
||||
println!("{:?}", command.to_String())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
pub mod client_v2;
|
||||
pub mod commands_v2;
|
||||
|
||||
use client_v2::ClientV2;
|
||||
use std::{
|
||||
collections::{HashMap, VecDeque},
|
||||
io,
|
||||
thread,
|
||||
sync::{
|
||||
mpsc::{channel, Sender, Receiver},
|
||||
Arc,
|
||||
Mutex,
|
||||
},
|
||||
ops::Deref,
|
||||
borrow::Borrow,
|
||||
time::Duration,
|
||||
net::TcpListener,
|
||||
io::Read,
|
||||
};
|
||||
use crate::server_v2::commands_v2::Commandsv2;
|
||||
use crate::lib::ThreadPool;
|
||||
use std::sync::mpsc::TryRecvError;
|
||||
use crate::server_v2::commands_v2::Commandsv2::Disconnect;
|
||||
|
||||
enum server_message {
|
||||
start,
|
||||
stop,
|
||||
kick(String),
|
||||
}
|
||||
|
||||
pub struct Serverv2 {
|
||||
name: String,
|
||||
host: String,
|
||||
owner: String,
|
||||
|
||||
rx: Arc<Mutex<Receiver<server_message>>>,
|
||||
tx: Arc<Mutex<Sender<server_message>>>,
|
||||
|
||||
connected_clients: Arc<Mutex<HashMap<String, ClientV2>>>,
|
||||
thread_pool: ThreadPool,
|
||||
}
|
||||
|
||||
impl Serverv2 {
|
||||
pub fn new(name: String, host: String, owner: String) -> Serverv2 {
|
||||
|
||||
let (tx,rx) = channel();
|
||||
|
||||
Serverv2 {
|
||||
name,
|
||||
host,
|
||||
owner,
|
||||
|
||||
rx: Arc::new(Mutex::new(rx)),
|
||||
tx: Arc::new(Mutex::new(tx)),
|
||||
|
||||
connected_clients: Arc::new(Mutex::new(HashMap::new())),
|
||||
thread_pool: ThreadPool::new(16)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(&self) -> Result<(), io::Error> {
|
||||
let listener = TcpListener::bind("0.0.0.0:6001")?;
|
||||
|
||||
|
||||
|
||||
|
||||
// accepting clients
|
||||
thread::spawn(move || {
|
||||
match rx.lock().unwrap().try_recv() {
|
||||
Ok(a) => {}
|
||||
Err(TryRecvError::Empty) => {}
|
||||
Err(TryRecvError::Disconnected) => {
|
||||
self.connected_clients.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|(id, client)| {
|
||||
let tx = client.get_tx();
|
||||
tx.send(Disconnect(None));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn stop(&self) {
|
||||
}
|
||||
|
||||
pub fn add_client(&self, client: ClientV2) -> Result<(), &str> {
|
||||
let mut client_map = self.connected_clients.lock().unwrap();
|
||||
if client_map.contains_key(client.uuid.as_str()) {
|
||||
return Err("!exists:");
|
||||
}
|
||||
|
||||
client_map.insert(client.uuid.to_string(), client);
|
||||
|
||||
self.thread_pool.execute(|| {client.run()});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_tx(&self, mesage: server_message) {
|
||||
self.tx.clone();
|
||||
}
|
||||
|
||||
fn log(mesaage: &str) {
|
||||
println!("Server: {}", mesaage);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue