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::fmt::Write;
14use std::marker::PhantomData;
15use std::ops::Deref;
16use std::str;
17use zx::BootInstant;
18
19pub use crate::constants::*;
20
21#[repr(C)]
25pub struct CppArray<'a, T> {
26 pub len: usize,
28 pub ptr: *const T,
33
34 phantom: PhantomData<&'a T>,
35}
36
37impl<T> Deref for CppArray<'_, T> {
38 type Target = [T];
39
40 fn deref(&self) -> &Self::Target {
41 if self.len == 0 || self.ptr.is_null() {
42 &[]
43 } else {
44 unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
47 }
48 }
49}
50
51impl<T> Default for CppArray<'_, T> {
52 fn default() -> Self {
53 CppArray { len: 0, ptr: std::ptr::null(), phantom: PhantomData }
54 }
55}
56
57impl<'a> From<&'a str> for CppString<'a> {
58 fn from(value: &'a str) -> Self {
59 Self { inner: CppArray { len: value.len(), ptr: value.as_ptr(), phantom: PhantomData } }
60 }
61}
62
63#[derive(Default)]
69#[repr(transparent)]
70pub struct CppString<'a> {
71 inner: CppArray<'a, u8>,
72}
73
74impl Deref for CppString<'_> {
75 type Target = str;
76
77 fn deref(&self) -> &Self::Target {
78 if self.inner.len == 0 || self.inner.ptr.is_null() {
79 ""
80 } else {
81 unsafe {
83 std::str::from_utf8_unchecked(std::slice::from_raw_parts(
84 self.inner.ptr,
85 self.inner.len,
86 ))
87 }
88 }
89 }
90}
91
92impl<'a> From<Option<&'a str>> for CppString<'a> {
93 fn from(value: Option<&'a str>) -> Self {
94 value.map(|v| v.into()).unwrap_or_default()
95 }
96}
97
98impl<'a, T> From<&'a [T]> for CppArray<'a, T> {
99 fn from(value: &'a [T]) -> Self {
100 CppArray { len: value.len(), ptr: value.as_ptr(), phantom: PhantomData }
101 }
102}
103
104impl<'a> From<BumpaloString<'a>> for CppString<'a> {
105 fn from(value: BumpaloString<'a>) -> Self {
106 value.into_bump_str().into()
107 }
108}
109
110#[repr(C)]
112pub struct LogMessage<'a> {
113 pub severity: u8,
115 pub tags: CppArray<'a, CppString<'a>>,
117 pub pid: u64,
119 pub tid: u64,
121 pub dropped: u64,
123 pub message: CppString<'a>,
125 pub timestamp: i64,
128}
129
130const_assert!(!std::mem::needs_drop::<LogMessage<'_>>());
132
133pub struct CPPLogMessageBuilder<'a> {
134 severity: u8,
135 tags: BumpaloVec<'a, BumpaloString<'a>>,
136 pid: Option<u64>,
137 tid: Option<u64>,
138 dropped: u64,
139 file: Option<String>,
140 line: Option<u64>,
141 moniker: Option<BumpaloString<'a>>,
142 message: Option<String>,
143 timestamp: i64,
144 kvps: String,
145 allocator: &'a Bump,
146}
147
148fn escape_quotes(input: &str, output: &mut String) {
150 for ch in input.chars() {
151 if ch == '"' || ch == '\\' {
152 output.push('\\');
153 }
154 output.push(ch);
155 }
156}
157
158impl<'a> CPPLogMessageBuilder<'a> {
159 fn set_raw_severity(mut self, raw_severity: u8) -> Self {
160 self.severity = raw_severity;
161 self
162 }
163
164 fn add_tag(mut self, tag: impl Into<String>) -> Self {
165 self.tags.push(BumpaloString::from_str_in(&tag.into(), self.allocator));
166 self
167 }
168
169 fn set_pid(mut self, pid: u64) -> Self {
170 self.pid = Some(pid);
171 self
172 }
173
174 fn set_tid(mut self, tid: u64) -> Self {
175 self.tid = Some(tid);
176 self
177 }
178
179 fn set_dropped(mut self, dropped: u64) -> Self {
180 self.dropped = dropped;
181 self
182 }
183
184 fn set_file(mut self, file: impl Into<String>) -> Self {
185 self.file = Some(file.into());
186 self
187 }
188
189 fn set_line(mut self, line: u64) -> Self {
190 self.line = Some(line);
191 self
192 }
193
194 fn set_message(mut self, msg: impl Into<String>) -> Self {
195 self.message = Some(msg.into());
196 self
197 }
198
199 fn add_kvp(mut self, kvp: &Argument<'_>) -> Self {
200 if !self.kvps.is_empty() {
201 self.kvps.push(' ');
202 }
203
204 self.kvps.push_str(kvp.name());
205 self.kvps.push('=');
206 match kvp.value() {
207 Value::Text(value) => {
208 self.kvps.push('"');
209 escape_quotes(&value, &mut self.kvps);
210 self.kvps.push('"');
211 }
212 Value::SignedInt(value) => {
213 write!(self.kvps, "{value}").unwrap();
214 }
215 Value::UnsignedInt(value) => {
216 write!(self.kvps, "{value}").unwrap();
217 }
218 Value::Floating(value) => {
219 write!(self.kvps, "{value}").unwrap();
220 }
221 Value::Boolean(value) => {
222 if value {
223 write!(self.kvps, "true").unwrap();
224 } else {
225 write!(self.kvps, "false").unwrap();
226 }
227 }
228 }
229 self
230 }
231
232 fn set_moniker(mut self, value: &str) -> Self {
233 self.moniker = Some(BumpaloString::from_str_in(value, self.allocator));
234 self
235 }
236
237 pub fn build(mut self) -> &'a mut LogMessage<'a> {
238 let allocator = self.allocator;
239
240 let msg_str = self
242 .message
243 .as_ref()
244 .map(|value| bumpalo::format!(in &allocator,"{value}",))
245 .unwrap_or_else(|| BumpaloString::new_in(allocator));
246
247 let mut output = match (&self.file, &self.line) {
248 (Some(file), Some(line)) => {
249 let mut value = bumpalo::format!(in &allocator, "[{file}({line})]",);
250 if !msg_str.is_empty() {
251 value.push(' ');
252 }
253 value
254 }
255 _ => BumpaloString::new_in(allocator),
256 };
257
258 output.push_str(&msg_str);
259 if !msg_str.is_empty() && !self.kvps.is_empty() {
260 output.push(' ');
261 }
262 output.push_str(&self.kvps);
263
264 if let Some(moniker) = &self.moniker {
265 let component_name = moniker.split("/").last();
266 if let Some(component_name) = component_name
267 && !self.tags.iter().any(|value| value.as_str() == component_name)
268 {
269 self.tags.insert(0, bumpalo::format!(in &allocator, "{}", component_name));
270 }
271 }
272
273 let tags: &[_] = self
274 .allocator
275 .alloc_slice_fill_iter(self.tags.drain(..).map(|s| s.into_bump_str().into()));
276
277 allocator.alloc(LogMessage {
278 severity: self.severity,
279 dropped: self.dropped,
280 tags: tags.into(),
281 pid: self.pid.unwrap_or(0),
282 tid: self.tid.unwrap_or(0),
283 message: output.into_bump_str().into(),
284 timestamp: self.timestamp,
285 })
286 }
287}
288
289struct CPPLogMessageBuilderBuilder<'a>(&'a Bump);
290
291impl<'a> CPPLogMessageBuilderBuilder<'a> {
292 fn configure(
293 self,
294 _component_url: Option<FlyStr>,
295 moniker: Option<ExtendedMoniker>,
296 severity: Severity,
297 timestamp: BootInstant,
298 ) -> Result<CPPLogMessageBuilder<'a>, MessageError> {
299 Ok(CPPLogMessageBuilder {
300 severity: severity as u8,
301 tags: BumpaloVec::new_in(self.0),
302 pid: None,
303 tid: None,
304 dropped: 0,
305 file: None,
306 timestamp: timestamp.into_nanos(),
307 line: None,
308 allocator: self.0,
309 kvps: String::new(),
310 moniker: moniker.map(|value| bumpalo::format!(in self.0,"{}", value)),
311 message: None,
312 })
313 }
314}
315
316pub fn build_logs_data<'a>(
317 input: &Record<'_>,
318 source: Option<ExtendedMetadata>,
319 allocator: &'a Bump,
320) -> Result<&'a mut LogMessage<'a>, MessageError> {
321 let builder = CPPLogMessageBuilderBuilder(allocator);
322 let (raw_severity, severity) = Severity::parse_exact(input.severity);
323 let (maybe_moniker, maybe_url, _) = source
324 .map(|value| (Some(value.moniker), Some(value.url), Some(value.rolled_out_logs)))
325 .unwrap_or((None, None, None));
326 let mut builder =
327 builder.configure(maybe_url.map(FlyStr::new), None, severity, input.timestamp)?;
328 if let Some(moniker) = maybe_moniker {
329 builder = builder.set_moniker(moniker.as_ref());
330 }
331 if let Some(raw_severity) = raw_severity {
332 builder = builder.set_raw_severity(raw_severity);
333 }
334
335 for argument in input.arguments.iter() {
336 match argument {
337 Argument::Tag(tag) => {
338 builder = builder.add_tag(tag.as_ref());
339 }
340 Argument::Pid(pid) => {
341 builder = builder.set_pid(pid.raw_koid());
342 }
343 Argument::Tid(tid) => {
344 builder = builder.set_tid(tid.raw_koid());
345 }
346 Argument::Dropped(dropped) => {
347 builder = builder.set_dropped(*dropped);
348 }
349 Argument::File(file) => {
350 builder = builder.set_file(file.as_ref());
351 }
352 Argument::Line(line) => {
353 builder = builder.set_line(*line);
354 }
355 Argument::Message(msg) => {
356 builder = builder.set_message(msg.as_ref());
357 }
358 Argument::Other { value: _, name: _ } => builder = builder.add_kvp(argument),
359 }
360 }
361
362 Ok(builder.build())
363}
364
365pub fn ffi_from_extended_record<'a, 'b>(
371 bytes: &'a [u8],
372 allocator: &'b Bump,
373) -> Result<(&'b mut LogMessage<'b>, &'a [u8]), MessageError> {
374 let (input, remaining) = diagnostics_log_encoding::parse::parse_record(bytes)?;
375 let (source, new_remaining) = if remaining.len() >= 16 {
376 let moniker_len = u32::from_le_bytes(remaining[0..4].try_into().unwrap()) as usize;
377 let component_url_len = u32::from_le_bytes(remaining[4..8].try_into().unwrap()) as usize;
378 let rolled_out_logs = u64::from_le_bytes(remaining[8..16].try_into().unwrap());
379 let mut offset: usize = 16;
380
381 let moniker_padded_len = (moniker_len + 7) & !7;
384 let component_url_padded_len = (component_url_len + 7) & !7;
385 let moniker_padded_end = offset + moniker_padded_len;
386 let url_padded_end = moniker_padded_end + component_url_padded_len;
387 if url_padded_end > remaining.len() {
388 return Err(MessageError::OutOfBounds);
389 }
390
391 let moniker = str::from_utf8(&remaining[offset..offset + moniker_len])?;
392 offset += moniker_padded_len;
393 let url = str::from_utf8(&remaining[offset..offset + component_url_len])?;
394 offset += component_url_padded_len;
395 (
396 Some(ExtendedMetadata {
397 moniker: ExtendedMoniker::parse_str(moniker)?,
398 url: url.into(),
399 rolled_out_logs,
400 }),
401 &remaining[offset..],
402 )
403 } else {
404 (None, remaining)
405 };
406 let record = build_logs_data(&input, source, allocator)?;
407 Ok((record, new_remaining))
408}
409
410pub struct CPPMessageFormatter<'a>(pub &'a Bump);
411impl<'a> MessageFormatter for &CPPMessageFormatter<'a> {
412 type Result = &'a mut LogMessage<'a>;
413
414 fn format(
415 &mut self,
416 record: &Record<'_>,
417 metadata: Option<ExtendedMetadata>,
418 ) -> Result<Self::Result, MessageError> {
419 build_logs_data(record, metadata, self.0)
420 }
421}
422
423#[cfg(test)]
424mod test {
425 use super::*;
426 use crate::MessageParser;
427 use bumpalo::Bump;
428 use diagnostics_log_encoding::encode::{Encoder, EncoderOpts};
429 use diagnostics_log_encoding::{Argument, Header, LOG_CONTROL_BIT, Record};
430 use std::io::Cursor;
431 use zx::BootInstant;
432
433 fn overwrite_header_tag(bytes: &mut [u8], tag: u32) {
434 if bytes.len() >= 8 {
435 let mut header = Header(u64::from_le_bytes(bytes[0..8].try_into().unwrap()));
436 header.set_tag(tag);
437 bytes[0..8].copy_from_slice(&header.0.to_le_bytes());
438 }
439 }
440
441 #[fuchsia::test]
442 fn test_short_read() {
443 let mut parser = MessageParser::default();
444 let allocator = Bump::new();
445 let formatter = CPPMessageFormatter(&allocator);
446 let bytes = vec![0u8; 7];
447 let res = parser.parse_next(&bytes, &formatter);
448 assert!(matches!(res, Err(MessageError::ShortRead { len: 7 })));
449 }
450
451 #[fuchsia::test]
452 fn test_normal_parsing() {
453 let mut parser = MessageParser::default();
454 let allocator = Bump::new();
455 let formatter = CPPMessageFormatter(&allocator);
456
457 let record = Record {
458 timestamp: BootInstant::from_nanos(72),
459 severity: 0x30,
460 arguments: vec![Argument::message("hello world")],
461 };
462 let mut buffer = Cursor::new(vec![0u8; 1024]);
463 let mut encoder = Encoder::new(&mut buffer, EncoderOpts::default());
464 encoder.write_record(record).unwrap();
465
466 let len = buffer.position() as usize;
467 let mut bytes = buffer.into_inner();
468 bytes.truncate(len);
469
470 let res = parser.parse_next(&bytes, &formatter).unwrap();
471 assert!(res.0.is_some());
472 let log_message = &res.0.unwrap();
473 assert_eq!(&*log_message.message, "hello world");
474 assert_eq!(log_message.timestamp, 72);
475 assert_eq!(log_message.severity, 0x30);
476 }
477
478 #[fuchsia::test]
479 fn test_escaping_in_kvp() {
480 let mut parser = MessageParser::default();
481 let allocator = Bump::new();
482 let formatter = CPPMessageFormatter(&allocator);
483
484 let record = Record {
485 timestamp: BootInstant::from_nanos(72),
486 severity: 0x30,
487 arguments: vec![
488 Argument::message("hello world"),
489 Argument::new("key", r#"val"with\escapes"#),
490 ],
491 };
492 let mut buffer = Cursor::new(vec![0u8; 1024]);
493 let mut encoder = Encoder::new(&mut buffer, EncoderOpts::default());
494 encoder.write_record(record).unwrap();
495
496 let len = buffer.position() as usize;
497 let mut bytes = buffer.into_inner();
498 bytes.truncate(len);
499
500 let res = parser.parse_next(&bytes, &formatter).unwrap();
501 assert!(res.0.is_some());
502 let log_message = &res.0.unwrap();
503 assert_eq!(&*log_message.message, r#"hello world key="val\"with\\escapes""#);
504 }
505
506 #[fuchsia::test]
507 fn test_out_of_bounds_extended_record() {
508 let allocator = Bump::new();
509
510 let record = Record {
511 timestamp: BootInstant::from_nanos(72),
512 severity: 0x30,
513 arguments: vec![Argument::message("hello world")],
514 };
515 let mut buffer = Cursor::new(vec![0u8; 1024]);
516 let mut encoder = Encoder::new(&mut buffer, EncoderOpts::default());
517 encoder.write_record(record).unwrap();
518 let len = buffer.position() as usize;
519 let mut bytes = buffer.into_inner();
520 bytes.truncate(len);
521
522 let extended_metadata_suffix = [
524 0xE8, 0x03, 0x00, 0x00, 0xE8, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
528 bytes.extend_from_slice(&extended_metadata_suffix);
529
530 let res = ffi_from_extended_record(&bytes, &allocator);
531 assert!(matches!(res, Err(MessageError::OutOfBounds)));
532 }
533
534 #[fuchsia::test]
535 fn test_control_message_tags() {
536 let allocator = Bump::new();
537 let formatter = CPPMessageFormatter(&allocator);
538 let mut parser = MessageParser::default();
539
540 let tag_id = 0;
541
542 let control_record = Record {
543 timestamp: BootInstant::from_nanos(72),
544 severity: 0x30,
545 arguments: vec![
546 Argument::new("moniker", "test/moniker"),
547 Argument::new("url", "fuchsia-pkg://test"),
548 ],
549 };
550
551 let mut buffer = Cursor::new(vec![0u8; 1024]);
552 let mut encoder = Encoder::new(&mut buffer, EncoderOpts::default());
553 encoder.write_record(control_record).unwrap();
554
555 let len = buffer.position() as usize;
556 let mut bytes = buffer.into_inner();
557 bytes.truncate(len);
558
559 overwrite_header_tag(&mut bytes, LOG_CONTROL_BIT);
560
561 let (log, _) = parser.parse_next(&bytes, &formatter).unwrap();
562 assert!(log.is_none());
563
564 let tag_data = parser.tag_map.get(&tag_id).unwrap();
565 assert_eq!(tag_data.moniker, ExtendedMoniker::parse_str("test/moniker").unwrap());
566 assert_eq!(tag_data.url, "fuchsia-pkg://test");
567
568 let rolled_out_record = Record {
569 timestamp: BootInstant::from_nanos(73),
570 severity: 0x30,
571 arguments: vec![Argument::new("rolled_out", 5u64)],
572 };
573
574 let mut buffer2 = Cursor::new(vec![0u8; 1024]);
575 let mut encoder2 = Encoder::new(&mut buffer2, EncoderOpts::default());
576 encoder2.write_record(rolled_out_record).unwrap();
577
578 let len2 = buffer2.position() as usize;
579 let mut bytes2 = buffer2.into_inner();
580 bytes2.truncate(len2);
581
582 overwrite_header_tag(&mut bytes2, LOG_CONTROL_BIT);
583
584 let (log2, _) = parser.parse_next(&bytes2, &formatter).unwrap();
585 assert!(log2.is_some());
586
587 let tag_data2 = parser.tag_map.get(&tag_id).unwrap();
588 assert_eq!(&*log2.unwrap().message, "rolled_out=5");
589 assert_eq!(tag_data2.moniker, ExtendedMoniker::parse_str("test/moniker").unwrap());
590
591 let normal_record = Record {
592 timestamp: BootInstant::from_nanos(74),
593 severity: 0x30,
594 arguments: vec![Argument::message("some log with tag")],
595 };
596
597 let mut buffer3 = Cursor::new(vec![0u8; 1024]);
598 let mut encoder3 = Encoder::new(&mut buffer3, EncoderOpts::default());
599 encoder3.write_record(normal_record).unwrap();
600
601 let len3 = buffer3.position() as usize;
602 let mut bytes3 = buffer3.into_inner();
603 bytes3.truncate(len3);
604
605 overwrite_header_tag(&mut bytes3, tag_id);
606
607 let (log3, _) = parser.parse_next(&bytes3, &formatter).unwrap();
608
609 let log_msg3 = &log3.unwrap();
610 assert_eq!(&*log_msg3.message, "some log with tag");
611 let tags = &*log_msg3.tags;
612 assert_eq!(tags.len(), 1);
613 assert_eq!(&*tags[0], "moniker");
614 }
615
616 #[fuchsia::test]
617 fn test_message_with_kvps() {
618 let mut parser = MessageParser::default();
619 let allocator = Bump::new();
620 let formatter = CPPMessageFormatter(&allocator);
621
622 let record = Record {
623 timestamp: BootInstant::from_nanos(100),
624 severity: 0x10,
625 arguments: vec![
626 Argument::message("A message"),
627 Argument::new("key1", "value1"),
628 Argument::new("key2", 123u64),
629 ],
630 };
631 let mut buffer = Cursor::new(vec![0u8; 1024]);
632 let mut encoder = Encoder::new(&mut buffer, EncoderOpts::default());
633 encoder.write_record(record).unwrap();
634
635 let len = buffer.position() as usize;
636 let mut bytes = buffer.into_inner();
637 bytes.truncate(len);
638
639 let res = parser.parse_next(&bytes, &formatter).unwrap();
640 assert!(res.0.is_some());
641 let log_message = res.0.unwrap();
642 assert_eq!(&*log_message.message, "A message key1=\"value1\" key2=123");
643 }
644
645 #[fuchsia::test]
646 fn test_file_line_message_with_kvps() {
647 let mut parser = MessageParser::default();
648 let allocator = Bump::new();
649 let formatter = CPPMessageFormatter(&allocator);
650
651 let record = Record {
652 timestamp: BootInstant::from_nanos(100),
653 severity: 0x10,
654 arguments: vec![
655 Argument::file("src/file.rs"),
656 Argument::line(42),
657 Argument::message("Another message"),
658 Argument::new("temp", 30.5),
659 Argument::new("valid", true),
660 ],
661 };
662 let mut buffer = Cursor::new(vec![0u8; 1024]);
663 let mut encoder = Encoder::new(&mut buffer, EncoderOpts::default());
664 encoder.write_record(record).unwrap();
665
666 let len = buffer.position() as usize;
667 let mut bytes = buffer.into_inner();
668 bytes.truncate(len);
669
670 let res = parser.parse_next(&bytes, &formatter).unwrap();
671 assert!(res.0.is_some());
672 let log_message = res.0.unwrap();
673 assert_eq!(&*log_message.message, "[src/file.rs(42)] Another message temp=30.5 valid=true");
674 }
675
676 #[fuchsia::test]
677 fn test_only_kvps() {
678 let mut parser = MessageParser::default();
679 let allocator = Bump::new();
680 let formatter = CPPMessageFormatter(&allocator);
681
682 let record = Record {
683 timestamp: BootInstant::from_nanos(100),
684 severity: 0x10,
685 arguments: vec![Argument::new("status", "ok"), Argument::new("code", 200i64)],
686 };
687 let mut buffer = Cursor::new(vec![0u8; 1024]);
688 let mut encoder = Encoder::new(&mut buffer, EncoderOpts::default());
689 encoder.write_record(record).unwrap();
690
691 let len = buffer.position() as usize;
692 let mut bytes = buffer.into_inner();
693 bytes.truncate(len);
694
695 let res = parser.parse_next(&bytes, &formatter).unwrap();
696 assert!(res.0.is_some());
697 let log_message = res.0.unwrap();
698 assert_eq!(&*log_message.message, "status=\"ok\" code=200");
699 }
700
701 #[fuchsia::test]
702 fn test_cpp_array_and_string_deref_handles_null_and_zero_len() {
703 use std::ops::Deref;
704
705 let empty_array: CppArray<'_, u32> =
707 CppArray { len: 0, ptr: std::ptr::null(), phantom: PhantomData };
708 assert_eq!(empty_array.deref(), &[] as &[u32]);
709
710 let zero_len_array: CppArray<'_, u32> =
711 CppArray { len: 0, ptr: 0x1234 as *const u32, phantom: PhantomData };
712 assert_eq!(zero_len_array.deref(), &[] as &[u32]);
713
714 let null_ptr_array: CppArray<'_, u32> =
715 CppArray { len: 5, ptr: std::ptr::null(), phantom: PhantomData };
716 assert_eq!(null_ptr_array.deref(), &[] as &[u32]);
717
718 let empty_string = CppString::default();
720 assert_eq!(empty_string.deref(), "");
721
722 let zero_len_string = CppString {
723 inner: CppArray { len: 0, ptr: 0x1234 as *const u8, phantom: PhantomData },
724 };
725 assert_eq!(zero_len_string.deref(), "");
726
727 let null_ptr_string =
728 CppString { inner: CppArray { len: 5, ptr: std::ptr::null(), phantom: PhantomData } };
729 assert_eq!(null_ptr_string.deref(), "");
730 }
731}