Skip to content

Commit fe9a7d5

Browse files
committed
feat: network/client
feat: network/channel feat: crc feat: utils/trace fix: StringByte docs: update README
1 parent 990dd5c commit fe9a7d5

File tree

24 files changed

+3062
-367
lines changed

24 files changed

+3062
-367
lines changed

Cargo.toml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,16 @@ A collection of quakeworld related libraries.
1414
[lib]
1515

1616
[features]
17-
default = ["mvd", "utils", "protocol", "ascii_strings"]
17+
default = ["mvd", "utils", "protocol", "state", "network", "trace", "connection", "crc" ]
18+
connection = ["protocol", "state", "network", "crc", "ascii_strings"]
19+
state = ["protocol", "utils"]
1820
mvd = ["utils", "protocol"]
1921
utils = []
2022
protocol = ["protocol-macros"]
2123
ascii_strings = ["utils"]
24+
network = []
25+
trace = []
26+
crc = []
2227

2328
[dependencies]
2429
protocol-macros = { version="0.0.1", path = "./protocol-macros", package="quakeworld-protocol-macros", optional=true }
@@ -33,3 +38,7 @@ serde = { version = "1.0", features = ["derive"] }
3338
serde_json = "1.0"
3439
simple-error = "0.2"
3540
lazy_static = "1.4.0"
41+
termcolor = "1.1.3"
42+
syn = "1.0.103"
43+
quote = "1.0.21"
44+
unstringify = "0.1.4"

README.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,22 @@ quakeworld
33
A rust library for working with quakeworld.
44
### available features
55
* protocol
6-
* [quakeworld::protocol::message::Message](./src/protocol/message.rs) - reading data types from a byte array
6+
* [quakeworld::protocol::message::Message](./src/protocol/message/mod.rs) - reading data types from a byte array
77
* [quakeworld::protocol::types](./src/protocol/types.rs) - data types
88

99
* mvd
1010
* [quakeworld::mvd::Mvd](./src/mvd/mod.rs) - parsing mvd file format
1111

12+
* state
13+
* [quakeworld::state::State](./src/state/mod.rs) - using Message types to create a game state
14+
1215
* utils
1316
* [quakeworld::utils::AsciiConverter](./src/utils/ascii_converter.rs) - converting byte arrays to printable ascii
1417
* [quakeworld::utils::Userinfo](./src/utils/userinfo.rs) - parsing userinfo strings
18+
* [quakeworld::utils::trace](./src/utils/trace.rs) - functions to print message read traces (see [here](./examples/trace.rs) for an example
19+
20+
* crc
21+
* [quakeworld::crc](./src/crc/mod.rs) - checksum functions
1522

1623
* ascii_strings - when reading strings they will be converted to printable ascii, original bytes are also being kept see [here](./src/protocol/types.rs#L12)
1724

@@ -20,8 +27,6 @@ Everything is serializable via [serde](https://github.com/serde-rs/serde) (json,
2027

2128
### Goals
2229
probably in order of being implemented too
23-
* state - generating a game state by consuming types
24-
* network - client
2530
* qwd - qwd format parsing
2631
* mvd - creating a mvd from states
2732

@@ -30,5 +35,6 @@ could be better, aka non existing at the moment
3035

3136
### Example
3237
* [minimal mvd parser](./examples/mvd_parser.rs)
38+
* [minimal client](./examples/client.rs)
3339
* [quakeworld swiss army knife](https://github.com/jogi1/qwsak)
3440
* [more elaborate mvd parser](https://github.com/jogi1/statyr) soon™

examples/client.rs

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
use std::error::Error;
2+
use std::net::UdpSocket;
3+
use std::env;
4+
use std::time::{Duration, Instant};
5+
6+
use quakeworld::protocol::message::Message;
7+
use quakeworld::protocol::types::{Packet,ServerMessage};
8+
use quakeworld::network::connection::{client, client::ClientConnectionState};
9+
use quakeworld::utils::ascii_converter::AsciiConverter;
10+
11+
fn generate_crc(_map_name: String) -> u32 {
12+
return 1849737252; // maps/maphub_v1.bsp
13+
}
14+
15+
fn connect(local_ip: String, remote_ip: String) -> Result<bool, Box<dyn Error>> {
16+
// initialize socket and client struct
17+
let ascii_converter = AsciiConverter::new();
18+
let socket = UdpSocket::bind(local_ip.clone())?;
19+
let mut buf = vec![0_u8; 1024 * 4];
20+
let s_a = socket.local_addr()?;
21+
let _ = socket.set_read_timeout(Some(Duration::new(2, 0)));
22+
let mut client = client::Client::new(s_a.to_string(), ascii_converter);
23+
24+
let mut last_time = Instant::now();
25+
26+
client.userinfo.update_from_string("name", "rust_user");
27+
client.userinfo.update_from_string("*client", "rust_quake");
28+
client.userinfo.update_from_string("spectator", "1");
29+
client.userinfo.update_from_string("rate", "25000");
30+
31+
// generate and send the challenge package
32+
let get_challenge_packet = client.connect(s_a.port());
33+
let r = socket.send_to(&get_challenge_packet, &remote_ip)?;
34+
assert_eq!(r, get_challenge_packet.len());
35+
36+
let mut count = 0;
37+
let mut connected = false;
38+
loop {
39+
// recieve server packet
40+
let mut message = Message::empty();
41+
message.bigendian = true;
42+
let server_packet = match socket.recv_from(&mut buf) {
43+
Ok((n, _)) => {
44+
buf[..n].to_vec()
45+
},
46+
Err(..) => {
47+
[].to_vec()
48+
},
49+
};
50+
51+
// handle a timeout
52+
if server_packet.is_empty() {
53+
let status = client.handle_timeout()?;
54+
if let Some(mut response) = status.response {
55+
let r = socket.send_to(&response, &remote_ip)?;
56+
assert_eq!(r, response.len());
57+
}
58+
continue;
59+
}
60+
61+
// parse the packet
62+
let status = client.handle_packet(server_packet)?;
63+
// see if we need to send a response
64+
if let Some(mut response) = status.response {
65+
if last_time.elapsed().as_secs() > 10 {
66+
message.write_client_command_string(format!("say hello! from rust for the {}... time", count));
67+
count += 1;
68+
response.extend(message.buffer.to_vec());
69+
last_time = Instant::now();
70+
}
71+
let r = socket.send_to(&response, &remote_ip)?;
72+
assert_eq!(r, response.len());
73+
}
74+
75+
// reduce socket read timeout once we are connected
76+
if client.state == ClientConnectionState::Connected && !connected {
77+
let _ = socket.set_read_timeout(Some(Duration::new(0, 200000000)));
78+
connected = true;
79+
}
80+
81+
if let Some(p) = status.packet {
82+
match p {
83+
Packet::Connected(p) => {
84+
for message in p.messages {
85+
match message {
86+
ServerMessage::Print(pr) => {
87+
println!("{}", pr.message.string);
88+
if pr.message.string.contains("rust panic!") {
89+
return Ok(false);
90+
}
91+
},
92+
ServerMessage::Disconnect(..) => {
93+
println!("we got diconnected :(");
94+
return Ok(true);
95+
},
96+
ServerMessage::Modellist(modellist) => {
97+
if modellist.start == 0 {
98+
client.map_crc = generate_crc(modellist.models[0].string.clone())
99+
}
100+
}
101+
_ => {}
102+
}
103+
}
104+
}
105+
_ => {}
106+
}
107+
}
108+
}
109+
}
110+
111+
fn main() {
112+
let args: Vec<String> = env::args().collect();
113+
if args.len() != 2 {
114+
println!("need to supply a remote ip");
115+
return
116+
}
117+
let remote_ip = &args[1];
118+
match connect("0.0.0.0:0".to_string(), remote_ip.to_string()) {
119+
Ok(rv) => {
120+
if rv {
121+
println!("we got disconnected");
122+
} else {
123+
println!("we were told to panic");
124+
}
125+
}
126+
Err(err) => {
127+
eprintln!("could not connect to {}: {}", remote_ip, err);
128+
}
129+
}
130+
}

examples/mvd_parser.rs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ use std::error::Error;
33
use std::fs::File;
44
use std::env;
55
use quakeworld::mvd::Mvd;
6+
use quakeworld::state::State;
7+
use quakeworld::protocol::types::ServerMessage;
68

79
// the most basic implementation of a mvd parser
810
fn parse_file(filename: String) -> Result<bool, Box<dyn Error>> {
@@ -20,15 +22,26 @@ fn parse_file(filename: String) -> Result<bool, Box<dyn Error>> {
2022
return Err(Box::new(err))
2123
}
2224
};
25+
#[cfg(feature = "ascii_strings")]
26+
let mut mvd = Mvd::new(*buffer, None)?;
27+
#[cfg(not(feature = "ascii_strings"))]
2328
let mut mvd = Mvd::new(*buffer)?;
29+
let mut state = State::new();
2430
while mvd.finished == false {
2531
let frame = mvd.parse_frame()?;
26-
println!("--- frame {}:{} ---", frame.frame, frame.time);
32+
// frame count and demo time
33+
//println!("--- frame {}:{} ---", frame.frame, frame.time);
34+
// if you need to keep the last state
35+
// let old_state = state.clone();
36+
state.apply_messages_mvd(&frame.messages, frame.last);
37+
// get the players when intermission is reached
2738
for message in frame.messages {
28-
// will just print the message name
29-
println!("{}", message);
30-
// for more verbose output
31-
// println!("{:?}", message);
39+
match message {
40+
ServerMessage::Intermission(_) => {
41+
println!("{:#?}", state.players);
42+
}
43+
_ => {}
44+
}
3245
}
3346
}
3447
return Ok(true)

examples/trace.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
use quakeworld::protocol::message::{Message, MessageFlags, MessageType};
2+
use quakeworld::protocol::types::{ServerClient, Print, ServerMessage};
3+
use quakeworld::utils::trace::print_message_trace;
4+
fn main() {
5+
let b: Vec<u8> = vec![8, 2, 0x68, 0x65, 0x6c, 0x6c, 0x6f,0x0];
6+
let mut message = Message::new(Box::new(b.clone()), 0, b.len(), false, MessageFlags {..Default::default()}, None, MessageType::Connection);
7+
message.trace.enabled = true;
8+
let msg_cmd = match message.read_u8(false) {
9+
Ok(cmd) => cmd,
10+
Err(e) => panic!("{}", e),
11+
};
12+
let cmd = match ServerClient::try_from(msg_cmd) {
13+
Ok(cmd) => cmd,
14+
Err(_) => panic!("failed reading print cmd"),
15+
};
16+
17+
let ret = match cmd.read_message(&mut message) {
18+
Ok(cmd) => cmd,
19+
Err(_) => panic!("failed reading print"),
20+
};
21+
match ret {
22+
ServerMessage::Print(p) => {
23+
assert_eq!(p.from, 2);
24+
assert_eq!(p.message.string, "hello");
25+
println!("{:?}", p);
26+
},
27+
_ => { panic!("its not print!");},
28+
}
29+
print_message_trace(&message);
30+
}

protocol-macros/src/lib.rs

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@ use proc_macro::TokenStream;
22
use quote::{quote, format_ident};
33
use syn::{Data, DataStruct, Fields};
44

5+
/*
6+
#[proc_macro]
7+
pub fn to_value(_item: TokenStream) -> TokenStream {
8+
fn type_of<T>(_: T) -> &'static str {
9+
std::any::type_name::<T>()
10+
}
11+
println!("{:?}", _item);
12+
"fn answer() -> u32 { 42 }".parse().unwrap()
13+
}
14+
*/
15+
516
#[proc_macro_derive(ParseMessage)]
617
pub fn parse_message_derive(input: TokenStream) -> TokenStream {
718
// Construct a representation of Rust code as a syntax tree
@@ -18,7 +29,22 @@ fn impl_parsemessage_macro(ast: &syn::DeriveInput) -> TokenStream {
1829
_ => panic!("expected a struct with named fields"),
1930
};
2031
let field_name = fields.iter().map(|field| &field.ident);
21-
let field_name2 = fields.iter().map(|field| &field.ident);
32+
let field_name_annotate = fields.iter().map(|field| {
33+
let ft = &field.ident;
34+
let q = quote! { #ft };
35+
format!("{}", q.to_string())
36+
});
37+
let field_name_value = fields.iter().map(|field| {
38+
let ft = &field.ident;
39+
let q = quote! { #ft };
40+
format_ident!("{}_value", q.to_string())
41+
});
42+
let field_name_value1 = fields.iter().map(|field| {
43+
let ft = &field.ident;
44+
let q = quote! { #ft };
45+
format_ident!("{}_value", q.to_string())
46+
});
47+
2248
let field_function = fields.iter().map(|field| {
2349
let ft = &field.ty;
2450
let q = quote! { #ft };
@@ -31,18 +57,20 @@ fn impl_parsemessage_macro(ast: &syn::DeriveInput) -> TokenStream {
3157
impl #struct_name {
3258
fn read(message: &mut Message) -> Result<ServerMessage, MessageError>
3359
{
60+
trace_start!(message, false);
3461
#(
35-
let #field_name = message.#field_function(false)?;
62+
trace_annotate!(message, #field_name_annotate);
63+
let #field_name_value = message.#field_function(false)?;
3664
)*
65+
let v = ServerMessage::#struct_name(
66+
#struct_name{
67+
#(
68+
#field_name: #field_name_value1,
69+
)*
70+
});
3771

38-
Ok(
39-
ServerMessage::#struct_name(
40-
#struct_name{
41-
#(
42-
#field_name2,
43-
)*
44-
})
45-
)
72+
trace_stop!(message, v);
73+
Ok(v)
4674
}
4775
}
4876
};

0 commit comments

Comments
 (0)