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 ```