http2byond/
lib.rs

1use bytes::{Buf, BufMut, Bytes, BytesMut};
2use std::io::prelude::*;
3use std::net::{SocketAddr, TcpStream};
4use std::time::Duration;
5
6/// This enum represents the possible return types of send_byond
7/// It can be nothing, a String (containing String), or a Number (containing f32)
8pub enum ByondTopicValue {
9    None,
10    String(String),
11    Number(f32),
12}
13
14/// Main (and only) function of this library.
15///
16/// # Arguments
17///
18/// * `target` - A TCP SocketAddr of a Dream Daemon instance.
19/// * `topic` - The string you want sent to Dream Daemon. Make sure to always start this with the character `?`.ByondTopicValue
20///
21/// # Examples
22///
23/// ```
24/// use http2byond::{send_byond, ByondTopicValue};
25/// use std::net::SocketAddr;
26/// match send_byond(&SocketAddr::from(([127, 0, 0, 1], 27012)), "?status") {
27///     Err(_) => {}
28///     Ok(btv_result) => {
29///         match btv_result {
30///             ByondTopicValue::None => println!("Byond returned nothing"),
31///             ByondTopicValue::String(str) => println!("Byond returned string {}", str),
32///             ByondTopicValue::Number(num) => println!("Byond returned number {}", num),
33///         }
34///     }
35/// }
36/// ```
37pub fn send_byond(target: &SocketAddr, topic: &str) -> std::io::Result<ByondTopicValue> {
38    let mut stream = TcpStream::connect(target)?;
39    stream.set_read_timeout(Some(Duration::new(5, 0)))?;
40
41    let topic_bytes = topic.as_bytes();
42
43    let mut buf = BytesMut::with_capacity(1024);
44    // Header of 00 83
45    buf.put_u16(0x0083);
46
47    // Unsigned short of data length
48    buf.put_u16(topic_bytes.len() as u16 + 6);
49
50    // 40 bytes of padding
51    buf.put_u32(0x0);
52    buf.put_u8(0x0);
53
54    // Append our topic
55    buf.put(topic_bytes);
56
57    // End with a 00
58    buf.put_u8(0x0);
59
60    stream.write_all(&buf)?;
61
62    // Receive response
63    let mut recv_buf = [0; 4096];
64    let bytes_read = stream.read(&mut recv_buf)?;
65
66    if bytes_read == 0 {
67        return Ok(ByondTopicValue::None);
68    }
69
70    let mut recv_buf = Bytes::from(Vec::from(recv_buf));
71
72    if recv_buf.try_get_u16()? == 0x0083 {
73        let mut size = recv_buf.try_get_u16()? - 1;
74        let data_type = recv_buf.try_get_u8()?;
75
76        let ret = match data_type {
77            0x2a => ByondTopicValue::Number(recv_buf.try_get_f32_le()?),
78            0x06 => {
79                let mut str = String::new();
80                while size > 0 {
81                    str.push(recv_buf.try_get_u8()? as char);
82                    size -= 1;
83                }
84                ByondTopicValue::String(str)
85            }
86            _ => ByondTopicValue::None,
87        };
88
89        return Ok(ret);
90    }
91
92    Ok(ByondTopicValue::None)
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn it_works() {
101        let res = send_byond(&SocketAddr::from(([127, 0, 0, 1], 27012)), "?status");
102        match res {
103            Err(x) => panic!("Error from send_byond {x}"),
104            Ok(wrapper) => match wrapper {
105                ByondTopicValue::None => println!("Returned NONE"),
106                ByondTopicValue::String(s) => println!("Returned string {s}"),
107                ByondTopicValue::Number(num) => println!("Returned f32 {num}"),
108            },
109        }
110    }
111}