Formal terrapipe query/result implementation

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

@ -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<Self> {
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<TPQueryMethod, TPQueryError> {
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<TPResult, TPResultError> {
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::<u8>() {
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);
}

@ -1,102 +0,0 @@
# Terrapipe
> Date: 2<sup>nd</sup> July, 2020<br>Copyright &copy; 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/<VERSION>/Q/<QTYPE>
--------------- 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 `<VALUES>` 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/<VERSION>/R/<RESPONSECODE>/<LENGTH>
--------------------- 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 `<VALUES>` 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
```
Loading…
Cancel
Save