1 //! Choice variant IR and lowerings
2 
3 use crate::{FieldAttrs, Tag, TypeAttrs};
4 use proc_macro2::TokenStream;
5 use quote::quote;
6 use syn::{Fields, Ident, Path, Type, Variant};
7 
8 #[derive(Clone, Debug, PartialEq, Eq)]
9 pub(super) enum TagOrPath {
10     Tag(Tag),
11     Path(Path),
12 }
13 
14 impl PartialEq<Tag> for TagOrPath {
eq(&self, rhs: &Tag) -> bool15     fn eq(&self, rhs: &Tag) -> bool {
16         match self {
17             Self::Tag(lhs) => lhs == rhs,
18             _ => false,
19         }
20     }
21 }
22 
23 impl From<Tag> for TagOrPath {
from(tag: Tag) -> Self24     fn from(tag: Tag) -> Self {
25         Self::Tag(tag)
26     }
27 }
28 
29 impl From<Path> for TagOrPath {
from(path: Path) -> Self30     fn from(path: Path) -> Self {
31         Self::Path(path)
32     }
33 }
34 
35 impl TryFrom<&Variant> for TagOrPath {
36     type Error = syn::Error;
37 
try_from(input: &Variant) -> syn::Result<Self>38     fn try_from(input: &Variant) -> syn::Result<Self> {
39         if let Fields::Unnamed(fields) = &input.fields {
40             if fields.unnamed.len() == 1 {
41                 if let Type::Path(path) = &fields.unnamed[0].ty {
42                     return Ok(path.path.clone().into());
43                 }
44             }
45         }
46 
47         Err(syn::Error::new_spanned(
48             &input.ident,
49             "no #[asn1(type=...)] specified for enum variant",
50         ))
51     }
52 }
53 
54 impl TagOrPath {
to_tokens(&self) -> TokenStream55     pub fn to_tokens(&self) -> TokenStream {
56         match self {
57             Self::Tag(tag) => tag.to_tokens(),
58             Self::Path(path) => quote! { <#path as ::der::FixedTag>::TAG },
59         }
60     }
61 }
62 
63 /// "IR" for a variant of a derived `Choice`.
64 pub(super) struct ChoiceVariant {
65     /// Variant name.
66     pub(super) ident: Ident,
67 
68     /// "Field" (in this case variant)-level attributes.
69     pub(super) attrs: FieldAttrs,
70 
71     /// Tag for the ASN.1 type.
72     pub(super) tag: TagOrPath,
73 }
74 
75 impl ChoiceVariant {
76     /// Create a new [`ChoiceVariant`] from the input [`Variant`].
new(input: &Variant, type_attrs: &TypeAttrs) -> syn::Result<Self>77     pub(super) fn new(input: &Variant, type_attrs: &TypeAttrs) -> syn::Result<Self> {
78         let ident = input.ident.clone();
79         let attrs = FieldAttrs::parse(&input.attrs, type_attrs)?;
80 
81         if attrs.extensible {
82             abort!(&ident, "`extensible` is not allowed on CHOICE");
83         }
84 
85         // Validate that variant is a 1-element tuple struct
86         match &input.fields {
87             // TODO(tarcieri): handle 0 bindings for ASN.1 NULL
88             Fields::Unnamed(fields) if fields.unnamed.len() == 1 => (),
89             _ => abort!(&ident, "enum variant must be a 1-element tuple struct"),
90         }
91 
92         let tag = match attrs.tag()? {
93             Some(x) => x.into(),
94             None => input.try_into()?,
95         };
96 
97         Ok(Self { ident, attrs, tag })
98     }
99 
100     /// Derive a match arm of the impl body for `TryFrom<der::asn1::Any<'_>>`.
to_decode_tokens(&self) -> TokenStream101     pub(super) fn to_decode_tokens(&self) -> TokenStream {
102         let tag = self.tag.to_tokens();
103         let ident = &self.ident;
104         let decoder = self.attrs.decoder();
105 
106         match self.attrs.asn1_type {
107             Some(..) => quote! { #tag => Ok(Self::#ident(#decoder.try_into()?)), },
108             None => quote! { #tag => Ok(Self::#ident(#decoder)), },
109         }
110     }
111 
112     /// Derive a match arm for the impl body for `der::EncodeValue::encode_value`.
to_encode_value_tokens(&self) -> TokenStream113     pub(super) fn to_encode_value_tokens(&self) -> TokenStream {
114         let ident = &self.ident;
115         let binding = quote!(variant);
116         let encoder = self.attrs.value_encode(&binding);
117         quote! {
118             Self::#ident(#binding) => #encoder,
119         }
120     }
121 
122     /// Derive a match arm for the impl body for `der::EncodeValue::value_len`.
to_value_len_tokens(&self) -> TokenStream123     pub(super) fn to_value_len_tokens(&self) -> TokenStream {
124         let ident = &self.ident;
125 
126         match self.attrs.context_specific {
127             Some(tag_number) => {
128                 let tag_number = tag_number.to_tokens();
129                 let tag_mode = self.attrs.tag_mode.to_tokens();
130 
131                 quote! {
132                     Self::#ident(variant) => ::der::asn1::ContextSpecificRef {
133                         tag_number: #tag_number,
134                         tag_mode: #tag_mode,
135                         value: variant,
136                     }.value_len(),
137                 }
138             }
139 
140             _ => quote! { Self::#ident(variant) => variant.value_len(), },
141         }
142     }
143 
144     /// Derive a match arm for the impl body for `der::Tagged::tag`.
to_tagged_tokens(&self) -> TokenStream145     pub(super) fn to_tagged_tokens(&self) -> TokenStream {
146         let ident = &self.ident;
147         let tag = self.tag.to_tokens();
148         quote! {
149             Self::#ident(_) => #tag,
150         }
151     }
152 }
153 
154 #[cfg(test)]
155 mod tests {
156     use super::ChoiceVariant;
157     use crate::{choice::variant::TagOrPath, Asn1Type, FieldAttrs, Tag, TagMode, TagNumber};
158     use proc_macro2::Span;
159     use quote::quote;
160     use syn::Ident;
161 
162     #[test]
simple()163     fn simple() {
164         let ident = Ident::new("ExampleVariant", Span::call_site());
165         let attrs = FieldAttrs::default();
166         let tag = Tag::Universal(Asn1Type::Utf8String).into();
167         let variant = ChoiceVariant { ident, attrs, tag };
168 
169         assert_eq!(
170             variant.to_decode_tokens().to_string(),
171             quote! {
172                 ::der::Tag::Utf8String => Ok(Self::ExampleVariant(
173                     reader.decode()?
174                 )),
175             }
176             .to_string()
177         );
178 
179         assert_eq!(
180             variant.to_encode_value_tokens().to_string(),
181             quote! {
182                 Self::ExampleVariant(variant) => variant.encode_value(encoder),
183             }
184             .to_string()
185         );
186 
187         assert_eq!(
188             variant.to_value_len_tokens().to_string(),
189             quote! {
190                 Self::ExampleVariant(variant) => variant.value_len(),
191             }
192             .to_string()
193         );
194 
195         assert_eq!(
196             variant.to_tagged_tokens().to_string(),
197             quote! {
198                 Self::ExampleVariant(_) => ::der::Tag::Utf8String,
199             }
200             .to_string()
201         )
202     }
203 
204     #[test]
utf8string()205     fn utf8string() {
206         let ident = Ident::new("ExampleVariant", Span::call_site());
207         let attrs = FieldAttrs {
208             asn1_type: Some(Asn1Type::Utf8String),
209             ..Default::default()
210         };
211         let tag = Tag::Universal(Asn1Type::Utf8String).into();
212         let variant = ChoiceVariant { ident, attrs, tag };
213 
214         assert_eq!(
215             variant.to_decode_tokens().to_string(),
216             quote! {
217                 ::der::Tag::Utf8String => Ok(Self::ExampleVariant(
218                     ::der::asn1::Utf8StringRef::decode(reader)?
219                     .try_into()?
220                 )),
221             }
222             .to_string()
223         );
224 
225         assert_eq!(
226             variant.to_encode_value_tokens().to_string(),
227             quote! {
228                 Self::ExampleVariant(variant) => ::der::asn1::Utf8StringRef::new(variant)?.encode_value(encoder),
229             }
230             .to_string()
231         );
232 
233         assert_eq!(
234             variant.to_value_len_tokens().to_string(),
235             quote! {
236                 Self::ExampleVariant(variant) => variant.value_len(),
237             }
238             .to_string()
239         );
240 
241         assert_eq!(
242             variant.to_tagged_tokens().to_string(),
243             quote! {
244                 Self::ExampleVariant(_) => ::der::Tag::Utf8String,
245             }
246             .to_string()
247         )
248     }
249 
250     #[test]
explicit()251     fn explicit() {
252         for tag_number in [0, 1, 2, 3] {
253             for constructed in [false, true] {
254                 let ident = Ident::new("ExplicitVariant", Span::call_site());
255                 let attrs = FieldAttrs {
256                     constructed,
257                     context_specific: Some(TagNumber(tag_number)),
258                     ..Default::default()
259                 };
260                 assert_eq!(attrs.tag_mode, TagMode::Explicit);
261 
262                 let tag = TagOrPath::Tag(Tag::ContextSpecific {
263                     constructed,
264                     number: TagNumber(tag_number),
265                 });
266 
267                 let variant = ChoiceVariant { ident, attrs, tag };
268                 let tag_number = TagNumber(tag_number).to_tokens();
269 
270                 assert_eq!(
271                     variant.to_decode_tokens().to_string(),
272                     quote! {
273                         ::der::Tag::ContextSpecific {
274                             constructed: #constructed,
275                             number: #tag_number,
276                         } => Ok(Self::ExplicitVariant(
277                             match ::der::asn1::ContextSpecific::<>::decode(reader)? {
278                                 field if field.tag_number == #tag_number => Some(field),
279                                 _ => None
280                             }
281                             .ok_or_else(|| {
282                                 der::Tag::ContextSpecific {
283                                     number: #tag_number,
284                                     constructed: #constructed
285                                 }
286                                 .value_error()
287                             })?
288                             .value
289                         )),
290                     }
291                     .to_string()
292                 );
293 
294                 assert_eq!(
295                     variant.to_encode_value_tokens().to_string(),
296                     quote! {
297                         Self::ExplicitVariant(variant) => ::der::asn1::ContextSpecificRef {
298                             tag_number: #tag_number,
299                             tag_mode: ::der::TagMode::Explicit,
300                             value: variant,
301                         }
302                         .encode_value(encoder),
303                     }
304                     .to_string()
305                 );
306 
307                 assert_eq!(
308                     variant.to_value_len_tokens().to_string(),
309                     quote! {
310                         Self::ExplicitVariant(variant) => ::der::asn1::ContextSpecificRef {
311                             tag_number: #tag_number,
312                             tag_mode: ::der::TagMode::Explicit,
313                             value: variant,
314                         }
315                         .value_len(),
316                     }
317                     .to_string()
318                 );
319 
320                 assert_eq!(
321                     variant.to_tagged_tokens().to_string(),
322                     quote! {
323                         Self::ExplicitVariant(_) => ::der::Tag::ContextSpecific {
324                             constructed: #constructed,
325                             number: #tag_number,
326                         },
327                     }
328                     .to_string()
329                 )
330             }
331         }
332     }
333 
334     #[test]
implicit()335     fn implicit() {
336         for tag_number in [0, 1, 2, 3] {
337             for constructed in [false, true] {
338                 let ident = Ident::new("ImplicitVariant", Span::call_site());
339 
340                 let attrs = FieldAttrs {
341                     constructed,
342                     context_specific: Some(TagNumber(tag_number)),
343                     tag_mode: TagMode::Implicit,
344                     ..Default::default()
345                 };
346 
347                 let tag = TagOrPath::Tag(Tag::ContextSpecific {
348                     constructed,
349                     number: TagNumber(tag_number),
350                 });
351 
352                 let variant = ChoiceVariant { ident, attrs, tag };
353                 let tag_number = TagNumber(tag_number).to_tokens();
354 
355                 assert_eq!(
356                     variant.to_decode_tokens().to_string(),
357                     quote! {
358                         ::der::Tag::ContextSpecific {
359                             constructed: #constructed,
360                             number: #tag_number,
361                         } => Ok(Self::ImplicitVariant(
362                             ::der::asn1::ContextSpecific::<>::decode_implicit(
363                                 reader,
364                                 #tag_number
365                             )?
366                             .ok_or_else(|| {
367                                 der::Tag::ContextSpecific {
368                                   number: #tag_number,
369                                   constructed: #constructed
370                                 }
371                                 .value_error()
372                             })?
373                             .value
374                         )),
375                     }
376                     .to_string()
377                 );
378 
379                 assert_eq!(
380                     variant.to_encode_value_tokens().to_string(),
381                     quote! {
382                         Self::ImplicitVariant(variant) => ::der::asn1::ContextSpecificRef {
383                             tag_number: #tag_number,
384                             tag_mode: ::der::TagMode::Implicit,
385                             value: variant,
386                         }
387                         .encode_value(encoder),
388                     }
389                     .to_string()
390                 );
391 
392                 assert_eq!(
393                     variant.to_value_len_tokens().to_string(),
394                     quote! {
395                         Self::ImplicitVariant(variant) => ::der::asn1::ContextSpecificRef {
396                             tag_number: #tag_number,
397                             tag_mode: ::der::TagMode::Implicit,
398                             value: variant,
399                         }
400                         .value_len(),
401                     }
402                     .to_string()
403                 );
404 
405                 assert_eq!(
406                     variant.to_tagged_tokens().to_string(),
407                     quote! {
408                         Self::ImplicitVariant(_) => ::der::Tag::ContextSpecific {
409                             constructed: #constructed,
410                             number: #tag_number,
411                         },
412                     }
413                     .to_string()
414                 )
415             }
416         }
417     }
418 }
419