2022-12-27 09:32:52 +05:30
|
|
|
/*
|
|
|
|
*
|
|
|
|
* Copyright (c) 2020-2022 Project CHIP Authors
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
use proc_macro::TokenStream;
|
|
|
|
use proc_macro2::Span;
|
|
|
|
use quote::{format_ident, quote};
|
|
|
|
use syn::Lit::{Int, Str};
|
|
|
|
use syn::NestedMeta::{Lit, Meta};
|
|
|
|
use syn::{parse_macro_input, DeriveInput, Lifetime};
|
|
|
|
use syn::{
|
|
|
|
Meta::{List, NameValue},
|
|
|
|
MetaList, MetaNameValue, Type,
|
|
|
|
};
|
|
|
|
|
|
|
|
struct TlvArgs {
|
|
|
|
start: u8,
|
|
|
|
datatype: String,
|
|
|
|
unordered: bool,
|
|
|
|
lifetime: syn::Lifetime,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for TlvArgs {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
start: 0,
|
|
|
|
datatype: "struct".to_string(),
|
|
|
|
unordered: false,
|
|
|
|
lifetime: Lifetime::new("'_", Span::call_site()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn parse_tlvargs(ast: &DeriveInput) -> TlvArgs {
|
|
|
|
let mut tlvargs: TlvArgs = Default::default();
|
|
|
|
|
2023-01-10 08:53:04 +01:00
|
|
|
if !ast.attrs.is_empty() {
|
2022-12-27 09:32:52 +05:30
|
|
|
if let List(MetaList {
|
|
|
|
path,
|
|
|
|
paren_token: _,
|
|
|
|
nested,
|
|
|
|
}) = ast.attrs[0].parse_meta().unwrap()
|
|
|
|
{
|
|
|
|
if path.is_ident("tlvargs") {
|
|
|
|
for a in nested {
|
|
|
|
if let Meta(NameValue(MetaNameValue {
|
|
|
|
path: key_path,
|
|
|
|
eq_token: _,
|
|
|
|
lit: key_val,
|
|
|
|
})) = a
|
|
|
|
{
|
|
|
|
if key_path.is_ident("start") {
|
|
|
|
if let Int(litint) = key_val {
|
|
|
|
tlvargs.start = litint.base10_parse::<u8>().unwrap();
|
|
|
|
}
|
|
|
|
} else if key_path.is_ident("lifetime") {
|
|
|
|
if let Str(litstr) = key_val {
|
|
|
|
tlvargs.lifetime =
|
|
|
|
Lifetime::new(&litstr.value(), Span::call_site());
|
|
|
|
}
|
|
|
|
} else if key_path.is_ident("datatype") {
|
|
|
|
if let Str(litstr) = key_val {
|
|
|
|
tlvargs.datatype = litstr.value();
|
|
|
|
}
|
|
|
|
} else if key_path.is_ident("unordered") {
|
|
|
|
tlvargs.unordered = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
tlvargs
|
|
|
|
}
|
|
|
|
|
|
|
|
fn parse_tag_val(field: &syn::Field) -> Option<u8> {
|
2023-01-10 08:53:04 +01:00
|
|
|
if !field.attrs.is_empty() {
|
2022-12-27 09:32:52 +05:30
|
|
|
if let List(MetaList {
|
|
|
|
path,
|
|
|
|
paren_token: _,
|
|
|
|
nested,
|
|
|
|
}) = field.attrs[0].parse_meta().unwrap()
|
|
|
|
{
|
|
|
|
if path.is_ident("tagval") {
|
|
|
|
for a in nested {
|
|
|
|
if let Lit(Int(litint)) = a {
|
|
|
|
return Some(litint.base10_parse::<u8>().unwrap());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Generate a ToTlv implementation for a structure
|
|
|
|
fn gen_totlv_for_struct(
|
|
|
|
fields: &syn::FieldsNamed,
|
|
|
|
struct_name: &proc_macro2::Ident,
|
2023-01-10 08:53:04 +01:00
|
|
|
tlvargs: &TlvArgs,
|
|
|
|
generics: &syn::Generics,
|
2022-12-27 09:32:52 +05:30
|
|
|
) -> TokenStream {
|
|
|
|
let mut tag_start = tlvargs.start;
|
|
|
|
let datatype = format_ident!("start_{}", tlvargs.datatype);
|
|
|
|
|
|
|
|
let mut idents = Vec::new();
|
|
|
|
let mut tags = Vec::new();
|
|
|
|
|
|
|
|
for field in fields.named.iter() {
|
|
|
|
// let field_name: &syn::Ident = field.ident.as_ref().unwrap();
|
|
|
|
// let name: String = field_name.to_string();
|
|
|
|
// let literal_key_str = syn::LitStr::new(&name, field.span());
|
|
|
|
// let type_name = &field.ty;
|
|
|
|
// keys.push(quote! { #literal_key_str });
|
|
|
|
idents.push(&field.ident);
|
|
|
|
// types.push(type_name.to_token_stream());
|
2023-01-10 08:53:04 +01:00
|
|
|
if let Some(a) = parse_tag_val(field) {
|
2022-12-27 09:32:52 +05:30
|
|
|
tags.push(a);
|
|
|
|
} else {
|
|
|
|
tags.push(tag_start);
|
|
|
|
tag_start += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let expanded = quote! {
|
|
|
|
impl #generics ToTLV for #struct_name #generics {
|
|
|
|
fn to_tlv(&self, tw: &mut TLVWriter, tag_type: TagType) -> Result<(), Error> {
|
|
|
|
tw. #datatype (tag_type)?;
|
|
|
|
#(
|
|
|
|
self.#idents.to_tlv(tw, TagType::Context(#tags))?;
|
|
|
|
)*
|
|
|
|
tw.end_container()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
// panic!("The generated code is {}", expanded);
|
|
|
|
expanded.into()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Generate a ToTlv implementation for an enum
|
|
|
|
fn gen_totlv_for_enum(
|
2023-01-10 08:53:04 +01:00
|
|
|
data_enum: &syn::DataEnum,
|
2022-12-27 09:32:52 +05:30
|
|
|
enum_name: &proc_macro2::Ident,
|
2023-01-10 08:53:04 +01:00
|
|
|
tlvargs: &TlvArgs,
|
|
|
|
generics: &syn::Generics,
|
2022-12-27 09:32:52 +05:30
|
|
|
) -> TokenStream {
|
|
|
|
let mut tag_start = tlvargs.start;
|
|
|
|
|
|
|
|
let mut variant_names = Vec::new();
|
|
|
|
let mut types = Vec::new();
|
|
|
|
let mut tags = Vec::new();
|
|
|
|
|
|
|
|
for v in data_enum.variants.iter() {
|
|
|
|
variant_names.push(&v.ident);
|
|
|
|
if let syn::Fields::Unnamed(fields) = &v.fields {
|
|
|
|
if let Type::Path(path) = &fields.unnamed[0].ty {
|
|
|
|
types.push(&path.path.segments[0].ident);
|
|
|
|
} else {
|
|
|
|
panic!("Path not found {:?}", v.fields);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
panic!("Unnamed field not found {:?}", v.fields);
|
|
|
|
}
|
|
|
|
tags.push(tag_start);
|
|
|
|
tag_start += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
let expanded = quote! {
|
|
|
|
impl #generics ToTLV for #enum_name #generics {
|
|
|
|
fn to_tlv(&self, tw: &mut TLVWriter, tag_type: TagType) -> Result<(), Error> {
|
|
|
|
tw.start_struct(tag_type)?;
|
|
|
|
match self {
|
|
|
|
#(
|
|
|
|
Self::#variant_names(c) => { c.to_tlv(tw, TagType::Context(#tags))?; },
|
|
|
|
)*
|
|
|
|
}
|
|
|
|
tw.end_container()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// panic!("Expanded to {}", expanded);
|
|
|
|
expanded.into()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Derive ToTLV Macro
|
|
|
|
///
|
|
|
|
/// This macro works for structures. It will create an implementation
|
|
|
|
/// of the ToTLV trait for that structure. All the members of the
|
|
|
|
/// structure, sequentially, will get Context tags starting from 0
|
|
|
|
/// Some configurations are possible through the 'tlvargs' attributes.
|
|
|
|
/// For example:
|
|
|
|
/// #[tlvargs(start = 1, datatype = "list")]
|
|
|
|
///
|
|
|
|
/// start: This can be used to override the default tag from which the
|
|
|
|
/// encoding starts (Default: 0)
|
|
|
|
/// datatype: This can be used to define whether this data structure is
|
|
|
|
/// to be encoded as a structure or list. Possible values: list
|
|
|
|
/// (Default: struct)
|
|
|
|
///
|
|
|
|
/// Additionally, structure members can use the tagval attribute to
|
|
|
|
/// define a specific tag to be used
|
|
|
|
/// For example:
|
|
|
|
/// #[argval(22)]
|
|
|
|
/// name: u8,
|
|
|
|
/// In the above case, the 'name' attribute will be encoded/decoded with
|
|
|
|
/// the tag 22
|
|
|
|
|
|
|
|
#[proc_macro_derive(ToTLV, attributes(tlvargs, tagval))]
|
|
|
|
pub fn derive_totlv(item: TokenStream) -> TokenStream {
|
|
|
|
let ast = parse_macro_input!(item as DeriveInput);
|
|
|
|
let name = &ast.ident;
|
|
|
|
|
|
|
|
let tlvargs = parse_tlvargs(&ast);
|
|
|
|
let generics = ast.generics;
|
|
|
|
|
|
|
|
if let syn::Data::Struct(syn::DataStruct {
|
|
|
|
fields: syn::Fields::Named(ref fields),
|
|
|
|
..
|
|
|
|
}) = ast.data
|
|
|
|
{
|
2023-01-10 08:53:04 +01:00
|
|
|
gen_totlv_for_struct(fields, name, &tlvargs, &generics)
|
2022-12-27 09:32:52 +05:30
|
|
|
} else if let syn::Data::Enum(data_enum) = ast.data {
|
2023-01-10 08:53:04 +01:00
|
|
|
gen_totlv_for_enum(&data_enum, name, &tlvargs, &generics)
|
2022-12-27 09:32:52 +05:30
|
|
|
} else {
|
|
|
|
panic!(
|
|
|
|
"Derive ToTLV - Only supported Struct for now {:?}",
|
|
|
|
ast.data
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Generate a FromTlv implementation for a structure
|
|
|
|
fn gen_fromtlv_for_struct(
|
|
|
|
fields: &syn::FieldsNamed,
|
|
|
|
struct_name: &proc_macro2::Ident,
|
|
|
|
tlvargs: TlvArgs,
|
2023-01-10 08:53:04 +01:00
|
|
|
generics: &syn::Generics,
|
2022-12-27 09:32:52 +05:30
|
|
|
) -> TokenStream {
|
|
|
|
let mut tag_start = tlvargs.start;
|
|
|
|
let lifetime = tlvargs.lifetime;
|
|
|
|
let datatype = format_ident!("confirm_{}", tlvargs.datatype);
|
|
|
|
|
|
|
|
let mut idents = Vec::new();
|
|
|
|
let mut types = Vec::new();
|
|
|
|
let mut tags = Vec::new();
|
|
|
|
|
|
|
|
for field in fields.named.iter() {
|
|
|
|
let type_name = &field.ty;
|
2023-01-10 08:53:04 +01:00
|
|
|
if let Some(a) = parse_tag_val(field) {
|
2022-12-27 09:32:52 +05:30
|
|
|
// TODO: The current limitation with this is that a hard-coded integer
|
|
|
|
// value has to be mentioned in the tagval attribute. This is because
|
|
|
|
// our tags vector is for integers, and pushing an 'identifier' on it
|
|
|
|
// wouldn't work.
|
|
|
|
tags.push(a);
|
|
|
|
} else {
|
|
|
|
tags.push(tag_start);
|
|
|
|
tag_start += 1;
|
|
|
|
}
|
|
|
|
idents.push(&field.ident);
|
|
|
|
|
|
|
|
if let Type::Path(path) = type_name {
|
|
|
|
types.push(&path.path.segments[0].ident);
|
|
|
|
} else {
|
|
|
|
panic!("Don't know what to do {:?}", type_name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Currently we don't use find_tag() because the tags come in sequential
|
|
|
|
// order. If ever the tags start coming out of order, we can use find_tag()
|
|
|
|
// instead
|
|
|
|
let expanded = if !tlvargs.unordered {
|
|
|
|
quote! {
|
|
|
|
impl #generics FromTLV <#lifetime> for #struct_name #generics {
|
|
|
|
fn from_tlv(t: &TLVElement<#lifetime>) -> Result<Self, Error> {
|
|
|
|
let mut t_iter = t.#datatype ()?.enter().ok_or(Error::Invalid)?;
|
|
|
|
let mut item = t_iter.next();
|
|
|
|
#(
|
|
|
|
let #idents = if Some(true) == item.map(|x| x.check_ctx_tag(#tags)) {
|
|
|
|
let backup = item;
|
|
|
|
item = t_iter.next();
|
|
|
|
#types::from_tlv(&backup.unwrap())
|
|
|
|
} else {
|
|
|
|
#types::tlv_not_found()
|
|
|
|
}?;
|
|
|
|
)*
|
|
|
|
Ok(Self {
|
|
|
|
#(#idents,
|
|
|
|
)*
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
quote! {
|
|
|
|
impl #generics FromTLV <#lifetime> for #struct_name #generics {
|
|
|
|
fn from_tlv(t: &TLVElement<#lifetime>) -> Result<Self, Error> {
|
|
|
|
#(
|
|
|
|
let #idents = if let Ok(s) = t.find_tag(#tags as u32) {
|
|
|
|
#types::from_tlv(&s)
|
|
|
|
} else {
|
|
|
|
#types::tlv_not_found()
|
|
|
|
}?;
|
|
|
|
)*
|
|
|
|
|
|
|
|
Ok(Self {
|
|
|
|
#(#idents,
|
|
|
|
)*
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
// panic!("The generated code is {}", expanded);
|
|
|
|
expanded.into()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Generate a FromTlv implementation for an enum
|
|
|
|
fn gen_fromtlv_for_enum(
|
2023-01-10 08:53:04 +01:00
|
|
|
data_enum: &syn::DataEnum,
|
2022-12-27 09:32:52 +05:30
|
|
|
enum_name: &proc_macro2::Ident,
|
|
|
|
tlvargs: TlvArgs,
|
2023-01-10 08:53:04 +01:00
|
|
|
generics: &syn::Generics,
|
2022-12-27 09:32:52 +05:30
|
|
|
) -> TokenStream {
|
|
|
|
let mut tag_start = tlvargs.start;
|
|
|
|
let lifetime = tlvargs.lifetime;
|
|
|
|
|
|
|
|
let mut variant_names = Vec::new();
|
|
|
|
let mut types = Vec::new();
|
|
|
|
let mut tags = Vec::new();
|
|
|
|
|
|
|
|
for v in data_enum.variants.iter() {
|
|
|
|
variant_names.push(&v.ident);
|
|
|
|
if let syn::Fields::Unnamed(fields) = &v.fields {
|
|
|
|
if let Type::Path(path) = &fields.unnamed[0].ty {
|
|
|
|
types.push(&path.path.segments[0].ident);
|
|
|
|
} else {
|
|
|
|
panic!("Path not found {:?}", v.fields);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
panic!("Unnamed field not found {:?}", v.fields);
|
|
|
|
}
|
|
|
|
tags.push(tag_start);
|
|
|
|
tag_start += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
let expanded = quote! {
|
|
|
|
impl #generics FromTLV <#lifetime> for #enum_name #generics {
|
|
|
|
fn from_tlv(t: &TLVElement<#lifetime>) -> Result<Self, Error> {
|
|
|
|
let mut t_iter = t.confirm_struct()?.enter().ok_or(Error::Invalid)?;
|
|
|
|
let mut item = t_iter.next().ok_or(Error::Invalid)?;
|
|
|
|
if let TagType::Context(tag) = item.get_tag() {
|
|
|
|
match tag {
|
|
|
|
#(
|
|
|
|
#tags => Ok(Self::#variant_names(#types::from_tlv(&item)?)),
|
|
|
|
)*
|
|
|
|
_ => Err(Error::Invalid),
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Err(Error::TLVTypeMismatch)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// panic!("Expanded to {}", expanded);
|
|
|
|
expanded.into()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Derive FromTLV Macro
|
|
|
|
///
|
|
|
|
/// This macro works for structures. It will create an implementation
|
|
|
|
/// of the FromTLV trait for that structure. All the members of the
|
|
|
|
/// structure, sequentially, will get Context tags starting from 0
|
|
|
|
/// Some configurations are possible through the 'tlvargs' attributes.
|
|
|
|
/// For example:
|
|
|
|
/// #[tlvargs(lifetime = "'a", start = 1, datatype = "list", unordered)]
|
|
|
|
///
|
|
|
|
/// start: This can be used to override the default tag from which the
|
|
|
|
/// decoding starts (Default: 0)
|
|
|
|
/// datatype: This can be used to define whether this data structure is
|
|
|
|
/// to be decoded as a structure or list. Possible values: list
|
|
|
|
/// (Default: struct)
|
|
|
|
/// lifetime: If the structure has a lifetime annotation, use this variable
|
|
|
|
/// to indicate that. The 'impl' will then use that lifetime
|
|
|
|
/// indicator.
|
|
|
|
/// unordered: By default, the decoder expects that the tags are in
|
|
|
|
/// sequentially increasing order. Set this if that is not the case.
|
|
|
|
///
|
|
|
|
/// Additionally, structure members can use the tagval attribute to
|
|
|
|
/// define a specific tag to be used
|
|
|
|
/// For example:
|
|
|
|
/// #[argval(22)]
|
|
|
|
/// name: u8,
|
|
|
|
/// In the above case, the 'name' attribute will be encoded/decoded with
|
|
|
|
/// the tag 22
|
|
|
|
|
|
|
|
#[proc_macro_derive(FromTLV, attributes(tlvargs, tagval))]
|
|
|
|
pub fn derive_fromtlv(item: TokenStream) -> TokenStream {
|
|
|
|
let ast = parse_macro_input!(item as DeriveInput);
|
|
|
|
let name = &ast.ident;
|
|
|
|
|
|
|
|
let tlvargs = parse_tlvargs(&ast);
|
|
|
|
|
|
|
|
let generics = ast.generics;
|
|
|
|
|
|
|
|
if let syn::Data::Struct(syn::DataStruct {
|
|
|
|
fields: syn::Fields::Named(ref fields),
|
|
|
|
..
|
|
|
|
}) = ast.data
|
|
|
|
{
|
2023-01-10 08:53:04 +01:00
|
|
|
gen_fromtlv_for_struct(fields, name, tlvargs, &generics)
|
2022-12-27 09:32:52 +05:30
|
|
|
} else if let syn::Data::Enum(data_enum) = ast.data {
|
2023-01-10 08:53:04 +01:00
|
|
|
gen_fromtlv_for_enum(&data_enum, name, tlvargs, &generics)
|
2022-12-27 09:32:52 +05:30
|
|
|
} else {
|
|
|
|
panic!(
|
|
|
|
"Derive FromTLV - Only supported Struct for now {:?}",
|
|
|
|
ast.data
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|