summaryrefslogtreecommitdiff
path: root/crates/windlass/src/messages.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/windlass/src/messages.rs')
-rw-r--r--crates/windlass/src/messages.rs237
1 files changed, 237 insertions, 0 deletions
diff --git a/crates/windlass/src/messages.rs b/crates/windlass/src/messages.rs
new file mode 100644
index 0000000..5b97d31
--- /dev/null
+++ b/crates/windlass/src/messages.rs
@@ -0,0 +1,237 @@
+use std::collections::BTreeMap;
+
+use crate::dictionary::Dictionary;
+use crate::encoding::{FieldType, FieldValue, MessageDecodeError};
+
+#[derive(thiserror::Error, Debug)]
+pub enum MessageSkipperError {
+ #[error("invalid argument format: {0}")]
+ InvalidArgumentFormat(String),
+ #[error("unknown type '{1}' for argument '{0}'")]
+ UnknownType(String, String),
+ #[error("invalid format field type '%{0}'")]
+ InvalidFormatFieldType(String),
+}
+
+pub(crate) fn format_command_args<'a>(
+ fields: impl Iterator<Item = (&'a str, FieldType)>,
+) -> String {
+ let mut buf = String::new();
+ for (idx, (name, ty)) in fields.enumerate() {
+ if idx != 0 {
+ buf.push(' ');
+ }
+ buf.push_str(name);
+ buf.push('=');
+ buf.push_str(match ty {
+ FieldType::U32 => "%u",
+ FieldType::I32 => "%i",
+ FieldType::U16 => "%hu",
+ FieldType::I16 => "%hi",
+ FieldType::U8 => "%c",
+ FieldType::String => "%s",
+ FieldType::ByteArray => "%*s",
+ });
+ }
+ buf
+}
+
+pub struct MessageParser {
+ pub name: String,
+ pub fields: Vec<(String, FieldType)>,
+ pub output: Option<OutputFormat>,
+}
+
+impl MessageParser {
+ pub(crate) fn new<'a>(
+ name: &str,
+ parts: impl Iterator<Item = &'a str>,
+ ) -> Result<MessageParser, MessageSkipperError> {
+ let mut fields = vec![];
+ for part in parts {
+ let (arg, ty) = part
+ .split_once('=')
+ .ok_or_else(|| MessageSkipperError::InvalidArgumentFormat(part.into()))?;
+
+ let field_type = FieldType::from_msg(ty)?;
+ fields.push((arg.to_string(), field_type));
+ }
+ Ok(Self {
+ name: name.to_string(),
+ fields,
+ output: None,
+ })
+ }
+
+ pub(crate) fn new_output(msg: &str) -> Result<MessageParser, MessageSkipperError> {
+ let mut fields = vec![];
+ let mut parts = vec![];
+
+ let mut work = msg;
+ while let Some(pos) = work.find('%') {
+ let (pre, rest) = work.split_at(pos);
+ if !pre.is_empty() {
+ parts.push(FormatBlock::Static(pre.to_string()));
+ }
+ if let Some(rest) = rest.strip_prefix("%%") {
+ parts.push(FormatBlock::Static("%".to_string()));
+ work = rest;
+ break;
+ }
+ let (format, rest) = FieldType::from_format(rest)?;
+ parts.push(FormatBlock::Field);
+ fields.push((format!("field_{}", fields.len()), format));
+ work = rest;
+ }
+ if !work.is_empty() {
+ parts.push(FormatBlock::Static(work.to_string()));
+ }
+
+ Ok(Self {
+ name: msg.to_string(),
+ fields,
+ output: Some(OutputFormat { parts }),
+ })
+ }
+
+ #[allow(dead_code)]
+ pub(crate) fn skip(&self, input: &mut &[u8]) -> Result<(), MessageDecodeError> {
+ for (_, field) in &self.fields {
+ field.skip(input)?;
+ }
+ Ok(())
+ }
+
+ pub(crate) fn skip_with_oid(
+ &self,
+ input: &mut &[u8],
+ ) -> Result<Option<u8>, MessageDecodeError> {
+ let mut oid = None;
+ for (name, field) in &self.fields {
+ if name == "oid" {
+ if let FieldValue::U8(read_oid) = field.read(input)? {
+ oid = Some(read_oid);
+ }
+ } else {
+ field.skip(input)?;
+ }
+ }
+ Ok(oid)
+ }
+
+ pub(crate) fn parse(
+ &self,
+ input: &mut &[u8],
+ ) -> Result<BTreeMap<String, FieldValue>, MessageDecodeError> {
+ let mut output = BTreeMap::new();
+ for (name, field) in &self.fields {
+ output.insert(name.to_string(), field.read(input)?);
+ }
+ Ok(output)
+ }
+
+ pub(crate) fn parse_and_output(
+ &self,
+ input: &mut &[u8],
+ ) -> Option<Result<String, MessageDecodeError>> {
+ self.output.as_ref().map(|output| {
+ let fields = self.parse(input)?;
+ Ok(output.format(fields.values()))
+ })
+ }
+}
+
+impl std::fmt::Debug for MessageParser {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_map()
+ .entry(&"name", &self.name)
+ .entry(&"fields", &self.fields)
+ .finish()
+ }
+}
+
+pub trait Message: 'static {
+ type Pod<'a>: Into<Self::PodOwned> + std::fmt::Debug;
+ type PodOwned: Clone + Send + std::fmt::Debug + 'static;
+ fn get_id(dict: Option<&Dictionary>) -> Option<u16>;
+ fn get_name() -> &'static str;
+ fn decode<'a>(input: &mut &'a [u8]) -> Result<Self::Pod<'a>, MessageDecodeError>;
+ fn fields() -> Vec<(&'static str, FieldType)>;
+}
+
+pub trait WithOid: 'static {}
+pub trait WithoutOid: 'static {}
+
+/// Represents an encoded message, with a type-level link to the message kind
+pub struct EncodedMessage<M> {
+ pub payload: FrontTrimmableBuffer,
+ pub _message_kind: std::marker::PhantomData<M>,
+}
+
+impl<R: Message> std::fmt::Debug for EncodedMessage<R> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("EncodedMessage")
+ .field("kind", &R::get_name())
+ .field("payload", &self.payload)
+ .finish()
+ }
+}
+
+impl<M: Message> EncodedMessage<M> {
+ #[doc(hidden)]
+ pub fn message_id(&self, dict: Option<&Dictionary>) -> Option<u16> {
+ M::get_id(dict)
+ }
+
+ #[doc(hidden)]
+ pub fn message_name(&self) -> &'static str {
+ M::get_name()
+ }
+}
+
+/// Wraps a `Vec<u8>` allowing removal of front bytes in a zero-copy way
+pub struct FrontTrimmableBuffer {
+ pub content: Vec<u8>,
+ pub offset: usize,
+}
+
+impl FrontTrimmableBuffer {
+ pub fn as_slice(&self) -> &[u8] {
+ &self.content[self.offset..]
+ }
+}
+
+impl std::fmt::Debug for FrontTrimmableBuffer {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ std::fmt::Debug::fmt(self.as_slice(), f)
+ }
+}
+
+/// Holds the format of a `output()` style debug message
+#[derive(Debug)]
+pub struct OutputFormat {
+ parts: Vec<FormatBlock>,
+}
+
+impl OutputFormat {
+ fn format<'a>(&self, mut fields: impl Iterator<Item = &'a FieldValue>) -> String {
+ let mut buf = String::new();
+ for part in &self.parts {
+ match part {
+ FormatBlock::Static(s) => buf.push_str(s),
+ FormatBlock::Field => {
+ if let Some(v) = fields.next() {
+ std::fmt::write(&mut buf, format_args!("{v}")).ok();
+ }
+ }
+ }
+ }
+ buf
+ }
+}
+
+#[derive(Debug)]
+enum FormatBlock {
+ Static(String),
+ Field,
+}