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
17#[cfg(target_os = "fuchsia")]
18pub use zx;
19
20#[cfg(not(target_os = "fuchsia"))]
21pub mod zx {
23 use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
24
25 pub mod sys {
27 #[allow(non_camel_case_types)]
28 pub type zx_koid_t = u64;
30
31 #[allow(non_camel_case_types)]
32 pub type zx_time_t = i64;
34 }
35
36 #[derive(
38 Clone,
39 Copy,
40 Debug,
41 Default,
42 Eq,
43 Hash,
44 Ord,
45 PartialEq,
46 PartialOrd,
47 FromBytes,
48 IntoBytes,
49 Immutable,
50 KnownLayout,
51 )]
52 #[repr(transparent)]
53 pub struct BootInstant(i64);
54
55 impl BootInstant {
56 pub const ZERO: Self = Self(0);
58
59 pub const fn from_nanos(nanos: i64) -> Self {
61 Self(nanos)
62 }
63
64 pub const fn into_nanos(self) -> i64 {
66 self.0
67 }
68
69 pub fn get() -> Self {
71 let nanos = match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) {
72 Ok(d) => d.as_nanos() as i64,
73 Err(e) => -(e.duration().as_nanos() as i64),
74 };
75 Self::from_nanos(nanos)
76 }
77 }
78
79 #[derive(
81 Clone,
82 Copy,
83 Debug,
84 Default,
85 Eq,
86 Hash,
87 Ord,
88 PartialEq,
89 PartialOrd,
90 FromBytes,
91 IntoBytes,
92 Immutable,
93 KnownLayout,
94 )]
95 #[repr(transparent)]
96 pub struct Koid(u64);
97
98 impl Koid {
99 pub const fn from_raw(raw: u64) -> Self {
101 Self(raw)
102 }
103
104 pub const fn raw_koid(&self) -> u64 {
106 self.0
107 }
108 }
109}
110
111pub use constants::*;
112
113pub type RawSeverity = u8;
115
116#[derive(Clone, Debug, PartialEq)]
118pub struct Record<'a> {
119 pub timestamp: zx::BootInstant,
121 pub severity: RawSeverity,
123 pub arguments: Vec<Argument<'a>>,
125}
126
127impl Record<'_> {
128 pub fn into_owned(self) -> Record<'static> {
130 Record {
131 timestamp: self.timestamp,
132 severity: self.severity,
133 arguments: self.arguments.into_iter().map(|arg| arg.into_owned()).collect(),
134 }
135 }
136}
137
138#[derive(Clone, Debug, PartialEq)]
140pub enum Argument<'a> {
141 Pid(zx::Koid),
143 Tid(zx::Koid),
145 Tag(Cow<'a, str>),
147 Dropped(u64),
149 File(Cow<'a, str>),
151 Message(Cow<'a, str>),
153 Line(u64),
155 Other {
157 name: Cow<'a, str>,
159 value: Value<'a>,
161 },
162}
163
164impl<'a> Argument<'a> {
165 pub fn new(name: impl Into<Cow<'a, str>>, value: impl Into<Value<'a>>) -> Self {
167 let name: Cow<'a, str> = name.into();
168 match (name.as_ref(), value.into()) {
169 (constants::PID, Value::UnsignedInt(pid)) => Self::pid(zx::Koid::from_raw(pid)),
170 (constants::TID, Value::UnsignedInt(pid)) => Self::tid(zx::Koid::from_raw(pid)),
171 (constants::TAG, Value::Text(tag)) => Self::tag(tag),
172 (constants::NUM_DROPPED, Value::UnsignedInt(dropped)) => Self::dropped(dropped),
173 (constants::FILE, Value::Text(file)) => Self::file(file),
174 (constants::LINE, Value::UnsignedInt(line)) => Self::line(line),
175 (constants::MESSAGE, Value::Text(msg)) => Self::message(msg),
176 (_, value) => Self::other(name, value),
177 }
178 }
179
180 #[inline]
181 pub fn pid(koid: zx::Koid) -> Self {
183 Argument::Pid(koid)
184 }
185
186 #[inline]
187 pub fn tid(koid: zx::Koid) -> Self {
189 Argument::Tid(koid)
190 }
191
192 #[inline]
193 pub fn message(message: impl Into<Cow<'a, str>>) -> Self {
195 Argument::Message(message.into())
196 }
197
198 #[inline]
199 pub fn tag(value: impl Into<Cow<'a, str>>) -> Self {
201 Argument::Tag(value.into())
202 }
203
204 #[inline]
205 pub fn dropped(value: u64) -> Self {
207 Argument::Dropped(value)
208 }
209
210 #[inline]
211 pub fn file(value: impl Into<Cow<'a, str>>) -> Self {
213 Argument::File(value.into())
214 }
215
216 #[inline]
217 pub fn line(value: u64) -> Self {
219 Argument::Line(value)
220 }
221
222 #[inline]
223 pub fn other(name: impl Into<Cow<'a, str>>, value: impl Into<Value<'a>>) -> Self {
225 Argument::Other { name: name.into(), value: value.into() }
226 }
227
228 pub fn into_owned(self) -> Argument<'static> {
230 match self {
231 Self::Pid(pid) => Argument::Pid(pid),
232 Self::Tid(tid) => Argument::Tid(tid),
233 Self::Tag(tag) => Argument::Tag(Cow::Owned(tag.into_owned())),
234 Self::Dropped(dropped) => Argument::Dropped(dropped),
235 Self::File(file) => Argument::File(Cow::Owned(file.into_owned())),
236 Self::Line(line) => Argument::Line(line),
237 Self::Message(msg) => Argument::Message(Cow::Owned(msg.into_owned())),
238 Self::Other { name, value } => {
239 Argument::Other { name: Cow::Owned(name.into_owned()), value: value.into_owned() }
240 }
241 }
242 }
243
244 pub fn name(&self) -> &str {
246 match self {
247 Self::Pid(_) => constants::PID,
248 Self::Tid(_) => constants::TID,
249 Self::Tag(_) => constants::TAG,
250 Self::Dropped(_) => constants::NUM_DROPPED,
251 Self::File(_) => constants::FILE,
252 Self::Line(_) => constants::LINE,
253 Self::Message(_) => constants::MESSAGE,
254 Self::Other { name, .. } => name.borrow(),
255 }
256 }
257
258 pub fn value(&'a self) -> Value<'a> {
260 match self {
261 Self::Pid(pid) => Value::UnsignedInt(pid.raw_koid()),
262 Self::Tid(tid) => Value::UnsignedInt(tid.raw_koid()),
263 Self::Tag(tag) => Value::Text(Cow::Borrowed(tag.as_ref())),
264 Self::Dropped(num_dropped) => Value::UnsignedInt(*num_dropped),
265 Self::File(file) => Value::Text(Cow::Borrowed(file.as_ref())),
266 Self::Message(msg) => Value::Text(Cow::Borrowed(msg.as_ref())),
267 Self::Line(line) => Value::UnsignedInt(*line),
268 Self::Other { value, .. } => value.clone_borrowed(),
269 }
270 }
271}
272
273#[derive(Clone, Debug, PartialEq)]
275pub enum Value<'a> {
276 SignedInt(i64),
278 UnsignedInt(u64),
280 Floating(f64),
282 Boolean(bool),
284 Text(Cow<'a, str>),
286}
287
288impl<'a> Value<'a> {
289 fn into_owned(self) -> Value<'static> {
290 match self {
291 Self::Text(s) => Value::Text(Cow::Owned(s.into_owned())),
292 Self::SignedInt(n) => Value::SignedInt(n),
293 Self::UnsignedInt(n) => Value::UnsignedInt(n),
294 Self::Floating(n) => Value::Floating(n),
295 Self::Boolean(n) => Value::Boolean(n),
296 }
297 }
298
299 fn clone_borrowed(&'a self) -> Value<'a> {
300 match self {
301 Self::Text(s) => Self::Text(Cow::Borrowed(s.as_ref())),
302 Self::SignedInt(n) => Self::SignedInt(*n),
303 Self::UnsignedInt(n) => Self::UnsignedInt(*n),
304 Self::Floating(n) => Self::Floating(*n),
305 Self::Boolean(n) => Self::Boolean(*n),
306 }
307 }
308}
309
310impl From<i32> for Value<'_> {
311 fn from(number: i32) -> Value<'static> {
312 Value::SignedInt(number as i64)
313 }
314}
315
316impl From<i64> for Value<'_> {
317 fn from(number: i64) -> Value<'static> {
318 Value::SignedInt(number)
319 }
320}
321
322impl From<u64> for Value<'_> {
323 fn from(number: u64) -> Value<'static> {
324 Value::UnsignedInt(number)
325 }
326}
327
328impl From<u32> for Value<'_> {
329 fn from(number: u32) -> Value<'static> {
330 Value::UnsignedInt(number as u64)
331 }
332}
333
334impl From<zx::Koid> for Value<'_> {
335 fn from(koid: zx::Koid) -> Value<'static> {
336 Value::UnsignedInt(koid.raw_koid())
337 }
338}
339
340impl From<f64> for Value<'_> {
341 fn from(number: f64) -> Value<'static> {
342 Value::Floating(number)
343 }
344}
345
346impl<'a> From<&'a str> for Value<'a> {
347 fn from(text: &'a str) -> Value<'a> {
348 Value::Text(Cow::Borrowed(text))
349 }
350}
351
352impl From<String> for Value<'static> {
353 fn from(text: String) -> Value<'static> {
354 Value::Text(Cow::Owned(text))
355 }
356}
357
358impl<'a> From<Cow<'a, str>> for Value<'a> {
359 fn from(text: Cow<'a, str>) -> Value<'a> {
360 Value::Text(text)
361 }
362}
363
364impl From<bool> for Value<'static> {
365 fn from(boolean: bool) -> Value<'static> {
366 Value::Boolean(boolean)
367 }
368}
369
370pub const MAX_SIZE_WORDS: u16 = 4095;
372
373pub const LOG_CONTROL_BIT: u32 = 1 << 31;
376
377bitfield! {
378 #[derive(IntoBytes, FromBytes, KnownLayout, Immutable)]
387 pub struct Header(u64);
388 impl Debug;
389
390 pub u8, raw_type, set_type: 3, 0;
392
393 pub u16, size_words, set_size_words: 15, 4;
395
396 u16, name_ref, set_name_ref: 31, 16;
398
399 bool, bool_val, set_bool_val: 32;
401
402 u16, value_ref, set_value_ref: 47, 32;
404
405 pub u32, tag, set_tag: 47, 16;
407
408 pub u8, severity, set_severity: 63, 56;
410}
411
412impl Header {
413 pub fn set_len(&mut self, new_len: usize) {
415 assert_eq!(new_len % 8, 0, "encoded message must be 8-byte aligned");
416 self.set_size_words((new_len / 8) as u16 + u16::from(!new_len.is_multiple_of(8)))
417 }
418}
419
420#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
425pub enum Metatag {
426 Target,
431}
432
433#[repr(u8)]
437enum ArgType {
438 Null = 0,
439 I32 = 1,
440 U32 = 2,
441 I64 = 3,
442 U64 = 4,
443 F64 = 5,
444 String = 6,
445 Pointer = 7,
446 Koid = 8,
447 Bool = 9,
448}
449
450impl TryFrom<u8> for ArgType {
451 type Error = parse::ParseError;
452 fn try_from(b: u8) -> Result<Self, Self::Error> {
453 Ok(match b {
454 0 => ArgType::Null,
455 1 => ArgType::I32,
456 2 => ArgType::U32,
457 3 => ArgType::I64,
458 4 => ArgType::U64,
459 5 => ArgType::F64,
460 6 => ArgType::String,
461 7 => ArgType::Pointer,
462 8 => ArgType::Koid,
463 9 => ArgType::Bool,
464 _ => return Err(parse::ParseError::ValueOutOfValidRange),
465 })
466 }
467}
468
469#[cfg(test)]
470mod tests {
471 use super::*;
472 use crate::encode::{Encoder, EncoderOpts, EncodingError, MutableBuffer};
473 use fidl_fuchsia_diagnostics_types::Severity;
474 use std::fmt::Debug;
475 use std::io::Cursor;
476
477 fn parse_argument(bytes: &[u8]) -> (&[u8], Argument<'static>) {
478 let (decoded_from_full, remaining) = crate::parse::parse_argument(bytes).unwrap();
479 (remaining, decoded_from_full.into_owned())
480 }
481
482 fn parse_record(bytes: &[u8]) -> (&[u8], Record<'static>) {
483 let (decoded_from_full, remaining) = crate::parse::parse_record(bytes).unwrap();
484 (remaining, decoded_from_full.into_owned())
485 }
486
487 const BUF_LEN: usize = 1024;
488
489 pub(crate) fn assert_roundtrips<T, F>(
490 val: T,
491 encoder_method: impl Fn(&mut Encoder<Cursor<Vec<u8>>>, &T) -> Result<(), EncodingError>,
492 parser: F,
493 canonical: Option<&[u8]>,
494 ) where
495 T: Debug + PartialEq,
496 F: Fn(&[u8]) -> (&[u8], T),
497 {
498 let mut encoder = Encoder::new(Cursor::new(vec![0; BUF_LEN]), EncoderOpts::default());
499 encoder_method(&mut encoder, &val).unwrap();
500
501 let (_, decoded_from_full) = parser(encoder.buf.get_ref());
503 assert_eq!(val, decoded_from_full, "decoded version with trailing padding must match");
504
505 if let Some(canonical) = canonical {
506 let recorded = encoder.buf.get_ref().split_at(canonical.len()).0;
507 assert_eq!(canonical, recorded, "encoded repr must match the canonical value provided");
508
509 let (zero_buf, decoded) = parser(recorded);
510 assert_eq!(val, decoded, "decoded version must match what we tried to encode");
511 assert_eq!(zero_buf.len(), 0, "must parse record exactly out of provided buffer");
512 }
513 }
514
515 const MINIMAL_LOG_HEADER: u64 = 0x3000000000000029;
518
519 #[fuchsia::test]
520 fn minimal_header() {
521 let mut poked = Header(0);
522 poked.set_type(TRACING_FORMAT_LOG_RECORD_TYPE);
523 poked.set_size_words(2);
524 poked.set_severity(Severity::Info.into_primitive());
525
526 assert_eq!(
527 poked.0, MINIMAL_LOG_HEADER,
528 "minimal log header should only describe type, size, and severity"
529 );
530 }
531
532 #[fuchsia::test]
533 fn no_args_roundtrip() {
534 let mut expected_record = MINIMAL_LOG_HEADER.to_le_bytes().to_vec();
535 let timestamp = zx::BootInstant::from_nanos(5_000_000i64);
536 expected_record.extend(timestamp.into_nanos().to_le_bytes());
537
538 assert_roundtrips(
539 Record { timestamp, severity: Severity::Info.into_primitive(), arguments: vec![] },
540 |encoder, val| encoder.write_record(val),
541 parse_record,
542 Some(&expected_record),
543 );
544 }
545
546 #[fuchsia::test]
547 fn signed_arg_roundtrip() {
548 assert_roundtrips(
549 Argument::other("signed", -1999),
550 |encoder, val| encoder.write_argument(val),
551 parse_argument,
552 None,
553 );
554 }
555
556 #[fuchsia::test]
557 fn unsigned_arg_roundtrip() {
558 assert_roundtrips(
559 Argument::other("unsigned", 42),
560 |encoder, val| encoder.write_argument(val),
561 parse_argument,
562 None,
563 );
564 }
565
566 #[fuchsia::test]
567 fn text_arg_roundtrip() {
568 assert_roundtrips(
569 Argument::other("stringarg", "owo"),
570 |encoder, val| encoder.write_argument(val),
571 parse_argument,
572 None,
573 );
574 }
575
576 #[fuchsia::test]
577 fn float_arg_roundtrip() {
578 assert_roundtrips(
579 Argument::other("float", 3.25),
580 |encoder, val| encoder.write_argument(val),
581 parse_argument,
582 None,
583 );
584 }
585
586 #[fuchsia::test]
587 fn bool_arg_roundtrip() {
588 assert_roundtrips(
589 Argument::other("bool", false),
590 |encoder, val| encoder.write_argument(val),
591 parse_argument,
592 None,
593 );
594 }
595
596 #[fuchsia::test]
597 fn arg_of_each_type_roundtrips() {
598 assert_roundtrips(
599 Record {
600 timestamp: zx::BootInstant::get(),
601 severity: Severity::Warn.into_primitive(),
602 arguments: vec![
603 Argument::other("signed", -10),
604 Argument::other("unsigned", 7),
605 Argument::other("float", 3.25),
606 Argument::other("bool", true),
607 Argument::other("msg", "test message one"),
608 ],
609 },
610 |encoder, val| encoder.write_record(val),
611 parse_record,
612 None,
613 );
614 }
615
616 #[fuchsia::test]
617 fn multiple_string_args() {
618 assert_roundtrips(
619 Record {
620 timestamp: zx::BootInstant::get(),
621 severity: Severity::Trace.into_primitive(),
622 arguments: vec![
623 Argument::other("msg", "test message one"),
624 Argument::other("msg", "test message two"),
625 Argument::other("msg", "test message three"),
626 ],
627 },
628 |encoder, val| encoder.write_record(val),
629 parse_record,
630 None,
631 );
632 }
633
634 #[fuchsia::test]
635 fn invalid_records() {
636 let mut encoder = Encoder::new(Cursor::new(vec![0; BUF_LEN]), EncoderOpts::default());
638 let mut header = Header(0);
639 header.set_type(TRACING_FORMAT_LOG_RECORD_TYPE);
640 header.set_size_words(0); encoder.buf.put_u64_le(header.0).unwrap();
642 encoder.buf.put_i64_le(zx::BootInstant::get().into_nanos()).unwrap();
643 encoder.write_argument(Argument::other("msg", "test message one")).unwrap();
644 assert!(crate::parse::parse_record(encoder.buf.get_ref()).is_err());
645 }
646}