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, Record, Value};
11use flyweights::FlyStr;
12use libc::{c_char, c_int};
13use moniker::Moniker;
14use std::{mem, str};
15mod constants;
16pub mod error;
17pub use constants::*;
18
19#[cfg(test)]
20mod test;
21
22#[derive(Clone)]
23pub struct MonikerWithUrl {
24 pub moniker: ExtendedMoniker,
25 pub url: FlyStr,
26}
27
28pub fn from_logger(source: MonikerWithUrl, msg: LoggerMessage) -> LogsData {
31 let (raw_severity, severity) = Severity::parse_exact(msg.raw_severity);
32 let mut builder = LogsDataBuilder::new(BuilderArgs {
33 timestamp: msg.timestamp,
34 component_url: Some(source.url),
35 moniker: source.moniker,
36 severity,
37 })
38 .set_pid(msg.pid)
39 .set_tid(msg.tid)
40 .set_dropped(msg.dropped_logs)
41 .set_message(msg.message);
42 if let Some(raw_severity) = raw_severity {
43 builder = builder.set_raw_severity(raw_severity);
44 }
45 for tag in &msg.tags {
46 builder = builder.add_tag(tag.as_ref());
47 }
48 builder.build()
49}
50
51#[cfg(fuchsia_api_level_at_least = "HEAD")]
52fn parse_archivist_args<'a>(
53 mut builder: LogsDataBuilder,
54 input: &'a Record<'a>,
55) -> Result<(LogsDataBuilder, usize), MessageError> {
56 let mut has_component_url = false;
57 let mut has_moniker = false;
58 let mut archivist_argument_count = 0;
59 for argument in input.arguments.iter().rev() {
60 match argument {
63 Argument::Other { value, name } => {
64 if name == fidl_fuchsia_diagnostics::COMPONENT_URL_ARG_NAME {
65 if let Value::Text(url) = value {
66 builder = builder.set_url(Some(FlyStr::new(url.clone())));
67 archivist_argument_count += 1;
68 has_component_url = true;
69 continue;
70 }
71 }
72 if name == fidl_fuchsia_diagnostics::MONIKER_ARG_NAME {
73 if let Value::Text(moniker) = value {
74 builder = builder.set_moniker(ExtendedMoniker::parse_str(moniker)?);
75 archivist_argument_count += 1;
76 has_moniker = true;
77 continue;
78 }
79 }
80 if name == fidl_fuchsia_diagnostics::ROLLED_OUT_ARG_NAME {
81 if let Value::UnsignedInt(ival) = value {
82 builder = builder.set_rolled_out(*ival);
83 archivist_argument_count += 1;
84 continue;
85 }
86 }
87 break;
88 }
89 _ => break,
90 }
91 }
92 if !has_component_url {
93 return Err(MessageError::MissingUrl);
94 }
95 if !has_moniker {
96 return Err(MessageError::MissingMoniker);
97 }
98 Ok((builder, archivist_argument_count))
99}
100
101#[cfg(fuchsia_api_level_less_than = "HEAD")]
102fn parse_archivist_args<'a>(
103 builder: LogsDataBuilder,
104 _input: &'a Record<'a>,
105) -> Result<(LogsDataBuilder, usize), MessageError> {
106 Ok((builder, 0))
107}
108
109fn parse_logs_data<'a>(
110 input: &'a Record<'a>,
111 source: Option<MonikerWithUrl>,
112) -> Result<LogsData, MessageError> {
113 let (raw_severity, severity) = Severity::parse_exact(input.severity);
114 let has_attribution = source.is_some();
115 let (maybe_moniker, maybe_url) =
116 source.map(|value| (Some(value.moniker), Some(value.url))).unwrap_or((None, None));
117 let mut builder = LogsDataBuilder::new(BuilderArgs {
118 component_url: maybe_url,
119 moniker: maybe_moniker.unwrap_or(ExtendedMoniker::ComponentInstance(
120 Moniker::parse_str("placeholder").unwrap(),
121 )),
122 severity,
123 timestamp: input.timestamp,
124 });
125 if let Some(raw_severity) = raw_severity {
126 builder = builder.set_raw_severity(raw_severity);
127 }
128 let archivist_argument_count = if has_attribution {
129 0
130 } else {
131 let (new_builder, count) = parse_archivist_args(builder, input)?;
132 builder = new_builder;
133 count
134 };
135
136 for argument in input.arguments.iter().take(input.arguments.len() - archivist_argument_count) {
137 match argument {
138 Argument::Tag(tag) => {
139 builder = builder.add_tag(tag.as_ref());
140 }
141 Argument::Pid(pid) => {
142 builder = builder.set_pid(pid.raw_koid());
143 }
144 Argument::Tid(tid) => {
145 builder = builder.set_tid(tid.raw_koid());
146 }
147 Argument::Dropped(dropped) => {
148 builder = builder.set_dropped(*dropped);
149 }
150 Argument::File(file) => {
151 builder = builder.set_file(file.as_ref());
152 }
153 Argument::Line(line) => {
154 builder = builder.set_line(*line);
155 }
156 Argument::Message(msg) => {
157 builder = builder.set_message(msg.as_ref());
158 }
159 Argument::Other { value, name } => {
160 let name = LogsField::Other(name.to_string());
161 builder = builder.add_key(match value {
162 Value::SignedInt(v) => LogsProperty::Int(name, *v),
163 Value::UnsignedInt(v) => LogsProperty::Uint(name, *v),
164 Value::Floating(v) => LogsProperty::Double(name, *v),
165 Value::Text(v) => LogsProperty::String(name, v.to_string()),
166 Value::Boolean(v) => LogsProperty::Bool(name, *v),
167 })
168 }
169 }
170 }
171
172 Ok(builder.build())
173}
174
175pub fn from_extended_record(bytes: &[u8]) -> Result<(LogsData, &[u8]), MessageError> {
181 let (input, remaining) = diagnostics_log_encoding::parse::parse_record(bytes)?;
182 let record = parse_logs_data(&input, None)?;
183 Ok((record, remaining))
184}
185
186pub fn from_structured(source: MonikerWithUrl, bytes: &[u8]) -> Result<LogsData, MessageError> {
191 let (input, _remaining) = diagnostics_log_encoding::parse::parse_record(bytes)?;
192 let record = parse_logs_data(&input, Some(source))?;
193 Ok(record)
194}
195
196#[derive(Clone, Debug, Eq, PartialEq)]
197pub struct LoggerMessage {
198 pub timestamp: zx::BootInstant,
199 pub raw_severity: u8,
200 pub pid: u64,
201 pub tid: u64,
202 pub size_bytes: usize,
203 pub dropped_logs: u64,
204 pub message: Box<str>,
205 pub tags: Vec<Box<str>>,
206}
207
208impl TryFrom<&[u8]> for LoggerMessage {
215 type Error = MessageError;
216
217 fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
218 if bytes.len() < MIN_PACKET_SIZE {
219 return Err(MessageError::ShortRead { len: bytes.len() });
220 }
221
222 let terminator = bytes[bytes.len() - 1];
223 if terminator != 0 {
224 return Err(MessageError::NotNullTerminated { terminator });
225 }
226
227 let pid = LittleEndian::read_u64(&bytes[..8]);
228 let tid = LittleEndian::read_u64(&bytes[8..16]);
229 let timestamp = zx::BootInstant::from_nanos(LittleEndian::read_i64(&bytes[16..24]));
230
231 let raw_severity = LittleEndian::read_i32(&bytes[24..28]);
232 let raw_severity = if raw_severity > (u8::MAX as i32) {
233 u8::MAX
234 } else if raw_severity < 0 {
235 0
236 } else {
237 u8::try_from(raw_severity).unwrap()
238 };
239 let dropped_logs = LittleEndian::read_u32(&bytes[28..METADATA_SIZE]) as u64;
240
241 let mut cursor = METADATA_SIZE;
243 let mut tag_len = bytes[cursor] as usize;
244 let mut tags = Vec::new();
245 while tag_len != 0 {
246 if tags.len() == MAX_TAGS {
247 return Err(MessageError::TooManyTags);
248 }
249
250 if tag_len > MAX_TAG_LEN - 1 {
251 return Err(MessageError::TagTooLong { index: tags.len(), len: tag_len });
252 }
253
254 if (cursor + tag_len + 1) > bytes.len() {
255 return Err(MessageError::OutOfBounds);
256 }
257
258 let tag_start = cursor + 1;
259 let tag_end = tag_start + tag_len;
260 let tag = String::from_utf8_lossy(&bytes[tag_start..tag_end]);
261 tags.push(tag.into());
262
263 cursor = tag_end;
264 tag_len = bytes[cursor] as usize;
265 }
266
267 let msg_start = cursor + 1;
268 let mut msg_end = cursor + 1;
269 while msg_end < bytes.len() {
270 if bytes[msg_end] > 0 {
271 msg_end += 1;
272 continue;
273 }
274 let message = String::from_utf8_lossy(&bytes[msg_start..msg_end]).into_owned();
275 let message_len = message.len();
276 let result = LoggerMessage {
277 timestamp,
278 raw_severity,
279 message: message.into_boxed_str(),
280 pid,
281 tid,
282 dropped_logs,
283 tags,
284 size_bytes: cursor + message_len + 1,
285 };
286 return Ok(result);
287 }
288
289 Err(MessageError::OutOfBounds)
290 }
291}
292
293#[allow(non_camel_case_types)]
294pub type fx_log_severity_t = c_int;
295
296#[repr(C)]
297#[derive(Debug, Copy, Clone, Default, Eq, PartialEq)]
298pub struct fx_log_metadata_t {
299 pub pid: zx::sys::zx_koid_t,
300 pub tid: zx::sys::zx_koid_t,
301 pub time: zx::sys::zx_time_t,
302 pub severity: fx_log_severity_t,
303 pub dropped_logs: u32,
304}
305
306#[repr(C)]
307#[derive(Clone)]
308pub struct fx_log_packet_t {
309 pub metadata: fx_log_metadata_t,
310 pub data: [c_char; MAX_DATAGRAM_LEN - METADATA_SIZE],
314}
315
316impl Default for fx_log_packet_t {
317 fn default() -> fx_log_packet_t {
318 fx_log_packet_t {
319 data: [0; MAX_DATAGRAM_LEN - METADATA_SIZE],
320 metadata: Default::default(),
321 }
322 }
323}
324
325impl fx_log_packet_t {
326 pub fn as_bytes(&self) -> &[u8] {
329 unsafe {
330 std::slice::from_raw_parts(
331 (self as *const Self) as *const u8,
332 mem::size_of::<fx_log_packet_t>(),
333 )
334 }
335 }
336
337 pub fn fill_data(&mut self, region: std::ops::Range<usize>, with: c_char) {
339 self.data[region].iter_mut().for_each(|c| *c = with);
340 }
341
342 pub fn add_data<T: std::convert::TryInto<c_char> + Copy>(&mut self, offset: usize, bytes: &[T])
344 where
345 <T as std::convert::TryInto<c_char>>::Error: std::fmt::Debug,
346 {
347 self.data[offset..(offset + bytes.len())]
348 .iter_mut()
349 .enumerate()
350 .for_each(|(i, x)| *x = bytes[i].try_into().unwrap());
351 }
352}