blob: e8bb183143cf13a4905a45880ee909835acec629 [file] [log] [blame]
//! Encoding diagnostic records using the Fuchsia Tracing format.
use {
crate::{ArgType, Header, StreamError, StringRef},
fidl_fuchsia_diagnostics_streaming::{Argument, Record, Value},
std::io::Cursor,
};
/// An `Encoder` wraps any value implementing `BufMutShared` and writes diagnostic stream records
/// into it.
pub struct Encoder<B> {
pub(crate) buf: B,
}
impl<B> Encoder<B>
where
B: BufMutShared,
{
/// Create a new `Encoder` from the provided buffer.
pub fn new(buf: B) -> Self {
Self { buf }
}
/// Write a record with this encoder.
///
/// Fails if there is insufficient space in the buffer for encoding, but it does not yet attempt
/// to zero out any partial record writes.
pub fn write_record(&mut self, record: &Record) -> Result<(), StreamError> {
// TODO(adamperry): on failure, zero out the region we were using
let starting_idx = self.buf.cursor();
let header_slot = self.buf.put_slot(std::mem::size_of::<u64>())?;
self.write_i64(record.timestamp)?;
for arg in &record.arguments {
self.write_argument(arg)?;
}
let mut header = Header(0);
header.set_type(crate::TRACING_FORMAT_LOG_RECORD_TYPE);
let length = self.buf.cursor() - starting_idx;
header.set_len(length);
assert_eq!(length % 8, 0, "all records must be written 8-byte aligned");
self.buf.fill_slot(header_slot, &header.0.to_le_bytes()[..]);
Ok(())
}
pub(super) fn write_argument(&mut self, argument: &Argument) -> Result<(), StreamError> {
let starting_idx = self.buf.cursor();
let header_slot = self.buf.put_slot(std::mem::size_of::<Header>())?;
let mut header = Header(0);
// TODO(adamperry) if the arg type is boolean then we can skip it if false right?
// TODO(adamperry) we can also switch to a null arg in the tracing format for true values
self.write_string(&argument.name)?;
header.set_name_ref(StringRef::Inline(&argument.name).mask());
match &argument.value {
Value::Signed(s) => {
header.set_type(ArgType::I64 as u8);
self.write_i64(*s)
}
Value::Unsigned(u) => {
header.set_type(ArgType::U64 as u8);
self.write_u64(*u)
}
Value::Floating(f) => {
header.set_type(ArgType::F64 as u8);
self.write_f64(*f)
}
Value::Text(t) => {
header.set_type(ArgType::String as u8);
header.set_value_ref(StringRef::Inline(t).mask());
self.write_string(t)
}
_ => Err(StreamError::Unsupported),
}?;
let record_len = self.buf.cursor() - starting_idx;
assert_eq!(record_len % 8, 0, "arguments must be 8-byte aligned");
header.set_size_words((record_len / 8) as u16);
self.buf.fill_slot(header_slot, &header.0.to_le_bytes()[..]);
Ok(())
}
/// Write an unsigned integer.
fn write_u64(&mut self, n: u64) -> Result<(), StreamError> {
self.buf.put_u64_le(n).map_err(|_| StreamError::BufferSize)
}
/// Write a signed integer.
fn write_i64(&mut self, n: i64) -> Result<(), StreamError> {
self.buf.put_i64_le(n).map_err(|_| StreamError::BufferSize)
}
/// Write a floating-point number.
fn write_f64(&mut self, n: f64) -> Result<(), StreamError> {
self.buf.put_f64(n).map_err(|_| StreamError::BufferSize)
}
/// Write a string padded to 8-byte alignment.
fn write_string(&mut self, src: &str) -> Result<(), StreamError> {
self.buf.put_slice(src.as_bytes()).map_err(|_| StreamError::BufferSize)?;
unsafe {
let align = std::mem::size_of::<u64>();
let num_padding_bytes = (align - src.len() % align) % align;
// TODO(adamperry) need to enforce that the buffer is zeroed
self.buf.advance_cursor(num_padding_bytes);
}
Ok(())
}
}
/// Analogous to `bytes::BufMut`, but immutably-sized and appropriate for use in shared memory.
pub trait BufMutShared {
/// Returns the number of total bytes this container can store. Shared memory buffers are not
/// expected to resize and this should return the same value during the entire lifetime of the
/// buffer.
fn capacity(&self) -> usize;
/// Returns the current position into which the next write is expected.
fn cursor(&self) -> usize;
/// Advance the write cursor by `n` bytes. This is marked unsafe because a malformed caller may
/// cause a subsequent out-of-bounds write.
unsafe fn advance_cursor(&mut self, n: usize);
/// Write a copy of the `src` slice into the buffer, starting at the provided offset.
///
/// Implementations are not expected to bounds check the requested copy, although they may do
/// so and still satisfy this trait's contract.
unsafe fn put_slice_at(&mut self, src: &[u8], offset: usize);
/// Returns whether the buffer has sufficient remaining capacity to write an incoming value.
fn has_remaining(&self, num_bytes: usize) -> bool {
(self.cursor() + num_bytes) <= self.capacity()
}
/// Advances the write cursor without immediately writing any bytes to the buffer. The returned
/// struct offers the ability to later write to the provided portion of the buffer.
fn put_slot(&mut self, width: usize) -> Result<WriteSlot, StreamError> {
if self.has_remaining(width) {
let slot = WriteSlot { range: self.cursor()..(self.cursor() + width) };
unsafe {
self.advance_cursor(width);
}
Ok(slot)
} else {
Err(StreamError::BufferSize)
}
}
/// Write `src` into the provided slot that was created at a previous point in the stream.
fn fill_slot(&mut self, slot: WriteSlot, src: &[u8]) {
assert_eq!(
src.len(),
slot.range.end - slot.range.start,
"WriteSlots can only insert exactly-sized content into the buffer"
);
unsafe {
self.put_slice_at(src, slot.range.start);
}
}
/// Writes the contents of the `src` buffer to `self`, starting at `self.cursor()` and
/// advancing the cursor by `src.len()`.
///
/// # Panics
///
/// This function panics if there is not enough remaining capacity in `self`.
fn put_slice(&mut self, src: &[u8]) -> Result<(), ()> {
if self.has_remaining(src.len()) {
unsafe {
self.put_slice_at(src, self.cursor());
self.advance_cursor(src.len());
}
Ok(())
} else {
Err(())
}
}
/// Writes an unsigned 64 bit integer to `self` in little-endian byte order.
///
/// Advances the cursor by 8 bytes.
///
/// # Examples
///
/// ```
/// use bytes::BufMut;
///
/// let mut buf = vec![0; 8];
/// buf.put_u64_le_at(0x0102030405060708, 0);
/// assert_eq!(buf, b"\x08\x07\x06\x05\x04\x03\x02\x01");
/// ```
///
/// # Panics
///
/// This function panics if there is not enough remaining capacity in `self`.
fn put_u64_le(&mut self, n: u64) -> Result<(), ()> {
self.put_slice(&n.to_le_bytes())
}
/// Writes a signed 64 bit integer to `self` in little-endian byte order.
///
/// The cursor position is advanced by 8.
///
/// # Examples
///
/// ```
/// use bytes::BufMut;
///
/// let mut buf = vec![0; 8];
/// buf.put_i64_le_at(0x0102030405060708, 0);
/// assert_eq!(buf, b"\x08\x07\x06\x05\x04\x03\x02\x01");
/// ```
///
/// # Panics
///
/// This function panics if there is not enough remaining capacity in `self`.
fn put_i64_le(&mut self, n: i64) -> Result<(), ()> {
self.put_slice(&n.to_le_bytes())
}
/// Writes a double-precision IEEE 754 floating point number to `self`.
///
/// The cursor position is advanced by 8.
///
/// # Examples
///
/// ```
/// use bytes::BufMut;
///
/// let mut buf = vec![];
/// buf.put_i64_le(0x0102030405060708);
/// assert_eq!(buf, b"\x08\x07\x06\x05\x04\x03\x02\x01");
/// ```
///
/// # Panics
///
/// This function panics if there is not enough remaining capacity in `self`.
fn put_f64(&mut self, n: f64) -> Result<(), ()> {
self.put_slice(&n.to_bits().to_ne_bytes())
}
}
/// A region of the buffer which was advanced past and can later be filled in.
#[must_use]
pub struct WriteSlot {
range: std::ops::Range<usize>,
}
impl<'a, T: BufMutShared + ?Sized> BufMutShared for &'a mut T {
fn capacity(&self) -> usize {
(**self).capacity()
}
fn cursor(&self) -> usize {
(**self).cursor()
}
unsafe fn advance_cursor(&mut self, n: usize) {
(**self).advance_cursor(n);
}
unsafe fn put_slice_at(&mut self, to_put: &[u8], offset: usize) {
(**self).put_slice_at(to_put, offset);
}
}
impl<T: BufMutShared + ?Sized> BufMutShared for Box<T> {
fn capacity(&self) -> usize {
(**self).capacity()
}
fn cursor(&self) -> usize {
(**self).cursor()
}
unsafe fn advance_cursor(&mut self, n: usize) {
(**self).advance_cursor(n);
}
unsafe fn put_slice_at(&mut self, to_put: &[u8], offset: usize) {
(**self).put_slice_at(to_put, offset);
}
}
impl BufMutShared for Cursor<Vec<u8>> {
fn capacity(&self) -> usize {
self.get_ref().len()
}
fn cursor(&self) -> usize {
self.position() as usize
}
unsafe fn advance_cursor(&mut self, n: usize) {
self.set_position(self.position() + n as u64);
}
unsafe fn put_slice_at(&mut self, to_put: &[u8], offset: usize) {
let dest = &mut self.get_mut()[offset..(offset + to_put.len())];
dest.copy_from_slice(to_put);
}
}