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::{Argument, Record, Value};
11use flyweights::FlyStr;
12use static_assertions::const_assert;
13use std::marker::PhantomData;
14use std::ops::Deref;
15use std::str;
16use zx::BootInstant;
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) => BumpaloString::from_str_in(name, allocator).into_bump_str().into(),
278 None => CppString::default(),
279 },
280 None => CppString::default(),
281 };
282
283 let tags: &[_] = allocator.alloc_slice_fill_iter(self.tags.drain(..));
284 let kvps: &[_] = allocator.alloc_slice_fill_iter(self.kvps.drain(..));
285
286 allocator.alloc(LogMessage {
287 severity: self.severity,
288 dropped: self.dropped,
289 tags: tags.into(),
290 pid: self.pid.unwrap_or(0),
291 tid: self.tid.unwrap_or(0),
292 message,
293 file,
294 line,
295 moniker_tag,
296 kvps: kvps.into(),
297 timestamp: self.timestamp,
298 })
299 }
300}
301
302struct CPPLogMessageBuilderBuilder<'a>(&'a Bump);
303
304impl<'a> CPPLogMessageBuilderBuilder<'a> {
305 fn configure(
306 self,
307 _component_url: Option<FlyStr>,
308 moniker: Option<ExtendedMoniker>,
309 severity: Severity,
310 timestamp: BootInstant,
311 ) -> Result<CPPLogMessageBuilder<'a>, MessageError> {
312 Ok(CPPLogMessageBuilder {
313 severity: severity as u8,
314 tags: BumpaloVec::new_in(self.0),
315 pid: None,
316 tid: None,
317 dropped: 0,
318 file: None,
319 timestamp: timestamp.into_nanos(),
320 line: None,
321 allocator: self.0,
322 kvps: BumpaloVec::new_in(self.0),
323 moniker: moniker.map(|value| bumpalo::format!(in self.0,"{}", value)),
324 message: None,
325 })
326 }
327}
328
329pub fn build_logs_data<'a>(
330 input: &Record<'a>,
331 source: Option<ExtendedMetadata>,
332 allocator: &'a Bump,
333) -> Result<&'a mut LogMessage<'a>, MessageError> {
334 let builder = CPPLogMessageBuilderBuilder(allocator);
335 let (raw_severity, severity) = Severity::parse_exact(input.severity);
336 let (maybe_moniker, maybe_url, _) = source
337 .map(|value| (Some(value.moniker), Some(value.url), Some(value.rolled_out_logs)))
338 .unwrap_or((None, None, None));
339 let mut builder =
340 builder.configure(maybe_url.map(FlyStr::new), None, severity, input.timestamp)?;
341 if let Some(moniker) = maybe_moniker {
342 builder = builder.set_moniker(moniker.as_ref());
343 }
344 if let Some(raw_severity) = raw_severity {
345 builder = builder.set_raw_severity(raw_severity);
346 }
347
348 for argument in input.arguments.iter() {
349 match argument {
350 Argument::Tag(tag) => {
351 builder = builder.add_tag(tag);
352 }
353 Argument::Pid(pid) => {
354 builder = builder.set_pid(pid.raw_koid());
355 }
356 Argument::Tid(tid) => {
357 builder = builder.set_tid(tid.raw_koid());
358 }
359 Argument::Dropped(dropped) => {
360 builder = builder.set_dropped(*dropped);
361 }
362 Argument::File(file) => {
363 builder = builder.set_file(file);
364 }
365 Argument::Line(line) => {
366 builder = builder.set_line(*line);
367 }
368 Argument::Message(msg) => {
369 builder = builder.set_message(msg);
370 }
371 Argument::Other { .. } => builder = builder.add_kvp(argument),
372 }
373 }
374
375 Ok(builder.build())
376}
377
378pub fn ffi_from_extended_record<'a>(
388 bytes: &'a [u8],
389 allocator: &'a Bump,
390) -> Result<(&'a mut LogMessage<'a>, &'a [u8]), MessageError> {
391 let (input, remaining) = diagnostics_log_encoding::parse::parse_record(bytes)?;
392 let (source, new_remaining) = if remaining.len() >= 16 {
393 let moniker_len = u32::from_le_bytes(remaining[0..4].try_into().unwrap()) as usize;
394 let component_url_len = u32::from_le_bytes(remaining[4..8].try_into().unwrap()) as usize;
395 let rolled_out_logs = u64::from_le_bytes(remaining[8..16].try_into().unwrap());
396 let mut offset: usize = 16;
397
398 let moniker_padded_len = (moniker_len + 7) & !7;
401 let component_url_padded_len = (component_url_len + 7) & !7;
402 let moniker_padded_end = offset + moniker_padded_len;
403 let url_padded_end = moniker_padded_end + component_url_padded_len;
404 if url_padded_end > remaining.len() {
405 return Err(MessageError::OutOfBounds);
406 }
407
408 let moniker = str::from_utf8(&remaining[offset..offset + moniker_len])?;
409 offset += moniker_padded_len;
410 let url = str::from_utf8(&remaining[offset..offset + component_url_len])?;
411 offset += component_url_padded_len;
412 (
413 Some(ExtendedMetadata {
414 moniker: ExtendedMoniker::parse_str(moniker)?,
415 url: url.into(),
416 rolled_out_logs,
417 }),
418 &remaining[offset..],
419 )
420 } else {
421 (None, remaining)
422 };
423 let record = build_logs_data(&input, source, allocator)?;
424 Ok((record, new_remaining))
425}
426
427pub struct CPPMessageFormatter<'a>(pub &'a Bump);
428impl<'a> MessageFormatter<'a> for &CPPMessageFormatter<'a> {
429 type Result = &'a mut LogMessage<'a>;
430
431 fn format(
432 &mut self,
433 record: &Record<'a>,
434 metadata: Option<ExtendedMetadata>,
435 ) -> Result<Self::Result, MessageError> {
436 build_logs_data(record, metadata, self.0)
437 }
438}
439
440#[cfg(test)]
441mod test {
442 use super::*;
443 use crate::MessageParser;
444 use bumpalo::Bump;
445 use diagnostics_log_encoding::encode::{Encoder, EncoderOpts};
446 use diagnostics_log_encoding::{Argument, Header, LOG_CONTROL_BIT, Record};
447 use std::io::Cursor;
448 use zx::BootInstant;
449
450 fn overwrite_header_tag(bytes: &mut [u8], tag: u32) {
451 if bytes.len() >= 8 {
452 let mut header = Header(u64::from_le_bytes(bytes[0..8].try_into().unwrap()));
453 header.set_tag(tag);
454 bytes[0..8].copy_from_slice(&header.0.to_le_bytes());
455 }
456 }
457
458 #[fuchsia::test]
459 fn test_short_read() {
460 let mut parser = MessageParser::default();
461 let allocator = Bump::new();
462 let formatter = CPPMessageFormatter(&allocator);
463 let bytes = vec![0u8; 7];
464 let res = parser.parse_next(&bytes, &formatter);
465 assert!(matches!(res, Err(MessageError::ShortRead { len: 7 })));
466 }
467
468 #[fuchsia::test]
469 fn test_normal_parsing() {
470 let mut parser = MessageParser::default();
471 let allocator = Bump::new();
472 let formatter = CPPMessageFormatter(&allocator);
473
474 let record = Record {
475 timestamp: BootInstant::from_nanos(72),
476 severity: 0x30,
477 arguments: vec![Argument::message("hello world")],
478 };
479 let mut buffer = Cursor::new(vec![0u8; 1024]);
480 let mut encoder = Encoder::new(&mut buffer, EncoderOpts::default());
481 encoder.write_record(record).unwrap();
482
483 let len = buffer.position() as usize;
484 let mut bytes = buffer.into_inner();
485 bytes.truncate(len);
486
487 let res = parser.parse_next(&bytes, &formatter).unwrap();
488 assert!(res.0.is_some());
489 let log_message = &res.0.unwrap();
490 assert_eq!(&*log_message.message, "hello world");
491 assert_eq!(log_message.timestamp, 72);
492 assert_eq!(log_message.severity, 0x30);
493 }
494
495 #[fuchsia::test]
496 fn test_escaping_in_kvp() {
497 let mut parser = MessageParser::default();
498 let allocator = Bump::new();
499 let formatter = CPPMessageFormatter(&allocator);
500
501 let record = Record {
502 timestamp: BootInstant::from_nanos(72),
503 severity: 0x30,
504 arguments: vec![
505 Argument::message("hello world"),
506 Argument::new("key", r#"val"with\escapes"#),
507 ],
508 };
509 let mut buffer = Cursor::new(vec![0u8; 1024]);
510 let mut encoder = Encoder::new(&mut buffer, EncoderOpts::default());
511 encoder.write_record(record).unwrap();
512
513 let len = buffer.position() as usize;
514 let mut bytes = buffer.into_inner();
515 bytes.truncate(len);
516
517 let res = parser.parse_next(&bytes, &formatter).unwrap();
518 assert!(res.0.is_some());
519 let log_message = &res.0.unwrap();
520 assert_eq!(&*log_message.message, "hello world");
521 assert_eq!(log_message.kvps.len, 1);
522 assert_eq!(&*log_message.kvps[0].key, "key");
523 assert!(matches!(
524 &log_message.kvps[0].value,
525 CppValue::Text(val) if &**val == r#"val"with\escapes"#
526 ));
527 }
528
529 #[fuchsia::test]
530 fn test_out_of_bounds_extended_record() {
531 let allocator = Bump::new();
532
533 let record = Record {
534 timestamp: BootInstant::from_nanos(72),
535 severity: 0x30,
536 arguments: vec![Argument::message("hello world")],
537 };
538 let mut buffer = Cursor::new(vec![0u8; 1024]);
539 let mut encoder = Encoder::new(&mut buffer, EncoderOpts::default());
540 encoder.write_record(record).unwrap();
541 let len = buffer.position() as usize;
542 let mut bytes = buffer.into_inner();
543 bytes.truncate(len);
544
545 let extended_metadata_suffix = [
547 0xE8, 0x03, 0x00, 0x00, 0xE8, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
551 bytes.extend_from_slice(&extended_metadata_suffix);
552
553 let res = ffi_from_extended_record(&bytes, &allocator);
554 assert!(matches!(res, Err(MessageError::OutOfBounds)));
555 }
556
557 #[fuchsia::test]
558 fn test_control_message_tags() {
559 let allocator = Bump::new();
560 let formatter = CPPMessageFormatter(&allocator);
561 let mut parser = MessageParser::default();
562
563 let tag_id = 0;
564
565 let control_record = Record {
566 timestamp: BootInstant::from_nanos(72),
567 severity: 0x30,
568 arguments: vec![
569 Argument::new("moniker", "test/moniker"),
570 Argument::new("url", "fuchsia-pkg://test"),
571 ],
572 };
573
574 let mut buffer = Cursor::new(vec![0u8; 1024]);
575 let mut encoder = Encoder::new(&mut buffer, EncoderOpts::default());
576 encoder.write_record(control_record).unwrap();
577
578 let len = buffer.position() as usize;
579 let mut bytes = buffer.into_inner();
580 bytes.truncate(len);
581
582 overwrite_header_tag(&mut bytes, LOG_CONTROL_BIT);
583
584 let (log, _) = parser.parse_next(&bytes, &formatter).unwrap();
585 assert!(log.is_none());
586
587 let tag_data = parser.tag_map.get(&tag_id).unwrap();
588 assert_eq!(tag_data.moniker, ExtendedMoniker::parse_str("test/moniker").unwrap());
589 assert_eq!(tag_data.url, "fuchsia-pkg://test");
590
591 let rolled_out_record = Record {
592 timestamp: BootInstant::from_nanos(73),
593 severity: 0x30,
594 arguments: vec![Argument::new("rolled_out", 5u64)],
595 };
596
597 let mut buffer2 = Cursor::new(vec![0u8; 1024]);
598 let mut encoder2 = Encoder::new(&mut buffer2, EncoderOpts::default());
599 encoder2.write_record(rolled_out_record).unwrap();
600
601 let len2 = buffer2.position() as usize;
602 let mut bytes2 = buffer2.into_inner();
603 bytes2.truncate(len2);
604
605 overwrite_header_tag(&mut bytes2, LOG_CONTROL_BIT);
606
607 let (log2, _) = parser.parse_next(&bytes2, &formatter).unwrap();
608 assert!(log2.is_some());
609
610 let log2_msg = log2.unwrap();
611 assert_eq!(&*log2_msg.message, "");
612 assert_eq!(log2_msg.kvps.len, 1);
613 assert_eq!(&*log2_msg.kvps[0].key, "rolled_out");
614 assert!(matches!(log2_msg.kvps[0].value, CppValue::UnsignedInt(5)));
615 let tag_data2 = parser.tag_map.get(&tag_id).unwrap();
616 assert_eq!(tag_data2.moniker, ExtendedMoniker::parse_str("test/moniker").unwrap());
617
618 let normal_record = Record {
619 timestamp: BootInstant::from_nanos(74),
620 severity: 0x30,
621 arguments: vec![Argument::message("some log with tag")],
622 };
623
624 let mut buffer3 = Cursor::new(vec![0u8; 1024]);
625 let mut encoder3 = Encoder::new(&mut buffer3, EncoderOpts::default());
626 encoder3.write_record(normal_record).unwrap();
627
628 let len3 = buffer3.position() as usize;
629 let mut bytes3 = buffer3.into_inner();
630 bytes3.truncate(len3);
631
632 overwrite_header_tag(&mut bytes3, tag_id);
633
634 let (log3, _) = parser.parse_next(&bytes3, &formatter).unwrap();
635
636 let log_msg3 = &log3.unwrap();
637 assert_eq!(&*log_msg3.message, "some log with tag");
638 assert_eq!(log_msg3.tags.len(), 0);
639 assert_eq!(&*log_msg3.moniker_tag, "moniker");
640 }
641
642 #[fuchsia::test]
643 fn test_message_with_kvps() {
644 let mut parser = MessageParser::default();
645 let allocator = Bump::new();
646 let formatter = CPPMessageFormatter(&allocator);
647
648 let record = Record {
649 timestamp: BootInstant::from_nanos(100),
650 severity: 0x10,
651 arguments: vec![
652 Argument::message("A message"),
653 Argument::new("key1", "value1"),
654 Argument::new("key2", 123u64),
655 ],
656 };
657 let mut buffer = Cursor::new(vec![0u8; 1024]);
658 let mut encoder = Encoder::new(&mut buffer, EncoderOpts::default());
659 encoder.write_record(record).unwrap();
660
661 let len = buffer.position() as usize;
662 let mut bytes = buffer.into_inner();
663 bytes.truncate(len);
664
665 let res = parser.parse_next(&bytes, &formatter).unwrap();
666 assert!(res.0.is_some());
667 let log_message = res.0.unwrap();
668 assert_eq!(&*log_message.message, "A message");
669 assert_eq!(log_message.kvps.len, 2);
670 assert_eq!(&*log_message.kvps[0].key, "key1");
671 assert!(matches!(
672 &log_message.kvps[0].value,
673 CppValue::Text(val) if &**val == "value1"
674 ));
675 assert_eq!(&*log_message.kvps[1].key, "key2");
676 assert!(matches!(&log_message.kvps[1].value, CppValue::UnsignedInt(123)));
677 }
678
679 #[fuchsia::test]
680 fn test_file_line_message_with_kvps() {
681 let mut parser = MessageParser::default();
682 let allocator = Bump::new();
683 let formatter = CPPMessageFormatter(&allocator);
684
685 let record = Record {
686 timestamp: BootInstant::from_nanos(100),
687 severity: 0x10,
688 arguments: vec![
689 Argument::file("src/file.rs"),
690 Argument::line(42),
691 Argument::message("Another message"),
692 Argument::new("temp", 30.5),
693 Argument::new("valid", true),
694 ],
695 };
696 let mut buffer = Cursor::new(vec![0u8; 1024]);
697 let mut encoder = Encoder::new(&mut buffer, EncoderOpts::default());
698 encoder.write_record(record).unwrap();
699
700 let len = buffer.position() as usize;
701 let mut bytes = buffer.into_inner();
702 bytes.truncate(len);
703
704 let res = parser.parse_next(&bytes, &formatter).unwrap();
705 assert!(res.0.is_some());
706 let log_message = res.0.unwrap();
707 assert_eq!(&*log_message.file, "src/file.rs");
708 assert_eq!(log_message.line, 42);
709 assert_eq!(&*log_message.message, "Another message");
710 assert_eq!(log_message.kvps.len, 2);
711 assert_eq!(&*log_message.kvps[0].key, "temp");
712 assert!(matches!(
713 &log_message.kvps[0].value,
714 CppValue::Floating(val) if *val == 30.5
715 ));
716 assert_eq!(&*log_message.kvps[1].key, "valid");
717 assert!(matches!(&log_message.kvps[1].value, CppValue::Boolean(true)));
718 }
719
720 #[fuchsia::test]
721 fn test_only_kvps() {
722 let mut parser = MessageParser::default();
723 let allocator = Bump::new();
724 let formatter = CPPMessageFormatter(&allocator);
725
726 let record = Record {
727 timestamp: BootInstant::from_nanos(100),
728 severity: 0x10,
729 arguments: vec![Argument::new("status", "ok"), Argument::new("code", 200i64)],
730 };
731 let mut buffer = Cursor::new(vec![0u8; 1024]);
732 let mut encoder = Encoder::new(&mut buffer, EncoderOpts::default());
733 encoder.write_record(record).unwrap();
734
735 let len = buffer.position() as usize;
736 let mut bytes = buffer.into_inner();
737 bytes.truncate(len);
738
739 let res = parser.parse_next(&bytes, &formatter).unwrap();
740 assert!(res.0.is_some());
741 let log_message = res.0.unwrap();
742 assert_eq!(&*log_message.message, "");
743 assert_eq!(log_message.kvps.len, 2);
744 assert_eq!(&*log_message.kvps[0].key, "status");
745 assert!(matches!(
746 &log_message.kvps[0].value,
747 CppValue::Text(val) if &**val == "ok"
748 ));
749 assert_eq!(&*log_message.kvps[1].key, "code");
750 assert!(matches!(&log_message.kvps[1].value, CppValue::SignedInt(200)));
751 }
752
753 #[fuchsia::test]
754 fn test_zero_copy_string_borrowing() {
755 let mut parser = MessageParser::default();
756 let allocator = Bump::new();
757 let formatter = CPPMessageFormatter(&allocator);
758
759 let record = Record {
760 timestamp: BootInstant::from_nanos(100),
761 severity: 0x30,
762 arguments: vec![
763 Argument::File("src/lib.rs".into()),
764 Argument::Line(10),
765 Argument::Message("hello zero-copy".into()),
766 ],
767 };
768 let mut buffer = Cursor::new(vec![0u8; 1024]);
769 let mut encoder = Encoder::new(&mut buffer, EncoderOpts::default());
770 encoder.write_record(record).unwrap();
771
772 let len = buffer.position() as usize;
773 let mut bytes = buffer.into_inner();
774 bytes.truncate(len);
775
776 let (log_message, _) = parser.parse_next(&bytes, &formatter).unwrap();
777 let log_message = log_message.unwrap();
778
779 let bytes_start = bytes.as_ptr() as usize;
780 let bytes_end = bytes_start + bytes.len();
781
782 let msg_ptr = log_message.message.inner.ptr as usize;
783 assert!(
784 bytes_start <= msg_ptr && msg_ptr < bytes_end,
785 "message pointer must borrow directly from incoming record buffer"
786 );
787
788 let file_ptr = log_message.file.inner.ptr as usize;
789 assert!(
790 bytes_start <= file_ptr && file_ptr < bytes_end,
791 "file pointer must borrow directly from incoming record buffer"
792 );
793 }
794
795 #[fuchsia::test]
796 fn test_moniker_tag() {
797 let allocator = Bump::new();
798 let builder = CPPLogMessageBuilder {
799 severity: 0x30,
800 tags: BumpaloVec::new_in(&allocator),
801 pid: None,
802 tid: None,
803 dropped: 0,
804 file: None,
805 line: None,
806 moniker: Some(BumpaloString::from_str_in("core/foo", &allocator)),
807 message: None,
808 timestamp: 0,
809 kvps: BumpaloVec::new_in(&allocator),
810 allocator: &allocator,
811 };
812 let msg = builder.build();
813 assert_eq!(&*msg.moniker_tag, "foo");
814 }
815
816 #[fuchsia::test]
817 fn test_cpp_array_and_string_deref_handles_null_and_zero_len() {
818 use std::ops::Deref;
819
820 let empty_array: CppArray<'_, u32> =
822 CppArray { len: 0, ptr: std::ptr::null(), phantom: PhantomData };
823 assert_eq!(empty_array.deref(), &[] as &[u32]);
824
825 let zero_len_array: CppArray<'_, u32> =
826 CppArray { len: 0, ptr: 0x1234 as *const u32, phantom: PhantomData };
827 assert_eq!(zero_len_array.deref(), &[] as &[u32]);
828
829 let null_ptr_array: CppArray<'_, u32> =
830 CppArray { len: 5, ptr: std::ptr::null(), phantom: PhantomData };
831 assert_eq!(null_ptr_array.deref(), &[] as &[u32]);
832
833 let empty_string = CppString::default();
835 assert_eq!(empty_string.deref(), "");
836
837 let zero_len_string = CppString {
838 inner: CppArray { len: 0, ptr: 0x1234 as *const u8, phantom: PhantomData },
839 };
840 assert_eq!(zero_len_string.deref(), "");
841
842 let null_ptr_string =
843 CppString { inner: CppArray { len: 5, ptr: std::ptr::null(), phantom: PhantomData } };
844 assert_eq!(null_ptr_string.deref(), "");
845 }
846}