• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

jasonish / suricata / 22802647571

07 Mar 2026 04:23PM UTC coverage: 75.827% (-3.4%) from 79.233%
22802647571

push

github

jasonish
github-ci: add schema ordering check for yaml schema

253365 of 334137 relevant lines covered (75.83%)

3384729.69 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.96
/rust/src/jsonbuilder.rs
1
/* Copyright (C) 2020-2024 Open Information Security Foundation
2
 *
3
 * You can copy, redistribute or modify this Program under the terms of
4
 * the GNU General Public License version 2 as published by the Free
5
 * Software Foundation.
6
 *
7
 * This program is distributed in the hope that it will be useful,
8
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 * GNU General Public License for more details.
11
 *
12
 * You should have received a copy of the GNU General Public License
13
 * version 2 along with this program; if not, write to the Free Software
14
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15
 * 02110-1301, USA.
16
 */
17

18
//! Module for building JSON documents.
19

20
#![allow(clippy::missing_safety_doc)]
21

22
use base64::{engine::general_purpose::STANDARD, Engine};
23
use num_traits::Unsigned;
24
use std::cmp::max;
25
use std::collections::TryReserveError;
26
use std::ffi::CStr;
27
use std::os::raw::c_char;
28
use std::str::Utf8Error;
29

30
const INIT_SIZE: usize = 4096;
31

32
#[derive(Debug, PartialEq, Eq)]
33
pub enum JsonError {
34
    InvalidState,
35
    Utf8Error(Utf8Error),
36
    Memory,
37
}
38

39
impl std::error::Error for JsonError {}
40

41
impl std::fmt::Display for JsonError {
42
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
×
43
        match self {
×
44
            JsonError::InvalidState => write!(f, "invalid state"),
×
45
            JsonError::Utf8Error(ref e) => e.fmt(f),
×
46
            JsonError::Memory => write!(f, "memory error"),
×
47
        }
48
    }
×
49
}
50

51
impl From<TryReserveError> for JsonError {
52
    fn from(_: TryReserveError) -> Self {
×
53
        JsonError::Memory
×
54
    }
×
55
}
56

57
impl From<Utf8Error> for JsonError {
58
    fn from(e: Utf8Error) -> Self {
×
59
        JsonError::Utf8Error(e)
×
60
    }
×
61
}
62

63
#[derive(Clone, Debug)]
64
enum Type {
65
    Object,
66
    Array,
67
}
68

69
#[derive(Debug, Clone, Copy, PartialEq)]
70
#[repr(C)]
71
enum State {
72
    None = 0,
73
    ObjectFirst,
74
    ObjectNth,
75
    ArrayFirst,
76
    ArrayNth,
77
}
78

79
impl State {
80
    fn from_u64(v: u64) -> Result<State, JsonError> {
2,479✔
81
        let s = match v {
2,479✔
82
            0 => State::None,
×
83
            1 => State::ObjectFirst,
×
84
            2 => State::ObjectNth,
2,479✔
85
            3 => State::ArrayFirst,
×
86
            4 => State::ArrayNth,
×
87
            _ => {
88
                return Err(JsonError::InvalidState);
×
89
            }
90
        };
91
        Ok(s)
2,479✔
92
    }
2,479✔
93
}
94

95
/// A "mark" or saved state for a JsonBuilder object.
96
///
97
/// The name is full, and the types are u64 as this object is used
98
/// directly in C as well.
99
#[repr(C)]
100
pub struct JsonBuilderMark {
101
    position: u64,
102
    state_index: u64,
103
    state: u64,
104
}
105

106
#[derive(Debug, Clone)]
107
pub struct JsonBuilder {
108
    buf: String,
109
    state: Vec<State>,
110
    init_type: Type,
111
}
112

113
impl JsonBuilder {
114
    /// Returns a new JsonBuilder in object state.
115
    pub fn try_new_object() -> Result<Self, JsonError> {
1,197,218✔
116
        Self::try_new_object_with_capacity(INIT_SIZE)
1,197,218✔
117
    }
1,197,218✔
118

119
    pub fn try_new_object_with_capacity(capacity: usize) -> Result<Self, JsonError> {
1,197,219✔
120
        let mut buf = String::new();
1,197,219✔
121
        buf.try_reserve(capacity)?;
1,197,219✔
122
        buf.push('{');
1,197,219✔
123
        let mut state = Vec::new();
1,197,219✔
124
        state.try_reserve(32)?;
1,197,219✔
125
        state.extend_from_slice(&[State::None, State::ObjectFirst]);
1,197,219✔
126
        Ok(Self {
1,197,219✔
127
            buf,
1,197,219✔
128
            state,
1,197,219✔
129
            init_type: Type::Object,
1,197,219✔
130
        })
1,197,219✔
131
    }
1,197,219✔
132

133
    /// Returns a new JsonBuilder in array state.
134
    pub fn try_new_array() -> Result<Self, JsonError> {
12,036✔
135
        Self::try_new_array_with_capacity(INIT_SIZE)
12,036✔
136
    }
12,036✔
137

138
    pub fn try_new_array_with_capacity(capacity: usize) -> Result<Self, JsonError> {
12,036✔
139
        let mut buf = String::new();
12,036✔
140
        buf.try_reserve(capacity)?;
12,036✔
141
        buf.push('[');
12,036✔
142
        let mut state = Vec::new();
12,036✔
143
        state.try_reserve(32)?;
12,036✔
144
        state.extend_from_slice(&[State::None, State::ArrayFirst]);
12,036✔
145
        Ok(Self {
12,036✔
146
            buf,
12,036✔
147
            state,
12,036✔
148
            init_type: Type::Array,
12,036✔
149
        })
12,036✔
150
    }
12,036✔
151

152
    /// A wrapper around String::push that pre-allocates data return
153
    /// an error if unable to.
154
    pub fn push(&mut self, ch: char) -> Result<&mut Self, JsonError> {
60,680,546✔
155
        if self.buf.capacity() == self.buf.len() {
60,680,546✔
156
            self.buf.try_reserve(INIT_SIZE)?;
29✔
157
        }
60,680,517✔
158
        self.buf.push(ch);
60,680,546✔
159
        Ok(self)
60,680,546✔
160
    }
60,680,546✔
161

162
    /// A wrapper around String::push_str that pre-allocates data
163
    /// return an error if unable to.
164
    pub fn push_str(&mut self, s: &str) -> Result<&mut Self, JsonError> {
96,183,634✔
165
        if self.buf.capacity() < self.buf.len() + s.len() {
96,183,634✔
166
            self.buf.try_reserve(max(INIT_SIZE, s.len()))?;
136✔
167
        }
96,183,498✔
168
        self.buf.push_str(s);
96,183,634✔
169
        Ok(self)
96,183,634✔
170
    }
96,183,634✔
171

172
    // Reset the builder to its initial state, without losing
173
    // the current capacity.
174
    pub fn reset(&mut self) {
12✔
175
        self.buf.truncate(0);
12✔
176
        self.state.clear();
12✔
177
        match self.init_type {
12✔
178
            Type::Array => {
×
179
                self.buf.push('[');
×
180
                self.state
×
181
                    .extend_from_slice(&[State::None, State::ArrayFirst]);
×
182
            }
×
183
            Type::Object => {
12✔
184
                self.buf.push('{');
12✔
185
                self.state
12✔
186
                    .extend_from_slice(&[State::None, State::ObjectFirst]);
12✔
187
            }
12✔
188
        }
189
    }
12✔
190

191
    // Closes the currently open datatype (object or array).
192
    pub fn close(&mut self) -> Result<&mut Self, JsonError> {
3,709,498✔
193
        match self.current_state() {
3,709,498✔
194
            State::ObjectFirst | State::ObjectNth => {
195
                self.push('}')?;
3,374,012✔
196
                self.pop_state();
3,374,012✔
197
                Ok(self)
3,374,012✔
198
            }
199
            State::ArrayFirst | State::ArrayNth => {
200
                self.push(']')?;
335,486✔
201
                self.pop_state();
335,486✔
202
                Ok(self)
335,486✔
203
            }
204
            State::None => {
205
                debug_validate_fail!("invalid state");
206
                Err(JsonError::InvalidState)
×
207
            }
208
        }
209
    }
3,709,498✔
210

211
    // Return the current state of the JsonBuilder.
212
    fn current_state(&self) -> State {
36,257,452✔
213
        if self.state.is_empty() {
36,257,452✔
214
            State::None
×
215
        } else {
216
            self.state[self.state.len() - 1]
36,257,452✔
217
        }
218
    }
36,257,452✔
219

220
    /// Move to a new state.
221
    fn push_state(&mut self, state: State) -> Result<(), JsonError> {
2,504,630✔
222
        if self.state.len() == self.state.capacity() {
2,504,630✔
223
            self.state.try_reserve(32)?;
×
224
        }
2,504,630✔
225
        self.state.push(state);
2,504,630✔
226
        Ok(())
2,504,630✔
227
    }
2,504,630✔
228

229
    /// Go back to the previous state.
230
    fn pop_state(&mut self) {
3,709,498✔
231
        self.state.pop();
3,709,498✔
232
    }
3,709,498✔
233

234
    /// Change the current state.
235
    fn set_state(&mut self, state: State) {
4,004,534✔
236
        let n = self.state.len() - 1;
4,004,534✔
237
        self.state[n] = state;
4,004,534✔
238
    }
4,004,534✔
239

240
    pub fn get_mark(&self) -> JsonBuilderMark {
10,457✔
241
        JsonBuilderMark {
10,457✔
242
            position: self.buf.len() as u64,
10,457✔
243
            state: self.current_state() as u64,
10,457✔
244
            state_index: self.state.len() as u64,
10,457✔
245
        }
10,457✔
246
    }
10,457✔
247

248
    pub fn restore_mark(&mut self, mark: &JsonBuilderMark) -> Result<(), JsonError> {
2,479✔
249
        let state = State::from_u64(mark.state)?;
2,479✔
250
        if mark.position < (self.buf.len() as u64) && mark.state_index < (self.state.len() as u64) {
2,479✔
251
            self.buf.truncate(mark.position as usize);
2,395✔
252
            self.state.truncate(mark.state_index as usize);
2,395✔
253
            self.state[(mark.state_index as usize) - 1] = state;
2,395✔
254
        }
2,395✔
255
        Ok(())
2,479✔
256
    }
2,479✔
257

258
    /// Open an object under the given key.
259
    ///
260
    /// For example:
261
    ///     Before: {
262
    ///     After:  {"key": {
263
    pub fn open_object(&mut self, key: &str) -> Result<&mut Self, JsonError> {
2,132,697✔
264
        match self.current_state() {
2,132,697✔
265
            State::ObjectFirst => {
266
                self.push('"')?;
2,968✔
267
                self.set_state(State::ObjectNth);
2,968✔
268
            }
269
            State::ObjectNth => {
270
                self.push_str(",\"")?;
2,129,729✔
271
            }
272
            _ => {
273
                debug_validate_fail!("invalid state");
274
                return Err(JsonError::InvalidState);
×
275
            }
276
        }
277
        self.push_str(key)?;
2,132,697✔
278
        self.push_str("\":{")?;
2,132,697✔
279
        self.push_state(State::ObjectFirst)?;
2,132,697✔
280
        Ok(self)
2,132,697✔
281
    }
2,132,697✔
282

283
    /// Start an object.
284
    ///
285
    /// Like open_object but does not create the object under a key. An
286
    /// error will be returned if starting an object does not make
287
    /// sense for the current state.
288
    pub fn start_object(&mut self) -> Result<&mut Self, JsonError> {
46,546✔
289
        match self.current_state() {
46,546✔
290
            State::ArrayFirst => {}
26,649✔
291
            State::ArrayNth => {
292
                self.push(',')?;
19,897✔
293
            }
294
            _ => {
295
                debug_validate_fail!("invalid state");
296
                return Err(JsonError::InvalidState);
×
297
            }
298
        }
299
        self.push('{')?;
46,546✔
300
        self.set_state(State::ArrayNth);
46,546✔
301
        self.push_state(State::ObjectFirst)?;
46,546✔
302
        Ok(self)
46,546✔
303
    }
46,546✔
304

305
    /// Open an array under the given key.
306
    ///
307
    /// For example:
308
    ///     Before: {
309
    ///     After:  {"key": [
310
    pub fn open_array(&mut self, key: &str) -> Result<&mut Self, JsonError> {
325,387✔
311
        match self.current_state() {
325,387✔
312
            State::ObjectFirst => {}
37,015✔
313
            State::ObjectNth => {
314
                self.push(',')?;
288,372✔
315
            }
316
            _ => {
317
                debug_validate_fail!("invalid state");
318
                return Err(JsonError::InvalidState);
×
319
            }
320
        }
321
        self.push('"')?;
325,387✔
322
        self.push_str(key)?;
325,387✔
323
        self.push_str("\":[")?;
325,387✔
324
        self.set_state(State::ObjectNth);
325,387✔
325
        self.push_state(State::ArrayFirst)?;
325,387✔
326
        Ok(self)
325,387✔
327
    }
325,387✔
328

329
    /// Add a string to an array.
330
    pub fn append_string(&mut self, val: &str) -> Result<&mut Self, JsonError> {
325,529✔
331
        match self.current_state() {
325,529✔
332
            State::ArrayFirst => {
333
                self.encode_string(val)?;
279,878✔
334
                self.set_state(State::ArrayNth);
279,878✔
335
                Ok(self)
279,878✔
336
            }
337
            State::ArrayNth => {
338
                self.push(',')?;
45,650✔
339
                self.encode_string(val)?;
45,650✔
340
                Ok(self)
45,650✔
341
            }
342
            _ => {
343
                debug_validate_fail!("invalid state");
344
                Err(JsonError::InvalidState)
1✔
345
            }
346
        }
347
    }
325,529✔
348

349
    pub fn append_string_from_bytes(&mut self, val: &[u8]) -> Result<&mut Self, JsonError> {
35,346✔
350
        match std::str::from_utf8(val) {
35,346✔
351
            Ok(s) => self.append_string(s),
35,339✔
352
            Err(_) => self.append_string(&try_string_from_bytes(val)?),
7✔
353
        }
354
    }
35,346✔
355

356
    /// Add a string to an array.
357
    pub fn append_base64(&mut self, val: &[u8]) -> Result<&mut Self, JsonError> {
3✔
358
        match self.current_state() {
3✔
359
            State::ArrayFirst => {
360
                self.push('"')?;
2✔
361
                self.encode_base64(val)?;
2✔
362
                self.push('"')?;
2✔
363
                self.set_state(State::ArrayNth);
2✔
364
                Ok(self)
2✔
365
            }
366
            State::ArrayNth => {
367
                self.push(',')?;
1✔
368
                self.push('"')?;
1✔
369
                self.encode_base64(val)?;
1✔
370
                self.push('"')?;
1✔
371
                Ok(self)
1✔
372
            }
373
            _ => {
374
                debug_validate_fail!("invalid state");
375
                Err(JsonError::InvalidState)
×
376
            }
377
        }
378
    }
3✔
379

380
    /// Add a byte array to a JSON array encoded as hex.
381
    pub fn append_hex(&mut self, val: &[u8]) -> Result<&mut Self, JsonError> {
×
382
        match self.current_state() {
×
383
            State::ArrayFirst => {
384
                self.push('"')?;
×
385
                for i in 0..val.len() {
×
386
                    self.push(HEX[(val[i] >> 4) as usize] as char)?;
×
387
                    self.push(HEX[(val[i] & 0xf) as usize] as char)?;
×
388
                }
389
                self.push('"')?;
×
390
                self.set_state(State::ArrayNth);
×
391
                Ok(self)
×
392
            }
393
            State::ArrayNth => {
394
                self.push(',')?;
×
395
                self.push('"')?;
×
396
                for i in 0..val.len() {
×
397
                    self.push(HEX[(val[i] >> 4) as usize] as char)?;
×
398
                    self.push(HEX[(val[i] & 0xf) as usize] as char)?;
×
399
                }
400
                self.push('"')?;
×
401
                Ok(self)
×
402
            }
403
            _ => {
404
                debug_validate_fail!("invalid state");
405
                Err(JsonError::InvalidState)
×
406
            }
407
        }
408
    }
×
409

410
    /// Add an unsigned integer to an array.
411
    pub fn append_uint(&mut self, val: u64) -> Result<&mut Self, JsonError> {
16,082✔
412
        match self.current_state() {
16,082✔
413
            State::ArrayFirst => {
15,931✔
414
                self.set_state(State::ArrayNth);
15,931✔
415
            }
15,931✔
416
            State::ArrayNth => {
417
                self.push(',')?;
151✔
418
            }
419
            _ => {
420
                debug_validate_fail!("invalid state");
421
                return Err(JsonError::InvalidState);
×
422
            }
423
        }
424
        self.push_str(&val.to_string())
16,082✔
425
    }
16,082✔
426

427
    pub fn append_float(&mut self, val: f64) -> Result<&mut Self, JsonError> {
5✔
428
        match self.current_state() {
5✔
429
            State::ArrayFirst => {
4✔
430
                self.set_state(State::ArrayNth);
4✔
431
            }
4✔
432
            State::ArrayNth => {
433
                self.push(',')?;
1✔
434
            }
435
            _ => {
436
                debug_validate_fail!("invalid state");
437
                return Err(JsonError::InvalidState);
×
438
            }
439
        }
440
        self.push_float(val)?;
5✔
441
        Ok(self)
5✔
442
    }
5✔
443

444
    pub fn set_object(&mut self, key: &str, js: &JsonBuilder) -> Result<&mut Self, JsonError> {
29,109✔
445
        match self.current_state() {
29,109✔
446
            State::ObjectNth => {
447
                self.push(',')?;
22,333✔
448
            }
449
            State::ObjectFirst => {
6,776✔
450
                self.set_state(State::ObjectNth);
6,776✔
451
            }
6,776✔
452
            _ => {
453
                debug_validate_fail!("invalid state");
454
                return Err(JsonError::InvalidState);
×
455
            }
456
        }
457
        self.push('"')?;
29,109✔
458
        self.push_str(key)?;
29,109✔
459
        self.push_str("\":")?;
29,109✔
460
        self.push_str(&js.buf)?;
29,109✔
461
        Ok(self)
29,109✔
462
    }
29,109✔
463

464
    /// Append an object onto this array.
465
    ///
466
    /// '[' -> '[{...}'
467
    /// '[{...}' -> '[{...},{...}'
468
    pub fn append_object(&mut self, js: &JsonBuilder) -> Result<&mut Self, JsonError> {
32,100✔
469
        match self.current_state() {
32,100✔
470
            State::ArrayFirst => {
11,925✔
471
                self.set_state(State::ArrayNth);
11,925✔
472
            }
11,925✔
473
            State::ArrayNth => {
474
                self.push(',')?;
20,175✔
475
            }
476
            _ => {
477
                debug_validate_fail!("invalid state");
478
                return Err(JsonError::InvalidState);
×
479
            }
480
        }
481
        self.push_str(&js.buf)?;
32,100✔
482
        Ok(self)
32,100✔
483
    }
32,100✔
484

485
    /// Set a key and string value type on an object.
486
    #[inline(always)]
487
    pub fn set_string(&mut self, key: &str, val: &str) -> Result<&mut Self, JsonError> {
14,686,846✔
488
        match self.current_state() {
14,686,846✔
489
            State::ObjectNth => {
490
                self.push(',')?;
12,399,979✔
491
            }
492
            State::ObjectFirst => {
2,286,867✔
493
                self.set_state(State::ObjectNth);
2,286,867✔
494
            }
2,286,867✔
495
            _ => {
496
                debug_validate_fail!("invalid state");
497
                return Err(JsonError::InvalidState);
×
498
            }
499
        }
500
        self.push('"')?;
14,686,846✔
501
        self.push_str(key)?;
14,686,846✔
502
        self.push_str("\":")?;
14,686,846✔
503
        self.encode_string(val)?;
14,686,846✔
504
        Ok(self)
14,686,846✔
505
    }
14,686,846✔
506

507
    /// Set a key and a string value on an object, with a limited size
508
    pub fn set_string_limited(
400✔
509
        &mut self, key: &str, val: &str, limit: usize,
400✔
510
    ) -> Result<&mut Self, JsonError> {
400✔
511
        if val.len() > limit {
400✔
512
            // Gracefully handle splitting UTF-8 strings at arbitrary locations.
513
            // Strings in Rust are UTF-8; and a UTF-8 code point is max 4 bytes.
514
            // Hence we will find a suitable boundary within 4 bytes of any byte
515
            // position, in any direction with sufficiently long strings left.
516
            // This is an approach similar to Rust's (currently nightly unstable
517
            // only) "floor_char_boundary" str method:
518
            // https://doc.rust-lang.org/std/primitive.str.html#method.floor_char_boundary
519
            for i in 0..=std::cmp::min(limit, 4) {
21✔
520
                // We first try the requested boundary. In the expected
521
                // ("happy") case the limit is at a code point boundary so the
522
                // slice will succeed immediately. If not, we successively try
523
                // earlier positions in the string until we find a suitable
524
                // position.
525
                if let Some(valtrunc) = val.get(0..limit - i) {
21✔
526
                    let additional_bytes = val.len() - limit;
11✔
527
                    let outstr = format!(
11✔
528
                        "{valtrunc}[truncated {additional_bytes} additional byte{}]",
11✔
529
                        if additional_bytes != 1 { "s" } else { "" }
11✔
530
                    );
531
                    self.set_string(key, &outstr)?;
11✔
532
                    break;
11✔
533
                }
10✔
534
            }
535
        } else {
536
            self.set_string(key, val)?;
389✔
537
        }
538
        Ok(self)
400✔
539
    }
400✔
540

541
    pub fn set_formatted(&mut self, formatted: &str) -> Result<&mut Self, JsonError> {
69,677✔
542
        match self.current_state() {
69,677✔
543
            State::ObjectNth => {
544
                self.push(',')?;
68,473✔
545
            }
546
            State::ObjectFirst => {
1,204✔
547
                self.set_state(State::ObjectNth);
1,204✔
548
            }
1,204✔
549
            _ => {
550
                debug_validate_fail!("invalid state");
551
                return Err(JsonError::InvalidState);
×
552
            }
553
        }
554
        self.push_str(formatted)?;
69,677✔
555
        Ok(self)
69,677✔
556
    }
69,677✔
557

558
    /// Set a key and a string value (from bytes) on an object.
559
    pub fn set_string_from_bytes(&mut self, key: &str, val: &[u8]) -> Result<&mut Self, JsonError> {
138,769✔
560
        match std::str::from_utf8(val) {
138,769✔
561
            Ok(s) => self.set_string(key, s),
138,698✔
562
            Err(_) => self.set_string(key, &try_string_from_bytes(val)?),
71✔
563
        }
564
    }
138,769✔
565

566
    /// Set a key with a string value taking only ascii-printable bytes.
567
    /// Non-printable characters are replaced by a dot `.`, except
568
    /// CR and LF which are escaped the regular json way \r and \n
569
    pub fn set_print_ascii(&mut self, key: &str, val: &[u8]) -> Result<&mut Self, JsonError> {
1,726✔
570
        match self.current_state() {
1,726✔
571
            State::ObjectNth => {
572
                self.push(',')?;
1,682✔
573
            }
574
            State::ObjectFirst => {
44✔
575
                self.set_state(State::ObjectNth);
44✔
576
            }
44✔
577
            _ => {
578
                debug_validate_fail!("invalid state");
579
                return Err(JsonError::InvalidState);
×
580
            }
581
        }
582
        self.push('"')?;
1,726✔
583
        self.push_str(key)?;
1,726✔
584
        self.push_str("\":\"")?;
1,726✔
585
        for &x in val.iter() {
245,590✔
586
            match x {
245,590✔
587
                b'\r' => {
588
                    self.push_str("\\r")?;
1,460✔
589
                }
590
                b'\n'=> {
591
                    self.push_str("\\n")?;
2,217✔
592
                }
593
                b'"'=> {
594
                    self.push_str("\\\"")?;
428✔
595
                }
596
                b'\\'=> {
597
                    self.push_str("\\\\")?;
504✔
598
                }
599
                _ => {
600
                    if !x.is_ascii() || x.is_ascii_control()  {
240,981✔
601
                        self.push('.')?;
126,316✔
602
                    } else {
603
                        self.push(x as char)?;
114,665✔
604
                    }
605
                }
606
            }
607
        }
608
        self.push('"')?;
1,726✔
609
        Ok(self)
1,726✔
610
    }
1,726✔
611

612
    /// Set a key and a string value (from bytes) on an object, with a limited size
613
    pub fn set_string_from_bytes_limited(
395✔
614
        &mut self, key: &str, val: &[u8], limit: usize,
395✔
615
    ) -> Result<&mut Self, JsonError> {
395✔
616
        let mut valtrunc = Vec::new();
395✔
617
        let val = if val.len() > limit {
395✔
618
            let additional_bytes = val.len() - limit;
22✔
619
            valtrunc.extend_from_slice(&val[..limit]);
22✔
620
            valtrunc.extend_from_slice(
22✔
621
                format!(
22✔
622
                    "[truncated {additional_bytes} additional byte{}]",
22✔
623
                    if additional_bytes != 1 { "s" } else { "" }
22✔
624
                )
625
                .as_bytes(),
22✔
626
            );
22✔
627
            &valtrunc
22✔
628
        } else {
629
            val
373✔
630
        };
631
        match std::str::from_utf8(val) {
395✔
632
            Ok(s) => self.set_string(key, s),
381✔
633
            Err(_) => self.set_string(key, &try_string_from_bytes(val)?),
14✔
634
        }
635
    }
395✔
636

637
    /// Set a key and a string field as the base64 encoded string of the value.
638
    pub fn set_base64(&mut self, key: &str, val: &[u8]) -> Result<&mut Self, JsonError> {
2,328✔
639
        match self.current_state() {
2,328✔
640
            State::ObjectNth => {
641
                self.push(',')?;
2,328✔
642
            }
643
            State::ObjectFirst => {
×
644
                self.set_state(State::ObjectNth);
×
645
            }
×
646
            _ => {
647
                debug_validate_fail!("invalid state");
648
                return Err(JsonError::InvalidState);
×
649
            }
650
        }
651
        self.push('"')?;
2,328✔
652
        self.push_str(key)?;
2,328✔
653
        self.push_str("\":\"")?;
2,328✔
654
        self.encode_base64(val)?;
2,328✔
655
        self.push('"')?;
2,328✔
656

657
        Ok(self)
2,328✔
658
    }
2,328✔
659

660
    /// Set a key and a string field as the hex encoded string of the value.
661
    pub fn set_hex(&mut self, key: &str, val: &[u8]) -> Result<&mut Self, JsonError> {
631✔
662
        match self.current_state() {
631✔
663
            State::ObjectNth => {
664
                self.push(',')?;
571✔
665
            }
666
            State::ObjectFirst => {
60✔
667
                self.set_state(State::ObjectNth);
60✔
668
            }
60✔
669
            _ => {
670
                debug_validate_fail!("invalid state");
671
                return Err(JsonError::InvalidState);
×
672
            }
673
        }
674
        self.push('"')?;
631✔
675
        self.push_str(key)?;
631✔
676
        self.push_str("\":\"")?;
631✔
677
        for i in 0..val.len() {
24,770✔
678
            self.push(HEX[(val[i] >> 4) as usize] as char)?;
24,770✔
679
            self.push(HEX[(val[i] & 0xf) as usize] as char)?;
24,770✔
680
        }
681
        self.push('"')?;
631✔
682

683
        Ok(self)
631✔
684
    }
631✔
685

686
    /// Set a key and an unsigned integer type on an object.
687
    pub fn set_uint<T>(&mut self, key: &str, val: T) -> Result<&mut Self, JsonError>
14,781,864✔
688
    where
14,781,864✔
689
        T: Unsigned + Into<u64>,
14,781,864✔
690
    {
14,781,864✔
691
        let val: u64 = val.into();
14,781,864✔
692
        match self.current_state() {
14,781,864✔
693
            State::ObjectNth => {
694
                self.push(',')?;
13,769,848✔
695
            }
696
            State::ObjectFirst => {
1,012,016✔
697
                self.set_state(State::ObjectNth);
1,012,016✔
698
            }
1,012,016✔
699
            _ => {
700
                debug_validate_fail!("invalid state");
701
                return Err(JsonError::InvalidState);
×
702
            }
703
        }
704
        self.push('"')?;
14,781,864✔
705
        self.push_str(key)?;
14,781,864✔
706
        self.push_str("\":")?;
14,781,864✔
707
        self.push_str(&val.to_string())?;
14,781,864✔
708
        Ok(self)
14,781,864✔
709
    }
14,781,864✔
710

711
    /// Set a key and a signed integer type on an object.
712
    pub fn set_int(&mut self, key: &str, val: i64) -> Result<&mut Self, JsonError> {
12,982✔
713
        match self.current_state() {
12,982✔
714
            State::ObjectNth => {
715
                self.push(',')?;
108✔
716
            }
717
            State::ObjectFirst => {
12,874✔
718
                self.set_state(State::ObjectNth);
12,874✔
719
            }
12,874✔
720
            _ => {
721
                debug_validate_fail!("invalid state");
722
                return Err(JsonError::InvalidState);
×
723
            }
724
        }
725
        self.push('"')?;
12,982✔
726
        self.push_str(key)?;
12,982✔
727
        self.push_str("\":")?;
12,982✔
728
        self.push_str(&val.to_string())?;
12,982✔
729
        Ok(self)
12,982✔
730
    }
12,982✔
731

732
    pub fn set_float(&mut self, key: &str, val: f64) -> Result<&mut Self, JsonError> {
31✔
733
        match self.current_state() {
31✔
734
            State::ObjectNth => {
735
                self.push(',')?;
17✔
736
            }
737
            State::ObjectFirst => {
14✔
738
                self.set_state(State::ObjectNth);
14✔
739
            }
14✔
740
            _ => {
741
                debug_validate_fail!("invalid state");
742
                return Err(JsonError::InvalidState);
×
743
            }
744
        }
745
        self.push('"')?;
31✔
746
        self.push_str(key)?;
31✔
747
        self.push_str("\":")?;
31✔
748
        self.push_float(val)?;
31✔
749
        Ok(self)
31✔
750
    }
31✔
751

752
    pub fn set_bool(&mut self, key: &str, val: bool) -> Result<&mut Self, JsonError> {
73,935✔
753
        match self.current_state() {
73,935✔
754
            State::ObjectNth => {
755
                self.push(',')?;
71,897✔
756
            }
757
            State::ObjectFirst => {
2,038✔
758
                self.set_state(State::ObjectNth);
2,038✔
759
            }
2,038✔
760
            _ => {
761
                debug_validate_fail!("invalid state");
762
                return Err(JsonError::InvalidState);
×
763
            }
764
        }
765
        self.push('"')?;
73,935✔
766
        self.push_str(key)?;
73,935✔
767
        if val {
73,935✔
768
            self.push_str("\":true")?;
31,197✔
769
        } else {
770
            self.push_str("\":false")?;
42,738✔
771
        }
772
        Ok(self)
73,935✔
773
    }
73,935✔
774

775
    pub fn capacity(&self) -> usize {
5✔
776
        self.buf.capacity()
5✔
777
    }
5✔
778

779
    fn push_float(&mut self, val: f64) -> Result<(), JsonError> {
36✔
780
        if val.is_nan() || val.is_infinite() {
36✔
781
            self.push_str("null")?;
6✔
782
        } else {
783
            self.push_str(&val.to_string())?;
30✔
784
        }
785
        Ok(())
36✔
786
    }
36✔
787

788
    /// Encode a string into the buffer, escaping as needed.
789
    ///
790
    /// The string is encoded into an intermediate vector as its faster
791
    /// than building onto the buffer.
792
    ///
793
    /// TODO: Revisit this, would be nice to build directly onto the
794
    ///    existing buffer.
795
    #[inline(always)]
796
    fn encode_string(&mut self, val: &str) -> Result<(), JsonError> {
15,012,374✔
797
        let mut buf = Vec::new();
15,012,374✔
798

15,012,374✔
799
        // Start by allocating a reasonable size buffer, it will be
15,012,374✔
800
        // grown if needed.
15,012,374✔
801
        buf.try_reserve(val.len() * 2 + 2)?;
15,012,374✔
802
        buf.resize(val.len() * 2 + 2, 0);
15,012,374✔
803

15,012,374✔
804
        let mut offset = 0;
15,012,374✔
805
        let bytes = val.as_bytes();
15,012,374✔
806
        buf[offset] = b'"';
15,012,374✔
807
        offset += 1;
15,012,374✔
808
        for &x in bytes.iter() {
245,362,080✔
809
            if offset + 7 >= buf.capacity() {
245,362,080✔
810
                // We could be smarter, but just double the buffer size.
811
                buf.try_reserve(buf.capacity())?;
2,473,103✔
812
                buf.resize(buf.capacity(), 0);
2,473,103✔
813
            }
242,888,977✔
814
            let escape = ESCAPED[x as usize];
245,362,080✔
815
            if escape == 0 {
245,362,080✔
816
                buf[offset] = x;
245,339,798✔
817
                offset += 1;
245,339,798✔
818
            } else if escape == b'u' {
245,339,798✔
819
                buf[offset] = b'\\';
6,993✔
820
                offset += 1;
6,993✔
821
                buf[offset] = b'u';
6,993✔
822
                offset += 1;
6,993✔
823
                buf[offset] = b'0';
6,993✔
824
                offset += 1;
6,993✔
825
                buf[offset] = b'0';
6,993✔
826
                offset += 1;
6,993✔
827
                buf[offset] = HEX[((x >> 4) & 0xf) as usize];
6,993✔
828
                offset += 1;
6,993✔
829
                buf[offset] = HEX[(x & 0xf) as usize];
6,993✔
830
                offset += 1;
6,993✔
831
            } else {
15,292✔
832
                buf[offset] = b'\\';
15,289✔
833
                offset += 1;
15,289✔
834
                buf[offset] = escape;
15,289✔
835
                offset += 1;
15,289✔
836
            }
15,289✔
837
        }
838
        buf[offset] = b'"';
15,012,374✔
839
        offset += 1;
15,012,374✔
840
        match std::str::from_utf8(&buf[0..offset]) {
15,012,374✔
841
            Ok(s) => {
15,012,374✔
842
                self.push_str(s)?;
15,012,374✔
843
            }
844
            Err(err) => {
×
845
                let error = format!(
×
846
                    "\"UTF8-ERROR: what=[escaped string] error={} output={:02x?} input={:02x?}\"",
×
847
                    err,
×
848
                    &buf[0..offset],
×
849
                    val.as_bytes(),
×
850
                );
×
851
                self.push_str(&error)?;
×
852
            }
853
        }
854
        Ok(())
15,012,374✔
855
    }
15,012,374✔
856

857
    fn encode_base64(&mut self, val: &[u8]) -> Result<&mut Self, JsonError> {
2,331✔
858
        let encoded_len = 4 * val.len().div_ceil(3);
2,331✔
859
        if self.buf.capacity() < self.buf.len() + encoded_len {
2,331✔
860
            self.buf.try_reserve(encoded_len)?;
32✔
861
        }
2,299✔
862
        STANDARD.encode_string(val, &mut self.buf);
2,331✔
863
        Ok(self)
2,331✔
864
    }
2,331✔
865
}
866

867
/// A Suricata specific function to create a string from bytes when UTF-8 decoding fails.
868
///
869
/// For bytes over 0x0f, we encode as hex like "\xf2".
870
fn try_string_from_bytes(input: &[u8]) -> Result<String, JsonError> {
92✔
871
    let mut out = String::new();
92✔
872

92✔
873
    // Allocate enough data to handle the worst case scenario of every
92✔
874
    // byte needing to be presented as a byte.
92✔
875
    out.try_reserve(input.len() * 4)?;
92✔
876

877
    for b in input.iter() {
16,690✔
878
        if *b < 128 {
16,690✔
879
            out.push(*b as char);
13,125✔
880
        } else {
13,131✔
881
            out.push_str(&format!("\\x{:02x}", *b));
3,565✔
882
        }
3,565✔
883
    }
884
    return Ok(out);
92✔
885
}
92✔
886

887
#[no_mangle]
888
pub extern "C" fn SCJbNewObject() -> *mut JsonBuilder {
1,165,064✔
889
    match JsonBuilder::try_new_object() {
1,165,064✔
890
        Ok(js) => {
1,165,064✔
891
            let boxed = Box::new(js);
1,165,064✔
892
            Box::into_raw(boxed)
1,165,064✔
893
        }
894
        Err(_) => std::ptr::null_mut(),
×
895
    }
896
}
1,165,064✔
897

898
#[no_mangle]
899
pub extern "C" fn SCJbNewArray() -> *mut JsonBuilder {
6,250✔
900
    match JsonBuilder::try_new_array() {
6,250✔
901
        Ok(js) => {
6,250✔
902
            let boxed = Box::new(js);
6,250✔
903
            Box::into_raw(boxed)
6,250✔
904
        }
905
        Err(_) => std::ptr::null_mut(),
×
906
    }
907
}
6,250✔
908

909
#[no_mangle]
910
pub extern "C" fn SCJbClone(js: &mut JsonBuilder) -> *mut JsonBuilder {
×
911
    let clone = Box::new(js.clone());
×
912
    Box::into_raw(clone)
×
913
}
×
914

915
#[no_mangle]
916
pub unsafe extern "C" fn SCJbFree(js: &mut JsonBuilder) {
1,171,314✔
917
    let _ = Box::from_raw(js);
1,171,314✔
918
}
1,171,314✔
919

920
#[no_mangle]
921
pub extern "C" fn SCJbCapacity(jb: &mut JsonBuilder) -> usize {
×
922
    jb.capacity()
×
923
}
×
924

925
#[no_mangle]
926
pub extern "C" fn SCJbReset(jb: &mut JsonBuilder) {
×
927
    jb.reset();
×
928
}
×
929

930
#[no_mangle]
931
pub unsafe extern "C" fn SCJbOpenObject(js: &mut JsonBuilder, key: *const c_char) -> bool {
2,072,748✔
932
    if let Ok(s) = CStr::from_ptr(key).to_str() {
2,072,748✔
933
        js.open_object(s).is_ok()
2,072,748✔
934
    } else {
935
        false
×
936
    }
937
}
2,072,748✔
938

939
#[no_mangle]
940
pub unsafe extern "C" fn SCJbStartObject(js: &mut JsonBuilder) -> bool {
4,882✔
941
    js.start_object().is_ok()
4,882✔
942
}
4,882✔
943

944
#[no_mangle]
945
pub unsafe extern "C" fn SCJbOpenArray(js: &mut JsonBuilder, key: *const c_char) -> bool {
287,977✔
946
    if let Ok(s) = CStr::from_ptr(key).to_str() {
287,977✔
947
        js.open_array(s).is_ok()
287,977✔
948
    } else {
949
        false
×
950
    }
951
}
287,977✔
952

953
#[no_mangle]
954
pub unsafe extern "C" fn SCJbSetString(
14,261,291✔
955
    js: &mut JsonBuilder, key: *const c_char, val: *const c_char,
14,261,291✔
956
) -> bool {
14,261,291✔
957
    if val.is_null() {
14,261,291✔
958
        return false;
8,322✔
959
    }
14,252,969✔
960
    if let Ok(key) = CStr::from_ptr(key).to_str() {
14,252,969✔
961
        if let Ok(val) = CStr::from_ptr(val).to_str() {
14,252,969✔
962
            return js.set_string(key, val).is_ok();
14,252,968✔
963
        }
1✔
964
    }
×
965
    return false;
1✔
966
}
14,261,291✔
967

968
#[no_mangle]
969
pub unsafe extern "C" fn SCJbSetStringFromBytes(
82,040✔
970
    js: &mut JsonBuilder, key: *const c_char, bytes: *const u8, len: u32,
82,040✔
971
) -> bool {
82,040✔
972
    if bytes.is_null() || len == 0 {
82,040✔
973
        return false;
351✔
974
    }
81,689✔
975
    if let Ok(key) = CStr::from_ptr(key).to_str() {
81,689✔
976
        let val = std::slice::from_raw_parts(bytes, len as usize);
81,689✔
977
        return js.set_string_from_bytes(key, val).is_ok();
81,689✔
978
    }
×
979
    return false;
×
980
}
82,040✔
981

982
#[no_mangle]
983
pub unsafe extern "C" fn SCJbSetPrintAsciiString(
1,731✔
984
    js: &mut JsonBuilder, key: *const c_char, bytes: *const u8, len: u32,
1,731✔
985
) -> bool {
1,731✔
986
    if bytes.is_null() || len == 0 {
1,731✔
987
        return false;
5✔
988
    }
1,726✔
989
    if let Ok(key) = CStr::from_ptr(key).to_str() {
1,726✔
990
        let val = std::slice::from_raw_parts(bytes, len as usize);
1,726✔
991
        return js.set_print_ascii(key, val).is_ok();
1,726✔
992
    }
×
993
    return false;
×
994
}
1,731✔
995

996
#[no_mangle]
997
pub unsafe extern "C" fn SCJbSetBase64(
2,334✔
998
    js: &mut JsonBuilder, key: *const c_char, bytes: *const u8, len: u32,
2,334✔
999
) -> bool {
2,334✔
1000
    if bytes.is_null() || len == 0 {
2,334✔
1001
        return false;
6✔
1002
    }
2,328✔
1003
    if let Ok(key) = CStr::from_ptr(key).to_str() {
2,328✔
1004
        let val = std::slice::from_raw_parts(bytes, len as usize);
2,328✔
1005
        return js.set_base64(key, val).is_ok();
2,328✔
1006
    }
×
1007
    return false;
×
1008
}
2,334✔
1009

1010
#[no_mangle]
1011
pub unsafe extern "C" fn SCJbSetHex(
261✔
1012
    js: &mut JsonBuilder, key: *const c_char, bytes: *const u8, len: u32,
261✔
1013
) -> bool {
261✔
1014
    if bytes.is_null() || len == 0 {
261✔
1015
        return false;
×
1016
    }
261✔
1017
    if let Ok(key) = CStr::from_ptr(key).to_str() {
261✔
1018
        let val = std::slice::from_raw_parts(bytes, len as usize);
261✔
1019
        return js.set_hex(key, val).is_ok();
261✔
1020
    }
×
1021
    return false;
×
1022
}
261✔
1023

1024
#[no_mangle]
1025
pub unsafe extern "C" fn SCJbSetFormatted(js: &mut JsonBuilder, formatted: *const c_char) -> bool {
69,675✔
1026
    if let Ok(formatted) = CStr::from_ptr(formatted).to_str() {
69,675✔
1027
        return js.set_formatted(formatted).is_ok();
69,675✔
1028
    }
×
1029
    return false;
×
1030
}
69,675✔
1031

1032
#[no_mangle]
1033
pub unsafe extern "C" fn SCJbAppendObject(jb: &mut JsonBuilder, obj: &JsonBuilder) -> bool {
496✔
1034
    jb.append_object(obj).is_ok()
496✔
1035
}
496✔
1036

1037
#[no_mangle]
1038
pub unsafe extern "C" fn SCJbSetObject(
23,799✔
1039
    js: &mut JsonBuilder, key: *const c_char, val: &mut JsonBuilder,
23,799✔
1040
) -> bool {
23,799✔
1041
    if let Ok(key) = CStr::from_ptr(key).to_str() {
23,799✔
1042
        return js.set_object(key, val).is_ok();
23,799✔
1043
    }
×
1044
    return false;
×
1045
}
23,799✔
1046

1047
#[no_mangle]
1048
pub unsafe extern "C" fn SCJbAppendString(js: &mut JsonBuilder, val: *const c_char) -> bool {
283,084✔
1049
    if val.is_null() {
283,084✔
1050
        return false;
×
1051
    }
283,084✔
1052
    if let Ok(val) = CStr::from_ptr(val).to_str() {
283,084✔
1053
        return js.append_string(val).is_ok();
283,084✔
1054
    }
×
1055
    return false;
×
1056
}
283,084✔
1057

1058
#[no_mangle]
1059
pub unsafe extern "C" fn SCJbAppendStringFromBytes(
17,379✔
1060
    js: &mut JsonBuilder, bytes: *const u8, len: u32,
17,379✔
1061
) -> bool {
17,379✔
1062
    if bytes.is_null() || len == 0 {
17,379✔
1063
        return false;
×
1064
    }
17,379✔
1065
    let val = std::slice::from_raw_parts(bytes, len as usize);
17,379✔
1066
    return js.append_string_from_bytes(val).is_ok();
17,379✔
1067
}
17,379✔
1068

1069
#[no_mangle]
1070
pub unsafe extern "C" fn SCJbAppendBase64(
3✔
1071
    js: &mut JsonBuilder, bytes: *const u8, len: u32,
3✔
1072
) -> bool {
3✔
1073
    if bytes.is_null() || len == 0 {
3✔
1074
        return false;
×
1075
    }
3✔
1076
    let val = std::slice::from_raw_parts(bytes, len as usize);
3✔
1077
    return js.append_base64(val).is_ok();
3✔
1078
}
3✔
1079

1080
#[no_mangle]
1081
pub unsafe extern "C" fn SCJbAppendUint(js: &mut JsonBuilder, val: u64) -> bool {
15,938✔
1082
    return js.append_uint(val).is_ok();
15,938✔
1083
}
15,938✔
1084

1085
#[no_mangle]
1086
pub unsafe extern "C" fn SCJbAppendFloat(js: &mut JsonBuilder, val: f64) -> bool {
×
1087
    return js.append_float(val).is_ok();
×
1088
}
×
1089

1090
#[no_mangle]
1091
pub unsafe extern "C" fn SCJbSetUint(js: &mut JsonBuilder, key: *const c_char, val: u64) -> bool {
14,585,348✔
1092
    if let Ok(key) = CStr::from_ptr(key).to_str() {
14,585,348✔
1093
        return js.set_uint(key, val).is_ok();
14,585,348✔
1094
    }
×
1095
    return false;
×
1096
}
14,585,348✔
1097

1098
#[no_mangle]
1099
pub unsafe extern "C" fn SCJbSetInt(js: &mut JsonBuilder, key: *const c_char, val: i64) -> bool {
563✔
1100
    if let Ok(key) = CStr::from_ptr(key).to_str() {
563✔
1101
        return js.set_int(key, val).is_ok();
563✔
1102
    }
×
1103
    return false;
×
1104
}
563✔
1105

1106
#[no_mangle]
1107
pub unsafe extern "C" fn SCJbSetFloat(js: &mut JsonBuilder, key: *const c_char, val: f64) -> bool {
26✔
1108
    if let Ok(key) = CStr::from_ptr(key).to_str() {
26✔
1109
        return js.set_float(key, val).is_ok();
26✔
1110
    }
×
1111
    return false;
×
1112
}
26✔
1113

1114
#[no_mangle]
1115
pub unsafe extern "C" fn SCJbSetBool(js: &mut JsonBuilder, key: *const c_char, val: bool) -> bool {
52,094✔
1116
    if let Ok(key) = CStr::from_ptr(key).to_str() {
52,094✔
1117
        return js.set_bool(key, val).is_ok();
52,094✔
1118
    }
×
1119
    return false;
×
1120
}
52,094✔
1121

1122
#[no_mangle]
1123
pub unsafe extern "C" fn SCJbClose(js: &mut JsonBuilder) -> bool {
3,535,832✔
1124
    js.close().is_ok()
3,535,832✔
1125
}
3,535,832✔
1126

1127
#[no_mangle]
1128
pub unsafe extern "C" fn SCJbLen(js: &JsonBuilder) -> usize {
1,148,648✔
1129
    js.buf.len()
1,148,648✔
1130
}
1,148,648✔
1131

1132
#[no_mangle]
1133
pub unsafe extern "C" fn SCJbPtr(js: &mut JsonBuilder) -> *const u8 {
1,146,438✔
1134
    js.buf.as_ptr()
1,146,438✔
1135
}
1,146,438✔
1136

1137
#[no_mangle]
1138
pub unsafe extern "C" fn SCJbGetMark(js: &mut JsonBuilder, mark: &mut JsonBuilderMark) {
6,281✔
1139
    let m = js.get_mark();
6,281✔
1140
    mark.position = m.position;
6,281✔
1141
    mark.state_index = m.state_index;
6,281✔
1142
    mark.state = m.state;
6,281✔
1143
}
6,281✔
1144

1145
#[no_mangle]
1146
pub unsafe extern "C" fn SCJbRestoreMark(js: &mut JsonBuilder, mark: &mut JsonBuilderMark) -> bool {
194✔
1147
    js.restore_mark(mark).is_ok()
194✔
1148
}
194✔
1149

1150
#[cfg(test)]
1151
mod test {
1152
    use super::*;
1153

1154
    #[test]
1155
    fn test_try_reserve() {
1✔
1156
        // Just a sanity check that try_reserve works as I expect.
1✔
1157
        let mut buf = String::new();
1✔
1158
        assert_eq!(buf.len(), 0);
1✔
1159
        assert_eq!(buf.capacity(), 0);
1✔
1160

1161
        buf.try_reserve(1).unwrap();
1✔
1162
        assert_eq!(buf.len(), 0);
1✔
1163
        assert!(buf.capacity() >= 1);
1✔
1164
    }
1✔
1165

1166
    #[test]
1167
    fn test_set_bool() {
1✔
1168
        let mut jb = JsonBuilder::try_new_object().unwrap();
1✔
1169
        jb.set_bool("first", true).unwrap();
1✔
1170
        assert_eq!(jb.buf, r#"{"first":true"#);
1✔
1171
        jb.set_bool("second", false).unwrap();
1✔
1172
        assert_eq!(jb.buf, r#"{"first":true,"second":false"#);
1✔
1173

1174
        let mut jb = JsonBuilder::try_new_object().unwrap();
1✔
1175
        jb.set_bool("first", false).unwrap();
1✔
1176
        assert_eq!(jb.buf, r#"{"first":false"#);
1✔
1177
        jb.set_bool("second", true).unwrap();
1✔
1178
        assert_eq!(jb.buf, r#"{"first":false,"second":true"#);
1✔
1179
    }
1✔
1180

1181
    #[test]
1182
    fn test_object_in_object() -> Result<(), JsonError> {
1✔
1183
        let mut js = JsonBuilder::try_new_object().unwrap();
1✔
1184

1✔
1185
        js.open_object("object")?;
1✔
1186
        assert_eq!(js.current_state(), State::ObjectFirst);
1✔
1187
        assert_eq!(js.buf, r#"{"object":{"#);
1✔
1188

1189
        js.set_string("one", "one")?;
1✔
1190
        assert_eq!(js.current_state(), State::ObjectNth);
1✔
1191
        assert_eq!(js.buf, r#"{"object":{"one":"one""#);
1✔
1192

1193
        js.close()?;
1✔
1194
        assert_eq!(js.current_state(), State::ObjectNth);
1✔
1195
        assert_eq!(js.buf, r#"{"object":{"one":"one"}"#);
1✔
1196

1197
        js.close()?;
1✔
1198
        assert_eq!(js.current_state(), State::None);
1✔
1199
        assert_eq!(js.buf, r#"{"object":{"one":"one"}}"#);
1✔
1200

1201
        Ok(())
1✔
1202
    }
1✔
1203

1204
    #[test]
1205
    fn test_empty_array_in_object() -> Result<(), JsonError> {
1✔
1206
        let mut js = JsonBuilder::try_new_object().unwrap();
1✔
1207

1✔
1208
        js.open_array("array")?;
1✔
1209
        assert_eq!(js.current_state(), State::ArrayFirst);
1✔
1210

1211
        js.close()?;
1✔
1212
        assert_eq!(js.current_state(), State::ObjectNth);
1✔
1213
        assert_eq!(js.buf, r#"{"array":[]"#);
1✔
1214

1215
        js.close()?;
1✔
1216
        assert_eq!(js.buf, r#"{"array":[]}"#);
1✔
1217

1218
        Ok(())
1✔
1219
    }
1✔
1220

1221
    #[test]
1222
    #[cfg(not(feature = "debug-validate"))]
1223
    fn test_array_in_object() -> Result<(), JsonError> {
1✔
1224
        let mut js = JsonBuilder::try_new_object().unwrap();
1✔
1225

1✔
1226
        // Attempt to add an item, should fail.
1✔
1227
        assert_eq!(
1✔
1228
            js.append_string("will fail").err().unwrap(),
1✔
1229
            JsonError::InvalidState
1✔
1230
        );
1✔
1231

1232
        js.open_array("array")?;
1✔
1233
        assert_eq!(js.current_state(), State::ArrayFirst);
1✔
1234

1235
        js.append_string("one")?;
1✔
1236
        assert_eq!(js.current_state(), State::ArrayNth);
1✔
1237
        assert_eq!(js.buf, r#"{"array":["one""#);
1✔
1238

1239
        js.append_string("two")?;
1✔
1240
        assert_eq!(js.current_state(), State::ArrayNth);
1✔
1241
        assert_eq!(js.buf, r#"{"array":["one","two""#);
1✔
1242

1243
        js.append_uint(3)?;
1✔
1244
        assert_eq!(js.current_state(), State::ArrayNth);
1✔
1245
        assert_eq!(js.buf, r#"{"array":["one","two",3"#);
1✔
1246

1247
        js.close()?;
1✔
1248
        assert_eq!(js.current_state(), State::ObjectNth);
1✔
1249
        assert_eq!(js.buf, r#"{"array":["one","two",3]"#);
1✔
1250

1251
        js.close()?;
1✔
1252
        assert_eq!(js.current_state(), State::None);
1✔
1253
        assert_eq!(js.buf, r#"{"array":["one","two",3]}"#);
1✔
1254

1255
        Ok(())
1✔
1256
    }
1✔
1257

1258
    #[test]
1259
    fn basic_test() -> Result<(), JsonError> {
1✔
1260
        let mut js = JsonBuilder::try_new_object().unwrap();
1✔
1261
        assert_eq!(js.current_state(), State::ObjectFirst);
1✔
1262
        assert_eq!(js.buf, "{");
1✔
1263

1264
        js.set_string("one", "one")?;
1✔
1265
        assert_eq!(js.current_state(), State::ObjectNth);
1✔
1266
        assert_eq!(js.buf, r#"{"one":"one""#);
1✔
1267

1268
        js.set_string("two", "two")?;
1✔
1269
        assert_eq!(js.current_state(), State::ObjectNth);
1✔
1270
        assert_eq!(js.buf, r#"{"one":"one","two":"two""#);
1✔
1271

1272
        js.set_uint("three", 3u8)?;
1✔
1273

1274
        js.close()?;
1✔
1275
        assert_eq!(js.current_state(), State::None);
1✔
1276
        assert_eq!(js.buf, r#"{"one":"one","two":"two","three":3}"#);
1✔
1277

1278
        Ok(())
1✔
1279
    }
1✔
1280

1281
    #[test]
1282
    fn test_combine() -> Result<(), JsonError> {
1✔
1283
        let mut main = JsonBuilder::try_new_object().unwrap();
1✔
1284
        let mut obj = JsonBuilder::try_new_object().unwrap();
1✔
1285
        obj.close()?;
1✔
1286

1287
        let mut array = JsonBuilder::try_new_array().unwrap();
1✔
1288
        array.append_string("one")?;
1✔
1289
        array.append_uint(2)?;
1✔
1290
        array.close()?;
1✔
1291
        main.set_object("object", &obj)?;
1✔
1292
        main.set_object("array", &array)?;
1✔
1293
        main.close()?;
1✔
1294

1295
        assert_eq!(main.buf, r#"{"object":{},"array":["one",2]}"#);
1✔
1296

1297
        Ok(())
1✔
1298
    }
1✔
1299

1300
    #[test]
1301
    fn test_objects_in_array() -> Result<(), JsonError> {
1✔
1302
        let mut js = JsonBuilder::try_new_array()?;
1✔
1303
        assert_eq!(js.buf, r#"["#);
1✔
1304

1305
        js.start_object()?;
1✔
1306
        assert_eq!(js.buf, r#"[{"#);
1✔
1307

1308
        js.set_string("uid", "0")?;
1✔
1309
        assert_eq!(js.buf, r#"[{"uid":"0""#);
1✔
1310

1311
        js.close()?;
1✔
1312
        assert_eq!(js.buf, r#"[{"uid":"0"}"#);
1✔
1313

1314
        js.start_object()?;
1✔
1315
        assert_eq!(js.buf, r#"[{"uid":"0"},{"#);
1✔
1316

1317
        js.set_string("username", "root")?;
1✔
1318
        assert_eq!(js.buf, r#"[{"uid":"0"},{"username":"root""#);
1✔
1319

1320
        js.close()?;
1✔
1321
        assert_eq!(js.buf, r#"[{"uid":"0"},{"username":"root"}"#);
1✔
1322

1323
        js.close()?;
1✔
1324
        assert_eq!(js.buf, r#"[{"uid":"0"},{"username":"root"}]"#);
1✔
1325

1326
        Ok(())
1✔
1327
    }
1✔
1328

1329
    #[test]
1330
    fn test_grow() -> Result<(), JsonError> {
1✔
1331
        let mut jb = JsonBuilder::try_new_object_with_capacity(1).unwrap();
1✔
1332

1✔
1333
        // For efficiency reasons, more capacity may be allocated than
1✔
1334
        // requested.
1✔
1335
        assert!(jb.capacity() > 0);
1✔
1336
        let capacity = jb.capacity();
1✔
1337

1✔
1338
        let mut value = String::new();
1✔
1339
        for i in 0..capacity {
8✔
1340
            value.push_str((i % 10).to_string().as_str());
8✔
1341
        }
8✔
1342
        jb.set_string("foo", &value)?;
1✔
1343
        assert!(jb.capacity() > capacity);
1✔
1344
        Ok(())
1✔
1345
    }
1✔
1346

1347
    #[test]
1348
    fn test_reset() -> Result<(), JsonError> {
1✔
1349
        let mut jb = JsonBuilder::try_new_object().unwrap();
1✔
1350
        assert_eq!(jb.buf, "{");
1✔
1351
        jb.set_string("foo", "bar")?;
1✔
1352
        let cap = jb.capacity();
1✔
1353
        jb.reset();
1✔
1354
        assert_eq!(jb.buf, "{");
1✔
1355
        assert_eq!(jb.capacity(), cap);
1✔
1356
        Ok(())
1✔
1357
    }
1✔
1358

1359
    #[test]
1360
    fn test_append_string_from_bytes() -> Result<(), JsonError> {
1✔
1361
        let mut jb = JsonBuilder::try_new_array().unwrap();
1✔
1362
        let s = &[0x41, 0x41, 0x41, 0x00];
1✔
1363
        jb.append_string_from_bytes(s)?;
1✔
1364
        assert_eq!(jb.buf, r#"["AAA\u0000""#);
1✔
1365

1366
        let s = &[0x00, 0x01, 0x02, 0x03];
1✔
1367
        let mut jb = JsonBuilder::try_new_array().unwrap();
1✔
1368
        jb.append_string_from_bytes(s)?;
1✔
1369
        assert_eq!(jb.buf, r#"["\u0000\u0001\u0002\u0003""#);
1✔
1370

1371
        Ok(())
1✔
1372
    }
1✔
1373

1374
    #[test]
1375
    fn test_set_string_from_bytes() {
1✔
1376
        let mut jb = JsonBuilder::try_new_object().unwrap();
1✔
1377
        jb.set_string_from_bytes("first", &[]).unwrap();
1✔
1378
        assert_eq!(jb.buf, r#"{"first":"""#);
1✔
1379
        jb.set_string_from_bytes("second", &[]).unwrap();
1✔
1380
        assert_eq!(jb.buf, r#"{"first":"","second":"""#);
1✔
1381
    }
1✔
1382

1383
    #[test]
1384
    fn test_append_string_from_bytes_grow() -> Result<(), JsonError> {
1✔
1385
        let s = &[0x00, 0x01, 0x02, 0x03];
1✔
1386
        let mut jb = JsonBuilder::try_new_array().unwrap();
1✔
1387
        jb.append_string_from_bytes(s)?;
1✔
1388

1389
        for i in 1..1000 {
1,000✔
1390
            let mut s = Vec::new();
999✔
1391
            s.resize(i, 0x41);
999✔
1392
            let mut jb = JsonBuilder::try_new_array().unwrap();
999✔
1393
            jb.append_string_from_bytes(&s)?;
999✔
1394
        }
1395

1396
        Ok(())
1✔
1397
    }
1✔
1398

1399
    #[test]
1400
    fn test_set_string_limited() {
1✔
1401
        let mut jb = JsonBuilder::try_new_object().unwrap();
1✔
1402
        jb.set_string_limited("val", "foobar", 10).unwrap();
1✔
1403
        assert_eq!(jb.buf, r#"{"val":"foobar""#);
1✔
1404
        jb.reset();
1✔
1405
        jb.set_string_limited("val", "foobar", 2).unwrap();
1✔
1406
        assert_eq!(jb.buf, r#"{"val":"fo[truncated 4 additional bytes]""#);
1✔
1407
        jb.reset();
1✔
1408
        jb.set_string_limited("val", "foobar", 0).unwrap();
1✔
1409
        assert_eq!(jb.buf, r#"{"val":"[truncated 6 additional bytes]""#);
1✔
1410
        jb.reset();
1✔
1411
        let unicode_str = "Hello, 世界! 👋😊";
1✔
1412
        // invalid unicode boundary, naive access should panic
1✔
1413
        let result = std::panic::catch_unwind(|| _ = unicode_str[..9]);
1✔
1414
        assert!(result.is_err());
1✔
1415
        // our code should just skip the incomplete character
1416
        jb.set_string_limited("val", unicode_str, 9).unwrap();
1✔
1417
        assert_eq!(jb.buf, r#"{"val":"Hello, [truncated 14 additional bytes]""#);
1✔
1418
        jb.reset();
1✔
1419
        // valid unicode boundary, naive access should not panic
1✔
1420
        let result = std::panic::catch_unwind(|| _ = unicode_str[..10]);
1✔
1421
        assert!(result.is_ok());
1✔
1422
        jb.set_string_limited("val", unicode_str, 10).unwrap();
1✔
1423
        assert_eq!(
1✔
1424
            jb.buf,
1✔
1425
            r#"{"val":"Hello, 世[truncated 13 additional bytes]""#
1✔
1426
        );
1✔
1427
        jb.reset();
1✔
1428
        let unicode_str2 = "世";
1✔
1429
        // this character has three UTF-8 bytes
1✔
1430
        assert_eq!(
1✔
1431
            unicode_str2,
1✔
1432
            std::str::from_utf8(&[0xE4, 0xB8, 0x96]).unwrap()
1✔
1433
        );
1✔
1434
        let result = std::panic::catch_unwind(|| _ = unicode_str2[..1]);
1✔
1435
        assert!(result.is_err());
1✔
1436
        jb.set_string_limited("val", unicode_str2, 1).unwrap();
1✔
1437
        assert_eq!(jb.buf, r#"{"val":"[truncated 2 additional bytes]""#);
1✔
1438
        jb.reset();
1✔
1439
        jb.set_string_limited("val", unicode_str2, 2).unwrap();
1✔
1440
        assert_eq!(jb.buf, r#"{"val":"[truncated 1 additional byte]""#);
1✔
1441
        jb.reset();
1✔
1442
        // with limit 3 or more we should include it in the log
1✔
1443
        jb.set_string_limited("val", unicode_str2, 3).unwrap();
1✔
1444
        assert_eq!(jb.buf, r#"{"val":"世""#);
1✔
1445
        jb.reset();
1✔
1446
        jb.set_string_limited("val", unicode_str2, 4).unwrap();
1✔
1447
        assert_eq!(jb.buf, r#"{"val":"世""#);
1✔
1448
        jb.reset();
1✔
1449
        jb.set_string_limited("val", unicode_str2, 0).unwrap();
1✔
1450
        assert_eq!(jb.buf, r#"{"val":"[truncated 3 additional bytes]""#);
1✔
1451
        let unicode_str3 = "🏴󠁧󠁢󠁷󠁬󠁳󠁿";
1✔
1452
        // this character consists of multiple code points
1✔
1453
        jb.reset();
1✔
1454
        jb.set_string_limited("val", unicode_str3, 7).unwrap();
1✔
1455
        assert_eq!(jb.buf, r#"{"val":"🏴[truncated 21 additional bytes]""#);
1✔
1456
        jb.reset();
1✔
1457
        jb.set_string_limited("val", unicode_str3, 2).unwrap();
1✔
1458
        assert_eq!(jb.buf, r#"{"val":"[truncated 26 additional bytes]""#);
1✔
1459
    }
1✔
1460

1461
    #[test]
1462
    fn test_set_string_from_bytes_limited() {
1✔
1463
        let mut jb = JsonBuilder::try_new_object().unwrap();
1✔
1464
        jb.set_string_from_bytes_limited("first", b"foobar", 10)
1✔
1465
            .unwrap();
1✔
1466
        assert_eq!(jb.buf, r#"{"first":"foobar""#);
1✔
1467
        jb.set_string_from_bytes_limited("second", b"foobar", 2)
1✔
1468
            .unwrap();
1✔
1469
        assert_eq!(
1✔
1470
            jb.buf,
1✔
1471
            r#"{"first":"foobar","second":"fo[truncated 4 additional bytes]""#
1✔
1472
        );
1✔
1473
        jb.set_string_from_bytes_limited("third", b"foobar", 0)
1✔
1474
            .unwrap();
1✔
1475
        assert_eq!(
1✔
1476
            jb.buf,
1✔
1477
            r#"{"first":"foobar","second":"fo[truncated 4 additional bytes]","third":"[truncated 6 additional bytes]""#
1✔
1478
        );
1✔
1479
    }
1✔
1480

1481
    #[test]
1482
    fn test_invalid_utf8() {
1✔
1483
        let mut jb = JsonBuilder::try_new_object().unwrap();
1✔
1484
        jb.set_string_from_bytes("invalid", &[0xf0, 0xf1, 0xf2])
1✔
1485
            .unwrap();
1✔
1486
        assert_eq!(jb.buf, r#"{"invalid":"\\xf0\\xf1\\xf2""#);
1✔
1487

1488
        let mut jb = JsonBuilder::try_new_array().unwrap();
1✔
1489
        jb.append_string_from_bytes(&[0xf0, 0xf1, 0xf2]).unwrap();
1✔
1490
        assert_eq!(jb.buf, r#"["\\xf0\\xf1\\xf2""#);
1✔
1491
    }
1✔
1492

1493
    #[test]
1494
    fn test_marks() {
1✔
1495
        let mut jb = JsonBuilder::try_new_object().unwrap();
1✔
1496
        jb.set_string("foo", "bar").unwrap();
1✔
1497
        assert_eq!(jb.buf, r#"{"foo":"bar""#);
1✔
1498
        assert_eq!(jb.current_state(), State::ObjectNth);
1✔
1499
        assert_eq!(jb.state.len(), 2);
1✔
1500
        let mark = jb.get_mark();
1✔
1501

1✔
1502
        // Mutate such that states are transitioned.
1✔
1503
        jb.open_array("bar").unwrap();
1✔
1504
        jb.start_object().unwrap();
1✔
1505
        assert_eq!(jb.buf, r#"{"foo":"bar","bar":[{"#);
1✔
1506
        assert_eq!(jb.current_state(), State::ObjectFirst);
1✔
1507
        assert_eq!(jb.state.len(), 4);
1✔
1508

1509
        // Restore to mark.
1510
        jb.restore_mark(&mark).unwrap();
1✔
1511
        assert_eq!(jb.buf, r#"{"foo":"bar""#);
1✔
1512
        assert_eq!(jb.current_state(), State::ObjectNth);
1✔
1513
        assert_eq!(jb.state.len(), 2);
1✔
1514
    }
1✔
1515

1516
    #[test]
1517
    fn test_set_formatted() {
1✔
1518
        let mut jb = JsonBuilder::try_new_object().unwrap();
1✔
1519
        jb.set_formatted("\"foo\":\"bar\"").unwrap();
1✔
1520
        assert_eq!(jb.buf, r#"{"foo":"bar""#);
1✔
1521
        jb.set_formatted("\"bar\":\"foo\"").unwrap();
1✔
1522
        assert_eq!(jb.buf, r#"{"foo":"bar","bar":"foo""#);
1✔
1523
        jb.close().unwrap();
1✔
1524
        assert_eq!(jb.buf, r#"{"foo":"bar","bar":"foo"}"#);
1✔
1525
    }
1✔
1526

1527
    #[test]
1528
    fn test_set_float() {
1✔
1529
        let mut jb = JsonBuilder::try_new_object().unwrap();
1✔
1530
        jb.set_float("one", 1.1).unwrap();
1✔
1531
        jb.set_float("two", 2.2).unwrap();
1✔
1532
        jb.close().unwrap();
1✔
1533
        assert_eq!(jb.buf, r#"{"one":1.1,"two":2.2}"#);
1✔
1534
    }
1✔
1535

1536
    #[test]
1537
    fn test_append_float() {
1✔
1538
        let mut jb = JsonBuilder::try_new_array().unwrap();
1✔
1539
        jb.append_float(1.1).unwrap();
1✔
1540
        jb.append_float(2.2).unwrap();
1✔
1541
        jb.close().unwrap();
1✔
1542
        assert_eq!(jb.buf, r#"[1.1,2.2]"#);
1✔
1543
    }
1✔
1544

1545
    #[test]
1546
    fn test_set_nan() {
1✔
1547
        let mut jb = JsonBuilder::try_new_object().unwrap();
1✔
1548
        jb.set_float("nan", f64::NAN).unwrap();
1✔
1549
        jb.close().unwrap();
1✔
1550
        assert_eq!(jb.buf, r#"{"nan":null}"#);
1✔
1551
    }
1✔
1552

1553
    #[test]
1554
    fn test_append_nan() {
1✔
1555
        let mut jb = JsonBuilder::try_new_array().unwrap();
1✔
1556
        jb.append_float(f64::NAN).unwrap();
1✔
1557
        jb.close().unwrap();
1✔
1558
        assert_eq!(jb.buf, r#"[null]"#);
1✔
1559
    }
1✔
1560

1561
    #[test]
1562
    fn test_set_inf() {
1✔
1563
        let mut jb = JsonBuilder::try_new_object().unwrap();
1✔
1564
        jb.set_float("inf", f64::INFINITY).unwrap();
1✔
1565
        jb.close().unwrap();
1✔
1566
        assert_eq!(jb.buf, r#"{"inf":null}"#);
1✔
1567

1568
        let mut jb = JsonBuilder::try_new_object().unwrap();
1✔
1569
        jb.set_float("inf", f64::NEG_INFINITY).unwrap();
1✔
1570
        jb.close().unwrap();
1✔
1571
        assert_eq!(jb.buf, r#"{"inf":null}"#);
1✔
1572
    }
1✔
1573

1574
    #[test]
1575
    fn test_append_inf() {
1✔
1576
        let mut jb = JsonBuilder::try_new_array().unwrap();
1✔
1577
        jb.append_float(f64::INFINITY).unwrap();
1✔
1578
        jb.close().unwrap();
1✔
1579
        assert_eq!(jb.buf, r#"[null]"#);
1✔
1580

1581
        let mut jb = JsonBuilder::try_new_array().unwrap();
1✔
1582
        jb.append_float(f64::NEG_INFINITY).unwrap();
1✔
1583
        jb.close().unwrap();
1✔
1584
        assert_eq!(jb.buf, r#"[null]"#);
1✔
1585
    }
1✔
1586
}
1587

1588
// Escape table as seen in serde-json (MIT/Apache license)
1589

1590
const QU: u8 = b'"';
1591
const BS: u8 = b'\\';
1592
const BB: u8 = b'b';
1593
const TT: u8 = b't';
1594
const NN: u8 = b'n';
1595
const FF: u8 = b'f';
1596
const RR: u8 = b'r';
1597
const UU: u8 = b'u';
1598
const __: u8 = 0;
1599

1600
// Look up table for characters that need escaping in a product string
1601
static ESCAPED: [u8; 256] = [
1602
    // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
1603
    UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, // 0
1604
    UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, // 1
1605
    __, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, // 2
1606
    __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 3
1607
    __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 4
1608
    __, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, // 5
1609
    __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 6
1610
    __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 7
1611
    __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 8
1612
    __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 9
1613
    __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // A
1614
    __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // B
1615
    __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // C
1616
    __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // D
1617
    __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // E
1618
    __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // F
1619
];
1620

1621
pub static HEX: [u8; 16] = [
1622
    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',
1623
];
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc