use core::cmp::min;
use pw_status::{Error, Result};
use pw_stream::{Cursor, Write};
use pw_varint::VarintEncode;
use crate::MessageWriter;
pub enum Argument<'a> {
String(&'a str),
Char(u8),
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::Char(val as u8)
}
}
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)
}
}
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)
}
}
struct CursorMessageWriter<'a> {
cursor: Cursor<&'a mut [u8]>,
}
impl MessageWriter for CursorMessageWriter<'_> {
fn new() -> Self {
unimplemented!();
}
fn write(&mut self, data: &[u8]) -> Result<()> {
self.cursor.write_all(data)
}
fn remaining(&self) -> usize {
self.cursor.remaining()
}
fn finalize(self) -> Result<()> {
unimplemented!();
}
}
pub fn encode_string<W: MessageWriter>(writer: &mut W, value: &str) -> Result<()> {
const MAX_STRING_LENGTH: usize = 0x7f;
let string_bytes = value.as_bytes();
let max_len = min(MAX_STRING_LENGTH, writer.remaining() - 1);
let overflow = max_len < string_bytes.len();
let len = min(max_len, string_bytes.len());
let mut header = len as u8;
if overflow {
header |= 0x80;
}
writer.write(&[header])?;
writer.write(&string_bytes[..len])
}
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)?;
writer.write(&encode_buffer[..len])?;
}
Argument::Char(c) => writer.write(&[*c])?,
}
}
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) {
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 {
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");
do_string_encode_test::<64>(
"testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttest",
b"\xbftesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttes",
);
do_string_encode_test::<1024>(
"testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest",
b"\xfftesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttes",
);
}
}