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