1use crate::error::MessageError;
6use byteorder::{ByteOrder, LittleEndian};
7use diagnostics_data::{
8 BuilderArgs, Data, ExtendedMoniker, Logs, LogsData, LogsDataBuilder, LogsField, LogsProperty,
9 Severity,
10};
11use diagnostics_log_encoding::{
12 ARCHIVIST_URL, Argument, Header, LOG_CONTROL_BIT, MONIKER, ROLLED_OUT, Record, URL, Value,
13};
14use flyweights::FlyStr;
15use libc::{c_char, c_int};
16use moniker::Moniker;
17use std::collections::HashMap;
18use std::{mem, str};
19
20#[cfg(fuchsia_api_level_at_least = "HEAD")]
21use fidl_fuchsia_diagnostics as fdiagnostics;
22
23mod constants;
24pub mod error;
25pub mod ffi;
26pub use constants::*;
27
28#[cfg(test)]
29mod test;
30
31#[derive(Clone)]
32pub struct MonikerWithUrl {
33 pub moniker: ExtendedMoniker,
34 pub url: FlyStr,
35}
36
37pub fn from_logger(source: MonikerWithUrl, msg: LoggerMessage) -> LogsData {
40 let (raw_severity, severity) = Severity::parse_exact(msg.raw_severity);
41 let mut builder = LogsDataBuilder::new(BuilderArgs {
42 timestamp: msg.timestamp,
43 component_url: Some(source.url),
44 moniker: source.moniker,
45 severity,
46 })
47 .set_pid(msg.pid)
48 .set_tid(msg.tid)
49 .set_dropped(msg.dropped_logs)
50 .set_message(msg.message);
51 if let Some(raw_severity) = raw_severity {
52 builder = builder.set_raw_severity(raw_severity);
53 }
54 for tag in &msg.tags {
55 builder = builder.add_tag(tag.as_ref());
56 }
57 builder.build()
58}
59
60#[derive(Clone)]
61pub struct ExtendedMetadata {
62 pub moniker: ExtendedMoniker,
63 pub url: FlyStr,
64 pub rolled_out_logs: u64,
65}
66
67#[cfg(fuchsia_api_level_less_than = "HEAD")]
68fn parse_archivist_args<'a>(
69 builder: LogsDataBuilder,
70 _input: &'a Record<'a>,
71) -> Result<(LogsDataBuilder, usize), MessageError> {
72 Ok((builder, 0))
73}
74
75#[cfg(fuchsia_api_level_at_least = "HEAD")]
76fn parse_archivist_args<'a>(
77 mut builder: LogsDataBuilder,
78 input: &'a Record<'a>,
79) -> Result<(LogsDataBuilder, usize), MessageError> {
80 let mut archivist_argument_count = 0;
81 for argument in input.arguments.iter().rev() {
82 match argument {
85 Argument::Other { name, value } => {
86 if name == fdiagnostics::COMPONENT_URL_ARG_NAME {
87 if let Value::Text(url) = value {
88 builder = builder.set_url(Some(FlyStr::new(url.as_ref())));
89 archivist_argument_count += 1;
90 continue;
91 }
92 } else if name == fdiagnostics::MONIKER_ARG_NAME {
93 if let Value::Text(moniker) = value {
94 builder = builder.set_moniker(ExtendedMoniker::parse_str(moniker)?);
95 archivist_argument_count += 1;
96 continue;
97 }
98 } else if name == fdiagnostics::ROLLED_OUT_ARG_NAME
99 && let Value::UnsignedInt(count) = value
100 {
101 builder = builder.set_rolled_out(*count);
102 archivist_argument_count += 1;
103 continue;
104 }
105 }
106 _ => break,
107 }
108 }
109 Ok((builder, archivist_argument_count))
110}
111
112pub fn parse_logs_data<'a>(
113 input: &'a Record<'a>,
114 source: Option<ExtendedMetadata>,
115 rolled_out: u64,
116) -> Result<LogsData, MessageError> {
117 let (raw_severity, severity) = Severity::parse_exact(input.severity);
118 let has_attribution = source.is_some();
119
120 let (maybe_moniker, maybe_url) =
121 source.map(|value| (Some(value.moniker), Some(value.url))).unwrap_or((None, None));
122
123 let mut builder = LogsDataBuilder::new(BuilderArgs {
124 component_url: maybe_url,
125 moniker: maybe_moniker.unwrap_or(ExtendedMoniker::ComponentInstance(
126 Moniker::parse_str("placeholder").unwrap(),
127 )),
128 severity,
129 timestamp: input.timestamp,
130 });
131
132 if rolled_out > 0 {
133 builder = builder.set_rolled_out(rolled_out);
134 }
135
136 if let Some(raw_severity) = raw_severity {
137 builder = builder.set_raw_severity(raw_severity);
138 }
139 let archivist_argument_count = if has_attribution {
140 0
141 } else {
142 let (new_builder, count) = parse_archivist_args(builder, input)?;
143 builder = new_builder;
144 count
145 };
146
147 for argument in input.arguments.iter().take(input.arguments.len() - archivist_argument_count) {
148 match argument {
149 Argument::Tag(tag) => {
150 builder = builder.add_tag(tag.as_ref());
151 }
152 Argument::Pid(pid) => {
153 builder = builder.set_pid(pid.raw_koid());
154 }
155 Argument::Tid(tid) => {
156 builder = builder.set_tid(tid.raw_koid());
157 }
158 Argument::Dropped(dropped) => {
159 builder = builder.set_dropped(*dropped);
160 }
161 Argument::File(file) => {
162 builder = builder.set_file(file.as_ref());
163 }
164 Argument::Line(line) => {
165 builder = builder.set_line(*line);
166 }
167 Argument::Message(msg) => {
168 builder = builder.set_message(msg.as_ref());
169 }
170 Argument::Other { value, name } => {
171 let name = LogsField::Other(name.to_string());
172 builder = builder.add_key(match value {
173 Value::SignedInt(v) => LogsProperty::Int(name, *v),
174 Value::UnsignedInt(v) => LogsProperty::Uint(name, *v),
175 Value::Floating(v) => LogsProperty::Double(name, *v),
176 Value::Text(v) => LogsProperty::String(name, v.to_string()),
177 Value::Boolean(v) => LogsProperty::Bool(name, *v),
178 })
179 }
180 }
181 }
182
183 Ok(builder.build())
184}
185
186#[derive(Default)]
214pub struct MessageParser {
215 tag_map: HashMap<u32, ExtendedMetadata>,
216}
217
218pub trait MessageFormatter {
219 type Result;
220
221 fn format(
222 &mut self,
223 record: &Record<'_>,
224 metadata: Option<ExtendedMetadata>,
225 ) -> Result<Self::Result, MessageError>;
226}
227
228#[derive(Default)]
229pub struct RustMessageFormatter;
230
231impl MessageFormatter for RustMessageFormatter {
232 type Result = Data<Logs>;
233
234 fn format(
235 &mut self,
236 record: &Record<'_>,
237 metadata: Option<ExtendedMetadata>,
238 ) -> Result<Self::Result, MessageError> {
239 let rolled_out = metadata.as_ref().map(|value| value.rolled_out_logs).unwrap_or(0);
240 parse_logs_data(record, metadata, rolled_out)
241 }
242}
243
244impl MessageParser {
245 pub fn parse_next<'a, F: MessageFormatter>(
262 &mut self,
263 bytes: &'a [u8],
264 mut formatter: F,
265 ) -> Result<(Option<F::Result>, &'a [u8]), MessageError> {
266 if bytes.len() < 8 {
267 return Err(MessageError::ShortRead { len: bytes.len() });
268 }
269 let header_bytes: [u8; 8] = bytes[0..8].try_into().unwrap();
270 let header_val = u64::from_le_bytes(header_bytes);
271 let header = Header(header_val);
272 let tag = header.tag();
273 let base_tag = tag & !LOG_CONTROL_BIT;
274 let is_control = (tag & LOG_CONTROL_BIT) != 0;
275
276 let (input, remaining) = diagnostics_log_encoding::parse::parse_record(bytes)?;
277
278 if is_control {
279 let mut moniker = None;
280 let mut url = None;
281 let mut rolled_out = None;
282 for arg in &input.arguments {
283 if arg.name() == MONIKER
284 && let Value::Text(v) = arg.value()
285 {
286 moniker = Some(v);
287 } else if arg.name() == URL
288 && let Value::Text(v) = arg.value()
289 {
290 url = Some(v);
291 } else if arg.name() == ROLLED_OUT
292 && let Value::UnsignedInt(v) = arg.value()
293 {
294 rolled_out = Some(v);
295 }
296 }
297 if let Some(count) = rolled_out {
298 let metadata =
299 self.tag_map.get(&base_tag).cloned().unwrap_or_else(|| ExtendedMetadata {
300 moniker: diagnostics_data::ExtendedMoniker::ComponentInstance(
301 moniker::Moniker::parse_str("/UNKNOWN").unwrap(),
302 ),
303 url: flyweights::FlyStr::new(ARCHIVIST_URL),
304 rolled_out_logs: count,
305 });
306 let data = formatter.format(&input, Some(metadata))?;
307 return Ok((Some(data), remaining));
308 }
309 if let (Some(m), Some(u)) = (moniker, url)
310 && let Ok(extended_moniker) = ExtendedMoniker::parse_str(&m)
311 {
312 self.tag_map.insert(
313 base_tag,
314 ExtendedMetadata {
315 moniker: extended_moniker,
316 url: FlyStr::new(u),
317 rolled_out_logs: 0,
318 },
319 );
320 }
321 Ok((None, remaining))
322 } else {
323 let metadata = self.tag_map.get(&base_tag).cloned();
324 let data = formatter.format(&input, metadata)?;
325 Ok((Some(data), remaining))
326 }
327 }
328}
329
330pub fn from_extended_record(bytes: &[u8]) -> Result<(LogsData, &[u8]), MessageError> {
334 let (input, remaining) = diagnostics_log_encoding::parse::parse_record(bytes)?;
335 let (source, new_remaining, rolled_out_logs) = if remaining.len() >= 16 {
336 let moniker_len = u32::from_le_bytes(remaining[0..4].try_into().unwrap()) as usize;
337 let component_url_len = u32::from_le_bytes(remaining[4..8].try_into().unwrap()) as usize;
338 let rolled_out_logs = u64::from_le_bytes(remaining[8..16].try_into().unwrap());
339 let mut offset = 16;
340 let moniker = str::from_utf8(&remaining[offset..offset + moniker_len])?;
341 let moniker_padded_len = (moniker_len + 7) & !7;
342 offset += moniker_padded_len;
343 let url = str::from_utf8(&remaining[offset..offset + component_url_len])?;
344 let component_url_padded_len = (component_url_len + 7) & !7;
345 offset += component_url_padded_len;
346 (
347 Some(ExtendedMetadata {
348 moniker: ExtendedMoniker::parse_str(moniker)?,
349 url: FlyStr::new(url),
350 rolled_out_logs: 0,
351 }),
352 &remaining[offset..],
353 rolled_out_logs,
354 )
355 } else {
356 (None, remaining, 0)
357 };
358 let record = parse_logs_data(&input, source, rolled_out_logs)?;
359 Ok((record, new_remaining))
360}
361
362pub fn from_structured(source: MonikerWithUrl, bytes: &[u8]) -> Result<LogsData, MessageError> {
367 let (input, _remaining) = diagnostics_log_encoding::parse::parse_record(bytes)?;
368 let record = parse_logs_data(
369 &input,
370 Some(ExtendedMetadata { moniker: source.moniker, url: source.url, rolled_out_logs: 0 }),
371 0,
372 )?;
373 Ok(record)
374}
375
376#[derive(Clone, Debug, Eq, PartialEq)]
377pub struct LoggerMessage {
378 pub timestamp: zx::BootInstant,
379 pub raw_severity: u8,
380 pub pid: u64,
381 pub tid: u64,
382 pub size_bytes: usize,
383 pub dropped_logs: u64,
384 pub message: Box<str>,
385 pub tags: Vec<Box<str>>,
386}
387
388impl TryFrom<&[u8]> for LoggerMessage {
395 type Error = MessageError;
396
397 fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
398 if bytes.len() < MIN_PACKET_SIZE {
399 return Err(MessageError::ShortRead { len: bytes.len() });
400 }
401
402 let terminator = bytes[bytes.len() - 1];
403 if terminator != 0 {
404 return Err(MessageError::NotNullTerminated { terminator });
405 }
406
407 let pid = LittleEndian::read_u64(&bytes[..8]);
408 let tid = LittleEndian::read_u64(&bytes[8..16]);
409 let timestamp = zx::BootInstant::from_nanos(LittleEndian::read_i64(&bytes[16..24]));
410
411 let raw_severity = LittleEndian::read_i32(&bytes[24..28]);
412 let raw_severity = if raw_severity > (u8::MAX as i32) {
413 u8::MAX
414 } else if raw_severity < 0 {
415 0
416 } else {
417 u8::try_from(raw_severity).unwrap()
418 };
419 let dropped_logs = LittleEndian::read_u32(&bytes[28..METADATA_SIZE]) as u64;
420
421 let mut cursor = METADATA_SIZE;
423 let mut tag_len = bytes[cursor] as usize;
424 let mut tags = Vec::new();
425 while tag_len != 0 {
426 if tags.len() == MAX_TAGS {
427 return Err(MessageError::TooManyTags);
428 }
429
430 if tag_len > MAX_TAG_LEN - 1 {
431 return Err(MessageError::TagTooLong { index: tags.len(), len: tag_len });
432 }
433
434 if (cursor + tag_len + 1) > bytes.len() {
435 return Err(MessageError::OutOfBounds);
436 }
437
438 let tag_start = cursor + 1;
439 let tag_end = tag_start + tag_len;
440 let tag = String::from_utf8_lossy(&bytes[tag_start..tag_end]);
441 tags.push(tag.into());
442
443 cursor = tag_end;
444 tag_len = bytes[cursor] as usize;
445 }
446
447 let msg_start = cursor + 1;
448 let mut msg_end = cursor + 1;
449 while msg_end < bytes.len() {
450 if bytes[msg_end] > 0 {
451 msg_end += 1;
452 continue;
453 }
454 let message = String::from_utf8_lossy(&bytes[msg_start..msg_end]).into_owned();
455 let message_len = message.len();
456 let result = LoggerMessage {
457 timestamp,
458 raw_severity,
459 message: message.into_boxed_str(),
460 pid,
461 tid,
462 dropped_logs,
463 tags,
464 size_bytes: cursor + message_len + 1,
465 };
466 return Ok(result);
467 }
468
469 Err(MessageError::OutOfBounds)
470 }
471}
472
473#[allow(non_camel_case_types)]
474pub type fx_log_severity_t = c_int;
475
476#[repr(C)]
477#[derive(Debug, Copy, Clone, Default, Eq, PartialEq)]
478pub struct fx_log_metadata_t {
479 pub pid: zx::sys::zx_koid_t,
480 pub tid: zx::sys::zx_koid_t,
481 pub time: zx::sys::zx_time_t,
482 pub severity: fx_log_severity_t,
483 pub dropped_logs: u32,
484}
485
486#[repr(C)]
487#[derive(Clone)]
488pub struct fx_log_packet_t {
489 pub metadata: fx_log_metadata_t,
490 pub data: [c_char; MAX_DATAGRAM_LEN - METADATA_SIZE],
494}
495
496impl Default for fx_log_packet_t {
497 fn default() -> fx_log_packet_t {
498 fx_log_packet_t {
499 data: [0; MAX_DATAGRAM_LEN - METADATA_SIZE],
500 metadata: Default::default(),
501 }
502 }
503}
504
505impl fx_log_packet_t {
506 pub fn as_bytes(&self) -> &[u8] {
509 unsafe {
510 std::slice::from_raw_parts(
511 (self as *const Self) as *const u8,
512 mem::size_of::<fx_log_packet_t>(),
513 )
514 }
515 }
516
517 pub fn fill_data(&mut self, region: std::ops::Range<usize>, with: c_char) {
519 self.data[region].iter_mut().for_each(|c| *c = with);
520 }
521
522 pub fn add_data<T: std::convert::TryInto<c_char> + Copy>(&mut self, offset: usize, bytes: &[T])
524 where
525 <T as std::convert::TryInto<c_char>>::Error: std::fmt::Debug,
526 {
527 self.data[offset..(offset + bytes.len())]
528 .iter_mut()
529 .enumerate()
530 .for_each(|(i, x)| *x = bytes[i].try_into().unwrap());
531 }
532}