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>);