wgtk/net/
element.rs

1//! Definitions for elements contained in bundles (and so in packets).
2
3use std::io::{self, Read, Write};
4
5use crate::util::io::*;
6
7
8pub mod login;
9pub mod reply;
10pub mod base;
11pub mod client;
12
13pub mod entity;
14
15
16/// A trait to be implemented on a structure that can be interpreted as
17/// bundle's elements. Elements are slices of data in a bundle of packets. 
18/// If a bundle contains multiple elements they are written contiguously.
19/// 
20/// Note that elements doesn't need to specify their length because they
21/// could be used for replies to requests, if you want to use the element
22/// as a top element (which mean that it provides a way to know its length
23/// in the bundle), implement the [`TopElement`] trait and specify its type
24/// of length.
25/// 
26/// You must provide a configuration type that will be given to encode
27/// and decode functions.
28pub trait Element: Sized {
29
30    /// Type of the element's config that is being encoded and decoded.
31    type Config;
32
33    /// Encode the element with the given writer and the given configuration.
34    fn encode(&self, write: &mut impl Write, config: &Self::Config) -> io::Result<()>;
35
36    /// Decode the element from the given reader and the given configuration.
37    /// 
38    /// The total length that is available in the reader is also given. **Note
39    /// that** the given length will be equal to zero if the element's length
40    /// is set to [`ElementLength::Unknown`] (relevant for top elements).
41    fn decode(read: &mut impl Read, len: usize, config: &Self::Config) -> io::Result<Self>;
42
43}
44
45/// A "top element" extends the behavior of a regular [`Element`] by providing
46/// a length that describes how to encode and decode the length of this element.
47/// Only top elements can be directly written and read from a bundle, non-top 
48/// elements are however useful when embedded in other (top) elements, such as
49/// reply element.
50pub trait TopElement: Element {
51
52    /// The type of length that prefixes the element's content and describe
53    /// how much space is taken by the element.
54    const LEN: ElementLength;
55
56}
57
58/// This trait provides an easier implementation of [`Element`] with not config value as
59/// opposed to regular elements, therefore both traits cannot be implemented at the same 
60/// time.
61pub trait SimpleElement: Sized {
62
63    /// Encode the element with the given writer.
64    fn encode(&self, write: &mut impl Write) -> io::Result<()>;
65
66    /// Decode the element from the given reader.
67    /// 
68    /// The total length that is available in the reader is also given. **Note
69    /// that** the given length will be equal to zero if the element's length
70    /// is set to [`ElementLength::Unknown`] (relevant for top elements).
71    fn decode(read: &mut impl Read, len: usize) -> io::Result<Self>;
72
73}
74
75impl<E: SimpleElement> Element for E {
76
77    type Config = ();
78
79    #[inline]
80    fn encode(&self, write: &mut impl Write, _config: &Self::Config) -> io::Result<()> {
81        SimpleElement::encode(self, write)
82    }
83
84    #[inline]
85    fn decode(read: &mut impl Read, len: usize, _config: &Self::Config) -> io::Result<Self> {
86        SimpleElement::decode(read, len)
87    }
88
89}
90
91
92/// An alternative trait to both [`Element`] that automatically implements 
93/// nothing for encode and provides the default value on decoding without 
94/// actually reading.
95pub trait NoopElement: Default {}
96
97impl<E: NoopElement> SimpleElement for E {
98
99    fn encode(&self, _write: &mut impl Write) -> io::Result<()> {
100        Ok(())
101    }
102
103    fn decode(_read: &mut impl Read, _len: usize) -> io::Result<Self> {
104        Ok(Self::default())
105    }
106    
107}
108
109
110/// The empty tuple is considered an empty element. This can sometime
111/// be useful for default generic types.
112impl NoopElement for () { }
113impl TopElement for () {
114    const LEN: ElementLength = ElementLength::Fixed(0);
115}
116
117
118/// Type of length used by a specific message codec.
119/// This describes how the length of an element should be encoded in the packet.
120#[derive(Copy, Clone, Eq, PartialEq, Debug)]
121pub enum ElementLength {
122    /// A fixed length element, the length is not written in the header.
123    Fixed(u32),
124    /// The length is encoded on 8 bits in the element's header.
125    Variable8,
126    /// The length is encoded on 16 bits in the element's header.
127    Variable16,
128    /// The length is encoded on 24 bits in the element's header.
129    Variable24,
130    /// The length is encoded on 32 bits in the element's header.
131    Variable32,
132    /// The real length of the element is queried dynamically with a callback,
133    /// given the element's identifier.
134    Callback(fn(id: u8) -> ElementLength),
135}
136
137impl ElementLength {
138
139    /// Read the length from a given reader.
140    pub fn read(mut self, mut reader: impl Read, id: u8) -> std::io::Result<u32> {
141
142        // If the length is a callback, get the real length from the message id.
143        if let Self::Callback(cb) = self {
144            self = cb(id);
145        }
146
147        match self {
148            Self::Fixed(len) => Ok(len),
149            Self::Variable8 => reader.read_u8().map(|n| n as u32),
150            Self::Variable16 => reader.read_u16().map(|n| n as u32),
151            Self::Variable24 => reader.read_u24().map(|n| n),
152            Self::Variable32 => reader.read_u32().map(|n| n),
153            Self::Callback(_) => panic!("cyclic callback")
154        }
155
156    }
157
158    /// Write the length to the given writer.
159    pub fn write(self, mut writer: impl Write, len: u32) -> std::io::Result<()> {
160
161        match self {
162            Self::Fixed(expected_len) => { 
163                assert_eq!(expected_len, len); 
164                Ok(()) 
165            }
166            Self::Variable8 => writer.write_u8(len as u8),
167            Self::Variable16 => writer.write_u16(len as u16),
168            Self::Variable24 => writer.write_u24(len),
169            Self::Variable32 => writer.write_u32(len),
170            Self::Callback(_) => Ok(())
171        }
172
173    }
174
175    /// Return the size in bytes of this type of length.
176    pub fn len(&self) -> usize {
177        match self {
178            Self::Fixed(_) => 0,
179            Self::Variable8 => 1,
180            Self::Variable16 => 2,
181            Self::Variable24 => 3,
182            Self::Variable32 => 4,
183            Self::Callback(_) => 0,
184        }
185    }
186
187}
188
189
190/// An utility structure for storing ranges of element's ids. It provides way
191/// of converting between **element id** (with optional **sub-id**) and 
192/// **exposed id**.
193/// 
194/// This structure is small and therefore can be copied.
195#[derive(Debug, Clone, Copy, PartialEq, Eq)]
196pub struct ElementIdRange {
197    pub first: u8,
198    pub last: u8,
199}
200
201impl ElementIdRange {
202
203    pub const fn new(first: u8, last: u8) -> Self {
204        Self { first, last }
205    }
206
207    /// Returns the number of slots in this range.
208    #[inline]
209    pub const fn slots_count(self) -> u8 {
210        self.last - self.first + 1
211    }
212
213    /// Returns the number of slots that requires a sub-id. These slots are 
214    /// starting from the end of the range. For example, if this function
215    /// returns 1, this means that the last slot (`.last`), if used, will be
216    /// followed by a sub-id.
217    /// 
218    /// You must given the total number of exposed ids, because the presence
219    /// of sub-id depends on how exposed ids can fit in the id range.
220    #[inline]
221    pub const fn sub_slots_count(self, exposed_count: u16) -> u8 {
222        // Calculate the number of excess exposed ids, compared to slots count.
223        let excess_count = exposed_count as i32 - self.slots_count() as i32;
224        // If the are excess slots, calculate how much additional bytes are 
225        // required to represent such number.
226        if excess_count > 0 {
227            (excess_count / 255 + 1) as u8
228        } else {
229            0
230        }
231    }
232    
233    /// Returns the number of full slots that don't require a sub-id. This
234    /// is the opposite of `sub_slots_count`, read its documentation.
235    #[inline]
236    pub const fn full_slots_count(self, exposed_count: u16) -> u8 {
237        self.slots_count() - self.sub_slots_count(exposed_count)
238    }
239
240    /// Get the element's id and optional sub-id from the given exposed id
241    /// and total count of exposed ids.
242    pub fn from_exposed_id(self, exposed_count: u16, exposed_id: u16) -> (u8, Option<u8>) {
243
244        let full_slots = self.full_slots_count(exposed_count);
245
246        if exposed_id < full_slots as u16 {
247            // If the exposed id fits in the full slots.
248            (self.first + exposed_id as u8, None)
249        } else {
250            // If the given exposed id require to be put in a sub-slot.
251            // First we get how much offset the given exposed id is from the first
252            // sub slot (full_slots represent the first sub slot).
253            let overflow = exposed_id - full_slots as u16;
254            let first_sub_slot = self.first + full_slots;
255            // Casts are safe.
256            ((first_sub_slot as u16 + overflow / 256) as u8, Some((overflow % 256) as u8))
257        }
258
259    }
260
261    /// Get the exposed id from an element, but only return some exposed id if
262    /// it fits into 
263    pub fn to_exposed_id_checked(self, exposed_count: u16, element_id: u8) -> Option<u16> {
264        let raw_exposed_id = element_id - self.first;
265        (raw_exposed_id < self.full_slots_count(exposed_count)).then_some(raw_exposed_id as u16)
266    }
267
268    /// Get the exposed id from an element id and optionally a sub-id, which 
269    /// should be lazily provided with a closure.
270    pub fn to_exposed_id(self, exposed_count: u16, element_id: u8, sub_id_getter: impl FnOnce() -> u8) -> u16 {
271        
272        // This is the raw exposed id, it will be used, with full_slots to determine
273        // if a sub-id is needed.
274        let exposed_id = element_id - self.first;
275        let full_slots = self.full_slots_count(exposed_count);
276        
277        if exposed_id < full_slots {
278            exposed_id as u16
279        } else {
280            // Calculate of the sub-slot offset within sub-slots.
281            let offset_id = exposed_id - full_slots;
282            let sub_id = sub_id_getter();
283            // Calculate the final exposed id from the sub-id and offset.
284            full_slots as u16 + 256 * offset_id as u16 + sub_id as u16
285        }
286        
287    }
288
289}