diff --git a/rust/cbindgen.toml b/rust/cbindgen.toml index ce8471d9b1..d53fd7f235 100644 --- a/rust/cbindgen.toml +++ b/rust/cbindgen.toml @@ -94,7 +94,8 @@ exclude = [ "SuricataContext", "SuricataFileContext", "TFTPState", - "TFTPTransaction" + "TFTPTransaction", + "free", ] # Types of items that we'll generate. If empty, then all types of item are emitted. diff --git a/rust/src/jsonbuilder.rs b/rust/src/jsonbuilder.rs new file mode 100644 index 0000000000..5e72ca2ba7 --- /dev/null +++ b/rust/src/jsonbuilder.rs @@ -0,0 +1,910 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#![allow(clippy::missing_safety_doc)] + +use crate::json; +use std::ffi::CStr; +use std::os::raw::c_char; +use std::str::Utf8Error; + +const INIT_SIZE: usize = 4096; + +#[derive(Debug, PartialEq)] +pub enum JsonError { + InvalidState, + Utf8Error(Utf8Error), +} + +impl std::error::Error for JsonError {} + +impl std::fmt::Display for JsonError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + JsonError::InvalidState => write!(f, "invalid state"), + JsonError::Utf8Error(ref e) => e.fmt(f), + } + } +} + +impl From for JsonError { + fn from(e: Utf8Error) -> Self { + JsonError::Utf8Error(e) + } +} + +#[derive(Clone, Debug)] +enum Type { + Object, + Array, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +enum State { + None, + ObjectFirst, + ObjectNth, + ArrayFirst, + ArrayNth, +} + +#[derive(Debug, Clone)] +pub struct JsonBuilder { + buf: String, + state: Vec, + init_type: Type, +} + +impl JsonBuilder { + /// Returns a new JsonBuilder in object state. + pub fn new_object() -> Self { + Self::new_object_with_capacity(INIT_SIZE) + } + + pub fn new_object_with_capacity(capacity: usize) -> Self { + let mut buf = String::with_capacity(capacity); + buf.push('{'); + Self { + buf: buf, + state: vec![State::None, State::ObjectFirst], + init_type: Type::Object, + } + } + + /// Returns a new JsonBuilder in array state. + pub fn new_array() -> Self { + Self::new_array_with_capacity(INIT_SIZE) + } + + pub fn new_array_with_capacity(capacity: usize) -> Self { + let mut buf = String::with_capacity(capacity); + buf.push('['); + Self { + buf: buf, + state: vec![State::None, State::ArrayFirst], + init_type: Type::Array, + } + } + + // Reset the builder to its initial state, without losing + // the current capacity. + pub fn reset(&mut self) { + self.buf.truncate(0); + match self.init_type { + Type::Array => { + self.buf.push('['); + self.state = vec![State::None, State::ArrayFirst]; + } + Type::Object => { + self.buf.push('{'); + self.state = vec![State::None, State::ObjectFirst]; + } + } + } + + // Closes the currently open datatype (object or array). + pub fn close(&mut self) -> Result<&mut Self, JsonError> { + match self.current_state() { + State::ObjectFirst | State::ObjectNth => { + self.buf.push('}'); + self.pop_state(); + Ok(self) + } + State::ArrayFirst | State::ArrayNth => { + self.buf.push(']'); + self.pop_state(); + Ok(self) + } + _ => Err(JsonError::InvalidState), + } + } + + // Return the current state of the JsonBuilder. + fn current_state(&self) -> State { + if self.state.is_empty() { + State::None + } else { + self.state[self.state.len() - 1] + } + } + + /// Move to a new state. + fn push_state(&mut self, state: State) { + self.state.push(state); + } + + /// Go back to the previous state. + fn pop_state(&mut self) { + self.state.pop(); + } + + /// Change the current state. + fn set_state(&mut self, state: State) { + let n = self.state.len() - 1; + self.state[n] = state; + } + + /// Open an object under the given key. + /// + /// For example: + /// Before: { + /// After: {"key": { + pub fn open_object(&mut self, key: &str) -> Result<&mut Self, JsonError> { + match self.current_state() { + State::ObjectFirst => { + self.buf.push('"'); + self.set_state(State::ObjectNth); + } + State::ObjectNth => { + self.buf.push_str(",\""); + } + _ => { + return Err(JsonError::InvalidState); + } + } + self.buf.push_str(key); + self.buf.push_str("\":{"); + self.push_state(State::ObjectFirst); + Ok(self) + } + + /// Start an object. + /// + /// Like open_object but does not create the object under a key. An + /// error will be returned if starting an object does not make + /// sense for the current state. + pub fn start_object(&mut self) -> Result<&mut Self, JsonError> { + match self.current_state() { + State::ArrayFirst => {} + State::ArrayNth => { + self.buf.push(','); + } + _ => { + return Err(JsonError::InvalidState); + } + } + self.buf.push('{'); + self.set_state(State::ArrayNth); + self.push_state(State::ObjectFirst); + Ok(self) + } + + /// Open an array under the given key. + /// + /// For example: + /// Before: { + /// After: {"key": [ + pub fn open_array(&mut self, key: &str) -> Result<&mut Self, JsonError> { + match self.current_state() { + State::ObjectFirst => {} + State::ObjectNth => { + self.buf.push(','); + } + _ => { + return Err(JsonError::InvalidState); + } + } + self.buf.push('"'); + self.buf.push_str(key); + self.buf.push_str("\":["); + self.set_state(State::ObjectNth); + self.push_state(State::ArrayFirst); + Ok(self) + } + + /// Add a string to an array. + pub fn append_string(&mut self, val: &str) -> Result<&mut Self, JsonError> { + match self.current_state() { + State::ArrayFirst => { + self.encode_string(val)?; + self.set_state(State::ArrayNth); + Ok(self) + } + State::ArrayNth => { + self.buf.push(','); + self.encode_string(val)?; + Ok(self) + } + _ => Err(JsonError::InvalidState), + } + } + + pub fn append_string_from_bytes(&mut self, val: &[u8]) -> Result<&mut Self, JsonError> { + match std::str::from_utf8(val) { + Ok(s) => self.append_string(s), + Err(_) => self.append_string(&string_from_bytes(val)), + } + } + + /// Add an unsigned integer to an array. + pub fn append_uint(&mut self, val: u64) -> Result<&mut Self, JsonError> { + match self.current_state() { + State::ArrayFirst => { + self.set_state(State::ArrayNth); + } + State::ArrayNth => { + self.buf.push(','); + } + _ => { + return Err(JsonError::InvalidState); + } + } + self.buf.push_str(&val.to_string()); + Ok(self) + } + + pub fn set_object(&mut self, key: &str, js: &JsonBuilder) -> Result<&mut Self, JsonError> { + match self.current_state() { + State::ObjectNth => { + self.buf.push(','); + } + State::ObjectFirst => { + self.set_state(State::ObjectNth); + } + _ => { + return Err(JsonError::InvalidState); + } + } + self.buf.push('"'); + self.buf.push_str(key); + self.buf.push_str("\":"); + self.buf.push_str(&js.buf); + Ok(self) + } + + /// Append an object onto this array. + /// + /// '[' -> '[{...}' + /// '[{...}' -> '[{...},{...}' + pub fn append_object(&mut self, js: &JsonBuilder) -> Result<&mut Self, JsonError> { + match self.current_state() { + State::ArrayFirst => { + self.set_state(State::ArrayNth); + } + State::ArrayNth => { + self.buf.push(','); + } + _ => { + return Err(JsonError::InvalidState); + } + } + self.buf.push_str(&js.buf); + Ok(self) + } + + pub fn set_jsont( + &mut self, + key: &str, + jsont: &mut json::JsonT, + ) -> Result<&mut Self, JsonError> { + match self.current_state() { + State::ObjectNth => self.buf.push(','), + State::ObjectFirst => self.set_state(State::ObjectNth), + _ => return Err(JsonError::InvalidState), + } + self.buf.push('"'); + self.buf.push_str(key); + self.buf.push_str("\":"); + self.append_jsont(jsont)?; + Ok(self) + } + + fn append_jsont(&mut self, jsont: &mut json::JsonT) -> Result<&mut Self, JsonError> { + unsafe { + let raw = json::json_dumps(jsont, 0); + let rendered = std::ffi::CStr::from_ptr(raw).to_str()?; + self.buf.push_str(rendered); + libc::free(raw as *mut std::os::raw::c_void); + Ok(self) + } + } + + /// Set a key and string value type on an object. + #[inline(always)] + pub fn set_string(&mut self, key: &str, val: &str) -> Result<&mut Self, JsonError> { + match self.current_state() { + State::ObjectNth => { + self.buf.push(','); + } + State::ObjectFirst => { + self.set_state(State::ObjectNth); + } + _ => { + return Err(JsonError::InvalidState); + } + } + self.buf.push('"'); + self.buf.push_str(key); + self.buf.push_str("\":"); + self.encode_string(val)?; + Ok(self) + } + + /// Set a key and a string value (from bytes) on an object. + pub fn set_string_from_bytes(&mut self, key: &str, val: &[u8]) -> Result<&mut Self, JsonError> { + match std::str::from_utf8(val) { + Ok(s) => self.set_string(key, s), + Err(_) => self.set_string(key, &string_from_bytes(val)), + } + } + + /// Set a key and an unsigned integer type on an object. + pub fn set_uint(&mut self, key: &str, val: u64) -> Result<&mut Self, JsonError> { + match self.current_state() { + State::ObjectNth => { + self.buf.push(','); + } + State::ObjectFirst => { + self.set_state(State::ObjectNth); + } + _ => { + return Err(JsonError::InvalidState); + } + } + self.buf.push('"'); + self.buf.push_str(key); + self.buf.push_str("\":"); + self.buf.push_str(&val.to_string()); + Ok(self) + } + + pub fn set_bool(&mut self, key: &str, val: bool) -> Result<&mut Self, JsonError> { + match self.current_state() { + State::ObjectNth => { + self.buf.push(','); + } + State::ObjectFirst => { + self.set_state(State::ObjectNth); + } + _ => { + return Err(JsonError::InvalidState); + } + } + self.buf.push('"'); + self.buf.push_str(key); + if val { + self.buf.push_str("\":true"); + } else { + self.buf.push_str("\":false"); + } + Ok(self) + } + + pub fn capacity(&self) -> usize { + self.buf.capacity() + } + + /// Encode a string into the buffer, escaping as needed. + /// + /// The string is encoded into an intermediate vector as its faster + /// than building onto the buffer. + #[inline(always)] + fn encode_string(&mut self, val: &str) -> Result<(), JsonError> { + let mut buf = vec![0; val.len() * 2 + 2]; + let mut offset = 0; + let bytes = val.as_bytes(); + buf[offset] = b'"'; + offset += 1; + for &x in bytes.iter() { + if offset + 7 >= buf.capacity() { + let mut extend = vec![0; buf.capacity()]; + buf.append(&mut extend); + } + let escape = ESCAPED[x as usize]; + if escape == 0 { + buf[offset] = x; + offset += 1; + } else if escape == b'u' { + buf[offset] = b'\\'; + offset += 1; + buf[offset] = b'u'; + offset += 1; + buf[offset] = b'0'; + offset += 1; + buf[offset] = b'0'; + offset += 1; + buf[offset] = HEX[(x >> 4 & 0xf) as usize]; + offset += 1; + buf[offset] = HEX[(x & 0xf) as usize]; + offset += 1; + } else { + buf[offset] = b'\\'; + offset += 1; + buf[offset] = escape; + offset += 1; + } + } + buf[offset] = b'"'; + offset += 1; + match std::str::from_utf8(&buf[0..offset]) { + Ok(s) => { + self.buf.push_str(s); + } + Err(err) => { + let error = format!( + "\"UTF8-ERROR: what=[escaped string] error={} output={:02x?} input={:02x?}\"", + err, + &buf[0..offset], + val.as_bytes(), + ); + self.buf.push_str(&error); + } + } + Ok(()) + } +} + +/// A Suricata specific function to create a string from bytes when UTF-8 decoding fails. +/// +/// For bytes over 0x0f, we encode as hex like "\xf2". +fn string_from_bytes(input: &[u8]) -> String { + let mut out = String::with_capacity(input.len()); + for b in input.iter() { + if *b < 128 { + out.push(*b as char); + } else { + out.push_str(&format!("\\x{:02x}", *b)); + } + } + return out; +} + +#[no_mangle] +pub extern "C" fn jb_new_object() -> *mut JsonBuilder { + let boxed = Box::new(JsonBuilder::new_object()); + Box::into_raw(boxed) +} + +#[no_mangle] +pub extern "C" fn jb_new_array() -> *mut JsonBuilder { + let boxed = Box::new(JsonBuilder::new_array()); + Box::into_raw(boxed) +} + +#[no_mangle] +pub extern "C" fn jb_clone(js: &mut JsonBuilder) -> *mut JsonBuilder { + let clone = Box::new(js.clone()); + Box::into_raw(clone) +} + +#[no_mangle] +pub unsafe extern "C" fn jb_free(js: &mut JsonBuilder) { + let _: Box = std::mem::transmute(js); +} + +#[no_mangle] +pub extern "C" fn jb_capacity(jb: &mut JsonBuilder) -> usize { + jb.capacity() +} + +#[no_mangle] +pub extern "C" fn jb_reset(jb: &mut JsonBuilder) { + jb.reset(); +} + +#[no_mangle] +pub unsafe extern "C" fn jb_open_object(js: &mut JsonBuilder, key: *const c_char) -> bool { + if let Ok(s) = CStr::from_ptr(key).to_str() { + js.open_object(s).is_ok() + } else { + false + } +} + +#[no_mangle] +pub unsafe extern "C" fn jb_start_object(js: &mut JsonBuilder) -> bool { + js.start_object().is_ok() +} + +#[no_mangle] +pub unsafe extern "C" fn jb_open_array(js: &mut JsonBuilder, key: *const c_char) -> bool { + if let Ok(s) = CStr::from_ptr(key).to_str() { + js.open_array(s).is_ok() + } else { + false + } +} + +#[no_mangle] +pub unsafe extern "C" fn jb_set_string( + js: &mut JsonBuilder, + key: *const c_char, + val: *const c_char, +) -> bool { + if val == std::ptr::null() { + return false; + } + if let Ok(key) = CStr::from_ptr(key).to_str() { + if let Ok(val) = CStr::from_ptr(val).to_str() { + return js.set_string(key, val).is_ok(); + } + } + return false; +} + +#[no_mangle] +pub unsafe extern "C" fn jb_set_jsont( + jb: &mut JsonBuilder, + key: *const c_char, + jsont: &mut json::JsonT, +) -> bool { + if let Ok(key) = CStr::from_ptr(key).to_str() { + return jb.set_jsont(key, jsont).is_ok(); + } + return false; +} + +#[no_mangle] +pub unsafe extern "C" fn jb_append_object(jb: &mut JsonBuilder, obj: &JsonBuilder) -> bool { + jb.append_object(obj).is_ok() +} + +#[no_mangle] +pub unsafe extern "C" fn jb_set_object( + js: &mut JsonBuilder, + key: *const c_char, + val: &mut JsonBuilder, +) -> bool { + if let Ok(key) = CStr::from_ptr(key).to_str() { + return js.set_object(key, val).is_ok(); + } + return false; +} + +#[no_mangle] +pub unsafe extern "C" fn jb_append_string(js: &mut JsonBuilder, val: *const c_char) -> bool { + if val == std::ptr::null() { + return false; + } + if let Ok(val) = CStr::from_ptr(val).to_str() { + return js.append_string(val).is_ok(); + } + return false; +} + +#[no_mangle] +pub unsafe extern "C" fn jb_append_uint(js: &mut JsonBuilder, val: u64) -> bool { + return js.append_uint(val).is_ok(); +} + +#[no_mangle] +pub unsafe extern "C" fn jb_set_uint(js: &mut JsonBuilder, key: *const c_char, val: u64) -> bool { + if let Ok(key) = CStr::from_ptr(key).to_str() { + return js.set_uint(key, val).is_ok(); + } + return false; +} + +#[no_mangle] +pub unsafe extern "C" fn jb_set_bool(js: &mut JsonBuilder, key: *const c_char, val: bool) -> bool { + if let Ok(key) = CStr::from_ptr(key).to_str() { + return js.set_bool(key, val).is_ok(); + } + return false; +} + +#[no_mangle] +pub unsafe extern "C" fn jb_close(js: &mut JsonBuilder) -> bool { + js.close().is_ok() +} + +#[no_mangle] +pub unsafe extern "C" fn jb_len(js: &JsonBuilder) -> usize { + js.buf.len() +} + +#[no_mangle] +pub unsafe extern "C" fn jb_ptr(js: &mut JsonBuilder) -> *const u8 { + js.buf.as_ptr() +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_set_bool() { + let mut jb = JsonBuilder::new_object(); + jb.set_bool("first", true).unwrap(); + assert_eq!(jb.buf, r#"{"first":true"#); + jb.set_bool("second", false).unwrap(); + assert_eq!(jb.buf, r#"{"first":true,"second":false"#); + + let mut jb = JsonBuilder::new_object(); + jb.set_bool("first", false).unwrap(); + assert_eq!(jb.buf, r#"{"first":false"#); + jb.set_bool("second", true).unwrap(); + assert_eq!(jb.buf, r#"{"first":false,"second":true"#); + } + + #[test] + fn test_object_in_object() -> Result<(), JsonError> { + let mut js = JsonBuilder::new_object(); + + js.open_object("object")?; + assert_eq!(js.current_state(), State::ObjectFirst); + assert_eq!(js.buf, r#"{"object":{"#); + + js.set_string("one", "one")?; + assert_eq!(js.current_state(), State::ObjectNth); + assert_eq!(js.buf, r#"{"object":{"one":"one""#); + + js.close()?; + assert_eq!(js.current_state(), State::ObjectNth); + assert_eq!(js.buf, r#"{"object":{"one":"one"}"#); + + js.close()?; + assert_eq!(js.current_state(), State::None); + assert_eq!(js.buf, r#"{"object":{"one":"one"}}"#); + + Ok(()) + } + + #[test] + fn test_empty_array_in_object() -> Result<(), JsonError> { + let mut js = JsonBuilder::new_object(); + + js.open_array("array")?; + assert_eq!(js.current_state(), State::ArrayFirst); + + js.close()?; + assert_eq!(js.current_state(), State::ObjectNth); + assert_eq!(js.buf, r#"{"array":[]"#); + + js.close()?; + assert_eq!(js.buf, r#"{"array":[]}"#); + + Ok(()) + } + + #[test] + fn test_array_in_object() -> Result<(), JsonError> { + let mut js = JsonBuilder::new_object(); + + // Attempt to add an item, should fail. + assert_eq!( + js.append_string("will fail").err().unwrap(), + JsonError::InvalidState + ); + + js.open_array("array")?; + assert_eq!(js.current_state(), State::ArrayFirst); + + js.append_string("one")?; + assert_eq!(js.current_state(), State::ArrayNth); + assert_eq!(js.buf, r#"{"array":["one""#); + + js.append_string("two")?; + assert_eq!(js.current_state(), State::ArrayNth); + assert_eq!(js.buf, r#"{"array":["one","two""#); + + js.append_uint(3)?; + assert_eq!(js.current_state(), State::ArrayNth); + assert_eq!(js.buf, r#"{"array":["one","two",3"#); + + js.close()?; + assert_eq!(js.current_state(), State::ObjectNth); + assert_eq!(js.buf, r#"{"array":["one","two",3]"#); + + js.close()?; + assert_eq!(js.current_state(), State::None); + assert_eq!(js.buf, r#"{"array":["one","two",3]}"#); + + Ok(()) + } + + #[test] + fn basic_test() -> Result<(), JsonError> { + let mut js = JsonBuilder::new_object(); + assert_eq!(js.current_state(), State::ObjectFirst); + assert_eq!(js.buf, "{"); + + js.set_string("one", "one")?; + assert_eq!(js.current_state(), State::ObjectNth); + assert_eq!(js.buf, r#"{"one":"one""#); + + js.set_string("two", "two")?; + assert_eq!(js.current_state(), State::ObjectNth); + assert_eq!(js.buf, r#"{"one":"one","two":"two""#); + + js.close()?; + assert_eq!(js.current_state(), State::None); + assert_eq!(js.buf, r#"{"one":"one","two":"two"}"#); + + Ok(()) + } + + #[test] + fn test_combine() -> Result<(), JsonError> { + let mut main = JsonBuilder::new_object(); + let mut obj = JsonBuilder::new_object(); + obj.close()?; + + let mut array = JsonBuilder::new_array(); + array.append_string("one")?; + array.append_uint(2)?; + array.close()?; + main.set_object("object", &obj)?; + main.set_object("array", &array)?; + main.close()?; + + assert_eq!(main.buf, r#"{"object":{},"array":["one",2]}"#); + + Ok(()) + } + + #[test] + fn test_objects_in_array() -> Result<(), JsonError> { + let mut js = JsonBuilder::new_array(); + assert_eq!(js.buf, r#"["#); + + js.start_object()?; + assert_eq!(js.buf, r#"[{"#); + + js.set_string("uid", "0")?; + assert_eq!(js.buf, r#"[{"uid":"0""#); + + js.close()?; + assert_eq!(js.buf, r#"[{"uid":"0"}"#); + + js.start_object()?; + assert_eq!(js.buf, r#"[{"uid":"0"},{"#); + + js.set_string("username", "root")?; + assert_eq!(js.buf, r#"[{"uid":"0"},{"username":"root""#); + + js.close()?; + assert_eq!(js.buf, r#"[{"uid":"0"},{"username":"root"}"#); + + js.close()?; + assert_eq!(js.buf, r#"[{"uid":"0"},{"username":"root"}]"#); + + Ok(()) + } + + #[test] + fn test_grow() -> Result<(), JsonError> { + let mut jb = JsonBuilder::new_object_with_capacity(1); + assert_eq!(jb.capacity(), 1); + jb.set_string("foo", "bar")?; + assert!(jb.capacity() > 1); + Ok(()) + } + + #[test] + fn test_reset() -> Result<(), JsonError> { + let mut jb = JsonBuilder::new_object(); + assert_eq!(jb.buf, "{"); + jb.set_string("foo", "bar")?; + let cap = jb.capacity(); + jb.reset(); + assert_eq!(jb.buf, "{"); + assert_eq!(jb.capacity(), cap); + Ok(()) + } + + #[test] + fn test_append_string_from_bytes() -> Result<(), JsonError> { + let mut jb = JsonBuilder::new_array(); + let s = &[0x41, 0x41, 0x41, 0x00]; + jb.append_string_from_bytes(s)?; + assert_eq!(jb.buf, r#"["AAA\u0000""#); + + let s = &[0x00, 0x01, 0x02, 0x03]; + let mut jb = JsonBuilder::new_array(); + jb.append_string_from_bytes(s)?; + assert_eq!(jb.buf, r#"["\u0000\u0001\u0002\u0003""#); + + Ok(()) + } + + #[test] + fn test_set_string_from_bytes() { + let mut jb = JsonBuilder::new_object(); + jb.set_string_from_bytes("first", &[]).unwrap(); + assert_eq!(jb.buf, r#"{"first":"""#); + jb.set_string_from_bytes("second", &[]).unwrap(); + assert_eq!(jb.buf, r#"{"first":"","second":"""#); + } + + #[test] + fn test_append_string_from_bytes_grow() -> Result<(), JsonError> { + let s = &[0x00, 0x01, 0x02, 0x03]; + let mut jb = JsonBuilder::new_array(); + jb.append_string_from_bytes(s)?; + + for i in 1..1000 { + let mut s = Vec::new(); + for _ in 0..i { + s.push(0x41); + } + let mut jb = JsonBuilder::new_array(); + jb.append_string_from_bytes(&s)?; + } + + Ok(()) + } + + #[test] + fn test_invalid_utf8() { + let mut jb = JsonBuilder::new_object(); + jb.set_string_from_bytes("invalid", &[0xf0, 0xf1, 0xf2]) + .unwrap(); + assert_eq!(jb.buf, r#"{"invalid":"\\xf0\\xf1\\xf2""#); + + let mut jb = JsonBuilder::new_array(); + jb.append_string_from_bytes(&[0xf0, 0xf1, 0xf2]).unwrap(); + assert_eq!(jb.buf, r#"["\\xf0\\xf1\\xf2""#); + } +} + +// Escape table as seen in serde-json (MIT/Apache license) + +const QU: u8 = b'"'; +const BS: u8 = b'\\'; +const BB: u8 = b'b'; +const TT: u8 = b't'; +const NN: u8 = b'n'; +const FF: u8 = b'f'; +const RR: u8 = b'r'; +const UU: u8 = b'u'; +const __: u8 = 0; + +// Look up table for characters that need escaping in a product string +static ESCAPED: [u8; 256] = [ + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, // 0 + UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, // 1 + __, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, // 2 + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 3 + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 4 + __, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, // 5 + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 6 + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 7 + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 8 + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 9 + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // A + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // B + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // C + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // D + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // E + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // F +]; + +static HEX: [u8; 16] = [ + b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'a', b'b', b'c', b'd', b'e', b'f', +]; diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 68ee041a00..0c51dcd85c 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -44,6 +44,7 @@ pub mod core; pub mod common; pub mod conf; pub mod json; +pub mod jsonbuilder; #[macro_use] pub mod applayer; pub mod filecontainer;