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