1use crate::error::MessageError;
6use crate::{ExtendedMetadata, MessageFormatter};
7use bumpalo::Bump;
8use bumpalo::collections::{String as BumpaloString, Vec as BumpaloVec};
9use diagnostics_data::{ExtendedMoniker, Severity};
10use diagnostics_log_encoding::zx::BootInstant;
11use diagnostics_log_encoding::{Argument, Record, Value};
12use flyweights::FlyStr;
13use static_assertions::const_assert;
14use std::marker::PhantomData;
15use std::ops::Deref;
16use std::str;
17
18pub use crate::constants::*;
19
20#[repr(C)]
24pub struct CppArray<'a, T> {
25 pub len: usize,
27 pub ptr: *const T,
32
33 phantom: PhantomData<&'a T>,
34}
35
36impl<T> Deref for CppArray<'_, T> {
37 type Target = [T];
38
39 fn deref(&self) -> &Self::Target {
40 if self.len == 0 || self.ptr.is_null() {
41 &[]
42 } else {
43 unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
46 }
47 }
48}
49
50impl<T> Default for CppArray<'_, T> {
51 fn default() -> Self {
52 CppArray { len: 0, ptr: std::ptr::null(), phantom: PhantomData }
53 }
54}
55
56impl<'a> From<&'a str> for CppString<'a> {
57 fn from(value: &'a str) -> Self {
58 Self { inner: CppArray { len: value.len(), ptr: value.as_ptr(), phantom: PhantomData } }
59 }
60}
61
62#[derive(Default)]
68#[repr(transparent)]
69pub struct CppString<'a> {
70 inner: CppArray<'a, u8>,
71}
72
73impl Deref for CppString<'_> {
74 type Target = str;
75
76 fn deref(&self) -> &Self::Target {
77 if self.inner.len == 0 || self.inner.ptr.is_null() {
78 ""
79 } else {
80 unsafe {
82 std::str::from_utf8_unchecked(std::slice::from_raw_parts(
83 self.inner.ptr,
84 self.inner.len,
85 ))
86 }
87 }
88 }
89}
90
91impl std::fmt::Display for CppString<'_> {
92 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93 std::fmt::Display::fmt(&**self, f)
94 }
95}
96
97impl<'a> From<Option<&'a str>> for CppString<'a> {
98 fn from(value: Option<&'a str>) -> Self {
99 value.map(|v| v.into()).unwrap_or_default()
100 }
101}
102
103impl<'a, T> From<&'a [T]> for CppArray<'a, T> {
104 fn from(value: &'a [T]) -> Self {
105 CppArray { len: value.len(), ptr: value.as_ptr(), phantom: PhantomData }
106 }
107}
108
109impl<'a> From<BumpaloString<'a>> for CppString<'a> {
110 fn from(value: BumpaloString<'a>) -> Self {
111 value.into_bump_str().into()
112 }
113}
114
115#[repr(C, u8)]
117pub enum CppValue<'a> {
118 SignedInt(i64),
119 UnsignedInt(u64),
120 Floating(f64),
121 Boolean(bool),
122 Text(CppString<'a>),
123}
124
125#[repr(C)]
127pub struct CppKeyValue<'a> {
128 pub key: CppString<'a>,
129 pub value: CppValue<'a>,
130}
131
132#[repr(C)]
141pub struct LogMessage<'a> {
142 pub severity: u8,
144 pub tags: CppArray<'a, CppString<'a>>,
146 pub pid: u64,
148 pub tid: u64,
150 pub dropped: u64,
152 pub message: CppString<'a>,
154 pub file: CppString<'a>,
156 pub line: u64,
158 pub moniker_tag: CppString<'a>,
160 pub kvps: CppArray<'a, CppKeyValue<'a>>,
162 pub timestamp: i64,
165}
166
167const_assert!(!std::mem::needs_drop::<LogMessage<'_>>());
169
170pub struct CPPLogMessageBuilder<'a> {
171 severity: u8,
172 tags: BumpaloVec<'a, CppString<'a>>,
173 pid: Option<u64>,
174 tid: Option<u64>,
175 dropped: u64,
176 file: Option<CppString<'a>>,
177 line: Option<u64>,
178 moniker: Option<BumpaloString<'a>>,
179 message: Option<CppString<'a>>,
180 timestamp: i64,
181 kvps: BumpaloVec<'a, CppKeyValue<'a>>,
182 allocator: &'a Bump,
183}
184
185impl<'a> CPPLogMessageBuilder<'a> {
186 fn set_raw_severity(mut self, raw_severity: u8) -> Self {
187 self.severity = raw_severity;
188 self
189 }
190
191 fn cow_to_cpp_string(&self, cow: &std::borrow::Cow<'a, str>) -> CppString<'a> {
192 match cow {
193 std::borrow::Cow::Borrowed(s) => (*s).into(),
194 std::borrow::Cow::Owned(s) => {
195 BumpaloString::from_str_in(s, self.allocator).into_bump_str().into()
196 }
197 }
198 }
199
200 fn add_tag(mut self, tag: &std::borrow::Cow<'a, str>) -> Self {
201 let s = self.cow_to_cpp_string(tag);
202 self.tags.push(s);
203 self
204 }
205
206 fn set_pid(mut self, pid: u64) -> Self {
207 self.pid = Some(pid);
208 self
209 }
210
211 fn set_tid(mut self, tid: u64) -> Self {
212 self.tid = Some(tid);
213 self
214 }
215
216 fn set_dropped(mut self, dropped: u64) -> Self {
217 self.dropped = dropped;
218 self
219 }
220
221 fn set_file(mut self, file: &std::borrow::Cow<'a, str>) -> Self {
222 self.file = Some(self.cow_to_cpp_string(file));
223 self
224 }
225
226 fn set_line(mut self, line: u64) -> Self {
227 self.line = Some(line);
228 self
229 }
230
231 fn set_message(mut self, msg: &std::borrow::Cow<'a, str>) -> Self {
232 self.message = Some(self.cow_to_cpp_string(msg));
233 self
234 }
235
236 fn add_kvp(mut self, kvp: &Argument<'a>) -> Self {
237 let key: CppString<'a> = match kvp {
238 Argument::Other { name, .. } => self.cow_to_cpp_string(name),
239 other => {
240 BumpaloString::from_str_in(other.name(), self.allocator).into_bump_str().into()
241 }
242 };
243 let value = match kvp {
244 Argument::Pid(pid) => CppValue::UnsignedInt(pid.raw_koid()),
245 Argument::Tid(tid) => CppValue::UnsignedInt(tid.raw_koid()),
246 Argument::Tag(tag) => CppValue::Text(self.cow_to_cpp_string(tag)),
247 Argument::Dropped(dropped) => CppValue::UnsignedInt(*dropped),
248 Argument::File(file) => CppValue::Text(self.cow_to_cpp_string(file)),
249 Argument::Line(line) => CppValue::UnsignedInt(*line),
250 Argument::Message(msg) => CppValue::Text(self.cow_to_cpp_string(msg)),
251 Argument::Other { value, .. } => match value {
252 Value::Text(t) => CppValue::Text(self.cow_to_cpp_string(t)),
253 Value::SignedInt(v) => CppValue::SignedInt(*v),
254 Value::UnsignedInt(v) => CppValue::UnsignedInt(*v),
255 Value::Floating(v) => CppValue::Floating(*v),
256 Value::Boolean(v) => CppValue::Boolean(*v),
257 },
258 };
259 self.kvps.push(CppKeyValue { key, value });
260 self
261 }
262
263 fn set_moniker(mut self, value: &str) -> Self {
264 self.moniker = Some(BumpaloString::from_str_in(value, self.allocator));
265 self
266 }
267
268 pub fn build(mut self) -> &'a mut LogMessage<'a> {
269 let allocator = self.allocator;
270
271 let file: CppString<'a> = self.file.unwrap_or_default();
272 let line: u64 = self.line.unwrap_or(0);
273 let message: CppString<'a> = self.message.unwrap_or_default();
274
275 let moniker_tag: CppString<'a> = match &self.moniker {
276 Some(moniker) => match moniker.split('/').next_back() {
277 Some(name) if name != "." => {
278 BumpaloString::from_str_in(name, allocator).into_bump_str().into()
279 }
280 _ => CppString::default(),
281 },
282 None => CppString::default(),
283 };
284
285 let tags: &[_] = allocator.alloc_slice_fill_iter(self.tags.drain(..));
286 let kvps: &[_] = allocator.alloc_slice_fill_iter(self.kvps.drain(..));
287
288 allocator.alloc(LogMessage {
289 severity: self.severity,
290 dropped: self.dropped,
291 tags: tags.into(),
292 pid: self.pid.unwrap_or(0),
293 tid: self.tid.unwrap_or(0),
294 message,
295 file,
296 line,
297 moniker_tag,
298 kvps: kvps.into(),
299 timestamp: self.timestamp,
300 })
301 }
302}
303
304struct CPPLogMessageBuilderBuilder<'a>(&'a Bump);
305
306impl<'a> CPPLogMessageBuilderBuilder<'a> {
307 fn configure(
308 self,
309 _component_url: Option<FlyStr>,
310 moniker: Option<ExtendedMoniker>,
311 severity: Severity,
312 timestamp: BootInstant,
313 ) -> Result<CPPLogMessageBuilder<'a>, MessageError> {
314 Ok(CPPLogMessageBuilder {
315 severity: severity as u8,
316 tags: BumpaloVec::new_in(self.0),
317 pid: None,
318 tid: None,
319 dropped: 0,
320 file: None,
321 timestamp: timestamp.into_nanos(),
322 line: None,
323 allocator: self.0,
324 kvps: BumpaloVec::new_in(self.0),
325 moniker: moniker.map(|value| bumpalo::format!(in self.0,"{}", value)),
326 message: None,
327 })
328 }
329}
330
331pub fn build_logs_data<'a>(
332 input: &Record<'a>,
333 source: Option<ExtendedMetadata>,
334 allocator: &'a Bump,
335) -> Result<&'a mut LogMessage<'a>, MessageError> {
336 let builder = CPPLogMessageBuilderBuilder(allocator);
337 let (raw_severity, severity) = Severity::parse_exact(input.severity);
338 let (maybe_moniker, maybe_url, _) = source
339 .map(|value| (Some(value.moniker), Some(value.url), Some(value.rolled_out_logs)))
340 .unwrap_or((None, None, None));
341 let mut builder =
342 builder.configure(maybe_url.map(FlyStr::new), None, severity, input.timestamp)?;
343 if let Some(moniker) = maybe_moniker {
344 builder = builder.set_moniker(moniker.as_ref());
345 }
346 if let Some(raw_severity) = raw_severity {
347 builder = builder.set_raw_severity(raw_severity);
348 }
349
350 for argument in input.arguments.iter() {
351 match argument {
352 Argument::Tag(tag) => {
353 builder = builder.add_tag(tag);
354 }
355 Argument::Pid(pid) => {
356 builder = builder.set_pid(pid.raw_koid());
357 }
358 Argument::Tid(tid) => {
359 builder = builder.set_tid(tid.raw_koid());
360 }
361 Argument::Dropped(dropped) => {
362 builder = builder.set_dropped(*dropped);
363 }
364 Argument::File(file) => {
365 builder = builder.set_file(file);
366 }
367 Argument::Line(line) => {
368 builder = builder.set_line(*line);
369 }
370 Argument::Message(msg) => {
371 builder = builder.set_message(msg);
372 }
373 Argument::Other { .. } => builder = builder.add_kvp(argument),
374 }
375 }
376
377 Ok(builder.build())
378}
379
380pub fn ffi_from_extended_record<'a>(
390 bytes: &'a [u8],
391 allocator: &'a Bump,
392) -> Result<(&'a mut LogMessage<'a>, &'a [u8]), MessageError> {
393 let (input, remaining) = diagnostics_log_encoding::parse::parse_record(bytes)?;
394 let (source, new_remaining) = if remaining.len() >= 16 {
395 let moniker_len = u32::from_le_bytes(remaining[0..4].try_into().unwrap()) as usize;
396 let component_url_len = u32::from_le_bytes(remaining[4..8].try_into().unwrap()) as usize;
397 let rolled_out_logs = u64::from_le_bytes(remaining[8..16].try_into().unwrap());
398 let mut offset: usize = 16;
399
400 let moniker_padded_len = (moniker_len + 7) & !7;
403 let component_url_padded_len = (component_url_len + 7) & !7;
404 let moniker_padded_end = offset + moniker_padded_len;
405 let url_padded_end = moniker_padded_end + component_url_padded_len;
406 if url_padded_end > remaining.len() {
407 return Err(MessageError::OutOfBounds);
408 }
409
410 let moniker = str::from_utf8(&remaining[offset..offset + moniker_len])?;
411 offset += moniker_padded_len;
412 let url = str::from_utf8(&remaining[offset..offset + component_url_len])?;
413 offset += component_url_padded_len;
414 (
415 Some(ExtendedMetadata {
416 moniker: ExtendedMoniker::parse_str(moniker)?,
417 url: url.into(),
418 rolled_out_logs,
419 }),
420 &remaining[offset..],
421 )
422 } else {
423 (None, remaining)
424 };
425 let record = build_logs_data(&input, source, allocator)?;
426 Ok((record, new_remaining))
427}
428
429pub struct CPPMessageFormatter<'a>(pub &'a Bump);
430impl<'a> MessageFormatter<'a> for &CPPMessageFormatter<'a> {
431 type Result = &'a mut LogMessage<'a>;
432
433 fn format(
434 &mut self,
435 record: &Record<'a>,
436 metadata: Option<ExtendedMetadata>,
437 ) -> Result<Self::Result, MessageError> {
438 build_logs_data(record, metadata, self.0)
439 }
440}
441
442#[cfg(test)]
443mod test {
444 use super::*;
445 use crate::MessageParser;
446 use bumpalo::Bump;
447 use diagnostics_log_encoding::encode::{Encoder, EncoderOpts};
448 use diagnostics_log_encoding::zx::BootInstant;
449 use diagnostics_log_encoding::{Argument, Header, LOG_CONTROL_BIT, Record};
450 use std::io::Cursor;
451
452 fn overwrite_header_tag(bytes: &mut [u8], tag: u32) {
453 if bytes.len() >= 8 {
454 let mut header = Header(u64::from_le_bytes(bytes[0..8].try_into().unwrap()));
455 header.set_tag(tag);
456 bytes[0..8].copy_from_slice(&header.0.to_le_bytes());
457 }
458 }
459
460 #[fuchsia::test]
461 fn test_short_read() {
462 let mut parser = MessageParser::default();
463 let allocator = Bump::new();
464 let formatter = CPPMessageFormatter(&allocator);
465 let bytes = vec![0u8; 7];
466 let res = parser.parse_next(&bytes, &formatter);
467 assert!(matches!(res, Err(MessageError::ShortRead { len: 7 })));
468 }
469
470 #[fuchsia::test]
471 fn test_normal_parsing() {
472 let mut parser = MessageParser::default();
473 let allocator = Bump::new();
474 let formatter = CPPMessageFormatter(&allocator);
475
476 let record = Record {
477 timestamp: BootInstant::from_nanos(72),
478 severity: 0x30,
479 arguments: vec![Argument::message("hello world")],
480 };
481 let mut buffer = Cursor::new(vec![0u8; 1024]);
482 let mut encoder = Encoder::new(&mut buffer, EncoderOpts::default());
483 encoder.write_record(record).unwrap();
484
485 let len = buffer.position() as usize;
486 let mut bytes = buffer.into_inner();
487 bytes.truncate(len);
488
489 let res = parser.parse_next(&bytes, &formatter).unwrap();
490 assert!(res.0.is_some());
491 let log_message = &res.0.unwrap();
492 assert_eq!(&*log_message.message, "hello world");
493 assert_eq!(log_message.timestamp, 72);
494 assert_eq!(log_message.severity, 0x30);
495 }
496
497 #[fuchsia::test]
498 fn test_escaping_in_kvp() {
499 let mut parser = MessageParser::default();
500 let allocator = Bump::new();
501 let formatter = CPPMessageFormatter(&allocator);
502
503 let record = Record {
504 timestamp: BootInstant::from_nanos(72),
505 severity: 0x30,
506 arguments: vec![
507 Argument::message("hello world"),
508 Argument::new("key", r#"val"with\escapes"#),
509 ],
510 };
511 let mut buffer = Cursor::new(vec![0u8; 1024]);
512 let mut encoder = Encoder::new(&mut buffer, EncoderOpts::default());
513 encoder.write_record(record).unwrap();
514
515 let len = buffer.position() as usize;
516 let mut bytes = buffer.into_inner();
517 bytes.truncate(len);
518
519 let res = parser.parse_next(&bytes, &formatter).unwrap();
520 assert!(res.0.is_some());
521 let log_message = &res.0.unwrap();
522 assert_eq!(&*log_message.message, "hello world");
523 assert_eq!(log_message.kvps.len, 1);
524 assert_eq!(&*log_message.kvps[0].key, "key");
525 assert!(matches!(
526 &log_message.kvps[0].value,
527 CppValue::Text(val) if &**val == r#"val"with\escapes"#
528 ));
529 }
530
531 #[fuchsia::test]
532 fn test_out_of_bounds_extended_record() {
533 let allocator = Bump::new();
534
535 let record = Record {
536 timestamp: BootInstant::from_nanos(72),
537 severity: 0x30,
538 arguments: vec![Argument::message("hello world")],
539 };
540 let mut buffer = Cursor::new(vec![0u8; 1024]);
541 let mut encoder = Encoder::new(&mut buffer, EncoderOpts::default());
542 encoder.write_record(record).unwrap();
543 let len = buffer.position() as usize;
544 let mut bytes = buffer.into_inner();
545 bytes.truncate(len);
546
547 let extended_metadata_suffix = [
549 0xE8, 0x03, 0x00, 0x00, 0xE8, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
553 bytes.extend_from_slice(&extended_metadata_suffix);
554
555 let res = ffi_from_extended_record(&bytes, &allocator);
556 assert!(matches!(res, Err(MessageError::OutOfBounds)));
557 }
558
559 #[fuchsia::test]
560 fn test_control_message_tags() {
561 let allocator = Bump::new();
562 let formatter = CPPMessageFormatter(&allocator);
563 let mut parser = MessageParser::default();
564
565 let tag_id = 0;
566
567 let control_record = Record {
568 timestamp: BootInstant::from_nanos(72),
569 severity: 0x30,
570 arguments: vec![
571 Argument::new("moniker", "test/moniker"),
572 Argument::new("url", "fuchsia-pkg://test"),
573 ],
574 };
575
576 let mut buffer = Cursor::new(vec![0u8; 1024]);
577 let mut encoder = Encoder::new(&mut buffer, EncoderOpts::default());
578 encoder.write_record(control_record).unwrap();
579
580 let len = buffer.position() as usize;
581 let mut bytes = buffer.into_inner();
582 bytes.truncate(len);
583
584 overwrite_header_tag(&mut bytes, LOG_CONTROL_BIT);
585
586 let (log, _) = parser.parse_next(&bytes, &formatter).unwrap();
587 assert!(log.is_none());
588
589 let tag_data = parser.tag_map.get(&tag_id).unwrap();
590 assert_eq!(tag_data.moniker, ExtendedMoniker::parse_str("test/moniker").unwrap());
591 assert_eq!(tag_data.url, "fuchsia-pkg://test");
592
593 let rolled_out_record = Record {
594 timestamp: BootInstant::from_nanos(73),
595 severity: 0x30,
596 arguments: vec![Argument::new("rolled_out", 5u64)],
597 };
598
599 let mut buffer2 = Cursor::new(vec![0u8; 1024]);
600 let mut encoder2 = Encoder::new(&mut buffer2, EncoderOpts::default());
601 encoder2.write_record(rolled_out_record).unwrap();
602
603 let len2 = buffer2.position() as usize;
604 let mut bytes2 = buffer2.into_inner();
605 bytes2.truncate(len2);
606
607 overwrite_header_tag(&mut bytes2, LOG_CONTROL_BIT);
608
609 let (log2, _) = parser.parse_next(&bytes2, &formatter).unwrap();
610 assert!(log2.is_some());
611
612 let log2_msg = log2.unwrap();
613 assert_eq!(&*log2_msg.message, "");
614 assert_eq!(log2_msg.kvps.len, 1);
615 assert_eq!(&*log2_msg.kvps[0].key, "rolled_out");
616 assert!(matches!(log2_msg.kvps[0].value, CppValue::UnsignedInt(5)));
617 let tag_data2 = parser.tag_map.get(&tag_id).unwrap();
618 assert_eq!(tag_data2.moniker, ExtendedMoniker::parse_str("test/moniker").unwrap());
619
620 let normal_record = Record {
621 timestamp: BootInstant::from_nanos(74),
622 severity: 0x30,
623 arguments: vec![Argument::message("some log with tag")],
624 };
625
626 let mut buffer3 = Cursor::new(vec![0u8; 1024]);
627 let mut encoder3 = Encoder::new(&mut buffer3, EncoderOpts::default());
628 encoder3.write_record(normal_record).unwrap();
629
630 let len3 = buffer3.position() as usize;
631 let mut bytes3 = buffer3.into_inner();
632 bytes3.truncate(len3);
633
634 overwrite_header_tag(&mut bytes3, tag_id);
635
636 let (log3, _) = parser.parse_next(&bytes3, &formatter).unwrap();
637
638 let log_msg3 = &log3.unwrap();
639 assert_eq!(&*log_msg3.message, "some log with tag");
640 assert_eq!(log_msg3.tags.len(), 0);
641 assert_eq!(&*log_msg3.moniker_tag, "moniker");
642 }
643
644 #[fuchsia::test]
645 fn test_message_with_kvps() {
646 let mut parser = MessageParser::default();
647 let allocator = Bump::new();
648 let formatter = CPPMessageFormatter(&allocator);
649
650 let record = Record {
651 timestamp: BootInstant::from_nanos(100),
652 severity: 0x10,
653 arguments: vec![
654 Argument::message("A message"),
655 Argument::new("key1", "value1"),
656 Argument::new("key2", 123u64),
657 ],
658 };
659 let mut buffer = Cursor::new(vec![0u8; 1024]);
660 let mut encoder = Encoder::new(&mut buffer, EncoderOpts::default());
661 encoder.write_record(record).unwrap();
662
663 let len = buffer.position() as usize;
664 let mut bytes = buffer.into_inner();
665 bytes.truncate(len);
666
667 let res = parser.parse_next(&bytes, &formatter).unwrap();
668 assert!(res.0.is_some());
669 let log_message = res.0.unwrap();
670 assert_eq!(&*log_message.message, "A message");
671 assert_eq!(log_message.kvps.len, 2);
672 assert_eq!(&*log_message.kvps[0].key, "key1");
673 assert!(matches!(
674 &log_message.kvps[0].value,
675 CppValue::Text(val) if &**val == "value1"
676 ));
677 assert_eq!(&*log_message.kvps[1].key, "key2");
678 assert!(matches!(&log_message.kvps[1].value, CppValue::UnsignedInt(123)));
679 }
680
681 #[fuchsia::test]
682 fn test_file_line_message_with_kvps() {
683 let mut parser = MessageParser::default();
684 let allocator = Bump::new();
685 let formatter = CPPMessageFormatter(&allocator);
686
687 let record = Record {
688 timestamp: BootInstant::from_nanos(100),
689 severity: 0x10,
690 arguments: vec![
691 Argument::file("src/file.rs"),
692 Argument::line(42),
693 Argument::message("Another message"),
694 Argument::new("temp", 30.5),
695 Argument::new("valid", true),
696 ],
697 };
698 let mut buffer = Cursor::new(vec![0u8; 1024]);
699 let mut encoder = Encoder::new(&mut buffer, EncoderOpts::default());
700 encoder.write_record(record).unwrap();
701
702 let len = buffer.position() as usize;
703 let mut bytes = buffer.into_inner();
704 bytes.truncate(len);
705
706 let res = parser.parse_next(&bytes, &formatter).unwrap();
707 assert!(res.0.is_some());
708 let log_message = res.0.unwrap();
709 assert_eq!(&*log_message.file, "src/file.rs");
710 assert_eq!(log_message.line, 42);
711 assert_eq!(&*log_message.message, "Another message");
712 assert_eq!(log_message.kvps.len, 2);
713 assert_eq!(&*log_message.kvps[0].key, "temp");
714 assert!(matches!(
715 &log_message.kvps[0].value,
716 CppValue::Floating(val) if *val == 30.5
717 ));
718 assert_eq!(&*log_message.kvps[1].key, "valid");
719 assert!(matches!(&log_message.kvps[1].value, CppValue::Boolean(true)));
720 }
721
722 #[fuchsia::test]
723 fn test_only_kvps() {
724 let mut parser = MessageParser::default();
725 let allocator = Bump::new();
726 let formatter = CPPMessageFormatter(&allocator);
727
728 let record = Record {
729 timestamp: BootInstant::from_nanos(100),
730 severity: 0x10,
731 arguments: vec![Argument::new("status", "ok"), Argument::new("code", 200i64)],
732 };
733 let mut buffer = Cursor::new(vec![0u8; 1024]);
734 let mut encoder = Encoder::new(&mut buffer, EncoderOpts::default());
735 encoder.write_record(record).unwrap();
736
737 let len = buffer.position() as usize;
738 let mut bytes = buffer.into_inner();
739 bytes.truncate(len);
740
741 let res = parser.parse_next(&bytes, &formatter).unwrap();
742 assert!(res.0.is_some());
743 let log_message = res.0.unwrap();
744 assert_eq!(&*log_message.message, "");
745 assert_eq!(log_message.kvps.len, 2);
746 assert_eq!(&*log_message.kvps[0].key, "status");
747 assert!(matches!(
748 &log_message.kvps[0].value,
749 CppValue::Text(val) if &**val == "ok"
750 ));
751 assert_eq!(&*log_message.kvps[1].key, "code");
752 assert!(matches!(&log_message.kvps[1].value, CppValue::SignedInt(200)));
753 }
754
755 #[fuchsia::test]
756 fn test_zero_copy_string_borrowing() {
757 let mut parser = MessageParser::default();
758 let allocator = Bump::new();
759 let formatter = CPPMessageFormatter(&allocator);
760
761 let record = Record {
762 timestamp: BootInstant::from_nanos(100),
763 severity: 0x30,
764 arguments: vec![
765 Argument::File("src/lib.rs".into()),
766 Argument::Line(10),
767 Argument::Message("hello zero-copy".into()),
768 ],
769 };
770 let mut buffer = Cursor::new(vec![0u8; 1024]);
771 let mut encoder = Encoder::new(&mut buffer, EncoderOpts::default());
772 encoder.write_record(record).unwrap();
773
774 let len = buffer.position() as usize;
775 let mut bytes = buffer.into_inner();
776 bytes.truncate(len);
777
778 let (log_message, _) = parser.parse_next(&bytes, &formatter).unwrap();
779 let log_message = log_message.unwrap();
780
781 let bytes_start = bytes.as_ptr() as usize;
782 let bytes_end = bytes_start + bytes.len();
783
784 let msg_ptr = log_message.message.inner.ptr as usize;
785 assert!(
786 bytes_start <= msg_ptr && msg_ptr < bytes_end,
787 "message pointer must borrow directly from incoming record buffer"
788 );
789
790 let file_ptr = log_message.file.inner.ptr as usize;
791 assert!(
792 bytes_start <= file_ptr && file_ptr < bytes_end,
793 "file pointer must borrow directly from incoming record buffer"
794 );
795 }
796
797 #[fuchsia::test]
798 fn test_moniker_tag() {
799 let allocator = Bump::new();
800 let builder = CPPLogMessageBuilder {
801 severity: 0x30,
802 tags: BumpaloVec::new_in(&allocator),
803 pid: None,
804 tid: None,
805 dropped: 0,
806 file: None,
807 line: None,
808 moniker: Some(BumpaloString::from_str_in("core/foo", &allocator)),
809 message: None,
810 timestamp: 0,
811 kvps: BumpaloVec::new_in(&allocator),
812 allocator: &allocator,
813 };
814 let msg = builder.build();
815 assert_eq!(&*msg.moniker_tag, "foo");
816
817 let root_builder = CPPLogMessageBuilder {
818 severity: 0x30,
819 tags: BumpaloVec::new_in(&allocator),
820 pid: None,
821 tid: None,
822 dropped: 0,
823 file: None,
824 line: None,
825 moniker: Some(BumpaloString::from_str_in(".", &allocator)),
826 message: None,
827 timestamp: 0,
828 kvps: BumpaloVec::new_in(&allocator),
829 allocator: &allocator,
830 };
831 let root_msg = root_builder.build();
832 assert_eq!(&*root_msg.moniker_tag, "");
833 }
834
835 #[fuchsia::test]
836 fn test_cpp_array_and_string_deref_handles_null_and_zero_len() {
837 use std::ops::Deref;
838
839 let empty_array: CppArray<'_, u32> =
841 CppArray { len: 0, ptr: std::ptr::null(), phantom: PhantomData };
842 assert_eq!(empty_array.deref(), &[] as &[u32]);
843
844 let zero_len_array: CppArray<'_, u32> =
845 CppArray { len: 0, ptr: 0x1234 as *const u32, phantom: PhantomData };
846 assert_eq!(zero_len_array.deref(), &[] as &[u32]);
847
848 let null_ptr_array: CppArray<'_, u32> =
849 CppArray { len: 5, ptr: std::ptr::null(), phantom: PhantomData };
850 assert_eq!(null_ptr_array.deref(), &[] as &[u32]);
851
852 let empty_string = CppString::default();
854 assert_eq!(empty_string.deref(), "");
855
856 let zero_len_string = CppString {
857 inner: CppArray { len: 0, ptr: 0x1234 as *const u8, phantom: PhantomData },
858 };
859 assert_eq!(zero_len_string.deref(), "");
860
861 let null_ptr_string =
862 CppString { inner: CppArray { len: 5, ptr: std::ptr::null(), phantom: PhantomData } };
863 assert_eq!(null_ptr_string.deref(), "");
864 }
865}