blob: c553f1ee6f966ab96ab6e8b7b6d3b8294b9c5b72 [file] [log] [blame] [edit]
// Copyright 2023 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
use core::cmp::min;
use pw_status::{Error, Result};
use pw_stream::{Cursor, Write};
use pw_varint::VarintEncode;
use crate::MessageWriter;
// The `Argument` enum is used to marshal arguments to pass to the tokenization
// engine.
pub enum Argument<'a> {
String(&'a str),
Varint(i64),
}
impl<'a> From<&'a str> for Argument<'a> {
fn from(val: &'a str) -> Self {
Self::String(val)
}
}
impl From<char> for Argument<'_> {
fn from(val: char) -> Self {
Self::Varint(val as i64)
}
}
impl From<u8> for Argument<'_> {
fn from(val: u8) -> Self {
Self::Varint(val as i64)
}
}
impl From<i32> for Argument<'_> {
fn from(val: i32) -> Self {
Self::Varint(val as i64)
}
}
impl From<u32> for Argument<'_> {
fn from(val: u32) -> Self {
Self::Varint(val as i64)
}
}
// TODO: b/400978670 - investigate whether changing these
// 64bit values to references saves space on 32bit systems.
impl From<i64> for Argument<'_> {
fn from(val: i64) -> Self {
Self::Varint(val)
}
}
impl From<u64> for Argument<'_> {
fn from(val: u64) -> Self {
Self::Varint(val as i64)
}
}
impl From<usize> for Argument<'_> {
fn from(val: usize) -> Self {
Self::Varint(val as i64)
}
}
// Wraps a `Cursor` so that `tokenize_to_buffer` and `tokenize_to_writer` can
// share implementations. It is not meant to be used outside of
// `tokenize_to_buffer`.
struct CursorMessageWriter<'a> {
cursor: Cursor<&'a mut [u8]>,
}
impl MessageWriter for CursorMessageWriter<'_> {
fn new() -> Self {
// Ensure `tokenize_to_buffer` never calls `new()`.
unimplemented!();
}
fn write(&mut self, data: &[u8]) -> Result<()> {
self.cursor.write_all(data)
}
fn remaining(&self) -> usize {
self.cursor.remaining()
}
fn finalize(self) -> Result<()> {
// Ensure `tokenize_to_buffer` never calls `finalize()`.
unimplemented!();
}
}
// Encode a string in Tokenizer format: length byte + data with the high bit of
// the length byte used to signal that the string was truncated.
pub fn encode_string<W: MessageWriter>(writer: &mut W, value: &str) -> Result<()> {
const MAX_STRING_LENGTH: usize = 0x7f;
let string_bytes = value.as_bytes();
// Limit the encoding to the lesser of 127 or the available space in the buffer.
let max_len = min(MAX_STRING_LENGTH, writer.remaining().saturating_sub(1));
let overflow = max_len < string_bytes.len();
let len = min(max_len, string_bytes.len());
// First byte of an encoded string is it's length.
let mut header = len as u8;
// The high bit of the first byte is used to indicate if the string was
// truncated.
if overflow {
header |= 0x80;
}
writer.write(&[header])?;
writer.write(&string_bytes[..len])
}
// Write out a tokenized message to an already created `MessageWriter`.
fn tokenize_engine<W: crate::MessageWriter>(
writer: &mut W,
token: u32,
args: &[Argument<'_>],
) -> Result<()> {
writer.write(&token.to_le_bytes()[..])?;
for arg in args {
match arg {
Argument::String(s) => encode_string(writer, s)?,
Argument::Varint(i) => {
let mut encode_buffer = [0u8; 10];
let len = i.varint_encode(&mut encode_buffer)?;
let encoded_slice = encode_buffer.get(..len).ok_or(Error::OutOfRange)?;
writer.write(encoded_slice)?;
}
}
}
Ok(())
}
#[inline(never)]
pub fn tokenize_to_buffer(buffer: &mut [u8], token: u32, args: &[Argument<'_>]) -> Result<usize> {
let mut writer = CursorMessageWriter {
cursor: Cursor::new(buffer),
};
tokenize_engine(&mut writer, token, args)?;
Ok(writer.cursor.position())
}
#[inline(never)]
pub fn tokenize_to_buffer_no_args(buffer: &mut [u8], token: u32) -> Result<usize> {
let token_bytes = &token.to_le_bytes()[..];
let token_len = token_bytes.len();
if buffer.len() < token_len {
return Err(Error::OutOfRange);
}
buffer[..token_len].copy_from_slice(token_bytes);
Ok(token_len)
}
#[inline(never)]
pub fn tokenize_to_writer<W: crate::MessageWriter>(
token: u32,
args: &[Argument<'_>],
) -> Result<()> {
let mut writer = W::new();
match tokenize_engine(&mut writer, token, args) {
// Still finalize the writer even if the buffer
// is full so as to avoid loosing the entire
// log message.
Ok(_) | Err(Error::OutOfRange) => writer.finalize(),
Err(error) => Err(error),
}
}
#[inline(never)]
pub fn tokenize_to_writer_no_args<W: crate::MessageWriter>(token: u32) -> Result<()> {
let mut writer = W::new();
let result = writer.write(&token.to_le_bytes()[..]);
match result {
// Still finalize the writer even if the buffer
// is full so as to avoid loosing the entire
// log message.
Ok(_) | Err(Error::OutOfRange) => writer.finalize(),
Err(error) => Err(error),
}
}
#[cfg(test)]
mod test {
use pw_stream::Seek;
use super::*;
fn do_string_encode_test<const BUFFER_LEN: usize>(value: &str, expected: &[u8]) {
let mut buffer = [0u8; BUFFER_LEN];
let mut writer = CursorMessageWriter {
cursor: Cursor::new(&mut buffer),
};
encode_string(&mut writer, value).unwrap();
let len = writer.cursor.stream_position().unwrap() as usize;
let buffer = writer.cursor.into_inner();
assert_eq!(len, expected.len());
assert_eq!(&buffer[..len], expected);
}
#[test]
fn test_string_encode() {
do_string_encode_test::<64>("test", b"\x04test");
do_string_encode_test::<4>("test", b"\x83tes");
do_string_encode_test::<1>("test", b"\x80");
// Truncates when the string does not fit.
do_string_encode_test::<64>(
"testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttest",
b"\xbftesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttes",
);
// Truncates when string is over 127 bytes.
do_string_encode_test::<1024>(
"testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest",
b"\xfftesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttes",
);
}
}