From a059f8b3e48386d37cee6bd18ee094f29bac5b68 Mon Sep 17 00:00:00 2001 From: Sayan Nandan Date: Mon, 6 Jul 2020 15:16:10 +0530 Subject: [PATCH 1/6] Implement basic parsing for terrapipe queries --- libcore/src/terrapipe.rs | 174 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 172 insertions(+), 2 deletions(-) diff --git a/libcore/src/terrapipe.rs b/libcore/src/terrapipe.rs index d537e3d3..7a7b8cd7 100644 --- a/libcore/src/terrapipe.rs +++ b/libcore/src/terrapipe.rs @@ -8,14 +8,184 @@ * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * */ +const METAFRAME_PROTOCOL_TAG: &'static str = "TP"; +const METAFRAME_QUERY_TAG: &'static str = "Q"; +const METAFRAME_QUERY_SET_TAG: &'static str = "SET"; +const METAFRAME_QUERY_GET_TAG: &'static str = "GET"; +const METAFRAME_QUERY_UPDATE_TAG: &'static str = "UPDATE"; +const METAFRAME_QUERY_DEL_TAG: &'static str = "DEL"; + +pub struct Version(u8, u8, u8); + +impl Version { + pub fn new_from_str<'a>(val: &'a str) -> Option { + let vals: Vec<&str> = val.split(".").collect(); + if vals.len() != 3 { + return None; + } + let semver = ( + vals[0].parse::(), + vals[1].parse::(), + vals[2].parse::(), + ); + if let (Ok(major), Ok(minor), Ok(patch)) = semver { + return Some(Version(major, minor, patch)); + } else { + return None; + } + } + pub fn is_compatible_with(&self, other: &Version) -> bool { + if self.0 == other.0 { + true + } else { + false + } + } +} + +type Key = String; + +pub enum TPQueryType { + GET(Key), + SET(Key, Key), + UPDATE(Key, Key), + DEL(Key, Key), +} + +pub fn parse_query_packet(own_version: Version, packet: &String) { + let rlines: Vec<&str> = packet.lines().collect(); + // This should give us two or more lines + if rlines.len() < 2 { + eprintln!("error: 5 CorruptDataframe"); + return; + } + // This is the meta frame + let meta_frame: Vec<&str> = rlines[0].split("/").collect(); + if meta_frame.len() != 3 { + eprintln!("error: 4 InvalidMetaframe"); + return; + } + // This is the data frame + let data_frame: Vec<&str> = rlines[1].split_whitespace().collect(); + // Check if version is valid + let version_header: Vec<&str> = meta_frame[0].split(" ").collect(); + if let Some(version_tag) = version_header.get(0) { + if version_tag == &METAFRAME_PROTOCOL_TAG { + if let Some(version) = version_header.get(1) { + if let Some(v) = Version::new_from_str(&version) { + if !v.is_compatible_with(&own_version) { + eprintln!("error: 6 ProtocolVersionMismatch"); + } else { + () + } + } else { + eprintln!("error: 4 InvalidMetaframe"); + } + } else { + eprintln!("error: 4 InvalidMetaframe"); + } + } else { + eprintln!("error: 4 InvalidMetaframe"); + } + } else { + eprintln!("error: 4 InvalidMetaframe"); + } + + // Now get request type + let request_partition: Vec<&str> = meta_frame[1].split(" ").collect(); + if let Some(request_tag) = request_partition.get(0) { + if request_tag == &METAFRAME_QUERY_TAG { + if let Some(qtype) = request_partition.get(1) { + match qtype { + &METAFRAME_QUERY_SET_TAG => { + // This is a set request + if let Some(key) = data_frame.get(0) { + if let Some(value) = data_frame.get(1) { + if data_frame.get(2).is_none() { + println!("SET {} {}", key, value); + } else { + eprintln!("error: 5 Corrupt Dataframe"); + return; + } + } else { + eprintln!("error: 5 Corrupt Dataframe"); + return; + } + } else { + eprintln!("error: 5 Corrupt Dataframe"); + return; + } + } + &METAFRAME_QUERY_UPDATE_TAG => { + // This is an update request + if let Some(key) = data_frame.get(0) { + if let Some(value) = data_frame.get(1) { + if data_frame.get(2).is_none() { + println!("UPDATE {} {}", key, value); + } else { + eprintln!("error: 5 Corrupt Dataframe"); + return; + } + } else { + eprintln!("error: 5 Corrupt Dataframe"); + return; + } + } else { + eprintln!("error: 5 Corrupt Dataframe"); + return; + } + } + &METAFRAME_QUERY_GET_TAG => { + // This is a get request + if let Some(key) = data_frame.get(0) { + if data_frame.get(1).is_none() { + println!("GET {}", key); + } else { + eprintln!("error: 5 Corrupt Dataframe"); + return; + } + } else { + eprintln!("error: 5 Corrupt Dataframe"); + return; + } + } + _ => { + eprintln!("error: 4 Invalid Metaframe"); + return; + } + } + } else { + eprintln!("error: 5 Corrupt dataframe"); + return; + } + } else { + eprintln!("error: 5 Corrupt dataframe"); + return; + } + } else { + eprintln!("error: 5 Corrupt dataframe"); + return; + } +} + +#[cfg(test)] +#[test] +fn test_parse_header_query() { + let query_packet_get = "TP 0.1.1/Q GET/5\nsayan".to_owned(); + parse_query_packet(Version(0, 1, 1), &query_packet_get); + let query_packet_set = "TP 0.1.1/Q SET/8\nsayan 18".to_owned(); + parse_query_packet(Version(0, 1, 1), &query_packet_set); + let erroring_packet_set = "TP 0.1.1/Q SET/13\nhi sayan".to_owned(); + parse_query_packet(Version(0, 1, 1), &erroring_packet_set); +} From 3740b53efd04616262664402a52767f4605d2d86 Mon Sep 17 00:00:00 2001 From: Sayan Nandan Date: Mon, 6 Jul 2020 21:11:35 +0530 Subject: [PATCH 2/6] Formal terrapipe query parsing --- LICENSE | 3 + libcore/src/terrapipe.rs | 313 +++++++++++------- ...rapipe-spec.md => terrapipe-spec-0.1.0.md} | 83 ++--- 3 files changed, 239 insertions(+), 160 deletions(-) rename libcore/{terrapipe-spec.md => terrapipe-spec-0.1.0.md} (55%) diff --git a/LICENSE b/LICENSE index bbba84b0..529a91d7 100644 --- a/LICENSE +++ b/LICENSE @@ -615,3 +615,6 @@ reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. + + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/libcore/src/terrapipe.rs b/libcore/src/terrapipe.rs index 7a7b8cd7..8ee59ae8 100644 --- a/libcore/src/terrapipe.rs +++ b/libcore/src/terrapipe.rs @@ -19,16 +19,56 @@ * */ -const METAFRAME_PROTOCOL_TAG: &'static str = "TP"; -const METAFRAME_QUERY_TAG: &'static str = "Q"; -const METAFRAME_QUERY_SET_TAG: &'static str = "SET"; -const METAFRAME_QUERY_GET_TAG: &'static str = "GET"; -const METAFRAME_QUERY_UPDATE_TAG: &'static str = "UPDATE"; -const METAFRAME_QUERY_DEL_TAG: &'static str = "DEL"; +//! This is the implementation of the terrabasedb/RFC#1 +const MF_PROTOCOL_TAG: &'static str = "TP"; +const MF_QUERY_TAG: &'static str = "Q"; +const MF_QUERY_SET_TAG: &'static str = "SET"; +const MF_QUERY_GET_TAG: &'static str = "GET"; +const MF_QUERY_UPDATE_TAG: &'static str = "UPDATE"; +const MF_QUERY_DEL_TAG: &'static str = "DEL"; + +macro_rules! result_packet { + ($version:expr, $respcode:expr, $data:expr) => {{ + let data = $data.to_string(); + format!( + "TP/{}/R/{}/{}\n{}", + $version.to_string(), + $respcode, + data.len(), + $data + ) + }}; +} + +macro_rules! query_packet { + ($version:expr, $querytype:expr, $data:expr) => { + format!( + "TP/{}/Q/{}\n{}", + $version.to_string(), + $querytype.to_string(), + $data + ) + }; +} + +/// Anything that implements `ToString` automatically implements `ToTPArgs` +pub trait ToTPArgs: ToString { + fn to_tp_args(&self) -> String; +} + +/// Minimal representation of _semver_ +#[derive(Debug)] pub struct Version(u8, u8, u8); +impl ToString for Version { + fn to_string(&self) -> String { + format!("{}.{}.{}", self.0, self.1, self.2) + } +} + impl Version { + /// Parse a new semver using a string in the form x.y.z pub fn new_from_str<'a>(val: &'a str) -> Option { let vals: Vec<&str> = val.split(".").collect(); if vals.len() != 3 { @@ -45,6 +85,7 @@ impl Version { return None; } } + /// Use semver to check if the versions are compatible with each other pub fn is_compatible_with(&self, other: &Version) -> bool { if self.0 == other.0 { true @@ -54,138 +95,172 @@ impl Version { } } +/// `Key` is a type alias for `String` type Key = String; +/// `Value` is a type alias for `String` +type Value = String; -pub enum TPQueryType { +/// A fully parsed and ready-to-execute Query action +#[derive(Debug, PartialEq)] +pub enum TPQueryMethod { GET(Key), - SET(Key, Key), - UPDATE(Key, Key), - DEL(Key, Key), + SET(Key, Value), + UPDATE(Key, Value), + DEL(Key), +} + +/// Representation of query types +#[derive(Debug, PartialEq)] +pub enum TPQueryType { + GET, + SET, + UPDATE, + DEL, +} + +impl ToString for TPQueryType { + fn to_string(&self) -> String { + use TPQueryType::*; + if self == &GET { + return MF_QUERY_GET_TAG.to_owned(); + } else if self == &SET { + return MF_QUERY_SET_TAG.to_owned(); + } else if self == &UPDATE { + return MF_QUERY_UPDATE_TAG.to_owned(); + } else { + return MF_QUERY_DEL_TAG.to_owned(); + } + } +} + +/// Errors that may occur while parsing a query packet +#[derive(Debug, PartialEq)] +pub enum TPQueryError { + /// `1: Not Found` + /// + /// The target resource could not be found + NotFound, + /// `2: Overwrite Error` + /// + /// This usually occurs when a query tries to alter the value + /// of an existing key using `SET` instead of `UPDATE` + OverwriteError, + /// `3: Method Not Allowed` + /// + /// The client is trying to do something illegal like sending a `Result` + /// packet instead of a `Query` packet + MethodNotAllowed, + /// `4: Internal Server Error` + /// + /// There is an internal server error + InternalServerError, + /// `5: Invalid Metaframe` + /// + /// The metaframe of the query packet has some incorrect partitions or + /// has an incorrect format + InvalidMetaframe, + /// `6: Corrupt Dataframe` + /// + /// The dataframe may be missing some bytes or more bytes were expected + CorruptDataframe, + /// `7: Protocol Version Mismatch` + /// + /// The protocol used by the client is not compatible with the protocol + /// used by the server + ProtocolVersionMismatch, + /// `8: Corrupt Packet` + /// + /// The packet is either empty or is missing a newline + CorruptPacket, } -pub fn parse_query_packet(own_version: Version, packet: &String) { +#[cfg(test)] +#[test] +fn test_result_macros() { + let proto_version = Version(0, 1, 0); + let query = query_packet!(proto_version, TPQueryType::GET, "sayan"); + let result = result_packet!(proto_version, 0, 17); + let query_should_be = "TP/0.1.0/Q/GET\nsayan".to_owned(); + let result_should_be = "TP/0.1.0/R/0/2\n17".to_owned(); + assert_eq!(query, query_should_be); + assert_eq!(result, result_should_be); +} + +pub fn parse_query_packet( + packet: String, + self_version: &Version, +) -> Result { let rlines: Vec<&str> = packet.lines().collect(); - // This should give us two or more lines if rlines.len() < 2 { - eprintln!("error: 5 CorruptDataframe"); - return; + return Err(TPQueryError::CorruptPacket); } - // This is the meta frame - let meta_frame: Vec<&str> = rlines[0].split("/").collect(); - if meta_frame.len() != 3 { - eprintln!("error: 4 InvalidMetaframe"); - return; + let metaframe: Vec<&str> = rlines[0].split("/").collect(); + if metaframe.len() != 4 { + return Err(TPQueryError::InvalidMetaframe); } - // This is the data frame - let data_frame: Vec<&str> = rlines[1].split_whitespace().collect(); - // Check if version is valid - let version_header: Vec<&str> = meta_frame[0].split(" ").collect(); - if let Some(version_tag) = version_header.get(0) { - if version_tag == &METAFRAME_PROTOCOL_TAG { - if let Some(version) = version_header.get(1) { - if let Some(v) = Version::new_from_str(&version) { - if !v.is_compatible_with(&own_version) { - eprintln!("error: 6 ProtocolVersionMismatch"); - } else { - () - } - } else { - eprintln!("error: 4 InvalidMetaframe"); - } - } else { - eprintln!("error: 4 InvalidMetaframe"); - } + + if metaframe[0] != MF_PROTOCOL_TAG { + return Err(TPQueryError::InvalidMetaframe); + } + if let Some(v) = Version::new_from_str(metaframe[1]) { + if self_version.is_compatible_with(&v) { + () } else { - eprintln!("error: 4 InvalidMetaframe"); + return Err(TPQueryError::ProtocolVersionMismatch); } - } else { - eprintln!("error: 4 InvalidMetaframe"); } - // Now get request type - let request_partition: Vec<&str> = meta_frame[1].split(" ").collect(); - if let Some(request_tag) = request_partition.get(0) { - if request_tag == &METAFRAME_QUERY_TAG { - if let Some(qtype) = request_partition.get(1) { - match qtype { - &METAFRAME_QUERY_SET_TAG => { - // This is a set request - if let Some(key) = data_frame.get(0) { - if let Some(value) = data_frame.get(1) { - if data_frame.get(2).is_none() { - println!("SET {} {}", key, value); - } else { - eprintln!("error: 5 Corrupt Dataframe"); - return; - } - } else { - eprintln!("error: 5 Corrupt Dataframe"); - return; - } - } else { - eprintln!("error: 5 Corrupt Dataframe"); - return; - } - } - &METAFRAME_QUERY_UPDATE_TAG => { - // This is an update request - if let Some(key) = data_frame.get(0) { - if let Some(value) = data_frame.get(1) { - if data_frame.get(2).is_none() { - println!("UPDATE {} {}", key, value); - } else { - eprintln!("error: 5 Corrupt Dataframe"); - return; - } - } else { - eprintln!("error: 5 Corrupt Dataframe"); - return; - } - } else { - eprintln!("error: 5 Corrupt Dataframe"); - return; - } - } - &METAFRAME_QUERY_GET_TAG => { - // This is a get request - if let Some(key) = data_frame.get(0) { - if data_frame.get(1).is_none() { - println!("GET {}", key); - } else { - eprintln!("error: 5 Corrupt Dataframe"); - return; - } - } else { - eprintln!("error: 5 Corrupt Dataframe"); - return; - } - } - _ => { - eprintln!("error: 4 Invalid Metaframe"); - return; - } + if metaframe[2] != MF_QUERY_TAG { + return Err(TPQueryError::InvalidMetaframe); + } + let dataframe: Vec<&str> = rlines[1].split_whitespace().collect(); + if dataframe.len() == 0 { + return Err(TPQueryError::CorruptDataframe); + } + match metaframe[3] { + MF_QUERY_GET_TAG => { + // This is a GET query + if let Some(key) = dataframe.get(0) { + if dataframe.get(1).is_none() { + return Ok(TPQueryMethod::GET(key.to_string())); + } + } + } + MF_QUERY_SET_TAG => { + // This is a SET query + if let Some(key) = dataframe.get(0) { + if let Some(value) = dataframe.get(1) { + return Ok(TPQueryMethod::SET(key.to_string(), value.to_string())); + } + } + } + MF_QUERY_UPDATE_TAG => { + // This is a SET query + if let Some(key) = dataframe.get(0) { + if let Some(value) = dataframe.get(1) { + return Ok(TPQueryMethod::UPDATE(key.to_string(), value.to_string())); + } + } + } + MF_QUERY_DEL_TAG => { + // This is a DEL query + if let Some(key) = dataframe.get(0) { + if dataframe.get(1).is_none() { + return Ok(TPQueryMethod::DEL(key.to_string())); } - } else { - eprintln!("error: 5 Corrupt dataframe"); - return; } - } else { - eprintln!("error: 5 Corrupt dataframe"); - return; } - } else { - eprintln!("error: 5 Corrupt dataframe"); - return; + // Some random illegal command + _ => return Err(TPQueryError::MethodNotAllowed), } + Err(TPQueryError::CorruptDataframe) } #[cfg(test)] #[test] -fn test_parse_header_query() { - let query_packet_get = "TP 0.1.1/Q GET/5\nsayan".to_owned(); - parse_query_packet(Version(0, 1, 1), &query_packet_get); - let query_packet_set = "TP 0.1.1/Q SET/8\nsayan 18".to_owned(); - parse_query_packet(Version(0, 1, 1), &query_packet_set); - let erroring_packet_set = "TP 0.1.1/Q SET/13\nhi sayan".to_owned(); - parse_query_packet(Version(0, 1, 1), &erroring_packet_set); +fn test_query_packet_parsing() { + let qpacket = query_packet!(Version(0, 1, 0), TPQueryType::GET, "sayan"); + let query_should_be = TPQueryMethod::GET("sayan".to_owned()); + let parsed_qpacket = parse_query_packet(qpacket, &Version(0, 1, 0)).unwrap(); + assert_eq!(query_should_be, parsed_qpacket); } diff --git a/libcore/terrapipe-spec.md b/libcore/terrapipe-spec-0.1.0.md similarity index 55% rename from libcore/terrapipe-spec.md rename to libcore/terrapipe-spec-0.1.0.md index 184f3aa2..396e7bbe 100644 --- a/libcore/terrapipe-spec.md +++ b/libcore/terrapipe-spec-0.1.0.md @@ -17,8 +17,7 @@ TP makes use of two packets: The `Q` uery packet has the following structure: ``` -TP /Q / -\n +TP//Q/ --------------- DATA ---------- ``` @@ -27,75 +26,77 @@ TP /Q / ### Line 1: Meta frame The first line is called the meta frame. The `` and their corresponding meanings are as follows: -- **`VERSION`**: The version of the protocol, in semver form, i.e `major.minor.patch`. -An example can be: `0.1.0` -- **`QTYPE`**: This is the type of query. It can have the following values: - - `GET`: For `GET` operations - - `SET`: For `SET` operations - - `UPDATE`: For `UDPATE` operations - - `DEL`: For `DEL` operations -- **`LENGTH`**: The number of bytes that are being transmitted. This is useful for preallocating buffers for copying the data. + +* **`VERSION`**: The version of the protocol, in semver form, i.e `major.minor.patch` . + +An example can be: `0.1.0` + +* **`QTYPE`**: This is the type of query. It can have the following values: + - `GET` : For `GET` operations + - `SET` : For `SET` operations + - `UPDATE` : For `UDPATE` operations + - `DEL` : For `DEL` operations #### Example meta frame -``` -TP 0.1.0/Q GET/15 + +``` +TP/0.1.0/Q/GET ``` -### Line 2: Line break -This is a line break that separates the meta frame from the data frame. +### Line 2 and subsequent lines: Data frame -### Line 3: Data frame The data frame doesn't have any defined format. It can be anything that can be transferred over TCP - that is, well, anything: letters, numbers or vaguely bytes. -## The `R`esult packet -The `R`esult packet has the following structure: -``` -TP /R // -\n +## The `R` esult packet + +The `R` esult packet has the following structure: + +``` +TP//R// --------------------- DATA ------------------- ``` + **Note:** The first line is followed by a line break, and then the subsequent lines. ### Line 1: Meta frame -Just like the `Q`uery packet, the first line is called the meta frame. + +Just like the `Q` uery packet, the first line is called the meta frame. The `` and their corresponding meanings are as follows: -- **`VERSION`**: The version of the protocol, in semver form, i.e `major.minor.patch`. -An example can be: `0.1.0` -- **`QTYPE`**: This is the type of query. It can have the following values: - - `GET`: For responses to `GET` operations - - `SET`: For responses to `SET` operations - - `UPDATE`: For responses to `UPDATE` operations - - `DEL`: For response to `DEL` operations - - This must match with the initial query packet. -- **`RESPONSECODE`**: This is the outcome of the query. It can have the following values: + +* **`VERSION`**: The version of the protocol, in semver form, i.e `major.minor.patch` . + +An example can be: `0.1.0` + +* **`RESPONSECODE`**: This is the outcome of the query. It can have the following values: - 0: Okay - 1: Not found - 2: Method not allowed - 3: Server error - 4: Corrupt byte - 5: Protocol version mismatch -- **`LENGTH`**: The number of bytes that are being transmitted. This is useful for preallocating buffers for copying the data. +* **`LENGTH`**: The number of bytes that are being transmitted. This is useful for preallocating buffers for copying the data. #### Example data frame -``` + +``` sayan is writing a protocol ``` ## An example of a query/result + Let us assume a key called `sayan` with a value of '17' exists on the database. Our client, uses `0.1.0` version of tp and sends a `GET` request for the key to our server which also uses version `0.1.0` of tp. -### The `Q`uery packet -``` -TP 0.1.0/Q GET/5 -\n +### The `Q` uery packet + +``` +TP/0.1.0/Q/GET sayan ``` -### The `R`esult packet -``` -TP 0.1.0/R GET/0/2 -\n +### The `R` esult packet + +``` +TP/0.1.0/R/0/2 17 ``` From e84c58d4ad543ff6e9cbd557b2431593bcba9c7f Mon Sep 17 00:00:00 2001 From: Sayan Nandan Date: Wed, 8 Jul 2020 12:23:04 +0530 Subject: [PATCH 3/6] Formal terrapipe query/result implementation --- libcore/src/terrapipe.rs | 153 +++++++++++++++++++++++++++++--- libcore/terrapipe-spec-0.1.0.md | 102 --------------------- 2 files changed, 140 insertions(+), 115 deletions(-) delete mode 100644 libcore/terrapipe-spec-0.1.0.md diff --git a/libcore/src/terrapipe.rs b/libcore/src/terrapipe.rs index 8ee59ae8..39ac5b03 100644 --- a/libcore/src/terrapipe.rs +++ b/libcore/src/terrapipe.rs @@ -21,13 +21,24 @@ //! This is the implementation of the terrabasedb/RFC#1 +use std::fmt; + +/// The 'TP' protocol tag used in the meta frame const MF_PROTOCOL_TAG: &'static str = "TP"; +/// The 'Q' tag used in the meta frame const MF_QUERY_TAG: &'static str = "Q"; +/// The 'R' tag used in the meta frame +const MF_RESULT_TAG: &'static str = "R"; +/// 'SET' tag in the meta frame const MF_QUERY_SET_TAG: &'static str = "SET"; +/// 'GET' tag in the meta frame const MF_QUERY_GET_TAG: &'static str = "GET"; +/// 'UPDATE' tag in the meta frame const MF_QUERY_UPDATE_TAG: &'static str = "UPDATE"; +/// 'DEL' tag in the meta frame const MF_QUERY_DEL_TAG: &'static str = "DEL"; +/// A macro to easily create result packets - to be used by servers macro_rules! result_packet { ($version:expr, $respcode:expr, $data:expr) => {{ let data = $data.to_string(); @@ -41,6 +52,7 @@ macro_rules! result_packet { }}; } +/// A macro to easily create query packets - to be used by clients macro_rules! query_packet { ($version:expr, $querytype:expr, $data:expr) => { format!( @@ -133,9 +145,11 @@ impl ToString for TPQueryType { } } -/// Errors that may occur while parsing a query packet +/// Errors that may occur while parsing a query/result packet #[derive(Debug, PartialEq)] -pub enum TPQueryError { +pub enum TPError { + /// `0: Okay` + Okay, /// `1: Not Found` /// /// The target resource could not be found @@ -174,6 +188,31 @@ pub enum TPQueryError { CorruptPacket, } +impl TPError { + /// Returns a `TPError` variant from an `u8` and returns `None` if it + /// isn't a valid code + pub fn from_u8(code: u8) -> Option { + use TPError::*; + let val = match code { + 0 => Okay, + 1 => NotFound, + 2 => OverwriteError, + 3 => MethodNotAllowed, + 4 => InternalServerError, + 5 => InvalidMetaframe, + 6 => CorruptDataframe, + 7 => ProtocolVersionMismatch, + 8 => CorruptPacket, + _ => return None, + }; + Some(val) + } +} + +/// Errors that may occur while parsing a query packet +#[derive(Debug, PartialEq)] +pub struct TPQueryError(TPError); + #[cfg(test)] #[test] fn test_result_macros() { @@ -185,37 +224,43 @@ fn test_result_macros() { assert_eq!(query, query_should_be); assert_eq!(result, result_should_be); } - +/// Parse a query packet that is sent by the client +/// ## Returns +/// This returns a `TPQueryMethod` which can be used to execute the action or +/// it returns a `TPQueryError` in the case an error occurs while parsing the packet pub fn parse_query_packet( packet: String, self_version: &Version, ) -> Result { let rlines: Vec<&str> = packet.lines().collect(); if rlines.len() < 2 { - return Err(TPQueryError::CorruptPacket); + return Err(TPQueryError(TPError::CorruptPacket)); } let metaframe: Vec<&str> = rlines[0].split("/").collect(); if metaframe.len() != 4 { - return Err(TPQueryError::InvalidMetaframe); + return Err(TPQueryError(TPError::InvalidMetaframe)); } - if metaframe[0] != MF_PROTOCOL_TAG { - return Err(TPQueryError::InvalidMetaframe); + if metaframe[0].ne(MF_PROTOCOL_TAG) { + return Err(TPQueryError(TPError::InvalidMetaframe)); } if let Some(v) = Version::new_from_str(metaframe[1]) { if self_version.is_compatible_with(&v) { () } else { - return Err(TPQueryError::ProtocolVersionMismatch); + return Err(TPQueryError(TPError::ProtocolVersionMismatch)); } } - if metaframe[2] != MF_QUERY_TAG { - return Err(TPQueryError::InvalidMetaframe); + if metaframe[2].ne(MF_QUERY_TAG) { + return Err(TPQueryError(TPError::InvalidMetaframe)); } + /* TODO: This is temporary - the dataframe in the future may be + multiple lines long + */ let dataframe: Vec<&str> = rlines[1].split_whitespace().collect(); if dataframe.len() == 0 { - return Err(TPQueryError::CorruptDataframe); + return Err(TPQueryError(TPError::CorruptDataframe)); } match metaframe[3] { MF_QUERY_GET_TAG => { @@ -251,9 +296,81 @@ pub fn parse_query_packet( } } // Some random illegal command - _ => return Err(TPQueryError::MethodNotAllowed), + _ => return Err(TPQueryError(TPError::MethodNotAllowed)), + } + Err(TPQueryError(TPError::CorruptDataframe)) +} + +/// Errors that may occur while parsing a result packet +#[derive(Debug, PartialEq)] +pub enum TPResultError { + /// The standard `TPError`s + StandardError(TPError), + /// In the event someone tried to parse a result from a _patched_ server + /// which sent a weird error code, use this variant + UnrecognizedError(String), +} + +/// `TPResult` is type alias for `String` +pub type TPResult = String; + +/// Parse a result packet sent by the server +/// ## Returns +/// If there was no error in parsing the packet, then a `TPResult` is returned. +/// Otherwise a `TPResultError` is returned +pub fn parse_result_packet( + packet: String, + self_version: &Version, +) -> Result { + use TPResultError::*; + let rlines: Vec<&str> = packet.lines().collect(); + if rlines.len() < 2 { + return Err(StandardError(TPError::CorruptPacket)); + } + + let metaframe: Vec<&str> = rlines[0].split("/").collect(); + if metaframe.len() != 5 { + return Err(StandardError(TPError::InvalidMetaframe)); + } + let dataframe: Vec<&str> = rlines[1].split(" ").collect(); + + if metaframe[0].ne(MF_PROTOCOL_TAG) || metaframe[2].ne(MF_RESULT_TAG) { + return Err(StandardError(TPError::InvalidMetaframe)); + } + + // Check version compatibility + if let Some(version) = Version::new_from_str(metaframe[1]) { + if !self_version.is_compatible_with(&version) { + return Err(StandardError(TPError::ProtocolVersionMismatch)); + } + } else { + return Err(StandardError(TPError::InvalidMetaframe)); + } + + let respcode = match metaframe[4].parse::() { + Ok(v) => v, + Err(_) => return Err(UnrecognizedError(metaframe[4].to_owned())), + }; + + if let Some(respcode) = TPError::from_u8(respcode) { + match respcode { + Okay => { + // Enter dataframe and check result + if let Some(value) = dataframe.get(0) { + if dataframe.get(1).is_none() { + return Ok(value.to_string()); + } else { + return Err(StandardError(TPError::CorruptDataframe)); + } + } else { + return Err(StandardError(TPError::CorruptDataframe)); + } + } + x @ _ => return Err(StandardError(x)), + } + } else { + return Err(UnrecognizedError(respcode.to_string())); } - Err(TPQueryError::CorruptDataframe) } #[cfg(test)] @@ -264,3 +381,13 @@ fn test_query_packet_parsing() { let parsed_qpacket = parse_query_packet(qpacket, &Version(0, 1, 0)).unwrap(); assert_eq!(query_should_be, parsed_qpacket); } + +#[cfg(test)] +#[test] +fn test_result_packet_parsing() { + let v = Version(0, 1, 0); + let rpacket = result_packet!(v, 0, 18); + let result_should_be = 18.to_string(); + let parsed_rpacket = parse_result_packet(rpacket, &v).unwrap(); + assert_eq!(result_should_be, parsed_rpacket); +} diff --git a/libcore/terrapipe-spec-0.1.0.md b/libcore/terrapipe-spec-0.1.0.md deleted file mode 100644 index 396e7bbe..00000000 --- a/libcore/terrapipe-spec-0.1.0.md +++ /dev/null @@ -1,102 +0,0 @@ -# Terrapipe - -> Date: 2nd July, 2020
Copyright © 2020 Sayan Nandan - -Terrapipe is a protocol that is used by Terrabase for data transfer. It is an application layer -protocol that builds on top of TCP. Just like HTTP's request/response action, Terrapipe (i.e tp://) -also makes use of a query/result action. -From now on, I will refer to Terrapipe as _TP_ or _tp_. - -TP makes use of two packets: - -1. **The `Q` uery packet**: This is sent by the client -2. **The `R` esult packet**: This is sent by the server - -## The `Q` uery packet - -The `Q` uery packet has the following structure: - -``` -TP//Q/ ---------------- DATA ---------- -``` - -**Note:** The first line, is followed by a line break, and then the subsequent lines. - -### Line 1: Meta frame - -The first line is called the meta frame. The `` and their corresponding meanings are as follows: - -* **`VERSION`**: The version of the protocol, in semver form, i.e `major.minor.patch` . - -An example can be: `0.1.0` - -* **`QTYPE`**: This is the type of query. It can have the following values: - - `GET` : For `GET` operations - - `SET` : For `SET` operations - - `UPDATE` : For `UDPATE` operations - - `DEL` : For `DEL` operations - -#### Example meta frame - -``` -TP/0.1.0/Q/GET -``` - -### Line 2 and subsequent lines: Data frame - -The data frame doesn't have any defined format. It can be anything that can be transferred over TCP - that is, well, anything: letters, numbers or vaguely bytes. - -## The `R` esult packet - -The `R` esult packet has the following structure: - -``` -TP//R// ---------------------- DATA ------------------- -``` - -**Note:** The first line is followed by a line break, and then the subsequent lines. - -### Line 1: Meta frame - -Just like the `Q` uery packet, the first line is called the meta frame. -The `` and their corresponding meanings are as follows: - -* **`VERSION`**: The version of the protocol, in semver form, i.e `major.minor.patch` . - -An example can be: `0.1.0` - -* **`RESPONSECODE`**: This is the outcome of the query. It can have the following values: - - 0: Okay - - 1: Not found - - 2: Method not allowed - - 3: Server error - - 4: Corrupt byte - - 5: Protocol version mismatch -* **`LENGTH`**: The number of bytes that are being transmitted. This is useful for preallocating buffers for copying the data. - -#### Example data frame - -``` -sayan is writing a protocol -``` - -## An example of a query/result - -Let us assume a key called `sayan` with a value of '17' exists on the database. -Our client, uses `0.1.0` version of tp and sends a `GET` request for the key to our server which also uses version `0.1.0` of tp. - -### The `Q` uery packet - -``` -TP/0.1.0/Q/GET -sayan -``` - -### The `R` esult packet - -``` -TP/0.1.0/R/0/2 -17 -``` From cfcc73bd860af1cc67325b2439fae2a60ece5cde Mon Sep 17 00:00:00 2001 From: Sayan Nandan Date: Wed, 8 Jul 2020 13:28:26 +0530 Subject: [PATCH 4/6] Add code to benchmark parsers --- Cargo.lock | 87 ++++++++++++++++++++++++++++++++++++++++ libcore/Cargo.toml | 5 +++ libcore/src/terrapipe.rs | 40 +++++++++++++++--- 3 files changed, 127 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d003514b..f542e5de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,8 +1,79 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "devtimer" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "getrandom" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", + "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libc" +version = "0.2.72" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "libcore" version = "0.1.0" +dependencies = [ + "devtimer 4.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ppv-lite86 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "terrabase-cli" @@ -12,3 +83,19 @@ version = "0.1.0" name = "terrabase-server" version = "0.1.0" +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +"checksum devtimer 4.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6035b7b9244bf9637cd7ef80b5e1c54404bef92cccd34738c85c45f04ae8b244" +"checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" +"checksum libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)" = "a9f8082297d534141b30c8d39e9b1773713ab50fdbe4ff30f750d063b3bfd701" +"checksum ppv-lite86 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" +"checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +"checksum rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +"checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" diff --git a/libcore/Cargo.toml b/libcore/Cargo.toml index a560f08e..a4bc2723 100644 --- a/libcore/Cargo.toml +++ b/libcore/Cargo.toml @@ -7,3 +7,8 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] + + +[dev-dependencies] +devtimer = "4.0.0" +rand = "0.7.3" \ No newline at end of file diff --git a/libcore/src/terrapipe.rs b/libcore/src/terrapipe.rs index 39ac5b03..abf1d28a 100644 --- a/libcore/src/terrapipe.rs +++ b/libcore/src/terrapipe.rs @@ -229,7 +229,7 @@ fn test_result_macros() { /// This returns a `TPQueryMethod` which can be used to execute the action or /// it returns a `TPQueryError` in the case an error occurs while parsing the packet pub fn parse_query_packet( - packet: String, + packet: &String, self_version: &Version, ) -> Result { let rlines: Vec<&str> = packet.lines().collect(); @@ -319,7 +319,7 @@ pub type TPResult = String; /// If there was no error in parsing the packet, then a `TPResult` is returned. /// Otherwise a `TPResultError` is returned pub fn parse_result_packet( - packet: String, + packet: &String, self_version: &Version, ) -> Result { use TPResultError::*; @@ -347,7 +347,7 @@ pub fn parse_result_packet( return Err(StandardError(TPError::InvalidMetaframe)); } - let respcode = match metaframe[4].parse::() { + let respcode = match metaframe[3].parse::() { Ok(v) => v, Err(_) => return Err(UnrecognizedError(metaframe[4].to_owned())), }; @@ -378,7 +378,7 @@ pub fn parse_result_packet( fn test_query_packet_parsing() { let qpacket = query_packet!(Version(0, 1, 0), TPQueryType::GET, "sayan"); let query_should_be = TPQueryMethod::GET("sayan".to_owned()); - let parsed_qpacket = parse_query_packet(qpacket, &Version(0, 1, 0)).unwrap(); + let parsed_qpacket = parse_query_packet(&qpacket, &Version(0, 1, 0)).unwrap(); assert_eq!(query_should_be, parsed_qpacket); } @@ -388,6 +388,36 @@ fn test_result_packet_parsing() { let v = Version(0, 1, 0); let rpacket = result_packet!(v, 0, 18); let result_should_be = 18.to_string(); - let parsed_rpacket = parse_result_packet(rpacket, &v).unwrap(); + let parsed_rpacket = parse_result_packet(&rpacket, &v).unwrap(); assert_eq!(result_should_be, parsed_rpacket); } + +#[cfg(test)] +#[test] +fn benchmark_packet_parsing() { + let version = Version(0, 1, 0); + use devtimer; + use rand::{distributions::Alphanumeric, thread_rng, Rng}; + // First generate about 5000 random keys and 5000 random values + let rankeys: Vec = (0..5000) + .map(|_| thread_rng().sample_iter(&Alphanumeric).take(30).collect()) + .collect(); + let ranvalues: Vec = (0..5000) + .map(|_| thread_rng().sample_iter(&Alphanumeric).take(30).collect()) + .collect(); + let queries: Vec = (0..5000) + .map(|n| query_packet!(version, TPQueryType::GET, rankeys[n])) + .collect(); + let results: Vec = (0..5000) + .map(|n| result_packet!(version, 0, ranvalues[n])) + .collect(); + + let qpacket_bench = devtimer::run_benchmark(5000, |n| { + parse_query_packet(&queries[n], &version).unwrap(); + }); + let rpacket_bench = devtimer::run_benchmark(5000, |n| { + parse_result_packet(&results[n], &version).unwrap(); + }); + qpacket_bench.print_stats(); + rpacket_bench.print_stats(); +} From ad7987878c6ceb2b3e6afed2a619a8ad9e5c5b99 Mon Sep 17 00:00:00 2001 From: Sayan Nandan Date: Thu, 9 Jul 2020 11:15:05 +0530 Subject: [PATCH 5/6] Add qpacket tests --- libcore/src/terrapipe.rs | 61 ++++++++++++++++++++++++++++++++++------ 1 file changed, 52 insertions(+), 9 deletions(-) diff --git a/libcore/src/terrapipe.rs b/libcore/src/terrapipe.rs index abf1d28a..b94cc980 100644 --- a/libcore/src/terrapipe.rs +++ b/libcore/src/terrapipe.rs @@ -43,7 +43,7 @@ macro_rules! result_packet { ($version:expr, $respcode:expr, $data:expr) => {{ let data = $data.to_string(); format!( - "TP/{}/R/{}/{}\n{}", + "TP/{}/R/{}/{}\n\n{}", $version.to_string(), $respcode, data.len(), @@ -56,7 +56,7 @@ macro_rules! result_packet { macro_rules! query_packet { ($version:expr, $querytype:expr, $data:expr) => { format!( - "TP/{}/Q/{}\n{}", + "TP/{}/Q/{}\n\n{}", $version.to_string(), $querytype.to_string(), $data @@ -219,8 +219,8 @@ fn test_result_macros() { let proto_version = Version(0, 1, 0); let query = query_packet!(proto_version, TPQueryType::GET, "sayan"); let result = result_packet!(proto_version, 0, 17); - let query_should_be = "TP/0.1.0/Q/GET\nsayan".to_owned(); - let result_should_be = "TP/0.1.0/R/0/2\n17".to_owned(); + let query_should_be = "TP/0.1.0/Q/GET\n\nsayan".to_owned(); + let result_should_be = "TP/0.1.0/R/0/2\n\n17".to_owned(); assert_eq!(query, query_should_be); assert_eq!(result, result_should_be); } @@ -258,10 +258,10 @@ pub fn parse_query_packet( /* TODO: This is temporary - the dataframe in the future may be multiple lines long */ - let dataframe: Vec<&str> = rlines[1].split_whitespace().collect(); - if dataframe.len() == 0 { - return Err(TPQueryError(TPError::CorruptDataframe)); - } + let dataframe: Vec<&str> = match rlines.get(2) { + Some(s) => s.split_whitespace().collect(), + None => return Err(TPQueryError(TPError::CorruptDataframe)), + }; match metaframe[3] { MF_QUERY_GET_TAG => { // This is a GET query @@ -332,7 +332,10 @@ pub fn parse_result_packet( if metaframe.len() != 5 { return Err(StandardError(TPError::InvalidMetaframe)); } - let dataframe: Vec<&str> = rlines[1].split(" ").collect(); + let dataframe: Vec<&str> = match rlines.get(2) { + Some(s) => s.split_whitespace().collect(), + None => return Err(StandardError(TPError::CorruptDataframe)), + }; if metaframe[0].ne(MF_PROTOCOL_TAG) || metaframe[2].ne(MF_RESULT_TAG) { return Err(StandardError(TPError::InvalidMetaframe)); @@ -421,3 +424,43 @@ fn benchmark_packet_parsing() { qpacket_bench.print_stats(); rpacket_bench.print_stats(); } + +#[cfg(test)] +#[test] +fn test_qpacket_error() { + let v = Version(0, 1, 0); + let ep_bad_mf_tp_tag = "AP/0.1.0/Q/GET\n\nsayan".to_owned(); + let eq_invalid_mf = TPQueryError(TPError::InvalidMetaframe); + assert_eq!( + parse_query_packet(&ep_bad_mf_tp_tag, &v).err().unwrap(), + eq_invalid_mf + ); + let ep_bad_mf_q_tag = "TP/0.1.0/W/GET\n\nsayan".to_owned(); + assert_eq!( + parse_query_packet(&ep_bad_mf_q_tag, &v).err().unwrap(), + eq_invalid_mf + ); + let ep_bad_mf_version = "TP/0.1/W/GET\n\nsayan".to_owned(); + assert_eq!( + parse_query_packet(&ep_bad_mf_version, &v).err().unwrap(), + eq_invalid_mf + ); + let eq_method_not_allowed = TPQueryError(TPError::MethodNotAllowed); + let ep_bad_mf_method = "TP/0.1.0/Q/WTH\n\nsayan".to_owned(); + assert_eq!( + parse_query_packet(&ep_bad_mf_method, &v).err().unwrap(), + eq_method_not_allowed + ); + let ep_corruptpacket = "TP/0.1.0/Q/GET".to_owned(); + let eq_corruptpacket = TPQueryError(TPError::CorruptPacket); + assert_eq!( + parse_query_packet(&ep_corruptpacket, &v).err().unwrap(), + eq_corruptpacket + ); + let ep_corrupt_df = "TP/0.1.0/Q/GET\n\n".to_owned(); + let eq_corrupt_df = TPQueryError(TPError::CorruptDataframe); + assert_eq!( + parse_query_packet(&ep_corrupt_df, &v).err().unwrap(), + eq_corrupt_df + ); +} From ff932f686c389db8dcc13a6799b3312f036f8d8f Mon Sep 17 00:00:00 2001 From: Sayan Nandan Date: Thu, 9 Jul 2020 11:29:54 +0530 Subject: [PATCH 6/6] Add rpacket tests --- libcore/src/terrapipe.rs | 52 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/libcore/src/terrapipe.rs b/libcore/src/terrapipe.rs index b94cc980..cab9c90e 100644 --- a/libcore/src/terrapipe.rs +++ b/libcore/src/terrapipe.rs @@ -355,13 +355,22 @@ pub fn parse_result_packet( Err(_) => return Err(UnrecognizedError(metaframe[4].to_owned())), }; + let respsize = match metaframe[4].parse::() { + Ok(r) => r, + Err(_) => return Err(StandardError(TPError::InvalidMetaframe)), + }; + if let Some(respcode) = TPError::from_u8(respcode) { match respcode { - Okay => { + TPError::Okay => { // Enter dataframe and check result if let Some(value) = dataframe.get(0) { if dataframe.get(1).is_none() { - return Ok(value.to_string()); + if value.len() == respsize { + return Ok(value.to_string()); + } else { + return Err(StandardError(TPError::CorruptDataframe)); + } } else { return Err(StandardError(TPError::CorruptDataframe)); } @@ -464,3 +473,42 @@ fn test_qpacket_error() { eq_corrupt_df ); } + +#[cfg(test)] +#[test] +fn test_rpacket_error() { + let v = Version(0, 1, 0); + use TPResultError::*; + let bad_tp_tag = "AP/0.1.0/R/5\n\nsayan".to_owned(); + let bad_version = "TP/0.1/R/0/5\n\nsayan".to_owned(); + let bad_mf_r_tag = "TP/0.1.0/X/0/5\n\nsayan".to_owned(); + let bad_mf_no_size = "TP/0.1.0/R/0\n\nsayan".to_owned(); + let bad_df = "TP/0.1.0/R/0/5\n\n".to_owned(); + let bad_df_size = "TP/0.1.0/0/4\n\nsaya".to_owned(); + let err_invalid_mf = StandardError(TPError::InvalidMetaframe); + let err_corrupt_df = StandardError(TPError::CorruptDataframe); + assert_eq!( + parse_result_packet(&bad_tp_tag, &v).err().unwrap(), + err_invalid_mf + ); + assert_eq!( + parse_result_packet(&bad_version, &v).err().unwrap(), + err_invalid_mf + ); + assert_eq!( + parse_result_packet(&bad_mf_r_tag, &v).err().unwrap(), + err_invalid_mf + ); + assert_eq!( + parse_result_packet(&bad_mf_no_size, &v).err().unwrap(), + err_invalid_mf + ); + assert_eq!( + parse_result_packet(&bad_df, &v).err().unwrap(), + err_corrupt_df + ); + assert_eq!( + parse_result_packet(&bad_df_size, &v).err().unwrap(), + err_invalid_mf + ); +}