Skip to content
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
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "meta-memcache-socket"
version = "0.1.5"
version = "0.1.6"
edition = "2021"

[lib]
Expand Down
17 changes: 5 additions & 12 deletions src/impl_build_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,10 @@ pub fn impl_build_cmd(
request_flags: Option<&RequestFlags>,
legacy_size_format: bool,
) -> Option<Vec<u8>> {
if key.len() >= MAX_KEY_LEN {
// Key is too long
if key.is_empty() || key.len() >= MAX_KEY_LEN {
return None;
}
let mut binary = false;
for c in key.iter() {
if *c <= b' ' || *c > b'~' {
// Not ascii or containing spaces
binary = true;
break;
}
}
let binary = key.iter().any(|&c| c <= b' ' || c > b'~');
if binary && key.len() >= MAX_BIN_KEY_LEN {
// Key is too long
return None;
Expand All @@ -40,7 +32,7 @@ pub fn impl_build_cmd(
if binary {
// If the key contains binary or spaces, it will be send in b64
let result = general_purpose::STANDARD.encode(key);
buf.extend_from_slice(&result.as_bytes());
buf.extend_from_slice(result.as_bytes());
} else {
// Otherwise, it will be send as is
buf.extend_from_slice(key);
Expand All @@ -52,7 +44,8 @@ pub fn impl_build_cmd(
if legacy_size_format {
buf.push(b'S');
}
buf.extend_from_slice(&size.to_string().as_bytes());
let mut itoa_buf = itoa::Buffer::new();
buf.extend_from_slice(itoa_buf.format(size).as_bytes());
}

// Add request flags
Expand Down
38 changes: 35 additions & 3 deletions src/impl_build_cmd_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ mod tests {
Some(666), // ma_delta_value,
Some(777), // cas_token
Some(b"opaque".to_vec()), // opaque
Some(65 as u8), // mode
Some(b'A'), // mode (APPEND)
);

let result = impl_build_cmd(cmd, key, None, Some(&request_flags), false).unwrap();
Expand Down Expand Up @@ -138,12 +138,44 @@ mod tests {
assert_eq!(result, b"mg S2V5IHdpdGggc3BhY2Vz b\r\n");
}

#[test]
fn test_empty_key_rejected() {
assert!(impl_build_cmd(b"mg", b"", None, None, false).is_none());
}

#[test]
fn test_key_at_max_length() {
// 249 bytes is OK (< 250)
let key = &vec![b'X'; 249];
assert!(impl_build_cmd(b"mg", key, None, None, false).is_some());
}

#[test]
fn test_key_at_exact_max_length() {
// 250 bytes is rejected (>= MAX_KEY_LEN)
let key = &vec![b'X'; 250];
assert!(impl_build_cmd(b"mg", key, None, None, false).is_none());
}

#[test]
fn test_binary_key_at_max_length() {
// 186 binary bytes is OK (< 187 = MAX_BIN_KEY_LEN)
let key = &vec![0x00u8; 186];
assert!(impl_build_cmd(b"mg", key, None, None, false).is_some());
}

#[test]
fn test_binary_key_at_exact_max_length() {
// 187 binary bytes is rejected (>= MAX_BIN_KEY_LEN)
let key = &vec![0x00u8; 187];
assert!(impl_build_cmd(b"mg", key, None, None, false).is_none());
}

#[test]
fn test_impl_build_cmd_large_key() {
let cmd = b"mg";
let key = &vec![b'X'; 250];
let no_result = impl_build_cmd(cmd, key, None, None, false);
assert!(no_result.is_none());
assert!(impl_build_cmd(cmd, key, None, None, false).is_none());
}

#[test]
Expand Down
61 changes: 26 additions & 35 deletions src/impl_parse_header.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use memchr::memmem;

use crate::{constants::*, ResponseFlags};

pub fn impl_parse_header(
Expand All @@ -9,43 +11,32 @@ pub fn impl_parse_header(
return None;
}
let end = end.min(data.len());
let mut n = start + 2;
while n < end - 1 {
if data[n] == b'\r' && data[n + 1] == b'\n' {
let endl_pos = n + 2;
match &data[start..start + 2] {
b"VA" => {
match ResponseFlags::from_value_header(&data[start..n]) {
Some((size, flags)) => {
return Some((endl_pos, Some(RESPONSE_VALUE), Some(size), Some(flags)));
}
None => {
return Some((endl_pos, None, None, None));
}
};
}
b"HD" | b"OK" => {
let flags = ResponseFlags::from_success_header(&data[start..n]);
return Some((endl_pos, Some(RESPONSE_SUCCESS), None, Some(flags)));
}
b"NS" => {
return Some((endl_pos, Some(RESPONSE_NOT_STORED), None, None));
}
b"EX" => {
return Some((endl_pos, Some(RESPONSE_CONFLICT), None, None));
}
b"EN" | b"NF" => {
return Some((endl_pos, Some(RESPONSE_MISS), None, None));
}
b"MN" => {
return Some((endl_pos, Some(RESPONSE_NOOP), None, None));
}
_ => {
return Some((endl_pos, None, None, None));
let search_start = start + 2;
if search_start >= end {
return None;
}
// Use memmem SIMD-accelerated search for \r\n
if let Some(pos) = memmem::find(&data[search_start..end], b"\r\n") {
let n = search_start + pos;
let endl_pos = n + 2;
match &data[start..start + 2] {
b"VA" => match ResponseFlags::from_value_header(&data[start..n]) {
Some((size, flags)) => {
Some((endl_pos, Some(RESPONSE_VALUE), Some(size), Some(flags)))
}
None => Some((endl_pos, None, None, None)),
},
b"HD" | b"OK" => {
let flags = ResponseFlags::from_success_header(&data[start..n]);
Some((endl_pos, Some(RESPONSE_SUCCESS), None, Some(flags)))
}
b"NS" => Some((endl_pos, Some(RESPONSE_NOT_STORED), None, None)),
b"EX" => Some((endl_pos, Some(RESPONSE_CONFLICT), None, None)),
b"EN" | b"NF" => Some((endl_pos, Some(RESPONSE_MISS), None, None)),
b"MN" => Some((endl_pos, Some(RESPONSE_NOOP), None, None)),
_ => Some((endl_pos, None, None, None)),
}
n += 1;
} else {
None
}
None
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ mod impl_build_cmd_tests;
mod impl_parse_header;
mod impl_parse_header_tests;
mod request_flags;
mod request_flags_tests;
mod response_flags;
mod response_flags_tests;
pub use constants::*;
use impl_build_cmd::impl_build_cmd;
use impl_parse_header::impl_parse_header;
Expand Down
40 changes: 10 additions & 30 deletions src/request_flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ pub struct RequestFlags {

impl RequestFlags {
pub fn push_bytes(&self, buf: &mut Vec<u8>) {
let mut itoa_buf = itoa::Buffer::new();
if self.no_reply {
buf.push(b' ');
buf.push(b'q');
Expand Down Expand Up @@ -97,42 +98,42 @@ impl RequestFlags {
if let Some(v) = self.cache_ttl {
buf.push(b' ');
buf.push(b'T');
buf.extend_from_slice(&v.to_string().as_bytes());
buf.extend_from_slice(itoa_buf.format(v).as_bytes());
}
if let Some(v) = self.recache_ttl {
buf.push(b' ');
buf.push(b'R');
buf.extend_from_slice(&v.to_string().as_bytes());
buf.extend_from_slice(itoa_buf.format(v).as_bytes());
}
if let Some(v) = self.vivify_on_miss_ttl {
buf.push(b' ');
buf.push(b'N');
buf.extend_from_slice(&v.to_string().as_bytes());
buf.extend_from_slice(itoa_buf.format(v).as_bytes());
}
if let Some(v) = self.client_flag {
buf.push(b' ');
buf.push(b'F');
buf.extend_from_slice(&v.to_string().as_bytes());
buf.extend_from_slice(itoa_buf.format(v).as_bytes());
}
if let Some(v) = self.ma_initial_value {
buf.push(b' ');
buf.push(b'J');
buf.extend_from_slice(&v.to_string().as_bytes());
buf.extend_from_slice(itoa_buf.format(v).as_bytes());
}
if let Some(v) = self.ma_delta_value {
buf.push(b' ');
buf.push(b'D');
buf.extend_from_slice(&v.to_string().as_bytes());
buf.extend_from_slice(itoa_buf.format(v).as_bytes());
}
if let Some(v) = self.cas_token {
buf.push(b' ');
buf.push(b'C');
buf.extend_from_slice(&v.to_string().as_bytes());
buf.extend_from_slice(itoa_buf.format(v).as_bytes());
}
if let Some(v) = &self.opaque {
buf.push(b' ');
buf.push(b'O');
buf.extend_from_slice(&v);
buf.extend_from_slice(v);
}
if let Some(v) = self.mode {
if v != SET_MODE_SET && v != MA_MODE_INC {
Expand Down Expand Up @@ -242,28 +243,7 @@ impl RequestFlags {
}

pub fn copy(&self) -> Self {
RequestFlags {
no_reply: self.no_reply,
return_client_flag: self.return_client_flag,
return_cas_token: self.return_cas_token,
return_value: self.return_value,
return_ttl: self.return_ttl,
return_size: self.return_size,
return_last_access: self.return_last_access,
return_fetched: self.return_fetched,
return_key: self.return_key,
no_update_lru: self.no_update_lru,
mark_stale: self.mark_stale,
cache_ttl: self.cache_ttl,
recache_ttl: self.recache_ttl,
vivify_on_miss_ttl: self.vivify_on_miss_ttl,
client_flag: self.client_flag,
ma_initial_value: self.ma_initial_value,
ma_delta_value: self.ma_delta_value,
cas_token: self.cas_token,
opaque: self.opaque.clone(),
mode: self.mode,
}
self.clone()
}

pub fn __str__(&self) -> String {
Expand Down
Loading
Loading