generated from PlexSheep/rs-base
Compare commits
No commits in common. "b7fcd9c455286e61669bb58bc15cfe3119923d0d" and "ba2230cf764583c9eb5656b2821bb8db624db324" have entirely different histories.
b7fcd9c455
...
ba2230cf76
9 changed files with 8 additions and 390 deletions
23
Cargo.toml
23
Cargo.toml
|
@ -1,27 +1,16 @@
|
||||||
[package]
|
[package]
|
||||||
name = "netpong"
|
name = "template"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
publish = true
|
publish = false
|
||||||
authors = ["Christoph J. Scherr <software@cscherr.de>"]
|
authors = ["Christoph J. Scherr <software@cscherr.de>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "let your servers play ping pong"
|
description = "No description yet"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
homepage = "https://git.cscherr.de/PlexSheep/netpong"
|
homepage = "https://git.cscherr.de/PlexSheep/rs-base"
|
||||||
repository = "https://git.cscherr.de/PlexSheep/netpong"
|
repository = "https://git.cscherr.de/PlexSheep/rs-base"
|
||||||
keywords = ["networking"]
|
keywords = ["template"]
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.79"
|
|
||||||
clap = "4.4.18"
|
|
||||||
clap-num = "1.0.2"
|
|
||||||
clap-verbosity-flag = "2.1.2"
|
|
||||||
libpt = { version = "0.3.10", features = ["net"] }
|
|
||||||
thiserror = "1.0.56"
|
|
||||||
threadpool = { version = "1.8.1", optional = true }
|
|
||||||
tokio = { version = "1.35.1", features = ["net", "rt", "macros"] }
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["server"]
|
|
||||||
server = ["dep:threadpool"]
|
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
import socket
|
|
||||||
|
|
||||||
HOST = "127.0.0.1"
|
|
||||||
PORT = 9999
|
|
||||||
|
|
||||||
payload = b"ping\0"
|
|
||||||
|
|
||||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
||||||
s.connect((HOST, PORT))
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
s.sendall(payload)
|
|
||||||
print("> ping")
|
|
||||||
except Exception as e:
|
|
||||||
break
|
|
||||||
reply = s.recv(1024).decode()
|
|
||||||
if reply == "":
|
|
||||||
break
|
|
||||||
print(f"< {reply}")
|
|
||||||
print("connection shut down")
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
|
|
|
@ -1,82 +0,0 @@
|
||||||
use libpt::log::{Level, Logger};
|
|
||||||
|
|
||||||
use clap::Parser;
|
|
||||||
use clap_verbosity_flag::{InfoLevel, Verbosity};
|
|
||||||
|
|
||||||
use crate::common::conf::Mode;
|
|
||||||
|
|
||||||
/// short about section displayed in help
|
|
||||||
const ABOUT_ROOT: &'static str = r##"
|
|
||||||
Let your hosts play ping pong over the network
|
|
||||||
"##;
|
|
||||||
/// longer about section displayed in help, is combined with [the short help](ABOUT_ROOT)
|
|
||||||
static LONG_ABOUT_ROOT: &'static str = r##"
|
|
||||||
|
|
||||||
Connect to a ping pong server and periodically send a ping. The server will reply with a pong.
|
|
||||||
That's it really. You can also host your own netpong server (server feature).
|
|
||||||
"##;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Parser)]
|
|
||||||
#[command(
|
|
||||||
author,
|
|
||||||
version,
|
|
||||||
about = ABOUT_ROOT,
|
|
||||||
long_about = format!("{}{}", ABOUT_ROOT ,LONG_ABOUT_ROOT),
|
|
||||||
help_template =
|
|
||||||
r#"{about-section}
|
|
||||||
{usage-heading} {usage}
|
|
||||||
{all-args}{tab}
|
|
||||||
|
|
||||||
libpt: {version}
|
|
||||||
Author: {author-with-newline}
|
|
||||||
"#
|
|
||||||
)]
|
|
||||||
pub(crate) struct Cli {
|
|
||||||
// clap_verbosity_flag seems to make this a global option implicitly
|
|
||||||
/// set a verbosity, multiple allowed (f.e. -vvv)
|
|
||||||
#[command(flatten)]
|
|
||||||
pub(crate) verbose: Verbosity<InfoLevel>,
|
|
||||||
|
|
||||||
/// show additional logging meta data
|
|
||||||
#[arg(long)]
|
|
||||||
pub(crate) meta: bool,
|
|
||||||
|
|
||||||
// host a server instead of connecting to one
|
|
||||||
#[cfg(feature = "server")]
|
|
||||||
#[arg(short, long, default_value_t = false)]
|
|
||||||
pub(crate) server: bool,
|
|
||||||
|
|
||||||
// how much threads the server should use
|
|
||||||
#[cfg(feature = "server")]
|
|
||||||
#[arg(short, long, default_value_t = 4)]
|
|
||||||
pub(crate) threads: usize,
|
|
||||||
|
|
||||||
#[arg(short, long, default_value_t = Mode::Tcp, ignore_case = true)]
|
|
||||||
pub(crate) mode: Mode,
|
|
||||||
|
|
||||||
/// Address of the server
|
|
||||||
pub(crate) addr: std::net::SocketAddr,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Cli {
|
|
||||||
pub fn cli_parse() -> Self {
|
|
||||||
let cli = Self::parse();
|
|
||||||
let ll: Level = match cli.verbose.log_level().unwrap().as_str() {
|
|
||||||
"TRACE" => Level::TRACE,
|
|
||||||
"DEBUG" => Level::DEBUG,
|
|
||||||
"INFO" => Level::INFO,
|
|
||||||
"WARN" => Level::WARN,
|
|
||||||
"ERROR" => Level::ERROR,
|
|
||||||
_ => {
|
|
||||||
unreachable!();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if cli.meta {
|
|
||||||
Logger::init(None, Some(ll)).expect("could not initialize Logger");
|
|
||||||
} else {
|
|
||||||
// less verbose version
|
|
||||||
Logger::init_mini(Some(ll)).expect("could not initialize Logger");
|
|
||||||
}
|
|
||||||
return cli;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
use crate::common::args::Cli;
|
|
||||||
use clap::ValueEnum;
|
|
||||||
use std::{fmt::Display, time::Duration};
|
|
||||||
|
|
||||||
const DEFAULT_TIMEOUT_LEN: u64 = 5000; // ms
|
|
||||||
const DEFAULT_DELAY_LEN: u64 = 500; // ms
|
|
||||||
const DEFAULT_WIN_AFTER: usize = 20;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub enum Mode {
|
|
||||||
Tcp,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ValueEnum for Mode {
|
|
||||||
fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
|
|
||||||
Some(match self {
|
|
||||||
Self::Tcp => clap::builder::PossibleValue::new("tcp"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
fn value_variants<'a>() -> &'a [Self] {
|
|
||||||
&[Self::Tcp]
|
|
||||||
}
|
|
||||||
fn from_str(input: &str, ignore_case: bool) -> Result<Self, String> {
|
|
||||||
let comp: String = if ignore_case {
|
|
||||||
input.to_lowercase()
|
|
||||||
} else {
|
|
||||||
input.to_string()
|
|
||||||
};
|
|
||||||
match comp.as_str() {
|
|
||||||
"tcp" => return Ok(Self::Tcp),
|
|
||||||
_ => return Err(format!("\"{input}\" is not a valid mode")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Mode {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
let repr: String = match self {
|
|
||||||
Self::Tcp => format!("tcp"),
|
|
||||||
};
|
|
||||||
write!(f, "{}", repr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Config {
|
|
||||||
pub addr: std::net::SocketAddr,
|
|
||||||
pub mode: Mode,
|
|
||||||
pub threads: usize,
|
|
||||||
pub timeout: Duration,
|
|
||||||
pub delay: Duration,
|
|
||||||
pub win_after: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Config {
|
|
||||||
pub fn new(cli: &Cli) -> Self {
|
|
||||||
Config {
|
|
||||||
addr: cli.addr.clone(),
|
|
||||||
mode: cli.mode.clone(),
|
|
||||||
threads: cli.threads,
|
|
||||||
timeout: Duration::from_millis(DEFAULT_TIMEOUT_LEN),
|
|
||||||
delay: Duration::from_millis(DEFAULT_DELAY_LEN),
|
|
||||||
win_after: DEFAULT_WIN_AFTER,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
pub mod args;
|
|
||||||
pub mod conf;
|
|
34
src/main.rs
34
src/main.rs
|
@ -1,33 +1,3 @@
|
||||||
//! # netpong
|
fn main() {
|
||||||
//! This is pretty silly and only let's your client send "ping"
|
println!("Hello, world!");
|
||||||
//! over a connection, while letting a server reply pong to
|
|
||||||
//! every connection. But I had to make it for some reason.
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use libpt::log::*;
|
|
||||||
use tokio;
|
|
||||||
|
|
||||||
mod client;
|
|
||||||
mod common;
|
|
||||||
mod server;
|
|
||||||
|
|
||||||
use common::{args::Cli, conf::*};
|
|
||||||
|
|
||||||
use crate::server::Server;
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() -> Result<()> {
|
|
||||||
let cli = Cli::cli_parse();
|
|
||||||
debug!("dumping cli args:\n{:#?}", cli);
|
|
||||||
|
|
||||||
let cfg = Config::new(&cli);
|
|
||||||
|
|
||||||
#[cfg(feature = "server")]
|
|
||||||
if cli.server {
|
|
||||||
info!("starting server");
|
|
||||||
return Server::build(cfg).await?.run().await;
|
|
||||||
}
|
|
||||||
// implicit else, so we can work without the server feature
|
|
||||||
info!("starting client");
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
use std::{fmt::Display, string::FromUtf8Error};
|
|
||||||
|
|
||||||
use anyhow;
|
|
||||||
use thiserror::Error;
|
|
||||||
use tokio::time::error::Elapsed;
|
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, ServerError>;
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum ServerError {
|
|
||||||
Timeout(Elapsed),
|
|
||||||
Anyhow(anyhow::Error),
|
|
||||||
IO(std::io::Error),
|
|
||||||
Format(FromUtf8Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<anyhow::Error> for ServerError {
|
|
||||||
fn from(value: anyhow::Error) -> Self {
|
|
||||||
Self::Anyhow(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<std::io::Error> for ServerError {
|
|
||||||
fn from(value: std::io::Error) -> Self {
|
|
||||||
Self::IO(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<FromUtf8Error> for ServerError {
|
|
||||||
fn from(value: FromUtf8Error) -> Self {
|
|
||||||
Self::Format(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Elapsed> for ServerError {
|
|
||||||
fn from(value: Elapsed) -> Self {
|
|
||||||
Self::Timeout(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for ServerError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{:?}", self)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,125 +0,0 @@
|
||||||
#![cfg(feature = "server")]
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use libpt::log::{debug, info, trace, warn};
|
|
||||||
use tokio::{
|
|
||||||
io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
|
|
||||||
net::{TcpListener, TcpStream},
|
|
||||||
time::timeout,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::common::conf::Config;
|
|
||||||
|
|
||||||
pub mod errors;
|
|
||||||
use errors::*;
|
|
||||||
|
|
||||||
pub struct Server {
|
|
||||||
cfg: Config,
|
|
||||||
pub timeout: Option<Duration>,
|
|
||||||
server: TcpListener,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Server {
|
|
||||||
pub async fn build(cfg: Config) -> anyhow::Result<Self> {
|
|
||||||
let server = TcpListener::bind(cfg.addr).await?;
|
|
||||||
let timeout = Some(Duration::from_secs(5));
|
|
||||||
Ok(Server {
|
|
||||||
cfg,
|
|
||||||
timeout,
|
|
||||||
server,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
pub async fn run(self) -> anyhow::Result<()> {
|
|
||||||
loop {
|
|
||||||
let (stream, addr) = match self.server.accept().await {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(err) => {
|
|
||||||
warn!("could not accept stream: {err:?}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
match self.handle_stream(stream).await {
|
|
||||||
Ok(_) => (),
|
|
||||||
Err(err) => {
|
|
||||||
match err {
|
|
||||||
ServerError::Timeout(_) => {
|
|
||||||
info!("stream {:?} timed out", addr)
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
warn!("error while handling stream: {:?}", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handle_stream(&self, stream: TcpStream) -> Result<()> {
|
|
||||||
let mut pings: usize = 0;
|
|
||||||
let addr = match stream.peer_addr() {
|
|
||||||
Ok(a) => a,
|
|
||||||
Err(err) => {
|
|
||||||
debug!("could not get peer address: {:?}", err);
|
|
||||||
return Err(err.into());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
info!("new peer: {:?}", addr);
|
|
||||||
let mut buf = Vec::new();
|
|
||||||
let mut reader = BufReader::new(stream);
|
|
||||||
let mut len;
|
|
||||||
loop {
|
|
||||||
len = match self.read(&mut reader, &mut buf).await {
|
|
||||||
Ok(len) => len,
|
|
||||||
Err(err) => {
|
|
||||||
match err {
|
|
||||||
ServerError::Timeout(_) => {
|
|
||||||
info!("peer {:?} timed out", addr)
|
|
||||||
}
|
|
||||||
_ => return Err(err),
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
trace!("received message: {:X?}", buf);
|
|
||||||
if len == 0 {
|
|
||||||
trace!("len is apperently 0: {len:?}");
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
let msg = self.decode(&buf)?;
|
|
||||||
info!("< {:?} : {}", addr, msg);
|
|
||||||
if msg.contains("ping") {
|
|
||||||
pings += 1;
|
|
||||||
}
|
|
||||||
if pings < self.cfg.win_after {
|
|
||||||
reader.write_all(b"pong\0").await?;
|
|
||||||
info!("> {:?} : pong", addr,);
|
|
||||||
} else {
|
|
||||||
reader.write_all(b"you win!\0").await?;
|
|
||||||
info!("> {:?} : you win!", addr,);
|
|
||||||
reader.shutdown().await?;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
buf.clear();
|
|
||||||
|
|
||||||
// we should wait, so that we don't spam the client
|
|
||||||
std::thread::sleep(self.cfg.delay);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
info!("disconnected peer: {:?}", addr);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn decode(&self, buf: &Vec<u8>) -> Result<String> {
|
|
||||||
Ok(String::from_utf8(buf.clone())?.replace('\n', "\\n"))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
async fn read(&self, reader: &mut BufReader<TcpStream>, buf: &mut Vec<u8>) -> Result<usize> {
|
|
||||||
match timeout(self.cfg.timeout, reader.read_until(0x00, buf)).await? {
|
|
||||||
Ok(len) => Ok(len),
|
|
||||||
Err(err) => Err(err.into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue