| //! Encoding diagnostic records using the Fuchsia Tracing format. |
| |
| use { |
| crate::{ArgType, Header, StreamError, StringRef}, |
| fidl_fuchsia_diagnostics_stream::{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); |
| header.set_severity(record.severity.into_primitive()); |
| |
| 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::for_str(&argument.name).mask()); |
| |
| match &argument.value { |
| Value::SignedInt(s) => { |
| header.set_type(ArgType::I64 as u8); |
| self.write_i64(*s) |
| } |
| Value::UnsignedInt(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::for_str(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); |
| } |
| } |