1#![warn(missing_docs)]
8
9use bitfield::bitfield;
10use std::borrow::{Borrow, Cow};
11use zerocopy::{FromBytes, Immutable, 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
276pub const MAX_SIZE_WORDS: u16 = 4095;
278
279pub const LOG_CONTROL_BIT: u32 = 1 << 31;
282
283bitfield! {
284 #[derive(IntoBytes, FromBytes, KnownLayout, Immutable)]
293 pub struct Header(u64);
294 impl Debug;
295
296 pub u8, raw_type, set_type: 3, 0;
298
299 pub u16, size_words, set_size_words: 15, 4;
301
302 u16, name_ref, set_name_ref: 31, 16;
304
305 bool, bool_val, set_bool_val: 32;
307
308 u16, value_ref, set_value_ref: 47, 32;
310
311 pub u32, tag, set_tag: 47, 16;
313
314 pub u8, severity, set_severity: 63, 56;
316}
317
318impl Header {
319 pub fn set_len(&mut self, new_len: usize) {
321 assert_eq!(new_len % 8, 0, "encoded message must be 8-byte aligned");
322 self.set_size_words((new_len / 8) as u16 + u16::from(!new_len.is_multiple_of(8)))
323 }
324}
325
326#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
331pub enum Metatag {
332 Target,
337}
338
339#[repr(u8)]
343enum ArgType {
344 Null = 0,
345 I32 = 1,
346 U32 = 2,
347 I64 = 3,
348 U64 = 4,
349 F64 = 5,
350 String = 6,
351 Pointer = 7,
352 Koid = 8,
353 Bool = 9,
354}
355
356impl TryFrom<u8> for ArgType {
357 type Error = parse::ParseError;
358 fn try_from(b: u8) -> Result<Self, Self::Error> {
359 Ok(match b {
360 0 => ArgType::Null,
361 1 => ArgType::I32,
362 2 => ArgType::U32,
363 3 => ArgType::I64,
364 4 => ArgType::U64,
365 5 => ArgType::F64,
366 6 => ArgType::String,
367 7 => ArgType::Pointer,
368 8 => ArgType::Koid,
369 9 => ArgType::Bool,
370 _ => return Err(parse::ParseError::ValueOutOfValidRange),
371 })
372 }
373}
374
375#[cfg(test)]
376mod tests {
377 use super::*;
378 use crate::encode::{Encoder, EncoderOpts, EncodingError, MutableBuffer};
379 use fidl_fuchsia_diagnostics_types::Severity;
380 use std::fmt::Debug;
381 use std::io::Cursor;
382
383 fn parse_argument(bytes: &[u8]) -> (&[u8], Argument<'static>) {
384 let (decoded_from_full, remaining) = crate::parse::parse_argument(bytes).unwrap();
385 (remaining, decoded_from_full.into_owned())
386 }
387
388 fn parse_record(bytes: &[u8]) -> (&[u8], Record<'static>) {
389 let (decoded_from_full, remaining) = crate::parse::parse_record(bytes).unwrap();
390 (remaining, decoded_from_full.into_owned())
391 }
392
393 const BUF_LEN: usize = 1024;
394
395 pub(crate) fn assert_roundtrips<T, F>(
396 val: T,
397 encoder_method: impl Fn(&mut Encoder<Cursor<Vec<u8>>>, &T) -> Result<(), EncodingError>,
398 parser: F,
399 canonical: Option<&[u8]>,
400 ) where
401 T: Debug + PartialEq,
402 F: Fn(&[u8]) -> (&[u8], T),
403 {
404 let mut encoder = Encoder::new(Cursor::new(vec![0; BUF_LEN]), EncoderOpts::default());
405 encoder_method(&mut encoder, &val).unwrap();
406
407 let (_, decoded_from_full) = parser(encoder.buf.get_ref());
409 assert_eq!(val, decoded_from_full, "decoded version with trailing padding must match");
410
411 if let Some(canonical) = canonical {
412 let recorded = encoder.buf.get_ref().split_at(canonical.len()).0;
413 assert_eq!(canonical, recorded, "encoded repr must match the canonical value provided");
414
415 let (zero_buf, decoded) = parser(recorded);
416 assert_eq!(val, decoded, "decoded version must match what we tried to encode");
417 assert_eq!(zero_buf.len(), 0, "must parse record exactly out of provided buffer");
418 }
419 }
420
421 const MINIMAL_LOG_HEADER: u64 = 0x3000000000000029;
424
425 #[fuchsia::test]
426 fn minimal_header() {
427 let mut poked = Header(0);
428 poked.set_type(TRACING_FORMAT_LOG_RECORD_TYPE);
429 poked.set_size_words(2);
430 poked.set_severity(Severity::Info.into_primitive());
431
432 assert_eq!(
433 poked.0, MINIMAL_LOG_HEADER,
434 "minimal log header should only describe type, size, and severity"
435 );
436 }
437
438 #[fuchsia::test]
439 fn no_args_roundtrip() {
440 let mut expected_record = MINIMAL_LOG_HEADER.to_le_bytes().to_vec();
441 let timestamp = zx::BootInstant::from_nanos(5_000_000i64);
442 expected_record.extend(timestamp.into_nanos().to_le_bytes());
443
444 assert_roundtrips(
445 Record { timestamp, severity: Severity::Info.into_primitive(), arguments: vec![] },
446 |encoder, val| encoder.write_record(val),
447 parse_record,
448 Some(&expected_record),
449 );
450 }
451
452 #[fuchsia::test]
453 fn signed_arg_roundtrip() {
454 assert_roundtrips(
455 Argument::other("signed", -1999),
456 |encoder, val| encoder.write_argument(val),
457 parse_argument,
458 None,
459 );
460 }
461
462 #[fuchsia::test]
463 fn unsigned_arg_roundtrip() {
464 assert_roundtrips(
465 Argument::other("unsigned", 42),
466 |encoder, val| encoder.write_argument(val),
467 parse_argument,
468 None,
469 );
470 }
471
472 #[fuchsia::test]
473 fn text_arg_roundtrip() {
474 assert_roundtrips(
475 Argument::other("stringarg", "owo"),
476 |encoder, val| encoder.write_argument(val),
477 parse_argument,
478 None,
479 );
480 }
481
482 #[fuchsia::test]
483 fn float_arg_roundtrip() {
484 assert_roundtrips(
485 Argument::other("float", 3.25),
486 |encoder, val| encoder.write_argument(val),
487 parse_argument,
488 None,
489 );
490 }
491
492 #[fuchsia::test]
493 fn bool_arg_roundtrip() {
494 assert_roundtrips(
495 Argument::other("bool", false),
496 |encoder, val| encoder.write_argument(val),
497 parse_argument,
498 None,
499 );
500 }
501
502 #[fuchsia::test]
503 fn arg_of_each_type_roundtrips() {
504 assert_roundtrips(
505 Record {
506 timestamp: zx::BootInstant::get(),
507 severity: Severity::Warn.into_primitive(),
508 arguments: vec![
509 Argument::other("signed", -10),
510 Argument::other("unsigned", 7),
511 Argument::other("float", 3.25),
512 Argument::other("bool", true),
513 Argument::other("msg", "test message one"),
514 ],
515 },
516 |encoder, val| encoder.write_record(val),
517 parse_record,
518 None,
519 );
520 }
521
522 #[fuchsia::test]
523 fn multiple_string_args() {
524 assert_roundtrips(
525 Record {
526 timestamp: zx::BootInstant::get(),
527 severity: Severity::Trace.into_primitive(),
528 arguments: vec![
529 Argument::other("msg", "test message one"),
530 Argument::other("msg", "test message two"),
531 Argument::other("msg", "test message three"),
532 ],
533 },
534 |encoder, val| encoder.write_record(val),
535 parse_record,
536 None,
537 );
538 }
539
540 #[fuchsia::test]
541 fn invalid_records() {
542 let mut encoder = Encoder::new(Cursor::new(vec![0; BUF_LEN]), EncoderOpts::default());
544 let mut header = Header(0);
545 header.set_type(TRACING_FORMAT_LOG_RECORD_TYPE);
546 header.set_size_words(0); encoder.buf.put_u64_le(header.0).unwrap();
548 encoder.buf.put_i64_le(zx::BootInstant::get().into_nanos()).unwrap();
549 encoder.write_argument(Argument::other("msg", "test message one")).unwrap();
550 assert!(crate::parse::parse_record(encoder.buf.get_ref()).is_err());
551 }
552}