Skip to content

Commit f0a9034

Browse files
authored
V0.8.0 (#3)
1 parent ed00654 commit f0a9034

File tree

7 files changed

+273
-302
lines changed

7 files changed

+273
-302
lines changed

Cargo.toml

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,26 @@ description = "Get server addresses from QuakeWorld master servers."
44
keywords = ["masters", "quake", "quakeworld", "servers"]
55
repository = "https://github.com/quakeworld/masterstat"
66
authors = ["Viktor Persson <viktor.persson@arcsin.se>"]
7-
version = "0.7.0"
7+
version = "0.8.0"
88
edition = "2024"
99
license = "MIT"
10-
include = [
11-
"/Cargo.toml",
12-
"/LICENSE",
13-
"/README.md",
14-
"/src/**",
15-
"/tests/**",
16-
]
17-
18-
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
10+
include = ["/Cargo.toml", "/LICENSE", "/README.md", "/src/**", "/tests/**"]
1911

2012
[dependencies]
2113
anyhow = "1.0.97"
22-
binrw = "0.14.1"
14+
binrw = "0.15.0"
2315
futures = "0.3.31"
24-
tokio = { version = "1.44.1", features = ["macros", "net", "rt-multi-thread", "sync", "time"] }
25-
tinyudp = "0.5.1"
26-
27-
serde = { optional = true, version = "1.0.219", features = ["derive"] }
28-
serde_json = { optional = true, version = "1.0.140" }
16+
tokio = { version = "1.44.1", features = [
17+
"macros",
18+
"net",
19+
"rt-multi-thread",
20+
"sync",
21+
"time",
22+
] }
23+
tinyudp = { version = "0.6.0", features = ["tokio"] }
2924

3025
[dev-dependencies]
3126
pretty_assertions = "1.4.1"
3227

3328
[features]
3429
ci = []
35-
json = ["dep:serde", "dep:serde_json"]

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# masterstat [![Test](https://github.com/quakeworld/masterstat/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/quakeworld/masterstat/actions/workflows/test.yml) [![crates](https://img.shields.io/crates/v/masterstat)](https://crates.io/crates/masterstat) [![docs.rs](https://img.shields.io/docsrs/masterstat)](https://docs.rs/masterstat/)
22

3-
> Get server addresses from QuakeWorld master servers
3+
> A Rust crate for querying QuakeWorld master servers
44
55
## Installation
66

src/command.rs

Lines changed: 0 additions & 199 deletions
This file was deleted.

src/lib.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
//!
33
//! Get server addresses from QuakeWorld master servers.
44
5-
mod command;
5+
mod query;
6+
mod query_multiple;
67
mod server_address;
78

8-
pub use crate::command::{server_addresses, server_addresses_from_many};
9-
pub use crate::server_address::ServerAddress;
9+
pub use crate::query::query;
10+
pub use crate::query_multiple::{MultiQueryResult, query_multiple};

src/query.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
use std::{io::Cursor, time::Duration};
2+
3+
use anyhow::{Result, anyhow as e};
4+
use binrw::BinRead;
5+
6+
use crate::server_address::RawServerAddress;
7+
8+
/// Get server addresses from a single master server
9+
///
10+
/// # Example
11+
///
12+
/// ```
13+
/// use std::time::Duration;
14+
///
15+
/// async fn test() {
16+
/// let master = "master.quakeworld.nu:27000";
17+
/// let timeout = Duration::from_secs(2);
18+
/// match masterstat::query(&master, timeout).await {
19+
/// Ok(addresses) => { println!("found {} server addresses", addresses.len()) },
20+
/// Err(e) => { eprintln!("error: {}", e); }
21+
/// }
22+
/// }
23+
/// ```
24+
pub async fn query(master_address: &str, timeout: Duration) -> Result<Vec<String>> {
25+
const STATUS_MSG: [u8; 3] = [99, 10, 0];
26+
const BUFFER_SIZE: usize = 64 * 1024;
27+
let options = tinyudp::ReadOptions::new(timeout, BUFFER_SIZE);
28+
let response = tinyudp::send_and_receive_async(master_address, &STATUS_MSG, options).await?;
29+
parse_response(&response)
30+
}
31+
32+
fn parse_response(response: &[u8]) -> Result<Vec<String>> {
33+
const RESPONSE_HEADER: [u8; 6] = [255, 255, 255, 255, 100, 10];
34+
35+
if !response.starts_with(&RESPONSE_HEADER) {
36+
return Err(e!("Invalid response"));
37+
}
38+
39+
let body = &mut Cursor::new(&response[RESPONSE_HEADER.len()..]);
40+
let mut addresses = vec![];
41+
42+
while let Ok(raw_address) = RawServerAddress::read(body) {
43+
addresses.push(raw_address.to_string());
44+
}
45+
46+
Ok(addresses)
47+
}
48+
49+
#[cfg(test)]
50+
mod tests {
51+
use super::*;
52+
use pretty_assertions::assert_eq;
53+
54+
#[tokio::test]
55+
#[cfg_attr(feature = "ci", ignore)]
56+
async fn test_query() -> Result<()> {
57+
let master = "master.quakeservers.net:27000";
58+
let timeout = Duration::from_secs(2);
59+
let result = query(master, timeout).await?;
60+
assert!(!result.is_empty());
61+
Ok(())
62+
}
63+
64+
#[tokio::test]
65+
async fn test_parse_response() -> Result<()> {
66+
// invalid response header
67+
{
68+
let response = [0xff, 0xff];
69+
let result = parse_response(&response);
70+
assert_eq!(result.unwrap_err().to_string(), "Invalid response");
71+
}
72+
73+
// valid response
74+
{
75+
let response = [
76+
0xff, 0xff, 0xff, 0xff, 0x64, 0x0a, 192, 168, 1, 1, 0x75, 0x30, 192, 168, 1, 2,
77+
0x75, 0x30,
78+
];
79+
let result = parse_response(&response)?;
80+
assert_eq!(result.len(), 2);
81+
assert_eq!(result[0], "192.168.1.1:30000".to_string());
82+
assert_eq!(result[1], "192.168.1.2:30000".to_string());
83+
}
84+
85+
Ok(())
86+
}
87+
}

0 commit comments

Comments
 (0)