1#![warn(missing_docs)]
8
9use bitfield::bitfield;
10use std::borrow::{Borrow, Cow};
11use zerocopy::{FromBytes, IntoBytes, KnownLayout};
12
13mod constants;
14pub mod encode;
15pub mod parse;
16
17pub use constants::*;
18
19pub type RawSeverity = u8;
21
22#[derive(Clone, Debug, PartialEq)]
24pub struct Record<'a> {
25 pub timestamp: zx::BootInstant,
27 pub severity: RawSeverity,
29 pub arguments: Vec<Argument<'a>>,
31}
32
33impl Record<'_> {
34 pub fn into_owned(self) -> Record<'static> {
36 Record {
37 timestamp: self.timestamp,
38 severity: self.severity,
39 arguments: self.arguments.into_iter().map(|arg| arg.into_owned()).collect(),
40 }
41 }
42}
43
44#[derive(Clone, Debug, PartialEq)]
46pub enum Argument<'a> {
47 Pid(zx::Koid),
49 Tid(zx::Koid),
51 Tag(Cow<'a, str>),
53 Dropped(u64),
55 File(Cow<'a, str>),
57 Message(Cow<'a, str>),
59 Line(u64),
61 Other {
63 name: Cow<'a, str>,
65 value: Value<'a>,
67 },
68}
69
70impl<'a> Argument<'a> {
71 pub fn new(name: impl Into<Cow<'a, str>>, value: impl Into<Value<'a>>) -> Self {
73 let name: Cow<'a, str> = name.into();
74 match (name.as_ref(), value.into()) {
75 (constants::PID, Value::UnsignedInt(pid)) => Self::pid(zx::Koid::from_raw(pid)),
76 (constants::TID, Value::UnsignedInt(pid)) => Self::tid(zx::Koid::from_raw(pid)),
77 (constants::TAG, Value::Text(tag)) => Self::tag(tag),
78 (constants::NUM_DROPPED, Value::UnsignedInt(dropped)) => Self::dropped(dropped),
79 (constants::FILE, Value::Text(file)) => Self::file(file),
80 (constants::LINE, Value::UnsignedInt(line)) => Self::line(line),
81 (constants::MESSAGE, Value::Text(msg)) => Self::message(msg),
82 (_, value) => Self::other(name, value),
83 }
84 }
85
86 #[inline]
87 pub fn pid(koid: zx::Koid) -> Self {
89 Argument::Pid(koid)
90 }
91
92 #[inline]
93 pub fn tid(koid: zx::Koid) -> Self {
95 Argument::Tid(koid)
96 }
97
98 #[inline]
99 pub fn message(message: impl Into<Cow<'a, str>>) -> Self {
101 Argument::Message(message.into())
102 }
103
104 #[inline]
105 pub fn tag(value: impl Into<Cow<'a, str>>) -> Self {
107 Argument::Tag(value.into())
108 }
109
110 #[inline]
111 pub fn dropped(value: u64) -> Self {
113 Argument::Dropped(value)
114 }
115
116 #[inline]
117 pub fn file(value: impl Into<Cow<'a, str>>) -> Self {
119 Argument::File(value.into())
120 }
121
122 #[inline]
123 pub fn line(value: u64) -> Self {
125 Argument::Line(value)
126 }
127
128 #[inline]
129 pub fn other(name: impl Into<Cow<'a, str>>, value: impl Into<Value<'a>>) -> Self {
131 Argument::Other { name: name.into(), value: value.into() }
132 }
133
134 pub fn into_owned(self) -> Argument<'static> {
136 match self {
137 Self::Pid(pid) => Argument::Pid(pid),
138 Self::Tid(tid) => Argument::Tid(tid),
139 Self::Tag(tag) => Argument::Tag(Cow::Owned(tag.into_owned())),
140 Self::Dropped(dropped) => Argument::Dropped(dropped),
141 Self::File(file) => Argument::File(Cow::Owned(file.into_owned())),
142 Self::Line(line) => Argument::Line(line),
143 Self::Message(msg) => Argument::Message(Cow::Owned(msg.into_owned())),
144 Self::Other { name, value } => {
145 Argument::Other { name: Cow::Owned(name.into_owned()), value: value.into_owned() }
146 }
147 }
148 }
149
150 pub fn name(&self) -> &str {
152 match self {
153 Self::Pid(_) => constants::PID,
154 Self::Tid(_) => constants::TID,
155 Self::Tag(_) => constants::TAG,
156 Self::Dropped(_) => constants::NUM_DROPPED,
157 Self::File(_) => constants::FILE,
158 Self::Line(_) => constants::LINE,
159 Self::Message(_) => constants::MESSAGE,
160 Self::Other { name, .. } => name.borrow(),
161 }
162 }
163
164 pub fn value(&'a self) -> Value<'a> {
166 match self {
167 Self::Pid(pid) => Value::UnsignedInt(pid.raw_koid()),
168 Self::Tid(tid) => Value::UnsignedInt(tid.raw_koid()),
169 Self::Tag(tag) => Value::Text(Cow::Borrowed(tag.as_ref())),
170 Self::Dropped(num_dropped) => Value::UnsignedInt(*num_dropped),
171 Self::File(file) => Value::Text(Cow::Borrowed(file.as_ref())),
172 Self::Message(msg) => Value::Text(Cow::Borrowed(msg.as_ref())),
173 Self::Line(line) => Value::UnsignedInt(*line),
174 Self::Other { value, .. } => value.clone_borrowed(),
175 }
176 }
177}
178
179#[derive(Clone, Debug, PartialEq)]
181pub enum Value<'a> {
182 SignedInt(i64),
184 UnsignedInt(u64),
186 Floating(f64),
188 Boolean(bool),
190 Text(Cow<'a, str>),
192}
193
194impl<'a> Value<'a> {
195 fn into_owned(self) -> Value<'static> {
196 match self {
197 Self::Text(s) => Value::Text(Cow::Owned(s.into_owned())),
198 Self::SignedInt(n) => Value::SignedInt(n),
199 Self::UnsignedInt(n) => Value::UnsignedInt(n),
200 Self::Floating(n) => Value::Floating(n),
201 Self::Boolean(n) => Value::Boolean(n),
202 }
203 }
204
205 fn clone_borrowed(&'a self) -> Value<'a> {
206 match self {
207 Self::Text(s) => Self::Text(Cow::Borrowed(s.as_ref())),
208 Self::SignedInt(n) => Self::SignedInt(*n),
209 Self::UnsignedInt(n) => Self::UnsignedInt(*n),
210 Self::Floating(n) => Self::Floating(*n),
211 Self::Boolean(n) => Self::Boolean(*n),
212 }
213 }
214}
215
216impl From<i32> for Value<'_> {
217 fn from(number: i32) -> Value<'static> {
218 Value::SignedInt(number as i64)
219 }
220}
221
222impl From<i64> for Value<'_> {
223 fn from(number: i64) -> Value<'static> {
224 Value::SignedInt(number)
225 }
226}
227
228impl From<u64> for Value<'_> {
229 fn from(number: u64) -> Value<'static> {
230 Value::UnsignedInt(number)
231 }
232}
233
234impl From<u32> for Value<'_> {
235 fn from(number: u32) -> Value<'static> {
236 Value::UnsignedInt(number as u64)
237 }
238}
239
240impl From<zx::Koid> for Value<'_> {
241 fn from(koid: zx::Koid) -> Value<'static> {
242 Value::UnsignedInt(koid.raw_koid())
243 }
244}
245
246impl From<f64> for Value<'_> {
247 fn from(number: f64) -> Value<'static> {
248 Value::Floating(number)
249 }
250}
251
252impl<'a> From<&'a str> for Value<'a> {
253 fn from(text: &'a str) -> Value<'a> {
254 Value::Text(Cow::Borrowed(text))
255 }
256}
257
258impl From<String> for Value<'static> {
259 fn from(text: String) -> Value<'static> {
260 Value::Text(Cow::Owned(text))
261 }
262}
263
264impl<'a> From<Cow<'a, str>> for Value<'a> {
265 fn from(text: Cow<'a, str>) -> Value<'a> {
266 Value::Text(text)
267 }
268}
269
270impl From<bool> for Value<'static> {
271 fn from(boolean: bool) -> Value<'static> {
272 Value::Boolean(boolean)
273 }
274}
275
276bitfield! {
277 #[derive(IntoBytes, FromBytes, KnownLayout)]
286 pub struct Header(u64);
287 impl Debug;
288
289 pub u8, raw_type, set_type: 3, 0;
291
292 pub u16, size_words, set_size_words: 15, 4;
294
295 u16, name_ref, set_name_ref: 31, 16;
297
298 bool, bool_val, set_bool_val: 32;
300
301 u16, value_ref, set_value_ref: 47, 32;
303
304 pub u8, severity, set_severity: 63, 56;
306}
307
308impl Header {
309 pub fn set_len(&mut self, new_len: usize) {
311 assert_eq!(new_len % 8, 0, "encoded message must be 8-byte aligned");
312 self.set_size_words((new_len / 8) as u16 + u16::from(new_len % 8 > 0))
313 }
314}
315
316#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
321pub enum Metatag {
322 Target,
327}
328
329#[repr(u8)]
333enum ArgType {
334 Null = 0,
335 I32 = 1,
336 U32 = 2,
337 I64 = 3,
338 U64 = 4,
339 F64 = 5,
340 String = 6,
341 Pointer = 7,
342 Koid = 8,
343 Bool = 9,
344}
345
346impl TryFrom<u8> for ArgType {
347 type Error = parse::ParseError;
348 fn try_from(b: u8) -> Result<Self, Self::Error> {
349 Ok(match b {
350 0 => ArgType::Null,
351 1 => ArgType::I32,
352 2 => ArgType::U32,
353 3 => ArgType::I64,
354 4 => ArgType::U64,
355 5 => ArgType::F64,
356 6 => ArgType::String,
357 7 => ArgType::Pointer,
358 8 => ArgType::Koid,
359 9 => ArgType::Bool,
360 _ => return Err(parse::ParseError::ValueOutOfValidRange),
361 })
362 }
363}
364
365#[cfg(test)]
366mod tests {
367 use super::*;
368 use crate::encode::{Encoder, EncoderOpts, EncodingError, MutableBuffer};
369 use fidl_fuchsia_diagnostics::Severity;
370 use std::fmt::Debug;
371 use std::io::Cursor;
372
373 fn parse_argument(bytes: &[u8]) -> (&[u8], Argument<'static>) {
374 let (decoded_from_full, remaining) = crate::parse::parse_argument(bytes).unwrap();
375 (remaining, decoded_from_full.into_owned())
376 }
377
378 fn parse_record(bytes: &[u8]) -> (&[u8], Record<'static>) {
379 let (decoded_from_full, remaining) = crate::parse::parse_record(bytes).unwrap();
380 (remaining, decoded_from_full.into_owned())
381 }
382
383 const BUF_LEN: usize = 1024;
384
385 pub(crate) fn assert_roundtrips<T, F>(
386 val: T,
387 encoder_method: impl Fn(&mut Encoder<Cursor<Vec<u8>>>, &T) -> Result<(), EncodingError>,
388 parser: F,
389 canonical: Option<&[u8]>,
390 ) where
391 T: Debug + PartialEq,
392 F: Fn(&[u8]) -> (&[u8], T),
393 {
394 let mut encoder = Encoder::new(Cursor::new(vec![0; BUF_LEN]), EncoderOpts::default());
395 encoder_method(&mut encoder, &val).unwrap();
396
397 let (_, decoded_from_full) = parser(encoder.buf.get_ref());
399 assert_eq!(val, decoded_from_full, "decoded version with trailing padding must match");
400
401 if let Some(canonical) = canonical {
402 let recorded = encoder.buf.get_ref().split_at(canonical.len()).0;
403 assert_eq!(canonical, recorded, "encoded repr must match the canonical value provided");
404
405 let (zero_buf, decoded) = parser(recorded);
406 assert_eq!(val, decoded, "decoded version must match what we tried to encode");
407 assert_eq!(zero_buf.len(), 0, "must parse record exactly out of provided buffer");
408 }
409 }
410
411 const MINIMAL_LOG_HEADER: u64 = 0x3000000000000029;
414
415 #[fuchsia::test]
416 fn minimal_header() {
417 let mut poked = Header(0);
418 poked.set_type(TRACING_FORMAT_LOG_RECORD_TYPE);
419 poked.set_size_words(2);
420 poked.set_severity(Severity::Info.into_primitive());
421
422 assert_eq!(
423 poked.0, MINIMAL_LOG_HEADER,
424 "minimal log header should only describe type, size, and severity"
425 );
426 }
427
428 #[fuchsia::test]
429 fn no_args_roundtrip() {
430 let mut expected_record = MINIMAL_LOG_HEADER.to_le_bytes().to_vec();
431 let timestamp = zx::BootInstant::from_nanos(5_000_000i64);
432 expected_record.extend(timestamp.into_nanos().to_le_bytes());
433
434 assert_roundtrips(
435 Record { timestamp, severity: Severity::Info.into_primitive(), arguments: vec![] },
436 |encoder, val| encoder.write_record(val),
437 parse_record,
438 Some(&expected_record),
439 );
440 }
441
442 #[fuchsia::test]
443 fn signed_arg_roundtrip() {
444 assert_roundtrips(
445 Argument::other("signed", -1999),
446 |encoder, val| encoder.write_argument(val),
447 parse_argument,
448 None,
449 );
450 }
451
452 #[fuchsia::test]
453 fn unsigned_arg_roundtrip() {
454 assert_roundtrips(
455 Argument::other("unsigned", 42),
456 |encoder, val| encoder.write_argument(val),
457 parse_argument,
458 None,
459 );
460 }
461
462 #[fuchsia::test]
463 fn text_arg_roundtrip() {
464 assert_roundtrips(
465 Argument::other("stringarg", "owo"),
466 |encoder, val| encoder.write_argument(val),
467 parse_argument,
468 None,
469 );
470 }
471
472 #[fuchsia::test]
473 fn float_arg_roundtrip() {
474 assert_roundtrips(
475 Argument::other("float", 3.25),
476 |encoder, val| encoder.write_argument(val),
477 parse_argument,
478 None,
479 );
480 }
481
482 #[fuchsia::test]
483 fn bool_arg_roundtrip() {
484 assert_roundtrips(
485 Argument::other("bool", false),
486 |encoder, val| encoder.write_argument(val),
487 parse_argument,
488 None,
489 );
490 }
491
492 #[fuchsia::test]
493 fn arg_of_each_type_roundtrips() {
494 assert_roundtrips(
495 Record {
496 timestamp: zx::BootInstant::get(),
497 severity: Severity::Warn.into_primitive(),
498 arguments: vec![
499 Argument::other("signed", -10),
500 Argument::other("unsigned", 7),
501 Argument::other("float", 3.25),
502 Argument::other("bool", true),
503 Argument::other("msg", "test message one"),
504 ],
505 },
506 |encoder, val| encoder.write_record(val),
507 parse_record,
508 None,
509 );
510 }
511
512 #[fuchsia::test]
513 fn multiple_string_args() {
514 assert_roundtrips(
515 Record {
516 timestamp: zx::BootInstant::get(),
517 severity: Severity::Trace.into_primitive(),
518 arguments: vec![
519 Argument::other("msg", "test message one"),
520 Argument::other("msg", "test message two"),
521 Argument::other("msg", "test message three"),
522 ],
523 },
524 |encoder, val| encoder.write_record(val),
525 parse_record,
526 None,
527 );
528 }
529
530 #[fuchsia::test]
531 fn invalid_records() {
532 let mut encoder = Encoder::new(Cursor::new(vec![0; BUF_LEN]), EncoderOpts::default());
534 let mut header = Header(0);
535 header.set_type(TRACING_FORMAT_LOG_RECORD_TYPE);
536 header.set_size_words(0); encoder.buf.put_u64_le(header.0).unwrap();
538 encoder.buf.put_i64_le(zx::BootInstant::get().into_nanos()).unwrap();
539 encoder.write_argument(Argument::other("msg", "test message one")).unwrap();
540 assert!(crate::parse::parse_record(encoder.buf.get_ref()).is_err());
541 }
542}