#![no_std] use core::num; use num_enum::IntoPrimitive; pub const LOG_BUF_CAPACITY: usize = 8192; pub const LOG_FIELDS: usize = 6; pub type LogValueLength = u16; #[repr(u8)] #[derive(Copy, Clone, Eq, PartialEq, Debug, Hash, IntoPrimitive)] pub enum Level { /// The "error" level. /// /// Designates very serious errors. Error = 1, /// The "warn" level. /// /// Designates hazardous situations. Warn, /// The "info" level. /// /// Designates useful information. Info, /// The "debug" level. /// /// Designates lower priority information. Debug, /// The "trace" level. /// /// Designates very low priority, often extremely verbose, information. Trace, } macro_rules! impl_formatter_for_types { ($trait:path : { $($type:ty),*}) => { $( impl $trait for $type {} )* }; } pub trait DefaultFormatter {} impl_formatter_for_types!( DefaultFormatter: { bool, i8, i16, i32, i64, isize, u8, u16, u32, u64, usize, f32, f64, char, str, &str } ); pub trait LowerHexFormatter {} impl_formatter_for_types!( LowerHexFormatter: { i8, i16, i32, i64, isize, u8, u16, u32, u64, usize, &[u8] } ); pub trait UpperHexFormatter {} impl_formatter_for_types!( UpperHexFormatter: { i8, i16, i32, i64, isize, u8, u16, u32, u64, usize, &[u8] } ); pub trait IpFormatter {} impl IpFormatter for u32 {} impl IpFormatter for [u8; 16] {} impl IpFormatter for [u16; 8] {} pub trait LowerMacFormatter {} impl LowerMacFormatter for [u8; 6] {} pub trait UpperMacFormatter {} impl UpperMacFormatter for [u8; 6] {} #[repr(u8)] #[derive(Copy, Clone, Debug, IntoPrimitive)] pub enum RecordField { Target = 1, Level, Module, File, Line, NumArgs, } /// Types which are supported by aya-log and can be safely sent from eBPF /// programs to userspace. #[repr(u8)] #[derive(Copy, Clone, Debug, IntoPrimitive)] pub enum Argument { DisplayHint, I8, I16, I32, I64, Isize, U8, U16, U32, U64, Usize, F32, F64, /// `[u8; 6]` array which represents a MAC address. ArrU8Len6, /// `[u8; 16]` array which represents an IPv6 address. ArrU8Len16, /// `[u16; 8]` array which represents an IPv6 address. ArrU16Len8, Bytes, Str, } /// All display hints #[repr(u8)] #[derive(Copy, Clone, Debug, PartialEq, Eq, IntoPrimitive)] pub enum DisplayHint { /// Default string representation. Default = 1, /// `:x` LowerHex, /// `:X` UpperHex, /// `:i` Ip, /// `:mac` LowerMac, /// `:MAC` UpperMac, } // Must be inlined, else the BPF backend emits: // // llvm: :0:0: in function _ZN14aya_log_common5write17hc9ed05433e23a663E { i64, i64 } (i8, ptr, i64, ptr, i64): only integer returns supported #[inline(always)] pub(crate) fn write(tag: u8, value: &[u8], buf: &mut [u8]) -> Result { let wire_len: LogValueLength = value .len() .try_into() .map_err(|num::TryFromIntError { .. }| ())?; let mut size = 0; macro_rules! copy_from_slice { ($value:expr) => {{ let buf = buf.get_mut(size..).ok_or(())?; let buf = buf.get_mut(..$value.len()).ok_or(())?; buf.copy_from_slice($value); size += $value.len(); }}; } copy_from_slice!(&[tag]); copy_from_slice!(&wire_len.to_ne_bytes()); copy_from_slice!(value); Ok(size) } pub trait WriteToBuf { #[allow(clippy::result_unit_err)] fn write(self, buf: &mut [u8]) -> Result; } macro_rules! impl_write_to_buf { ($type:ident, $arg_type:expr) => { impl WriteToBuf for $type { // This need not be inlined because the return value is Result where N is // mem::size_of<$type>, which is a compile-time constant. #[inline(never)] fn write(self, buf: &mut [u8]) -> Result { write($arg_type.into(), &self.to_ne_bytes(), buf) } } }; } impl_write_to_buf!(i8, Argument::I8); impl_write_to_buf!(i16, Argument::I16); impl_write_to_buf!(i32, Argument::I32); impl_write_to_buf!(i64, Argument::I64); impl_write_to_buf!(isize, Argument::Isize); impl_write_to_buf!(u8, Argument::U8); impl_write_to_buf!(u16, Argument::U16); impl_write_to_buf!(u32, Argument::U32); impl_write_to_buf!(u64, Argument::U64); impl_write_to_buf!(usize, Argument::Usize); impl_write_to_buf!(f32, Argument::F32); impl_write_to_buf!(f64, Argument::F64); impl WriteToBuf for [u8; 16] { // This need not be inlined because the return value is Result where N is 16, which is a // compile-time constant. #[inline(never)] fn write(self, buf: &mut [u8]) -> Result { write(Argument::ArrU8Len16.into(), &self, buf) } } impl WriteToBuf for [u16; 8] { // This need not be inlined because the return value is Result where N is 16, which is a // compile-time constant. #[inline(never)] fn write(self, buf: &mut [u8]) -> Result { let bytes = unsafe { core::mem::transmute::<_, [u8; 16]>(self) }; write(Argument::ArrU16Len8.into(), &bytes, buf) } } impl WriteToBuf for [u8; 6] { // This need not be inlined because the return value is Result where N is 6, which is a // compile-time constant. #[inline(never)] fn write(self, buf: &mut [u8]) -> Result { write(Argument::ArrU8Len6.into(), &self, buf) } } impl WriteToBuf for &[u8] { // Must be inlined, else the BPF backend emits: // // llvm: :0:0: in function _ZN63_$LT$$RF$$u5b$u8$u5d$$u20$as$u20$aya_log_common..WriteToBuf$GT$5write17h08f30a45f7b9f09dE { i64, i64 } (ptr, i64, ptr, i64): only integer returns supported #[inline(always)] fn write(self, buf: &mut [u8]) -> Result { write(Argument::Bytes.into(), self, buf) } } impl WriteToBuf for &str { // Must be inlined, else the BPF backend emits: // // llvm: :0:0: in function _ZN54_$LT$$RF$str$u20$as$u20$aya_log_common..WriteToBuf$GT$5write17h7e2d1ccaa758e2b5E { i64, i64 } (ptr, i64, ptr, i64): only integer returns supported #[inline(always)] fn write(self, buf: &mut [u8]) -> Result { write(Argument::Str.into(), self.as_bytes(), buf) } } impl WriteToBuf for DisplayHint { // This need not be inlined because the return value is Result where N is 1, which is a // compile-time constant. #[inline(never)] fn write(self, buf: &mut [u8]) -> Result { let v: u8 = self.into(); write(Argument::DisplayHint.into(), &v.to_ne_bytes(), buf) } } #[allow(clippy::result_unit_err)] #[doc(hidden)] #[inline(always)] // This function takes too many arguments to not be inlined. pub fn write_record_header( buf: &mut [u8], target: &str, level: Level, module: &str, file: &str, line: u32, num_args: usize, ) -> Result { let level: u8 = level.into(); let mut size = 0; macro_rules! write { ($tag:expr, $value:expr) => {{ let buf = buf.get_mut(size..).ok_or(())?; size += write($tag.into(), $value, buf)?; }}; } write!(RecordField::Target, target.as_bytes()); write!(RecordField::Level, &level.to_ne_bytes()); write!(RecordField::Module, module.as_bytes()); write!(RecordField::File, file.as_bytes()); write!(RecordField::Line, &line.to_ne_bytes()); write!(RecordField::NumArgs, &num_args.to_ne_bytes()); Ok(size) } #[cfg(test)] mod test { use super::*; #[test] fn log_value_length_sufficient() { assert!( LOG_BUF_CAPACITY <= LogValueLength::MAX.into(), "{} > {}", LOG_BUF_CAPACITY, LogValueLength::MAX ); } }