specmc_protocol/
packets.rs

1use specmc_base::{
2    ensure_tokens,
3    parse::{Identifier, Parse, ParseError},
4};
5use strtoint::strtoint;
6
7use crate::base::FieldList;
8
9#[derive(Debug, Clone, PartialEq, Eq, Hash)]
10pub enum Direction {
11    Serverbound,
12    Clientbound,
13}
14impl Parse for Direction {
15    fn parse(tokens: &mut Vec<String>) -> Result<Self, ParseError> {
16        use Direction::*;
17        match tokens.pop().ok_or(ParseError::EndOfFile)?.as_str() {
18            "serverbound" => Ok(Serverbound),
19            "clientbound" => Ok(Clientbound),
20            token => Err(ParseError::InvalidToken {
21                token: token.to_string(),
22                error: "Invalid direction".to_string(),
23            }),
24        }
25    }
26}
27
28#[derive(Debug, Clone, PartialEq)]
29pub struct Packet {
30    pub name: Identifier,
31    pub direction: Direction,
32    pub state: Identifier,
33    pub id: u32,
34    pub fields: FieldList,
35}
36impl Parse for Packet {
37    fn parse(tokens: &mut Vec<String>) -> Result<Self, ParseError> {
38        ensure_tokens!(tokens, "packet");
39        let name: Identifier = Identifier::parse(tokens)?;
40        ensure_tokens!(tokens, "(");
41        let direction: Direction = Direction::parse(tokens)?;
42        ensure_tokens!(tokens, ",");
43        let state: Identifier = Identifier::parse(tokens)?;
44        ensure_tokens!(tokens, ",");
45        let id: String = tokens.pop().ok_or(ParseError::EndOfFile)?;
46        let id: u32 = strtoint(&id).map_err(|_| ParseError::InvalidToken {
47            token: id,
48            error: "Invalid packet id".to_string(),
49        })?;
50        ensure_tokens!(tokens, ")");
51        ensure_tokens!(tokens, "{");
52        let fields: FieldList = FieldList::parse(tokens)?;
53        ensure_tokens!(tokens, "}");
54
55        Ok(Packet {
56            name,
57            direction,
58            state,
59            id,
60            fields,
61        })
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use std::collections::HashSet;
68
69    use specmc_base::tokenize;
70
71    use crate::{
72        base::{BaseType, Field, IntegerType, Value},
73        test_parse,
74        types::Type,
75    };
76
77    use super::*;
78
79    #[test]
80    fn test_direction() {
81        let mut tokens: Vec<String> = tokenize!("serverbound clientbound unknown");
82
83        test_parse!(tokens, Direction, Ok(Direction::Serverbound));
84        test_parse!(tokens, Direction, Ok(Direction::Clientbound));
85
86        test_parse!(
87            tokens,
88            Direction,
89            Err(ParseError::InvalidToken {
90                token: "unknown".to_string(),
91                error: "Invalid direction".to_string()
92            })
93        );
94        assert!(tokens.is_empty());
95        test_parse!(tokens, Direction, Err(ParseError::EndOfFile));
96    }
97
98    #[test]
99    fn test_packet() {
100        let mut tokens: Vec<String> = tokenize!(
101            "
102            packet TestPacket(serverbound, Play, 0x42) {
103                i32 number
104                String message
105                bool flag
106                if (flag) {
107                    i32 other
108                }
109                VarInt length = len(data)
110                List[u8] data
111            }
112            packet MalformedPacket(serverbound, Play, 0x42) {
113                i32 number
114                String message
115            "
116        );
117
118        test_parse!(
119            tokens,
120            Packet,
121            Ok(Packet {
122                name: Identifier("TestPacket".to_string()),
123                direction: Direction::Serverbound,
124                state: Identifier("Play".to_string()),
125                id: 66,
126                fields: FieldList(vec![
127                    Field {
128                        ty: Type::BaseType(BaseType::Integer(IntegerType::I32)),
129                        name: Identifier("number".to_string()),
130                        value: None,
131                        conditions: HashSet::new(),
132                    },
133                    Field {
134                        ty: Type::BaseType(BaseType::String { length: None }),
135                        name: Identifier("message".to_string()),
136                        value: None,
137                        conditions: HashSet::new(),
138                    },
139                    Field {
140                        ty: Type::BaseType(BaseType::Bool),
141                        name: Identifier("flag".to_string()),
142                        value: None,
143                        conditions: HashSet::new(),
144                    },
145                    Field {
146                        ty: Type::BaseType(BaseType::Integer(IntegerType::I32)),
147                        name: Identifier("other".to_string()),
148                        value: None,
149                        conditions: HashSet::from_iter(vec!["flag".to_string()]),
150                    },
151                    Field {
152                        ty: Type::BaseType(BaseType::Integer(IntegerType::VarInt)),
153                        name: Identifier("length".to_string()),
154                        value: Some(Value::Length(Identifier("data".to_string()))),
155                        conditions: HashSet::new(),
156                    },
157                    Field {
158                        ty: Type::BaseType(BaseType::List {
159                            ty: Box::new(Type::BaseType(BaseType::Integer(IntegerType::U8))),
160                            length: None,
161                        }),
162                        name: Identifier("data".to_string()),
163                        value: None,
164                        conditions: HashSet::new(),
165                    },
166                ])
167            })
168        );
169
170        test_parse!(tokens, Packet, Err(ParseError::EndOfFile));
171        assert!(tokens.is_empty());
172    }
173}