diff --git a/Cargo.toml b/Cargo.toml index 918b58a..dedfac9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,9 +8,11 @@ authors = ["twitchyliquid64"] edition = "2018" repository = "https://github.com/twitchyliquid64/usbd-hid" - [dependencies] usbd-hid-macros = { path = "macros", version = "0.4.5" } usb-device = "0.2.1" serde = { version = "1.0", default-features = false } ssmarshal = { version = "1.0", default-features = false } + +[features] +default = ["serde/derive"] \ No newline at end of file diff --git a/descriptors/src/lib.rs b/descriptors/src/lib.rs index 21869d0..8182f0f 100644 --- a/descriptors/src/lib.rs +++ b/descriptors/src/lib.rs @@ -52,7 +52,7 @@ impl Into for LocalItemKind { /// 'Report Descriptor' of the spec, version 1.11. #[repr(u8)] #[allow(unused)] -#[derive(Copy, Debug, Clone, Eq, PartialEq)] +#[derive(Copy, Debug, Clone, Eq, PartialEq, Hash)] pub enum MainItemKind { Input = 0b1000, Output = 0b1001, diff --git a/macros/src/gen.rs b/macros/src/gen.rs new file mode 100644 index 0000000..f1db48b --- /dev/null +++ b/macros/src/gen.rs @@ -0,0 +1,96 @@ +use proc_macro::TokenStream as TokenStream1; +use std::collections::{HashMap, HashSet}; +use std::iter::FromIterator; + +use proc_macro2::{Ident, Span}; +use proc_macro2::TokenStream; +use quote::ToTokens; +use syn::{Attribute, AttrStyle, Fields, ItemEnum, ItemStruct, parse_str, Type, TypeTuple, Variant, Result, parse}; +use syn::punctuated::Punctuated; +use syn::spanned::Spanned; + +pub(crate) fn generate_type( + template: &ItemStruct, + ident: Ident, + mut fields: HashMap, Fields> +) -> TokenStream { + let template = template.clone(); + if let Some(fields) = fields.remove(&None) { + ItemStruct{ + ident, + fields, + ..template + }.to_token_stream() + } else { + let mut variants_iter = Vec::::new(); + for i in 0u8..=255u8 { + variants_iter.push(Variant { + attrs: Vec::new(), + ident: parse_str(format!("R{}", i).as_str()).unwrap(), + fields: fields.remove(&Some(i)).unwrap_or(Fields::Unit), + discriminant: None + }); + + if fields.is_empty() { + break + } + } + + ItemEnum { + attrs: template.attrs, + vis: template.vis, + generics: template.generics, + brace_token: Default::default(), + ident, + enum_token: syn::token::Enum{ span: Span::call_site() }, + variants: Punctuated::from_iter(variants_iter) + }.to_token_stream() + } +} + +pub(crate) fn extract_and_format_derive(empty_struct: &mut ItemStruct) -> Result { + let pos = empty_struct.attrs.iter().position(|a| a.path.is_ident("derive") && a.style == AttrStyle::Outer); + + let drives = { + let mut d: HashSet = if let Some(i) = pos { + let existing_drive = empty_struct.attrs.remove(i); + match parse::(TokenStream1::from(existing_drive.tokens))? { + Type::Paren(p) => { + let mut h = HashSet::new(); + h.insert(*p.elem); + h + } + Type::Tuple(t) => { + t.elems.into_iter().collect() + } + t => { + return Err( + parse::Error::new(t.span(), "`#[gen_hid_descriptor]` unrecognized derive") + ) + } + } + } else { + HashSet::new() + }; + + d.insert(parse_str("Deserialize").unwrap()); + d.insert(parse_str("Serialize").unwrap()); + + d + }; + + let mut attrs_ts = TokenStream::new(); + + TypeTuple { + paren_token: Default::default(), + elems: Punctuated::from_iter(drives) + }.to_tokens(&mut attrs_ts); + + Ok(Attribute { + pound_token: syn::token::Pound { spans: [Span::call_site()] }, + style: AttrStyle::Outer, + bracket_token: Default::default(), + path: parse_str("derive").unwrap(), + tokens: attrs_ts + }) +} \ No newline at end of file diff --git a/macros/src/item.rs b/macros/src/item.rs index a6c90d9..0fbf456 100644 --- a/macros/src/item.rs +++ b/macros/src/item.rs @@ -12,6 +12,7 @@ pub struct MainItem { pub logical_maximum: isize, pub report_count: u16, pub report_size: u16, + pub report_id: Option, pub padding_bits: Option, } @@ -132,6 +133,7 @@ fn unary_item(id: Ident, kind: MainItemKind, bit_width: usize) -> ReportUnaryFie logical_maximum: 0, report_count: 1, report_size: bit_width as u16, + report_id: None, padding_bits: None, }, } diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 4b4e71a..2b650eb 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -4,22 +4,32 @@ extern crate proc_macro; extern crate usbd_hid_descriptors; use proc_macro::TokenStream; -use proc_macro2::Span; +use std::collections::{HashMap}; +use std::iter::FromIterator; + +use byteorder::{ByteOrder, LittleEndian}; +use item::*; +use proc_macro2::{Span, TokenStream as TokStream2}; use quote::quote; +use spec::*; +use syn::{Field, FieldsNamed}; +use syn::{Expr, Fields, ItemStruct, parse, parse_macro_input, parse_str, Type}; +use syn::{Pat, PatSlice, Result}; use syn::punctuated::Punctuated; use syn::token::Bracket; -use syn::{parse, parse_macro_input, Expr, Fields, ItemStruct}; -use syn::{Pat, PatSlice, Result}; - -use byteorder::{ByteOrder, LittleEndian}; use usbd_hid_descriptors::*; +use usbd_hid_descriptors::MainItemKind::{Input, Output}; + +use crate::gen::{extract_and_format_derive, generate_type}; +use crate::split::read_struct; +use crate::utils::{group_by, map_group_by}; mod spec; -use spec::*; mod item; -use item::*; -mod packer; -use packer::{gen_serializer, uses_report_ids}; +mod split; +#[macro_use] +mod utils; +mod gen; /// Attribute to generate a HID descriptor & serialization code /// @@ -202,10 +212,10 @@ use packer::{gen_serializer, uses_report_ids}; pub fn gen_hid_descriptor(args: TokenStream, input: TokenStream) -> TokenStream { let decl = parse_macro_input!(input as ItemStruct); let spec = parse_macro_input!(args as GroupSpec); - let ident = decl.ident.clone(); + let ident = &decl.ident; // Error if the struct doesn't name its fields. - match decl.clone().fields { + match decl.fields { Fields::Named(_) => (), _ => { return parse::Error::new( @@ -217,48 +227,115 @@ pub fn gen_hid_descriptor(args: TokenStream, input: TokenStream) -> TokenStream } }; - let do_serialize = !uses_report_ids(&Spec::Collection(spec.clone())); + let do_serialize = true; + + let (descriptor, fields) = guard_syn!(compile_descriptor(spec, &decl.fields)); + + let (struct_fields, mut empty_struct) = guard_syn!(read_struct(&decl)); + + let mut named_fields: HashMap<_, Field> = struct_fields.named + .into_iter() + .filter(|f| f.ident.is_some()) + .map(|f| (f.ident.as_ref().unwrap().clone(), f)) + .collect(); + + let mut by_op_and_id: HashMap<_, HashMap<_, _>> = + group_by(&fields, |f| f.descriptor_item.kind) + .iter() + .map(|(k, v)| + ( + *k, + map_group_by( + v, + |f| f.descriptor_item.report_id, + |f| named_fields.remove(&f.ident).unwrap() + ).into_iter().map(|(k,v)| { + ( + k, + syn::Fields::Named(FieldsNamed{ + brace_token: Default::default(), + named: Punctuated::from_iter(v) + }) + ) + } ).collect() + ) + ).collect(); + + if !named_fields.is_empty() { + println!( + "WARN: unused fields [{}]in struct {} will be removed", + named_fields.iter().fold(String::new(), |o, (i, _)| o + i.to_string().as_str() + ","), + ident); + } + + let new_derive = guard_syn!(extract_and_format_derive(&mut empty_struct)); + + empty_struct.attrs.push(new_derive); - let output = match compile_descriptor(spec, &decl.fields) { - Ok(d) => d, - Err(e) => return e.to_compile_error().into(), + let in_type = by_op_and_id.remove(&Input).map(|v| + generate_type(&empty_struct, ident.clone(), v) + ); + + let out_ident = { + if in_type.is_none() { + ident.clone() + } else { + parse_str(format!("{}{}", ident, "Out").as_str()).unwrap() + } }; - let (descriptor, fields) = output; - let mut out = quote! { - #[derive(Debug, Clone, Copy)] - #[repr(C, packed)] - #decl + let out_type = by_op_and_id.remove(&Output).map(|v| + generate_type(&empty_struct, out_ident.clone(), v) + ); + + let trait_impl = { + if do_serialize { + let dev_to_host_type = { + let orig = ident.to_string(); + let in_type_str = if in_type.is_none() { + EMPTY_TYPE + } else { + orig.as_str() + }; + + parse_str::(in_type_str).unwrap() + }; + + let host_to_dev_type = { + let out_type_str = if out_type.is_none() { + EMPTY_TYPE.to_owned() + } else { + out_ident.to_string() + }; - impl SerializedDescriptor for #ident { - fn desc() -> &'static[u8] { - &#descriptor + parse_str::(out_type_str.as_str()).unwrap() + }; + quote! { + impl HIDDescriptorTypes for #ident { + type DeviceToHostReport = #dev_to_host_type; + type HostToDeviceReport = #host_to_dev_type; + } } + }else { + TokStream2::new() } }; - if do_serialize { - let input_serializer = match gen_serializer(fields, MainItemKind::Input) { - Ok(s) => s, - Err(e) => return e.to_compile_error().into(), - }; + TokenStream::from( + quote! { + #in_type - out = quote! { - #out + #out_type - impl Serialize for #ident { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - #input_serializer + impl HIDDescriptor for #ident { + fn desc() -> &'static[u8] { + &#descriptor } } - impl AsInputReport for #ident {} - }; - } - TokenStream::from(out) + #trait_impl + } + ) } fn compile_descriptor( @@ -270,14 +347,12 @@ fn compile_descriptor( }; let mut elems = Punctuated::new(); - if let Err(e) = compiler.emit_group(&mut elems, &spec, fields) { - return Err(e); - }; + compiler.emit_group(&mut elems, &spec, fields)?; Ok(( PatSlice { attrs: vec![], - elems: elems, + elems, bracket_token: Bracket { span: Span::call_site(), }, @@ -292,6 +367,7 @@ struct DescCompilation { logical_maximum: Option, report_size: Option, report_count: Option, + report_id: Option, processed_fields: Vec, } @@ -359,10 +435,8 @@ impl DescCompilation { self.emit(elems, &mut prefix, buf, signed); } - fn handle_globals(&mut self, elems: &mut Punctuated, item: MainItem, quirks: ItemQuirks) { - if self.logical_minimum.is_none() - || self.logical_minimum.clone().unwrap() != item.logical_minimum - { + fn handle_globals(&mut self, elems: &mut Punctuated, item: &MainItem, quirks: &ItemQuirks) { + if self.logical_minimum.map_or(true, |c| c != item.logical_minimum) { self.emit_item( elems, ItemType::Global.into(), @@ -373,9 +447,7 @@ impl DescCompilation { ); self.logical_minimum = Some(item.logical_minimum); } - if self.logical_maximum.is_none() - || self.logical_maximum.clone().unwrap() != item.logical_maximum - { + if self.logical_maximum.map_or(true, |c| c != item.logical_maximum) { self.emit_item( elems, ItemType::Global.into(), @@ -386,7 +458,7 @@ impl DescCompilation { ); self.logical_maximum = Some(item.logical_maximum); } - if self.report_size.is_none() || self.report_size.clone().unwrap() != item.report_size { + if self.report_size.map_or(true, |c| c != item.report_size) { self.emit_item( elems, ItemType::Global.into(), @@ -397,7 +469,7 @@ impl DescCompilation { ); self.report_size = Some(item.report_size); } - if self.report_count.is_none() || self.report_count.clone().unwrap() != item.report_count { + if self.report_count.map_or(true, |c| c != item.report_count) { self.emit_item( elems, ItemType::Global.into(), @@ -408,6 +480,11 @@ impl DescCompilation { ); self.report_count = Some(item.report_count); } + item.report_id.map(|report_id| { + if self.report_id.map_or(true, |c| c != report_id) { + self.report_id = Some(report_id); + } + }); } fn emit_field( @@ -416,7 +493,7 @@ impl DescCompilation { i: &ItemSpec, item: MainItem, ) { - self.handle_globals(elems, item.clone(), i.quirks); + self.handle_globals(elems, &item, &i.quirks); let item_data = match &i.settings { Some(s) => s.0 as isize, None => 0x02, // 0x02 = Data,Var,Abs @@ -437,7 +514,7 @@ impl DescCompilation { report_count: padding, ..item }; - self.handle_globals(elems, padding.clone(), i.quirks); + self.handle_globals(elems, &padding, &i.quirks); let mut const_settings = MainItemSetting { 0: 0 }; const_settings.set_constant(true); @@ -459,8 +536,6 @@ impl DescCompilation { spec: &GroupSpec, fields: &Fields, ) -> Result<()> { - // println!("GROUP: {:?}", spec); - if let Some(usage_page) = spec.usage_page { self.emit_item( elems, @@ -523,12 +598,12 @@ impl DescCompilation { } for name in spec.clone() { - let f = spec.get(name.clone()).unwrap(); - match f { + match spec.get(name.clone()).unwrap() { Spec::MainItem(i) => { let d = field_decl(fields, name); match analyze_field(d.clone(), d.ty, i) { - Ok(item) => { + Ok(mut item) => { + spec.report_id.map(|id| item.descriptor_item.report_id = Some(id)); self.processed_fields.push(item.clone()); self.emit_field(elems, i, item.descriptor_item) } @@ -536,9 +611,7 @@ impl DescCompilation { } } Spec::Collection(g) => { - if let Err(e) = self.emit_group(elems, g, fields) { - return Err(e); - } + self.emit_group(elems, g, fields)? } } } @@ -562,3 +635,6 @@ fn byte_literal(lit: u8) -> Pat { })), }) } + +// TODO: Change `!` to never_type is in stable +static EMPTY_TYPE: &str = "UnsupportedDescriptor"; diff --git a/macros/src/packer.rs b/macros/src/packer.rs deleted file mode 100644 index 6ea3744..0000000 --- a/macros/src/packer.rs +++ /dev/null @@ -1,93 +0,0 @@ -extern crate usbd_hid_descriptors; -use usbd_hid_descriptors::*; - -use proc_macro2::TokenStream; -use quote::quote; -use syn::{parse, Ident, Index, Result}; - -use crate::spec::*; -use crate::item::*; - -use core::iter::Extend; - -pub fn uses_report_ids(spec: &Spec) -> bool { - match spec { - Spec::MainItem(_) => false, - Spec::Collection(c) => { - for (_, s) in &c.fields { - if uses_report_ids(&s) { - return true; - } - } - c.report_id.is_some() - }, - } -} - -fn make_unary_serialize_invocation(bits: usize, ident: Ident, signed: bool) -> TokenStream { - match (bits, signed) { - (8, false) => quote!({ s.serialize_element(&(self.#ident as u8))?; }), - (16, false) => quote!({ s.serialize_element(&(self.#ident as u16))?; }), - (32, false) => quote!({ s.serialize_element(&(self.#ident as u32))?; }), - (8, true) => quote!({ s.serialize_element(&(self.#ident as i8))?; }), - (16, true) => quote!({ s.serialize_element(&(self.#ident as i16))?; }), - (32, true) => quote!({ s.serialize_element(&(self.#ident as i32))?; }), - _ => quote!(), - } -} - -pub fn gen_serializer(fields: Vec, typ: MainItemKind) -> Result { - let mut elems = Vec::new(); - - for field in fields { - if field.descriptor_item.kind != typ { - continue; - } - let signed = field.descriptor_item.logical_minimum < 0; - - let rc = match field.descriptor_item.report_size { - 1 => { - if field.descriptor_item.report_count == 1 { - elems.push(make_unary_serialize_invocation(field.bit_width, field.ident.clone(), signed)); - } else { - let ident = field.ident.clone(); - elems.push(quote!({ s.serialize_element(&self.#ident)?; })); - } - Ok(()) - }, - 8 => { // u8 / i8 - if field.descriptor_item.report_count == 1 { - elems.push(make_unary_serialize_invocation(8, field.ident.clone(), signed)); - } else { - let ident = field.ident.clone(); - elems.push(quote!({ s.serialize_element(&self.#ident)?; })); - } - Ok(()) - }, - 16 | 32 => { // u16 / i16 / u32 / i32 - if field.descriptor_item.report_count == 1 { - elems.push(make_unary_serialize_invocation(field.descriptor_item.report_size as usize, field.ident.clone(), signed)); - Ok(()) - } else { - Err(parse::Error::new(field.ident.span(),"Arrays of 16/32bit fields not supported")) - } - }, - _ => Err( - parse::Error::new(field.ident.span(),"Unsupported report size for serialization") - ) - }; - - if let Err(e) = rc { - return Err(e); - } - } - - let mut out = TokenStream::new(); - let idx = Index::from(elems.len()); - out.extend(elems); - Ok(quote!({ - let mut s = serializer.serialize_tuple(#idx)?; - #out - s.end() - })) -} diff --git a/macros/src/spec.rs b/macros/src/spec.rs index 6b53c38..f8effca 100644 --- a/macros/src/spec.rs +++ b/macros/src/spec.rs @@ -20,7 +20,7 @@ pub enum Spec { // ItemQuirks describes minor settings which can be tweaked for // compatibility. -#[derive(Debug, Clone, Default, Copy)] +#[derive(Debug, Clone, Default)] pub struct ItemQuirks { pub allow_short_form: bool, } @@ -42,7 +42,7 @@ pub struct GroupSpec { pub fields: HashMap, pub field_order: Vec, - pub report_id: Option, + pub report_id: Option, pub usage_page: Option, pub collection: Option, @@ -95,7 +95,7 @@ impl GroupSpec { pub fn try_set_attr(&mut self, input: ParseStream, name: String, val: u32) -> Result<()> { match name.as_str() { "report_id" => { - self.report_id = Some(val); + self.report_id = Some(val as u8); Ok(()) } "usage_page" => { @@ -139,8 +139,8 @@ impl IntoIterator for GroupSpec { } } -pub fn try_resolve_constant(key_name: String, path: String) -> Option { - match (key_name.as_str(), path.as_str()) { +pub fn try_resolve_constant(key_name: &str, path: String) -> Option { + match (key_name, path.as_str()) { ("collection", "PHYSICAL") => Some(0x0), ("collection", "APPLICATION") => Some(0x1), ("collection", "LOGICAL") => Some(0x2), @@ -218,24 +218,24 @@ pub fn try_resolve_constant(key_name: String, path: String) -> Option { } } -fn parse_group_spec(input: ParseStream, field: Expr) -> Result { +fn parse_group_spec(input: ParseStream, field: &Expr) -> Result { let mut collection_attrs: Vec<(String, u32)> = vec![]; - if let Expr::Assign(ExprAssign { left, .. }) = field.clone() { - if let Expr::Tuple(ExprTuple { elems, .. }) = *left { + if let Expr::Assign(ExprAssign { left, .. }) = field { + if let Expr::Tuple(ExprTuple { elems, .. }) = left.as_ref() { for elem in elems { - let group_attr = maybe_parse_kv_lhs(elem.clone()); - if group_attr.is_none() || group_attr.clone().unwrap().len() != 1 { + let group_attr = maybe_parse_kv_lhs(&elem); + if group_attr.as_ref().map_or(true, |g| g.len() != 1) { return Err(parse::Error::new( input.span(), "`#[gen_hid_descriptor]` group spec key can only have a single element", )); } - let group_attr = group_attr.unwrap()[0].clone(); + let group_attr = &group_attr.unwrap()[0]; let mut val: Option = None; if let Expr::Assign(ExprAssign { right, .. }) = elem { - if let Expr::Lit(ExprLit { lit, .. }) = *right { + if let Expr::Lit(ExprLit { lit, .. }) = right.as_ref() { if let Lit::Int(lit) = lit { if let Ok(num) = lit.base10_parse::() { val = Some(num); @@ -244,10 +244,9 @@ fn parse_group_spec(input: ParseStream, field: Expr) -> Result { } else if let Expr::Path(ExprPath { path: Path { segments, .. }, .. - }) = *right - { + }) = right.as_ref() { val = try_resolve_constant( - group_attr.clone(), + group_attr.as_str(), quote! { #segments }.to_string(), ); if val.is_none() { @@ -264,7 +263,7 @@ fn parse_group_spec(input: ParseStream, field: Expr) -> Result { if val.is_none() { return Err(parse::Error::new(input.span(), "`#[gen_hid_descriptor]` group spec attribute value must be a numeric literal or recognized constant")); } - collection_attrs.push((group_attr, val.unwrap())); + collection_attrs.push((group_attr.clone(), val.unwrap())); } } } @@ -288,15 +287,14 @@ fn parse_group_spec(input: ParseStream, field: Expr) -> Result { if let Expr::Block(ExprBlock { block: Block { stmts, .. }, .. - }) = *right - { + }) = right.as_ref() { for stmt in stmts { if let Stmt::Expr(e) = stmt { - if let Err(e) = out.from_field(input, e) { + if let Err(e) = out.parse_field(input, e) { return Err(e); } } else if let Stmt::Semi(e, _) = stmt { - if let Err(e) = out.from_field(input, e) { + if let Err(e) = out.parse_field(input, e) { return Err(e); } } else { @@ -314,12 +312,12 @@ fn parse_group_spec(input: ParseStream, field: Expr) -> Result { } /// maybe_parse_kv_lhs returns a vector of :: separated idents. -fn maybe_parse_kv_lhs(field: Expr) -> Option> { +fn maybe_parse_kv_lhs(field: &Expr) -> Option> { if let Expr::Assign(ExprAssign { left, .. }) = field { if let Expr::Path(ExprPath { path: Path { segments, .. }, .. - }) = *left + }) = left.as_ref() { let mut out: Vec = vec![]; for s in segments { @@ -331,13 +329,13 @@ fn maybe_parse_kv_lhs(field: Expr) -> Option> { return None; } -fn parse_item_attrs(attrs: Vec) -> (Option, Option, ItemQuirks) { +fn parse_item_attrs(attrs: &Vec) -> (Option, Option, ItemQuirks) { let mut out: MainItemSetting = MainItemSetting { 0: 0 }; let mut had_settings: bool = false; let mut packed_bits: Option = None; let mut quirks: ItemQuirks = ItemQuirks{ ..Default::default() }; - for attr in attrs { + for attr in attrs.clone() { match attr.path.segments[0].ident.to_string().as_str() { "packed_bits" => { for tok in attr.tokens { @@ -411,10 +409,10 @@ fn parse_item_attrs(attrs: Vec) -> (Option, Opt } // maybe_parse_kv tries to parse an expression like 'blah=blah'. -fn maybe_parse_kv(field: Expr) -> Option<(String, String, Option, Option, ItemQuirks)> { +fn maybe_parse_kv(field: &Expr) -> Option<(String, String, Option, Option, ItemQuirks)> { // Match out the identifier on the left of the equals. let name: String; - if let Some(lhs) = maybe_parse_kv_lhs(field.clone()) { + if let Some(lhs) = maybe_parse_kv_lhs(&field) { if lhs.len() != 1 { return None; } @@ -424,7 +422,7 @@ fn maybe_parse_kv(field: Expr) -> Option<(String, String, Option Option<(String, String, Option = input.parse_terminated(Expr::parse)?; if fields.len() == 0 { - return Err(parse::Error::new( + Err(parse::Error::new( input.span(), "`#[gen_hid_descriptor]` expected information about the HID report", - )); - } - for field in fields { - if let Err(e) = out.from_field(input, field) { - return Err(e); + )) + }else { + for field in fields { + out.parse_field(input, &field)?; } + Ok(out) } - Ok(out) + } } impl GroupSpec { - fn from_field(&mut self, input: ParseStream, field: Expr) -> Result<()> { - if let Some(i) = maybe_parse_kv(field.clone()) { - let (name, item_kind, settings, bits, quirks) = i; + fn parse_field(&mut self, input: ParseStream, field: &Expr) -> Result<()> { + if let Some((name, + item_kind, + settings, + bits, + quirks)) = maybe_parse_kv(field) { self.set_item(name, item_kind.into(), settings, bits, quirks); return Ok(()); }; - match parse_group_spec(input, field.clone()) { + match parse_group_spec(input, field) { Err(e) => return Err(e), Ok(g) => self.add_nested_group(g), }; diff --git a/macros/src/split.rs b/macros/src/split.rs new file mode 100644 index 0000000..7ba0f05 --- /dev/null +++ b/macros/src/split.rs @@ -0,0 +1,26 @@ +use proc_macro2::{TokenStream}; +use quote::quote; +use syn::{Fields, FieldsNamed, ItemStruct, parse, Result}; + +pub(crate) fn wrap_struct(item: ItemStruct) -> TokenStream { + quote! { + #[derive(Debug, Clone, Copy, Serialize, Deserialize)] + #[repr(C, packed)] + #item + } +} + +pub(crate) fn read_struct(orig: &ItemStruct) -> Result<(FieldsNamed, ItemStruct)> { + let out = orig.clone(); + if let Fields::Named(named_fields) = out.fields { + Ok((named_fields, ItemStruct { + fields: Fields::Unit, + ..out + })) + } else { + Err(parse::Error::new( + out.ident.span(), + "`#[gen_hid_descriptor]` internal error when generating type", + )) + } +} \ No newline at end of file diff --git a/macros/src/utils.rs b/macros/src/utils.rs new file mode 100644 index 0000000..71f416f --- /dev/null +++ b/macros/src/utils.rs @@ -0,0 +1,37 @@ +use std::hash::Hash; +use std::collections::HashMap; +use std::convert::identity; + +pub(crate) fn group_by(vec: I, key_by: F) -> HashMap> + where + F: Fn(&V) -> K, + I: IntoIterator +{ + map_group_by(vec, key_by, identity) +} + +pub(crate) fn map_group_by(vec: I, key_by: F, mut map: G) -> HashMap> + where + F: Fn(&O) -> K, + G: FnMut(O) -> V, + I: IntoIterator +{ + let mut out = HashMap::new(); + for e in vec { + let key = key_by(&e); + if !out.contains_key(&key) { + out.insert(key.clone(), Vec::new()); + } + out.get_mut(&key).unwrap().push(map(e)); + } + out +} + +macro_rules! guard_syn { + ($exp:expr) => { + match $exp { + Ok(t) => t, + Err(e) => return e.to_compile_error().into(), + } + } +} diff --git a/src/descriptor.rs b/src/descriptor.rs index 20ca65a..a4698cf 100644 --- a/src/descriptor.rs +++ b/src/descriptor.rs @@ -1,25 +1,30 @@ //! Implements generation of HID report descriptors as well as common reports -extern crate usbd_hid_macros; -extern crate serde; -use serde::ser::{Serialize, Serializer, SerializeTuple}; - -pub use usbd_hid_macros::gen_hid_descriptor; /// Report types where serialized HID report descriptors are available. -pub trait SerializedDescriptor { - fn desc() -> &'static[u8]; +pub trait HIDDescriptor { + fn desc() -> &'static [u8]; +} + +/// Report types where serializable or deserializable in/out types are available. +pub trait HIDDescriptorTypes: HIDDescriptor { + type DeviceToHostReport; + type HostToDeviceReport; } -/// Report types which serialize into input reports, ready for transmission. -pub trait AsInputReport: Serialize {} +/// Placeholder Type that is nither serializable nor deserializable +pub struct UnsupportedDescriptor; /// Prelude for modules which use the `gen_hid_descriptor` macro. +/// To use managed serialize/deserialize features, crate `serde` must be +/// included e.g. `serde = { version = "~1.0", default-features = false }` pub mod generator_prelude { pub use usbd_hid_macros::gen_hid_descriptor; - pub use crate::descriptor::{SerializedDescriptor, AsInputReport}; - pub use serde::ser::{Serialize, SerializeTuple, Serializer}; + pub use crate::descriptor::{HIDDescriptor, HIDDescriptorTypes, UnsupportedDescriptor}; + pub use serde::{Serialize, Deserialize}; } +use generator_prelude::*; + /// MouseReport describes a report and its companion descriptor than can be used /// to send mouse movements and button presses to a host. #[gen_hid_descriptor( @@ -40,6 +45,7 @@ pub mod generator_prelude { } )] #[allow(dead_code)] +#[derive(Debug)] pub struct MouseReport { pub buttons: u8, pub x: i8, @@ -63,6 +69,7 @@ pub struct MouseReport { } )] #[allow(dead_code)] +#[derive(Debug)] pub struct KeyboardReport { pub modifier: u8, pub leds: u8, diff --git a/src/hid_class.rs b/src/hid_class.rs index e8856a6..e7e6fbd 100644 --- a/src/hid_class.rs +++ b/src/hid_class.rs @@ -2,9 +2,11 @@ use usb_device::class_prelude::*; use usb_device::Result; -use crate::descriptor::AsInputReport; -extern crate ssmarshal; -use ssmarshal::serialize; +use ssmarshal::{serialize, deserialize}; +use core::marker::PhantomData; +use serde::{Serialize}; +use serde::de::DeserializeOwned; +use crate::descriptor::{HIDDescriptor, HIDDescriptorTypes}; const USB_CLASS_HID: u8 = 0x03; const USB_SUBCLASS_NONE: u8 = 0x00; @@ -25,41 +27,29 @@ const HID_REQ_SET_REPORT: u8 = 0x09; /// /// Users are expected to provide the report descriptor, as well as pack /// and unpack reports which are read or staged for transmission. -pub struct HIDClass<'a, B: UsbBus> { +pub struct HIDClass<'a, B: UsbBus, T: HIDDescriptor> { if_num: InterfaceNumber, out_ep: EndpointOut<'a, B>, in_ep: EndpointIn<'a, B>, - report_descriptor: &'static [u8], + desc_type: PhantomData, } -impl HIDClass<'_, B> { +impl<'a, B: UsbBus, T: HIDDescriptor> HIDClass<'a, B, T> { /// Creates a new HIDClass with the provided UsbBus & HID report descriptor. /// /// poll_ms configures how frequently the host should poll for reading/writing /// HID reports. A lower value means better throughput & latency, at the expense /// of CPU on the device & bandwidth on the bus. A value of 10 is reasonable for /// high performance uses, and a value of 255 is good for best-effort usecases. - pub fn new<'a>(alloc: &'a UsbBusAllocator, report_descriptor: &'static [u8], poll_ms: u8) -> HIDClass<'a, B> { - HIDClass { + pub fn new(alloc: &'a UsbBusAllocator, poll_ms: u8) -> Self { + Self { if_num: alloc.interface(), out_ep: alloc.interrupt(64, poll_ms), in_ep: alloc.interrupt(64, poll_ms), - report_descriptor: report_descriptor, + desc_type: Default::default(), } } - /// Tries to write an input report by serializing the given report structure. - /// A BufferOverflow error is returned if the serialized report is greater than - /// 64 bytes in size. - pub fn push_input(&self, r: &IR) -> Result { - let mut buff: [u8; 64] = [0; 64]; - let size = match serialize(&mut buff, r) { - Ok(l) => l, - Err(_) => return Err(UsbError::BufferOverflow), - }; - self.in_ep.write(&buff[0..size]) - } - /// Tries to write an input (device-to-host) report from the given raw bytes. /// Data is expected to be a valid HID report for INPUT items. If report ID's /// were used in the descriptor, the report ID corresponding to this report @@ -76,7 +66,38 @@ impl HIDClass<'_, B> { } } -impl UsbClass for HIDClass<'_, B> { +impl HIDClass<'_, B, T> where T: HIDDescriptorTypes{ + + /// Tries to write an input report by serializing the given report structure. + /// A BufferOverflow error is returned if the serialized report is greater than + /// 64 bytes in size. + pub fn push_input(&self, r: &I) -> Result { + let mut buff: [u8; 64] = [0; 64]; + let size = match serialize(&mut buff, r) { + Ok(l) => l, + Err(_) => return Err(UsbError::BufferOverflow), + }; + self.push_raw_input(&buff[0..size]) + } +} + +impl <'sr, B: UsbBus, T, O: DeserializeOwned> HIDClass<'_, B, T> where T: HIDDescriptorTypes { + + /// Tries to read an output report by deserializing the incoming bytes. + /// - A BufferOverflow error is returned if the report sent by the host + /// is greater than 64 bytes in size. + /// - A ParseError is returned if the deserialization process fails. + pub fn pull_output(&self) -> Result<(O, usize)> { + let mut buff: [u8; 64] = [0; 64]; + self.pull_raw_output(&mut buff)?; + match deserialize::(&buff) { + Ok(l) => Ok(l), + Err(_) => return Err(UsbError::ParseError), + } + } +} + +impl UsbClass for HIDClass<'_, B, T> { fn get_configuration_descriptors(&self, writer: &mut DescriptorWriter) -> Result<()> { writer.interface( self.if_num, @@ -97,7 +118,7 @@ impl UsbClass for HIDClass<'_, B> { // We have a HID report descriptor the host should read HID_DESC_DESCTYPE_HID_REPORT, // HID report descriptor size, - (self.report_descriptor.len() & 0xFF) as u8, (self.report_descriptor.len()>>8 & 0xFF) as u8, + (T::desc().len() & 0xFF) as u8, (T::desc().len()>>8 & 0xFF) as u8, ])?; writer.endpoint(&self.out_ep)?; @@ -118,7 +139,7 @@ impl UsbClass for HIDClass<'_, B> { (control::RequestType::Standard, control::Request::GET_DESCRIPTOR) => { match (req.value>>8) as u8 { HID_DESC_DESCTYPE_HID_REPORT => { - xfer.accept_with_static(self.report_descriptor).ok(); + xfer.accept_with_static(T::desc()).ok(); }, HID_DESC_DESCTYPE_HID => { let buf = &[ @@ -135,7 +156,7 @@ impl UsbClass for HIDClass<'_, B> { // We have a HID report descriptor the host should read HID_DESC_DESCTYPE_HID_REPORT, // HID report descriptor size, - (self.report_descriptor.len() & 0xFF) as u8, (self.report_descriptor.len()>>8 & 0xFF) as u8, + (T::desc().len() & 0xFF) as u8, (T::desc().len()>>8 & 0xFF) as u8, ]; xfer.accept_with(buf).ok(); }, diff --git a/src/lib.rs b/src/lib.rs index 03536b1..4e875bb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -201,6 +201,11 @@ mod tests { assert_eq!(CustomPackedBits::desc(), expected); } + #[test] + fn test_custom_packed_bits_in() { + // test compiles + CustomPackedBits::R1 { f1: 1, f2: 2, f3: [3u8; 3] }; + } #[test] fn test_mouse_descriptor() { @@ -249,4 +254,27 @@ mod tests { ]; assert_eq!(KeyboardReport::desc()[0..51], expected[0..51]); } + + #[gen_hid_descriptor( + (report_id = 0x01,) = { + #[packed_bits 3] f1=input; + #[packed_bits 20] f3=input; + }, + (report_id = 0xff,) = { + #[packed_bits 9] f2=output; + } + )] + #[allow(dead_code)] + struct CustomPackedOutBits { + f1: u8, + f2: u16, + f3: [u8; 3], + } + + #[test] + fn custom_packed_out_bits() { + CustomPackedOutBits::R1{f1: 1, f3: [3u8; 3]}; + CustomPackedOutBitsOut::R255{f2: 2}; + } + }