Formal terrapipe query parsing

next
Sayan Nandan 4 years ago
parent a059f8b3e4
commit 3740b53efd
No known key found for this signature in database
GPG Key ID: C31EFD7DDA12AEE0

@ -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 an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee. copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS

@ -19,16 +19,56 @@
* *
*/ */
const METAFRAME_PROTOCOL_TAG: &'static str = "TP"; //! This is the implementation of the terrabasedb/RFC#1
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";
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); pub struct Version(u8, u8, u8);
impl ToString for Version {
fn to_string(&self) -> String {
format!("{}.{}.{}", self.0, self.1, self.2)
}
}
impl Version { 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<Self> { pub fn new_from_str<'a>(val: &'a str) -> Option<Self> {
let vals: Vec<&str> = val.split(".").collect(); let vals: Vec<&str> = val.split(".").collect();
if vals.len() != 3 { if vals.len() != 3 {
@ -45,6 +85,7 @@ impl Version {
return None; return None;
} }
} }
/// Use semver to check if the versions are compatible with each other
pub fn is_compatible_with(&self, other: &Version) -> bool { pub fn is_compatible_with(&self, other: &Version) -> bool {
if self.0 == other.0 { if self.0 == other.0 {
true true
@ -54,138 +95,172 @@ impl Version {
} }
} }
/// `Key` is a type alias for `String`
type Key = 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), GET(Key),
SET(Key, Key), SET(Key, Value),
UPDATE(Key, Key), UPDATE(Key, Value),
DEL(Key, Key), 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,
}
#[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(own_version: Version, packet: &String) { pub fn parse_query_packet(
packet: String,
self_version: &Version,
) -> Result<TPQueryMethod, TPQueryError> {
let rlines: Vec<&str> = packet.lines().collect(); let rlines: Vec<&str> = packet.lines().collect();
// This should give us two or more lines
if rlines.len() < 2 { if rlines.len() < 2 {
eprintln!("error: 5 CorruptDataframe"); return Err(TPQueryError::CorruptPacket);
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 { let metaframe: Vec<&str> = rlines[0].split("/").collect();
eprintln!("error: 4 InvalidMetaframe"); if metaframe.len() != 4 {
return Err(TPQueryError::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 { } 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"); if metaframe[2] != MF_QUERY_TAG {
return; return Err(TPQueryError::InvalidMetaframe);
} }
} else { let dataframe: Vec<&str> = rlines[1].split_whitespace().collect();
eprintln!("error: 5 Corrupt Dataframe"); if dataframe.len() == 0 {
return; 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()));
} }
&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 { MF_QUERY_SET_TAG => {
eprintln!("error: 5 Corrupt Dataframe"); // This is a SET query
return; if let Some(key) = dataframe.get(0) {
if let Some(value) = dataframe.get(1) {
return Ok(TPQueryMethod::SET(key.to_string(), value.to_string()));
} }
} }
&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 { MF_QUERY_UPDATE_TAG => {
eprintln!("error: 5 Corrupt Dataframe"); // This is a SET query
return; if let Some(key) = dataframe.get(0) {
if let Some(value) = dataframe.get(1) {
return Ok(TPQueryMethod::UPDATE(key.to_string(), value.to_string()));
} }
} }
_ => {
eprintln!("error: 4 Invalid Metaframe");
return;
} }
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 { // Some random illegal command
eprintln!("error: 5 Corrupt dataframe"); _ => return Err(TPQueryError::MethodNotAllowed),
return;
} }
Err(TPQueryError::CorruptDataframe)
} }
#[cfg(test)] #[cfg(test)]
#[test] #[test]
fn test_parse_header_query() { fn test_query_packet_parsing() {
let query_packet_get = "TP 0.1.1/Q GET/5\nsayan".to_owned(); let qpacket = query_packet!(Version(0, 1, 0), TPQueryType::GET, "sayan");
parse_query_packet(Version(0, 1, 1), &query_packet_get); let query_should_be = TPQueryMethod::GET("sayan".to_owned());
let query_packet_set = "TP 0.1.1/Q SET/8\nsayan 18".to_owned(); let parsed_qpacket = parse_query_packet(qpacket, &Version(0, 1, 0)).unwrap();
parse_query_packet(Version(0, 1, 1), &query_packet_set); assert_eq!(query_should_be, parsed_qpacket);
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);
} }

@ -17,8 +17,7 @@ TP makes use of two packets:
The `Q` uery packet has the following structure: The `Q` uery packet has the following structure:
``` ```
TP <VERSION>/Q <QTYPE>/<LENGTH> TP/<VERSION>/Q/<QTYPE>
\n
--------------- DATA ---------- --------------- DATA ----------
``` ```
@ -27,75 +26,77 @@ TP <VERSION>/Q <QTYPE>/<LENGTH>
### Line 1: Meta frame ### Line 1: Meta frame
The first line is called the meta frame. The `<VALUES>` and their corresponding meanings are as follows: The first line is called the meta frame. The `<VALUES>` and their corresponding meanings are as follows:
- **`VERSION`**: The version of the protocol, in semver form, i.e `major.minor.patch`.
* **`VERSION`**: The version of the protocol, in semver form, i.e `major.minor.patch` .
An example can be: `0.1.0` An example can be: `0.1.0`
- **`QTYPE`**: This is the type of query. It can have the following values:
* **`QTYPE`**: This is the type of query. It can have the following values:
- `GET` : For `GET` operations - `GET` : For `GET` operations
- `SET` : For `SET` operations - `SET` : For `SET` operations
- `UPDATE` : For `UDPATE` operations - `UPDATE` : For `UDPATE` operations
- `DEL` : For `DEL` operations - `DEL` : For `DEL` operations
- **`LENGTH`**: The number of bytes that are being transmitted. This is useful for preallocating buffers for copying the data.
#### Example meta frame #### Example meta frame
``` ```
TP 0.1.0/Q GET/15 TP/0.1.0/Q/GET
``` ```
### Line 2: Line break ### Line 2 and subsequent lines: Data frame
This is a line break that separates the meta frame from the 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 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
The `R` esult packet has the following structure: The `R` esult packet has the following structure:
``` ```
TP <VERSION>/R <QTYPE>/<RESPONSECODE>/<LENGTH> TP/<VERSION>/R/<RESPONSECODE>/<LENGTH>
\n
--------------------- DATA ------------------- --------------------- DATA -------------------
``` ```
**Note:** The first line is followed by a line break, and then the subsequent lines. **Note:** The first line is followed by a line break, and then the subsequent lines.
### Line 1: Meta frame ### 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 `<VALUES>` and their corresponding meanings are as follows: The `<VALUES>` and their corresponding meanings are as follows:
- **`VERSION`**: The version of the protocol, in semver form, i.e `major.minor.patch`.
* **`VERSION`**: The version of the protocol, in semver form, i.e `major.minor.patch` .
An example can be: `0.1.0` 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 * **`RESPONSECODE`**: This is the outcome of the query. It can have the following values:
- `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:
- 0: Okay - 0: Okay
- 1: Not found - 1: Not found
- 2: Method not allowed - 2: Method not allowed
- 3: Server error - 3: Server error
- 4: Corrupt byte - 4: Corrupt byte
- 5: Protocol version mismatch - 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 #### Example data frame
``` ```
sayan is writing a protocol sayan is writing a protocol
``` ```
## An example of a query/result ## An example of a query/result
Let us assume a key called `sayan` with a value of '17' exists on the database. 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. 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 ### The `Q` uery packet
``` ```
TP 0.1.0/Q GET/5 TP/0.1.0/Q/GET
\n
sayan sayan
``` ```
### The `R` esult packet ### The `R` esult packet
``` ```
TP 0.1.0/R GET/0/2 TP/0.1.0/R/0/2
\n
17 17
``` ```
Loading…
Cancel
Save