#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::too_many_arguments)]
#![allow(clippy::match_same_arms)]
#![allow(clippy::needless_continue)]
#![allow(clippy::manual_let_else)]
#![allow(clippy::redundant_else)]
use super::commands::{
memcache_arithmetic, memcache_cas, memcache_delete, memcache_retrieval, memcache_storage,
memcache_touch,
};
use crate::msg::{KeyPos, Msg, MsgParseResult, MsgType};
pub const MEMCACHE_MAX_KEY_LENGTH: usize = 250;
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct HashTag {
pub open: u8,
pub close: u8,
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
#[repr(u32)]
pub enum ReqState {
#[default]
Start = 0,
ReqType = 1,
SpacesBeforeKey = 2,
Key = 3,
SpacesBeforeKeys = 4,
SpacesBeforeFlags = 5,
Flags = 6,
SpacesBeforeExpiry = 7,
Expiry = 8,
SpacesBeforeVlen = 9,
Vlen = 10,
SpacesBeforeCas = 11,
Cas = 12,
RuntoVal = 13,
Val = 14,
SpacesBeforeNum = 15,
Num = 16,
RuntoCrlf = 17,
Crlf = 18,
Noreply = 19,
AfterNoreply = 20,
AlmostDone = 21,
}
impl ReqState {
fn from_u32(v: u32) -> Self {
match v {
1 => Self::ReqType,
2 => Self::SpacesBeforeKey,
3 => Self::Key,
4 => Self::SpacesBeforeKeys,
5 => Self::SpacesBeforeFlags,
6 => Self::Flags,
7 => Self::SpacesBeforeExpiry,
8 => Self::Expiry,
9 => Self::SpacesBeforeVlen,
10 => Self::Vlen,
11 => Self::SpacesBeforeCas,
12 => Self::Cas,
13 => Self::RuntoVal,
14 => Self::Val,
15 => Self::SpacesBeforeNum,
16 => Self::Num,
17 => Self::RuntoCrlf,
18 => Self::Crlf,
19 => Self::Noreply,
20 => Self::AfterNoreply,
21 => Self::AlmostDone,
_ => Self::Start,
}
}
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
#[repr(u32)]
pub enum RspState {
#[default]
Start = 0,
RspNum = 1,
RspStr = 2,
SpacesBeforeKey = 3,
Key = 4,
SpacesBeforeFlags = 5,
Flags = 6,
SpacesBeforeVlen = 7,
Vlen = 8,
RuntoVal = 9,
Val = 10,
ValLf = 11,
End = 12,
RuntoCrlf = 13,
Crlf = 14,
AlmostDone = 15,
}
impl RspState {
fn from_u32(v: u32) -> Self {
match v {
1 => Self::RspNum,
2 => Self::RspStr,
3 => Self::SpacesBeforeKey,
4 => Self::Key,
5 => Self::SpacesBeforeFlags,
6 => Self::Flags,
7 => Self::SpacesBeforeVlen,
8 => Self::Vlen,
9 => Self::RuntoVal,
10 => Self::Val,
11 => Self::ValLf,
12 => Self::End,
13 => Self::RuntoCrlf,
14 => Self::Crlf,
15 => Self::AlmostDone,
_ => Self::Start,
}
}
}
const CR: u8 = b'\r';
const LF: u8 = b'\n';
fn classify_command(token: &[u8]) -> MsgType {
match token {
b"get" => MsgType::ReqMcGet,
b"gets" => MsgType::ReqMcGets,
b"set" => MsgType::ReqMcSet,
b"add" => MsgType::ReqMcAdd,
b"cas" => MsgType::ReqMcCas,
b"incr" => MsgType::ReqMcIncr,
b"decr" => MsgType::ReqMcDecr,
b"quit" => MsgType::ReqMcQuit,
b"touch" => MsgType::ReqMcTouch,
b"append" => MsgType::ReqMcAppend,
b"delete" => MsgType::ReqMcDelete,
b"prepend" => MsgType::ReqMcPrepend,
b"replace" => MsgType::ReqMcReplace,
_ => MsgType::Unknown,
}
}
fn classify_response(token: &[u8]) -> MsgType {
match token {
b"END" => MsgType::RspMcEnd,
b"VALUE" => MsgType::RspMcValue,
b"ERROR" => MsgType::RspMcError,
b"STORED" => MsgType::RspMcStored,
b"EXISTS" => MsgType::RspMcExists,
b"DELETED" => MsgType::RspMcDeleted,
b"TOUCHED" => MsgType::RspMcTouched,
b"NOT_FOUND" => MsgType::RspMcNotFound,
b"NOT_STORED" => MsgType::RspMcNotStored,
b"CLIENT_ERROR" => MsgType::RspMcClientError,
b"SERVER_ERROR" => MsgType::RspMcServerError,
_ => MsgType::Unknown,
}
}
fn make_keypos(input: &[u8], start: usize, end: usize, hash_tag: Option<HashTag>) -> KeyPos {
let bytes = input[start..end].to_vec();
if let Some(tag) = hash_tag {
if let Some(open_idx) = bytes.iter().position(|&b| b == tag.open) {
if let Some(close_offset) = bytes[open_idx + 1..].iter().position(|&b| b == tag.close) {
let tag_start = open_idx + 1;
let tag_end = open_idx + 1 + close_offset;
return KeyPos::new(bytes, tag_start..tag_end);
}
}
}
KeyPos::without_tag(bytes)
}
#[allow(clippy::too_many_lines)]
pub fn memcache_parse_req(r: &mut Msg, input: &[u8]) -> MsgParseResult {
memcache_parse_req_tagged(r, input, None)
}
#[allow(clippy::too_many_lines)]
pub fn memcache_parse_req_tagged(
r: &mut Msg,
input: &[u8],
hash_tag: Option<HashTag>,
) -> MsgParseResult {
if !r.is_request() {
r.set_parse_result(MsgParseResult::Error);
return MsgParseResult::Error;
}
let mut state = ReqState::from_u32(r.parser_state());
let mut p = r.parser_pos();
let mut token: Option<usize> = r.parser_token();
let mut vlen = r.vlen();
let mut ty = r.ty();
let mut is_read = r.flags().is_read;
let mut quit = r.flags().quit;
let mut expect_reply = r.flags().expect_datastore_reply;
let mut ntokens = r.ntokens();
'machine: while p < input.len() {
let ch = input[p];
match state {
ReqState::Start => {
if ch == b' ' {
p += 1;
continue;
}
if !ch.is_ascii_lowercase() {
return finish_error(r, state, p, token, vlen, ty, is_read, quit, ntokens);
}
token = Some(p);
state = ReqState::ReqType;
}
ReqState::ReqType => {
if ch == b' ' || ch == CR {
let start = match token {
Some(s) => s,
None => {
return finish_error(
r, state, p, token, vlen, ty, is_read, quit, ntokens,
);
}
};
let cmd = &input[start..p];
token = None;
ty = classify_command(cmd);
ntokens = ntokens.saturating_add(1);
is_read = matches!(
ty,
MsgType::ReqMcGet | MsgType::ReqMcGets | MsgType::ReqMcQuit
);
if matches!(ty, MsgType::ReqMcQuit) {
quit = true;
state = ReqState::Crlf;
continue;
}
match ty {
MsgType::ReqMcGet
| MsgType::ReqMcGets
| MsgType::ReqMcDelete
| MsgType::ReqMcCas
| MsgType::ReqMcSet
| MsgType::ReqMcAdd
| MsgType::ReqMcReplace
| MsgType::ReqMcAppend
| MsgType::ReqMcPrepend
| MsgType::ReqMcIncr
| MsgType::ReqMcDecr
| MsgType::ReqMcTouch => {
if ch == CR {
return finish_error(
r, state, p, token, vlen, ty, is_read, quit, ntokens,
);
}
state = ReqState::SpacesBeforeKey;
p += 1;
continue;
}
MsgType::Unknown => {
return finish_error(
r, state, p, token, vlen, ty, is_read, quit, ntokens,
);
}
_ => {
return finish_error(
r, state, p, token, vlen, ty, is_read, quit, ntokens,
);
}
}
} else if !ch.is_ascii_lowercase() {
return finish_error(r, state, p, token, vlen, ty, is_read, quit, ntokens);
} else {
p += 1;
}
}
ReqState::SpacesBeforeKey => {
if ch == b' ' {
p += 1;
} else {
token = None;
state = ReqState::Key;
}
}
ReqState::Key => {
if token.is_none() {
token = Some(p);
}
if ch == b' ' || ch == CR {
let start = token.expect("token recorded");
let keylen = p - start;
if keylen == 0 || keylen > MEMCACHE_MAX_KEY_LENGTH {
return finish_error(r, state, p, token, vlen, ty, is_read, quit, ntokens);
}
let kp = make_keypos(input, start, p, hash_tag);
r.push_key(kp);
ntokens = ntokens.saturating_add(1);
token = None;
let storage = memcache_storage(ty);
let arithmetic = memcache_arithmetic(ty);
let touch = memcache_touch(ty);
let delete = memcache_delete(ty);
let retrieval = memcache_retrieval(ty);
if storage {
state = ReqState::SpacesBeforeFlags;
} else if arithmetic || touch {
state = ReqState::SpacesBeforeNum;
} else if delete {
state = ReqState::RuntoCrlf;
} else if retrieval {
state = ReqState::SpacesBeforeKeys;
} else {
state = ReqState::RuntoCrlf;
}
if ch == CR {
if storage || arithmetic {
return finish_error(
r, state, p, token, vlen, ty, is_read, quit, ntokens,
);
}
} else {
p += 1;
}
} else {
p += 1;
}
}
ReqState::SpacesBeforeKeys => {
match ch {
b' ' => {
p += 1;
}
CR => {
state = ReqState::AlmostDone;
p += 1;
}
_ => {
token = None;
state = ReqState::Key;
}
}
}
ReqState::SpacesBeforeFlags => {
if ch == b' ' {
p += 1;
} else if ch.is_ascii_digit() {
token = Some(p);
state = ReqState::Flags;
p += 1;
} else {
return finish_error(r, state, p, token, vlen, ty, is_read, quit, ntokens);
}
}
ReqState::Flags => {
if ch.is_ascii_digit() {
p += 1;
} else if ch == b' ' {
token = None;
state = ReqState::SpacesBeforeExpiry;
p += 1;
} else {
return finish_error(r, state, p, token, vlen, ty, is_read, quit, ntokens);
}
}
ReqState::SpacesBeforeExpiry => {
if ch == b' ' {
p += 1;
} else if ch.is_ascii_digit() {
token = Some(p);
state = ReqState::Expiry;
p += 1;
} else {
return finish_error(r, state, p, token, vlen, ty, is_read, quit, ntokens);
}
}
ReqState::Expiry => {
if ch.is_ascii_digit() {
p += 1;
} else if ch == b' ' {
token = None;
state = ReqState::SpacesBeforeVlen;
p += 1;
} else {
return finish_error(r, state, p, token, vlen, ty, is_read, quit, ntokens);
}
}
ReqState::SpacesBeforeVlen => {
if ch == b' ' {
p += 1;
} else if ch.is_ascii_digit() {
vlen = u32::from(ch - b'0');
state = ReqState::Vlen;
p += 1;
} else {
return finish_error(r, state, p, token, vlen, ty, is_read, quit, ntokens);
}
}
ReqState::Vlen => {
if ch.is_ascii_digit() {
vlen = vlen.saturating_mul(10).saturating_add(u32::from(ch - b'0'));
p += 1;
} else if memcache_cas(ty) {
if ch != b' ' {
return finish_error(r, state, p, token, vlen, ty, is_read, quit, ntokens);
}
token = None;
state = ReqState::SpacesBeforeCas;
} else if ch == b' ' || ch == CR {
token = None;
state = ReqState::RuntoCrlf;
} else {
return finish_error(r, state, p, token, vlen, ty, is_read, quit, ntokens);
}
}
ReqState::SpacesBeforeCas => {
if ch == b' ' {
p += 1;
} else if ch.is_ascii_digit() {
token = Some(p);
state = ReqState::Cas;
p += 1;
} else {
return finish_error(r, state, p, token, vlen, ty, is_read, quit, ntokens);
}
}
ReqState::Cas => {
if ch.is_ascii_digit() {
p += 1;
} else if ch == b' ' || ch == CR {
token = None;
state = ReqState::RuntoCrlf;
} else {
return finish_error(r, state, p, token, vlen, ty, is_read, quit, ntokens);
}
}
ReqState::RuntoVal => match ch {
LF => {
state = ReqState::Val;
p += 1;
}
_ => {
return finish_error(r, state, p, token, vlen, ty, is_read, quit, ntokens);
}
},
ReqState::Val => {
let m = p.saturating_add(vlen as usize);
if m >= input.len() {
let consumed = input.len() - p;
vlen = vlen.saturating_sub(consumed as u32);
p = input.len();
break 'machine;
}
if input[m] != CR {
return finish_error(r, state, p, token, vlen, ty, is_read, quit, ntokens);
}
p = m + 1;
state = ReqState::AlmostDone;
}
ReqState::SpacesBeforeNum => {
if ch == b' ' {
p += 1;
} else if ch.is_ascii_digit() || ch == b'-' {
token = Some(p);
state = ReqState::Num;
p += 1;
} else {
return finish_error(r, state, p, token, vlen, ty, is_read, quit, ntokens);
}
}
ReqState::Num => {
if ch.is_ascii_digit() {
p += 1;
} else if ch == b' ' || ch == CR {
token = None;
state = ReqState::RuntoCrlf;
} else {
return finish_error(r, state, p, token, vlen, ty, is_read, quit, ntokens);
}
}
ReqState::RuntoCrlf => match ch {
b' ' => {
p += 1;
}
b'n' => {
if memcache_storage(ty)
|| memcache_arithmetic(ty)
|| memcache_delete(ty)
|| memcache_touch(ty)
{
token = Some(p);
state = ReqState::Noreply;
p += 1;
} else {
return finish_error(r, state, p, token, vlen, ty, is_read, quit, ntokens);
}
}
CR => {
if memcache_storage(ty) {
state = ReqState::RuntoVal;
} else {
state = ReqState::AlmostDone;
}
p += 1;
}
_ => {
return finish_error(r, state, p, token, vlen, ty, is_read, quit, ntokens);
}
},
ReqState::Noreply => match ch {
b' ' | CR => {
let start = match token {
Some(s) => s,
None => {
return finish_error(
r, state, p, token, vlen, ty, is_read, quit, ntokens,
);
}
};
if p - start == 7 && &input[start..p] == b"noreply" {
token = None;
expect_reply = false;
state = ReqState::AfterNoreply;
} else {
return finish_error(r, state, p, token, vlen, ty, is_read, quit, ntokens);
}
}
_ => {
p += 1;
}
},
ReqState::AfterNoreply => match ch {
b' ' => {
p += 1;
}
CR => {
if memcache_storage(ty) {
state = ReqState::RuntoVal;
} else {
state = ReqState::AlmostDone;
}
p += 1;
}
_ => {
return finish_error(r, state, p, token, vlen, ty, is_read, quit, ntokens);
}
},
ReqState::Crlf => match ch {
b' ' => {
p += 1;
}
CR => {
state = ReqState::AlmostDone;
p += 1;
}
_ => {
return finish_error(r, state, p, token, vlen, ty, is_read, quit, ntokens);
}
},
ReqState::AlmostDone => match ch {
LF => {
return finish_done(r, p + 1, ty, is_read, quit, expect_reply, ntokens, vlen);
}
_ => {
return finish_error(r, state, p, token, vlen, ty, is_read, quit, ntokens);
}
},
}
}
r.set_parser_state(state as u32);
r.set_parser_pos(p);
r.set_parser_token(token);
r.set_vlen(vlen);
r.set_ntokens(ntokens);
if ty != MsgType::Unknown {
r.set_type(ty);
}
r.flags_mut().is_read = is_read;
r.flags_mut().quit = quit;
r.flags_mut().expect_datastore_reply = expect_reply;
r.set_parse_result(MsgParseResult::Again);
MsgParseResult::Again
}
#[allow(clippy::too_many_arguments)]
fn finish_done(
r: &mut Msg,
next_pos: usize,
ty: MsgType,
is_read: bool,
quit: bool,
expect_reply: bool,
ntokens: u32,
vlen: u32,
) -> MsgParseResult {
r.set_type(ty);
r.flags_mut().is_read = is_read;
r.flags_mut().quit = quit;
r.flags_mut().expect_datastore_reply = expect_reply;
r.set_ntokens(ntokens);
r.set_vlen(vlen);
r.set_parser_state(ReqState::Start as u32);
r.set_parser_pos(next_pos);
r.set_parser_token(None);
r.set_parse_result(MsgParseResult::Ok);
MsgParseResult::Ok
}
#[allow(clippy::too_many_arguments)]
fn finish_error(
r: &mut Msg,
state: ReqState,
pos: usize,
token: Option<usize>,
vlen: u32,
ty: MsgType,
is_read: bool,
quit: bool,
ntokens: u32,
) -> MsgParseResult {
r.set_parser_state(state as u32);
r.set_parser_pos(pos);
r.set_parser_token(token);
r.set_vlen(vlen);
r.set_ntokens(ntokens);
if ty != MsgType::Unknown {
r.set_type(ty);
}
r.flags_mut().is_read = is_read;
r.flags_mut().quit = quit;
r.set_parse_result(MsgParseResult::Error);
MsgParseResult::Error
}
#[allow(clippy::too_many_lines)]
pub fn memcache_parse_rsp(r: &mut Msg, input: &[u8]) -> MsgParseResult {
if r.is_request() {
r.set_parse_result(MsgParseResult::Error);
return MsgParseResult::Error;
}
let mut state = RspState::from_u32(r.parser_state());
let mut p = r.parser_pos();
let mut token: Option<usize> = r.parser_token();
let mut vlen = r.vlen();
let mut ty = r.ty();
let mut end_marker = r.end_marker();
while p < input.len() {
let ch = input[p];
match state {
RspState::Start => {
if ch.is_ascii_digit() {
state = RspState::RspNum;
} else {
state = RspState::RspStr;
}
}
RspState::RspNum => {
if token.is_none() {
token = Some(p);
}
if ch.is_ascii_digit() {
p += 1;
} else if ch == b' ' || ch == CR {
token = None;
ty = MsgType::RspMcNum;
state = RspState::Crlf;
} else {
return finish_error_rsp(r, state, p, token, vlen, ty, end_marker);
}
}
RspState::RspStr => {
if token.is_none() {
token = Some(p);
}
if ch == b' ' || ch == CR {
let start = token.expect("token recorded");
let key_bytes = &input[start..p];
ty = classify_response(key_bytes);
if ty == MsgType::RspMcEnd {
end_marker = Some(start);
}
match ty {
MsgType::Unknown => {
return finish_error_rsp(r, state, p, token, vlen, ty, end_marker);
}
MsgType::RspMcStored
| MsgType::RspMcNotStored
| MsgType::RspMcExists
| MsgType::RspMcNotFound
| MsgType::RspMcDeleted
| MsgType::RspMcTouched
| MsgType::RspMcEnd
| MsgType::RspMcError => {
state = RspState::Crlf;
}
MsgType::RspMcValue => {
state = RspState::SpacesBeforeKey;
}
MsgType::RspMcClientError | MsgType::RspMcServerError => {
state = RspState::RuntoCrlf;
}
_ => {
return finish_error_rsp(r, state, p, token, vlen, ty, end_marker);
}
}
} else {
p += 1;
}
}
RspState::SpacesBeforeKey => {
if ch == b' ' {
p += 1;
} else {
state = RspState::Key;
}
}
RspState::Key => {
if ch == b' ' {
state = RspState::SpacesBeforeFlags;
}
p += 1;
}
RspState::SpacesBeforeFlags => {
if ch == b' ' {
p += 1;
} else if ch.is_ascii_digit() {
state = RspState::Flags;
} else {
return finish_error_rsp(r, state, p, token, vlen, ty, end_marker);
}
}
RspState::Flags => {
if ch.is_ascii_digit() {
p += 1;
} else if ch == b' ' {
state = RspState::SpacesBeforeVlen;
p += 1;
} else {
return finish_error_rsp(r, state, p, token, vlen, ty, end_marker);
}
}
RspState::SpacesBeforeVlen => {
if ch == b' ' {
p += 1;
} else if ch.is_ascii_digit() {
state = RspState::Vlen;
vlen = 0;
} else {
return finish_error_rsp(r, state, p, token, vlen, ty, end_marker);
}
}
RspState::Vlen => {
if ch.is_ascii_digit() {
vlen = vlen.saturating_mul(10).saturating_add(u32::from(ch - b'0'));
p += 1;
} else if ch == b' ' || ch == CR {
state = RspState::RuntoCrlf;
} else {
return finish_error_rsp(r, state, p, token, vlen, ty, end_marker);
}
}
RspState::RuntoVal => match ch {
LF => {
state = RspState::Val;
token = None;
p += 1;
}
_ => {
return finish_error_rsp(r, state, p, token, vlen, ty, end_marker);
}
},
RspState::Val => {
let m = p.saturating_add(vlen as usize);
if m >= input.len() {
let consumed = input.len() - p;
vlen = vlen.saturating_sub(consumed as u32);
p = input.len();
break;
}
if input[m] != CR {
return finish_error_rsp(r, state, p, token, vlen, ty, end_marker);
}
p = m + 1;
state = RspState::ValLf;
}
RspState::ValLf => match ch {
LF => {
state = RspState::RspStr;
p += 1;
}
_ => {
return finish_error_rsp(r, state, p, token, vlen, ty, end_marker);
}
},
RspState::End => {
if token.is_none() {
if ch != b'E' {
return finish_error_rsp(r, state, p, token, vlen, ty, end_marker);
}
token = Some(p);
p += 1;
} else if ch == CR {
let start = token.expect("token recorded");
if p - start == 3 && &input[start..p] == b"END" {
end_marker = Some(start);
state = RspState::AlmostDone;
token = None;
p += 1;
} else {
return finish_error_rsp(r, state, p, token, vlen, ty, end_marker);
}
} else {
p += 1;
}
}
RspState::RuntoCrlf => match ch {
CR => {
if ty == MsgType::RspMcValue {
state = RspState::RuntoVal;
} else {
state = RspState::AlmostDone;
}
p += 1;
}
_ => {
p += 1;
}
},
RspState::Crlf => match ch {
b' ' => {
p += 1;
}
CR => {
state = RspState::AlmostDone;
p += 1;
}
_ => {
return finish_error_rsp(r, state, p, token, vlen, ty, end_marker);
}
},
RspState::AlmostDone => match ch {
LF => {
r.set_type(ty);
r.set_vlen(vlen);
r.set_end_marker(end_marker);
r.set_parser_state(RspState::Start as u32);
r.set_parser_pos(p + 1);
r.set_parser_token(None);
r.set_parse_result(MsgParseResult::Ok);
return MsgParseResult::Ok;
}
_ => {
return finish_error_rsp(r, state, p, token, vlen, ty, end_marker);
}
},
}
}
r.set_parser_state(state as u32);
r.set_parser_pos(p);
r.set_parser_token(token);
r.set_vlen(vlen);
r.set_end_marker(end_marker);
if ty != MsgType::Unknown {
r.set_type(ty);
}
r.set_parse_result(MsgParseResult::Again);
MsgParseResult::Again
}
fn finish_error_rsp(
r: &mut Msg,
state: RspState,
pos: usize,
token: Option<usize>,
vlen: u32,
ty: MsgType,
end_marker: Option<usize>,
) -> MsgParseResult {
r.set_parser_state(state as u32);
r.set_parser_pos(pos);
r.set_parser_token(token);
r.set_vlen(vlen);
r.set_end_marker(end_marker);
if ty != MsgType::Unknown {
r.set_type(ty);
}
r.set_parse_result(MsgParseResult::Error);
MsgParseResult::Error
}
#[cfg(test)]
mod tests {
use super::*;
fn parse_req(input: &[u8]) -> Msg {
let mut m = Msg::new(0, MsgType::Unknown, true);
let _ = memcache_parse_req(&mut m, input);
m
}
fn parse_rsp(input: &[u8]) -> Msg {
let mut m = Msg::new(0, MsgType::Unknown, false);
let _ = memcache_parse_rsp(&mut m, input);
m
}
#[test]
fn parse_get() {
let m = parse_req(b"get key1\r\n");
assert_eq!(m.parse_result(), MsgParseResult::Ok);
assert_eq!(m.ty(), MsgType::ReqMcGet);
assert_eq!(m.keys()[0].key(), b"key1");
assert!(m.flags().is_read);
}
#[test]
fn parse_set() {
let m = parse_req(b"set key1 0 0 3\r\nval\r\n");
assert_eq!(m.parse_result(), MsgParseResult::Ok);
assert_eq!(m.ty(), MsgType::ReqMcSet);
assert_eq!(m.keys()[0].key(), b"key1");
assert_eq!(m.vlen(), 3);
}
#[test]
fn parse_set_noreply() {
let m = parse_req(b"set key1 0 0 3 noreply\r\nval\r\n");
assert_eq!(m.parse_result(), MsgParseResult::Ok);
assert!(!m.flags().expect_datastore_reply);
}
#[test]
fn parse_delete() {
let m = parse_req(b"delete key1\r\n");
assert_eq!(m.parse_result(), MsgParseResult::Ok);
assert_eq!(m.ty(), MsgType::ReqMcDelete);
}
#[test]
fn parse_incr() {
let m = parse_req(b"incr counter 1\r\n");
assert_eq!(m.parse_result(), MsgParseResult::Ok);
assert_eq!(m.ty(), MsgType::ReqMcIncr);
}
#[test]
fn parse_quit() {
let m = parse_req(b"quit\r\n");
assert_eq!(m.parse_result(), MsgParseResult::Ok);
assert_eq!(m.ty(), MsgType::ReqMcQuit);
assert!(m.flags().quit);
}
#[test]
fn parse_get_multikey() {
let m = parse_req(b"get a b c\r\n");
assert_eq!(m.parse_result(), MsgParseResult::Ok);
let keys: Vec<&[u8]> = m.keys().iter().map(crate::msg::KeyPos::key).collect();
assert_eq!(keys, vec![&b"a"[..], b"b", b"c"]);
}
#[test]
fn parse_cas() {
let m = parse_req(b"cas key1 0 0 3 7\r\nval\r\n");
assert_eq!(m.parse_result(), MsgParseResult::Ok);
assert_eq!(m.ty(), MsgType::ReqMcCas);
}
#[test]
fn parse_too_long_key_errors() {
let mut input = b"get ".to_vec();
input.extend(std::iter::repeat_n(b'k', MEMCACHE_MAX_KEY_LENGTH + 1));
input.extend_from_slice(b"\r\n");
let m = parse_req(&input);
assert_eq!(m.parse_result(), MsgParseResult::Error);
}
#[test]
fn parse_empty_key_errors() {
let m = parse_req(b"get \r\n");
assert_eq!(m.parse_result(), MsgParseResult::Error);
}
#[test]
fn parse_truncated_returns_again() {
let m = parse_req(b"get key");
assert_eq!(m.parse_result(), MsgParseResult::Again);
}
#[test]
fn parse_stored_response() {
let m = parse_rsp(b"STORED\r\n");
assert_eq!(m.ty(), MsgType::RspMcStored);
}
#[test]
fn parse_value_response() {
let m = parse_rsp(b"VALUE key 0 3\r\nval\r\nEND\r\n");
assert_eq!(m.parse_result(), MsgParseResult::Ok);
assert_eq!(m.ty(), MsgType::RspMcEnd);
}
#[test]
fn parse_numeric_response() {
let m = parse_rsp(b"42\r\n");
assert_eq!(m.parse_result(), MsgParseResult::Ok);
assert_eq!(m.ty(), MsgType::RspMcNum);
}
#[test]
fn parse_server_error_response() {
let m = parse_rsp(b"SERVER_ERROR oops\r\n");
assert_eq!(m.parse_result(), MsgParseResult::Ok);
assert_eq!(m.ty(), MsgType::RspMcServerError);
}
#[test]
fn parse_response_unknown_keyword_errors() {
let m = parse_rsp(b"BOGUS\r\n");
assert_eq!(m.parse_result(), MsgParseResult::Error);
}
}