skrillax_protocol/
lib.rs

1//! Provides a macro to create 'protocols'.
2//!
3//! This expected to be used for the [skrillax_stream] crate.
4//!
5//! A protocol defines a set of opcodes and their respective structures. It is
6//! essentially a mapping of `opcode -> struct`. To encourage more static
7//! dispatch and better developer ergonomics, we want to provide a nice way of
8//! constructing these mappings. Otherwise, this would become quite tedious.
9//! Additionally, this also generates some convenience functions to
10//! automatically move between different protocols that are related.
11//!
12//! The macro to use is the [define_protocol!] macro - any other macro exports
13//! are just helper macros and should be ignored.
14
15#[doc(hidden)]
16#[macro_export]
17macro_rules! __match_packet_opcode {
18    ($opcodeVar:ident =>) => {false};
19    ($opcodeVar:ident => $($packet:ident),+) => {
20        $(<$packet as $crate::__internal::Packet>::ID == $opcodeVar)||+
21    };
22}
23
24#[doc(hidden)]
25#[macro_export]
26macro_rules! __match_protocol_opcode {
27    ($opcodeVar:ident =>) => {false};
28    ($opcodeVar:ident => $($proto:ident),+) => {
29        $($proto::has_opcode($opcodeVar))||+
30    };
31}
32
33/// Defines a protocol from a list of packets and/or other protocols.
34///
35/// A protocol always has a name and may contain packets and/or other
36/// protocols. This protocol will then be represented as an enum, where
37/// each of the packets/protocols is its own variant. With this enum,
38/// all the necessary traits for usage with [skrillax_stream] will then
39/// be implemented. In particular, the following traits will be
40/// implement by the generated enum:
41/// - [InputProtocol](skrillax_stream::InputProtocol)
42/// - [OutputProtocol](skrillax_stream::OutputProtocol)
43/// - [From], to create the protocol from a variant value
44/// - [TryFrom], to extract a variant value from the protocol
45///
46/// The basic macro invokation looks like this:
47/// ```text
48/// define_protocol! { MyProtocolName =>
49///     MyPacket,
50///     MyOtherPacket
51///     +
52///     MyProtocol
53/// }
54/// ```
55/// This assumes `MyPacket` & `MyOtherPacket` derive
56/// [skrillax_packet::Packet] and `MyProtocol` has also been created
57/// using `define_protocol!`.
58///
59/// (!) One limitation of `define_protocol!` is, because it always provides
60/// an implementation for
61/// [InputProtocol](skrillax_stream::InputProtocol) &
62/// [OutputProtocol](skrillax_stream::OutputProtocol), it requires all
63/// packets _and_ protocols to be both `Serialize` & `Derserialize`. For
64/// some packets, that may not be possible, for example when there's a
65/// zero-length optional field. Any protocols those packets are included
66/// would also automatically not be both serialize and deserialize and
67/// could thus also not be used.
68#[macro_export]
69macro_rules! define_protocol {
70    ($name:ident => $($enumValue:ident),*) => {
71        define_protocol! {
72            $name => $($enumValue),* +
73        }
74    };
75    ($name:ident => $($enumValue:ident),* + $($innerProto:ident),*) => {
76        $crate::define_protocol_enum! { $name =>
77            $($enumValue),* + $($innerProto),*
78        }
79
80        $crate::define_input_protocol! { $name =>
81            $($enumValue),* + $($innerProto),*
82        }
83
84        $crate::define_output_protocol! { $name =>
85            $($enumValue),* + $($innerProto),*
86        }
87    };
88}
89
90/// Defines an "outbound" protocol, i.e. a protocol we only care about
91/// sending out to another party.
92#[macro_export]
93macro_rules! define_outbound_protocol {
94    ($name:ident => $($enumValue:ident),*) => {
95        define_outbound_protocol! {
96            $name => $($enumValue),* +
97        }
98    };
99    ($name:ident => $($enumValue:ident),* + $($innerProto:ident),*) => {
100        $crate::define_protocol_enum! { $name =>
101            $($enumValue),* + $($innerProto),*
102        }
103
104        $crate::define_output_protocol! { $name =>
105            $($enumValue),* + $($innerProto),*
106        }
107    }
108}
109
110/// Defines an "inbound" protocol, i.e. a protocol we only care about
111/// receiving from another party.
112#[macro_export]
113macro_rules! define_inbound_protocol {
114    ($name:ident => $($enumValue:ident),*) => {
115        define_inbound_protocol! {
116            $name => $($enumValue),* +
117        }
118    };
119    ($name:ident => $($enumValue:ident),* + $($innerProto:ident),*) => {
120        $crate::define_protocol_enum! { $name =>
121            $($enumValue),* + $($innerProto),*
122        }
123
124        $crate::define_input_protocol! { $name =>
125            $($enumValue),* + $($innerProto),*
126        }
127    }
128}
129
130#[doc(hidden)]
131#[macro_export]
132macro_rules! define_protocol_enum {
133    ($name:ident => $($enumValue:ident),* + $($innerProto:ident),*) => {
134        #[derive(Debug, Clone)]
135        pub enum $name {
136            $(
137                $enumValue($enumValue),
138            )*
139            $(
140                $innerProto($innerProto),
141            )*
142        }
143
144        $(
145            impl From<$enumValue> for $name {
146                fn from(value: $enumValue) -> Self {
147                    $name::$enumValue(value)
148                }
149            }
150        )*
151
152        $(
153            impl From<$innerProto> for $name {
154                fn from(value: $innerProto) -> Self {
155                    $name::$innerProto(value)
156                }
157            }
158        )*
159
160        $(
161            impl TryFrom<$name> for $enumValue {
162                type Error = $name;
163                fn try_from(value: $name) -> Result<Self, Self::Error> {
164                    #[allow(unreachable_patterns)]
165                    match value {
166                        $name::$enumValue(inner) => Ok(inner),
167                        _ => Err(value)
168                    }
169                }
170            }
171        )*
172
173        $(
174            impl TryFrom<$name> for $innerProto {
175                type Error = $name;
176                fn try_from(value: $name) -> Result<Self, Self::Error> {
177                    #[allow(unreachable_patterns)]
178                    match value {
179                        $name::$innerProto(inner) => Ok(inner),
180                        _ => Err(value)
181                    }
182                }
183            }
184        )*
185    }
186}
187
188#[doc(hidden)]
189#[macro_export]
190macro_rules! define_input_protocol {
191    ($name:ident => $($enumValue:ident),* + $($innerProto:ident),*) => {
192        impl $crate::__internal::InputProtocol for $name {
193            type Proto = Box<$name>;
194
195            fn create_from(opcode: u16, data: &[u8]) -> Result<(usize, Box<Self>), $crate::__internal::InStreamError> {
196                match opcode {
197                    $(
198                        <$enumValue as $crate::__internal::Packet>::ID => {
199                            let (consumed, res) = <$enumValue as $crate::__internal::TryFromPacket>::try_deserialize(data)?;
200                            Ok((consumed, Box::new($name::$enumValue(res))))
201                        }
202                    )*
203                    _ => {
204                        #[allow(unused)]
205                        use $crate::__internal::MatchOpcode;
206                        $(
207                        if $innerProto::has_opcode(opcode) {
208                            let (consumed, res) = $innerProto::create_from(opcode, data)?;
209                            return Ok((consumed, Box::new($name::$innerProto(*res))));
210                        }
211                        )*
212                        Err($crate::__internal::InStreamError::UnmatchedOpcode(opcode))
213                    }
214                }
215            }
216        }
217
218        impl $crate::__internal::MatchOpcode for $name {
219            fn has_opcode(opcode: u16) -> bool {
220                $crate::__match_packet_opcode!(opcode => $($enumValue),*) ||
221                $crate::__match_protocol_opcode!(opcode => $($innerProto),*)
222            }
223        }
224    }
225}
226
227#[doc(hidden)]
228#[macro_export]
229macro_rules! define_output_protocol {
230    ($name:ident => $($enumValue:ident),* + $($innerProto:ident),*) => {
231        impl From<$name> for $crate::__internal::OutgoingPacket {
232            fn from(value: $name) -> $crate::__internal::OutgoingPacket {
233                match value {
234                    $(
235                        $name::$enumValue(inner) => inner.into(),
236                    )*
237                    $(
238                        $name::$innerProto(inner) => inner.into(),
239                    )*
240                }
241            }
242        }
243    }
244}
245
246#[doc(hidden)]
247pub mod __internal {
248    pub use skrillax_packet::*;
249    pub use skrillax_stream::stream::*;
250
251    pub trait MatchOpcode {
252        fn has_opcode(opcode: u16) -> bool;
253    }
254
255    impl<T: Packet> MatchOpcode for T {
256        fn has_opcode(opcode: u16) -> bool {
257            Self::ID == opcode
258        }
259    }
260}
261
262#[cfg(test)]
263mod test {
264    use skrillax_packet::{OutgoingPacket, Packet, TryFromPacket};
265    use skrillax_serde::{ByteSize, Deserialize, Serialize};
266    use skrillax_stream::InputProtocol;
267
268    #[derive(Packet, Deserialize, ByteSize, Serialize, Debug, Clone)]
269    #[packet(opcode = 0x1000)]
270    pub struct TestPacket {
271        inner: String,
272    }
273
274    #[derive(Packet, Serialize, ByteSize, Debug, Clone)]
275    #[packet(opcode = 0x1001)]
276    pub struct OutboundPacketOnly {
277        #[silkroad(size = 0)]
278        opt: Option<String>,
279    }
280
281    define_protocol! { TestProtocol =>
282        TestPacket
283    }
284
285    define_protocol! { WrapperProtocol =>
286        +
287        TestProtocol
288    }
289
290    define_outbound_protocol! { OutboundProto =>
291        OutboundPacketOnly
292    }
293
294    #[test]
295    fn test_protocol() {
296        TestProtocol::create_from(0x1000, &[0x00, 0x00]).unwrap();
297        WrapperProtocol::create_from(0x1000, &[0x00, 0x00]).unwrap();
298    }
299
300    #[test]
301    fn test_convert() {
302        let (_, packet) = TestPacket::try_deserialize(&[0x00, 0x00]).unwrap();
303        let proto: TestProtocol = packet.into();
304        let wrapper: WrapperProtocol = proto.into();
305        let inner_proto: TestProtocol = wrapper.try_into().expect("Should get back inner");
306        let _: TestPacket = inner_proto.try_into().expect("Should get back packet");
307    }
308
309    #[test]
310    fn test_outbound_only() {
311        let packet = OutboundPacketOnly { opt: None };
312        let proto: OutboundProto = packet.into();
313        let data: OutgoingPacket = proto.into();
314        assert!(matches!(
315            data,
316            OutgoingPacket::Simple { opcode: 0x1001, .. }
317        ))
318    }
319}