rust_tcp_ipc/protocol/
mod.rs

1use std::fmt::Debug;
2
3/// The error type for parsing a header which was transferred via TCP.
4#[derive(Debug, Clone, Copy, PartialEq)]
5pub enum ParseHeaderError {
6    /// Parsing of the command failed. This typically indicates that the protocol implementation is incomplete.
7    CommandParseFailed,
8    /// Parsing of the length failed, possibly because the length is too large (>=2^32)
9    LengthParseFailed,
10}
11/// This trait represents the TCP-Protocol to be used.
12///
13/// Messages are assumed to be given as u8-slice, consisting of a header and a payload.
14///
15/// The header combines a command (like Start, Stop, Pause, ...) and the lenght of the payload.
16///
17/// Many of the implementations show as examples should work for all cases, I'm just unable to define them generically (possible due to missing integer generics).
18///
19/// Since the protocol trait is only used to bundle some functions & types together, a trivial enum is ok:
20/// # Example
21/// ```
22/// enum ProtocolExample {}
23/// ```
24
25pub trait Protocol: 'static {
26    /// This type models the possible commands, like Start, Stop, Pause. It typical is represented by an enum.
27    /// # Example
28    /// ```
29    /// enum ExampleCommands {Start, Stop, Pause}
30    /// ```
31    type Commands: Clone + Copy + Debug + PartialEq + Send + Sync + 'static;
32    /// This type models possible busy_states like Idle, Working, Failure.
33    /// # Example
34    /// ```
35    /// enum ExampleBusyStates {Idle, Working, Failure}
36    /// ```
37    type BusyStates: Clone + Copy + Debug + PartialEq + Send + 'static;
38    /// This type represents the commands' underlying u8-array. (Currently, Rust supports no integer generics.)
39    /// # Example
40    /// ```
41    /// type CommandAsArray = [u8;3];
42    /// ```
43    type CommandAsArray: Debug;
44    /// This type represents the payload-length' underlying u8-array. (Currently, Rust supports no integer generics.)
45    /// # Example
46    /// ```
47    /// type LengthAsArray = [u8;2];
48    /// ```
49    type LengthAsArray: Debug;
50    /// This type represents the header' underlying u8-array.
51    /// The array size is usually the sum of the command-array size & the length-array size.
52    /// (Currently, Rust supports no integer generics.)
53    /// # Example
54    /// ```
55    /// type HeaderAsArray = [u8;5];
56    /// ```
57    type HeaderAsArray: Debug;
58    /// This function returns a default BusyState "Idle".
59    /// # Example
60    /// ```
61    /// fn idle() -> Self::BusyStates {ExampleBusyStates::Idle}
62    /// ```
63    fn idle() -> Self::BusyStates;
64    /// This function checks if a message has to be answered immediately and not be forwarded to the user.
65    /// If the message is to be answered immediately, a command and a message must be constructed.
66    /// If the message should be forwarded to the user, answer None.
67    /// A possible application is for "heartbeat" checks while the user is doing a computation.
68    /// # Example
69    /// ```
70    /// fn message_is_answered_via_immediate_route(
71    ///      command: &Self::Commands,
72    ///      message: &[u8],
73    ///      busy_state: &Self::BusyStates,
74    ///  ) -> Option<(Self::Commands, Vec<u8>)> {
75    ///     None
76    /// }
77    /// ```
78    fn message_is_answered_via_immediate_route(
79        command: &Self::Commands,
80        message: &[u8],
81        busy_state: &Self::BusyStates,
82    ) -> Option<(Self::Commands, Vec<u8>)>;
83    /// This function parses a command-array into a command (enum-variant). If this fails, None is return.
84    /// # Example
85    /// ```
86    /// fn parse_command(command: &Self::CommandAsArray) -> Option<Self::Commands> {
87    ///     use self::ExampleCommands::*;
88    ///     match command {
89    ///         [0,0,0]=>Start,
90    ///         [1,2,3]=>Stop,
91    ///         [255,255,255]=>Failure,
92    ///         _ => None,
93    ///     }
94    /// }
95    /// ```
96    fn parse_command(command: &Self::CommandAsArray) -> Option<Self::Commands>;
97    /// This function parses a length-array into a payload-length. If this fails, None is return.
98    /// It is to be used only internally.
99    /// # Example
100    /// ```
101    /// fn parse_length(length: &Self::LengthAsArray) -> Option<Self::usize> {
102    ///     length[0] as usize +length[1] as usize * 256
103    /// }
104    /// ```
105    fn parse_length(length: &Self::LengthAsArray) -> Option<usize>;
106    /// This function splits an incoming message into header-array & payload-slice. If this fails (because the message is too short), None is returned.
107    /// It is to be used only internally.
108    /// # Example
109    /// ```
110    /// fn message_slice_to_header_array(input: &[u8]) -> Option<(&Self::HeaderAsArray, &[u8])> {
111    ///     const HEADER_SIZE_EXAMPLE:usize = 5;
112    ///     if input.len() >= HEADER_SIZE_EXAMPLE {
113    ///         Some((
114    ///             unsafe {
115    ///             &*(input[0..HEADER_SIZE_EXAMPLE].as_ptr() as *const [_; HEADER_SIZE_EXAMPLE])
116    ///         },
117    ///         &input[HEADER_SIZE_EXAMPLE..],
118    ///        ))
119    ///     } else {
120    ///         None
121    ///     }
122    /// }
123    /// ```
124    fn message_slice_to_header_array(input: &[u8]) -> Option<(&Self::HeaderAsArray, &[u8])>;
125    /// This function splits header-array into a command-array and a length-array.
126    /// It is to be used only internally.
127    /// # Example
128    /// The following example is "length first", so the payload length takes the first (two) bytes from the incoming header. The remaining bytes encode the command.
129    /// ```
130    /// fn split_header_array(header: &Self::HeaderAsArray) -> (&Self::CommandAsArray, &Self::LengthAsArray) {
131    ///     const LENGTH_SIZE_EXAMPLE : usize = 2;
132    ///     const HEADER_SIZE_EXAMPLE : usize = 5;
133    ///     (
134    ///         unsafe {
135    ///             &*(header[LENGTH_SIZE_EXAMPLE..HEADER_SIZE_EXAMPLE].as_ptr()
136    ///                     as *const [_; COMMAND_SIZE_EXAMPLE])
137    ///         },
138    ///         unsafe {
139    ///             &*(header[0..LENGTH_SIZE_EXAMPLE].as_ptr() as *const [_; LENGTH_SIZE_EXAMPLE])
140    ///         },
141    ///     )
142    /// }
143    /// ```
144    fn split_header_array(
145        header: &Self::HeaderAsArray,
146    ) -> (&Self::CommandAsArray, &Self::LengthAsArray);
147    /// This function converts a command (enum-variant) to an array. This has to be the inverse of "parse_command".
148    /// It is to be used only internally.
149    /// # Example
150    /// ```
151    /// fn command_to_array(command: Self::Commands) -> Self::CommandAsArray {
152    ///     use self::ExampleCommands::*;
153    ///     match command {
154    ///         Start=>[0,0,0],
155    ///         Stop=>[1,2,3],
156    ///         Failure=>[255,255,255],
157    ///     }
158    /// }
159    /// ```
160    fn command_to_array(command: Self::Commands) -> Self::CommandAsArray;
161    /// This function computes a length (as array-representation) from a command and a message.
162    /// If this fails (for example, if the message is too long), None is return.
163    /// It is to be used only internally.
164    /// # Example
165    /// ```
166    /// fn get_length_as_array(command: Self::Commands, message: &[u8]) -> Option<Self::LengthAsArray> {
167    ///     let length = message.len() as u64;
168    ///     if length >= 256u64.pow(3) {
169    ///         return None;
170    ///     }
171    ///     let mut length_array = [0; 3];
172    ///     for i in 0u32..3 {
173    ///         length_array[(2 - i) as usize] = (length / 256u64.pow(i) % 256) as u8;
174    ///     }
175    ///     Some(length_array)
176    /// }
177    /// ```
178    fn get_length_as_array(command: Self::Commands, message: &[u8]) -> Option<Self::LengthAsArray>;
179    /// This function constructs the message header from a command and a length.
180    /// The implementation below should work (I'm just unable to get it to work generically).
181    /// # Example
182    /// ```
183    /// fn construct_header(command: Self::CommandAsArray, length: Self::LengthAsArray) -> Vec<u8> {
184    ///     let mut header = Vec::new();
185    ///     header.extend_from_slice(&length);
186    ///     header.extend_from_slice(&command);
187    ///     header
188    /// }
189    /// ```
190    fn construct_header(command: Self::CommandAsArray, length: Self::LengthAsArray) -> Vec<u8>;
191
192    /// This function parses a header into a command & a message length.
193    /// The default implementation is fine.
194    fn parse_header(
195        header: &Self::HeaderAsArray,
196    ) -> Result<(Self::Commands, usize), (ParseHeaderError, &Self::HeaderAsArray)> {
197        let (command, length) = Self::split_header_array(header);
198        if let Some(command) = Self::parse_command(command) {
199            if let Some(length) = Self::parse_length(length) {
200                Ok((command, length))
201            } else {
202                Err((ParseHeaderError::LengthParseFailed, header))
203            }
204        } else {
205            Err((ParseHeaderError::CommandParseFailed, header))
206        }
207    }
208    /// This function construct a message from a command & a payloay/message.
209    /// The default implementation is fine.
210    fn construct_message(command: Self::Commands, message: &[u8]) -> Option<Vec<u8>> {
211        if let Some(length) = Self::get_length_as_array(command, message) {
212            let command = Self::command_to_array(command);
213            let mut new_message = Self::construct_header(command, length);
214            new_message.extend_from_slice(message);
215            Some(new_message)
216        } else {
217            None
218        }
219    }
220}
221
222/// A type alias combining a command (as enum-variant) & a message (as byte-vector).
223pub type Message<P> = (<P as Protocol>::Commands, Vec<u8>);