usbd_hid_macros/
lib.rs

1//! Internal implementation details of usbd-hid.
2#![no_std]
3
4extern crate alloc;
5extern crate proc_macro;
6extern crate usbd_hid_descriptors;
7
8use alloc::{boxed::Box, vec, vec::Vec};
9use proc_macro::TokenStream;
10use proc_macro2::Span;
11use quote::quote;
12use syn::punctuated::Punctuated;
13use syn::token::Bracket;
14use syn::{parse, parse_macro_input, Expr, Fields, ItemStruct};
15use syn::{Pat, PatSlice, Result};
16
17use byteorder::{ByteOrder, LittleEndian};
18use usbd_hid_descriptors::*;
19
20mod spec;
21use spec::*;
22mod item;
23use item::*;
24mod packer;
25use packer::{gen_serializer, uses_report_ids};
26
27/// Attribute to generate a HID descriptor & serialization code
28///
29/// You are expected to provide two inputs to this generator:
30///
31///  - A struct of named fields (which follows the `gen_hid_descriptor` attribute)
32///  - A specially-formatted section describing the properties of the descriptor (this
33///    section must be provided as arguments to the `gen_hid_descriptor()` attribute)
34///
35/// The generated HID descriptor will be available as a `&[u8]` by calling
36/// `YourStructType::desc()`. `YourStructType` also now implements `SerializedDescriptor`.
37///
38/// As long as a descriptor describes only input or output types, and a report ID is
39/// not used, the wire format for transmitting and recieving the data described by the
40/// descriptor is simply the packed representation of the struct itself.
41/// Where report ID's are used anywhere in the descriptor, you must prepend the relevant
42/// report ID to the packed representation of the struct prior to transmission.
43///
44/// If inputs and outputs are mixed within the same HID descriptor, then only the struct
45/// fields used in that direction can be present in a payload being transmitted in that
46/// direction.
47///
48/// If report ID's are not used, input (device-to-host) serialization code is generated
49/// automatically, and is represented by the implementation of the `AsInputReport` trait.
50///
51/// # Examples
52///
53/// - Custom 32-octet array, sent from device to host
54///
55/// ```ignore
56/// #[gen_hid_descriptor(
57///     (collection = APPLICATION, usage_page = VENDOR_DEFINED_START, usage = 0x01) = {
58///         buff=input;
59///     }
60/// )]
61/// struct CustomInputReport {
62///     buff: [u8; 32],
63/// }
64/// ```
65///
66/// - Custom input / output, sent in either direction
67///
68/// ```ignore
69/// #[gen_hid_descriptor(
70///     (collection = APPLICATION, usage_page = VENDOR_DEFINED_START, usage = 0x01) = {
71///         input_buffer=input;
72///         output_buffer=output;
73///     }
74/// )]
75/// struct CustomBidirectionalReport {
76///     input_buffer: [u8; 32],
77///     output_buffer: [u8; 32],
78/// }
79/// ```
80///
81/// Because both inputs and outputs are used, the data format when sending / recieving is the
82/// 32 bytes in the relevant direction, **NOT** the full 64 bytes contained within the struct.
83///
84/// - Packed bitfields
85///
86/// ```ignore
87/// #[gen_hid_descriptor(
88///     (report_id = 0x01,) = {
89///         #[packed_bits 3] f1=input;
90///         #[packed_bits 9] f2=input;
91///     }
92/// )]
93/// struct CustomPackedBits {
94///     f1: u8,
95///     f2: u16,
96/// }
97/// ```
98///
99/// Because the `#[packed_bits]` sub-attribute was used, the two input fields specified are
100/// interpreted as packed bits. As such, `f1` describes 3 boolean inputs, and `f2` describes
101/// 9 boolean inputs. Padding constants are automatically generated.
102///
103/// The `#[packed_bits <num bits>]` feature is intended to be used for describing button presses.
104///
105/// - Customizing the settings on a report item
106///
107/// ```ignore
108/// #[gen_hid_descriptor(
109///     (collection = APPLICATION, usage_page = VENDOR_DEFINED_START, usage = 0x01) = {
110///         (usage_min = X, usage_max = Y) = {
111///             #[item_settings data,variable,relative] x=input;
112///             #[item_settings data,variable,relative] y=input;
113///         };
114///     }
115/// )]
116/// struct CustomCoords {
117///     x: i8,
118///     y: i8,
119/// }
120/// ```
121///
122/// The above example describes a report which sends X & Y co-ordinates. As indicated in
123/// the `#[item_settings]` sub-attribute, the individual inputs are described as:
124///
125///  - Datapoints (`data`) - as opposed to constant
126///  - Variable (`variable`) - as opposed to an array
127///  - Relative (`relative`) - as opposed to absolute
128///
129/// # Supported struct types
130///
131/// The struct following the attribute must consist entirely of named fields, using
132/// only types enumerated below, or fixed-size arrays of the types enumerated below.
133///
134///  - u8 / i8
135///  - u16 / i16
136///  - u32 / i32
137///
138/// `LOGICAL_MINIMUM` & `LOGICAL_MAXIMUM` are automatically set in the descriptor, based
139/// on the type & whether `#[packed_bits]` was set on the field or not.
140///
141/// # Descriptor format
142///
143/// The parameters of the HID descriptor should be provided as arguments to the attribute.
144/// The arguments should follow the basic form:
145///
146/// ```ignore
147/// #[gen_hid_descriptor(
148///     <collection-spec> OR <item-spec>;
149///     <collection-spec> OR <item-spec>;
150///     ...
151///     <collection-spec> OR <item-spec>
152/// )]
153/// ```
154///
155/// ## `collection-spec`:
156///
157/// ```text
158///     (parameter = <constant or 0xxxx>, ...) = {
159///         <collection-spec> OR <item-spec>;
160///         ...
161///     }
162/// ```
163///
164/// Note: All collection specs must end in a semicolon, except the top-level one.
165///
166/// Note: Parameters are a tuple, so make sure you have a trailing comma if you only have one
167/// parameter.
168///
169/// The valid parameters are `collection`, `usage_page`, `usage`, `usage_min`, `usage_max`,
170/// `unit_exponent`, and `report_id`.
171/// These simply configure parameters that apply to contained items in the report.
172/// Use of the `collection` parameter automatically creates a collection feature for all items
173/// which are contained within it, and other parameters specified in the same collection-spec
174/// apply to the collection, not directly to the elements of the collection (ie: defining a
175/// collection + a usage generates a descriptor where the usage is set on the collection, not the
176/// items contained within the collection).
177///
178/// ## `item-spec`:
179///
180/// ```ignore
181///     #[packed_bits <num_items>] #[item_settings <setting>,...] <fieldname>=input OR output;
182/// ```
183///
184/// The two sub-attributes are both optional.
185///
186///   - `fieldname` refers to the name of a field within the struct. All fields must be specified.
187///   - `input` fields are sent in reports from device to host. `output` fields are sent in reports
188///     from host to device. This matches the terminology used in the USB & HID specifications.
189///   - `packed_bits` configures the field as a set of `num_items` booleans rather than a number.
190///     If the number of packed bits is less than the natural bit width of the field, the
191///     remaining most-significant bits are set as constants within the report and are not used.
192///     `packed_bits` is typically used to implement buttons.
193///   - `item_settings` describes settings on the input/output item, as enumerated in section
194///     6.2.2.5 of the [HID specification, version 1.11](https://www.usb.org/sites/default/files/documents/hid1_11.pdf).
195///     By default, all items are configured as `(Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)`.
196///
197/// ## Quirks
198///
199/// By default generated descriptors are such to maximize compatibility. To change this
200/// behaviour, you can use a `#[quirks <settings>]` attribute on the relevant input/output
201/// item.
202/// For now, the only quirk is `#[quirks allow_short]`, which allows global features to be
203/// serialized in a 1 byte form. This is disabled by default as the Windows HID parser
204/// considers it invalid.
205#[proc_macro_attribute]
206pub fn gen_hid_descriptor(args: TokenStream, input: TokenStream) -> TokenStream {
207    let decl = parse_macro_input!(input as ItemStruct);
208    let spec = parse_macro_input!(args as GroupSpec);
209    let ident = decl.ident.clone();
210
211    // Error if the struct doesn't name its fields.
212    match decl.fields {
213        Fields::Named(_) => (),
214        _ => {
215            return parse::Error::new(
216                ident.span(),
217                "`#[gen_hid_descriptor]` type must name fields",
218            )
219            .to_compile_error()
220            .into()
221        }
222    };
223
224    let do_serialize = !uses_report_ids(&Spec::Collection(spec.clone()));
225
226    let output = match compile_descriptor(spec, &decl.fields) {
227        Ok(d) => d,
228        Err(e) => return e.to_compile_error().into(),
229    };
230    let (descriptor, fields) = output;
231
232    let mut out = quote! {
233        #[derive(Debug, Clone, Copy, Eq, PartialEq)]
234        #[repr(C, packed)]
235        #decl
236
237        impl SerializedDescriptor for #ident {
238            fn desc() -> &'static[u8] {
239                &#descriptor
240            }
241        }
242    };
243
244    if do_serialize {
245        let input_serializer = match gen_serializer(fields, MainItemKind::Input) {
246            Ok(s) => s,
247            Err(e) => return e.to_compile_error().into(),
248        };
249
250        out = quote! {
251            #out
252
253            impl Serialize for #ident {
254                fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
255                where
256                    S: Serializer,
257                {
258                    #input_serializer
259                }
260            }
261            impl AsInputReport for #ident {}
262        };
263    }
264
265    TokenStream::from(out)
266}
267
268fn compile_descriptor(
269    spec: GroupSpec,
270    fields: &Fields,
271) -> Result<(PatSlice, Vec<ReportUnaryField>)> {
272    let mut compiler = DescCompilation {
273        ..Default::default()
274    };
275    let mut elems = Punctuated::new();
276    compiler.emit_group(&mut elems, &spec, fields)?;
277
278    Ok((
279        PatSlice {
280            attrs: vec![],
281            elems,
282            bracket_token: Bracket {
283                span: Span::call_site(),
284            },
285        },
286        compiler.report_fields(),
287    ))
288}
289
290#[derive(Default)]
291struct DescCompilation {
292    logical_minimum: Option<isize>,
293    logical_maximum: Option<isize>,
294    report_size: Option<u16>,
295    report_count: Option<u16>,
296    processed_fields: Vec<ReportUnaryField>,
297}
298
299impl DescCompilation {
300    fn report_fields(&self) -> Vec<ReportUnaryField> {
301        self.processed_fields.clone()
302    }
303
304    fn emit(
305        &self,
306        elems: &mut Punctuated<Pat, syn::token::Comma>,
307        prefix: &mut ItemPrefix,
308        buf: [u8; 4],
309        signed: bool,
310    ) {
311        // println!("buf: {:?}", buf);
312        if buf[1..4] == [0, 0, 0] && !(signed && buf[0] == 255) {
313            prefix.set_byte_count(1);
314            elems.push(byte_literal(prefix.0));
315            elems.push(byte_literal(buf[0]));
316        } else if buf[2..4] == [0, 0] && !(signed && buf[1] == 255) {
317            prefix.set_byte_count(2);
318            elems.push(byte_literal(prefix.0));
319            elems.push(byte_literal(buf[0]));
320            elems.push(byte_literal(buf[1]));
321        } else {
322            prefix.set_byte_count(3);
323            elems.push(byte_literal(prefix.0));
324            elems.push(byte_literal(buf[0]));
325            elems.push(byte_literal(buf[1]));
326            elems.push(byte_literal(buf[2]));
327            elems.push(byte_literal(buf[3]));
328        }
329        // println!("emitted {} data bytes", prefix.byte_count());
330    }
331
332    fn emit_item(
333        &self,
334        elems: &mut Punctuated<Pat, syn::token::Comma>,
335        typ: u8,
336        kind: u8,
337        num: isize,
338        signed: bool,
339        allow_short_form: bool,
340    ) {
341        let mut prefix = ItemPrefix(0);
342        prefix.set_tag(kind);
343        prefix.set_type(typ);
344
345        // TODO: Support long tags.
346
347        // Section 6.2.2.4: An Input item could have a data size of zero (0)
348        // bytes. In this case the value of each data bit for the item can be
349        // assumed to be zero. This is functionally identical to using a item
350        // tag that specifies a 4-byte data item followed by four zero bytes.
351        let allow_short = typ == ItemType::Main.into() && kind == MainItemKind::Input.into();
352        if allow_short_form && allow_short && num == 0 {
353            prefix.set_byte_count(0);
354            elems.push(byte_literal(prefix.0));
355            return;
356        }
357
358        let mut buf = [0; 4];
359        LittleEndian::write_i32(&mut buf, num as i32);
360        self.emit(elems, &mut prefix, buf, signed);
361    }
362
363    fn handle_globals(
364        &mut self,
365        elems: &mut Punctuated<Pat, syn::token::Comma>,
366        item: MainItem,
367        quirks: ItemQuirks,
368    ) {
369        if self.logical_minimum.is_none() || self.logical_minimum.unwrap() != item.logical_minimum {
370            self.emit_item(
371                elems,
372                ItemType::Global.into(),
373                GlobalItemKind::LogicalMin.into(),
374                item.logical_minimum,
375                true,
376                quirks.allow_short_form,
377            );
378            self.logical_minimum = Some(item.logical_minimum);
379        }
380        if self.logical_maximum.is_none() || self.logical_maximum.unwrap() != item.logical_maximum {
381            self.emit_item(
382                elems,
383                ItemType::Global.into(),
384                GlobalItemKind::LogicalMax.into(),
385                item.logical_maximum,
386                true,
387                quirks.allow_short_form,
388            );
389            self.logical_maximum = Some(item.logical_maximum);
390        }
391        if self.report_size.is_none() || self.report_size.unwrap() != item.report_size {
392            self.emit_item(
393                elems,
394                ItemType::Global.into(),
395                GlobalItemKind::ReportSize.into(),
396                item.report_size as isize,
397                true,
398                quirks.allow_short_form,
399            );
400            self.report_size = Some(item.report_size);
401        }
402        if self.report_count.is_none() || self.report_count.unwrap() != item.report_count {
403            self.emit_item(
404                elems,
405                ItemType::Global.into(),
406                GlobalItemKind::ReportCount.into(),
407                item.report_count as isize,
408                true,
409                quirks.allow_short_form,
410            );
411            self.report_count = Some(item.report_count);
412        }
413    }
414
415    fn emit_field(
416        &mut self,
417        elems: &mut Punctuated<Pat, syn::token::Comma>,
418        i: &ItemSpec,
419        item: MainItem,
420    ) {
421        self.handle_globals(elems, item.clone(), i.quirks);
422        let item_data = match &i.settings {
423            Some(s) => s.0 as isize,
424            None => 0x02, // 0x02 = Data,Var,Abs
425        };
426        self.emit_item(
427            elems,
428            ItemType::Main.into(),
429            item.kind.into(),
430            item_data,
431            true,
432            i.quirks.allow_short_form,
433        );
434
435        if let Some(padding) = item.padding_bits {
436            // Make another item of type constant to carry the remaining bits.
437            let padding = MainItem {
438                report_size: 1,
439                report_count: padding,
440                ..item
441            };
442            self.handle_globals(elems, padding, i.quirks);
443
444            let mut const_settings = MainItemSetting(0);
445            const_settings.set_constant(true);
446            const_settings.set_variable(true);
447            self.emit_item(
448                elems,
449                ItemType::Main.into(),
450                item.kind.into(),
451                const_settings.0 as isize,
452                true,
453                i.quirks.allow_short_form,
454            );
455        }
456    }
457
458    fn emit_group(
459        &mut self,
460        elems: &mut Punctuated<Pat, syn::token::Comma>,
461        spec: &GroupSpec,
462        fields: &Fields,
463    ) -> Result<()> {
464        // println!("GROUP: {:?}", spec);
465
466        if let Some(usage_page) = spec.usage_page {
467            self.emit_item(
468                elems,
469                ItemType::Global.into(),
470                GlobalItemKind::UsagePage.into(),
471                usage_page as isize,
472                false,
473                false,
474            );
475        }
476        for usage in &spec.usage {
477            self.emit_item(
478                elems,
479                ItemType::Local.into(),
480                LocalItemKind::Usage.into(),
481                *usage as isize,
482                false,
483                false,
484            );
485        }
486        if let Some(usage_min) = spec.usage_min {
487            self.emit_item(
488                elems,
489                ItemType::Local.into(),
490                LocalItemKind::UsageMin.into(),
491                usage_min as isize,
492                false,
493                false,
494            );
495        }
496        if let Some(usage_max) = spec.usage_max {
497            self.emit_item(
498                elems,
499                ItemType::Local.into(),
500                LocalItemKind::UsageMax.into(),
501                usage_max as isize,
502                false,
503                false,
504            );
505        }
506        if let Some(report_id) = spec.report_id {
507            self.emit_item(
508                elems,
509                ItemType::Global.into(),
510                GlobalItemKind::ReportID.into(),
511                report_id as isize,
512                false,
513                false,
514            );
515        }
516        if let Some(collection) = spec.collection {
517            self.emit_item(
518                elems,
519                ItemType::Main.into(),
520                MainItemKind::Collection.into(),
521                collection as isize,
522                false,
523                false,
524            );
525        }
526        if let Some(logical_minimum) = spec.logical_min {
527            // Set to 0 to indicate that we've already set the default
528            // See handle_globals
529            self.logical_minimum = Some(0);
530            self.emit_item(
531                elems,
532                ItemType::Global.into(),
533                GlobalItemKind::LogicalMin.into(),
534                logical_minimum as isize,
535                false,
536                false,
537            );
538        }
539        if let Some(unit_exponent) = spec.unit_exponent {
540            self.emit_item(
541                elems,
542                ItemType::Global.into(),
543                GlobalItemKind::UnitExponent.into(),
544                unit_exponent as isize,
545                false,
546                false,
547            );
548        }
549
550        for name in spec.clone() {
551            let f = spec.get(name.clone()).unwrap();
552            match f {
553                Spec::MainItem(i) => {
554                    let d = field_decl(fields, name);
555                    match analyze_field(d.clone(), d.ty, i) {
556                        Ok(item) => {
557                            self.processed_fields.push(item.clone());
558                            self.emit_field(elems, i, item.descriptor_item)
559                        }
560                        Err(e) => return Err(e),
561                    }
562                }
563                Spec::Collection(g) => {
564                    self.emit_group(elems, g, fields)?;
565                }
566            }
567        }
568
569        if spec.collection.is_some() {
570            // Close collection.
571            elems.push(byte_literal(0xc0));
572        }
573        Ok(())
574    }
575}
576
577fn byte_literal(lit: u8) -> Pat {
578    // print!("{:x} ", lit);
579    // println!();
580    Pat::Lit(syn::PatLit {
581        attrs: vec![],
582        expr: Box::new(Expr::Lit(syn::ExprLit {
583            attrs: vec![],
584            lit: syn::Lit::Byte(syn::LitByte::new(lit, Span::call_site())),
585        })),
586    })
587}