diff --git a/crates/windlass/examples/sim.rs b/crates/windlass/examples/sim.rs index 2bcf4cf..7e29c4b 100644 --- a/crates/windlass/examples/sim.rs +++ b/crates/windlass/examples/sim.rs @@ -10,7 +10,7 @@ use tokio::{ time::{sleep, sleep_until, Instant as TokioInstant}, }; use tracing::{info, trace}; -use windlass::Connection; +use windlass::application::Connection; #[tokio::main(flavor = "current_thread")] async fn main() { diff --git a/crates/windlass/src/application.rs b/crates/windlass/src/application.rs index bcb4e70..dc884ac 100644 --- a/crates/windlass/src/application.rs +++ b/crates/windlass/src/application.rs @@ -1,13 +1,12 @@ -use std::{ - io::{self, Read, Write}, - time::Instant, -}; +//! Application layer protocol - deals with commands, responses, etc + +use std::{io::Write, time::Instant}; use thiserror::Error; use tracing::{debug, trace}; use crate::{ - dictionary::{self, Dictionary, RawDictionary}, + dictionary::{self, Dictionary}, encoding::{parse_vlq_int, MessageDecodeError}, transport, }; @@ -15,7 +14,6 @@ use crate::{ /// Application-level connection to a klipper MCU pub struct Connection { transport: transport::Connection, - dictionary: DictionaryReceiveState, } @@ -142,25 +140,12 @@ impl DictionaryReceiveState { // done receiving dictionary trace!("done receiving dictionary, attempting decode"); - let dict = Self::decode_complete(so_far.as_slice())?; + let dict = Dictionary::from_complete_lookup_resp(so_far.as_slice())?; trace!(dict = ?dict, "dictionary in place"); *self = Self::Done(dict); Ok(()) } - - fn decode_complete(data: &[u8]) -> Result { - let mut decoder = flate2::read::ZlibDecoder::new(data); - - let mut buf = Vec::new(); - decoder.read_to_end(&mut buf).map_err(Error::DictionaryIO)?; - - let raw_dict: RawDictionary = - serde_json::from_slice(&buf).map_err(Error::DictionaryJson)?; - let dict = Dictionary::try_from(raw_dict).map_err(Error::DictionaryData)?; - - Ok(dict) - } } #[derive(Error, Debug)] @@ -171,14 +156,8 @@ pub enum Error { #[error("timed out while waiting for identify")] IdentifyTimeout, - #[error("error decompressing zlib in identify response")] - DictionaryIO(io::Error), - - #[error("json returned from identify was invalid")] - DictionaryJson(serde_json::Error), - #[error("data in dictionary was invalid")] - DictionaryData(dictionary::Error), + DictionaryData(#[from] dictionary::Error), #[error("message decode error: {0}")] MessageDecodeError(#[from] MessageDecodeError), diff --git a/crates/windlass/src/dictionary.rs b/crates/windlass/src/dictionary.rs index 73b042d..2f5982c 100644 --- a/crates/windlass/src/dictionary.rs +++ b/crates/windlass/src/dictionary.rs @@ -1,6 +1,10 @@ //! Metadata about the Application-layer protocol in use -use std::{collections::BTreeMap, ops::RangeInclusive}; +use std::{ + collections::BTreeMap, + io::{self, Read}, + ops::RangeInclusive, +}; use crate::{ encoding::encode_vlq_int, @@ -94,12 +98,24 @@ impl Dictionary { Ok(v) } } + + pub fn from_complete_lookup_resp(data: &[u8]) -> Result { + let mut decoder = flate2::read::ZlibDecoder::new(data); + + let mut buf = Vec::new(); + decoder.read_to_end(&mut buf).map_err(Error::IO)?; + + let raw_dict: Raw = serde_json::from_slice(&buf).map_err(Error::Json)?; + let dict = Self::try_from(raw_dict)?; + + Ok(dict) + } } -impl TryFrom for Dictionary { +impl TryFrom for Dictionary { type Error = Error; - fn try_from(raw: RawDictionary) -> Result { + fn try_from(raw: Raw) -> Result { let mut message_ids = BTreeMap::new(); let mut message_parsers = BTreeMap::new(); @@ -209,11 +225,17 @@ pub enum Error { /// Received a command with an invalid tag #[error("command tag {0} output valid range of -32..95")] InvalidCommandTag(u16), + + #[error("error decompressing zlib in identify response")] + IO(io::Error), + + #[error("json returned from identify was invalid")] + Json(serde_json::Error), } /// The raw JSON data dictionary response from the microcontroller #[derive(Debug, serde::Deserialize)] -pub(crate) struct RawDictionary { +pub struct Raw { #[serde(default)] config: BTreeMap, diff --git a/crates/windlass/src/encoding.rs b/crates/windlass/src/encoding.rs index 6b50c3c..213a83e 100644 --- a/crates/windlass/src/encoding.rs +++ b/crates/windlass/src/encoding.rs @@ -3,7 +3,7 @@ use std::fmt::Display; use crate::messages::MessageSkipperError; /// Calculate the CRC-16 of the given buffer -pub(crate) fn crc16(buf: &[u8]) -> u16 { +pub fn crc16(buf: &[u8]) -> u16 { let mut crc = 0xFFFFu16; for b in buf { let b = *b ^ ((crc & 0xFF) as u8); @@ -16,7 +16,7 @@ pub(crate) fn crc16(buf: &[u8]) -> u16 { /// Encode the given integer as a [VLQ](https://www.klipper3d.org/Protocol.html#variable-length-quantities), pushing it to the back of `output`. #[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)] -pub(crate) fn encode_vlq_int(output: &mut Vec, v: u32) { +pub fn encode_vlq_int(output: &mut Vec, v: u32) { // This is ok, as we're just doing bit fiddling and the signedness // will be figured out at the other side again. let sv = v as i32; @@ -39,7 +39,7 @@ pub(crate) fn encode_vlq_int(output: &mut Vec, v: u32) { /// Parse a [VLQ](https://www.klipper3d.org/Protocol.html#variable-length-quantities) from the top of `data`. #[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)] -pub(crate) fn parse_vlq_int(data: &mut &[u8]) -> Result { +pub fn parse_vlq_int(data: &mut &[u8]) -> Result { // Casting is fine here, as we're just doing bit fiddling let mut c = u32::from(next_byte(data)?); let mut v = c & 0x7F; @@ -55,7 +55,7 @@ pub(crate) fn parse_vlq_int(data: &mut &[u8]) -> Result } /// Read the next byte from `data`, or error -pub(crate) fn next_byte(data: &mut &[u8]) -> Result { +pub fn next_byte(data: &mut &[u8]) -> Result { if data.is_empty() { Err(MessageDecodeError::UnexpectedEof) } else { diff --git a/crates/windlass/src/lib.rs b/crates/windlass/src/lib.rs index ed8ad0c..764ce68 100644 --- a/crates/windlass/src/lib.rs +++ b/crates/windlass/src/lib.rs @@ -1,20 +1,25 @@ //! Windlass is an implementation of the host side of the Klipper protocol. +//! +//! Written in a [sans-io style](https://sans-io.readthedocs.io/). +//! The following runtimes are supported natively: +//! * none (TODO) +//! +//! To write your own driver, see the interfaces in [`crate::application`] or [`crate::transport`]. #![deny(clippy::all, clippy::pedantic, clippy::nursery)] #![allow( clippy::must_use_candidate, clippy::missing_errors_doc, - clippy::future_not_send + clippy::future_not_send, + clippy::cognitive_complexity )] #[macro_use] #[doc(hidden)] -pub mod macros; -pub mod dictionary; -pub mod encoding; +mod macros; +mod dictionary; +mod encoding; pub mod messages; pub mod application; pub mod transport; - -pub use application::Connection; diff --git a/crates/windlass/src/transport/mod.rs b/crates/windlass/src/transport/mod.rs index 5c6c236..f2c4a97 100644 --- a/crates/windlass/src/transport/mod.rs +++ b/crates/windlass/src/transport/mod.rs @@ -1,4 +1,4 @@ -//! Implementation of the transport-level protocol +//! Transport layer protocol - deals with message blocks mod frame; mod rtt;