Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unify parser and reimplement tests for it #19

Merged
merged 9 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
215 changes: 108 additions & 107 deletions memcrab-cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,107 +1,108 @@
use anyhow::bail;
use clap::Parser;
use core::num::NonZeroUsize;
use memcrab::RawClient;
use memcrab_cache::Cache;
use rustyline::error::ReadlineError;
use rustyline::DefaultEditor;

#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
#[arg(short = 'H', long, default_value = "127.0.0.1")]
host: String,

#[arg(short, long, default_value = "9090")]
port: String,

#[arg(short, long, action)]
server: bool,
}

async fn eval_lines(addr: String) -> anyhow::Result<()> {
let addr = addr.parse()?;
let mut client = RawClient::connect(addr).await?;
let mut editor = DefaultEditor::new()?;
loop {
let line = editor.readline("memcrab> ");
match line {
Ok(line) => {
eval_line(&mut client, line)
.await
.unwrap_or_else(|e| println!("error: {:?}", e));
}
Err(ReadlineError::Interrupted) => {
println!("Ctrl-c");
}
Err(ReadlineError::Eof) => {
println!("quit");
break;
}
Err(err) => {
println!("error: {:?}", err);
break;
}
}
}

Ok(())
}

async fn eval_line(client: &mut RawClient, line: String) -> anyhow::Result<()> {
let tokens = line.split_whitespace().collect::<Vec<_>>();
if tokens.is_empty() {
return Ok(());
}
if tokens[0] == "get" {
if tokens.len() != 2 {
bail!("syntax error: expected one key after `get`");
}
let resp = client.get(tokens[1]).await?;
match resp {
Some(val) => println!("{}: {:?}", tokens[1], val),
None => println!("no value set"),
}
} else if tokens[0] == "set" {
if tokens.len() < 3 {
bail!("syntax error: expected one key and bytes after `set`");
}

client
.set(
tokens[1],
tokens[2..]
.iter()
.map(|&s| s.parse().unwrap())
.collect::<Vec<u8>>(),
)
.await?;
} else {
bail!("syntax error: unexpected token {}", tokens[0]);
}
Ok(())
}

#[allow(unused)]
async fn serve(addr: String) -> anyhow::Result<()> {
let maxbytes = 100_000;
let maxlen = NonZeroUsize::new(110).unwrap();
let cache = Cache::new(maxlen, maxbytes);

todo!("server is not implemented");
Ok(())
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
let cli = Cli::parse();

let addr = format!("{}:{}", cli.host, cli.port);

if cli.server {
serve(addr).await?;
} else {
eval_lines(addr).await?;
}
Ok(())
}
// use anyhow::bail;
// use clap::Parser;
// use core::num::NonZeroUsize;
// use memcrab::RawClient;
// use memcrab_cache::Cache;
// use rustyline::error::ReadlineError;
// use rustyline::DefaultEditor;
//
// #[derive(Parser)]
// #[command(author, version, about, long_about = None)]
// struct Cli {
// #[arg(short = 'H', long, default_value = "127.0.0.1")]
// host: String,
//
// #[arg(short, long, default_value = "9090")]
// port: String,
//
// #[arg(short, long, action)]
// server: bool,
// }
//
// async fn eval_lines(addr: String) -> anyhow::Result<()> {
// let addr = addr.parse()?;
// let mut client = RawClient::connect(addr).await?;
// let mut editor = DefaultEditor::new()?;
// loop {
// let line = editor.readline("memcrab> ");
// match line {
// Ok(line) => {
// eval_line(&mut client, line)
// .await
// .unwrap_or_else(|e| println!("error: {:?}", e));
// }
// Err(ReadlineError::Interrupted) => {
// println!("Ctrl-c");
// }
// Err(ReadlineError::Eof) => {
// println!("quit");
// break;
// }
// Err(err) => {
// println!("error: {:?}", err);
// break;
// }
// }
// }
//
// Ok(())
// }
//
// async fn eval_line(client: &mut RawClient, line: String) -> anyhow::Result<()> {
// let tokens = line.split_whitespace().collect::<Vec<_>>();
// if tokens.is_empty() {
// return Ok(());
// }
// if tokens[0] == "get" {
// if tokens.len() != 2 {
// bail!("syntax error: expected one key after `get`");
// }
// let resp = client.get(tokens[1]).await?;
// match resp {
// Some(val) => println!("{}: {:?}", tokens[1], val),
// None => println!("no value set"),
// }
// } else if tokens[0] == "set" {
// if tokens.len() < 3 {
// bail!("syntax error: expected one key and bytes after `set`");
// }
//
// client
// .set(
// tokens[1],
// tokens[2..]
// .iter()
// .map(|&s| s.parse().unwrap())
// .collect::<Vec<u8>>(),
// )
// .await?;
// } else {
// bail!("syntax error: unexpected token {}", tokens[0]);
// }
// Ok(())
// }
//
// #[allow(unused)]
// async fn serve(addr: String) -> anyhow::Result<()> {
// let maxbytes = 100_000;
// let maxlen = NonZeroUsize::new(110).unwrap();
// let cache = Cache::new(maxlen, maxbytes);
//
// todo!("server is not implemented");
// Ok(())
// }
//
// #[tokio::main]
// async fn main() -> anyhow::Result<()> {
// let cli = Cli::parse();
//
// let addr = format!("{}:{}", cli.host, cli.port);
//
// if cli.server {
// serve(addr).await?;
// } else {
// eval_lines(addr).await?;
// }
// Ok(())
// }
fn main() {}
76 changes: 76 additions & 0 deletions memcrab-protocol/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# memcrab-protocol

This crate contains the implementation of the protocol that is used by server and the official Rust client.

**Note that this is not meant to be the official Rust API for memcrab, use the official wrapper client instead.**


## Usage
```rust
TODO

use memcrab_protocol::Socket;
use memcrab_protocol::{Message, Request, Response};

#[tokio::main]
async fn main() {
}
```


## Protocol description

### Encoding

This is a binary protocol. The keys should be encoded as valid UTF-8 strings though.

### Messages
TCP messages are not framed to distinct messages by themselves. Instead, we need to implement message borders ourselves.


Memcrab messages contain a header of a fixed length, and then payload of variable length.


The first byte of the header encodes the kind of message. The rest of the header encodes the information about the lengths of the payload or other metainfo.


Message kinds are shared by all messages for client and server. Clients should only send request messages and understand responses messages however, vice versa.


| Message kind | first byte | rest of the header | payload
| --- | --- | --- | ---
| VersionRequest | 0 | version | none
| PingRequest | 1 | none | none
| GetRequest | 2 | klen | key
| SetRequest | 3 | klen, vlen, exp | key, value
| DeleteRequest | 4 | klen | key
| ClearRequest | 5 | none | none

| PongResponse | 128 | none | none
| OkResponse | 129 | none | none
| ValueResponse | 130 | vlen | value
| KeyNotFoundResponse | 131 | none | none
| ErrorResponse | 132 | vlen | value


The lengths of fields for klen, vlen, version, etc are as follows:


| header field | size (bytes) |
| --- | --- |
| klen | 8 |
| vlen | 8 |
| version | 2 |
| exp | 4 |

The header length is 21 bytes.


### Versioning
Protocol is versioned by a number and are not backwards compatible.

The current version is `0`.

The clients must send `Version` message as their first message. The server must close the connection if the version is not compatible.


Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
pub type Version = u16;
pub type ErrMsgLen = u64;
pub type KeyLen = u64;
pub type ValueLen = u64;
pub type Expiration = u32;
23 changes: 13 additions & 10 deletions memcrab-protocol/src/err.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
use thiserror::Error;

#[derive(Error, Debug)]
pub enum ClientSideError {
pub enum Error {
#[error("io")]
IO(#[from] std::io::Error),
#[error("parsing failed")]
Parsing(#[from] ParsingError),
}

pub type ServerSideError = ClientSideError;
#[error("cannot parse message")]
Parse(#[from] ParseError),
}

#[derive(Error, Debug)]
pub enum ParsingError {
#[error("invalid header")]
Header,
#[error("invalid payload")]
Payload,
pub enum ParseError {
#[error("invalid message kind")]
UnknownKind,

#[error("malformed string")]
InvalidString,

#[error("message is too big")]
TooBig,
}
18 changes: 18 additions & 0 deletions memcrab-protocol/src/kind.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use num_enum::{IntoPrimitive, TryFromPrimitive};

#[repr(u8)]
#[derive(Debug, Clone, Copy, TryFromPrimitive, IntoPrimitive)]
pub enum MessageKind {
VersionRequest = 0,
PingRequest = 1,
GetRequest = 2,
SetRequest = 3,
DeleteRequest = 4,
ClearRequest = 5,

PongResponse = 128,
OkResponse = 129,
ValueResponse = 130,
KeyNotFoundResponse = 131,
ErrorResponse = 132,
}
28 changes: 13 additions & 15 deletions memcrab-protocol/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
mod alias;
mod err;

#[allow(unused)]
mod transport;

#[allow(unused)]
pub(crate) mod mapping;

pub mod io;

use mapping::alias::Version;

pub use err::{ClientSideError, ParsingError, ServerSideError};
pub use transport::{ClientSocket, ErrorResponse, Request, Response, ServerSocket};

pub const PROTOCOL_VERSION: Version = 0;
mod kind;
mod message;
mod sizes;
mod socket;
mod stream;
mod version;

pub use err::{Error, ParseError};
pub use message::{Message, Request, Response};
pub use socket::Socket;
pub use stream::{AsyncReader, AsyncWriter};
pub use version::VERSION;
Loading