rasn_compiler/generator/rasn/
mod.rs

1use std::{
2    env,
3    error::Error,
4    io::{self, Write},
5    path::PathBuf,
6    process::{Command, Stdio},
7    str::FromStr,
8};
9
10use crate::{error::CompilerError, intermediate::*};
11use proc_macro2::TokenStream;
12use quote::{quote, ToTokens};
13
14#[cfg(target_family = "wasm")]
15use wasm_bindgen::prelude::*;
16
17use super::{
18    error::{GeneratorError, GeneratorErrorType},
19    Backend, GeneratedModule,
20};
21
22mod builder;
23mod template;
24mod utils;
25
26#[derive(Debug, Default)]
27/// A compiler backend that generates bindings to be used with
28/// the `rasn` framework for rust.
29pub struct Rasn {
30    config: Config,
31    tagging_environment: TaggingEnvironment,
32    extensibility_environment: ExtensibilityEnvironment,
33}
34
35#[cfg_attr(target_family = "wasm", wasm_bindgen)]
36#[derive(Debug)]
37/// A configuration for the [Rasn] backend
38pub struct Config {
39    /// ASN.1 Open Types are represented as the `rasn::types::Any` type,
40    /// which holds a binary `content`. If `opaque_open_types` is `false`,
41    /// the compiler will generate additional de-/encode methods for
42    /// all rust types that hold an open type.
43    /// For example, bindings for a `SEQUENCE` with a field of Open Type
44    /// value will include a method for explicitly decoding the Open Type field.
45    /// _Non-opaque open types are still experimental. If you have trouble_
46    /// _generating correct bindings, switch back to opaque open types._
47    pub opaque_open_types: bool,
48    /// The compiler will try to match module import dependencies of the ASN.1
49    /// module as close as possible, importing only those types from other modules
50    /// that are imported in the ASN.1 module. If the `default_wildcard_imports`
51    /// is set to `true` , the compiler will import the entire module using
52    /// the wildcard `*` for each module that the input ASN.1 module imports from.
53    pub default_wildcard_imports: bool,
54    /// To make working with the generated types a bit more ergonomic, the compiler
55    /// can generate `From` impls for the wrapper inner types in a `CHOICE`, as long
56    /// as the generated impls are not ambiguous.
57    /// This is disabled by default to generate less code, but can be enabled with
58    /// `generate_from_impls` set to `true`.
59    pub generate_from_impls: bool,
60}
61
62#[cfg(target_family = "wasm")]
63#[wasm_bindgen]
64impl Config {
65    #[wasm_bindgen(constructor)]
66    pub fn new(
67        opaque_open_types: bool,
68        default_wildcard_imports: bool,
69        generate_from_impls: Option<bool>,
70    ) -> Self {
71        Self {
72            opaque_open_types,
73            default_wildcard_imports,
74            generate_from_impls: generate_from_impls.unwrap_or(false),
75        }
76    }
77}
78
79impl Default for Config {
80    fn default() -> Self {
81        Self {
82            opaque_open_types: true,
83            default_wildcard_imports: false,
84            generate_from_impls: false,
85        }
86    }
87}
88
89impl Backend for Rasn {
90    type Config = Config;
91
92    const FILE_EXTENSION: &'static str = ".rs";
93
94    fn new(
95        config: Self::Config,
96        tagging_environment: TaggingEnvironment,
97        extensibility_environment: ExtensibilityEnvironment,
98    ) -> Self {
99        Self {
100            config,
101            extensibility_environment,
102            tagging_environment,
103        }
104    }
105
106    fn from_config(config: Self::Config) -> Self {
107        Self {
108            config,
109            ..Default::default()
110        }
111    }
112
113    fn config(&self) -> &Self::Config {
114        &self.config
115    }
116
117    fn generate_module(
118        &mut self,
119        tlds: Vec<ToplevelDefinition>,
120    ) -> Result<GeneratedModule, GeneratorError> {
121        if let Some((module_ref, _)) = tlds.first().and_then(|tld| tld.get_index().cloned()) {
122            let module = module_ref.borrow();
123            self.tagging_environment = module.tagging_environment;
124            self.extensibility_environment = module.extensibility_environment;
125            let name = self.to_rust_snake_case(&module.name);
126            let imports = module.imports.iter().map(|import| {
127                let module =
128                    self.to_rust_snake_case(&import.global_module_reference.module_reference);
129                let mut usages = Some(vec![]);
130                'imports: for usage in &import.types {
131                    if usage.contains("{}") || usage.chars().all(|c| c.is_uppercase() || c == '-') {
132                        usages = None;
133                        break 'imports;
134                    } else if usage.starts_with(|c: char| c.is_lowercase()) {
135                        if let Some(us) = usages.as_mut() {
136                            us.push(self.to_rust_const_case(usage).to_token_stream())
137                        }
138                    } else if usage.starts_with(|c: char| c.is_uppercase()) {
139                        if let Some(us) = usages.as_mut() {
140                            us.push(self.to_rust_title_case(usage).to_token_stream())
141                        }
142                    }
143                }
144                let used_imports = if self.config.default_wildcard_imports {
145                    vec![TokenStream::from_str("*").unwrap()]
146                } else {
147                    usages.unwrap_or(vec![TokenStream::from_str("*").unwrap()])
148                };
149                quote!(use super:: #module::{ #(#used_imports),* };)
150            });
151            let (pdus, warnings): (Vec<TokenStream>, Vec<CompilerError>) =
152                tlds.into_iter().fold((vec![], vec![]), |mut acc, tld| {
153                    match self.generate_tld(tld) {
154                        Ok(s) => {
155                            acc.0.push(s);
156                            acc
157                        }
158                        Err(e) => {
159                            acc.1.push(e.into());
160                            acc
161                        }
162                    }
163                });
164            Ok(GeneratedModule {
165                generated: Some(quote! {
166                #[allow(non_camel_case_types, non_snake_case, non_upper_case_globals, unused,
167                        clippy::too_many_arguments,)]
168                pub mod #name {
169                    extern crate alloc;
170
171                    use core::borrow::Borrow;
172                    use rasn::prelude::*;
173                    use lazy_static::lazy_static;
174
175                    #(#imports)*
176
177                    #(#pdus)*
178                }
179            }.to_string()), warnings})
180        } else {
181            Ok(GeneratedModule::empty())
182        }
183    }
184
185    fn format_bindings(bindings: &str) -> Result<String, CompilerError> {
186        Self::internal_fmt(bindings).map_err(|e| {
187            GeneratorError {
188                top_level_declaration: None,
189                details: e.to_string(),
190                kind: GeneratorErrorType::FormattingError,
191            }
192            .into()
193        })
194    }
195
196    fn generate(&self, tld: ToplevelDefinition) -> Result<String, GeneratorError> {
197        self.generate_tld(tld).map(|ts| ts.to_string())
198    }
199}
200
201impl Rasn {
202    fn get_rustfmt_path() -> Result<PathBuf, Box<dyn Error>> {
203        // Try ~/.cargo/bin/rustfmt style paths first
204        if let Ok(path) = env::var("CARGO_HOME")
205            .map(PathBuf::from)
206            .map(|mut path| {
207                path.push("bin/rustfmt");
208                path
209            }) {
210            if path.exists() {
211                return Ok(path);
212            }
213        }
214
215        // Alternatively, maybe rustfmt and cargo are in the same directory
216        if let Ok(path) = env::var("CARGO")
217            .map(PathBuf::from)
218            .map(|mut path| {
219                path.set_file_name("rustfmt");
220                path
221            }) {
222            if path.exists() {
223                return Ok(path);
224            }
225        }
226
227        Err("No rustfmt found.".into())
228    }
229
230    fn internal_fmt(bindings: &str) -> Result<String, Box<dyn Error>> {
231        let rustfmt = Self::get_rustfmt_path()?;
232        let mut cmd = Command::new(rustfmt);
233
234        cmd.stdin(Stdio::piped()).stdout(Stdio::piped());
235
236        let mut child = cmd.spawn()?;
237        let mut child_stdin = child.stdin.take().unwrap();
238        let mut child_stdout = child.stdout.take().unwrap();
239
240        // Write to stdin in a new thread, so that we can read from stdout on this
241        // thread. This keeps the child from blocking on writing to its stdout which
242        // might block us from writing to its stdin.
243        let bindings = bindings.to_owned();
244        let stdin_handle = ::std::thread::spawn(move || {
245            let _ = child_stdin.write_all(bindings.as_bytes());
246            bindings
247        });
248
249        let mut output = vec![];
250        io::copy(&mut child_stdout, &mut output)?;
251
252        let status = child.wait()?;
253        let bindings = stdin_handle.join().expect(
254            "The thread writing to rustfmt's stdin doesn't do \
255             anything that could panic",
256        );
257
258        match String::from_utf8(output) {
259            Ok(bindings) => match status.code() {
260                Some(0) => Ok(bindings),
261                Some(2) => Err(Box::new(io::Error::new(
262                    io::ErrorKind::Other,
263                    "Rustfmt parsing errors.".to_string(),
264                ))),
265                Some(3) => Ok(bindings),
266                _ => Err(Box::new(io::Error::new(
267                    io::ErrorKind::Other,
268                    "Internal rustfmt error".to_string(),
269                ))),
270            },
271            _ => Ok(bindings),
272        }
273    }
274}