diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000000..83fc85e850
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,13 @@
+version: 2
+updates:
+- package-ecosystem: cargo
+ directory: "/"
+ schedule:
+ interval: daily
+ ignore:
+ - dependency-name: "embedded-hal"
+ versions: ["^0.2"]
+- package-ecosystem: github-actions
+ directory: "/"
+ schedule:
+ interval: daily
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 98154469f0..89244914c3 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -10,12 +10,31 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- - uses: actions-rs/toolchain@v1.0.1
+ with:
+ fetch-depth: 0
+ ref: ${{ github.event.repository.default_branch }}
+ - uses: actions-rs/toolchain@v1.0.7
with:
toolchain: stable
override: true
- name: Build
run: cargo build
- - uses: katyo/publish-crates@v1
- with:
- registry-token: ${{ secrets.CARGO }}
+ - name: Extract version from tag
+ id: get_version
+ run: echo "::set-output name=version::${GITHUB_REF/refs\/tags\//}"
+ - name: Commit version changes
+ run: |
+ git config --global user.name 'github-actions[bot]'
+ git config --global user.email '41898282+github-actions[bot]@users.noreply.github.com'
+ - name: Set and publish workspace crates
+ env:
+ CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO }}
+ run: |
+ cargo install cargo-workspaces
+ cargo workspaces version custom ${{ steps.get_version.outputs.version }} \
+ --exact --yes --no-git-tag --no-git-push \
+ -m "Commit new release ${{ steps.get_version.outputs.version }}"
+ cargo workspaces publish --yes --no-verify --publish-as-is
+ - name: Push commit
+ run: |
+ git push origin ${{ github.event.repository.default_branch }}
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 20df235010..1eb228de28 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -3,81 +3,135 @@ name: Test all targets
on: [push, pull_request]
jobs:
- quick-tests:
+ formatting:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@master
- - uses: actions-rs/toolchain@v1.0.1
- with:
- toolchain: stable
- target: ${{ matrix.TARGET }}
- override: true
- - name: Check Type
- run: cargo fmt -- --check
- - name: Run internal tests
- run: cargo test --verbose --features all-dialects -- --nocapture
- - name: Build mavlink-dump
- run: cargo build --verbose --bin mavlink-dump --features ardupilotmega
+ - uses: actions/checkout@master
+ - uses: dtolnay/rust-toolchain@stable
+ with:
+ components: rustfmt
+ - name: Run rustfmt
+ run: cargo fmt --all -- --check
+
+ linting:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@master
+ - uses: dtolnay/rust-toolchain@master
+ with:
+ toolchain: nightly-2024-09-10
+ components: clippy
+ - uses: actions-rs-plus/clippy-check@v2
+ with:
+ args: --all --all-targets --features format-generated-code --features signing --features tokio-1
+
+ internal-tests:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ dialect: ["ardupilotmega", "asluav", "matrixpilot", "minimal", "paparazzi", "python_array_test", "slugs", "standard", "test", "ualberta", "uavionix", "icarous", "common"]
+ signing: ["", "--features signing"]
+ steps:
+ - uses: actions/checkout@master
+ - uses: dtolnay/rust-toolchain@stable
+ - name: Run internal tests
+ run: cargo test --verbose --features ${{ matrix.dialect }} ${{ matrix.signing }} -- --nocapture
+
+ mavlink-dump:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@master
+ - uses: dtolnay/rust-toolchain@stable
+ - name: Build mavlink-dump
+ run: cargo build --verbose --example mavlink-dump
+
+ msrv:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ features: ["", "--features serde,tokio-1", "--features signing"]
+ steps:
+ - uses: actions/checkout@master
+ - name: Get MSRV from Cargo.toml
+ run: |
+ MSRV=$(grep 'rust-version' Cargo.toml | sed 's/.*= *"\(.*\)".*/\1/')
+ echo "MSRV=$MSRV" >> $GITHUB_ENV
+ - uses: dtolnay/rust-toolchain@master
+ with:
+ toolchain: ${{ env.MSRV }}
+ - uses: taiki-e/install-action@cargo-no-dev-deps
+ - run: cargo no-dev-deps check --all --lib --bins ${{ matrix.features }}
build:
- needs: quick-tests
+ needs: [formatting, linting, internal-tests, mavlink-dump, msrv]
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- - os: macos-latest
- TARGET: x86_64-apple-darwin
- FEATURES: --features all-dialects
+ - os: macos-latest
+ TARGET: x86_64-apple-darwin
+ FEATURES: --features ardupilotmega
- - os: ubuntu-latest
- TARGET: arm-unknown-linux-musleabihf
- FLAGS: --features ardupilotmega
+ - os: ubuntu-latest
+ TARGET: arm-unknown-linux-musleabihf
+ FLAGS: --features ardupilotmega
- - os: ubuntu-latest
- TARGET: armv7-unknown-linux-musleabihf
- FLAGS: --features ardupilotmega
+ - os: ubuntu-latest
+ TARGET: armv7-unknown-linux-musleabihf
+ FLAGS: --features ardupilotmega
- - os: ubuntu-latest
- TARGET: x86_64-unknown-linux-musl
- FLAGS: --features all-dialects
+ - os: ubuntu-latest
+ TARGET: x86_64-unknown-linux-musl
+ FLAGS: --features ardupilotmega
- - os: ubuntu-latest
- TARGET: x86_64-unknown-linux-musl
- FLAGS: --features all-dialects,emit-extensions
+ - os: ubuntu-latest
+ TARGET: x86_64-unknown-linux-musl
+ FLAGS: --features ardupilotmega,emit-description,emit-extensions
- - os: ubuntu-latest
- TARGET: thumbv7m-none-eabi
- FLAGS: --no-default-features --features embedded
+ - os: ubuntu-latest
+ TARGET: thumbv7m-none-eabi
+ FLAGS: --no-default-features --features embedded
- - os: windows-latest
- TARGET: x86_64-pc-windows-msvc
- FLAGS: --features all-dialects
+ - os: windows-latest
+ TARGET: x86_64-pc-windows-msvc
+ FLAGS: --features ardupilotmega
steps:
- - name: Building ${{ matrix.TARGET }}
- run: echo "${{ matrix.TARGET }}"
- - uses: actions/checkout@master
- - uses: actions-rs/toolchain@v1.0.1
- with:
- toolchain: stable
- target: ${{ matrix.TARGET }}
- override: true
- - uses: actions-rs/cargo@v1
- with:
- use-cross: true
- command: build
- args: --verbose --release --target=${{ matrix.TARGET }} ${{ matrix.FLAGS }}
+ - name: Building ${{ matrix.TARGET }}
+ run: echo "${{ matrix.TARGET }}"
+ - uses: actions/checkout@master
+ - uses: dtolnay/rust-toolchain@stable
+ with:
+ target: ${{ matrix.TARGET }}
+ - uses: actions-rs/cargo@v1
+ with:
+ use-cross: true
+ command: build
+ args: --verbose --release --package=mavlink --target=${{ matrix.TARGET }} ${{ matrix.FLAGS }}
test-embedded-size:
needs: build
runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@master
+ - uses: dtolnay/rust-toolchain@nightly
+ with:
+ target: thumbv7em-none-eabihf
+ - name: Build
+ run: cargo +nightly build --target thumbv7em-none-eabihf --manifest-path mavlink/examples/embedded/Cargo.toml --out-dir $PWD --release -Z unstable-options
+
+ docs:
+ needs: internal-tests
+ runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- - uses: actions-rs/toolchain@v1.0.1
+ - uses: dtolnay/rust-toolchain@nightly
+ - name: Build docs
+ run: cargo doc --features "default all-dialects emit-description emit-extensions format-generated-code tokio-1 signing"
+ - name: Deploy
+ uses: peaceiris/actions-gh-pages@v3
+ if: ${{ github.ref == 'refs/heads/master' }}
with:
- toolchain: nightly
- target: thumbv7em-none-eabihf
- override: true
- - name: Build
- run: cargo +nightly build --target thumbv7em-none-eabihf --manifest-path examples/embedded/Cargo.toml --out-dir $PWD --release -Z unstable-options
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ publish_dir: ./target/doc
\ No newline at end of file
diff --git a/.gitmodules b/.gitmodules
index 3fbebb0955..80bff682ff 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,3 @@
-[submodule "mavlink"]
- path = mavlink
+[submodule "mavlink/mavlink"]
+ path = mavlink/mavlink
url = https://github.com/mavlink/mavlink
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 0000000000..2a7f278391
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,128 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1707604355173
+
+
+ 1707604355173
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Cargo.toml b/Cargo.toml
index d542f56f8a..71ebeb23ac 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,82 +1,14 @@
+[workspace]
+members = ["mavlink", "mavlink-bindgen", "mavlink-core"]
+resolver = "1"
-[package]
-name = "mavlink"
-version = "0.10.1"
-authors = ["Todd Stellanova", "Michal Podhradsky", "Kevin Mehall", "Tim Ryan", "Patrick José Pereira", "Ibiyemi Abiodun"]
-build = "build/main.rs"
-description = "Implements the MAVLink data interchange format for UAVs."
-readme = "README.md"
-license = "MIT/Apache-2.0"
-repository = "https://github.com/mavlink/rust-mavlink"
-edition = "2018"
-
-[build-dependencies]
-crc-any = "2.3.0"
-bytes = { version = "1.0", default-features = false }
-xml-rs = "0.2"
-quote = "0.3"
-lazy_static = "1.2.0"
-serde = { version = "1.0.115", optional = true, features = ["derive"] }
-
-[[bin]]
-name = "mavlink-dump"
-required-features = ["ardupilotmega"]
-
-[dependencies]
-crc-any = "2.3.5"
-bytes = { version = "1.0", default-features = false }
+[workspace.dependencies]
+crc-any = { version = "2.3.5", default-features = false }
num-traits = { version = "0.2", default-features = false }
-num-derive = "0.3.2"
+num-derive = "0.4"
bitflags = "1.2.1"
-serial = { version = "0.4", optional = true }
-serde = { version = "1.0.115", optional = true, features = ["derive"] }
byteorder = { version = "1.3.4", default-features = false }
-embedded-hal = { version = "0.2", optional = true }
-nb = { version = "0.1", optional = true }
-
-[features]
-"ardupilotmega" = ["common", "icarous", "uavionix"]
-"asluav" = ["common"]
-"autoquad" = ["common"]
-"matrixpilot" = ["common"]
-"minimal" = []
-"paparazzi" = ["common"]
-"python_array_test" = ["common"]
-"slugs" = ["common"]
-"standard" = ["common"]
-"test" = []
-"ualberta" = ["common"]
-"uavionix" = ["common"]
-"icarous" = []
-"common" = []
-
-"all-dialects" = [
- "ardupilotmega",
- "asluav",
- "autoquad",
- "matrixpilot",
- "minimal",
- "paparazzi",
- "python_array_test",
- "slugs",
- "standard",
- "test",
- "ualberta",
- "uavionix",
- "icarous",
- "common",
-]
-
-"emit-description" = []
-"emit-extensions" = []
-"std" = ["byteorder/std"]
-"udp" = []
-"tcp" = []
-"direct-serial" = []
-"embedded" = ["embedded-hal", "nb"]
-default= ["std", "tcp", "udp", "direct-serial", "serial", "serde", "common"]
-# build with all features on docs.rs so that users viewing documentation
-# can see everything
-[package.metadata.docs.rs]
-features = ["default", "all-dialects"]
+[workspace.package]
+edition = "2021"
+rust-version = "1.70.0"
diff --git a/README.md b/README.md
index 0e179fb6d6..689c6bf1f5 100644
--- a/README.md
+++ b/README.md
@@ -4,27 +4,45 @@
[![Crate info](https://img.shields.io/crates/v/mavlink.svg)](https://crates.io/crates/mavlink)
[![Documentation](https://docs.rs/mavlink/badge.svg)](https://docs.rs/mavlink)
-Rust implementation of the [MAVLink](http://qgroundcontrol.org/mavlink/start) UAV messaging protocol,
+Rust implementation of the [MAVLink](https://mavlink.io/en) UAV messaging protocol,
with bindings for all message sets.
Add to your Cargo.toml:
```
-mavlink = "0.10.1"
+mavlink = "0.12.2"
```
## Examples
-See [src/bin/mavlink-dump.rs](src/bin/mavlink-dump.rs) for a usage example.
+See [examples/](mavlink/examples/mavlink-dump/src/main.rs) for different usage examples.
+
+### mavlink-dump
+[examples/mavlink-dump](mavlink/examples/mavlink-dump/src/main.rs) contains an executable example that can be used to test message reception.
+
+It can be executed directly by running:
+```
+cargo run --example mavlink-dump [options]
+```
It's also possible to install the working example via `cargo` command line:
```sh
-cargo install mavlink
+cargo install --path examples/mavlink-dump
+```
+
+It can then be executed by running:
+```
+mavlink-dump [options]
+```
+
+Execution call example:
+```sh
+mavlink-dump udpin:127.0.0.1:14540
```
### Community projects
Check some projects built by the community:
- [mavlink2rest](https://github.com/patrickelectric/mavlink2rest): A REST server that provides easy and friendly access to mavlink messages.
-- [mavlink-camera-manager](https://github.com/patrickelectric/mavlink-camera-manager): Extensible cross-platform camera server.
+- [mavlink-camera-manager](https://github.com/mavlink/mavlink-camera-manager): Extensible cross-platform camera server.
## License
diff --git a/build/binder.rs b/build/binder.rs
deleted file mode 100644
index d3b663c510..0000000000
--- a/build/binder.rs
+++ /dev/null
@@ -1,23 +0,0 @@
-use quote::Ident;
-use std::io::Write;
-
-pub fn generate(modules: Vec, out: &mut W) {
- let modules_tokens = modules.into_iter().map(|module| {
- let module_ident = Ident::from(module.clone());
-
- quote! {
- #[allow(non_camel_case_types)]
- #[allow(non_snake_case)]
- #[allow(unused_variables)]
- #[allow(unused_mut)]
- #[cfg(feature = #module)]
- pub mod #module_ident;
- }
- });
-
- let tokens = quote! {
- #(#modules_tokens)*
- };
-
- writeln!(out, "{}", tokens).unwrap();
-}
diff --git a/build/main.rs b/build/main.rs
deleted file mode 100644
index a4750ebead..0000000000
--- a/build/main.rs
+++ /dev/null
@@ -1,113 +0,0 @@
-#![recursion_limit = "256"]
-#[macro_use]
-extern crate quote;
-
-extern crate xml;
-
-mod binder;
-mod parser;
-mod util;
-
-use crate::util::to_module_name;
-use std::env;
-use std::fs::{read_dir, File};
-use std::path::{Path, PathBuf};
-use std::process::Command;
-
-pub fn main() {
- let src_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
-
- // Update and init submodule
- match Command::new("git")
- .arg("submodule")
- .arg("update")
- .arg("--init")
- .current_dir(&src_dir)
- .status()
- {
- Ok(_) => {}
- Err(error) => eprintln!("{}", error),
- }
-
- // find & apply patches to XML definitions to avoid crashes
- let mut patch_dir = src_dir.to_path_buf();
- patch_dir.push("build/patches");
- let mut mavlink_dir = src_dir.to_path_buf();
- mavlink_dir.push("mavlink");
-
- if let Ok(dir) = read_dir(patch_dir) {
- for entry in dir {
- if let Ok(entry) = entry {
- match Command::new("git")
- .arg("apply")
- .arg(entry.path().as_os_str())
- .current_dir(&mavlink_dir)
- .status()
- {
- Ok(_) => (),
- Err(error) => eprintln!("{}", error),
- }
- }
- }
- }
-
- let mut definitions_dir = src_dir.to_path_buf();
- definitions_dir.push("mavlink/message_definitions/v1.0");
-
- let out_dir = env::var("OUT_DIR").unwrap();
-
- let mut modules = vec![];
-
- for entry in read_dir(&definitions_dir).expect("could not read definitions directory") {
- let entry = entry.expect("could not read directory entry");
-
- let definition_file = entry.file_name();
- let module_name = to_module_name(&definition_file);
-
- let mut definition_rs = PathBuf::from(&module_name);
- definition_rs.set_extension("rs");
-
- modules.push(module_name);
-
- let in_path = Path::new(&definitions_dir).join(&definition_file);
- let mut inf = File::open(&in_path).unwrap();
-
- let dest_path = Path::new(&out_dir).join(definition_rs);
- let mut outf = File::create(&dest_path).unwrap();
-
- // generate code
- parser::generate(&mut inf, &mut outf);
-
- // format code
- match Command::new("rustfmt")
- .arg(dest_path.as_os_str())
- .current_dir(&out_dir)
- .status()
- {
- Ok(_) => (),
- Err(error) => eprintln!("{}", error),
- }
-
- // Re-run build if definition file changes
- println!("cargo:rerun-if-changed={}", entry.path().to_string_lossy());
- }
-
- // output mod.rs
- {
- let dest_path = Path::new(&out_dir).join("mod.rs");
- let mut outf = File::create(&dest_path).unwrap();
-
- // generate code
- binder::generate(modules, &mut outf);
-
- // format code
- match Command::new("rustfmt")
- .arg(dest_path.as_os_str())
- .current_dir(&out_dir)
- .status()
- {
- Ok(_) => (),
- Err(error) => eprintln!("{}", error),
- }
- }
-}
diff --git a/mavlink b/mavlink
deleted file mode 160000
index 6723147f26..0000000000
--- a/mavlink
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 6723147f26d2625383a588c88633c568c9e9b929
diff --git a/mavlink-bindgen/Cargo.toml b/mavlink-bindgen/Cargo.toml
new file mode 100644
index 0000000000..2c23aeefaa
--- /dev/null
+++ b/mavlink-bindgen/Cargo.toml
@@ -0,0 +1,33 @@
+[package]
+name = "mavlink-bindgen"
+version = "0.13.2"
+edition.workspace = true
+license = "MIT/Apache-2.0"
+description = "Library used by rust-mavlink."
+readme = "README.md"
+repository = "https://github.com/mavlink/rust-mavlink"
+rust-version.workspace = true
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+crc-any = { workspace = true, default-features = false }
+quick-xml = "0.36"
+quote = "1"
+proc-macro2 = "1.0.43"
+lazy_static = "1.2.0"
+serde = { version = "1.0.115", optional = true, features = ["derive"] }
+clap = { version = "~4.3.24", optional = true, default-features =false, features = ["derive", "help", "usage", "error-context"] }
+thiserror = "1.0.56"
+
+# Required to support MSRV 1.65.0
+clap_lex = { version = "=0.5.0", optional=true }
+clap_builder = { version = "~4.3.24", optional=true}
+anstyle = { version = "=1.0.2", optional=true }
+anstyle-query = { version = "=1.0.0", optional=true }
+anstyle-parse = { version = "=0.2.1", optional=true }
+
+[features]
+cli = ["dep:clap", "dep:clap_lex", "dep:clap_builder", "dep:anstyle", "dep:anstyle-query", "dep:anstyle-parse"]
+emit-extensions = []
+emit-description = []
diff --git a/mavlink-bindgen/README.md b/mavlink-bindgen/README.md
new file mode 100644
index 0000000000..c813b5fc3a
--- /dev/null
+++ b/mavlink-bindgen/README.md
@@ -0,0 +1,98 @@
+# mavlink-bindgen
+
+[![Build status](https://github.com/mavlink/rust-mavlink/actions/workflows/test.yml/badge.svg)](https://github.com/mavlink/rust-mavlink/actions/workflows/test.yml)
+[![Crate info](https://img.shields.io/crates/v/mavlink-bindgen.svg)](https://crates.io/crates/mavlink-bindgen)
+[![Documentation](https://docs.rs/mavlink-bindgen/badge.svg)](https://docs.rs/mavlink-bindgen)
+
+Library and CLI for generating code for the Rust implementation of the [MAVLink](https://mavlink.io/en) UAV messaging protocol.
+
+`mavlink-bindgen` can be used to create MAVLink bindings for Rust. This is used from `build.rs` in the [mavlink](https://crates.io/crates/mavlink) crate to create bindings from the standard MAVLink dialects in .
+
+## Usage
+
+`mavlink-bindgen` can be used as a code generator from `build.rs` as done is the `mavlink` crate for a custom MAVLink dialect or as a CLI tool to generate rust binding from XML dialect definitions. The generated code will depend on the [mavlink-core](https://crates.io/crates/mavlink-core) crate in both use cases. Each dialect generated will be locked behind a feature flag of the same name, that must be enabled when using the generated code.
+
+### CLI
+
+Build the binary using cargo with `cli` feature enabled:
+
+```shell
+cd mavlink-bindgen
+cargo build --features cli
+```
+
+Alternatively you can build and install `mavlink-bindgen` to your locally installed crates:
+
+```shell
+cargo install mavlink-bindgen --features cli
+```
+
+To generate code using the resulting binary:
+
+```shell
+mavlink-bindgen --format-generated-code message_definitions mavlink_dialects
+```
+
+The full command line options are shown below.
+
+```shell
+Usage: mavlink-bindgen [OPTIONS]
+
+Arguments:
+ Path to the directory containing the MAVLink dialect definitions
+ Path to the directory where the code is generated into, must already exist
+
+Options:
+ --format-generated-code format code generated code
+ --emit-cargo-build-messages prints cargo build message indicating when the code has to be rebuild
+ -h, --help Print help
+```
+
+The output dir will contain a `mod.rs` file with each dialect in its own file locked behind a feature flag.
+
+### Library as build dependency
+
+Add to your Cargo.toml:
+
+```toml
+mavlink-bindgen = "0.13.1"
+```
+
+Add a `build/main.rs` or `build.rs` to your project if it does not already exist. Then add the following to the `main` function to generate the code:
+
+```rs
+let out_dir = env::var("OUT_DIR").unwrap();
+let result = match mavlink_bindgen::generate(definitions_dir, out_dir) {
+ Ok(r) => r,
+ Err(e) => {
+ eprintln!("{e}");
+ return ExitCode::FAILURE;
+ }
+};
+```
+
+If the generated code should be formated use
+
+```rs
+ mavlink_bindgen::format_generated_code(&result);
+```
+
+To tell cargo when to regenerate code from the definitions use:
+
+```rs
+ mavlink_bindgen::emit_cargo_build_messages(&result);
+```
+
+Finally include the generated code into the `lib.rs` or `main.rs` :
+
+```rs
+#![cfg_attr(not(feature = "std"), no_std)]
+// include generate definitions
+include!(concat!(env!("OUT_DIR"), "/mod.rs"));
+
+pub use mavlink_core::*;
+```
+
+Since each dialect is locked behind a feature flag these need to be enabled for the dialects to become available when using the generated code.
+
+This approach is used by the `mavlink` crate see its build script for an example.
diff --git a/mavlink-bindgen/src/binder.rs b/mavlink-bindgen/src/binder.rs
new file mode 100644
index 0000000000..b9a911cc1c
--- /dev/null
+++ b/mavlink-bindgen/src/binder.rs
@@ -0,0 +1,25 @@
+use quote::{format_ident, quote};
+use std::io::Write;
+
+pub fn generate(modules: Vec<&str>, out: &mut W) {
+ let modules_tokens = modules.into_iter().map(|module| {
+ let module_ident = format_ident!("{}", module);
+
+ quote! {
+ #[allow(non_camel_case_types)]
+ #[allow(clippy::derive_partial_eq_without_eq)]
+ #[allow(clippy::field_reassign_with_default)]
+ #[allow(non_snake_case)]
+ #[allow(clippy::unnecessary_cast)]
+ #[allow(clippy::bad_bit_mask)]
+ #[cfg(feature = #module)]
+ pub mod #module_ident;
+ }
+ });
+
+ let tokens = quote! {
+ #(#modules_tokens)*
+ };
+
+ writeln!(out, "{tokens}").unwrap();
+}
diff --git a/mavlink-bindgen/src/cli.rs b/mavlink-bindgen/src/cli.rs
new file mode 100644
index 0000000000..5ff37b26f1
--- /dev/null
+++ b/mavlink-bindgen/src/cli.rs
@@ -0,0 +1,34 @@
+use std::path::PathBuf;
+
+use clap::Parser;
+use mavlink_bindgen::{emit_cargo_build_messages, format_generated_code, generate, BindGenError};
+
+#[derive(Parser)]
+/// Generate Rust bindings from MAVLink message dialect XML files.
+struct Cli {
+ /// Path to the directory containing the MAVLink dialect definitions.
+ definitions_dir: PathBuf,
+ /// Path to the directory where the code is generated into, must already exist.
+ destination_dir: PathBuf,
+ /// format code generated code
+ #[arg(long)]
+ format_generated_code: bool,
+ /// prints cargo build messages indicating when the code has to be rebuild
+ #[arg(long)]
+ emit_cargo_build_messages: bool,
+}
+
+pub fn main() -> Result<(), BindGenError> {
+ let args = Cli::parse();
+ let result = generate(args.definitions_dir, args.destination_dir)?;
+
+ if args.format_generated_code {
+ format_generated_code(&result);
+ }
+
+ if args.emit_cargo_build_messages {
+ emit_cargo_build_messages(&result);
+ }
+
+ Ok(())
+}
diff --git a/mavlink-bindgen/src/error.rs b/mavlink-bindgen/src/error.rs
new file mode 100644
index 0000000000..fe32023389
--- /dev/null
+++ b/mavlink-bindgen/src/error.rs
@@ -0,0 +1,29 @@
+use thiserror::Error;
+
+#[derive(Error, Debug)]
+pub enum BindGenError {
+ /// Represents a failure to read the MAVLink definitions directory.
+ #[error("Could not read definitions directory {path}: {source}")]
+ CouldNotReadDefinitionsDirectory {
+ source: std::io::Error,
+ path: std::path::PathBuf,
+ },
+ /// Represents a failure to read a MAVLink definition file.
+ #[error("Could not read definition file {path}: {source}")]
+ CouldNotReadDefinitionFile {
+ source: std::io::Error,
+ path: std::path::PathBuf,
+ },
+ /// Represents a failure to read a directory entry in the MAVLink definitions directory.
+ #[error("Could not read MAVLink definitions directory entry {path}: {source}")]
+ CouldNotReadDirectoryEntryInDefinitionsDirectory {
+ source: std::io::Error,
+ path: std::path::PathBuf,
+ },
+ /// Represents a failure to create a Rust file for the generated MAVLink bindings.
+ #[error("Could not create Rust bindings file {dest_path}: {source}")]
+ CouldNotCreateRustBindingsFile {
+ source: std::io::Error,
+ dest_path: std::path::PathBuf,
+ },
+}
diff --git a/mavlink-bindgen/src/lib.rs b/mavlink-bindgen/src/lib.rs
new file mode 100644
index 0000000000..23aae4b1d0
--- /dev/null
+++ b/mavlink-bindgen/src/lib.rs
@@ -0,0 +1,129 @@
+pub use crate::error::BindGenError;
+use std::fs::{read_dir, File};
+use std::io::BufWriter;
+use std::ops::Deref;
+use std::path::{Path, PathBuf};
+use std::process::Command;
+
+pub mod binder;
+pub mod error;
+pub mod parser;
+mod util;
+
+#[derive(Debug)]
+pub struct GeneratedBinding {
+ pub module_name: String,
+ pub mavlink_xml: PathBuf,
+ pub rust_module: PathBuf,
+}
+
+#[derive(Debug)]
+pub struct GeneratedBindings {
+ pub bindings: Vec,
+ pub mod_rs: PathBuf,
+}
+
+/// Generate Rust MAVLink dialect binding for dialects present in `definitions_dir` into `destination_dir`.
+///
+/// If successful returns paths of generated bindings linked to their dialect definitions files.
+pub fn generate, P2: AsRef>(
+ definitions_dir: P1,
+ destination_dir: P2,
+) -> Result {
+ _generate(definitions_dir.as_ref(), destination_dir.as_ref())
+}
+
+fn _generate(
+ definitions_dir: &Path,
+ destination_dir: &Path,
+) -> Result {
+ let mut bindings = vec![];
+
+ for entry_maybe in read_dir(definitions_dir).map_err(|source| {
+ BindGenError::CouldNotReadDefinitionsDirectory {
+ source,
+ path: definitions_dir.to_path_buf(),
+ }
+ })? {
+ let entry = entry_maybe.map_err(|source| {
+ BindGenError::CouldNotReadDirectoryEntryInDefinitionsDirectory {
+ source,
+ path: definitions_dir.to_path_buf(),
+ }
+ })?;
+
+ let definition_file = PathBuf::from(entry.file_name());
+ let module_name = util::to_module_name(&definition_file);
+
+ let definition_rs = PathBuf::from(&module_name).with_extension("rs");
+
+ let dest_path = destination_dir.join(definition_rs);
+ let mut outf = BufWriter::new(File::create(&dest_path).map_err(|source| {
+ BindGenError::CouldNotCreateRustBindingsFile {
+ source,
+ dest_path: dest_path.clone(),
+ }
+ })?);
+
+ // generate code
+ parser::generate(definitions_dir, &definition_file, &mut outf)?;
+
+ bindings.push(GeneratedBinding {
+ module_name,
+ mavlink_xml: entry.path(),
+ rust_module: dest_path,
+ });
+ }
+
+ // output mod.rs
+ {
+ let dest_path = destination_dir.join("mod.rs");
+ let mut outf = File::create(&dest_path).map_err(|source| {
+ BindGenError::CouldNotCreateRustBindingsFile {
+ source,
+ dest_path: dest_path.clone(),
+ }
+ })?;
+
+ // generate code
+ binder::generate(
+ bindings
+ .iter()
+ .map(|binding| binding.module_name.deref())
+ .collect(),
+ &mut outf,
+ );
+
+ Ok(GeneratedBindings {
+ bindings,
+ mod_rs: dest_path,
+ })
+ }
+}
+
+/// Formats generated code using `rustfmt`.
+pub fn format_generated_code(result: &GeneratedBindings) {
+ if let Err(error) = Command::new("rustfmt")
+ .args(
+ result
+ .bindings
+ .iter()
+ .map(|binding| binding.rust_module.clone()),
+ )
+ .arg(result.mod_rs.clone())
+ .status()
+ {
+ eprintln!("{error}");
+ }
+}
+
+/// Prints definitions for cargo that describe which files the generated code depends on, indicating when it has to be regenerated.
+pub fn emit_cargo_build_messages(result: &GeneratedBindings) {
+ for binding in &result.bindings {
+ // Re-run build if definition file changes
+ println!(
+ "cargo:rerun-if-changed={}",
+ binding.mavlink_xml.to_string_lossy()
+ );
+ }
+}
diff --git a/mavlink-bindgen/src/main.rs b/mavlink-bindgen/src/main.rs
new file mode 100644
index 0000000000..f2c68ba787
--- /dev/null
+++ b/mavlink-bindgen/src/main.rs
@@ -0,0 +1,20 @@
+#![recursion_limit = "256"]
+
+use std::process::ExitCode;
+
+#[cfg(feature = "cli")]
+mod cli;
+
+fn main() -> ExitCode {
+ #[cfg(feature = "cli")]
+ if let Err(e) = cli::main() {
+ eprintln!("{e}");
+ return ExitCode::FAILURE;
+ }
+
+ #[cfg(not(feature = "cli"))]
+ panic!("Compiled without cli feature");
+
+ #[cfg(feature = "cli")]
+ ExitCode::SUCCESS
+}
diff --git a/build/parser.rs b/mavlink-bindgen/src/parser.rs
similarity index 50%
rename from build/parser.rs
rename to mavlink-bindgen/src/parser.rs
index b621bcf3f0..dce7263129 100644
--- a/build/parser.rs
+++ b/mavlink-bindgen/src/parser.rs
@@ -1,43 +1,71 @@
use crc_any::CRCu16;
use std::cmp::Ordering;
+use std::collections::hash_map::Entry;
+use std::collections::{HashMap, HashSet};
use std::default::Default;
-use std::io::{Read, Write};
-use std::u32;
+use std::fs::File;
+use std::io::{BufReader, Write};
+use std::path::{Path, PathBuf};
+use std::str::FromStr;
-use xml::reader::{EventReader, XmlEvent};
+use quick_xml::{events::Event, Reader};
-use quote::{Ident, Tokens};
+use proc_macro2::{Ident, TokenStream};
+use quote::{format_ident, quote};
-use crate::util::to_module_name;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
+use crate::error::BindGenError;
+
#[derive(Debug, PartialEq, Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct MavProfile {
- pub includes: Vec,
- pub messages: Vec,
- pub enums: Vec,
+ pub messages: HashMap,
+ pub enums: HashMap,
}
impl MavProfile {
+ fn add_message(&mut self, message: &MavMessage) {
+ match self.messages.entry(message.name.clone()) {
+ Entry::Occupied(entry) => {
+ assert!(
+ entry.get() == message,
+ "Message '{}' defined twice but definitions are different",
+ message.name
+ );
+ }
+ Entry::Vacant(entry) => {
+ entry.insert(message.clone());
+ }
+ }
+ }
+
+ fn add_enum(&mut self, enm: &MavEnum) {
+ match self.enums.entry(enm.name.clone()) {
+ Entry::Occupied(entry) => {
+ entry.into_mut().try_combine(enm);
+ }
+ Entry::Vacant(entry) => {
+ entry.insert(enm.clone());
+ }
+ }
+ }
+
/// Go over all fields in the messages, and if you encounter an enum,
/// update this enum with information about whether it is a bitmask, and what
/// is the desired width of such.
fn update_enums(mut self) -> Self {
- for msg in &self.messages {
+ for msg in self.messages.values() {
for field in &msg.fields {
- if let Some(ref enum_name) = field.enumtype {
- // it is an enum
- if let Some(ref dsp) = field.display {
- // it is a bitmask
- if dsp == "bitmask" {
- // find the corresponding enum
- for mut enm in &mut self.enums {
- if enm.name == *enum_name {
- // this is the right enum
- enm.bitfield = Some(field.mavtype.rust_type());
- }
+ if let Some(enum_name) = &field.enumtype {
+ // it is a bitmask
+ if let Some("bitmask") = &field.display.as_deref() {
+ // find the corresponding enum
+ for enm in self.enums.values_mut() {
+ if enm.name == *enum_name {
+ // this is the right enum
+ enm.bitfield = Some(field.mavtype.rust_primitive_type());
}
}
}
@@ -58,108 +86,61 @@ impl MavProfile {
// }
/// Simple header comment
- fn emit_comments(&self) -> Ident {
- Ident::from(format!(
- "// This file was automatically generated, do not edit \n"
- ))
- }
-
- /// Emit includes
- fn emit_includes(&self) -> Vec {
- self.includes
- .iter()
- .map(|i| Ident::from(to_module_name(i)))
- .collect::>()
+ fn emit_comments(&self) -> TokenStream {
+ quote!(#![doc = "This file was automatically generated, do not edit"])
}
/// Emit rust messages
- fn emit_msgs(&self) -> Vec {
- self.messages
- .iter()
- .map(|d| d.emit_rust())
- .collect::>()
+ fn emit_msgs(&self) -> Vec {
+ self.messages.values().map(|d| d.emit_rust()).collect()
}
/// Emit rust enums
- fn emit_enums(&self) -> Vec {
- self.enums
- .iter()
- .map(|d| d.emit_rust())
- .collect::>()
+ fn emit_enums(&self) -> Vec {
+ self.enums.values().map(|d| d.emit_rust()).collect()
}
/// Get list of original message names
- fn emit_enum_names(&self) -> Vec {
+ fn emit_enum_names(&self) -> Vec {
self.messages
- .iter()
+ .values()
.map(|msg| {
- let name = Ident::from(msg.name.clone());
+ let name = format_ident!("{}", msg.name);
quote!(#name)
})
- .collect::>()
+ .collect()
}
/// Emit message names with "_DATA" at the end
- fn emit_struct_names(&self) -> Vec {
+ fn emit_struct_names(&self) -> Vec {
self.messages
- .iter()
+ .values()
.map(|msg| msg.emit_struct_name())
- .collect::>()
+ .collect()
}
- /// A list of message IDs
- fn emit_msg_ids(&self) -> Vec {
- self.messages
- .iter()
- .map(|msg| {
- let id = Ident::from(msg.id.to_string());
- quote!(#id)
- })
- .collect::>()
- }
-
- /// CRC values needed for mavlink parsing
- fn emit_msg_crc(&self) -> Vec {
- self.messages
- .iter()
- .map(|msg| {
- let crc = Ident::from(extra_crc(&msg).to_string());
- quote!(#crc)
- })
- .collect::>()
- }
-
- fn emit_rust(&self) -> Tokens {
+ fn emit_rust(&self) -> TokenStream {
//TODO verify that id_width of u8 is OK even in mavlink v1
- let id_width = Ident::from("u32");
+ let id_width = format_ident!("u32");
let comment = self.emit_comments();
let msgs = self.emit_msgs();
- let includes = self.emit_includes();
let enum_names = self.emit_enum_names();
let struct_names = self.emit_struct_names();
let enums = self.emit_enums();
- let msg_ids = self.emit_msg_ids();
- let msg_crc = self.emit_msg_crc();
-
- let mav_message = self.emit_mav_message(&enum_names, &struct_names, &includes);
- let mav_message_from_includes = self.emit_mav_message_from_includes(&includes);
- let mav_message_parse =
- self.emit_mav_message_parse(&enum_names, &struct_names, &msg_ids, &includes);
- let mav_message_crc = self.emit_mav_message_crc(&id_width, &msg_ids, &msg_crc, &includes);
- let mav_message_name = self.emit_mav_message_name(&enum_names, &includes);
- let mav_message_id = self.emit_mav_message_id(&enum_names, &msg_ids, &includes);
- let mav_message_id_from_name =
- self.emit_mav_message_id_from_name(&enum_names, &msg_ids, &includes);
+
+ let mav_message = self.emit_mav_message(&enum_names, &struct_names);
+ let mav_message_parse = self.emit_mav_message_parse(&enum_names, &struct_names);
+ let mav_message_crc = self.emit_mav_message_crc(&id_width, &struct_names);
+ let mav_message_name = self.emit_mav_message_name(&enum_names, &struct_names);
+ let mav_message_id = self.emit_mav_message_id(&enum_names, &struct_names);
+ let mav_message_id_from_name = self.emit_mav_message_id_from_name(&struct_names);
let mav_message_default_from_id =
- self.emit_mav_message_default_from_id(&enum_names, &msg_ids, &includes);
- let mav_message_serialize = self.emit_mav_message_serialize(&enum_names, &includes);
+ self.emit_mav_message_default_from_id(&enum_names, &struct_names);
+ let mav_message_serialize = self.emit_mav_message_serialize(&enum_names);
quote! {
#comment
- use crate::MavlinkVersion;
- #[allow(unused_imports)]
- use bytes::{Buf, BufMut, Bytes, BytesMut};
#[allow(unused_imports)]
use num_derive::FromPrimitive;
#[allow(unused_imports)]
@@ -171,19 +152,11 @@ impl MavProfile {
#[allow(unused_imports)]
use bitflags::bitflags;
- use crate::{Message, error::*};
- #[allow(unused_imports)]
- use crate::{#(#includes::*),*};
+ use mavlink_core::{MavlinkVersion, Message, MessageData, bytes::Bytes, bytes_mut::BytesMut};
#[cfg(feature = "serde")]
use serde::{Serialize, Deserialize};
- #[cfg(not(feature = "std"))]
- use alloc::vec::Vec;
-
- #[cfg(not(feature = "std"))]
- use alloc::string::ToString;
-
#(#enums)*
#(#msgs)*
@@ -191,8 +164,6 @@ impl MavProfile {
#[derive(Clone, PartialEq, Debug)]
#mav_message
- #mav_message_from_includes
-
impl Message for MavMessage {
#mav_message_parse
#mav_message_name
@@ -205,99 +176,42 @@ impl MavProfile {
}
}
- fn emit_mav_message(
- &self,
- enums: &Vec,
- structs: &Vec,
- includes: &Vec,
- ) -> Tokens {
- let includes = includes.into_iter().map(|include| {
- quote! {
- #include(crate::#include::MavMessage)
- }
- });
-
+ fn emit_mav_message(&self, enums: &[TokenStream], structs: &[TokenStream]) -> TokenStream {
quote! {
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type"))]
+ #[repr(u32)]
pub enum MavMessage {
#(#enums(#structs),)*
- #(#includes,)*
}
}
}
- fn emit_mav_message_from_includes(&self, includes: &Vec) -> Tokens {
- let froms = includes.into_iter().map(|include| {
- quote! {
- impl From for MavMessage {
- fn from(message: crate::#include::MavMessage) -> Self {
- MavMessage::#include(message)
- }
- }
- }
- });
-
- quote! {
- #(#froms)*
- }
- }
-
fn emit_mav_message_parse(
&self,
- enums: &Vec,
- structs: &Vec,
- ids: &Vec,
- includes: &Vec,
- ) -> Tokens {
- let id_width = Ident::from("u32");
-
- // try parsing all included message variants if it doesn't land in the id
- // range for this message
- let includes_branches = includes.into_iter().map(|i| {
- quote! {
- if let Ok(msg) = crate::#i::MavMessage::parse(version, id, payload) {
- return Ok(MavMessage::#i(msg))
- }
- }
- });
+ enums: &[TokenStream],
+ structs: &[TokenStream],
+ ) -> TokenStream {
+ let id_width = format_ident!("u32");
quote! {
- fn parse(version: MavlinkVersion, id: #id_width, payload: &[u8]) -> Result {
+ fn parse(version: MavlinkVersion, id: #id_width, payload: &[u8]) -> Result {
match id {
- #(#ids => #structs::deser(version, payload).map(|s| MavMessage::#enums(s)),)*
+ #(#structs::ID => #structs::deser(version, payload).map(Self::#enums),)*
_ => {
- #(#includes_branches)*
- Err(ParserError::UnknownMessage { id })
+ Err(::mavlink_core::error::ParserError::UnknownMessage { id })
},
}
}
}
}
- fn emit_mav_message_crc(
- &self,
- id_width: &Ident,
- ids: &Vec,
- crc: &Vec,
- includes: &Vec,
- ) -> Tokens {
- let includes_branch = includes.into_iter().map(|include| {
- quote! {
- match crate::#include::MavMessage::extra_crc(id) {
- 0 => {},
- any => return any
- }
- }
- });
-
+ fn emit_mav_message_crc(&self, id_width: &Ident, structs: &[TokenStream]) -> TokenStream {
quote! {
fn extra_crc(id: #id_width) -> u8 {
match id {
- #(#ids => #crc,)*
+ #(#structs::ID => #structs::EXTRA_CRC,)*
_ => {
- #(#includes_branch)*
-
0
},
}
@@ -305,72 +219,33 @@ impl MavProfile {
}
}
- fn emit_mav_message_name(&self, enums: &Vec, includes: &Vec) -> Tokens {
- let enum_names = enums
- .iter()
- .map(|enum_name| {
- let name = Ident::from(format!("\"{}\"", enum_name));
- quote!(#name)
- })
- .collect::>();
-
+ fn emit_mav_message_name(&self, enums: &[TokenStream], structs: &[TokenStream]) -> TokenStream {
quote! {
fn message_name(&self) -> &'static str {
match self {
- #(MavMessage::#enums(..) => #enum_names,)*
- #(MavMessage::#includes(msg) => msg.message_name(),)*
+ #(Self::#enums(..) => #structs::NAME,)*
}
}
}
}
- fn emit_mav_message_id(
- &self,
- enums: &Vec,
- ids: &Vec,
- includes: &Vec,
- ) -> Tokens {
- let id_width = Ident::from("u32");
+ fn emit_mav_message_id(&self, enums: &[TokenStream], structs: &[TokenStream]) -> TokenStream {
+ let id_width = format_ident!("u32");
quote! {
fn message_id(&self) -> #id_width {
match self {
- #(MavMessage::#enums(..) => #ids,)*
- #(MavMessage::#includes(msg) => msg.message_id(),)*
+ #(Self::#enums(..) => #structs::ID,)*
}
}
}
}
- fn emit_mav_message_id_from_name(
- &self,
- enums: &Vec,
- ids: &Vec,
- includes: &Vec,
- ) -> Tokens {
- let includes_branch = includes.into_iter().map(|include| {
- quote! {
- match crate::#include::MavMessage::message_id_from_name(name) {
- Ok(name) => return Ok(name),
- Err(..) => {}
- }
- }
- });
-
- let enum_names = enums
- .iter()
- .map(|enum_name| {
- let name = Ident::from(format!("\"{}\"", enum_name));
- quote!(#name)
- })
- .collect::>();
-
+ fn emit_mav_message_id_from_name(&self, structs: &[TokenStream]) -> TokenStream {
quote! {
fn message_id_from_name(name: &str) -> Result {
match name {
- #(#enum_names => Ok(#ids),)*
+ #(#structs::NAME => Ok(#structs::ID),)*
_ => {
- #(#includes_branch)*
-
Err("Invalid message name.")
}
}
@@ -380,53 +255,33 @@ impl MavProfile {
fn emit_mav_message_default_from_id(
&self,
- enums: &Vec,
- ids: &Vec,
- includes: &Vec,
- ) -> Tokens {
- let data_name = enums
- .iter()
- .map(|enum_name| {
- let name = Ident::from(format!("{}_DATA", enum_name));
- quote!(#name)
- })
- .collect::>();
-
- let includes_branches = includes.into_iter().map(|include| {
- quote! {
- if let Ok(msg) = crate::#include::MavMessage::default_message_from_id(id) {
- return Ok(MavMessage::#include(msg));
- }
- }
- });
-
+ enums: &[TokenStream],
+ structs: &[TokenStream],
+ ) -> TokenStream {
quote! {
- fn default_message_from_id(id: u32) -> Result {
+ fn default_message_from_id(id: u32) -> Result {
match id {
- #(#ids => Ok(MavMessage::#enums(#data_name::default())),)*
+ #(#structs::ID => Ok(Self::#enums(#structs::default())),)*
_ => {
- #(#includes_branches)*
-
- return Err("Invalid message id.");
+ Err("Invalid message id.")
}
}
}
}
}
- fn emit_mav_message_serialize(&self, enums: &Vec, includes: &Vec) -> Tokens {
+ fn emit_mav_message_serialize(&self, enums: &Vec) -> TokenStream {
quote! {
- fn ser(&self) -> Vec {
+ fn ser(&self, version: MavlinkVersion, bytes: &mut [u8]) -> usize {
match self {
- #(&MavMessage::#enums(ref body) => body.ser(),)*
- #(&MavMessage::#includes(ref msg) => msg.ser(),)*
+ #(Self::#enums(body) => body.ser(version, bytes),)*
}
}
}
}
}
-#[derive(Debug, PartialEq, Clone, Default)]
+#[derive(Debug, PartialEq, Eq, Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct MavEnum {
pub name: String,
@@ -437,48 +292,95 @@ pub struct MavEnum {
}
impl MavEnum {
- fn has_enum_values(&self) -> bool {
- self.entries.iter().all(|x| x.value.is_some())
+ fn try_combine(&mut self, enm: &Self) {
+ if self.name == enm.name {
+ for enum_entry in &enm.entries {
+ let found_entry = self.entries.iter().find(|elem| {
+ elem.name == enum_entry.name && elem.value.unwrap() == enum_entry.value.unwrap()
+ });
+ match found_entry {
+ Some(entry) => panic!("Enum entry {} already exists", entry.name),
+ None => self.entries.push(enum_entry.clone()),
+ }
+ }
+ }
}
- fn emit_defs(&self) -> Vec {
- let mut cnt = 0;
+ fn emit_defs(&self) -> Vec {
+ let mut cnt = 0u32;
self.entries
.iter()
.map(|enum_entry| {
- let name = Ident::from(enum_entry.name.clone());
+ let name = format_ident!("{}", enum_entry.name.clone());
let value;
- if !self.has_enum_values() {
- value = Ident::from(cnt.to_string());
+
+ #[cfg(feature = "emit-description")]
+ let description = if let Some(description) = enum_entry.description.as_ref() {
+ quote!(#[doc = #description])
+ } else {
+ quote!()
+ };
+
+ #[cfg(not(feature = "emit-description"))]
+ let description = quote!();
+
+ if enum_entry.value.is_none() {
cnt += 1;
+ value = quote!(#cnt);
} else {
- value = Ident::from(enum_entry.value.unwrap().to_string());
+ let tmp_value = enum_entry.value.unwrap();
+ cnt = cnt.max(tmp_value);
+ let tmp = TokenStream::from_str(&tmp_value.to_string()).unwrap();
+ value = quote!(#tmp);
};
if self.bitfield.is_some() {
- quote!(const #name = #value;)
+ quote! {
+ #description
+ const #name = #value;
+ }
} else {
- quote!(#name = #value,)
+ quote! {
+ #description
+ #name = #value,
+ }
}
})
- .collect::>()
+ .collect()
}
- fn emit_name(&self) -> Tokens {
- let name = Ident::from(self.name.clone());
+ fn emit_name(&self) -> TokenStream {
+ let name = format_ident!("{}", self.name);
quote!(#name)
}
- fn emit_rust(&self) -> Tokens {
+ fn emit_const_default(&self) -> TokenStream {
+ let default = format_ident!("{}", self.entries[0].name);
+ quote!(pub const DEFAULT: Self = Self::#default;)
+ }
+
+ fn emit_rust(&self) -> TokenStream {
let defs = self.emit_defs();
- let default = Ident::from(self.entries[0].name.clone());
let enum_name = self.emit_name();
+ let const_default = self.emit_const_default();
+
+ #[cfg(feature = "emit-description")]
+ let description = if let Some(description) = self.description.as_ref() {
+ let desc = description.to_string();
+ quote!(#[doc = #desc])
+ } else {
+ quote!()
+ };
+
+ #[cfg(not(feature = "emit-description"))]
+ let description = quote!();
let enum_def;
if let Some(width) = self.bitfield.clone() {
- let width = Ident::from(width);
+ let width = format_ident!("{}", width);
enum_def = quote! {
bitflags!{
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+ #description
pub struct #enum_name: #width {
#(#defs)*
}
@@ -489,6 +391,8 @@ impl MavEnum {
#[derive(Debug, Copy, Clone, PartialEq, FromPrimitive, ToPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type"))]
+ #[repr(u32)]
+ #description
pub enum #enum_name {
#(#defs)*
}
@@ -498,16 +402,20 @@ impl MavEnum {
quote! {
#enum_def
+ impl #enum_name {
+ #const_default
+ }
+
impl Default for #enum_name {
fn default() -> Self {
- #enum_name::#default
+ Self::DEFAULT
}
}
}
}
}
-#[derive(Debug, PartialEq, Clone, Default)]
+#[derive(Debug, PartialEq, Eq, Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct MavEnumEntry {
pub value: Option,
@@ -528,12 +436,12 @@ pub struct MavMessage {
impl MavMessage {
/// Return Token of "MESSAGE_NAME_DATA
/// for mavlink struct data
- fn emit_struct_name(&self) -> Tokens {
- let name = Ident::from(format!("{}_DATA", self.name));
+ fn emit_struct_name(&self) -> TokenStream {
+ let name = format_ident!("{}", format!("{}_DATA", self.name));
quote!(#name)
}
- fn emit_name_types(&self) -> (Vec, usize) {
+ fn emit_name_types(&self) -> (Vec, usize) {
let mut encoded_payload_len: usize = 0;
let field_toks = self
.fields
@@ -546,59 +454,90 @@ impl MavMessage {
let description = field.emit_description();
#[cfg(not(feature = "emit-description"))]
- let description = Ident::from("");
+ let description = quote!();
// From MAVLink specification:
// If sent by an implementation that doesn't have the extensions fields
// then the recipient will see zero values for the extensions fields.
let serde_default = if field.is_extension {
- Ident::from(r#"#[cfg_attr(feature = "serde", serde(default))]"#)
+ if field.enumtype.is_some() {
+ quote!(#[cfg_attr(feature = "serde", serde(default))])
+ } else {
+ quote!(#[cfg_attr(feature = "serde", serde(default = "crate::RustDefault::rust_default"))])
+ }
} else {
- Ident::from("")
+ quote!()
+ };
+
+ let serde_with_attr = if matches!(field.mavtype, MavType::Array(_, _)) {
+ quote!(#[cfg_attr(feature = "serde", serde(with = "serde_arrays"))])
+ } else {
+ quote!()
};
quote! {
#description
#serde_default
+ #serde_with_attr
#nametype
}
})
- .collect::>();
+ .collect::>();
(field_toks, encoded_payload_len)
}
/// Generate description for the given message
#[cfg(feature = "emit-description")]
- fn emit_description(&self) -> Tokens {
- let mut desc = String::from(format!("\n/// id: {}\n", self.id));
+ fn emit_description(&self) -> TokenStream {
+ let mut ts = TokenStream::new();
+ let desc = format!("id: {}", self.id);
+ ts.extend(quote!(#[doc = #desc]));
if let Some(val) = self.description.clone() {
- desc = desc + &format!("/// {}.\n", val);
+ let doc = &format!("{val}.");
+ ts.extend(quote!(#[doc = #doc]));
}
- let desc = Ident::from(desc);
- quote!(#desc)
+ ts
}
- fn emit_serialize_vars(&self) -> Tokens {
- let ser_vars = self
- .fields
- .iter()
- .map(|f| f.rust_writer())
- .collect::>();
+ fn emit_serialize_vars(&self) -> TokenStream {
+ let ser_vars = self.fields.iter().map(|f| f.rust_writer());
+
quote! {
- let mut _tmp = Vec::new();
+ let mut __tmp = BytesMut::new(bytes);
+
+ // TODO: these lints are produced on a couple of cubepilot messages
+ // because they are generated as empty structs with no fields and
+ // therefore Self::ENCODED_LEN is 0. This itself is a bug because
+ // cubepilot.xml has unclosed tags in fields, which the parser has
+ // bad time handling. It should probably be fixed in both the parser
+ // and mavlink message definitions. However, until it's done, let's
+ // skip the lints.
+ #[allow(clippy::absurd_extreme_comparisons)]
+ #[allow(unused_comparisons)]
+ if __tmp.remaining() < Self::ENCODED_LEN {
+ panic!(
+ "buffer is too small (need {} bytes, but got {})",
+ Self::ENCODED_LEN,
+ __tmp.remaining(),
+ )
+ }
+
#(#ser_vars)*
- _tmp
+ if matches!(version, MavlinkVersion::V2) {
+ let len = __tmp.len();
+ ::mavlink_core::utils::remove_trailing_zeroes(&bytes[..len])
+ } else {
+ __tmp.len()
+ }
}
}
- fn emit_deserialize_vars(&self) -> Tokens {
+ fn emit_deserialize_vars(&self) -> TokenStream {
let deser_vars = self
.fields
.iter()
.map(|f| f.rust_reader())
- .collect::>();
-
- let encoded_len_name = Ident::from(format!("{}_DATA::ENCODED_LEN", self.name));
+ .collect::>();
if deser_vars.is_empty() {
// struct has no fields
@@ -607,42 +546,65 @@ impl MavMessage {
}
} else {
quote! {
- let avail_len = _input.len();
-
- // fast zero copy
- let mut buf = BytesMut::from(_input);
+ let avail_len = __input.len();
- // handle payload length truncuation due to empty fields
- if avail_len < #encoded_len_name {
+ let mut payload_buf = [0; Self::ENCODED_LEN];
+ let mut buf = if avail_len < Self::ENCODED_LEN {
//copy available bytes into an oversized buffer filled with zeros
- let mut payload_buf = [0; #encoded_len_name];
- payload_buf[0..avail_len].copy_from_slice(_input);
- buf = BytesMut::from(&payload_buf[..]);
- }
+ payload_buf[0..avail_len].copy_from_slice(__input);
+ Bytes::new(&payload_buf)
+ } else {
+ // fast zero copy
+ Bytes::new(__input)
+ };
- let mut _struct = Self::default();
+ let mut __struct = Self::default();
#(#deser_vars)*
- Ok(_struct)
+ Ok(__struct)
+ }
+ }
+ }
+
+ fn emit_default_impl(&self) -> TokenStream {
+ let msg_name = self.emit_struct_name();
+ quote! {
+ impl Default for #msg_name {
+ fn default() -> Self {
+ Self::DEFAULT.clone()
+ }
}
}
}
- fn emit_rust(&self) -> Tokens {
+ fn emit_const_default(&self) -> TokenStream {
+ let initializers = self
+ .fields
+ .iter()
+ .map(|field| field.emit_default_initializer());
+ quote!(pub const DEFAULT: Self = Self { #(#initializers)* };)
+ }
+
+ fn emit_rust(&self) -> TokenStream {
let msg_name = self.emit_struct_name();
+ let id = self.id;
+ let name = self.name.clone();
+ let extra_crc = extra_crc(self);
let (name_types, msg_encoded_len) = self.emit_name_types();
let deser_vars = self.emit_deserialize_vars();
let serialize_vars = self.emit_serialize_vars();
+ let const_default = self.emit_const_default();
+ let default_impl = self.emit_default_impl();
#[cfg(feature = "emit-description")]
let description = self.emit_description();
#[cfg(not(feature = "emit-description"))]
- let description = Ident::from("");
+ let description = quote!();
quote! {
#description
- #[derive(Debug, Clone, PartialEq, Default)]
+ #[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct #msg_name {
#(#name_types)*
@@ -650,12 +612,24 @@ impl MavMessage {
impl #msg_name {
pub const ENCODED_LEN: usize = #msg_encoded_len;
+ #const_default
+ }
+
+ #default_impl
- pub fn deser(version: MavlinkVersion, _input: &[u8]) -> Result {
+ impl MessageData for #msg_name {
+ type Message = MavMessage;
+
+ const ID: u32 = #id;
+ const NAME: &'static str = #name;
+ const EXTRA_CRC: u8 = #extra_crc;
+ const ENCODED_LEN: usize = #msg_encoded_len;
+
+ fn deser(_version: MavlinkVersion, __input: &[u8]) -> Result {
#deser_vars
}
- pub fn ser(&self) -> Vec {
+ fn ser(&self, version: MavlinkVersion, bytes: &mut [u8]) -> usize {
#serialize_vars
}
}
@@ -676,126 +650,134 @@ pub struct MavField {
impl MavField {
/// Emit rust name of a given field
- fn emit_name(&self) -> Tokens {
- let name = Ident::from(self.name.clone());
+ fn emit_name(&self) -> TokenStream {
+ let name = format_ident!("{}", self.name);
quote!(#name)
}
/// Emit rust type of the field
- fn emit_type(&self) -> Tokens {
+ fn emit_type(&self) -> TokenStream {
let mavtype;
- match self.mavtype {
- MavType::Array(_, _) => {
- mavtype = Ident::from(self.mavtype.rust_type());
- }
- _ => match self.enumtype {
- Some(ref enumname) => {
- mavtype = Ident::from(enumname.clone());
- }
- _ => {
- mavtype = Ident::from(self.mavtype.rust_type());
- }
- },
+ if matches!(self.mavtype, MavType::Array(_, _)) {
+ let rt = TokenStream::from_str(&self.mavtype.rust_type()).unwrap();
+ mavtype = quote!(#rt);
+ } else if let Some(enumname) = &self.enumtype {
+ let en = TokenStream::from_str(enumname).unwrap();
+ mavtype = quote!(#en);
+ } else {
+ let rt = TokenStream::from_str(&self.mavtype.rust_type()).unwrap();
+ mavtype = quote!(#rt);
}
- quote!(#mavtype)
+ mavtype
}
/// Generate description for the given field
#[cfg(feature = "emit-description")]
- fn emit_description(&self) -> Tokens {
- let mut desc = Vec::new();
+ fn emit_description(&self) -> TokenStream {
+ let mut ts = TokenStream::new();
if let Some(val) = self.description.clone() {
- desc.push(format!("\n/// {}.", val));
+ let desc = format!("{val}.");
+ ts.extend(quote!(#[doc = #desc]));
}
- desc.push("\n".to_string());
- let desc: String = desc.iter().map(|s| s.to_string()).collect();
- let desc = Ident::from(desc);
- quote!(#desc)
+ ts
}
/// Combine rust name and type of a given field
- fn emit_name_type(&self) -> Tokens {
+ fn emit_name_type(&self) -> TokenStream {
let name = self.emit_name();
let fieldtype = self.emit_type();
quote!(pub #name: #fieldtype,)
}
/// Emit writer
- fn rust_writer(&self) -> Tokens {
+ fn rust_writer(&self) -> TokenStream {
let mut name = "self.".to_string() + &self.name.clone();
- if let Some(_) = &self.enumtype {
- if let Some(dsp) = &self.display {
- // potentially a bitflag
- if dsp == "bitmask" {
- // it is a bitflag
- name += ".bits()";
- } else {
- panic!("Display option not implemented");
- }
- } else {
- match self.mavtype {
- MavType::Array(_, _) => {} // cast are not necessary for arrays
- _ => {
- // an enum, have to use "*foo as u8" cast
- name += " as ";
- name += &self.mavtype.rust_type();
+ if self.enumtype.is_some() {
+ // casts are not necessary for arrays, because they are currently
+ // generated as primitive arrays
+ if !matches!(self.mavtype, MavType::Array(_, _)) {
+ if let Some(dsp) = &self.display {
+ // potentially a bitflag
+ if dsp == "bitmask" {
+ // it is a bitflag
+ name += ".bits()";
+ } else {
+ panic!("Display option not implemented");
}
+ } else {
+ // an enum, have to use "*foo as u8" cast
+ name += " as ";
+ name += &self.mavtype.rust_type();
}
}
}
- let name = Ident::from(name);
- let buf = Ident::from("_tmp");
- self.mavtype.rust_writer(name, buf)
+ let ts = TokenStream::from_str(&name).unwrap();
+ let name = quote!(#ts);
+ let buf = format_ident!("__tmp");
+ self.mavtype.rust_writer(&name, buf)
}
/// Emit reader
- fn rust_reader(&self) -> Tokens {
- let name = Ident::from("_struct.".to_string() + &self.name.clone());
- let buf = Ident::from("buf");
+ fn rust_reader(&self) -> TokenStream {
+ let _name = TokenStream::from_str(&self.name).unwrap();
+
+ let name = quote!(__struct.#_name);
+ let buf = format_ident!("buf");
if let Some(enum_name) = &self.enumtype {
+ // TODO: handle enum arrays properly, rather than just generating
+ // primitive arrays
+ if let MavType::Array(_t, _size) = &self.mavtype {
+ return self.mavtype.rust_reader(&name, buf);
+ }
if let Some(dsp) = &self.display {
if dsp == "bitmask" {
// bitflags
- let tmp = self
- .mavtype
- .rust_reader(Ident::from("let tmp"), buf.clone());
- let enum_name_ident = Ident::from(enum_name.clone());
+ let tmp = self.mavtype.rust_reader("e!(let tmp), buf);
+ let enum_name_ident = format_ident!("{}", enum_name);
quote! {
#tmp
#name = #enum_name_ident::from_bits(tmp & #enum_name_ident::all().bits())
- .ok_or(ParserError::InvalidFlag { flag_type: #enum_name.to_string(), value: tmp as u32 })?;
+ .ok_or(::mavlink_core::error::ParserError::InvalidFlag { flag_type: #enum_name, value: tmp as u32 })?;
}
} else {
panic!("Display option not implemented");
}
} else {
- match &self.mavtype {
- MavType::Array(_t, _size) => {
- return self.mavtype.rust_reader(name, buf);
- }
- _ => {}
- }
// handle enum by FromPrimitive
- let tmp = self
- .mavtype
- .rust_reader(Ident::from("let tmp"), buf.clone());
- let val = Ident::from("from_".to_string() + &self.mavtype.rust_type());
+ let tmp = self.mavtype.rust_reader("e!(let tmp), buf);
+ let val = format_ident!("from_{}", &self.mavtype.rust_type());
quote!(
#tmp
#name = FromPrimitive::#val(tmp)
- .ok_or(ParserError::InvalidEnum { enum_type: #enum_name.to_string(), value: tmp as u32 })?;
+ .ok_or(::mavlink_core::error::ParserError::InvalidEnum { enum_type: #enum_name, value: tmp as u32 })?;
)
}
} else {
- self.mavtype.rust_reader(name, buf)
+ self.mavtype.rust_reader(&name, buf)
+ }
+ }
+
+ fn emit_default_initializer(&self) -> TokenStream {
+ let field = self.emit_name();
+ // FIXME: Is this actually expected behaviour??
+ if matches!(self.mavtype, MavType::Array(_, _)) {
+ let default_value = self.mavtype.emit_default_value();
+ quote!(#field: #default_value,)
+ } else if let Some(enumname) = &self.enumtype {
+ let ty = TokenStream::from_str(enumname).unwrap();
+ quote!(#field: #ty::DEFAULT,)
+ } else {
+ let default_value = self.mavtype.emit_default_value();
+ quote!(#field: #default_value,)
}
}
}
-#[derive(Debug, PartialEq, Clone)]
+#[derive(Debug, PartialEq, Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum MavType {
UInt8MavlinkVersion,
+ #[default]
UInt8,
UInt16,
UInt32,
@@ -810,14 +792,8 @@ pub enum MavType {
Array(Box, usize),
}
-impl Default for MavType {
- fn default() -> MavType {
- MavType::UInt8
- }
-}
-
impl MavType {
- fn parse_type(s: &str) -> Option {
+ fn parse_type(s: &str) -> Option {
use self::MavType::*;
match s {
"uint8_t_mavlink_version" => Some(UInt8MavlinkVersion),
@@ -834,10 +810,10 @@ impl MavType {
"Double" => Some(Double),
"double" => Some(Double),
_ => {
- if s.ends_with("]") {
- let start = s.find("[")?;
+ if s.ends_with(']') {
+ let start = s.find('[')?;
let size = s[start + 1..(s.len() - 1)].parse::().ok()?;
- let mtype = MavType::parse_type(&s[0..start])?;
+ let mtype = Self::parse_type(&s[0..start])?;
Some(Array(Box::new(mtype), size))
} else {
None
@@ -847,10 +823,10 @@ impl MavType {
}
/// Emit reader of a given type
- pub fn rust_reader(&self, val: Ident, buf: Ident) -> Tokens {
+ pub fn rust_reader(&self, val: &TokenStream, buf: Ident) -> TokenStream {
use self::MavType::*;
- match self.clone() {
- Char => quote! {#val = #buf.get_u8() as char;},
+ match self {
+ Char => quote! {#val = #buf.get_u8();},
UInt8 => quote! {#val = #buf.get_u8();},
UInt16 => quote! {#val = #buf.get_u16_le();},
UInt32 => quote! {#val = #buf.get_u32_le();},
@@ -862,24 +838,12 @@ impl MavType {
Int64 => quote! {#val = #buf.get_i64_le();},
Float => quote! {#val = #buf.get_f32_le();},
Double => quote! {#val = #buf.get_f64_le();},
- Array(t, size) => {
- if size > 32 {
- // it is a vector
- let r = t.rust_reader(Ident::from("let val"), buf.clone());
- quote! {
- for _ in 0..#size {
- #r
- #val.push(val);
- }
- }
- } else {
- // handle as a slice
- let r = t.rust_reader(Ident::from("let val"), buf.clone());
- quote! {
- for idx in 0..#size {
- #r
- #val[idx] = val;
- }
+ Array(t, _) => {
+ let r = t.rust_reader("e!(let val), buf);
+ quote! {
+ for v in &mut #val {
+ #r
+ *v = val;
}
}
}
@@ -887,12 +851,12 @@ impl MavType {
}
/// Emit writer of a given type
- pub fn rust_writer(&self, val: Ident, buf: Ident) -> Tokens {
+ pub fn rust_writer(&self, val: &TokenStream, buf: Ident) -> TokenStream {
use self::MavType::*;
- match self.clone() {
+ match self {
UInt8MavlinkVersion => quote! {#buf.put_u8(#val);},
UInt8 => quote! {#buf.put_u8(#val);},
- Char => quote! {#buf.put_u8(#val as u8);},
+ Char => quote! {#buf.put_u8(#val);},
UInt16 => quote! {#buf.put_u16_le(#val);},
UInt32 => quote! {#buf.put_u32_le(#val);},
Int8 => quote! {#buf.put_i8(#val);},
@@ -903,7 +867,7 @@ impl MavType {
Int64 => quote! {#buf.put_i64_le(#val);},
Double => quote! {#buf.put_f64_le(#val);},
Array(t, _size) => {
- let w = t.rust_writer(Ident::from("*val"), buf.clone());
+ let w = t.rust_writer("e!(*val), buf);
quote! {
for val in val {
#w
@@ -916,7 +880,7 @@ impl MavType {
/// Size of a given Mavtype
fn len(&self) -> usize {
use self::MavType::*;
- match self.clone() {
+ match self {
UInt8MavlinkVersion | UInt8 | Int8 | Char => 1,
UInt16 | Int16 => 2,
UInt32 | Int32 | Float => 4,
@@ -928,7 +892,7 @@ impl MavType {
/// Used for ordering of types
fn order_len(&self) -> usize {
use self::MavType::*;
- match self.clone() {
+ match self {
UInt8MavlinkVersion | UInt8 | Int8 | Char => 1,
UInt16 | Int16 => 2,
UInt32 | Int32 | Float => 4,
@@ -940,7 +904,7 @@ impl MavType {
/// Used for crc calculation
pub fn primitive_type(&self) -> String {
use self::MavType::*;
- match self.clone() {
+ match self {
UInt8MavlinkVersion => "uint8_t".into(),
UInt8 => "uint8_t".into(),
Int8 => "int8_t".into(),
@@ -961,10 +925,10 @@ impl MavType {
/// Used for generating struct fields.
pub fn rust_type(&self) -> String {
use self::MavType::*;
- match self.clone() {
+ match self {
UInt8 | UInt8MavlinkVersion => "u8".into(),
Int8 => "i8".into(),
- Char => "char".into(),
+ Char => "u8".into(),
UInt16 => "u16".into(),
Int16 => "i16".into(),
UInt32 => "u32".into(),
@@ -973,18 +937,42 @@ impl MavType {
UInt64 => "u64".into(),
Int64 => "i64".into(),
Double => "f64".into(),
- Array(t, size) => {
- if size > 32 {
- // we have to use a vector to make our lives easier
- format!("Vec<{}> /* {} elements */", t.rust_type(), size)
- } else {
- // we can use a slice, as Rust derives lot of thinsg for slices <= 32 elements
- format!("[{};{}]", t.rust_type(), size)
- }
+ Array(t, size) => format!("[{};{}]", t.rust_type(), size),
+ }
+ }
+
+ pub fn emit_default_value(&self) -> TokenStream {
+ use self::MavType::*;
+ match self {
+ UInt8 | UInt8MavlinkVersion => quote!(0_u8),
+ Int8 => quote!(0_i8),
+ Char => quote!(0_u8),
+ UInt16 => quote!(0_u16),
+ Int16 => quote!(0_i16),
+ UInt32 => quote!(0_u32),
+ Int32 => quote!(0_i32),
+ Float => quote!(0.0_f32),
+ UInt64 => quote!(0_u64),
+ Int64 => quote!(0_i64),
+ Double => quote!(0.0_f64),
+ Array(ty, size) => {
+ let default_value = ty.emit_default_value();
+ quote!([#default_value; #size])
}
}
}
+ /// Return rust equivalent of the primitive type of a MavType. The primitive
+ /// type is the type itself for all except arrays, in which case it is the
+ /// element type.
+ pub fn rust_primitive_type(&self) -> String {
+ use self::MavType::*;
+ match self {
+ Array(t, _) => t.rust_primitive_type(),
+ _ => self.rust_type(),
+ }
+ }
+
/// Compare two MavTypes
pub fn compare(&self, other: &Self) -> Ordering {
let len = self.order_len();
@@ -992,7 +980,7 @@ impl MavType {
}
}
-#[derive(Debug, PartialEq, Clone, Copy)]
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type"))]
pub enum MavXmlElement {
@@ -1013,24 +1001,24 @@ pub enum MavXmlElement {
Extensions,
}
-fn identify_element(s: &str) -> Option {
+const fn identify_element(s: &[u8]) -> Option {
use self::MavXmlElement::*;
match s {
- "version" => Some(Version),
- "mavlink" => Some(Mavlink),
- "dialect" => Some(Dialect),
- "include" => Some(Include),
- "enums" => Some(Enums),
- "enum" => Some(Enum),
- "entry" => Some(Entry),
- "description" => Some(Description),
- "param" => Some(Param),
- "messages" => Some(Messages),
- "message" => Some(Message),
- "field" => Some(Field),
- "deprecated" => Some(Deprecated),
- "wip" => Some(Wip),
- "extensions" => Some(Extensions),
+ b"version" => Some(Version),
+ b"mavlink" => Some(Mavlink),
+ b"dialect" => Some(Dialect),
+ b"include" => Some(Include),
+ b"enums" => Some(Enums),
+ b"enum" => Some(Enum),
+ b"entry" => Some(Entry),
+ b"description" => Some(Description),
+ b"param" => Some(Param),
+ b"messages" => Some(Messages),
+ b"message" => Some(Message),
+ b"field" => Some(Field),
+ b"deprecated" => Some(Deprecated),
+ b"wip" => Some(Wip),
+ b"extensions" => Some(Extensions),
_ => None,
}
}
@@ -1039,7 +1027,7 @@ fn is_valid_parent(p: Option, s: MavXmlElement) -> bool {
use self::MavXmlElement::*;
match s {
Version => p == Some(Mavlink),
- Mavlink => p == None,
+ Mavlink => p.is_none(),
Dialect => p == Some(Mavlink),
Include => p == Some(Mavlink),
Enums => p == Some(Mavlink),
@@ -1056,70 +1044,82 @@ fn is_valid_parent(p: Option, s: MavXmlElement) -> bool {
}
}
-pub fn parse_profile(file: &mut dyn Read) -> MavProfile {
- let mut stack: Vec = vec![];
+pub fn parse_profile(
+ definitions_dir: &Path,
+ definition_file: &Path,
+ parsed_files: &mut HashSet,
+) -> Result {
+ let in_path = Path::new(&definitions_dir).join(definition_file);
+ parsed_files.insert(in_path.clone()); // Keep track of which files have been parsed
- let mut profile = MavProfile {
- includes: vec![],
- messages: vec![],
- enums: vec![],
- };
+ let mut stack: Vec = vec![];
+ let mut profile = MavProfile::default();
let mut field = MavField::default();
let mut message = MavMessage::default();
let mut mavenum = MavEnum::default();
let mut entry = MavEnumEntry::default();
- let mut include = String::new();
+ let mut include = PathBuf::new();
let mut paramid: Option = None;
let mut xml_filter = MavXmlFilter::default();
- let mut parser: Vec> =
- EventReader::new(file).into_iter().collect();
- xml_filter.filter(&mut parser);
+ let mut events: Vec> = Vec::new();
+ let file = File::open(&in_path).map_err(|e| BindGenError::CouldNotReadDefinitionFile {
+ source: e,
+ path: in_path.clone(),
+ })?;
+ let mut reader = Reader::from_reader(BufReader::new(file));
+ reader.config_mut().trim_text(true);
+
+ let mut buf = Vec::new();
+ loop {
+ match reader.read_event_into(&mut buf) {
+ Ok(Event::Eof) => {
+ events.push(Ok(Event::Eof));
+ break;
+ }
+ Ok(event) => events.push(Ok(event.into_owned())),
+ Err(why) => events.push(Err(why)),
+ }
+ buf.clear();
+ }
+ xml_filter.filter(&mut events);
let mut is_in_extension = false;
- for e in parser {
+ for e in events {
match e {
- Ok(XmlEvent::StartElement {
- name,
- attributes: attrs,
- ..
- }) => {
- let id = match identify_element(&name.to_string()) {
- None => {
- panic!("unexpected element {:?}", name);
- }
- Some(kind) => kind,
+ Ok(Event::Start(bytes)) => {
+ let Some(id) = identify_element(bytes.name().into_inner()) else {
+ panic!(
+ "unexpected element {:?}",
+ String::from_utf8_lossy(bytes.name().into_inner())
+ );
};
- if !is_valid_parent(
- match stack.last().clone() {
- Some(arg) => Some(arg.clone()),
- None => None,
- },
- id.clone(),
- ) {
- panic!("not valid parent {:?} of {:?}", stack.last(), id);
- }
+ assert!(
+ is_valid_parent(stack.last().copied(), id),
+ "not valid parent {:?} of {id:?}",
+ stack.last(),
+ );
match id {
MavXmlElement::Extensions => {
is_in_extension = true;
}
MavXmlElement::Message => {
- message = Default::default();
+ message = MavMessage::default();
}
MavXmlElement::Field => {
- field = Default::default();
+ field = MavField::default();
field.is_extension = is_in_extension;
}
MavXmlElement::Enum => {
- mavenum = Default::default();
+ mavenum = MavEnum::default();
}
MavXmlElement::Entry => {
- entry = Default::default();
+ entry = MavEnumEntry::default();
}
MavXmlElement::Include => {
- include = Default::default();
+ include = PathBuf::default();
}
MavXmlElement::Param => {
paramid = None;
@@ -1129,51 +1129,35 @@ pub fn parse_profile(file: &mut dyn Read) -> MavProfile {
stack.push(id);
- for attr in attrs {
+ for attr in bytes.attributes() {
+ let attr = attr.unwrap();
match stack.last() {
- Some(&MavXmlElement::Enum) => match attr.name.local_name.clone().as_ref() {
- "name" => {
- mavenum.name = attr
- .value
- .clone()
- .split("_")
- .map(|x| x.to_lowercase())
- .map(|x| {
- let mut v: Vec = x.chars().collect();
- v[0] = v[0].to_uppercase().nth(0).unwrap();
- v.into_iter().collect()
- })
- .collect::>()
- .join("");
+ Some(&MavXmlElement::Enum) => {
+ if attr.key.into_inner() == b"name" {
+ mavenum.name = to_pascal_case(attr.value);
//mavenum.name = attr.value.clone();
}
- _ => (),
- },
+ }
Some(&MavXmlElement::Entry) => {
- match attr.name.local_name.clone().as_ref() {
- "name" => {
- entry.name = attr.value.clone();
+ match attr.key.into_inner() {
+ b"name" => {
+ entry.name = String::from_utf8_lossy(&attr.value).to_string();
}
- "value" => {
+ b"value" => {
+ let value = String::from_utf8_lossy(&attr.value);
// Deal with hexadecimal numbers
- if attr.value.starts_with("0x") {
- entry.value = Some(
- u32::from_str_radix(
- attr.value.trim_start_matches("0x"),
- 16,
- )
- .unwrap(),
- );
- } else {
- entry.value = Some(attr.value.parse::().unwrap());
- }
+ let (src, radix) = value
+ .strip_prefix("0x")
+ .map(|value| (value, 16))
+ .unwrap_or((value.as_ref(), 10));
+ entry.value = u32::from_str_radix(src, radix).ok();
}
_ => (),
}
}
Some(&MavXmlElement::Message) => {
- match attr.name.local_name.clone().as_ref() {
- "name" => {
+ match attr.key.into_inner() {
+ b"name" => {
/*message.name = attr
.value
.clone()
@@ -1187,108 +1171,124 @@ pub fn parse_profile(file: &mut dyn Read) -> MavProfile {
.collect::>()
.join("");
*/
- message.name = attr.value.clone();
+ message.name = String::from_utf8_lossy(&attr.value).to_string();
}
- "id" => {
- //message.id = attr.value.parse::().unwrap();
- message.id = attr.value.parse::().unwrap();
+ b"id" => {
+ message.id =
+ String::from_utf8_lossy(&attr.value).parse().unwrap();
}
_ => (),
}
}
Some(&MavXmlElement::Field) => {
- match attr.name.local_name.clone().as_ref() {
- "name" => {
- field.name = attr.value.clone();
- if field.name == "type" {
- field.name = "mavtype".to_string();
- }
+ match attr.key.into_inner() {
+ b"name" => {
+ let name = String::from_utf8_lossy(&attr.value);
+ field.name = if name == "type" {
+ "mavtype".to_string()
+ } else {
+ name.to_string()
+ };
}
- "type" => {
- field.mavtype = MavType::parse_type(&attr.value).unwrap();
+ b"type" => {
+ let r#type = String::from_utf8_lossy(&attr.value);
+ field.mavtype = MavType::parse_type(&r#type).unwrap();
}
- "enum" => {
- field.enumtype = Some(
- attr.value
- .clone()
- .split("_")
- .map(|x| x.to_lowercase())
- .map(|x| {
- let mut v: Vec = x.chars().collect();
- v[0] = v[0].to_uppercase().nth(0).unwrap();
- v.into_iter().collect()
- })
- .collect::>()
- .join(""),
- );
+ b"enum" => {
+ field.enumtype = Some(to_pascal_case(attr.value));
//field.enumtype = Some(attr.value.clone());
}
- "display" => {
- field.display = Some(attr.value);
+ b"display" => {
+ field.display =
+ Some(String::from_utf8_lossy(&attr.value).to_string());
}
_ => (),
}
}
Some(&MavXmlElement::Param) => {
- if let None = entry.params {
+ if entry.params.is_none() {
entry.params = Some(vec![]);
}
- match attr.name.local_name.clone().as_ref() {
- "index" => {
- paramid = Some(attr.value.parse::().unwrap());
- }
- _ => (),
+ if attr.key.into_inner() == b"index" {
+ paramid =
+ Some(String::from_utf8_lossy(&attr.value).parse().unwrap());
}
}
_ => (),
}
}
}
- Ok(XmlEvent::Characters(s)) => {
+ Ok(Event::Empty(bytes)) => match bytes.name().into_inner() {
+ b"extensions" => {
+ is_in_extension = true;
+ }
+ b"entry" => {
+ entry = MavEnumEntry::default();
+ for attr in bytes.attributes() {
+ let attr = attr.unwrap();
+ match attr.key.into_inner() {
+ b"name" => {
+ entry.name = String::from_utf8_lossy(&attr.value).to_string();
+ }
+ b"value" => {
+ entry.value =
+ Some(String::from_utf8_lossy(&attr.value).parse().unwrap());
+ }
+ _ => (),
+ }
+ }
+ mavenum.entries.push(entry.clone());
+ }
+ _ => (),
+ },
+ Ok(Event::Text(bytes)) => {
+ let s = String::from_utf8_lossy(&bytes).to_string();
+
use self::MavXmlElement::*;
match (stack.last(), stack.get(stack.len() - 2)) {
(Some(&Description), Some(&Message)) => {
- message.description = Some(s.replace("\n", " "));
+ message.description = Some(s.replace('\n', " "));
}
(Some(&Field), Some(&Message)) => {
- field.description = Some(s.replace("\n", " "));
+ field.description = Some(s.replace('\n', " "));
}
(Some(&Description), Some(&Enum)) => {
- mavenum.description = Some(s.replace("\n", " "));
+ mavenum.description = Some(s.replace('\n', " "));
}
(Some(&Description), Some(&Entry)) => {
- entry.description = Some(s.replace("\n", " "));
+ entry.description = Some(s.replace('\n', " "));
}
(Some(&Param), Some(&Entry)) => {
- if let Some(ref mut params) = entry.params {
+ if let Some(params) = entry.params.as_mut() {
// Some messages can jump between values, like:
// 0, 1, 2, 7
- if params.len() < paramid.unwrap() {
- for index in params.len()..paramid.unwrap() {
+ let paramid = paramid.unwrap();
+ if params.len() < paramid {
+ for index in params.len()..paramid {
params.insert(index, String::from("The use of this parameter (if any), must be defined in the requested message. By default assumed not used (0)."));
}
}
- params[paramid.unwrap() - 1] = s;
+ params[paramid - 1] = s;
}
}
(Some(&Include), Some(&Mavlink)) => {
- include = s.replace("\n", "");
+ include = PathBuf::from(s.replace('\n', ""));
}
(Some(&Version), Some(&Mavlink)) => {
- eprintln!("TODO: version {:?}", s);
+ eprintln!("TODO: version {s:?}");
}
(Some(&Dialect), Some(&Mavlink)) => {
- eprintln!("TODO: dialect {:?}", s);
+ eprintln!("TODO: dialect {s:?}");
}
(Some(Deprecated), _) => {
- eprintln!("TODO: deprecated {:?}", s);
+ eprintln!("TODO: deprecated {s:?}");
}
data => {
- panic!("unexpected text data {:?} reading {:?}", data, s);
+ panic!("unexpected text data {data:?} reading {s:?}");
}
}
}
- Ok(XmlEvent::EndElement { .. }) => {
+ Ok(Event::End(_)) => {
match stack.last() {
Some(&MavXmlElement::Field) => message.fields.push(field.clone()),
Some(&MavXmlElement::Entry) => {
@@ -1312,13 +1312,23 @@ pub fn parse_profile(file: &mut dyn Read) -> MavProfile {
msg.fields.extend(not_extension_fields);
msg.fields.extend(extension_fields);
- profile.messages.push(msg);
+ profile.add_message(&msg);
}
Some(&MavXmlElement::Enum) => {
- profile.enums.push(mavenum.clone());
+ profile.add_enum(&mavenum);
}
Some(&MavXmlElement::Include) => {
- profile.includes.push(include.clone());
+ let include_file = Path::new(&definitions_dir).join(include.clone());
+ if !parsed_files.contains(&include_file) {
+ let included_profile =
+ parse_profile(definitions_dir, &include, parsed_files)?;
+ for message in included_profile.messages.values() {
+ profile.add_message(message);
+ }
+ for enm in included_profile.enums.values() {
+ profile.add_enum(enm);
+ }
+ }
}
_ => (),
}
@@ -1326,7 +1336,7 @@ pub fn parse_profile(file: &mut dyn Read) -> MavProfile {
// println!("{}-{}", indent(depth), name);
}
Err(e) => {
- eprintln!("Error: {}", e);
+ eprintln!("Error: {e}");
break;
}
_ => {}
@@ -1334,17 +1344,24 @@ pub fn parse_profile(file: &mut dyn Read) -> MavProfile {
}
//let profile = profile.update_messages(); //TODO verify no longer needed
- profile.update_enums()
+ Ok(profile.update_enums())
}
/// Generate protobuf represenation of mavlink message set
/// Generate rust representation of mavlink message set with appropriate conversion methods
-pub fn generate(input: &mut R, output_rust: &mut W) {
- let profile = parse_profile(input);
+pub fn generate(
+ definitions_dir: &Path,
+ definition_file: &Path,
+ output_rust: &mut W,
+) -> Result<(), BindGenError> {
+ let mut parsed_files: HashSet = HashSet::new();
+ let profile = parse_profile(definitions_dir, definition_file, &mut parsed_files)?;
// rust file
let rust_tokens = profile.emit_rust();
- writeln!(output_rust, "{}", rust_tokens).unwrap();
+ writeln!(output_rust, "{rust_tokens}").unwrap();
+
+ Ok(())
}
/// CRC operates over names of the message and names of its fields
@@ -1358,7 +1375,7 @@ pub fn extra_crc(msg: &MavMessage) -> u8 {
let mut crc = CRCu16::crc16mcrf4cc();
crc.digest(msg.name.as_bytes());
- crc.digest(" ".as_bytes());
+ crc.digest(b" ");
let mut f = msg.fields.clone();
// only mavlink 1 fields should be part of the extra_crc
@@ -1366,13 +1383,13 @@ pub fn extra_crc(msg: &MavMessage) -> u8 {
f.sort_by(|a, b| a.mavtype.compare(&b.mavtype));
for field in &f {
crc.digest(field.mavtype.primitive_type().as_bytes());
- crc.digest(" ".as_bytes());
+ crc.digest(b" ");
if field.name == "mavtype" {
- crc.digest("type".as_bytes());
+ crc.digest(b"type");
} else {
crc.digest(field.name.as_bytes());
}
- crc.digest(" ".as_bytes());
+ crc.digest(b" ");
if let MavType::Array(_, size) = field.mavtype {
crc.digest(&[size as u8]);
}
@@ -1393,8 +1410,8 @@ struct MavXmlFilter {
}
impl Default for MavXmlFilter {
- fn default() -> MavXmlFilter {
- MavXmlFilter {
+ fn default() -> Self {
+ Self {
#[cfg(not(feature = "emit-extensions"))]
extension_filter: ExtensionFilter { is_in: false },
}
@@ -1402,62 +1419,66 @@ impl Default for MavXmlFilter {
}
impl MavXmlFilter {
- pub fn filter(&mut self, elements: &mut Vec>) {
+ pub fn filter(&mut self, elements: &mut Vec>) {
// List of filters
elements.retain(|x| self.filter_extension(x));
}
#[cfg(feature = "emit-extensions")]
- pub fn filter_extension(
- &mut self,
- _element: &Result,
- ) -> bool {
- return true;
+ pub fn filter_extension(&mut self, _element: &Result) -> bool {
+ true
}
/// Ignore extension fields
#[cfg(not(feature = "emit-extensions"))]
- pub fn filter_extension(
- &mut self,
- element: &Result,
- ) -> bool {
+ pub fn filter_extension(&mut self, element: &Result) -> bool {
match element {
Ok(content) => {
match content {
- XmlEvent::StartElement { name, .. } => {
- let id = match identify_element(&name.to_string()) {
- None => {
- panic!("unexpected element {:?}", name);
- }
- Some(kind) => kind,
+ Event::Start(bytes) | Event::Empty(bytes) => {
+ let Some(id) = identify_element(bytes.name().into_inner()) else {
+ panic!(
+ "unexpected element {:?}",
+ String::from_utf8_lossy(bytes.name().into_inner())
+ );
};
- match id {
- MavXmlElement::Extensions => {
- self.extension_filter.is_in = true;
- }
- _ => {}
+ if id == MavXmlElement::Extensions {
+ self.extension_filter.is_in = true;
}
}
- XmlEvent::EndElement { name } => {
- let id = match identify_element(&name.to_string()) {
- None => {
- panic!("unexpected element {:?}", name);
- }
- Some(kind) => kind,
+ Event::End(bytes) => {
+ let Some(id) = identify_element(bytes.name().into_inner()) else {
+ panic!(
+ "unexpected element {:?}",
+ String::from_utf8_lossy(bytes.name().into_inner())
+ );
};
- match id {
- MavXmlElement::Message => {
- self.extension_filter.is_in = false;
- }
- _ => {}
+ if id == MavXmlElement::Message {
+ self.extension_filter.is_in = false;
}
}
_ => {}
}
- return !self.extension_filter.is_in;
+ !self.extension_filter.is_in
}
- Err(error) => panic!("Failed to filter XML: {}", error),
+ Err(error) => panic!("Failed to filter XML: {error}"),
}
}
}
+
+fn to_pascal_case(text: impl AsRef<[u8]>) -> String {
+ text.as_ref()
+ .split(|c| *c == b'_')
+ .map(String::from_utf8_lossy)
+ .map(capitalize_word)
+ .collect()
+}
+
+fn capitalize_word(text: impl AsRef) -> String {
+ let mut chars = text.as_ref().chars();
+ match chars.next() {
+ None => String::new(),
+ Some(char) => char.to_uppercase().to_string() + &chars.as_str().to_ascii_lowercase(),
+ }
+}
diff --git a/build/util.rs b/mavlink-bindgen/src/util.rs
similarity index 100%
rename from build/util.rs
rename to mavlink-bindgen/src/util.rs
diff --git a/mavlink-core/Cargo.toml b/mavlink-core/Cargo.toml
new file mode 100644
index 0000000000..413572b61f
--- /dev/null
+++ b/mavlink-core/Cargo.toml
@@ -0,0 +1,50 @@
+[package]
+name = "mavlink-core"
+version = "0.14.0"
+authors = [
+ "Todd Stellanova",
+ "Michal Podhradsky",
+ "Kevin Mehall",
+ "Tim Ryan",
+ "Patrick José Pereira",
+ "Ibiyemi Abiodun",
+]
+description = "Implements the MAVLink data interchange format for UAVs."
+readme = "../README.md"
+license = "MIT/Apache-2.0"
+repository = "https://github.com/mavlink/rust-mavlink"
+edition.workspace = true
+rust-version.workspace = true
+
+[dependencies]
+crc-any = { workspace = true, default-features = false }
+byteorder = { workspace = true, default-features = false }
+nb = { version = "1.0", optional = true }
+embedded-hal-02 = { version = "0.2", optional = true, package = "embedded-hal" }
+embedded-io = { version = "0.6.1", optional = true }
+embedded-io-async = { version = "0.6.1", optional = true }
+serde = { version = "1.0.115", optional = true, features = ["derive"] }
+serde_arrays = { version = "0.1.0", optional = true }
+serial = { version = "0.4", optional = true }
+tokio = { version = "1.0", default-features = false, features = ["io-util", "net", "sync", "fs"], optional = true }
+sha2 = { version = "0.10", optional = true }
+async-trait = { version = "0.1.18", optional = true }
+tokio-serial = { version = "5.4.4", default-features = false, optional = true }
+
+[features]
+"std" = ["byteorder/std"]
+"udp" = []
+"tcp" = []
+"direct-serial" = ["serial"]
+# NOTE: Only one of 'embedded' and 'embedded-hal-02' features can be enabled.
+# Use "embedded' feature to enable embedded-hal=1.0 (embedded-io and embedded-io-async is part of embedded-hal).
+# Use 'embedded-hal-0.2' feature to enable deprecated embedded-hal=0.2.3 (some hals is not supports embedded-hal=1.0 yet).
+"embedded" = ["dep:embedded-io", "dep:embedded-io-async"]
+"embedded-hal-02" = ["dep:nb", "dep:embedded-hal-02"]
+"serde" = ["dep:serde", "dep:serde_arrays"]
+"tokio-1" = ["dep:tokio", "dep:async-trait", "dep:tokio-serial"]
+"signing" = ["dep:sha2"]
+default = ["std", "tcp", "udp", "direct-serial", "serde"]
+
+[dev-dependencies]
+tokio = { version = "1.0", default-features = false, features = ["io-util", "net", "sync", "fs", "macros", "rt"] }
\ No newline at end of file
diff --git a/mavlink-core/src/async_connection/direct_serial.rs b/mavlink-core/src/async_connection/direct_serial.rs
new file mode 100644
index 0000000000..d9b2fe1de2
--- /dev/null
+++ b/mavlink-core/src/async_connection/direct_serial.rs
@@ -0,0 +1,120 @@
+//! Async Serial MAVLINK connection
+
+use core::ops::DerefMut;
+use std::io;
+
+use tokio::sync::Mutex;
+use tokio_serial::{SerialPort, SerialPortBuilderExt, SerialStream};
+
+use crate::{async_peek_reader::AsyncPeekReader, MavHeader, MavlinkVersion, Message};
+
+#[cfg(not(feature = "signing"))]
+use crate::{read_versioned_msg_async, write_versioned_msg_async};
+#[cfg(feature = "signing")]
+use crate::{
+ read_versioned_msg_async_signed, write_versioned_msg_async_signed, SigningConfig, SigningData,
+};
+
+use super::AsyncMavConnection;
+
+pub fn open(settings: &str) -> io::Result {
+ let settings_toks: Vec<&str> = settings.split(':').collect();
+ if settings_toks.len() < 2 {
+ return Err(io::Error::new(
+ io::ErrorKind::AddrNotAvailable,
+ "Incomplete port settings",
+ ));
+ }
+
+ let Ok(baud) = settings_toks[1].parse::() else {
+ return Err(io::Error::new(
+ io::ErrorKind::AddrNotAvailable,
+ "Invalid baud rate",
+ ));
+ };
+
+ let port_name = settings_toks[0];
+ let mut port = tokio_serial::new(port_name, baud).open_native_async()?;
+ port.set_data_bits(tokio_serial::DataBits::Eight)?;
+ port.set_parity(tokio_serial::Parity::None)?;
+ port.set_stop_bits(tokio_serial::StopBits::One)?;
+ port.set_flow_control(tokio_serial::FlowControl::None)?;
+
+ Ok(AsyncSerialConnection {
+ port: Mutex::new(AsyncPeekReader::new(port)),
+ sequence: Mutex::new(0),
+ protocol_version: MavlinkVersion::V2,
+ #[cfg(feature = "signing")]
+ signing_data: None,
+ })
+}
+
+pub struct AsyncSerialConnection {
+ port: Mutex>,
+ sequence: Mutex,
+ protocol_version: MavlinkVersion,
+ #[cfg(feature = "signing")]
+ signing_data: Option,
+}
+
+#[async_trait::async_trait]
+impl AsyncMavConnection for AsyncSerialConnection {
+ async fn recv(&self) -> Result<(MavHeader, M), crate::error::MessageReadError> {
+ let mut port = self.port.lock().await;
+
+ #[cfg(not(feature = "signing"))]
+ let result = read_versioned_msg_async(port.deref_mut(), self.protocol_version).await;
+ #[cfg(feature = "signing")]
+ let result = read_versioned_msg_async_signed(
+ port.deref_mut(),
+ self.protocol_version,
+ self.signing_data.as_ref(),
+ )
+ .await;
+ result
+ }
+
+ async fn send(
+ &self,
+ header: &MavHeader,
+ data: &M,
+ ) -> Result {
+ let mut port = self.port.lock().await;
+ let mut sequence = self.sequence.lock().await;
+
+ let header = MavHeader {
+ sequence: *sequence,
+ system_id: header.system_id,
+ component_id: header.component_id,
+ };
+
+ *sequence = sequence.wrapping_add(1);
+
+ #[cfg(not(feature = "signing"))]
+ let result =
+ write_versioned_msg_async(port.reader_mut(), self.protocol_version, header, data).await;
+ #[cfg(feature = "signing")]
+ let result = write_versioned_msg_async_signed(
+ port.reader_mut(),
+ self.protocol_version,
+ header,
+ data,
+ self.signing_data.as_ref(),
+ )
+ .await;
+ result
+ }
+
+ fn set_protocol_version(&mut self, version: MavlinkVersion) {
+ self.protocol_version = version;
+ }
+
+ fn get_protocol_version(&self) -> MavlinkVersion {
+ self.protocol_version
+ }
+
+ #[cfg(feature = "signing")]
+ fn setup_signing(&mut self, signing_data: Option) {
+ self.signing_data = signing_data.map(SigningData::from_config)
+ }
+}
diff --git a/mavlink-core/src/async_connection/file.rs b/mavlink-core/src/async_connection/file.rs
new file mode 100644
index 0000000000..5dae56d99e
--- /dev/null
+++ b/mavlink-core/src/async_connection/file.rs
@@ -0,0 +1,83 @@
+//! Async File MAVLINK connection
+
+use core::ops::DerefMut;
+
+use super::AsyncMavConnection;
+use crate::error::{MessageReadError, MessageWriteError};
+
+use crate::{async_peek_reader::AsyncPeekReader, MavHeader, MavlinkVersion, Message};
+
+use tokio::fs::File;
+use tokio::io;
+use tokio::sync::Mutex;
+
+#[cfg(not(feature = "signing"))]
+use crate::read_versioned_msg_async;
+
+#[cfg(feature = "signing")]
+use crate::{read_versioned_msg_async_signed, SigningConfig, SigningData};
+
+pub async fn open(file_path: &str) -> io::Result {
+ let file = File::open(file_path).await?;
+ Ok(AsyncFileConnection {
+ file: Mutex::new(AsyncPeekReader::new(file)),
+ protocol_version: MavlinkVersion::V2,
+ #[cfg(feature = "signing")]
+ signing_data: None,
+ })
+}
+
+pub struct AsyncFileConnection {
+ file: Mutex>,
+ protocol_version: MavlinkVersion,
+
+ #[cfg(feature = "signing")]
+ signing_data: Option,
+}
+
+#[async_trait::async_trait]
+impl AsyncMavConnection for AsyncFileConnection {
+ async fn recv(&self) -> Result<(MavHeader, M), crate::error::MessageReadError> {
+ let mut file = self.file.lock().await;
+
+ loop {
+ #[cfg(not(feature = "signing"))]
+ let result = read_versioned_msg_async(file.deref_mut(), self.protocol_version).await;
+ #[cfg(feature = "signing")]
+ let result = read_versioned_msg_async_signed(
+ file.deref_mut(),
+ self.protocol_version,
+ self.signing_data.as_ref(),
+ )
+ .await;
+ match result {
+ ok @ Ok(..) => {
+ return ok;
+ }
+ Err(MessageReadError::Io(e)) => {
+ if e.kind() == io::ErrorKind::UnexpectedEof {
+ return Err(MessageReadError::Io(e));
+ }
+ }
+ _ => {}
+ }
+ }
+ }
+
+ async fn send(&self, _header: &MavHeader, _data: &M) -> Result {
+ Ok(0)
+ }
+
+ fn set_protocol_version(&mut self, version: MavlinkVersion) {
+ self.protocol_version = version;
+ }
+
+ fn get_protocol_version(&self) -> MavlinkVersion {
+ self.protocol_version
+ }
+
+ #[cfg(feature = "signing")]
+ fn setup_signing(&mut self, signing_data: Option) {
+ self.signing_data = signing_data.map(SigningData::from_config)
+ }
+}
diff --git a/mavlink-core/src/async_connection/mod.rs b/mavlink-core/src/async_connection/mod.rs
new file mode 100644
index 0000000000..b2eb5c6757
--- /dev/null
+++ b/mavlink-core/src/async_connection/mod.rs
@@ -0,0 +1,137 @@
+use tokio::io;
+
+use crate::{MavFrame, MavHeader, MavlinkVersion, Message};
+
+#[cfg(feature = "tcp")]
+mod tcp;
+
+#[cfg(feature = "udp")]
+mod udp;
+
+#[cfg(feature = "direct-serial")]
+mod direct_serial;
+
+mod file;
+
+#[cfg(feature = "signing")]
+use crate::SigningConfig;
+
+/// An async MAVLink connection
+#[async_trait::async_trait]
+pub trait AsyncMavConnection {
+ /// Receive a mavlink message.
+ ///
+ /// Yield until a valid frame is received, ignoring invalid messages.
+ async fn recv(&self) -> Result<(MavHeader, M), crate::error::MessageReadError>;
+
+ /// Send a mavlink message
+ async fn send(
+ &self,
+ header: &MavHeader,
+ data: &M,
+ ) -> Result;
+
+ fn set_protocol_version(&mut self, version: MavlinkVersion);
+ fn get_protocol_version(&self) -> MavlinkVersion;
+
+ /// Write whole frame
+ async fn send_frame(
+ &self,
+ frame: &MavFrame,
+ ) -> Result {
+ self.send(&frame.header, &frame.msg).await
+ }
+
+ /// Read whole frame
+ async fn recv_frame(&self) -> Result, crate::error::MessageReadError> {
+ let (header, msg) = self.recv().await?;
+ let protocol_version = self.get_protocol_version();
+ Ok(MavFrame {
+ header,
+ msg,
+ protocol_version,
+ })
+ }
+
+ /// Send a message with default header
+ async fn send_default(&self, data: &M) -> Result {
+ let header = MavHeader::default();
+ self.send(&header, data).await
+ }
+
+ /// Setup secret key used for message signing, or disable message signing
+ #[cfg(feature = "signing")]
+ fn setup_signing(&mut self, signing_data: Option);
+}
+
+/// Connect asynchronously to a MAVLink node by address string.
+///
+/// The address must be in one of the following formats:
+///
+/// * `tcpin::` to create a TCP server, listening for incoming connections
+/// * `tcpout::` to create a TCP client
+/// * `udpin::` to create a UDP server, listening for incoming packets
+/// * `udpout::` to create a UDP client
+/// * `udpbcast::` to create a UDP broadcast
+/// * `serial::` to create a serial connection
+/// * `file:` to extract file data
+///
+/// The type of the connection is determined at runtime based on the address type, so the
+/// connection is returned as a trait object.
+pub async fn connect_async(
+ address: &str,
+) -> io::Result + Sync + Send>> {
+ let protocol_err = Err(io::Error::new(
+ io::ErrorKind::AddrNotAvailable,
+ "Protocol unsupported",
+ ));
+
+ if cfg!(feature = "tcp") && address.starts_with("tcp") {
+ #[cfg(feature = "tcp")]
+ {
+ tcp::select_protocol(address).await
+ }
+ #[cfg(not(feature = "tcp"))]
+ {
+ protocol_err
+ }
+ } else if cfg!(feature = "udp") && address.starts_with("udp") {
+ #[cfg(feature = "udp")]
+ {
+ udp::select_protocol(address).await
+ }
+ #[cfg(not(feature = "udp"))]
+ {
+ protocol_err
+ }
+ } else if cfg!(feature = "direct-serial") && address.starts_with("serial") {
+ #[cfg(feature = "direct-serial")]
+ {
+ Ok(Box::new(direct_serial::open(&address["serial:".len()..])?))
+ }
+ #[cfg(not(feature = "direct-serial"))]
+ {
+ protocol_err
+ }
+ } else if address.starts_with("file") {
+ Ok(Box::new(file::open(&address["file:".len()..]).await?))
+ } else {
+ protocol_err
+ }
+}
+
+/// Returns the socket address for the given address.
+pub(crate) fn get_socket_addr(
+ address: T,
+) -> Result {
+ let addr = match address.to_socket_addrs()?.next() {
+ Some(addr) => addr,
+ None => {
+ return Err(io::Error::new(
+ io::ErrorKind::Other,
+ "Host address lookup failed",
+ ));
+ }
+ };
+ Ok(addr)
+}
diff --git a/mavlink-core/src/async_connection/tcp.rs b/mavlink-core/src/async_connection/tcp.rs
new file mode 100644
index 0000000000..4cd1f9ac9a
--- /dev/null
+++ b/mavlink-core/src/async_connection/tcp.rs
@@ -0,0 +1,156 @@
+//! Async TCP MAVLink connection
+
+use super::{get_socket_addr, AsyncMavConnection};
+use crate::async_peek_reader::AsyncPeekReader;
+use crate::{MavHeader, MavlinkVersion, Message};
+
+use core::ops::DerefMut;
+use tokio::io;
+use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
+use tokio::net::{TcpListener, TcpStream};
+use tokio::sync::Mutex;
+
+#[cfg(not(feature = "signing"))]
+use crate::{read_versioned_msg_async, write_versioned_msg_async};
+#[cfg(feature = "signing")]
+use crate::{
+ read_versioned_msg_async_signed, write_versioned_msg_async_signed, SigningConfig, SigningData,
+};
+
+pub async fn select_protocol(
+ address: &str,
+) -> io::Result + Sync + Send>> {
+ let connection = if let Some(address) = address.strip_prefix("tcpout:") {
+ tcpout(address).await
+ } else if let Some(address) = address.strip_prefix("tcpin:") {
+ tcpin(address).await
+ } else {
+ Err(io::Error::new(
+ io::ErrorKind::AddrNotAvailable,
+ "Protocol unsupported",
+ ))
+ };
+
+ Ok(Box::new(connection?))
+}
+
+pub async fn tcpout(address: T) -> io::Result {
+ let addr = get_socket_addr(address)?;
+
+ let socket = TcpStream::connect(addr).await?;
+
+ let (reader, writer) = socket.into_split();
+
+ Ok(AsyncTcpConnection {
+ reader: Mutex::new(AsyncPeekReader::new(reader)),
+ writer: Mutex::new(TcpWrite {
+ socket: writer,
+ sequence: 0,
+ }),
+ protocol_version: MavlinkVersion::V2,
+ #[cfg(feature = "signing")]
+ signing_data: None,
+ })
+}
+
+pub async fn tcpin(address: T) -> io::Result {
+ let addr = get_socket_addr(address)?;
+ let listener = TcpListener::bind(addr).await?;
+
+ //For now we only accept one incoming stream: this yields until we get one
+ match listener.accept().await {
+ Ok((socket, _)) => {
+ let (reader, writer) = socket.into_split();
+ return Ok(AsyncTcpConnection {
+ reader: Mutex::new(AsyncPeekReader::new(reader)),
+ writer: Mutex::new(TcpWrite {
+ socket: writer,
+ sequence: 0,
+ }),
+ protocol_version: MavlinkVersion::V2,
+ #[cfg(feature = "signing")]
+ signing_data: None,
+ });
+ }
+ Err(e) => {
+ //TODO don't println in lib
+ println!("listener err: {e}");
+ }
+ }
+ Err(io::Error::new(
+ io::ErrorKind::NotConnected,
+ "No incoming connections!",
+ ))
+}
+
+pub struct AsyncTcpConnection {
+ reader: Mutex>,
+ writer: Mutex,
+ protocol_version: MavlinkVersion,
+ #[cfg(feature = "signing")]
+ signing_data: Option,
+}
+
+struct TcpWrite {
+ socket: OwnedWriteHalf,
+ sequence: u8,
+}
+
+#[async_trait::async_trait]
+impl AsyncMavConnection for AsyncTcpConnection {
+ async fn recv(&self) -> Result<(MavHeader, M), crate::error::MessageReadError> {
+ let mut reader = self.reader.lock().await;
+ #[cfg(not(feature = "signing"))]
+ let result = read_versioned_msg_async(reader.deref_mut(), self.protocol_version).await;
+ #[cfg(feature = "signing")]
+ let result = read_versioned_msg_async_signed(
+ reader.deref_mut(),
+ self.protocol_version,
+ self.signing_data.as_ref(),
+ )
+ .await;
+ result
+ }
+
+ async fn send(
+ &self,
+ header: &MavHeader,
+ data: &M,
+ ) -> Result {
+ let mut lock = self.writer.lock().await;
+
+ let header = MavHeader {
+ sequence: lock.sequence,
+ system_id: header.system_id,
+ component_id: header.component_id,
+ };
+
+ lock.sequence = lock.sequence.wrapping_add(1);
+ #[cfg(not(feature = "signing"))]
+ let result =
+ write_versioned_msg_async(&mut lock.socket, self.protocol_version, header, data).await;
+ #[cfg(feature = "signing")]
+ let result = write_versioned_msg_async_signed(
+ &mut lock.socket,
+ self.protocol_version,
+ header,
+ data,
+ self.signing_data.as_ref(),
+ )
+ .await;
+ result
+ }
+
+ fn set_protocol_version(&mut self, version: MavlinkVersion) {
+ self.protocol_version = version;
+ }
+
+ fn get_protocol_version(&self) -> MavlinkVersion {
+ self.protocol_version
+ }
+
+ #[cfg(feature = "signing")]
+ fn setup_signing(&mut self, signing_data: Option) {
+ self.signing_data = signing_data.map(SigningData::from_config)
+ }
+}
diff --git a/mavlink-core/src/async_connection/udp.rs b/mavlink-core/src/async_connection/udp.rs
new file mode 100644
index 0000000000..3f06b83e1d
--- /dev/null
+++ b/mavlink-core/src/async_connection/udp.rs
@@ -0,0 +1,279 @@
+//! Async UDP MAVLink connection
+
+use core::{ops::DerefMut, task::Poll};
+use std::{collections::VecDeque, io::Read, sync::Arc};
+
+use tokio::{
+ io::{self, AsyncRead, ReadBuf},
+ net::UdpSocket,
+ sync::Mutex,
+};
+
+use crate::{async_peek_reader::AsyncPeekReader, MavHeader, MavlinkVersion, Message};
+
+use super::{get_socket_addr, AsyncMavConnection};
+
+#[cfg(not(feature = "signing"))]
+use crate::{read_versioned_msg_async, write_versioned_msg_async};
+#[cfg(feature = "signing")]
+use crate::{
+ read_versioned_msg_async_signed, write_versioned_msg_signed, SigningConfig, SigningData,
+};
+
+pub async fn select_protocol(
+ address: &str,
+) -> io::Result + Sync + Send>> {
+ let connection = if let Some(address) = address.strip_prefix("udpin:") {
+ udpin(address).await
+ } else if let Some(address) = address.strip_prefix("udpout:") {
+ udpout(address).await
+ } else if let Some(address) = address.strip_prefix("udpbcast:") {
+ udpbcast(address).await
+ } else {
+ Err(io::Error::new(
+ io::ErrorKind::AddrNotAvailable,
+ "Protocol unsupported",
+ ))
+ };
+
+ Ok(Box::new(connection?))
+}
+
+pub async fn udpbcast(address: T) -> io::Result {
+ let addr = get_socket_addr(address)?;
+ let socket = UdpSocket::bind("0.0.0.0:0").await?;
+ socket
+ .set_broadcast(true)
+ .expect("Couldn't bind to broadcast address.");
+ AsyncUdpConnection::new(socket, false, Some(addr))
+}
+
+pub async fn udpout(address: T) -> io::Result {
+ let addr = get_socket_addr(address)?;
+ let socket = UdpSocket::bind("0.0.0.0:0").await?;
+ AsyncUdpConnection::new(socket, false, Some(addr))
+}
+
+pub async fn udpin(address: T) -> io::Result {
+ let addr = address
+ .to_socket_addrs()
+ .unwrap()
+ .next()
+ .expect("Invalid address");
+ let socket = UdpSocket::bind(addr).await?;
+ AsyncUdpConnection::new(socket, true, None)
+}
+
+struct UdpRead {
+ socket: Arc,
+ buffer: VecDeque,
+ last_recv_address: Option,
+}
+
+const MTU_SIZE: usize = 1500;
+impl AsyncRead for UdpRead {
+ fn poll_read(
+ mut self: core::pin::Pin<&mut Self>,
+ cx: &mut core::task::Context<'_>,
+ buf: &mut io::ReadBuf<'_>,
+ ) -> Poll> {
+ if self.buffer.is_empty() {
+ let mut read_buffer = [0u8; MTU_SIZE];
+ let mut read_buffer = ReadBuf::new(&mut read_buffer);
+
+ match self.socket.poll_recv_from(cx, &mut read_buffer) {
+ Poll::Ready(Ok(address)) => {
+ let n_buffer = read_buffer.filled().len();
+
+ let n = (&read_buffer.filled()[0..n_buffer]).read(buf.initialize_unfilled())?;
+ buf.advance(n);
+
+ self.buffer.extend(&read_buffer.filled()[n..n_buffer]);
+ self.last_recv_address = Some(address);
+ Poll::Ready(Ok(()))
+ }
+ Poll::Ready(Err(err)) => Poll::Ready(Err(err)),
+ Poll::Pending => Poll::Pending,
+ }
+ } else {
+ let read_result = self.buffer.read(buf.initialize_unfilled());
+ let result = match read_result {
+ Ok(n) => {
+ buf.advance(n);
+ Ok(())
+ }
+ Err(err) => Err(err),
+ };
+ Poll::Ready(result)
+ }
+ }
+}
+
+struct UdpWrite {
+ socket: Arc,
+ dest: Option,
+ sequence: u8,
+}
+
+pub struct AsyncUdpConnection {
+ reader: Mutex>,
+ writer: Mutex,
+ protocol_version: MavlinkVersion,
+ server: bool,
+ #[cfg(feature = "signing")]
+ signing_data: Option,
+}
+
+impl AsyncUdpConnection {
+ fn new(
+ socket: UdpSocket,
+ server: bool,
+ dest: Option,
+ ) -> io::Result {
+ let socket = Arc::new(socket);
+ Ok(Self {
+ server,
+ reader: Mutex::new(AsyncPeekReader::new(UdpRead {
+ socket: socket.clone(),
+ buffer: VecDeque::new(),
+ last_recv_address: None,
+ })),
+ writer: Mutex::new(UdpWrite {
+ socket,
+ dest,
+ sequence: 0,
+ }),
+ protocol_version: MavlinkVersion::V2,
+ #[cfg(feature = "signing")]
+ signing_data: None,
+ })
+ }
+}
+
+#[async_trait::async_trait]
+impl AsyncMavConnection for AsyncUdpConnection {
+ async fn recv(&self) -> Result<(MavHeader, M), crate::error::MessageReadError> {
+ let mut reader = self.reader.lock().await;
+
+ loop {
+ #[cfg(not(feature = "signing"))]
+ let result = read_versioned_msg_async(reader.deref_mut(), self.protocol_version).await;
+ #[cfg(feature = "signing")]
+ let result = read_versioned_msg_async_signed(
+ reader.deref_mut(),
+ self.protocol_version,
+ self.signing_data.as_ref(),
+ )
+ .await;
+ if self.server {
+ if let addr @ Some(_) = reader.reader_ref().last_recv_address {
+ self.writer.lock().await.dest = addr;
+ }
+ }
+ if let ok @ Ok(..) = result {
+ return ok;
+ }
+ }
+ }
+
+ async fn send(
+ &self,
+ header: &MavHeader,
+ data: &M,
+ ) -> Result {
+ let mut guard = self.writer.lock().await;
+ let state = &mut *guard;
+
+ let header = MavHeader {
+ sequence: state.sequence,
+ system_id: header.system_id,
+ component_id: header.component_id,
+ };
+
+ state.sequence = state.sequence.wrapping_add(1);
+
+ let len = if let Some(addr) = state.dest {
+ let mut buf = Vec::new();
+ #[cfg(not(feature = "signing"))]
+ write_versioned_msg_async(
+ &mut buf,
+ self.protocol_version,
+ header,
+ data,
+ #[cfg(feature = "signing")]
+ self.signing_data.as_ref(),
+ )
+ .await?;
+ #[cfg(feature = "signing")]
+ write_versioned_msg_signed(
+ &mut buf,
+ self.protocol_version,
+ header,
+ data,
+ #[cfg(feature = "signing")]
+ self.signing_data.as_ref(),
+ )?;
+ state.socket.send_to(&buf, addr).await?
+ } else {
+ 0
+ };
+
+ Ok(len)
+ }
+
+ fn set_protocol_version(&mut self, version: MavlinkVersion) {
+ self.protocol_version = version;
+ }
+
+ fn get_protocol_version(&self) -> MavlinkVersion {
+ self.protocol_version
+ }
+
+ #[cfg(feature = "signing")]
+ fn setup_signing(&mut self, signing_data: Option) {
+ self.signing_data = signing_data.map(SigningData::from_config)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use io::AsyncReadExt;
+
+ #[tokio::test]
+ async fn test_datagram_buffering() {
+ let receiver_socket = Arc::new(UdpSocket::bind("127.0.0.1:5001").await.unwrap());
+ let mut udp_reader = UdpRead {
+ socket: receiver_socket.clone(),
+ buffer: VecDeque::new(),
+ last_recv_address: None,
+ };
+ let sender_socket = UdpSocket::bind("0.0.0.0:0").await.unwrap();
+ sender_socket.connect("127.0.0.1:5001").await.unwrap();
+
+ let datagram: Vec = (0..50).collect::>();
+
+ let mut n_sent = sender_socket.send(&datagram).await.unwrap();
+ assert_eq!(n_sent, datagram.len());
+ n_sent = sender_socket.send(&datagram).await.unwrap();
+ assert_eq!(n_sent, datagram.len());
+
+ let mut buf = [0u8; 30];
+
+ let mut n_read = udp_reader.read(&mut buf).await.unwrap();
+ assert_eq!(n_read, 30);
+ assert_eq!(&buf[0..n_read], (0..30).collect::>().as_slice());
+
+ n_read = udp_reader.read(&mut buf).await.unwrap();
+ assert_eq!(n_read, 20);
+ assert_eq!(&buf[0..n_read], (30..50).collect::>().as_slice());
+
+ n_read = udp_reader.read(&mut buf).await.unwrap();
+ assert_eq!(n_read, 30);
+ assert_eq!(&buf[0..n_read], (0..30).collect::>().as_slice());
+
+ n_read = udp_reader.read(&mut buf).await.unwrap();
+ assert_eq!(n_read, 20);
+ assert_eq!(&buf[0..n_read], (30..50).collect::>().as_slice());
+ }
+}
diff --git a/mavlink-core/src/async_peek_reader.rs b/mavlink-core/src/async_peek_reader.rs
new file mode 100644
index 0000000000..1d6ada2c4b
--- /dev/null
+++ b/mavlink-core/src/async_peek_reader.rs
@@ -0,0 +1,147 @@
+//! This module implements a buffered/peekable reader using async I/O.
+//!
+//! The purpose of the buffered/peekable reader is to allow for backtracking parsers.
+//!
+//! This is the async version of [`crate::peek_reader::PeekReader`].
+//! A reader implementing the tokio library's [`tokio::io::AsyncBufRead`]/[`tokio::io::AsyncBufReadExt`] traits seems like a good fit, but
+//! it does not allow for peeking a specific number of bytes, so it provides no way to request
+//! more data from the underlying reader without consuming the existing data.
+//!
+//! This API still tries to adhere to the [`tokio::io::AsyncBufRead`]'s trait philosophy.
+//!
+//! The main type [`AsyncPeekReader`] does not implement [`tokio::io::AsyncBufReadExt`] itself, as there is no added benefit
+//! in doing so.
+//!
+use tokio::io::AsyncReadExt;
+
+use crate::error::MessageReadError;
+
+/// A buffered/peekable reader
+///
+/// This reader wraps a type implementing [`tokio::io::AsyncRead`] and adds buffering via an internal buffer.
+///
+/// It allows the user to `peek` a specified number of bytes (without consuming them),
+/// to `read` bytes (consuming them), or to `consume` them after `peek`ing.
+///
+/// NOTE: This reader is generic over the size of the buffer, defaulting to MAVLink's current largest
+/// possible message size of 280 bytes
+///
+pub struct AsyncPeekReader {
+ // Internal buffer
+ buffer: [u8; BUFFER_SIZE],
+ // The position of the next byte to read from the buffer.
+ cursor: usize,
+ // The position of the next byte to read into the buffer.
+ top: usize,
+ // The wrapped reader.
+ reader: R,
+}
+
+impl AsyncPeekReader {
+ /// Instantiates a new [`AsyncPeekReader`], wrapping the provided [`tokio::io::AsyncReadExt`] and using the default chunk size
+ pub fn new(reader: R) -> Self {
+ Self {
+ buffer: [0; BUFFER_SIZE],
+ cursor: 0,
+ top: 0,
+ reader,
+ }
+ }
+
+ /// Peeks an exact amount of bytes from the internal buffer
+ ///
+ /// If the internal buffer does not contain enough data, this function will read
+ /// from the underlying [`tokio::io::AsyncReadExt`] until it does, an error occurs or no more data can be read (EOF).
+ ///
+ /// If an EOF occurs and the specified amount could not be read, this function will return an [`ErrorKind::UnexpectedEof`].
+ ///
+ /// This function does not consume data from the buffer, so subsequent calls to `peek` or `read` functions
+ /// will still return the peeked data.
+ ///
+ pub async fn peek_exact(&mut self, amount: usize) -> Result<&[u8], MessageReadError> {
+ self.fetch(amount, false).await
+ }
+
+ /// Reads a specified amount of bytes from the internal buffer
+ ///
+ /// If the internal buffer does not contain enough data, this function will read
+ /// from the underlying [`tokio::io::AsyncReadExt`] until it does, an error occurs or no more data can be read (EOF).
+ ///
+ /// If an EOF occurs and the specified amount could not be read, this function will return an [`ErrorKind::UnexpectedEof`].
+ ///
+ /// This function consumes the data from the buffer, unless an error occurs, in which case no data is consumed.
+ ///
+ pub async fn read_exact(&mut self, amount: usize) -> Result<&[u8], MessageReadError> {
+ self.fetch(amount, true).await
+ }
+
+ /// Reads a byte from the internal buffer
+ ///
+ /// If the internal buffer does not contain enough data, this function will read
+ /// from the underlying [`tokio::io::AsyncReadExt`] until it does, an error occurs or no more data can be read (EOF).
+ ///
+ /// If an EOF occurs and the specified amount could not be read, this function will return an [`ErrorKind::UnexpectedEof`].
+ ///
+ /// This function consumes the data from the buffer, unless an error occurs, in which case no data is consumed.
+ ///
+ pub async fn read_u8(&mut self) -> Result {
+ let buf = self.read_exact(1).await?;
+ Ok(buf[0])
+ }
+
+ /// Consumes a specified amount of bytes from the buffer
+ ///
+ /// If the internal buffer does not contain enough data, this function will consume as much data as is buffered.
+ ///
+ pub fn consume(&mut self, amount: usize) -> usize {
+ let amount = amount.min(self.top - self.cursor);
+ self.cursor += amount;
+ amount
+ }
+
+ /// Returns an immutable reference to the underlying [`tokio::io::AsyncRead`]
+ ///
+ /// Reading directly from the underlying stream will cause data loss
+ pub fn reader_ref(&mut self) -> &R {
+ &self.reader
+ }
+
+ /// Returns a mutable reference to the underlying [`tokio::io::AsyncRead`]
+ ///
+ /// Reading directly from the underlying stream will cause data loss
+ pub fn reader_mut(&mut self) -> &mut R {
+ &mut self.reader
+ }
+
+ /// Internal function to fetch data from the internal buffer and/or reader
+ async fn fetch(&mut self, amount: usize, consume: bool) -> Result<&[u8], MessageReadError> {
+ let buffered = self.top - self.cursor;
+
+ // the caller requested more bytes than we have buffered, fetch them from the reader
+ if buffered < amount {
+ let bytes_read = amount - buffered;
+ assert!(bytes_read < BUFFER_SIZE);
+ let mut buf = [0u8; BUFFER_SIZE];
+
+ // read needed bytes from reader
+ self.reader.read_exact(&mut buf[..bytes_read]).await?;
+
+ // if some bytes were read, add them to the buffer
+
+ if self.buffer.len() - self.top < bytes_read {
+ // reallocate
+ self.buffer.copy_within(self.cursor..self.top, 0);
+ self.cursor = 0;
+ self.top = buffered;
+ }
+ self.buffer[self.top..self.top + bytes_read].copy_from_slice(&buf[..bytes_read]);
+ self.top += bytes_read;
+ }
+
+ let result = &self.buffer[self.cursor..self.cursor + amount];
+ if consume {
+ self.cursor += amount;
+ }
+ Ok(result)
+ }
+}
diff --git a/mavlink-core/src/bytes.rs b/mavlink-core/src/bytes.rs
new file mode 100644
index 0000000000..e1b11ff433
--- /dev/null
+++ b/mavlink-core/src/bytes.rs
@@ -0,0 +1,134 @@
+pub struct Bytes<'a> {
+ data: &'a [u8],
+ pos: usize,
+}
+
+impl<'a> Bytes<'a> {
+ pub fn new(data: &'a [u8]) -> Self {
+ Self { data, pos: 0 }
+ }
+
+ #[inline]
+ fn remaining(&self) -> usize {
+ self.data.len() - self.pos
+ }
+
+ #[inline]
+ pub fn remaining_bytes(&self) -> &'a [u8] {
+ &self.data[self.pos..]
+ }
+
+ #[inline]
+ fn check_remaining(&self, count: usize) {
+ assert!(
+ self.remaining() >= count,
+ "read buffer exhausted; remaining {} bytes, try read {count} bytes",
+ self.remaining(),
+ );
+ }
+
+ #[inline]
+ pub fn get_bytes(&mut self, count: usize) -> &[u8] {
+ self.check_remaining(count);
+
+ let bytes = &self.data[self.pos..(self.pos + count)];
+ self.pos += count;
+ bytes
+ }
+
+ #[inline]
+ pub fn get_array(&mut self) -> [u8; SIZE] {
+ let bytes = self.get_bytes(SIZE);
+ let mut arr = [0u8; SIZE];
+
+ arr.copy_from_slice(bytes);
+
+ debug_assert_eq!(arr.as_slice(), bytes);
+
+ arr
+ }
+
+ #[inline]
+ pub fn get_u8(&mut self) -> u8 {
+ self.check_remaining(1);
+
+ let val = self.data[self.pos];
+ self.pos += 1;
+ val
+ }
+
+ #[inline]
+ pub fn get_i8(&mut self) -> i8 {
+ self.check_remaining(1);
+
+ let val = self.data[self.pos] as i8;
+ self.pos += 1;
+ val
+ }
+
+ #[inline]
+ pub fn get_u16_le(&mut self) -> u16 {
+ u16::from_le_bytes(self.get_array())
+ }
+
+ #[inline]
+ pub fn get_i16_le(&mut self) -> i16 {
+ i16::from_le_bytes(self.get_array())
+ }
+
+ #[inline]
+ pub fn get_u24_le(&mut self) -> u32 {
+ const SIZE: usize = 3;
+ self.check_remaining(SIZE);
+
+ let mut val = [0u8; SIZE + 1];
+ val[..3].copy_from_slice(&self.data[self.pos..self.pos + SIZE]);
+ self.pos += SIZE;
+
+ debug_assert_eq!(val[3], 0);
+ u32::from_le_bytes(val)
+ }
+
+ #[inline]
+ pub fn get_i24_le(&mut self) -> i32 {
+ const SIZE: usize = 3;
+ self.check_remaining(SIZE);
+
+ let mut val = [0u8; SIZE + 1];
+ val[..3].copy_from_slice(&self.data[self.pos..self.pos + SIZE]);
+ self.pos += SIZE;
+
+ debug_assert_eq!(val[3], 0);
+ i32::from_le_bytes(val)
+ }
+
+ #[inline]
+ pub fn get_u32_le(&mut self) -> u32 {
+ u32::from_le_bytes(self.get_array())
+ }
+
+ #[inline]
+ pub fn get_i32_le(&mut self) -> i32 {
+ i32::from_le_bytes(self.get_array())
+ }
+
+ #[inline]
+ pub fn get_u64_le(&mut self) -> u64 {
+ u64::from_le_bytes(self.get_array())
+ }
+
+ #[inline]
+ pub fn get_i64_le(&mut self) -> i64 {
+ i64::from_le_bytes(self.get_array())
+ }
+
+ #[inline]
+ pub fn get_f32_le(&mut self) -> f32 {
+ f32::from_le_bytes(self.get_array())
+ }
+
+ #[inline]
+ pub fn get_f64_le(&mut self) -> f64 {
+ f64::from_le_bytes(self.get_array())
+ }
+}
diff --git a/mavlink-core/src/bytes_mut.rs b/mavlink-core/src/bytes_mut.rs
new file mode 100644
index 0000000000..df607c83c8
--- /dev/null
+++ b/mavlink-core/src/bytes_mut.rs
@@ -0,0 +1,178 @@
+pub struct BytesMut<'a> {
+ data: &'a mut [u8],
+ len: usize,
+}
+
+impl<'a> BytesMut<'a> {
+ pub fn new(data: &'a mut [u8]) -> Self {
+ Self { data, len: 0 }
+ }
+
+ #[inline]
+ pub fn len(&self) -> usize {
+ self.len
+ }
+
+ #[inline]
+ pub fn is_empty(&self) -> bool {
+ self.len() == 0
+ }
+
+ #[inline]
+ pub fn remaining(&self) -> usize {
+ self.data.len() - self.len
+ }
+
+ #[inline]
+ fn check_remaining(&self, count: usize) {
+ assert!(
+ self.remaining() >= count,
+ "write buffer overflow; remaining {} bytes, try add {count} bytes",
+ self.remaining(),
+ );
+ }
+
+ #[inline]
+ pub fn put_slice(&mut self, src: &[u8]) {
+ self.check_remaining(src.len());
+
+ unsafe {
+ core::ptr::copy_nonoverlapping(src.as_ptr(), &mut self.data[self.len], src.len());
+ }
+ self.len += src.len();
+ }
+
+ #[inline]
+ pub fn put_u8(&mut self, val: u8) {
+ self.check_remaining(1);
+
+ self.data[self.len] = val;
+ self.len += 1;
+ }
+
+ #[inline]
+ pub fn put_i8(&mut self, val: i8) {
+ self.check_remaining(1);
+
+ self.data[self.len] = val as u8;
+ self.len += 1;
+ }
+
+ #[inline]
+ pub fn put_u16_le(&mut self, val: u16) {
+ const SIZE: usize = core::mem::size_of::();
+ self.check_remaining(SIZE);
+
+ let src = val.to_le_bytes();
+ self.data[self.len..self.len + SIZE].copy_from_slice(&src[..]);
+ self.len += SIZE;
+ }
+
+ #[inline]
+ pub fn put_i16_le(&mut self, val: i16) {
+ const SIZE: usize = core::mem::size_of::();
+ self.check_remaining(SIZE);
+
+ let src = val.to_le_bytes();
+ self.data[self.len..self.len + SIZE].copy_from_slice(&src[..]);
+ self.len += SIZE;
+ }
+
+ #[inline]
+ pub fn put_u24_le(&mut self, val: u32) {
+ const SIZE: usize = 3;
+ const MAX: u32 = 2u32.pow(24) - 1;
+
+ assert!(
+ val <= MAX,
+ "Attempted to put value that is too large for 24 bits, \
+ attempted to push: {val}, max allowed: {MAX}",
+ );
+
+ let src = val.to_le_bytes();
+ self.data[self.len..self.len + SIZE].copy_from_slice(&src[..3]);
+ self.len += SIZE;
+ }
+
+ #[inline]
+ pub fn put_i24_le(&mut self, val: i32) {
+ const SIZE: usize = 3;
+ const MIN: i32 = 2i32.pow(23);
+ const MAX: i32 = 2i32.pow(23) - 1;
+
+ assert!(
+ val <= MAX,
+ "Attempted to put value that is too large for 24 bits, \
+ attempted to push: {val}, max allowed: {MAX}",
+ );
+ assert!(
+ val >= MIN,
+ "Attempted to put value that is too negative for 24 bits, \
+ attempted to push: {val}, min allowed: {MIN}",
+ );
+
+ let src = val.to_le_bytes();
+ self.data[self.len..self.len + SIZE].copy_from_slice(&src[..3]);
+ self.len += SIZE;
+ }
+
+ #[inline]
+ pub fn put_u32_le(&mut self, val: u32) {
+ const SIZE: usize = core::mem::size_of::();
+ self.check_remaining(SIZE);
+
+ let src = val.to_le_bytes();
+ self.data[self.len..self.len + SIZE].copy_from_slice(&src[..]);
+ self.len += SIZE;
+ }
+
+ #[inline]
+ pub fn put_i32_le(&mut self, val: i32) {
+ const SIZE: usize = core::mem::size_of::();
+ self.check_remaining(SIZE);
+
+ let src = val.to_le_bytes();
+ self.data[self.len..self.len + SIZE].copy_from_slice(&src[..]);
+ self.len += SIZE;
+ }
+
+ #[inline]
+ pub fn put_u64_le(&mut self, val: u64) {
+ const SIZE: usize = core::mem::size_of::();
+ self.check_remaining(SIZE);
+
+ let src = val.to_le_bytes();
+ self.data[self.len..self.len + SIZE].copy_from_slice(&src[..]);
+ self.len += SIZE;
+ }
+
+ #[inline]
+ pub fn put_i64_le(&mut self, val: i64) {
+ const SIZE: usize = core::mem::size_of::();
+ self.check_remaining(SIZE);
+
+ let src = val.to_le_bytes();
+ self.data[self.len..self.len + SIZE].copy_from_slice(&src[..]);
+ self.len += SIZE;
+ }
+
+ #[inline]
+ pub fn put_f32_le(&mut self, val: f32) {
+ const SIZE: usize = core::mem::size_of::();
+ self.check_remaining(SIZE);
+
+ let src = val.to_le_bytes();
+ self.data[self.len..self.len + SIZE].copy_from_slice(&src[..]);
+ self.len += SIZE;
+ }
+
+ #[inline]
+ pub fn put_f64_le(&mut self, val: f64) {
+ const SIZE: usize = core::mem::size_of::();
+ self.check_remaining(SIZE);
+
+ let src = val.to_le_bytes();
+ self.data[self.len..self.len + SIZE].copy_from_slice(&src[..]);
+ self.len += SIZE;
+ }
+}
diff --git a/src/connection/direct_serial.rs b/mavlink-core/src/connection/direct_serial.rs
similarity index 54%
rename from src/connection/direct_serial.rs
rename to mavlink-core/src/connection/direct_serial.rs
index ab20df006d..2954b98107 100644
--- a/src/connection/direct_serial.rs
+++ b/mavlink-core/src/connection/direct_serial.rs
@@ -1,18 +1,22 @@
-extern crate serial;
+//! Serial MAVLINK connection
use crate::connection::MavConnection;
-use crate::{read_versioned_msg, write_versioned_msg, MavHeader, MavlinkVersion, Message};
-use std::io::{self};
+use crate::peek_reader::PeekReader;
+use crate::{MavHeader, MavlinkVersion, Message};
+use core::ops::DerefMut;
+use std::io;
use std::sync::Mutex;
-//TODO why is this import so hairy?
-use crate::connection::direct_serial::serial::prelude::*;
use crate::error::{MessageReadError, MessageWriteError};
+use serial::{prelude::*, SystemPort};
-/// Serial MAVLINK connection
+#[cfg(not(feature = "signing"))]
+use crate::{read_versioned_msg, write_versioned_msg};
+#[cfg(feature = "signing")]
+use crate::{read_versioned_msg_signed, write_versioned_msg_signed, SigningConfig, SigningData};
pub fn open(settings: &str) -> io::Result {
- let settings_toks: Vec<&str> = settings.split(":").collect();
+ let settings_toks: Vec<&str> = settings.split(':').collect();
if settings_toks.len() < 2 {
return Err(io::Error::new(
io::ErrorKind::AddrNotAvailable,
@@ -20,15 +24,15 @@ pub fn open(settings: &str) -> io::Result {
));
}
- let baud_opt = settings_toks[1].parse::();
- if baud_opt.is_err() {
+ let Ok(baud) = settings_toks[1]
+ .parse::()
+ .map(serial::core::BaudRate::from_speed)
+ else {
return Err(io::Error::new(
io::ErrorKind::AddrNotAvailable,
"Invalid baud rate",
));
- }
-
- let baud = serial::core::BaudRate::from_speed(baud_opt.unwrap());
+ };
let settings = serial::core::PortSettings {
baud_rate: baud,
@@ -43,24 +47,35 @@ pub fn open(settings: &str) -> io::Result {
port.configure(&settings)?;
Ok(SerialConnection {
- port: Mutex::new(port),
+ port: Mutex::new(PeekReader::new(port)),
sequence: Mutex::new(0),
protocol_version: MavlinkVersion::V2,
+ #[cfg(feature = "signing")]
+ signing_data: None,
})
}
pub struct SerialConnection {
- port: Mutex,
+ port: Mutex>,
sequence: Mutex,
protocol_version: MavlinkVersion,
+ #[cfg(feature = "signing")]
+ signing_data: Option,
}
impl MavConnection for SerialConnection {
fn recv(&self) -> Result<(MavHeader, M), MessageReadError> {
let mut port = self.port.lock().unwrap();
-
loop {
- match read_versioned_msg(&mut *port, self.protocol_version) {
+ #[cfg(not(feature = "signing"))]
+ let result = read_versioned_msg(port.deref_mut(), self.protocol_version);
+ #[cfg(feature = "signing")]
+ let result = read_versioned_msg_signed(
+ port.deref_mut(),
+ self.protocol_version,
+ self.signing_data.as_ref(),
+ );
+ match result {
ok @ Ok(..) => {
return ok;
}
@@ -86,14 +101,29 @@ impl MavConnection for SerialConnection {
*sequence = sequence.wrapping_add(1);
- write_versioned_msg(&mut *port, self.protocol_version, header, data)
+ #[cfg(not(feature = "signing"))]
+ let result = write_versioned_msg(port.reader_mut(), self.protocol_version, header, data);
+ #[cfg(feature = "signing")]
+ let result = write_versioned_msg_signed(
+ port.reader_mut(),
+ self.protocol_version,
+ header,
+ data,
+ self.signing_data.as_ref(),
+ );
+ result
}
fn set_protocol_version(&mut self, version: MavlinkVersion) {
self.protocol_version = version;
}
- fn get_protocol_version(&self) -> MavlinkVersion {
+ fn protocol_version(&self) -> MavlinkVersion {
self.protocol_version
}
+
+ #[cfg(feature = "signing")]
+ fn setup_signing(&mut self, signing_data: Option) {
+ self.signing_data = signing_data.map(SigningData::from_config)
+ }
}
diff --git a/src/connection/file.rs b/mavlink-core/src/connection/file.rs
similarity index 51%
rename from src/connection/file.rs
rename to mavlink-core/src/connection/file.rs
index 777f1b280c..7258259347 100644
--- a/src/connection/file.rs
+++ b/mavlink-core/src/connection/file.rs
@@ -1,27 +1,35 @@
+//! File MAVLINK connection
+
use crate::connection::MavConnection;
use crate::error::{MessageReadError, MessageWriteError};
-use crate::{read_versioned_msg, MavHeader, MavlinkVersion, Message};
+use crate::peek_reader::PeekReader;
+use crate::{MavHeader, MavlinkVersion, Message};
+use core::ops::DerefMut;
use std::fs::File;
-use std::io::{self};
+use std::io;
use std::sync::Mutex;
-/// File MAVLINK connection
+#[cfg(not(feature = "signing"))]
+use crate::read_versioned_msg;
+#[cfg(feature = "signing")]
+use crate::{read_versioned_msg_signed, SigningConfig, SigningData};
pub fn open(file_path: &str) -> io::Result {
- let file = match File::open(&file_path) {
- Err(e) => return Err(e),
- Ok(file) => file,
- };
+ let file = File::open(file_path)?;
Ok(FileConnection {
- file: Mutex::new(file),
+ file: Mutex::new(PeekReader::new(file)),
protocol_version: MavlinkVersion::V2,
+ #[cfg(feature = "signing")]
+ signing_data: None,
})
}
pub struct FileConnection {
- file: Mutex,
+ file: Mutex>,
protocol_version: MavlinkVersion,
+ #[cfg(feature = "signing")]
+ signing_data: Option,
}
impl MavConnection for FileConnection {
@@ -31,7 +39,15 @@ impl MavConnection for FileConnection {
let mut file = self.file.lock().unwrap();
loop {
- match read_versioned_msg(&mut *file, self.protocol_version) {
+ #[cfg(not(feature = "signing"))]
+ let result = read_versioned_msg(file.deref_mut(), self.protocol_version);
+ #[cfg(feature = "signing")]
+ let result = read_versioned_msg_signed(
+ file.deref_mut(),
+ self.protocol_version,
+ self.signing_data.as_ref(),
+ );
+ match result {
ok @ Ok(..) => {
return ok;
}
@@ -53,7 +69,12 @@ impl MavConnection for FileConnection {
self.protocol_version = version;
}
- fn get_protocol_version(&self) -> MavlinkVersion {
+ fn protocol_version(&self) -> MavlinkVersion {
self.protocol_version
}
+
+ #[cfg(feature = "signing")]
+ fn setup_signing(&mut self, signing_data: Option) {
+ self.signing_data = signing_data.map(SigningData::from_config)
+ }
}
diff --git a/src/connection/mod.rs b/mavlink-core/src/connection/mod.rs
similarity index 82%
rename from src/connection/mod.rs
rename to mavlink-core/src/connection/mod.rs
index 10dacbe976..e60386a6aa 100644
--- a/src/connection/mod.rs
+++ b/mavlink-core/src/connection/mod.rs
@@ -11,6 +11,9 @@ mod udp;
#[cfg(feature = "direct-serial")]
mod direct_serial;
+#[cfg(feature = "signing")]
+use crate::SigningConfig;
+
mod file;
/// A MAVLink connection
@@ -24,7 +27,7 @@ pub trait MavConnection {
fn send(&self, header: &MavHeader, data: &M) -> Result;
fn set_protocol_version(&mut self, version: MavlinkVersion);
- fn get_protocol_version(&self) -> MavlinkVersion;
+ fn protocol_version(&self) -> MavlinkVersion;
/// Write whole frame
fn send_frame(&self, frame: &MavFrame) -> Result {
@@ -34,7 +37,7 @@ pub trait MavConnection {
/// Read whole frame
fn recv_frame(&self) -> Result, crate::error::MessageReadError> {
let (header, msg) = self.recv()?;
- let protocol_version = self.get_protocol_version();
+ let protocol_version = self.protocol_version();
Ok(MavFrame {
header,
msg,
@@ -47,6 +50,10 @@ pub trait MavConnection {
let header = MavHeader::default();
self.send(&header, data)
}
+
+ /// Setup secret key used for message signing, or disable message signing
+ #[cfg(feature = "signing")]
+ fn setup_signing(&mut self, signing_data: Option);
}
/// Connect to a MAVLink node by address string.
@@ -102,3 +109,13 @@ pub fn connect(address: &str) -> io::Result
protocol_err
}
}
+
+/// Returns the socket address for the given address.
+pub(crate) fn get_socket_addr(
+ address: T,
+) -> Result {
+ address.to_socket_addrs()?.next().ok_or(io::Error::new(
+ io::ErrorKind::Other,
+ "Host address lookup failed",
+ ))
+}
diff --git a/mavlink-core/src/connection/tcp.rs b/mavlink-core/src/connection/tcp.rs
new file mode 100644
index 0000000000..d362a5664a
--- /dev/null
+++ b/mavlink-core/src/connection/tcp.rs
@@ -0,0 +1,149 @@
+//! TCP MAVLink connection
+
+use crate::connection::MavConnection;
+use crate::peek_reader::PeekReader;
+use crate::{MavHeader, MavlinkVersion, Message};
+use core::ops::DerefMut;
+use std::io;
+use std::net::ToSocketAddrs;
+use std::net::{TcpListener, TcpStream};
+use std::sync::Mutex;
+use std::time::Duration;
+
+use super::get_socket_addr;
+
+#[cfg(not(feature = "signing"))]
+use crate::{read_versioned_msg, write_versioned_msg};
+
+#[cfg(feature = "signing")]
+use crate::{read_versioned_msg_signed, write_versioned_msg_signed, SigningConfig, SigningData};
+
+pub fn select_protocol(
+ address: &str,
+) -> io::Result + Sync + Send>> {
+ let connection = if let Some(address) = address.strip_prefix("tcpout:") {
+ tcpout(address)
+ } else if let Some(address) = address.strip_prefix("tcpin:") {
+ tcpin(address)
+ } else {
+ Err(io::Error::new(
+ io::ErrorKind::AddrNotAvailable,
+ "Protocol unsupported",
+ ))
+ };
+
+ Ok(Box::new(connection?))
+}
+
+pub fn tcpout(address: T) -> io::Result