vpp_plugin_api_gen/
lib.rs

1//! Generate Rust code for VPP API files
2//!
3//! A small Rust-based code generator that produces Rust bindings and helper scaffolding for VPP
4//! `.api` files for implementing VPP plugins. This crate can be run from a build script (see
5//! [`Builder`]) and emits both Rust source and a JSON representation of the parsed API.
6//!
7//! # Design rationale (why a Rust crate, not an extension to `vppapigen.py`)
8//!
9//! - Insulation against upstream changes: integrating with vppapigen.py via code outside of the vpp
10//!   repository like this would need to make the assumption that the vppapigen.py internal API
11//!   doesn't change in non-backwards-compatible ways which would be unreasonable to
12//!   assume. Implementing the parser standalone avoids that issue, although it does now mean that
13//!   any extensions to the API grammer used by plugins or their imports would need to be
14//!   implemented here.
15//! - Dependency control: shipping the generator as a crate avoids introducing an extra Python
16//!   dependencies into build systems that cannot be expressed via crate dependencies.
17//! - Robustness against environment: performing a `cargo build` when set into an unrelated Python
18//!   venv could lead to spurious failures if the venv doesn't have all of the Python dependencies
19//!   needed by vppapigen.py. Implementing in Rust avoids that.
20//!
21//! # Parsing strategy: why PEG (Parsing Expression Grammar) over CFG
22//!
23//! The implementation chooses a PEG-style parser (implemented in `parser.rs`) rather than a
24//! traditional context-free grammar (CFG) parser generator for several practical reasons:
25//!
26//! - Determinism and simplicity: PEGs are deterministic and describe a single unambiguous parse for
27//!   any input (given the grammar and choice ordering).
28//!   For the `.api` format — which is relatively small, regular and unambiguous — a PEG results in
29//!   simpler grammars and parsing code without needing extra disambiguation rules.
30//! - Ergonomics in Rust: mature PEG libraries and small hand-written PEG parsers are
31//!   straightforward to implement and embed in a Rust crate. CFG tools (LR, LALR) are typically
32//!   geared toward generating parser tables and a runtime which is less ergonomic to integrate into
33//!   a small generator crate and tends to complicate error reporting and tooling.
34//! - Better error locality: PEGs (and hand-written recursive-descent parsers) make it easier to
35//!   attach localized error messages and recover cleanly for diagnostics or partial parsing.
36//!
37//! Trade-offs and caveats:
38//! - PEG grammars do not support left-recursive rules naturally. For the `.api` grammar this is not
39//!   a practical limitation because the syntax is not left-recursive and is well-suited to a PEG
40//!   style.
41//! - CFG-based parser generators can handle certain ambiguous grammars more naturally and can
42//!   produce more compact parser tables for very large and complex grammars. Here, the space of
43//!   constructs is small and well-bounded, so the simplicity and determinism of PEG were preferred.
44
45#![warn(
46    missing_docs,
47    missing_copy_implementations,
48    missing_debug_implementations
49)]
50
51use std::{
52    env,
53    fs::{DirBuilder, File},
54    io::Write,
55    path::Path,
56};
57
58use thiserror::Error;
59
60use crate::{
61    json::generate_json,
62    parser::{ApiParser, Field, FieldSize, Message, Type, Union, VL_API_PREFIX, VL_API_SUFFIX},
63};
64
65mod json;
66mod parser;
67
68/// Errors that can occur during API file parsing and code generation
69#[derive(Debug, Error)]
70#[non_exhaustive]
71pub enum Error {
72    /// Parser error
73    #[error("Parser error")]
74    Parser(#[from] parser::Error),
75    /// Input/output error
76    #[error("I/O error")]
77    Io(#[from] std::io::Error),
78    /// Error whilst generating JSON
79    #[error("Failed to generate JSON")]
80    Json(#[from] serde_json::Error),
81    /// Attempt to use functionality that isn't yet implemented
82    #[error("{0}")]
83    Unimplemented(String),
84}
85
86fn to_upper_camel_case(s: &str) -> String {
87    s.split('_')
88        .flat_map(|word| {
89            let word = word.to_ascii_lowercase();
90            let mut chars = word.chars();
91            let capital = chars.next().map(|x| x.to_ascii_uppercase());
92            capital.into_iter().chain(chars).collect::<Vec<_>>()
93        })
94        .collect()
95}
96
97fn to_rust_type(r#type: &str) -> Result<String, Error> {
98    Ok(if let Some(t) = r#type.strip_prefix(VL_API_PREFIX) {
99        if let Some(t) = t.strip_suffix(VL_API_SUFFIX) {
100            to_upper_camel_case(t)
101        } else {
102            r#type.to_string()
103        }
104    } else if r#type == "string" {
105        return Err(Error::Unimplemented(
106            "string type not implemented".to_string(),
107        ));
108    } else {
109        r#type.to_string()
110    })
111}
112
113/// Generate Rust code for the VPP handling of APIs of from a `.api` file
114///
115/// # Examples
116///
117/// Example of use from a build script:
118///
119/// ```no_run
120/// use std::{env, path::PathBuf};
121///
122/// let output_dir = PathBuf::from(env::var("OUT_DIR").unwrap()).join("src");
123/// vpp_plugin_api_gen::Builder::new("example.api", &output_dir.to_string_lossy())
124///     .expect("unable to generate API binding")
125///     .generate()
126///     .expect("unable to generate API binding");
127/// ```
128///
129/// This can then be include in the plugin as follows:
130///
131/// ```ignore
132/// mod example_api {
133///     include!(concat!(env!("OUT_DIR"), "/src/example_api.rs"));
134/// }
135/// ```
136#[derive(Debug)]
137pub struct Builder {
138    parser: ApiParser,
139
140    module: String,
141    output_file: File,
142    output_json_file: File,
143}
144
145impl Builder {
146    /// Construct a new `Builder` for the given input file and outputting to the given directory
147    ///
148    /// Both a `<api-module>_api.rs` and a `<api-module>.api.json` file will be generated in
149    /// the output directory.
150    pub fn new(input_file: &str, output_dir: &str) -> Result<Self, Error> {
151        let in_build_script = env::var("OUT_DIR").is_ok() && env::var("CARGO_MANIFEST_DIR").is_ok();
152        if in_build_script {
153            println!("cargo:cargo-rerun-if-changed={}", input_file);
154        }
155
156        // Get file name without path
157        let input_file_name = Path::new(input_file).iter().next_back().unwrap();
158        let module = Path::new(input_file_name)
159            .file_stem()
160            .unwrap()
161            .to_string_lossy()
162            .to_string();
163
164        DirBuilder::new().recursive(true).create(output_dir)?;
165        let output_file = Path::new(output_dir).join(format!("{}_api.rs", module));
166        let output_json_file = Path::new(output_dir).join(format!("{}.api.json", module));
167
168        let parser = ApiParser::new(input_file)?;
169
170        if in_build_script {
171            for import in parser.imports() {
172                println!("cargo:cargo-rerun-if-changed={}", import);
173            }
174        }
175
176        Ok(Self {
177            parser,
178            module,
179            output_file: File::create(&output_file)?,
180            output_json_file: File::create(&output_json_file)?,
181        })
182    }
183
184    /// Generate Rust code for the API file
185    ///
186    /// Both a `<api-module>_api.rs` and a `<api-module>.api.json` file will be generated in
187    /// the output directory.
188    pub fn generate(self) -> Result<(), Error> {
189        ApiGenContext {
190            parser: &self.parser,
191            module: self.module,
192            output_file: self.output_file,
193            output_json_file: self.output_json_file,
194        }
195        .generate()
196    }
197}
198
199/// Helper structure for API code generation
200///
201/// Mark parser as non-mutable to avoid borrow-check errors when passing references obtained from
202/// parser into `&mut self` methods.
203struct ApiGenContext<'a> {
204    parser: &'a ApiParser,
205
206    module: String,
207    output_file: File,
208    output_json_file: File,
209}
210
211impl ApiGenContext<'_> {
212    fn generate_message(&mut self, id: usize, message: &Message) -> Result<(), Error> {
213        let upper_camel_name = to_upper_camel_case(message.name());
214
215        if let Some(comment) = message.comment() {
216            writeln!(
217                self.output_file,
218                "#[doc = \"{}\"]",
219                comment.replace("\"", "\\\"")
220            )?;
221        }
222        let opt_derives = if message.manual_print() {
223            ""
224        } else {
225            "Debug, PartialEq, "
226        };
227        writeln!(self.output_file, "#[derive({}Copy, Clone)]", opt_derives)?;
228        writeln!(self.output_file, "#[repr(C, packed)]")?;
229        writeln!(self.output_file, "pub struct {} {{", upper_camel_name)?;
230        for field in message.fields() {
231            // TODO: array types
232            writeln!(
233                self.output_file,
234                "    pub {}: {},",
235                field.name,
236                to_rust_type(&field.r#type)?
237            )?;
238        }
239        writeln!(self.output_file, "}}")?;
240        writeln!(self.output_file)?;
241
242        writeln!(self.output_file, "impl {} {{", upper_camel_name)?;
243        writeln!(self.output_file, "    pub const MSG_ID: u16 = {};", id)?;
244        writeln!(self.output_file)?;
245        writeln!(self.output_file, "    pub fn msg_id() -> u16 {{")?;
246        writeln!(self.output_file, "        msg_id_base() + Self::MSG_ID")?;
247        writeln!(self.output_file, "    }}")?;
248        writeln!(self.output_file, "}}")?;
249        writeln!(self.output_file)?;
250
251        writeln!(self.output_file, "impl Default for {} {{", upper_camel_name)?;
252        writeln!(self.output_file, "    fn default() -> Self {{")?;
253        writeln!(self.output_file, "        Self {{")?;
254        for field in message.fields() {
255            if field.name == "_vl_msg_id" {
256                writeln!(self.output_file, "            _vl_msg_id: Self::msg_id(),")?;
257            } else {
258                writeln!(
259                    self.output_file,
260                    "            {}: Default::default(),",
261                    field.name,
262                )?;
263            }
264        }
265        writeln!(self.output_file, "        }}")?;
266        writeln!(self.output_file, "    }}")?;
267        writeln!(self.output_file, "}}")?;
268        writeln!(self.output_file)?;
269
270        self.generate_endian_swap(message.name(), message.fields())?;
271
272        writeln!(
273            self.output_file,
274            "unsafe extern \"C\" fn {}_endian(a: *mut {}, to_net: bool) {{",
275            message.name(),
276            upper_camel_name
277        )?;
278        writeln!(
279            self.output_file,
280            "    ::vpp_plugin::vlibapi::EndianSwap::endian_swap(&mut *a, to_net);"
281        )?;
282        writeln!(self.output_file, "}}")?;
283        writeln!(self.output_file)?;
284
285        writeln!(
286            self.output_file,
287            "unsafe extern \"C\" fn {}_format(s: *mut u8, args: *mut ::vpp_plugin::bindings::va_list) -> *mut u8 {{",
288            message.name()
289        )?;
290        writeln!(
291            self.output_file,
292            "    let mut args = ::std::mem::transmute::<*mut ::vpp_plugin::bindings::va_list, ::vpp_plugin::macro_support::va_list::VaList<'_>>(args);"
293        )?;
294        writeln!(
295            self.output_file,
296            "    let t = args.get::<*const {}>();",
297            upper_camel_name
298        )?;
299        writeln!(
300            self.output_file,
301            "    let mut s = ::vpp_plugin::vppinfra::vec::Vec::from_raw(s);"
302        )?;
303        writeln!(
304            self.output_file,
305            "    s.extend(format!(\"{{:?}}\", &*t).as_bytes());"
306        )?;
307        writeln!(self.output_file, "    s.into_raw()")?;
308        writeln!(self.output_file, "}}")?;
309        writeln!(self.output_file)?;
310        writeln!(
311            self.output_file,
312            "unsafe extern \"C\" fn {}_calc_size(_a: *mut {}) -> ::vpp_plugin::bindings::uword {{",
313            message.name(),
314            upper_camel_name
315        )?;
316        // TODO: variable array types
317        writeln!(
318            self.output_file,
319            "    std::mem::size_of::<{}>() as ::vpp_plugin::bindings::uword",
320            upper_camel_name
321        )?;
322        writeln!(self.output_file, "}}")?;
323        writeln!(self.output_file)?;
324        Ok(())
325    }
326
327    fn generate_messages(&mut self) -> Result<(), Error> {
328        for (id, message) in self.parser.messages().iter().enumerate() {
329            self.generate_message(id, message)?;
330        }
331        Ok(())
332    }
333
334    fn generate_alias(&mut self, alias: &Field) -> Result<(), Error> {
335        let upper_camel_name = to_upper_camel_case(&alias.name);
336        // TODO: generate newtypes for manual_print
337        if let Some(FieldSize::Fixed(length)) = alias.size {
338            writeln!(
339                self.output_file,
340                "pub type {} = [{}; {}];",
341                upper_camel_name,
342                to_rust_type(&alias.r#type)?,
343                length
344            )?;
345        } else {
346            writeln!(
347                self.output_file,
348                "pub type {} = {};",
349                upper_camel_name,
350                to_rust_type(&alias.r#type)?
351            )?;
352        }
353
354        Ok(())
355    }
356
357    fn generate_aliases(&mut self) -> Result<(), Error> {
358        for message in self.parser.aliases() {
359            self.generate_alias(message)?;
360        }
361        Ok(())
362    }
363
364    fn generate_enums(&mut self) -> Result<(), Error> {
365        if let Some(e) = self.parser.enums().first() {
366            return Err(Error::Unimplemented(format!(
367                "Generating code for enums is not yet implemented (enum type {})",
368                e.name()
369            )));
370        }
371        if let Some(e) = self.parser.enumflags().first() {
372            return Err(Error::Unimplemented(format!(
373                "Generating code for enumflags is not yet implemented (enumflag type {})",
374                e.name()
375            )));
376        }
377        Ok(())
378    }
379
380    fn generate_union(&mut self, un: &Union) -> Result<(), Error> {
381        let upper_camel_name = to_upper_camel_case(un.name());
382
383        if let Some(comment) = un.comment() {
384            writeln!(
385                self.output_file,
386                "#[doc = \"{}\"]",
387                comment.replace("\"", "\\\"")
388            )?;
389        }
390        writeln!(self.output_file, "#[derive(Copy, Clone)]",)?;
391        writeln!(self.output_file, "#[repr(C, packed)]")?;
392        writeln!(self.output_file, "pub union {} {{", upper_camel_name)?;
393        for field in un.fields() {
394            // TODO: array types
395            writeln!(
396                self.output_file,
397                "    pub {}: {},",
398                field.name,
399                to_rust_type(&field.r#type)?
400            )?;
401        }
402        writeln!(self.output_file, "}}")?;
403        writeln!(self.output_file)?;
404        writeln!(
405            self.output_file,
406            "unsafe extern \"C\" fn {}_endian(a: *mut {}, _to_net: bool) {{",
407            un.name(),
408            upper_camel_name
409        )?;
410        for field in un.fields() {
411            // TODO: array types
412            match field.r#type.as_str() {
413                "u8" | "string" | "bool" => {
414                    writeln!(
415                        self.output_file,
416                        "    // (*a).{} = (*a).{} (no-op)",
417                        field.name, field.name
418                    )?;
419                }
420                "u16" | "u32" | "u64" | "i16" | "i32" | "i64" => {
421                    writeln!(
422                        self.output_file,
423                        "    (*a).{} = (*a).{}.to_be();",
424                        field.name, field.name
425                    )?;
426                }
427                "f64" => {
428                    writeln!(
429                        self.output_file,
430                        "    (*a).{} = f64::from_be_bytes((*a).{}.to_be_bytes());",
431                        field.name, field.name
432                    )?;
433                }
434                _ => {
435                    writeln!(
436                        self.output_file,
437                        "    {}_endian(::std::ptr::addr_of_mut!((*a).{}), to_net);",
438                        field.r#type, field.name
439                    )?;
440                }
441            }
442        }
443        writeln!(self.output_file, "}}")?;
444        writeln!(self.output_file)?;
445        writeln!(
446            self.output_file,
447            "unsafe extern \"C\" fn {}_calc_size(_a: *mut {}) -> ::vpp_plugin::bindings::uword {{",
448            un.name(),
449            upper_camel_name
450        )?;
451        // TODO: variable array types
452        writeln!(
453            self.output_file,
454            "    std::mem::size_of::<{}>() as ::vpp_plugin::bindings::uword",
455            upper_camel_name
456        )?;
457        writeln!(self.output_file, "}}")?;
458        writeln!(self.output_file)?;
459        Ok(())
460    }
461
462    fn generate_unions(&mut self) -> Result<(), Error> {
463        for un in self.parser.unions() {
464            self.generate_union(un)?;
465        }
466        Ok(())
467    }
468
469    fn generate_endian_swap(&mut self, name: &str, fields: &[Field]) -> Result<(), Error> {
470        writeln!(
471            self.output_file,
472            "impl ::vpp_plugin::vlibapi::EndianSwap for {} {{",
473            to_upper_camel_case(name)
474        )?;
475        writeln!(
476            self.output_file,
477            "    fn endian_swap(&mut self, to_net: bool) {{",
478        )?;
479        // Suppress potential used variable warning
480        writeln!(self.output_file, "        let _ = to_net;",)?;
481        for field in fields {
482            // TODO: array types
483            match field.r#type.as_str() {
484                "u8" | "string" | "bool" => {
485                    writeln!(
486                        self.output_file,
487                        "        // self.{} = self.{} (no-op)",
488                        field.name, field.name
489                    )?;
490                }
491                "u16" | "u32" | "u64" | "i16" | "i32" | "i64" => {
492                    writeln!(
493                        self.output_file,
494                        "        self.{} = self.{}.to_be();",
495                        field.name, field.name
496                    )?;
497                }
498                "f64" => {
499                    writeln!(
500                        self.output_file,
501                        "        self.{} = f64::from_be_bytes(self.{}.to_be_bytes());",
502                        field.name, field.name
503                    )?;
504                }
505                _ => {
506                    writeln!(
507                        self.output_file,
508                        "        ::vpp_plugin::vlibapi::EndianSwap::endian_swap(&mut self.{}, to_net);",
509                        field.name
510                    )?;
511                }
512            }
513        }
514        writeln!(self.output_file, "    }}",)?;
515        writeln!(self.output_file, "}}")?;
516        writeln!(self.output_file)?;
517
518        Ok(())
519    }
520
521    fn generate_type(&mut self, t: &Type) -> Result<(), Error> {
522        let upper_camel_name = to_upper_camel_case(t.name());
523
524        let opt_derives = if t.manual_print() {
525            ""
526        } else {
527            "Debug, PartialEq, Default, "
528        };
529        writeln!(self.output_file, "#[derive({}Copy, Clone)]", opt_derives)?;
530        writeln!(self.output_file, "#[repr(C, packed)]")?;
531        writeln!(self.output_file, "pub struct {} {{", upper_camel_name)?;
532        for field in t.fields() {
533            // TODO: array types
534            writeln!(
535                self.output_file,
536                "    pub {}: {},",
537                field.name,
538                to_rust_type(&field.r#type)?
539            )?;
540        }
541        writeln!(self.output_file, "}}")?;
542        writeln!(self.output_file)?;
543        if !t.manual_endian() {
544            self.generate_endian_swap(t.name(), t.fields())?;
545        }
546        writeln!(
547            self.output_file,
548            "unsafe extern \"C\" fn vl_api_{}_t_calc_size(_a: *mut {}) -> ::vpp_plugin::bindings::uword {{",
549            t.name(),
550            upper_camel_name
551        )?;
552        // TODO: variable array types
553        writeln!(
554            self.output_file,
555            "    std::mem::size_of::<{}>() as ::vpp_plugin::bindings::uword",
556            upper_camel_name
557        )?;
558        writeln!(self.output_file, "}}")?;
559        writeln!(self.output_file)?;
560        Ok(())
561    }
562
563    fn generate_types(&mut self) -> Result<(), Error> {
564        for t in self.parser.types() {
565            self.generate_type(t)?;
566        }
567        Ok(())
568    }
569
570    fn generate_register(&mut self) -> Result<(), Error> {
571        writeln!(self.output_file, "pub trait Handlers {{")?;
572        for service in self.parser.services() {
573            let caller_upper_camel = to_upper_camel_case(service.caller());
574            if service.reply() == "null" {
575                writeln!(
576                    self.output_file,
577                    "    fn {}(vm: &::vpp_plugin::vlib::BarrierHeldMainRef, mp: &{});",
578                    service.caller(),
579                    caller_upper_camel
580                )?;
581            } else {
582                // TODO: support variable array type for reply
583                let reply_message = format!(
584                    "::vpp_plugin::vlibapi::Message<{}>",
585                    to_upper_camel_case(service.reply())
586                );
587                let retval_in_reply_msg = self
588                    .parser
589                    .message(service.reply())
590                    .map(|reply| reply.has_retval())
591                    .unwrap_or_default();
592                if retval_in_reply_msg {
593                    writeln!(
594                        self.output_file,
595                        "    fn {}(vm: &::vpp_plugin::vlib::BarrierHeldMainRef, mp: &{}) -> Result<{}, i32>;",
596                        service.caller(),
597                        caller_upper_camel,
598                        reply_message
599                    )?;
600                } else {
601                    writeln!(
602                        self.output_file,
603                        "    fn {}(vm: &::vpp_plugin::vlib::BarrierHeldMainRef, mp: &{}) -> {};",
604                        service.caller(),
605                        caller_upper_camel,
606                        reply_message
607                    )?;
608                }
609            }
610        }
611        writeln!(self.output_file, "}}")?;
612        writeln!(self.output_file)?;
613
614        for service in self.parser.services() {
615            let caller_upper_camel = to_upper_camel_case(service.caller());
616            writeln!(
617                self.output_file,
618                "unsafe extern \"C\" fn {}_handler_raw<H: Handlers>(mp: *mut {}) {{",
619                service.caller(),
620                caller_upper_camel
621            )?;
622            writeln!(
623                self.output_file,
624                "    let vm = ::vpp_plugin::vlib::BarrierHeldMainRef::from_ptr_mut("
625            )?;
626            writeln!(
627                self.output_file,
628                "        ::vpp_plugin::bindings::vlib_get_main_not_inline(),"
629            )?;
630            writeln!(self.output_file, "    );")?;
631            writeln!(self.output_file, "    let mp = &*mp;")?;
632            if service.reply() == "null" {
633                writeln!(self.output_file, "    H::{}(vm, mp);", service.caller())?;
634            } else {
635                let retval_in_reply_msg = self
636                    .parser
637                    .message(service.reply())
638                    .map(|reply| reply.has_retval())
639                    .unwrap_or_default();
640                if retval_in_reply_msg {
641                    writeln!(
642                        self.output_file,
643                        "    let mut reply = match H::{}(vm, mp) {{",
644                        service.caller()
645                    )?;
646                    writeln!(self.output_file, "        Ok(reply) => reply,")?;
647                    writeln!(
648                        self.output_file,
649                        "        Err(retval) => {} {{",
650                        to_upper_camel_case(service.reply())
651                    )?;
652                    writeln!(self.output_file, "            retval,")?;
653                    writeln!(self.output_file, "            ..Default::default()")?;
654                    writeln!(self.output_file, "        }}")?;
655                    writeln!(self.output_file, "        .into(),")?;
656                    writeln!(self.output_file, "    }};")?;
657                } else {
658                    writeln!(
659                        self.output_file,
660                        "    let mut reply = H::{}(vm, mp);",
661                        service.caller()
662                    )?;
663                }
664                writeln!(
665                    self.output_file,
666                    "    ::vpp_plugin::vlibapi::registration_scope(|s| {{"
667                )?;
668                // TODO: check for client_index field in caller
669                // TODO: check for context field in caller and reply
670                writeln!(
671                    self.output_file,
672                    "        if let Some(reg) = s.from_client_index(vm, mp.client_index) {{"
673                )?;
674                writeln!(self.output_file, "            reply.context = mp.context;",)?;
675                writeln!(
676                    self.output_file,
677                    "            {}_endian(::std::ptr::addr_of_mut!(*reply), true);",
678                    service.reply()
679                )?;
680                writeln!(self.output_file, "            reg.send_message(reply);")?;
681                writeln!(self.output_file, "        }}")?;
682                writeln!(self.output_file, "    }})")?;
683            }
684            writeln!(self.output_file, "}}")?;
685            writeln!(self.output_file)?;
686        }
687
688        writeln!(
689            self.output_file,
690            "pub const MESSAGE_COUNT: u16 = {};",
691            self.parser.messages().len()
692        )?;
693        writeln!(self.output_file)?;
694
695        writeln!(
696            self.output_file,
697            "static MSG_ID_BASE: ::std::sync::atomic::AtomicU16 = ::std::sync::atomic::AtomicU16::new(0);"
698        )?;
699        writeln!(self.output_file)?;
700        writeln!(self.output_file, "pub fn msg_id_base() -> u16 {{")?;
701        writeln!(
702            self.output_file,
703            "    MSG_ID_BASE.load(::std::sync::atomic::Ordering::Relaxed)"
704        )?;
705        writeln!(self.output_file, "}}")?;
706        writeln!(self.output_file)?;
707
708        writeln!(
709            self.output_file,
710            "pub fn {}_register_messages<H: Handlers>() {{",
711            self.module
712        )?;
713        writeln!(self.output_file, "    unsafe {{")?;
714        writeln!(
715            self.output_file,
716            "        let am = ::vpp_plugin::bindings::vlibapi_helper_get_main();"
717        )?;
718        writeln!(
719            self.output_file,
720            "        let mut json_api_repr = ::vpp_plugin::vppinfra::vec::Vec::from_raw((*am).json_api_repr);"
721        )?;
722        writeln!(self.output_file, "        json_api_repr.push(",)?;
723        writeln!(
724            self.output_file,
725            "            concat!(include_str!(\"{}.api.json\"), \"\\0\")",
726            self.module
727        )?;
728        writeln!(self.output_file, "                .as_ptr()",)?;
729        writeln!(self.output_file, "                .cast_mut(),",)?;
730        writeln!(self.output_file, "        );",)?;
731        writeln!(
732            self.output_file,
733            "        (*am).json_api_repr = json_api_repr.into_raw();"
734        )?;
735        writeln!(self.output_file)?;
736        writeln!(
737            self.output_file,
738            "        let msg_id_base = ::vpp_plugin::bindings::vl_msg_api_get_msg_ids(",
739        )?;
740        writeln!(
741            self.output_file,
742            "            c\"{}_{:08x}\".as_ptr() as *mut ::std::os::raw::c_char,",
743            self.module,
744            self.parser.file_crc()
745        )?;
746        writeln!(self.output_file, "            MESSAGE_COUNT as i32,",)?;
747        writeln!(self.output_file, "        );",)?;
748        writeln!(self.output_file)?;
749        for message in self.parser.messages() {
750            writeln!(
751                self.output_file,
752                "        ::vpp_plugin::bindings::vl_msg_api_add_msg_name_crc("
753            )?;
754            writeln!(self.output_file, "            am,")?;
755            writeln!(
756                self.output_file,
757                "            c\"{}_{:08x}\".as_ptr() as *mut ::std::os::raw::c_char,",
758                message.name(),
759                message.crc()
760            )?;
761            writeln!(
762                self.output_file,
763                "            {}::MSG_ID as u32 + msg_id_base as u32,",
764                to_upper_camel_case(message.name()),
765            )?;
766            writeln!(self.output_file, "        );")?;
767            writeln!(self.output_file)?;
768        }
769        for service in self.parser.services() {
770            let caller = self.parser.message(service.caller()).unwrap();
771            let caller_upper_camel = to_upper_camel_case(service.caller());
772            writeln!(
773                self.output_file,
774                "        let mut c = vpp_plugin::bindings::vl_msg_api_msg_config_t {{"
775            )?;
776            writeln!(
777                self.output_file,
778                "            id: {}::MSG_ID as i32 + msg_id_base as i32,",
779                caller_upper_camel,
780            )?;
781            writeln!(
782                self.output_file,
783                "            name: c\"{}\".as_ptr() as *mut ::std::os::raw::c_char,",
784                service.caller()
785            )?;
786            writeln!(
787                self.output_file,
788                "            handler: {}_handler_raw::<H> as *mut ::std::os::raw::c_void,",
789                service.caller()
790            )?;
791            writeln!(
792                self.output_file,
793                "            endian: {}_endian as *mut ::std::os::raw::c_void,",
794                service.caller()
795            )?;
796            writeln!(
797                self.output_file,
798                "            format_fn: {}_format as *mut ::std::os::raw::c_void,",
799                service.caller()
800            )?;
801            writeln!(
802                self.output_file,
803                "            tojson: std::ptr::null_mut(),"
804            )?;
805            writeln!(
806                self.output_file,
807                "            fromjson: std::ptr::null_mut(),"
808            )?;
809            writeln!(
810                self.output_file,
811                "            calc_size: {}_calc_size as *mut ::std::os::raw::c_void,",
812                service.caller()
813            )?;
814            writeln!(self.output_file, "            ..Default::default()")?;
815            writeln!(self.output_file, "        }};")?;
816            writeln!(self.output_file, "        c.set_traced(1);")?;
817            writeln!(self.output_file, "        c.set_replay(1);")?;
818            // TODO: enforce always auto-endian?
819            if caller.auto_endian() {
820                writeln!(self.output_file, "        c.set_is_autoendian(1);")?;
821            }
822            writeln!(
823                self.output_file,
824                "        ::vpp_plugin::bindings::vl_msg_api_config(std::ptr::addr_of_mut!(c));"
825            )?;
826            writeln!(self.output_file)?;
827
828            if service.reply() != "null" {
829                let reply = self.parser.message(service.reply()).unwrap();
830                let reply_upper_camel = to_upper_camel_case(service.reply());
831                writeln!(
832                    self.output_file,
833                    "        let mut c = vpp_plugin::bindings::vl_msg_api_msg_config_t {{"
834                )?;
835                writeln!(
836                    self.output_file,
837                    "            id: {}::MSG_ID as i32 + msg_id_base as i32,",
838                    reply_upper_camel
839                )?;
840                writeln!(
841                    self.output_file,
842                    "            name: c\"{}\".as_ptr() as *mut ::std::os::raw::c_char,",
843                    service.reply()
844                )?;
845                writeln!(
846                    self.output_file,
847                    "            handler: ::std::ptr::null_mut(),"
848                )?;
849                writeln!(
850                    self.output_file,
851                    "            endian: {}_endian as *mut ::std::os::raw::c_void,",
852                    service.reply()
853                )?;
854                writeln!(
855                    self.output_file,
856                    "            format_fn: {}_format as *mut ::std::os::raw::c_void,",
857                    service.reply()
858                )?;
859                writeln!(
860                    self.output_file,
861                    "            tojson: std::ptr::null_mut(),"
862                )?;
863                writeln!(
864                    self.output_file,
865                    "            fromjson: std::ptr::null_mut(),"
866                )?;
867                writeln!(
868                    self.output_file,
869                    "            calc_size: {}_calc_size as *mut ::std::os::raw::c_void,",
870                    service.reply()
871                )?;
872                writeln!(self.output_file, "            ..Default::default()")?;
873                writeln!(self.output_file, "        }};")?;
874                writeln!(self.output_file, "        c.set_traced(1);")?;
875                writeln!(self.output_file, "        c.set_replay(1);")?;
876                // TODO: enforce always auto-endian?
877                if reply.auto_endian() {
878                    writeln!(self.output_file, "        c.set_is_autoendian(1);")?;
879                }
880                writeln!(
881                    self.output_file,
882                    "        ::vpp_plugin::bindings::vl_msg_api_config(std::ptr::addr_of_mut!(c));"
883                )?;
884                writeln!(self.output_file)?;
885            }
886
887            if let Some(stream_message_name) = service.stream_message() {
888                let stream_message = self.parser.message(stream_message_name).unwrap();
889                let stream_message_upper_camel = to_upper_camel_case(stream_message_name);
890                writeln!(
891                    self.output_file,
892                    "        let mut c = vpp_plugin::bindings::vl_msg_api_msg_config_t {{"
893                )?;
894                writeln!(
895                    self.output_file,
896                    "            id: {}::MSG_ID as i32 + msg_id_base as i32,",
897                    stream_message_upper_camel
898                )?;
899                writeln!(
900                    self.output_file,
901                    "            name: c\"{}\".as_ptr() as *mut ::std::os::raw::c_char,",
902                    stream_message_name
903                )?;
904                writeln!(
905                    self.output_file,
906                    "            handler: ::std::ptr::null_mut(),"
907                )?;
908                writeln!(
909                    self.output_file,
910                    "            endian: {}_endian as *mut ::std::os::raw::c_void,",
911                    stream_message_name
912                )?;
913                writeln!(
914                    self.output_file,
915                    "            format_fn: {}_format as *mut ::std::os::raw::c_void,",
916                    stream_message_name
917                )?;
918                writeln!(
919                    self.output_file,
920                    "            tojson: std::ptr::null_mut(),"
921                )?;
922                writeln!(
923                    self.output_file,
924                    "            fromjson: std::ptr::null_mut(),"
925                )?;
926                writeln!(
927                    self.output_file,
928                    "            calc_size: {}_calc_size as *mut ::std::os::raw::c_void,",
929                    stream_message_name
930                )?;
931                writeln!(self.output_file, "            ..Default::default()")?;
932                writeln!(self.output_file, "        }};")?;
933                writeln!(self.output_file, "        c.set_traced(1);")?;
934                writeln!(self.output_file, "        c.set_replay(1);")?;
935                // TODO: enforce always auto-endian?
936                if stream_message.auto_endian() {
937                    writeln!(self.output_file, "        c.set_is_autoendian(1);")?;
938                }
939                writeln!(
940                    self.output_file,
941                    "        ::vpp_plugin::bindings::vl_msg_api_config(std::ptr::addr_of_mut!(c));"
942                )?;
943                writeln!(self.output_file)?;
944            }
945        }
946
947        writeln!(
948            self.output_file,
949            "        MSG_ID_BASE.store(msg_id_base, ::std::sync::atomic::Ordering::Relaxed);"
950        )?;
951        writeln!(self.output_file, "    }}")?;
952        writeln!(self.output_file, "}}")?;
953
954        Ok(())
955    }
956
957    fn generate(mut self) -> Result<(), Error> {
958        self.output_json_file
959            .write_all(generate_json(self.parser)?.as_bytes())?;
960        self.generate_aliases()?;
961        self.generate_enums()?;
962        self.generate_unions()?;
963        self.generate_types()?;
964        self.generate_messages()?;
965        self.generate_register()?;
966        Ok(())
967    }
968}