]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
jsonbuilder: new module for generating json
authorJason Ish <jason.ish@oisf.net>
Tue, 7 Jan 2020 19:08:29 +0000 (13:08 -0600)
committerVictor Julien <victor@inliniac.net>
Wed, 3 Jun 2020 11:36:55 +0000 (13:36 +0200)
JsonBuilder is a Rust module for creating JSON output. Unlike
Jansson, the final JSON string is built up as items are added,
instead of building up an object tree and rendering it when
done.

The idea is to create a more efficient JSON serializer instead
of a flexible one.

rust/cbindgen.toml
rust/src/jsonbuilder.rs [new file with mode: 0644]
rust/src/lib.rs

index ce8471d9b167f9cd41b1a6da6daeb5b0a0080b8b..d53fd7f235d471098ab51039b84e4611bbddc5f2 100644 (file)
@@ -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 (file)
index 0000000..5e72ca2
--- /dev/null
@@ -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<Utf8Error> 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<State>,
+    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<JsonBuilder> = 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',
+];
index 68ee041a00bdad23d57229decba51f98739146df..0c51dcd85cc4d31ae5f7dc12bb613c37e611ad74 100644 (file)
@@ -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;