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