--- /dev/null
+/* 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',
+];