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