1// Copyright 2023 The Pigweed Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not
4// use this file except in compliance with the License. You may obtain a copy of
5// the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12// License for the specific language governing permissions and limitations under
13// the License.
1415use core::cmp::min;
1617use pw_status::{Error, Result};
18use pw_stream::{Cursor, Write};
19use pw_varint::VarintEncode;
2021use crate::MessageWriter;
2223// The `Argument` enum is used to marshal arguments to pass to the tokenization
24// engine.
25pub enum Argument<'a> {
26 String(&'a str),
27 Varint(i64),
28}
2930impl<'a> From<&'a str> for Argument<'a> {
31fn from(val: &'a str) -> Self {
32Self::String(val)
33 }
34}
3536impl From<char> for Argument<'_> {
37fn from(val: char) -> Self {
38Self::Varint(val as i64)
39 }
40}
4142impl From<u8> for Argument<'_> {
43fn from(val: u8) -> Self {
44Self::Varint(val as i64)
45 }
46}
4748impl From<i32> for Argument<'_> {
49fn from(val: i32) -> Self {
50Self::Varint(val as i64)
51 }
52}
5354impl From<u32> for Argument<'_> {
55fn from(val: u32) -> Self {
56Self::Varint(val as i64)
57 }
58}
5960// TODO: b/400978670 - investigate whether changing these
61// 64bit values to references saves space on 32bit systems.
62impl From<i64> for Argument<'_> {
63fn from(val: i64) -> Self {
64Self::Varint(val)
65 }
66}
6768impl From<u64> for Argument<'_> {
69fn from(val: u64) -> Self {
70Self::Varint(val as i64)
71 }
72}
7374impl From<usize> for Argument<'_> {
75fn from(val: usize) -> Self {
76Self::Varint(val as i64)
77 }
78}
7980// Wraps a `Cursor` so that `tokenize_to_buffer` and `tokenize_to_writer` can
81// share implementations. It is not meant to be used outside of
82// `tokenize_to_buffer`.
83struct CursorMessageWriter<'a> {
84 cursor: Cursor<&'a mut [u8]>,
85}
8687impl MessageWriter for CursorMessageWriter<'_> {
88fn new() -> Self {
89// Ensure `tokenize_to_buffer` never calls `new()`.
90unimplemented!();
91 }
9293fn write(&mut self, data: &[u8]) -> Result<()> {
94self.cursor.write_all(data)
95 }
9697fn remaining(&self) -> usize {
98self.cursor.remaining()
99 }
100101fn finalize(self) -> Result<()> {
102// Ensure `tokenize_to_buffer` never calls `finalize()`.
103unimplemented!();
104 }
105}
106107// Encode a string in Tokenizer format: length byte + data with the high bit of
108// the length byte used to signal that the string was truncated.
109pub fn encode_string<W: MessageWriter>(writer: &mut W, value: &str) -> Result<()> {
110const MAX_STRING_LENGTH: usize = 0x7f;
111112let string_bytes = value.as_bytes();
113114// Limit the encoding to the lesser of 127 or the available space in the buffer.
115let max_len = min(MAX_STRING_LENGTH, writer.remaining().saturating_sub(1));
116let overflow = max_len < string_bytes.len();
117let len = min(max_len, string_bytes.len());
118119// First byte of an encoded string is it's length.
120let mut header = len as u8;
121122// The high bit of the first byte is used to indicate if the string was
123 // truncated.
124if overflow {
125 header |= 0x80;
126 }
127 writer.write(&[header])?;
128129 writer.write(&string_bytes[..len])
130}
131132// Write out a tokenized message to an already created `MessageWriter`.
133fn tokenize_engine<W: crate::MessageWriter>(
134 writer: &mut W,
135 token: u32,
136 args: &[Argument<'_>],
137) -> Result<()> {
138 writer.write(&token.to_le_bytes()[..])?;
139for arg in args {
140match arg {
141 Argument::String(s) => encode_string(writer, s)?,
142 Argument::Varint(i) => {
143let mut encode_buffer = [0u8; 10];
144let len = i.varint_encode(&mut encode_buffer)?;
145let encoded_slice = encode_buffer.get(..len).ok_or(Error::OutOfRange)?;
146 writer.write(encoded_slice)?;
147 }
148 }
149 }
150151Ok(())
152}
153154#[inline(never)]
155pub fn tokenize_to_buffer(buffer: &mut [u8], token: u32, args: &[Argument<'_>]) -> Result<usize> {
156let mut writer = CursorMessageWriter {
157 cursor: Cursor::new(buffer),
158 };
159 tokenize_engine(&mut writer, token, args)?;
160Ok(writer.cursor.position())
161}
162163#[inline(never)]
164pub fn tokenize_to_buffer_no_args(buffer: &mut [u8], token: u32) -> Result<usize> {
165let token_bytes = &token.to_le_bytes()[..];
166let token_len = token_bytes.len();
167if buffer.len() < token_len {
168return Err(Error::OutOfRange);
169 }
170 buffer[..token_len].copy_from_slice(token_bytes);
171172Ok(token_len)
173}
174175#[inline(never)]
176pub fn tokenize_to_writer<W: crate::MessageWriter>(
177 token: u32,
178 args: &[Argument<'_>],
179) -> Result<()> {
180let mut writer = W::new();
181182match tokenize_engine(&mut writer, token, args) {
183// Still finalize the writer even if the buffer
184 // is full so as to avoid loosing the entire
185 // log message.
186Ok(_) | Err(Error::OutOfRange) => writer.finalize(),
187Err(error) => Err(error),
188 }
189}
190191#[inline(never)]
192pub fn tokenize_to_writer_no_args<W: crate::MessageWriter>(token: u32) -> Result<()> {
193let mut writer = W::new();
194let result = writer.write(&token.to_le_bytes()[..]);
195196match result {
197// Still finalize the writer even if the buffer
198 // is full so as to avoid loosing the entire
199 // log message.
200Ok(_) | Err(Error::OutOfRange) => writer.finalize(),
201Err(error) => Err(error),
202 }
203}
204205#[cfg(test)]
206mod test {
207use pw_stream::Seek;
208209use super::*;
210211fn do_string_encode_test<const BUFFER_LEN: usize>(value: &str, expected: &[u8]) {
212let mut buffer = [0u8; BUFFER_LEN];
213let mut writer = CursorMessageWriter {
214 cursor: Cursor::new(&mut buffer),
215 };
216 encode_string(&mut writer, value).unwrap();
217218let len = writer.cursor.stream_position().unwrap() as usize;
219let buffer = writer.cursor.into_inner();
220221assert_eq!(len, expected.len());
222assert_eq!(&buffer[..len], expected);
223 }
224225#[test]
226fn test_string_encode() {
227 do_string_encode_test::<64>("test", b"\x04test");
228 do_string_encode_test::<4>("test", b"\x83tes");
229 do_string_encode_test::<1>("test", b"\x80");
230231// Truncates when the string does not fit.
232do_string_encode_test::<64>(
233"testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttest",
234b"\xbftesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttes",
235 );
236237// Truncates when string is over 127 bytes.
238do_string_encode_test::<1024>(
239"testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest",
240b"\xfftesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttes",
241 );
242 }
243}