summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--Cargo.lock171
-rw-r--r--Cargo.toml1
-rw-r--r--client.dbbin0 -> 12288 bytes
-rw-r--r--src/bin/client.rs98
-rw-r--r--src/bin/client/connection.rs32
-rw-r--r--src/bin/client/database.rs30
-rw-r--r--src/bin/client/main.rs17
-rw-r--r--src/bin/client/mod.rs4
-rw-r--r--src/bin/client/platform.rs28
-rw-r--r--src/bin/client/repl.rs108
-rw-r--r--src/bin/server/args.rs (renamed from src/bin/server.rs)37
-rw-r--r--src/bin/server/handler.rs15
-rw-r--r--src/bin/server/main.rs23
-rw-r--r--src/bin/server/mod.rs2
14 files changed, 437 insertions, 129 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 17b9b81..63805f6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3,6 +3,156 @@
3version = 4 3version = 4
4 4
5[[package]] 5[[package]]
6name = "ahash"
7version = "0.8.12"
8source = "registry+https://github.com/rust-lang/crates.io-index"
9checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
10dependencies = [
11 "cfg-if",
12 "once_cell",
13 "version_check",
14 "zerocopy",
15]
16
17[[package]]
18name = "allocator-api2"
19version = "0.2.21"
20source = "registry+https://github.com/rust-lang/crates.io-index"
21checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
22
23[[package]]
24name = "bitflags"
25version = "2.11.0"
26source = "registry+https://github.com/rust-lang/crates.io-index"
27checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
28
29[[package]]
30name = "cfg-if"
31version = "1.0.4"
32source = "registry+https://github.com/rust-lang/crates.io-index"
33checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
34
35[[package]]
36name = "fallible-iterator"
37version = "0.2.0"
38source = "registry+https://github.com/rust-lang/crates.io-index"
39checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
40
41[[package]]
42name = "fallible-streaming-iterator"
43version = "0.1.9"
44source = "registry+https://github.com/rust-lang/crates.io-index"
45checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
46
47[[package]]
48name = "hashbrown"
49version = "0.14.5"
50source = "registry+https://github.com/rust-lang/crates.io-index"
51checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
52dependencies = [
53 "ahash",
54 "allocator-api2",
55]
56
57[[package]]
58name = "hashlink"
59version = "0.8.4"
60source = "registry+https://github.com/rust-lang/crates.io-index"
61checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
62dependencies = [
63 "hashbrown",
64]
65
66[[package]]
67name = "libsqlite3-sys"
68version = "0.26.0"
69source = "registry+https://github.com/rust-lang/crates.io-index"
70checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326"
71dependencies = [
72 "pkg-config",
73 "vcpkg",
74]
75
76[[package]]
77name = "once_cell"
78version = "1.21.3"
79source = "registry+https://github.com/rust-lang/crates.io-index"
80checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
81
82[[package]]
83name = "pkg-config"
84version = "0.3.32"
85source = "registry+https://github.com/rust-lang/crates.io-index"
86checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
87
88[[package]]
89name = "proc-macro2"
90version = "1.0.106"
91source = "registry+https://github.com/rust-lang/crates.io-index"
92checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
93dependencies = [
94 "unicode-ident",
95]
96
97[[package]]
98name = "quote"
99version = "1.0.44"
100source = "registry+https://github.com/rust-lang/crates.io-index"
101checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
102dependencies = [
103 "proc-macro2",
104]
105
106[[package]]
107name = "rusqlite"
108version = "0.29.0"
109source = "registry+https://github.com/rust-lang/crates.io-index"
110checksum = "549b9d036d571d42e6e85d1c1425e2ac83491075078ca9a15be021c56b1641f2"
111dependencies = [
112 "bitflags",
113 "fallible-iterator",
114 "fallible-streaming-iterator",
115 "hashlink",
116 "libsqlite3-sys",
117 "smallvec",
118]
119
120[[package]]
121name = "smallvec"
122version = "1.15.1"
123source = "registry+https://github.com/rust-lang/crates.io-index"
124checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
125
126[[package]]
127name = "syn"
128version = "2.0.117"
129source = "registry+https://github.com/rust-lang/crates.io-index"
130checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
131dependencies = [
132 "proc-macro2",
133 "quote",
134 "unicode-ident",
135]
136
137[[package]]
138name = "unicode-ident"
139version = "1.0.24"
140source = "registry+https://github.com/rust-lang/crates.io-index"
141checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
142
143[[package]]
144name = "vcpkg"
145version = "0.2.15"
146source = "registry+https://github.com/rust-lang/crates.io-index"
147checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
148
149[[package]]
150name = "version_check"
151version = "0.9.5"
152source = "registry+https://github.com/rust-lang/crates.io-index"
153checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
154
155[[package]]
6name = "winapi" 156name = "winapi"
7version = "0.3.9" 157version = "0.3.9"
8source = "registry+https://github.com/rust-lang/crates.io-index" 158source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -28,5 +178,26 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
28name = "zagan-bet" 178name = "zagan-bet"
29version = "0.1.0" 179version = "0.1.0"
30dependencies = [ 180dependencies = [
181 "rusqlite",
31 "winapi", 182 "winapi",
32] 183]
184
185[[package]]
186name = "zerocopy"
187version = "0.8.40"
188source = "registry+https://github.com/rust-lang/crates.io-index"
189checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5"
190dependencies = [
191 "zerocopy-derive",
192]
193
194[[package]]
195name = "zerocopy-derive"
196version = "0.8.40"
197source = "registry+https://github.com/rust-lang/crates.io-index"
198checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953"
199dependencies = [
200 "proc-macro2",
201 "quote",
202 "syn",
203]
diff --git a/Cargo.toml b/Cargo.toml
index 33903f4..85cacac 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,4 +4,5 @@ version = "0.1.0"
4edition = "2024" 4edition = "2024"
5 5
6[dependencies] 6[dependencies]
7rusqlite = "0.29"
7winapi = { version = "0.3", features = ["consoleapi", "processenv", "wincon", "minwindef", "winbase"] } 8winapi = { version = "0.3", features = ["consoleapi", "processenv", "wincon", "minwindef", "winbase"] }
diff --git a/client.db b/client.db
new file mode 100644
index 0000000..8ead0e2
--- /dev/null
+++ b/client.db
Binary files differ
diff --git a/src/bin/client.rs b/src/bin/client.rs
deleted file mode 100644
index 45c5090..0000000
--- a/src/bin/client.rs
+++ /dev/null
@@ -1,98 +0,0 @@
1use std::io::{BufRead, BufReader, Write, stdin, stdout};
2use std::net::TcpStream;
3use std::time::Instant;
4
5#[cfg(windows)]
6mod win {
7 use std::io;
8 use std::os::windows::io::AsRawHandle;
9 use winapi::um::consoleapi::GetConsoleMode;
10 use winapi::um::consoleapi::SetConsoleMode;
11 use winapi::um::wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING;
12 use winapi::um::winbase::STD_OUTPUT_HANDLE;
13 use winapi::um::processenv::GetStdHandle;
14 use winapi::shared::minwindef::DWORD;
15
16 pub fn enable_vt() -> io::Result<()> {
17 unsafe {
18 let h = GetStdHandle(STD_OUTPUT_HANDLE);
19 if h.is_null() { return Err(io::Error::last_os_error()); }
20
21 let mut mode: DWORD = 0;
22 if GetConsoleMode(h, &mut mode) == 0 {
23 return Err(io::Error::last_os_error());
24 }
25 let new = mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING;
26 if SetConsoleMode(h, new) == 0 {
27 return Err(io::Error::last_os_error());
28 }
29 Ok(())
30 }
31 }
32}
33
34fn main() {
35 #[cfg(windows)]
36 win::enable_vt().unwrap();
37
38 let mut addr: Option<String>;
39 let mut stream: Option<TcpStream> = None;
40 loop {
41 print!("Command: ");
42 stdout().flush().unwrap();
43 let mut line = String::new();
44 stdin().read_line(&mut line).unwrap();
45 let line = line.trim();
46 if line.is_empty() {
47 continue;
48 }
49 let parts: Vec<&str> = line.splitn(2, ' ').collect();
50 match parts[0] {
51 "connect" => {
52 if parts.len() < 2 {
53 println!("Usage: connect <addr>");
54 continue;
55 }
56 addr = Some(parts[1].to_string());
57 match TcpStream::connect(addr.as_ref().unwrap()) {
58 Ok(s) => {
59 let mut ping_writer = s.try_clone().unwrap();
60 let mut ping_reader = BufReader::new(s.try_clone().unwrap());
61 let start = Instant::now();
62 writeln!(ping_writer, "PING").unwrap();
63 let mut response = String::new();
64 ping_reader.read_line(&mut response).unwrap();
65 let elapsed = start.elapsed();
66 println!("Connected to {} with ping delay: {:?}", addr.as_ref().unwrap(), elapsed);
67 stream = Some(s);
68 }
69 Err(e) => {
70 println!("Failed to connect: {}", e);
71 }
72 }
73 }
74 "send" => {
75 if stream.is_none() {
76 println!("Not connected. Use 'connect <addr>' first.");
77 continue;
78 }
79 if parts.len() < 2 {
80 println!("Usage: send <message>");
81 continue;
82 }
83 let msg = parts[1];
84 let s = stream.as_mut().unwrap();
85 let writer_stream = s.try_clone().unwrap();
86 let mut writer = writer_stream;
87 let response_reader_stream = s.try_clone().unwrap();
88 let mut response_reader = BufReader::new(response_reader_stream);
89 writeln!(writer, "{}", msg).unwrap();
90 let mut response = String::new();
91 response_reader.read_line(&mut response).unwrap();
92 println!("Echo: {}", response.trim());
93 }
94 "quit" => break,
95 _ => println!("Unknown command. Available: connect, send, quit"),
96 }
97 }
98}
diff --git a/src/bin/client/connection.rs b/src/bin/client/connection.rs
new file mode 100644
index 0000000..9e402cb
--- /dev/null
+++ b/src/bin/client/connection.rs
@@ -0,0 +1,32 @@
1use std::io::{BufRead, BufReader, Write};
2use std::net::TcpStream;
3use std::sync::mpsc;
4use std::thread;
5use std::time::{Duration, Instant};
6
7pub fn connect(addr: &str) -> Result<TcpStream, Box<dyn std::error::Error>> {
8 let (tx, rx) = mpsc::channel();
9 let addr_str = addr.to_string();
10 thread::spawn(move || {
11 match TcpStream::connect(&addr_str) {
12 Ok(s) => tx.send(Ok(s)).unwrap(),
13 Err(e) => tx.send(Err(e)).unwrap(),
14 }
15 });
16 match rx.recv_timeout(Duration::from_secs(3)) {
17 Ok(Ok(s)) => {
18 let mut ping_writer = s.try_clone().unwrap();
19 let mut ping_reader = BufReader::new(s.try_clone().unwrap());
20 let start = Instant::now();
21 writeln!(ping_writer, "PING").unwrap();
22 let mut response = String::new();
23 ping_reader.read_line(&mut response).unwrap();
24 let elapsed = start.elapsed();
25 println!("Connected to {} with ping delay: {:?}", addr, elapsed);
26 Ok(s)
27 }
28 Ok(Err(e)) => Err(Box::new(e)),
29 Err(mpsc::RecvTimeoutError::Timeout) => Err("Connection timed out after 3 seconds".into()),
30 Err(_) => Err("Connection attempt failed".into()),
31 }
32}
diff --git a/src/bin/client/database.rs b/src/bin/client/database.rs
new file mode 100644
index 0000000..bc8f6f8
--- /dev/null
+++ b/src/bin/client/database.rs
@@ -0,0 +1,30 @@
1use rusqlite::Connection;
2
3pub fn init_db() -> Connection {
4 let conn = Connection::open("client.db").unwrap();
5 conn.execute(
6 "CREATE TABLE IF NOT EXISTS hosts (
7 host TEXT PRIMARY KEY,
8 last_used DATETIME DEFAULT CURRENT_TIMESTAMP
9 )",
10 [],
11 ).unwrap();
12 conn
13}
14
15pub fn get_last_host(conn: &Connection) -> Option<String> {
16 let mut stmt = conn.prepare("SELECT host FROM hosts ORDER BY last_used DESC LIMIT 1").unwrap();
17 let mut rows = stmt.query([]).unwrap();
18 if let Ok(Some(row)) = rows.next() {
19 row.get(0).ok()
20 } else {
21 None
22 }
23}
24
25pub fn save_host(conn: &Connection, host: &str) {
26 conn.execute(
27 "INSERT OR REPLACE INTO hosts (host, last_used) VALUES (?1, CURRENT_TIMESTAMP)",
28 [host],
29 ).unwrap();
30}
diff --git a/src/bin/client/main.rs b/src/bin/client/main.rs
new file mode 100644
index 0000000..ce9c92f
--- /dev/null
+++ b/src/bin/client/main.rs
@@ -0,0 +1,17 @@
1mod repl;
2mod database;
3mod connection;
4mod platform;
5
6use repl::ClientState;
7
8fn main() {
9 #[cfg(windows)]
10 platform::win::enable_vt().unwrap();
11
12 let mut state = ClientState::new();
13 if let Some(host) = state.addr.clone() {
14 state.connect_to(&host);
15 }
16 state.run();
17}
diff --git a/src/bin/client/mod.rs b/src/bin/client/mod.rs
new file mode 100644
index 0000000..938d862
--- /dev/null
+++ b/src/bin/client/mod.rs
@@ -0,0 +1,4 @@
1pub mod database;
2pub mod connection;
3pub mod platform;
4pub mod repl;
diff --git a/src/bin/client/platform.rs b/src/bin/client/platform.rs
new file mode 100644
index 0000000..1a6d71b
--- /dev/null
+++ b/src/bin/client/platform.rs
@@ -0,0 +1,28 @@
1#[cfg(windows)]
2pub mod win {
3 use std::io;
4 use std::os::windows::io::AsRawHandle;
5 use winapi::um::consoleapi::GetConsoleMode;
6 use winapi::um::consoleapi::SetConsoleMode;
7 use winapi::um::wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING;
8 use winapi::um::winbase::STD_OUTPUT_HANDLE;
9 use winapi::um::processenv::GetStdHandle;
10 use winapi::shared::minwindef::DWORD;
11
12 pub fn enable_vt() -> io::Result<()> {
13 unsafe {
14 let h = GetStdHandle(STD_OUTPUT_HANDLE);
15 if h.is_null() { return Err(io::Error::last_os_error()); }
16
17 let mut mode: DWORD = 0;
18 if GetConsoleMode(h, &mut mode) == 0 {
19 return Err(io::Error::last_os_error());
20 }
21 let new = mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING;
22 if SetConsoleMode(h, new) == 0 {
23 return Err(io::Error::last_os_error());
24 }
25 Ok(())
26 }
27 }
28}
diff --git a/src/bin/client/repl.rs b/src/bin/client/repl.rs
new file mode 100644
index 0000000..d38c54d
--- /dev/null
+++ b/src/bin/client/repl.rs
@@ -0,0 +1,108 @@
1use std::io::{BufRead, BufReader, Write, stdin, stdout};
2use std::net::TcpStream;
3use rusqlite::Connection;
4
5use crate::connection;
6use crate::database;
7
8pub struct ClientState {
9 pub stream: Option<TcpStream>,
10 pub conn: Connection,
11 pub addr: Option<String>,
12 pub default_port: u16,
13}
14
15impl ClientState {
16 pub fn new() -> Self {
17 let conn = database::init_db();
18 let default_ip = "127.0.0.1".to_string();
19 let default_port = 11525;
20 let last_host = database::get_last_host(&conn);
21 let default_host = format!("{}:{}", default_ip, default_port);
22 let addr = last_host.or(Some(default_host));
23
24 Self {
25 stream: None,
26 conn,
27 addr,
28 default_port,
29 }
30 }
31
32 pub fn connect_to(&mut self, addr: &str) {
33 self.addr = Some(addr.to_string());
34 match connection::connect(addr) {
35 Ok(s) => {
36 self.stream = Some(s);
37 database::save_host(&self.conn, addr);
38 }
39 Err(e) => println!("Failed to connect: {}", e),
40 }
41 }
42
43 pub fn run(&mut self) {
44 loop {
45 print!("> ");
46 stdout().flush().unwrap();
47 let mut line = String::new();
48 stdin().read_line(&mut line).unwrap();
49 let line = line.trim();
50 if line.is_empty() {
51 continue;
52 }
53 let parts: Vec<&str> = line.splitn(2, ' ').collect();
54 match parts[0] {
55 "connect" => {
56 let addr_str = if parts.len() < 2 {
57 if let Some(h) = &self.addr {
58 h.clone()
59 } else {
60 println!("No host specified and no previous connection. Usage: connect <ip>:<port>");
61 continue;
62 }
63 } else {
64 let mut a = parts[1].to_string();
65 if !a.contains(':') {
66 a = format!("{}:{}", a, self.default_port);
67 }
68 a
69 };
70 self.connect_to(&addr_str);
71 }
72 "send" => {
73 if self.stream.is_none() {
74 println!("Not connected. Use 'connect <ip>:<port>' first.");
75 continue;
76 }
77 if parts.len() < 2 {
78 println!("Usage: send <message>");
79 continue;
80 }
81 let msg = parts[1];
82 let s = self.stream.as_mut().unwrap();
83 let writer_stream = s.try_clone().unwrap();
84 let mut writer = writer_stream;
85 let response_reader_stream = s.try_clone().unwrap();
86 let mut response_reader = BufReader::new(response_reader_stream);
87 writeln!(writer, "{}", msg).unwrap();
88 let mut response = String::new();
89 response_reader.read_line(&mut response).unwrap();
90 println!("Echo: {}", response.trim());
91 }
92 "disconnect" => {
93 self.stream = None;
94 println!("Disconnected");
95 }
96 "help" => {
97 help();
98 }
99 "quit" => break,
100 _ => help(),
101 }
102 }
103 }
104}
105
106fn help() {
107 println!("Available commands: connect <ip>:<port>, send <message>, disconnect, help, quit");
108}
diff --git a/src/bin/server.rs b/src/bin/server/args.rs
index 215d2ae..31c0acc 100644
--- a/src/bin/server.rs
+++ b/src/bin/server/args.rs
@@ -1,24 +1,12 @@
1use std::io::{BufRead, BufReader, Write}; 1pub struct Args {
2use std::net::TcpListener; 2 pub ip: String,
3use std::thread; 3 pub port: u16,
4
5fn handle_client(stream: std::net::TcpStream) {
6 println!("Client connected");
7 let reader_stream = stream.try_clone().unwrap();
8 let reader = BufReader::new(reader_stream);
9 let mut writer = stream;
10 for line in reader.lines() {
11 let line = line.unwrap();
12 println!("Received: {}", line);
13 writeln!(writer, "{}", line).unwrap();
14 }
15 println!("Client disconnected");
16} 4}
17 5
18fn main() { 6pub fn parse_args() -> Args {
19 let args: Vec<String> = std::env::args().collect(); 7 let args: Vec<String> = std::env::args().collect();
20 let mut ip = "127.0.0.1".to_string(); 8 let mut ip = "127.0.0.1".to_string();
21 let mut port = 8080; 9 let mut port = 11525;
22 let mut i = 1; 10 let mut i = 1;
23 while i < args.len() { 11 while i < args.len() {
24 if args[i] == "-p" { 12 if args[i] == "-p" {
@@ -49,18 +37,5 @@ fn main() {
49 std::process::exit(1); 37 std::process::exit(1);
50 } 38 }
51 } 39 }
52 let listener = match TcpListener::bind(format!("{}:{}", ip, port)) { 40 Args { ip, port }
53 Ok(l) => l,
54 Err(e) => {
55 eprintln!("Failed to bind to {}:{}: {}", ip, port, e);
56 std::process::exit(1);
57 }
58 };
59 println!("Server listening on {}", listener.local_addr().unwrap());
60 for stream in listener.incoming() {
61 let stream = stream.unwrap();
62 thread::spawn(move || {
63 handle_client(stream);
64 });
65 }
66} 41}
diff --git a/src/bin/server/handler.rs b/src/bin/server/handler.rs
new file mode 100644
index 0000000..906a91f
--- /dev/null
+++ b/src/bin/server/handler.rs
@@ -0,0 +1,15 @@
1use std::io::{BufRead, BufReader, Write};
2use std::net::TcpStream;
3
4pub fn handle_client(stream: TcpStream) {
5 println!("Client connected");
6 let reader_stream = stream.try_clone().unwrap();
7 let reader = BufReader::new(reader_stream);
8 let mut writer = stream;
9 for line in reader.lines() {
10 let line = line.unwrap();
11 println!("Received: {}", line);
12 writeln!(writer, "{}", line).unwrap();
13 }
14 println!("Client disconnected");
15}
diff --git a/src/bin/server/main.rs b/src/bin/server/main.rs
new file mode 100644
index 0000000..2c6e1dc
--- /dev/null
+++ b/src/bin/server/main.rs
@@ -0,0 +1,23 @@
1mod args;
2mod handler;
3
4use std::net::TcpListener;
5use std::thread;
6
7fn main() {
8 let args = args::parse_args();
9 let listener = match TcpListener::bind(format!("{}:{}", args.ip, args.port)) {
10 Ok(l) => l,
11 Err(e) => {
12 eprintln!("Failed to bind to {}:{}: {}", args.ip, args.port, e);
13 std::process::exit(1);
14 }
15 };
16 println!("Server listening on {}", listener.local_addr().unwrap());
17 for stream in listener.incoming() {
18 let stream = stream.unwrap();
19 thread::spawn(move || {
20 handler::handle_client(stream);
21 });
22 }
23}
diff --git a/src/bin/server/mod.rs b/src/bin/server/mod.rs
new file mode 100644
index 0000000..adb60bb
--- /dev/null
+++ b/src/bin/server/mod.rs
@@ -0,0 +1,2 @@
1pub mod args;
2pub mod handler;