├── .github ├── dependabot.yml └── workflows │ ├── coverage.yml │ └── main.yml ├── .gitignore ├── .rustfmt.toml ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-MIT ├── README.md ├── asterix-derive ├── Cargo.toml └── src │ └── lib.rs ├── src ├── custom_read_write.rs ├── data_item.rs ├── fourty_eight.rs ├── fspec.rs ├── lib.rs ├── modifier.rs ├── thirty_four.rs └── types.rs └── tests └── public_api.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: daily 7 | 8 | - package-ecosystem: cargo 9 | directory: / 10 | schedule: 11 | interval: daily 12 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: Coverage 2 | 3 | on: [pull_request, push] 4 | 5 | permissions: 6 | contents: read 7 | 8 | jobs: 9 | coverage: 10 | runs-on: ubuntu-latest 11 | env: 12 | CARGO_TERM_COLOR: always 13 | steps: 14 | - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 15 | - name: Install Rust 16 | run: rustup update stable 17 | - name: Install cargo-llvm-cov 18 | uses: taiki-e/install-action@f23140dab9d53af716ede5c5e74661a2a8de049c # cargo-llvm-cov 19 | - name: Generate code coverage 20 | run: cargo llvm-cov --workspace --codecov --output-path codecov.json 21 | - name: Upload coverage to Codecov 22 | uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # v3.1.4 23 | with: 24 | files: codecov.json 25 | fail_ci_if_error: true 26 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | pull_request: 6 | branches: 7 | - master 8 | schedule: [cron: "40 1 * * *"] 9 | 10 | name: ci 11 | 12 | jobs: 13 | # build, test all supported targets 14 | build-test-stable: 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | targets: 19 | - x86_64-unknown-linux-gnu 20 | toolchain: 21 | - stable 22 | # msrv 23 | - 1.70.0 24 | 25 | steps: 26 | - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 27 | - uses: dtolnay/rust-toolchain@be73d7920c329f220ce78e0234b8f96b7ae60248 # master 28 | with: 29 | toolchain: ${{ matrix.toolchain }} 30 | target: ${{ matrix.targets }} 31 | - uses: Swatinem/rust-cache@3cf7f8cc28d1b4e7d01e3783be10a97d55d483c8 # v2.7.1 32 | - run: cargo build --workspace --target ${{ matrix.targets }} 33 | - run: cargo test --workspace --target ${{ matrix.targets }} 34 | 35 | # fmt and clippy on stable builds 36 | fmt-clippy-stable: 37 | runs-on: ubuntu-latest 38 | 39 | steps: 40 | - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 41 | - uses: dtolnay/rust-toolchain@be73d7920c329f220ce78e0234b8f96b7ae60248 # master 42 | with: 43 | toolchain: stable 44 | target: x86_64-unknown-linux-gnu 45 | components: rustfmt, clippy 46 | - uses: Swatinem/rust-cache@3cf7f8cc28d1b4e7d01e3783be10a97d55d483c8 # v2.7.1 47 | - run: cargo fmt --all --check 48 | - run: cargo clippy --workspace -- -D warnings 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | 16 | # Added info from idea editor 17 | .idea/ 18 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | use_small_heuristics = "Max" 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [0.4.0] - 2024-02-09 10 | - Add support for the following 48/030 packets: 11 | - `WrongDFReplyFormatDetected` 12 | - `TransponderAnomalyMs` 13 | - `TransponderAnomalySI` 14 | - `PotentialICConflict` 15 | - `ICConflictDetectionPossible` 16 | - `AMGAllocated(u16)` 17 | - `Manufacture(u16)` 18 | 19 | ## [0.3.1] - 2023-10-14 20 | - Update deku to 0.16 21 | - Update syn to 2.0 22 | - Improve use of syn NestedMeta for loading fspec 23 | 24 | ## [0.3.0] - 2022-11-22 25 | - Update deku to 0.15.0 26 | - Improve deku usage 27 | - [deku-derive] improve rust AST parsing 28 | - use assert_hex for better testing panic display 29 | 30 | ## [0.2.6] - 2020-01-13 31 | - Update deku to 0.10.0 32 | - Use deku attributes for loading fspec 33 | - Other code improvements 34 | 35 | ## [0.2.5] - 2020-11-06 36 | - Update/Use deku 0.9.1 37 | 38 | ## [0.2.4] - 2020-10-03 39 | - Update lib.rs example 40 | - Add CI 41 | 42 | ## [0.2.3] - 2020-10-03 43 | - Update deku: 0.8.0. Gives some speed improvements with less allocation for writing. 44 | 45 | ## [0.2.2] - 2020-09-27 46 | - Introduce asterix-derive(UpdateFspec): Automatic update of Fspec from data_items generation. 47 | - Cat34: Complete 48 | 49 | ## [0.2.1] - 2020-08-30 50 | - Cat48: Add TrackQuality 51 | - Cat48: Add WarningErrorConditionsTargetClass 52 | - Cat48: Add Mode3ACodeConfidenceIndicator 53 | - Cat48: Add ModeCCodeAndConfidenceIndicator 54 | - Cat48: Add HeightMeasuredBy3dRadar 55 | - Cat48: Add RadialDopplerSpeed 56 | - Cat48: Add ACASResolutionAdvisoryReport 57 | - Cat48: Add Mode1CodeOctalRepresentation 58 | - Cat48: Add Mode2CodeOctalRepresentation 59 | - Cat48: Add Mode1CodeConfidenceIndicator 60 | - Cat48: Add Mode2CodeConfidenceIndicator 61 | - Cat34: Add AntennaRotationSpeed 62 | - Update Data Item docs 63 | 64 | ## [0.2.0] - 2020-08-21 65 | - add AsterixPacket::finalize() for updating the packet to the correct fspec and len after 66 | messages are added 67 | 68 | ## [0.1.1] - 2020-08-16 69 | - Add License file (MIT) and prepare Cargo.toml file for release 70 | 71 | ## [0.1.0] - 2020-08-16 72 | - Initial Release for most of CAT048 and CAT034 73 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "asterix" 3 | version = "0.4.0" 4 | authors = ["wcampbell"] 5 | edition = "2021" 6 | categories = ["encoding", "parsing"] 7 | keywords = ["asterix", "radar", "aviation", "network", "protocol"] 8 | license = "MIT" 9 | readme = "README.md" 10 | description = "Encode/Decode for ASTERIX protocol using the deku library" 11 | repository = "https://github.com/wcampbell0x2a/asterix-rs" 12 | rust-version = "1.70.0" 13 | 14 | [workspace] 15 | members = [ 16 | "asterix-derive", 17 | ] 18 | 19 | [dependencies] 20 | deku = "0.16" 21 | asterix-derive = { version = "^0.4", path = "asterix-derive" } 22 | assert_hex = "0.4" 23 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Asterix Encode/Decode Library 2 | 3 | [github](https://github.com/wcampbell0x2a/asterix-rs) 4 | [crates.io](https://crates.io/crates/asterix) 5 | [docs.rs](https://docs.rs/asterix) 6 | [build status](https://github.com/wcampbell0x2a/asterix-rs/actions?query=branch%3Amaster) 7 | [Codecov](https://app.codecov.io/gh/wcampbell0x2a/asterix-rs) 8 | 9 | Currently supported: 10 | - CAT048 11 | - CAT034 12 | 13 | ## Usage 14 | *Compiler support: requires rustc 1.70+* 15 | 16 | Add the following to your `Cargo.toml` file: 17 | ```toml 18 | [dependencies] 19 | asterix = "v0.4.0" 20 | ``` 21 | 22 | ## Changelog 23 | 24 | See [CHANGELOG.md](https://github.com/wcampbell0x2a/asterix-rs/blob/master/CHANGELOG.md) 25 | -------------------------------------------------------------------------------- /asterix-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "asterix-derive" 3 | version = "0.4.0" 4 | authors = ["wcampbell "] 5 | edition = "2021" 6 | description = "proc-macro convenience with the deku library, for updating ASTERIX FSPECs" 7 | readme = "../README.md" 8 | license = "MIT" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | syn = {version = "2.0", features = ["full"]}#, features = ["extra-traits"]} 14 | quote = "1.0" 15 | proc-macro2 = "1.0" 16 | 17 | [lib] 18 | proc-macro = true 19 | -------------------------------------------------------------------------------- /asterix-derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use quote::quote; 3 | use syn::{parse_macro_input, Data, DeriveInput, Fields}; 4 | 5 | /// Generate syntax for updating the fspec from the deku style syntax that would be found 6 | /// in a ASTERIX category 7 | /// 8 | /// This is mostly a `hack` in the true sense of the word. Although it works pretty well for 9 | /// the well defined deku derives. 10 | /// 11 | /// Input: 12 | /// ```rust, ignore 13 | /// use asterix::data_item::*; 14 | /// #[deku(skip, cond = "is_fspec(DataSourceIdentifier::FRN_48, fspec, 0)")] 15 | /// pub data_source_identifier: Option, 16 | /// ``` 17 | /// 18 | /// General Output: 19 | /// ```rust, ignore 20 | /// use asterix::data_item::*; 21 | /// if self.data_source_identifier.is_some() { 22 | /// fspec[0] |= DataSourceIdentifier::FRN_48; 23 | /// } 24 | /// 25 | /// ``` 26 | /// 27 | /// There are a few parts that are pre-pended and appended to the end of the above statements, with 28 | /// generation for the vec and cleaning up the fspec. 29 | #[proc_macro_derive(UpdateFspec)] 30 | #[doc(hidden)] 31 | pub fn derive_answer_fn(input: TokenStream) -> TokenStream { 32 | // Parse the input tokens into a syntax tree 33 | let input = parse_macro_input!(input as DeriveInput); 34 | 35 | let name = &input.ident; // struct name 36 | 37 | // (self.name, fspec_num, FRN) 38 | let mut data_items: Vec<(String, String, String)> = vec![]; 39 | 40 | if let Data::Struct(s) = input.data { 41 | if let Fields::Named(f) = s.fields { 42 | for field in f.named.iter() { 43 | let ident = field.ident.as_ref().unwrap(); // they are 'named' fields 44 | // check if is first 'fspec' field in struct, skip 45 | if ident == "fspec" { 46 | continue; 47 | } 48 | for attr in &field.attrs { 49 | // check if doc ident, we don't need that one 50 | if !attr.path().is_ident("deku") { 51 | continue; 52 | } 53 | // ident should be 'deku' at this point 54 | // pulling out the `TokenStream` from `Meta::List` and parsing 55 | attr.parse_nested_meta(|meta| { 56 | if meta.path.is_ident("cond") { 57 | let value = meta.value()?; // this parses the `=` 58 | let token: syn::LitStr = value.parse()?; // this parses `"is_fspec(...)"` 59 | let fn_call = token.parse::().unwrap(); 60 | let frn = if let syn::Expr::Path(attrs) = &fn_call.args[0] { 61 | format!( 62 | "{}::{}", 63 | attrs.path.segments[0].ident, attrs.path.segments[1].ident, 64 | ) 65 | } else { 66 | unreachable!() 67 | }; 68 | 69 | let fspec_num = if let syn::Expr::Lit(lit) = &fn_call.args[2] { 70 | if let syn::Lit::Int(int) = &lit.lit { 71 | int.to_string() 72 | } else { 73 | unreachable!(); 74 | } 75 | } else { 76 | unreachable!(); 77 | }; 78 | data_items.push(( 79 | ident.to_string(), 80 | fspec_num.to_string(), 81 | frn.to_string(), 82 | )); 83 | } 84 | Ok(()) 85 | }) 86 | .expect("Error parsing nested meta"); 87 | } 88 | } 89 | } 90 | } 91 | 92 | let mut quotes = quote! {}; 93 | 94 | for data_item in data_items { 95 | let ident = syn::Ident::new(&data_item.0.to_string(), proc_macro2::Span::call_site()); 96 | let fspec_num = data_item.1.parse::().unwrap(); 97 | let frn = data_item.2; 98 | let frn = syn::parse_str::(&frn).unwrap(); 99 | quotes = quote! { 100 | #quotes 101 | if self.#ident.is_some() { 102 | fspec[#fspec_num] |= #frn; 103 | } 104 | } 105 | } 106 | 107 | let expanded = quote! { 108 | impl #name { 109 | pub fn update_fspec(&mut self) { 110 | let mut fspec = vec![0x00; 10]; 111 | #quotes 112 | trim_fspec(&mut fspec); 113 | add_fx(&mut fspec); 114 | self.fspec = fspec; 115 | } 116 | } 117 | }; 118 | // Hand the output tokens back to the compiler 119 | TokenStream::from(expanded) // also could be 'expanded.into()' 120 | } 121 | -------------------------------------------------------------------------------- /src/custom_read_write.rs: -------------------------------------------------------------------------------- 1 | use deku::bitvec::{BitSlice, BitVec, Msb0}; 2 | /// Several helpers for deku reading of certain types into certain types 3 | use deku::prelude::*; 4 | 5 | #[allow(dead_code)] 6 | pub(crate) enum Op { 7 | Multiply, 8 | Divide, 9 | Add, 10 | Subtract, 11 | } 12 | 13 | impl Op { 14 | pub(crate) fn calculate(&self, value: f32, modifier: f32) -> f32 { 15 | match self { 16 | Op::Multiply => value * modifier, 17 | Op::Divide => value / modifier, 18 | Op::Add => value + modifier, 19 | Op::Subtract => value - modifier, 20 | } 21 | } 22 | } 23 | 24 | pub(crate) mod read { 25 | use super::*; 26 | 27 | /// Read in big-endian bits to u32, multiply by f32, return f32 28 | pub(crate) fn bits_to_f32( 29 | rest: &BitSlice, 30 | bits: usize, 31 | modifier: f32, 32 | modifier_op: Op, 33 | ) -> Result<(&BitSlice, f32), DekuError> { 34 | let (rest, value) = u32::read(rest, (deku::ctx::Endian::Big, deku::ctx::BitSize(bits)))?; 35 | Ok(op(rest, value as f32, modifier, modifier_op)) 36 | } 37 | 38 | /// Read in big-endian bits to i16, multiply by f32, return f32 39 | pub(crate) fn bits_i16_to_f32( 40 | rest: &BitSlice, 41 | bits: usize, 42 | modifier: f32, 43 | modifier_op: Op, 44 | ) -> Result<(&BitSlice, f32), DekuError> { 45 | let (rest, value) = i16::read(rest, (deku::ctx::Endian::Big, deku::ctx::BitSize(bits)))?; 46 | Ok(op(rest, f32::from(value), modifier, modifier_op)) 47 | } 48 | 49 | pub(crate) fn op( 50 | rest: &BitSlice, 51 | value: f32, 52 | modifier: f32, 53 | modifier_op: Op, 54 | ) -> (&BitSlice, f32) { 55 | (rest, modifier_op.calculate(value, modifier)) 56 | } 57 | 58 | /// Read in big-endian bits, multiply by f32, return Some(f32) 59 | pub(crate) fn bits_to_optionf32( 60 | rest: &BitSlice, 61 | bits: usize, 62 | modifier: f32, 63 | modifier_op: Op, 64 | ) -> Result<(&BitSlice, Option), DekuError> { 65 | bits_to_f32(rest, bits, modifier, modifier_op).map(|(rest, f)| (rest, Some(f))) 66 | } 67 | } 68 | 69 | pub mod write { 70 | use super::*; 71 | 72 | pub(crate) fn f32_u32( 73 | value: &f32, 74 | bits: usize, 75 | modifier: f32, 76 | modifier_op: Op, 77 | output: &mut BitVec, 78 | ) -> Result<(), DekuError> { 79 | // TODO this should be function for this and the other one 80 | let value = modifier_op.calculate(*value, modifier); 81 | (value as u32).write(output, (deku::ctx::Endian::Big, deku::ctx::BitSize(bits))) 82 | } 83 | 84 | pub(crate) fn f32_optionu32( 85 | value: &Option, 86 | bits: usize, 87 | modifier: f32, 88 | modifier_op: Op, 89 | output: &mut BitVec, 90 | ) -> Result<(), DekuError> { 91 | value.map_or(Ok(()), |value| f32_u32(&value, bits, modifier, modifier_op, output)) 92 | } 93 | 94 | pub(crate) fn f32_i32( 95 | value: &f32, 96 | bits: usize, 97 | modifier: f32, 98 | modifier_op: Op, 99 | output: &mut BitVec, 100 | ) -> Result<(), DekuError> { 101 | let value = modifier_op.calculate(*value, modifier); 102 | (value as i32).write(output, (deku::ctx::Endian::Big, deku::ctx::BitSize(bits))) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/data_item.rs: -------------------------------------------------------------------------------- 1 | //! Defined Data Items that are used for formal parsing of data structs in categories 2 | 3 | use crate::custom_read_write::{read, write, Op}; 4 | use crate::fspec::is_fspec; 5 | use crate::modifier; 6 | use crate::types::{ 7 | DataFilterTYP, MessageCounterTYP, AIC, ANT, ARC, CDM, CHAB, CLU, CNF, CODE, COM, D, DLF, DOU, 8 | ERR, FOEFRI, FX, G, GHO, L, MAH, ME, MI, MSC, MSSC, MTYPE, NOGO, OVL, POL, RAB, RAD, RDP, RDPC, 9 | RDPR, RED, SCF, SI, SIM, SPI, STAT, STC, SUP, TCC, TRE, TST, TSV, TYP, V, XPP, 10 | }; 11 | use deku::bitvec::{BitSlice, BitVec, Msb0}; 12 | use deku::prelude::*; 13 | 14 | const RHO_MODIFIER: f32 = 1.0 / 256.0; 15 | const THETA_MODIFIER: f32 = 360.0 / 65536.0; 16 | 17 | /// Identification of the radar station from which the data is received 18 | /// 19 | /// Data Item I048/010 20 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 21 | #[deku(ctx = "_: deku::ctx::Endian")] 22 | pub struct DataSourceIdentifier { 23 | /// System Area Code 24 | pub sac: u8, 25 | /// System Identification Code 26 | pub sic: u8, 27 | } 28 | 29 | impl DataSourceIdentifier { 30 | pub const FRN_34: u8 = 0b1000_0000; 31 | pub const FRN_48: u8 = 0b1000_0000; 32 | } 33 | 34 | /// Absolute time stamping expressed as Co-ordinated Universal Time (UTC) 35 | /// 36 | /// Data Item I048/140 37 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 38 | #[deku(ctx = "_: deku::ctx::Endian")] 39 | pub struct TimeOfDay { 40 | #[deku( 41 | reader = "read::bits_to_f32(deku::rest, 24, Self::MODIFIER, Op::Divide)", 42 | writer = "write::f32_u32(&self.time, 24, Self::MODIFIER, Op::Multiply, deku::output)" 43 | )] 44 | pub time: f32, 45 | } 46 | 47 | impl TimeOfDay { 48 | pub const FRN_34: u8 = 0b10_0000; 49 | pub const FRN_48: u8 = 0b100_0000; 50 | const MODIFIER: f32 = 128.0; 51 | } 52 | 53 | /// Type and properties of the target report 54 | /// 55 | /// Data Item I048/020 56 | /// 57 | /// TODO: This can extend with FX bit. 58 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 59 | #[deku(ctx = "_: deku::ctx::Endian")] 60 | pub struct TargetReportDescriptor { 61 | pub typ: TYP, 62 | pub sim: SIM, 63 | pub rdp: RDP, 64 | pub spi: SPI, 65 | pub rab: RAB, 66 | pub fx1: FX, 67 | #[deku(skip, cond = "*fx1 != FX::ExtensionIntoFirstExtent")] 68 | pub tst: Option, 69 | #[deku(skip, cond = "*fx1 != FX::ExtensionIntoFirstExtent")] 70 | pub err: Option, 71 | #[deku(skip, cond = "*fx1 != FX::ExtensionIntoFirstExtent")] 72 | pub xpp: Option, 73 | #[deku(skip, cond = "*fx1 != FX::ExtensionIntoFirstExtent")] 74 | pub me: Option, 75 | #[deku(skip, cond = "*fx1 != FX::ExtensionIntoFirstExtent")] 76 | pub mi: Option, 77 | #[deku(skip, cond = "*fx1 != FX::ExtensionIntoFirstExtent")] 78 | pub foe_fri: Option, 79 | #[deku(skip, cond = "*fx1 != FX::ExtensionIntoFirstExtent")] 80 | pub fx2: Option, 81 | } 82 | 83 | impl TargetReportDescriptor { 84 | pub const FRN_48: u8 = 0b10_0000; 85 | } 86 | 87 | /// Measured position of an aircraft in local polar co-ordinates 88 | /// 89 | /// Data Item I048/040 90 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 91 | #[deku(ctx = "_: deku::ctx::Endian")] 92 | pub struct MeasuredPositionInPolarCoordinates { 93 | #[deku( 94 | reader = "read::bits_to_f32(deku::rest, 16, RHO_MODIFIER, Op::Multiply)", 95 | writer = "write::f32_u32(&self.rho, 16, RHO_MODIFIER, Op::Divide, deku::output)" 96 | )] 97 | pub rho: f32, 98 | #[deku( 99 | reader = "read::bits_to_f32(deku::rest, 16, THETA_MODIFIER, Op::Multiply)", 100 | writer = "write::f32_u32(&self.theta, 16, THETA_MODIFIER, Op::Divide, deku::output)" 101 | )] 102 | pub theta: f32, 103 | } 104 | 105 | impl MeasuredPositionInPolarCoordinates { 106 | pub const FRN_48: u8 = 0b1_0000; 107 | } 108 | 109 | /// Mode-3/A code converted into octal representation 110 | /// 111 | /// Data Item I048/070 112 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 113 | #[deku(ctx = "_: deku::ctx::Endian")] 114 | pub struct Mode3ACodeInOctalRepresentation { 115 | pub v: V, 116 | pub g: G, 117 | pub l: L, 118 | #[deku(bits = "1")] 119 | pub reserved: u8, 120 | /// Mode-3/A reply in octal representation 121 | #[deku(bits = "12", endian = "big")] 122 | pub reply: u16, 123 | } 124 | 125 | impl Mode3ACodeInOctalRepresentation { 126 | pub const FRN_48: u8 = 0b1000; 127 | } 128 | 129 | /// Flight Level converted into binary representation 130 | /// 131 | /// Data Item I048/090 132 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 133 | #[deku(ctx = "_: deku::ctx::Endian")] 134 | pub struct FlightLevelInBinaryRepresentation { 135 | pub v: V, 136 | pub g: G, 137 | #[deku( 138 | reader = "Self::read(deku::rest)", 139 | writer = "Self::write(&self.flight_level, deku::output)" 140 | )] 141 | pub flight_level: u16, 142 | } 143 | 144 | impl FlightLevelInBinaryRepresentation { 145 | pub const FRN_48: u8 = 0b100; 146 | const CTX: (deku::ctx::Endian, deku::ctx::BitSize) = 147 | (deku::ctx::Endian::Big, deku::ctx::BitSize(14_usize)); 148 | 149 | fn read(rest: &BitSlice) -> Result<(&BitSlice, u16), DekuError> { 150 | u16::read(rest, Self::CTX).map(|(rest, value)| (rest, value / 4)) 151 | } 152 | 153 | fn write(flight_level: &u16, output: &mut BitVec) -> Result<(), DekuError> { 154 | let value = *flight_level * 4; 155 | value.write(output, Self::CTX) 156 | } 157 | } 158 | 159 | /// Aircraft address (24-bits Mode S address) assigned uniquely to 160 | /// each aircraft 161 | /// 162 | /// Data Item I048/220 163 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 164 | #[deku(ctx = "_: deku::ctx::Endian")] 165 | pub struct AircraftAddress { 166 | #[deku(bytes = "3", endian = "big")] 167 | pub address: u32, 168 | } 169 | 170 | impl AircraftAddress { 171 | pub const FRN_48: u8 = 0b1000_0000; 172 | } 173 | 174 | /// Aircraft identification (in 8 characters) obtained from an aircraft 175 | /// equipped with a Mode S transponder 176 | /// 177 | /// Data Item I048/240 178 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 179 | #[deku(ctx = "_: deku::ctx::Endian")] 180 | pub struct AircraftIdentification { 181 | /// IA5 char array 182 | #[deku( 183 | reader = "Self::read(deku::rest)", 184 | writer = "Self::write(&self.identification, deku::output)" 185 | )] 186 | pub identification: String, 187 | } 188 | 189 | impl AircraftIdentification { 190 | pub const FRN_48: u8 = 0b100_0000; 191 | /// Read and convert to String 192 | fn read(rest: &BitSlice) -> Result<(&BitSlice, String), DekuError> { 193 | let (rest, one) = u8::read(rest, (deku::ctx::Endian::Big, deku::ctx::BitSize(6_usize)))?; 194 | let (rest, two) = u8::read(rest, (deku::ctx::Endian::Big, deku::ctx::BitSize(6_usize)))?; 195 | let (rest, three) = u8::read(rest, (deku::ctx::Endian::Big, deku::ctx::BitSize(6_usize)))?; 196 | let (rest, four) = u8::read(rest, (deku::ctx::Endian::Big, deku::ctx::BitSize(6_usize)))?; 197 | let (rest, five) = u8::read(rest, (deku::ctx::Endian::Big, deku::ctx::BitSize(6_usize)))?; 198 | let (rest, six) = u8::read(rest, (deku::ctx::Endian::Big, deku::ctx::BitSize(6_usize)))?; 199 | let (rest, seven) = u8::read(rest, (deku::ctx::Endian::Big, deku::ctx::BitSize(6_usize)))?; 200 | let (rest, _) = u8::read(rest, (deku::ctx::Endian::Big, deku::ctx::BitSize(6_usize)))?; 201 | let value = format!( 202 | "{}{}{}{}{}{}{}", 203 | Self::asterix_char_to_ascii(one) as char, 204 | Self::asterix_char_to_ascii(two) as char, 205 | Self::asterix_char_to_ascii(three) as char, 206 | Self::asterix_char_to_ascii(four) as char, 207 | Self::asterix_char_to_ascii(five) as char, 208 | Self::asterix_char_to_ascii(six) as char, 209 | Self::asterix_char_to_ascii(seven) as char 210 | ); 211 | Ok((rest, value)) 212 | } 213 | 214 | /// Parse from String to u8 and write 215 | fn write(field_a: &str, output: &mut BitVec) -> Result<(), DekuError> { 216 | for c in field_a.chars() { 217 | Self::asterix_ascii_to_ia5_char(c as u8) 218 | .write(output, (deku::ctx::Endian::Big, deku::ctx::BitSize(6_usize)))?; 219 | } 220 | 0_u8.write(output, (deku::ctx::Endian::Big, deku::ctx::BitSize(6_usize))) 221 | } 222 | 223 | const IA5_ALPHA: u8 = 0x01; 224 | const IA5_SPACE: u8 = 0x20; 225 | const IA5_DIGIT: u8 = 0x30; 226 | const ASC_DIGIT: u8 = b'0'; 227 | const ASC_ALPHA: u8 = b'A'; 228 | const ASC_SPACE: u8 = b' '; 229 | const ASC_ERROR: u8 = b'?'; 230 | 231 | /// parse into ascii from IA5 char array 232 | const fn asterix_char_to_ascii(code: u8) -> u8 { 233 | // space 234 | if code == Self::IA5_SPACE { 235 | Self::ASC_SPACE 236 | } 237 | // digit 238 | else if Self::IA5_DIGIT <= code && code < Self::IA5_DIGIT + 10 { 239 | Self::ASC_DIGIT + (code - Self::IA5_DIGIT) 240 | } 241 | // letter 242 | else if Self::IA5_ALPHA <= code && code < Self::IA5_ALPHA + 26 { 243 | Self::ASC_ALPHA + (code - Self::IA5_ALPHA) 244 | } else { 245 | Self::ASC_ERROR 246 | } 247 | } 248 | 249 | /// parse from IA5 char as u8 to u8 value 250 | const fn asterix_ascii_to_ia5_char(code: u8) -> u8 { 251 | // space 252 | if code == Self::ASC_SPACE { 253 | Self::IA5_SPACE 254 | } 255 | // digit 256 | else if Self::ASC_DIGIT <= code && code < Self::ASC_DIGIT + 10 { 257 | Self::IA5_DIGIT + (code - Self::ASC_DIGIT) 258 | } 259 | // letter 260 | else if Self::ASC_ALPHA <= code && code < Self::ASC_ALPHA + 26 { 261 | Self::IA5_ALPHA + (code - Self::ASC_ALPHA) 262 | } else { 263 | Self::ASC_ERROR 264 | } 265 | } 266 | } 267 | 268 | /// Mode S Comm B data as extracted from the aircraft 269 | /// transponder 270 | /// 271 | /// Data Item I048/250, Mode S MB Data 272 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 273 | #[deku(ctx = "_: deku::ctx::Endian")] 274 | pub struct ModeSMBData { 275 | #[deku(update = "self.mb_data.len()")] 276 | pub count: u8, 277 | #[deku(count = "count")] 278 | pub mb_data: Vec, 279 | #[deku(bits = "4")] 280 | pub bds1: u8, 281 | #[deku(bits = "4")] 282 | pub bds2: u8, 283 | } 284 | 285 | impl ModeSMBData { 286 | pub const FRN_48: u8 = 0b10_0000; 287 | } 288 | 289 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 290 | pub struct MBData { 291 | #[deku(count = "7")] 292 | pub data: Vec, 293 | } 294 | 295 | /// An integer value representing a unique reference to a track 296 | /// record within a particular track file 297 | /// 298 | /// Data Item I048/161 299 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 300 | #[deku(ctx = "_: deku::ctx::Endian")] 301 | pub struct TrackNumber { 302 | #[deku(bits = "4")] 303 | pub reserved: u8, 304 | #[deku(bits = "12", endian = "big")] 305 | pub number: u16, 306 | } 307 | 308 | impl TrackNumber { 309 | pub const FRN_48: u8 = 0b1_0000; 310 | } 311 | 312 | /// Calculated position of an aircraft in Cartesian co-ordinates 313 | /// 314 | /// Data Item I048/042 315 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 316 | #[deku(ctx = "_: deku::ctx::Endian")] 317 | pub struct CalculatedPositionCartesianCorr { 318 | #[deku( 319 | reader = "read::bits_i16_to_f32(deku::rest, 16, Self::MODIFIER, Op::Multiply)", 320 | writer = "write::f32_i32(&self.x, 16, Self::MODIFIER, Op::Divide, deku::output)" 321 | )] 322 | pub x: f32, 323 | #[deku( 324 | reader = "read::bits_i16_to_f32(deku::rest, 16, Self::MODIFIER, Op::Multiply)", 325 | writer = "write::f32_i32(&self.y, 16, Self::MODIFIER, Op::Divide, deku::output)" 326 | )] 327 | pub y: f32, 328 | } 329 | 330 | impl CalculatedPositionCartesianCorr { 331 | pub const FRN_48: u8 = 0b1000; 332 | const MODIFIER: f32 = 1.0 / 128.0; 333 | } 334 | 335 | /// Calculated track velocity expressed in polar co-ordinates 336 | /// 337 | /// Data Item I048/200 338 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 339 | #[deku(ctx = "_: deku::ctx::Endian")] 340 | pub struct CalculatedTrackVelocity { 341 | #[deku( 342 | reader = "read::bits_to_f32(deku::rest, 16, modifier::groundspeed(), Op::Multiply)", 343 | writer = "write::f32_u32(&self.groundspeed, 16, modifier::groundspeed(), Op::Divide, deku::output)" 344 | )] 345 | pub groundspeed: f32, 346 | #[deku( 347 | reader = "read::bits_to_f32(deku::rest, 16, modifier::heading1(), Op::Multiply)", 348 | writer = "write::f32_u32(&self.heading, 16, modifier::heading1(), Op::Divide, deku::output)" 349 | )] 350 | pub heading: f32, 351 | } 352 | 353 | impl CalculatedTrackVelocity { 354 | pub const FRN_48: u8 = 0b100; 355 | } 356 | 357 | /// Status of monoradar track (PSR and/or SSR updated) 358 | /// 359 | /// Data Item I048/170 360 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 361 | #[deku(ctx = "_: deku::ctx::Endian")] 362 | pub struct TrackStatus { 363 | pub cnf: CNF, 364 | pub rad: RAD, 365 | pub dou: DOU, 366 | pub mah: MAH, 367 | pub cdm: CDM, 368 | pub fx1: FX, 369 | #[deku(skip, cond = "*fx1 != FX::ExtensionIntoFirstExtent")] 370 | pub tre: Option, 371 | #[deku(skip, cond = "*fx1 != FX::ExtensionIntoFirstExtent")] 372 | pub gho: Option, 373 | #[deku(skip, cond = "*fx1 != FX::ExtensionIntoFirstExtent")] 374 | pub sup: Option, 375 | #[deku(skip, cond = "*fx1 != FX::ExtensionIntoFirstExtent")] 376 | pub tcc: Option, 377 | #[deku(skip, cond = "*fx1 != FX::ExtensionIntoFirstExtent", bits = "3")] 378 | pub reserved: Option, 379 | #[deku(skip, cond = "*fx1 != FX::ExtensionIntoFirstExtent")] 380 | pub fx2: Option, 381 | } 382 | 383 | impl TrackStatus { 384 | pub const FRN_48: u8 = 0b10; 385 | } 386 | 387 | /// Track quality in the form of a vector of standard deviations 388 | /// 389 | /// Data Item I048/210 390 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 391 | #[deku(ctx = "_: deku::ctx::Endian")] 392 | pub struct TrackQuality { 393 | #[deku( 394 | reader = "read::bits_to_f32(deku::rest, 8, Self::MODIFIER, Op::Divide)", 395 | writer = "write::f32_u32(&self.horizontal_stddev, 8, Self::MODIFIER, Op::Multiply, deku::output)" 396 | )] 397 | pub horizontal_stddev: f32, 398 | #[deku( 399 | reader = "read::bits_to_f32(deku::rest, 8, Self::MODIFIER, Op::Divide)", 400 | writer = "write::f32_u32(&self.vertical_stddev, 8, Self::MODIFIER, Op::Multiply, deku::output)" 401 | )] 402 | pub vertical_stddev: f32, 403 | #[deku( 404 | reader = "read::bits_to_f32(deku::rest, 8, modifier::groundspeed(), Op::Multiply)", 405 | writer = "write::f32_u32(&self.groundspeed_stddev, 8, modifier::groundspeed(), Op::Divide, deku::output)" 406 | )] 407 | pub groundspeed_stddev: f32, 408 | #[deku( 409 | reader = "read::bits_to_f32(deku::rest, 8, modifier::heading2(), Op::Multiply)", 410 | writer = "write::f32_u32(&self.heading_stddev, 8, modifier::heading2(), Op::Divide, deku::output)" 411 | )] 412 | pub heading_stddev: f32, 413 | } 414 | 415 | impl TrackQuality { 416 | pub const FRN_48: u8 = 0b1000_0000; 417 | const MODIFIER: f32 = 1.0 / 128.0; 418 | } 419 | 420 | /// Communications capability of the transponder, capability of the onboard ACAS equipment and 421 | /// flight status 422 | /// 423 | /// Data Item I048/230 424 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 425 | #[deku(ctx = "_: deku::ctx::Endian")] 426 | pub struct CommunicationsCapabilityFlightStatus { 427 | pub com: COM, 428 | pub stat: STAT, 429 | pub si: SI, 430 | #[deku(bits = "1")] 431 | pub reserved: u8, 432 | pub mssc: MSSC, 433 | pub arc: ARC, 434 | pub aic: AIC, 435 | #[deku(bits = "1")] 436 | pub b1a: u8, 437 | #[deku(bits = "4")] 438 | pub b1b: u8, 439 | } 440 | 441 | impl CommunicationsCapabilityFlightStatus { 442 | pub const FRN_48: u8 = 0b10; 443 | } 444 | 445 | /// Additional information on the quality of the target report 446 | /// 447 | /// Data Item I048/130 448 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 449 | #[deku(ctx = "_: deku::ctx::Endian")] 450 | pub struct RadarPlotCharacteristics { 451 | #[deku(until = "|b: &u8| *b & 0b0000_0001 == 0")] 452 | pub fspec: Vec, 453 | #[deku( 454 | skip, 455 | cond = "is_fspec(0b1000_0000, fspec, 0)", 456 | reader = "read::bits_to_optionf32(deku::rest, 8, Self::runlength_modifier(), Op::Multiply)", 457 | writer = "write::f32_optionu32(&self.srl, 8, Self::runlength_modifier(), Op::Divide, deku::output)" 458 | )] 459 | pub srl: Option, 460 | #[deku(skip, cond = "is_fspec(0b100_0000, fspec, 0)")] 461 | pub srr: Option, 462 | #[deku(skip, cond = "is_fspec(0b10_0000, fspec, 0)")] 463 | pub sam: Option, 464 | #[deku( 465 | skip, 466 | cond = "is_fspec(0b1_0000, fspec, 0)", 467 | reader = "read::bits_to_optionf32(deku::rest, 8, Self::runlength_modifier(), Op::Multiply)", 468 | writer = "write::f32_optionu32(&self.prl, 8, Self::runlength_modifier(), Op::Divide, deku::output)" 469 | )] 470 | pub prl: Option, 471 | #[deku(skip, cond = "is_fspec(0b1000, fspec, 0)")] 472 | pub pam: Option, 473 | #[deku( 474 | skip, 475 | cond = "is_fspec(0b100, fspec, 0)", 476 | reader = "read::bits_to_optionf32(deku::rest, 8, Self::nm_modifier(), Op::Multiply)", 477 | writer = "write::f32_optionu32(&self.rpd, 8, Self::nm_modifier(), Op::Divide, deku::output)" 478 | )] 479 | pub rpd: Option, 480 | #[deku( 481 | skip, 482 | cond = "is_fspec(0b100, fspec, 0)", 483 | reader = "read::bits_to_optionf32(deku::rest, 8, Self::apd_modifier(), Op::Multiply)", 484 | writer = "write::f32_optionu32(&self.apd, 8, Self::apd_modifier(), Op::Divide, deku::output)" 485 | )] 486 | pub apd: Option, 487 | } 488 | 489 | impl RadarPlotCharacteristics { 490 | pub const FRN_48: u8 = 0b10; 491 | 492 | fn runlength_modifier() -> f32 { 493 | 360.0 / f32::from(2_u16.pow(13)) 494 | } 495 | 496 | fn nm_modifier() -> f32 { 497 | 1.0 / 256.0 498 | } 499 | 500 | fn apd_modifier() -> f32 { 501 | 360.0 / f32::from(2_u16.pow(14)) 502 | } 503 | } 504 | 505 | /// This Data Item allows for a more convenient handling of the 506 | /// messages at the receiver side by further defining the type of 507 | /// transaction 508 | /// 509 | /// Data Item I034/000 510 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 511 | #[deku(ctx = "_: deku::ctx::Endian")] 512 | pub struct MessageType { 513 | pub t: MTYPE, 514 | } 515 | 516 | impl MessageType { 517 | pub const FRN_34: u8 = 0b100_0000; 518 | } 519 | 520 | /// Eight most significant bits of the antenna azimuth defining a 521 | /// particular azimuth sector 522 | /// 523 | /// Data Item I034/020 524 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 525 | #[deku(ctx = "_: deku::ctx::Endian")] 526 | pub struct SectorNumber { 527 | #[deku(reader = "Self::read(deku::rest)", writer = "Self::write(&self.num, deku::output)")] 528 | pub num: u16, 529 | } 530 | 531 | impl SectorNumber { 532 | pub const FRN_34: u8 = 0b1_0000; 533 | pub const FRN_48: u8 = 0b1_0000; 534 | const CTX: (deku::ctx::Endian, deku::ctx::BitSize) = 535 | (deku::ctx::Endian::Big, deku::ctx::BitSize(8_usize)); 536 | 537 | fn modifier() -> f32 { 538 | 360.0 / 2_f32.powi(8) 539 | } 540 | 541 | fn read(rest: &BitSlice) -> Result<(&BitSlice, u16), DekuError> { 542 | u16::read(rest, Self::CTX) 543 | .map(|(rest, value)| (rest, (f32::from(value) * Self::modifier()) as u16)) 544 | } 545 | 546 | fn write(num: &u16, output: &mut BitVec) -> Result<(), DekuError> { 547 | let value = (f32::from(*num) / Self::modifier()) as u8; 548 | value.write(output, Self::CTX) 549 | } 550 | } 551 | 552 | /// Warning/error conditions detected by a radar station for the target 553 | /// report involved. Target Classification information for the target 554 | /// involved 555 | /// 556 | /// Data Item I048/030 557 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 558 | #[deku(ctx = "_: deku::ctx::Endian")] 559 | pub struct WarningErrorConditionsTargetClass { 560 | #[deku(until = "|codefx: &CodeFx| codefx.fx == FX::EndOfDataItem")] 561 | pub codefxs: Vec, 562 | } 563 | 564 | impl WarningErrorConditionsTargetClass { 565 | pub const FRN_48: u8 = 0b100_0000; 566 | } 567 | 568 | #[derive(Debug, PartialEq, Clone, Copy, DekuRead, DekuWrite)] 569 | pub struct CodeFx { 570 | pub code: CODE, 571 | pub fx: FX, 572 | } 573 | 574 | /// Confidence level for each bit of a Mode-3/A reply as provided 575 | /// by a monopulse SSR station 576 | /// 577 | /// Data Item I048/080 578 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 579 | #[deku(ctx = "_: deku::ctx::Endian")] 580 | pub struct Mode3ACodeConfidenceIndicator { 581 | #[deku(bits = "4", endian = "big")] 582 | pub reserved: u8, 583 | #[deku(bits = "12", endian = "big")] 584 | pub confidence: u16, 585 | } 586 | 587 | impl Mode3ACodeConfidenceIndicator { 588 | pub const FRN_48: u8 = 0b10_0000; 589 | } 590 | 591 | /// Mode-C height in Gray notation as received from the 592 | /// transponder together with the confidence level for each reply bit 593 | /// as provided by a MSSR/Mode S station 594 | /// 595 | /// Data Item I048/100 596 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 597 | #[deku(ctx = "_: deku::ctx::Endian")] 598 | pub struct ModeCCodeAndConfidenceIndicator { 599 | pub v: V, 600 | pub g: G, 601 | #[deku(bits = "2", endian = "big")] 602 | pub reserved0: u8, 603 | #[deku(bits = "12", endian = "big")] 604 | pub mode_c_gray_notation: u16, 605 | #[deku(bits = "4", endian = "big")] 606 | pub reserved1: u8, 607 | #[deku(bits = "12", endian = "big")] 608 | pub confidence: u16, 609 | } 610 | 611 | impl ModeCCodeAndConfidenceIndicator { 612 | pub const FRN_48: u8 = 0b1_0000; 613 | } 614 | 615 | /// Height of a target as measured by a 3D radar. The height shall 616 | /// use mean sea level as the zero reference level 617 | /// 618 | /// Data Item I048/110 619 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 620 | #[deku(ctx = "_: deku::ctx::Endian")] 621 | pub struct HeightMeasuredBy3dRadar { 622 | #[deku(bits = "2", endian = "big")] 623 | pub reserved: u8, 624 | #[deku(reader = "Self::read(deku::rest)", writer = "Self::write(&self.height, deku::output)")] 625 | pub height: i32, 626 | } 627 | 628 | impl HeightMeasuredBy3dRadar { 629 | pub const FRN_48: u8 = 0b1000; 630 | const CTX: (deku::ctx::Endian, deku::ctx::BitSize) = 631 | (deku::ctx::Endian::Big, deku::ctx::BitSize(14_usize)); 632 | pub const MODIFIER: i32 = 25; 633 | 634 | fn read(rest: &BitSlice) -> Result<(&BitSlice, i32), DekuError> { 635 | i32::read(rest, Self::CTX).map(|(rest, value)| (rest, value * Self::MODIFIER)) 636 | } 637 | 638 | fn write(height: &i32, output: &mut BitVec) -> Result<(), DekuError> { 639 | let value = height / Self::MODIFIER; 640 | value.write(output, Self::CTX) 641 | } 642 | } 643 | 644 | /// Information on the Doppler Speed of the target report 645 | /// 646 | /// Data Item I048/120 647 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 648 | #[deku(ctx = "_: deku::ctx::Endian")] 649 | pub struct RadialDopplerSpeed { 650 | #[deku(bits = "1", endian = "big")] 651 | pub cal: u8, 652 | #[deku(bits = "1", endian = "big")] 653 | pub rds: u8, 654 | #[deku(bits = "6", endian = "big")] 655 | pub spare: u8, 656 | #[deku(skip, endian = "big", cond = "*cal == 0")] 657 | pub calculated_doppler_speed: Option, 658 | #[deku(skip, endian = "big", cond = "*rds == 0")] 659 | pub raw_doppler_speed: Option, 660 | } 661 | 662 | impl RadialDopplerSpeed { 663 | pub const FRN_48: u8 = 0b100; 664 | } 665 | 666 | /// Subfield of `HeightMeasuredBy3dRadar` 667 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 668 | #[deku(ctx = "_: deku::ctx::Endian")] 669 | pub struct CalculatedDopplerSpeed { 670 | pub d: D, 671 | #[deku(bits = "5", endian = "big")] 672 | pub spare: u8, 673 | #[deku(bits = "10", endian = "big")] 674 | pub cal: u16, 675 | } 676 | 677 | /// Subfield of `HeightMeasuredBy3dRadar` 678 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 679 | #[deku(ctx = "_: deku::ctx::Endian")] 680 | pub struct RawDopplerSpeed { 681 | /// Repetition Factor 682 | #[deku(endian = "big")] 683 | pub rep: u8, 684 | /// Doppler Speed: 1 m/sec 685 | #[deku(endian = "big")] 686 | pub dop: u16, 687 | /// Ambiquity Range: 1 m/sec 688 | #[deku(endian = "big")] 689 | pub amb: u16, 690 | /// Transmitter Frequency: 1 Mhz 691 | #[deku(endian = "big")] 692 | pub frq: u16, 693 | } 694 | 695 | /// Currently active Resolution Advisory (RA), if any, generated by the 696 | /// ACAS associated with the transponder transmitting the report and 697 | /// threat identity data 698 | /// 699 | /// Data Item I048/260 700 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 701 | #[deku(ctx = "_: deku::ctx::Endian")] 702 | pub struct ACASResolutionAdvisoryReport { 703 | pub mb_data: [u8; 7], 704 | } 705 | 706 | impl ACASResolutionAdvisoryReport { 707 | pub const FRN_48: u8 = 0b1000_0000; 708 | } 709 | 710 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 711 | #[deku(ctx = "_: deku::ctx::Endian")] 712 | pub struct Mode1CodeOctalRepresentation { 713 | pub v: V, 714 | pub g: G, 715 | pub l: L, 716 | #[deku(bits = "5", endian = "big")] 717 | pub data: u8, 718 | } 719 | 720 | impl Mode1CodeOctalRepresentation { 721 | pub const FRN_48: u8 = 0b100_0000; 722 | } 723 | 724 | /// Reply to Mode-2 interrogation 725 | /// 726 | /// Data Item I048/050 727 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 728 | #[deku(ctx = "_: deku::ctx::Endian")] 729 | pub struct Mode2CodeOctalRepresentation { 730 | pub v: V, 731 | pub g: G, 732 | pub l: L, 733 | #[deku(bits = "1", endian = "big")] 734 | pub spare: u8, 735 | #[deku(bits = "12", endian = "big")] 736 | pub data: u16, 737 | } 738 | 739 | impl Mode2CodeOctalRepresentation { 740 | pub const FRN_48: u8 = 0b10_0000; 741 | } 742 | 743 | /// Confidence level for each bit of a Mode-1 reply as provided by 744 | /// a monopulse SSR station 745 | /// 746 | /// Data Item I048/065 747 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 748 | #[deku(ctx = "_: deku::ctx::Endian")] 749 | pub struct Mode1CodeConfidenceIndicator { 750 | #[deku(bits = "3", endian = "big")] 751 | pub spare: u8, 752 | #[deku(bits = "5", endian = "big")] 753 | pub data: u8, 754 | } 755 | 756 | impl Mode1CodeConfidenceIndicator { 757 | pub const FRN_48: u8 = 0b1_0000; 758 | } 759 | 760 | /// Confidence level for each bit of a Mode-2 reply as provided by 761 | /// a monopulse SSR station 762 | /// 763 | /// Data Item I048/060 764 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 765 | #[deku(ctx = "_: deku::ctx::Endian")] 766 | pub struct Mode2CodeConfidenceIndicator { 767 | #[deku(bits = "4", endian = "big")] 768 | pub spare: u8, 769 | #[deku(bits = "12", endian = "big")] 770 | pub data: u16, 771 | } 772 | 773 | impl Mode2CodeConfidenceIndicator { 774 | pub const FRN_48: u8 = 0b1000; 775 | } 776 | 777 | /// Antenna rotation period as measured between two consecutive 778 | /// North crossings or as averaged during a period of time 779 | /// 780 | /// Data Item I034/041 781 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 782 | #[deku(ctx = "_: deku::ctx::Endian")] 783 | pub struct AntennaRotationSpeed { 784 | #[deku( 785 | reader = "read::bits_to_f32(deku::rest, 16, Self::MODIFIER, Op::Divide)", 786 | writer = "write::f32_u32(&self.period, 16, Self::MODIFIER, Op::Multiply, deku::output)" 787 | )] 788 | pub period: f32, 789 | } 790 | 791 | impl AntennaRotationSpeed { 792 | pub const FRN_34: u8 = 0b1000; 793 | const MODIFIER: f32 = 128.0; 794 | } 795 | 796 | /// Information concerning the configuration and status of a System 797 | /// 798 | /// Data Item I034/050 799 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 800 | #[deku(ctx = "_: deku::ctx::Endian")] 801 | pub struct SystemConfigurationAndStatus { 802 | #[deku(bits = "1")] 803 | pub com_bit: u8, 804 | #[deku(bits = "2")] 805 | pub spare_bit0: u8, 806 | #[deku(bits = "1")] 807 | pub psr_bit: u8, 808 | #[deku(bits = "1")] 809 | pub ssr_bit: u8, 810 | #[deku(bits = "1")] 811 | pub mds_bit: u8, 812 | #[deku(bits = "1")] 813 | pub spare_bit1: u8, 814 | pub fx_bit: FX, 815 | #[deku(skip, cond = "*com_bit != 1")] 816 | pub com: Option, 817 | #[deku(skip, cond = "*psr_bit != 1")] 818 | pub psr: Option, 819 | #[deku(skip, cond = "*ssr_bit != 1")] 820 | pub ssr: Option, 821 | #[deku(skip, cond = "*mds_bit != 1")] 822 | pub mds: Option, 823 | } 824 | 825 | impl SystemConfigurationAndStatus { 826 | pub const FRN_34: u8 = 0b0100; 827 | } 828 | 829 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 830 | pub struct ComSubField { 831 | pub nogo: NOGO, 832 | pub rdpc: RDPC, 833 | pub rdpr: RDPR, 834 | pub ovl_rdp: OVL, 835 | pub olv_xmt: OVL, 836 | pub msc: MSC, 837 | pub tsv: TSV, 838 | #[deku(bits = "1")] 839 | pub spare: u8, 840 | } 841 | 842 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 843 | pub struct Sensor { 844 | pub ant: ANT, 845 | pub chab: CHAB, 846 | pub ovl: OVL, 847 | pub msc: MSC, 848 | #[deku(bits = "3")] 849 | pub spare: u8, 850 | } 851 | 852 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 853 | pub struct MdsSubField { 854 | pub ant: ANT, 855 | pub chab: CHAB, 856 | pub ovl_sur: OVL, 857 | pub msc: MSC, 858 | pub scf: SCF, 859 | pub dlf: DLF, 860 | pub ovl_scf: OVL, 861 | pub ovl_dlf: OVL, 862 | #[deku(bits = "7")] 863 | pub spare: u8, 864 | } 865 | 866 | /// Status concerning the processing options, in use during the last antenna revolution, 867 | /// for the various Sensors, composing the System 868 | /// 869 | /// Data Item I034/060 870 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 871 | #[deku(ctx = "_: deku::ctx::Endian")] 872 | pub struct SystemProcessingMode { 873 | #[deku(bits = "1")] 874 | pub com_bit: u8, 875 | #[deku(bits = "2")] 876 | pub spare_bit0: u8, 877 | #[deku(bits = "1")] 878 | pub psr_bit: u8, 879 | #[deku(bits = "1")] 880 | pub ssr_bit: u8, 881 | #[deku(bits = "1")] 882 | pub mds_bit: u8, 883 | #[deku(bits = "1")] 884 | pub spare_bit1: u8, 885 | pub fx_bit: FX, 886 | #[deku(skip, cond = "*com_bit != 1")] 887 | pub com: Option, 888 | #[deku(skip, cond = "*psr_bit != 1")] 889 | pub psr: Option, 890 | #[deku(skip, cond = "*ssr_bit != 1")] 891 | pub ssr: Option, 892 | #[deku(skip, cond = "*mds_bit != 1")] 893 | pub mds: Option, 894 | } 895 | 896 | impl SystemProcessingMode { 897 | pub const FRN_34: u8 = 0b0010; 898 | } 899 | 900 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 901 | pub struct ComSubField2 { 902 | #[deku(bits = "1")] 903 | pub spare0: u8, 904 | pub red_rdp: RED, 905 | pub red_xmt: RED, 906 | #[deku(bits = "1")] 907 | pub spare1: u8, 908 | } 909 | 910 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 911 | pub struct PsrSubField { 912 | pub pol: POL, 913 | pub red_rad: RED, 914 | pub stc: STC, 915 | #[deku(bits = "2")] 916 | pub spare: u8, 917 | } 918 | 919 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 920 | pub struct SsrSubField { 921 | pub red_rad: RED, 922 | #[deku(bits = "5")] 923 | pub spare: u8, 924 | } 925 | 926 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 927 | pub struct MdsSubField2 { 928 | pub red_rad: RED, 929 | pub clu: CLU, 930 | #[deku(bits = "4")] 931 | pub spare: u8, 932 | } 933 | 934 | /// Message Count values, according the various types of messages, 935 | /// for the last completed antenna revolution, counted between two 936 | /// North crossings 937 | /// 938 | /// Data Item I034/070 939 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 940 | #[deku(ctx = "_: deku::ctx::Endian")] 941 | pub struct MessageCountValues { 942 | #[deku(update = "self.counters.len()")] 943 | pub count: u8, 944 | #[deku(count = "count")] 945 | pub counters: Vec, 946 | } 947 | 948 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 949 | pub struct MessageCounter { 950 | pub typ: MessageCounterTYP, 951 | #[deku(bits = "11")] 952 | pub counter: u16, 953 | } 954 | 955 | impl MessageCountValues { 956 | pub const FRN_34: u8 = 0b1000_0000; 957 | } 958 | 959 | /// Geographical window defined in polar co-ordinates. 960 | /// 961 | /// Data Item I034/100 962 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 963 | #[deku(ctx = "_: deku::ctx::Endian")] 964 | pub struct GenericPolarWindow { 965 | #[deku( 966 | reader = "read::bits_to_f32(deku::rest, 16, RHO_MODIFIER, Op::Multiply)", 967 | writer = "write::f32_u32(&self.rho_start, 16, RHO_MODIFIER, Op::Divide, deku::output)" 968 | )] 969 | pub rho_start: f32, 970 | #[deku( 971 | reader = "read::bits_to_f32(deku::rest, 16, RHO_MODIFIER, Op::Multiply)", 972 | writer = "write::f32_u32(&self.rho_end, 16, RHO_MODIFIER, Op::Divide, deku::output)" 973 | )] 974 | pub rho_end: f32, 975 | #[deku( 976 | reader = "read::bits_to_f32(deku::rest, 16, THETA_MODIFIER, Op::Multiply)", 977 | writer = "write::f32_u32(&self.theta_start, 16, THETA_MODIFIER, Op::Divide, deku::output)" 978 | )] 979 | pub theta_start: f32, 980 | #[deku( 981 | reader = "read::bits_to_f32(deku::rest, 16, THETA_MODIFIER, Op::Multiply)", 982 | writer = "write::f32_u32(&self.theta_end, 16, THETA_MODIFIER, Op::Divide, deku::output)" 983 | )] 984 | pub theta_end: f32, 985 | } 986 | 987 | impl GenericPolarWindow { 988 | pub const FRN_34: u8 = 0b0100_0000; 989 | } 990 | 991 | /// Data Filter, which allows suppression of individual data types. 992 | /// 993 | /// Data Item I034/110 994 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 995 | #[deku(ctx = "_: deku::ctx::Endian")] 996 | pub struct DataFilter { 997 | pub typ: DataFilterTYP, 998 | } 999 | 1000 | impl DataFilter { 1001 | pub const FRN_34: u8 = 0b0010_0000; 1002 | } 1003 | 1004 | /// 3D-Position of Data Source in WGS 84 Co-ordinates 1005 | /// 1006 | /// Data Item I034/120 1007 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 1008 | #[deku(ctx = "_: deku::ctx::Endian")] 1009 | pub struct ThreeDPositionOfDataSource { 1010 | pub height_of_wgs_84: u16, 1011 | #[deku( 1012 | reader = "read::bits_to_f32(deku::rest, 24, Self::WGS_MODIFIER, Op::Multiply)", 1013 | writer = "write::f32_u32(&self.latitude_in_wgs_84, 24, Self::WGS_MODIFIER, Op::Divide, deku::output)" 1014 | )] 1015 | pub latitude_in_wgs_84: f32, 1016 | #[deku( 1017 | reader = "read::bits_to_f32(deku::rest, 24, Self::WGS_MODIFIER, Op::Multiply)", 1018 | writer = "write::f32_u32(&self.longitude_in_wgs_84, 24, Self::WGS_MODIFIER, Op::Divide, deku::output)" 1019 | )] 1020 | pub longitude_in_wgs_84: f32, 1021 | } 1022 | 1023 | impl ThreeDPositionOfDataSource { 1024 | pub const WGS_MODIFIER: f32 = 180.0 / 8_388_608.0; 1025 | pub const FRN_34: u8 = 0b0001_0000; 1026 | } 1027 | 1028 | /// Averaged difference in range and in azimuth for the primary target 1029 | /// position with respect to the SSR target position as calculated by 1030 | /// the radar station 1031 | /// 1032 | /// Data Item I034/090 1033 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 1034 | #[deku(ctx = "_: deku::ctx::Endian")] 1035 | pub struct CollimationError { 1036 | #[deku( 1037 | reader = "read::bits_to_f32(deku::rest, 8, Self::MODIFIER, Op::Multiply)", 1038 | writer = "write::f32_u32(&self.range_error, 8, Self::MODIFIER, Op::Divide, deku::output)" 1039 | )] 1040 | pub range_error: f32, 1041 | #[deku( 1042 | reader = "read::bits_to_f32(deku::rest, 8, Self::AZIMUTH_MODIFIER, Op::Multiply)", 1043 | writer = "write::f32_u32(&self.azimuth_error, 8, Self::AZIMUTH_MODIFIER, Op::Divide, deku::output)" 1044 | )] 1045 | pub azimuth_error: f32, 1046 | } 1047 | 1048 | impl CollimationError { 1049 | pub const MODIFIER: f32 = 1.0 / 128.0; 1050 | // TODO #![feature(const_int_pow)] 1051 | //pub const AZIMUTH_MODIFIER: f32 = 360.0 / f32::from(2_u16.pow(14)); 1052 | pub const AZIMUTH_MODIFIER: f32 = 360.0 / 16384.0; 1053 | pub const FRN_34: u8 = 0b0000_1000; 1054 | } 1055 | 1056 | #[cfg(test)] 1057 | mod tests { 1058 | use super::*; 1059 | // tests from https://github.com/wireshark/wireshark/blob/master/test/suite_dissectors/group_asterix.py 1060 | 1061 | #[test] 1062 | fn tod_140() { 1063 | let mut input = BitSlice::from_slice(&[0xa8, 0xbf, 0xff]); 1064 | let item = TimeOfDay::read(&mut input, deku::ctx::Endian::Big).unwrap().1; 1065 | assert_eq!(item.time, 86_399.99); 1066 | } 1067 | 1068 | #[test] 1069 | fn target_report_descriptor_020() { 1070 | let mut input = BitSlice::from_slice(&[ 1071 | 0xe0 | 0x08 | 0x04 | 0x02 | 0x01, 1072 | 0x80 | 0x40 | 0x20 | 0x10 | 0x08 | 0x06, 1073 | ]); 1074 | let item = TargetReportDescriptor::read(&mut input, deku::ctx::Endian::Big).unwrap().1; 1075 | assert_eq!(item.typ, TYP::ModeSRollCallPlusPSR); 1076 | assert_eq!(item.sim, SIM::ActualTargetReport); 1077 | assert_eq!(item.rdp, RDP::ReportFromRDPChain2); 1078 | assert_eq!(item.spi, SPI::SpecialPositionIdentification); 1079 | assert_eq!(item.rab, RAB::ReportFromFieldMonitor); 1080 | assert_eq!(item.fx1, FX::ExtensionIntoFirstExtent); 1081 | assert_eq!(item.tst, Some(TST::TestTargetReport)); 1082 | assert_eq!(item.err, Some(ERR::ExtendedRangePresent)); 1083 | assert_eq!(item.xpp, Some(XPP::XPulsePresent)); 1084 | assert_eq!(item.me, Some(ME::MilitaryEmergency)); 1085 | assert_eq!(item.mi, Some(MI::MilitaryIdentification)); 1086 | assert_eq!(item.foe_fri, Some(FOEFRI::NoReply)); 1087 | } 1088 | } 1089 | -------------------------------------------------------------------------------- /src/fourty_eight.rs: -------------------------------------------------------------------------------- 1 | use crate::data_item::{ 2 | ACASResolutionAdvisoryReport, AircraftAddress, AircraftIdentification, 3 | CalculatedPositionCartesianCorr, CalculatedTrackVelocity, CommunicationsCapabilityFlightStatus, 4 | DataSourceIdentifier, FlightLevelInBinaryRepresentation, HeightMeasuredBy3dRadar, 5 | MeasuredPositionInPolarCoordinates, Mode1CodeConfidenceIndicator, Mode1CodeOctalRepresentation, 6 | Mode2CodeConfidenceIndicator, Mode2CodeOctalRepresentation, Mode3ACodeConfidenceIndicator, 7 | Mode3ACodeInOctalRepresentation, ModeCCodeAndConfidenceIndicator, ModeSMBData, 8 | RadarPlotCharacteristics, RadialDopplerSpeed, TargetReportDescriptor, TimeOfDay, TrackNumber, 9 | TrackQuality, TrackStatus, WarningErrorConditionsTargetClass, 10 | }; 11 | use crate::fspec::{add_fx, is_fspec, trim_fspec}; 12 | use crate::FSPEC_IDENT; 13 | use asterix_derive::UpdateFspec; 14 | use deku::prelude::*; 15 | 16 | /// Transmission of Monoradar Target Reports 17 | #[derive(Debug, Default, PartialEq, DekuRead, DekuWrite, UpdateFspec)] 18 | #[deku(endian = "big")] 19 | pub struct Cat48 { 20 | #[deku(until = "|b: &u8| *b & FSPEC_IDENT == 0")] 21 | pub fspec: Vec, 22 | /// FRN 1 23 | #[deku(skip, cond = "is_fspec(DataSourceIdentifier::FRN_48, fspec, 0)")] 24 | pub data_source_identifier: Option, 25 | /// FRN 2 26 | #[deku(skip, cond = "is_fspec(TimeOfDay::FRN_48, fspec, 0)")] 27 | pub time_of_day: Option, 28 | /// FRN 3 29 | #[deku(skip, cond = "is_fspec(TargetReportDescriptor::FRN_48, fspec, 0)")] 30 | pub target_report_descriptor: Option, 31 | /// FRN 4 32 | #[deku(skip, cond = "is_fspec(MeasuredPositionInPolarCoordinates::FRN_48, fspec, 0)")] 33 | pub measured_position_in_polar_coordinates: Option, 34 | /// FRN 5 35 | #[deku(skip, cond = "is_fspec(Mode3ACodeInOctalRepresentation::FRN_48, fspec, 0)")] 36 | pub mode_3_a_code_in_octal_representation: Option, 37 | /// FRN 6 38 | #[deku(skip, cond = "is_fspec(FlightLevelInBinaryRepresentation::FRN_48, fspec, 0)")] 39 | pub flight_level_in_binary_repre: Option, 40 | /// FRN 7 41 | #[deku(skip, cond = "is_fspec(RadarPlotCharacteristics::FRN_48, fspec, 0)")] 42 | pub radar_plot_characteristics: Option, 43 | /// FRN 8 44 | #[deku(skip, cond = "is_fspec(AircraftAddress::FRN_48, fspec, 1)")] 45 | pub aircraft_address: Option, 46 | /// FRN 9 47 | #[deku(skip, cond = "is_fspec(AircraftIdentification::FRN_48, fspec, 1)")] 48 | pub aircraft_identification: Option, 49 | /// FRN 10 50 | #[deku(skip, cond = "is_fspec(ModeSMBData::FRN_48, fspec, 1)")] 51 | pub mode_smb_data: Option, 52 | /// FRN 11 53 | #[deku(skip, cond = "is_fspec(TrackNumber::FRN_48, fspec, 1)")] 54 | pub track_number: Option, 55 | /// FRN 12 56 | #[deku(skip, cond = "is_fspec(CalculatedPositionCartesianCorr::FRN_48, fspec, 1)")] 57 | pub calculated_position_cartesian_coor: Option, 58 | /// FRN 13 59 | #[deku(skip, cond = "is_fspec(CalculatedTrackVelocity::FRN_48, fspec, 1)")] 60 | pub calculated_track_velocity: Option, 61 | /// FRN 14 62 | #[deku(skip, cond = "is_fspec(TrackStatus::FRN_48, fspec, 1)")] 63 | pub track_status: Option, 64 | /// FRN 15 65 | #[deku(skip, cond = "is_fspec(TrackQuality::FRN_48, fspec, 2)")] 66 | pub track_quality: Option, 67 | /// FRN 16 68 | #[deku(skip, cond = "is_fspec(WarningErrorConditionsTargetClass::FRN_48, fspec, 2)")] 69 | pub warning_error_con_target_class: Option, 70 | /// FRN 17 71 | #[deku(skip, cond = "is_fspec(Mode3ACodeConfidenceIndicator::FRN_48, fspec, 2)")] 72 | pub mode3a_code_confidence_indicator: Option, 73 | /// FRN 18 74 | #[deku(skip, cond = "is_fspec(ModeCCodeAndConfidenceIndicator::FRN_48, fspec, 2)")] 75 | pub modec_code_and_confidence_indicator: Option, 76 | /// FRN 19 77 | #[deku(skip, cond = "is_fspec(HeightMeasuredBy3dRadar::FRN_48, fspec, 2)")] 78 | pub height_measured_by_3d_radar: Option, 79 | /// FRN 20 80 | #[deku(skip, cond = "is_fspec(RadialDopplerSpeed::FRN_48, fspec, 2)")] 81 | pub radial_doppler_speed: Option, 82 | /// FRN 21 83 | #[deku(skip, cond = "is_fspec(CommunicationsCapabilityFlightStatus::FRN_48, fspec, 2)")] 84 | pub communications_capability_flight_status: Option, 85 | /// FRN 22 86 | #[deku(skip, cond = "is_fspec(ACASResolutionAdvisoryReport::FRN_48, fspec, 3)")] 87 | pub acas_resolution_advisory_report: Option, 88 | /// FRN 23 89 | #[deku(skip, cond = "is_fspec(Mode1CodeOctalRepresentation::FRN_48, fspec, 3)")] 90 | pub mode_1_code_octal_representation: Option, 91 | /// FRN 24 92 | #[deku(skip, cond = "is_fspec(Mode2CodeOctalRepresentation::FRN_48, fspec, 3)")] 93 | pub mode_2_code_octal_representation: Option, 94 | /// FRN 25 95 | #[deku(skip, cond = "is_fspec(Mode1CodeConfidenceIndicator::FRN_48, fspec, 3)")] 96 | pub mode_1_code_confidence: Option, 97 | /// FRN 26 98 | #[deku(skip, cond = "is_fspec(Mode2CodeConfidenceIndicator::FRN_48, fspec, 3)")] 99 | pub mode_2_code_confidence: Option, 100 | // FRN 27: Special Purpose Field 101 | // FRN 28: Reserved Expansion Field 102 | } 103 | -------------------------------------------------------------------------------- /src/fspec.rs: -------------------------------------------------------------------------------- 1 | use crate::FSPEC_IDENT; 2 | 3 | /// Usage in cond for checking if dataitem is to be read, by checking the fspec for the data item 4 | pub fn is_fspec(dataitem_fspec: u8, fspec: &[u8], pos: usize) -> bool { 5 | if pos < fspec.len() { 6 | dataitem_fspec & fspec[pos] != dataitem_fspec 7 | } else { 8 | true 9 | } 10 | } 11 | 12 | /// Remove trailing empty fspec entries 13 | pub fn trim_fspec(fspec: &mut Vec) { 14 | // - find last item in fspec that isn't 00... 15 | let mut remove_indicies = vec![]; 16 | for (n, f) in fspec.iter().rev().enumerate() { 17 | if *f != 0x00 { 18 | break; 19 | } 20 | remove_indicies.push(fspec.len() - n); 21 | } 22 | for i in &remove_indicies { 23 | fspec.remove(*i - 1); 24 | } 25 | } 26 | 27 | /// Add FX bits 28 | pub fn add_fx(fspec: &mut [u8]) { 29 | let fspec_len = fspec.len(); 30 | for f in fspec.iter_mut().take(fspec_len - 1) { 31 | *f |= FSPEC_IDENT 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Encode/Decode for ASTERIX protocol using the deku library 2 | //! 3 | //! # Creating an Asterix packet 4 | //! There are currently two ways of creating an AsterixPacket: 5 | //! 6 | //! ## From `&[u8]` 7 | //! ``` 8 | //! use deku::prelude::*; 9 | //! use asterix::*; 10 | //! use asterix::data_item::*; 11 | //! 12 | //! let bytes = &[0x22, 0x00, 0x0b, 0xf0, 0x19, 0x0d, 0x02, 0x35, 0x6d, 0xfa, 0x60]; 13 | //! let (_, mut packet) = AsterixPacket::from_bytes((bytes, 0)).unwrap(); 14 | //! ``` 15 | //! 16 | //! ## Packet Creation 17 | //! Create an CAT34 Asterix packet. 18 | //! 19 | //! ```rust 20 | //! use deku::prelude::*; 21 | //! use asterix::*; 22 | //! use asterix::data_item::*; 23 | //! use asterix::types::*; 24 | //! 25 | //! let mut thirty_eight = Cat34::default(); 26 | //! thirty_eight.data_source_identifier = Some(DataSourceIdentifier { sac: 25, sic: 13 }); 27 | //! thirty_eight.message_type = Some(MessageType { 28 | //! t: MTYPE::SectorCrossing, 29 | //! }); 30 | //! thirty_eight.time_of_day = Some(TimeOfDay { time: 27355.953 }); 31 | //! thirty_eight.sector_number = Some(SectorNumber { num: 135 }); 32 | //! 33 | //! let mut packet = AsterixPacket::default(); 34 | //! packet.category = 34; 35 | //! packet.messages = vec![asterix::AsterixMessage::Cat34(thirty_eight)]; 36 | //! ``` 37 | //! 38 | //! # Encoding Packets 39 | //! ```rust 40 | //! use deku::prelude::*; 41 | //! use asterix::*; 42 | //! use asterix::data_item::*; 43 | //! 44 | //! // Create / Mutate a packet 45 | //! let mut packet = AsterixPacket::default(); 46 | //! 47 | //! // finalize(): Updates fspec for all packet messages, as well as setting the length as per 48 | //! // the protocol. 49 | //! packet.finalize().unwrap(); 50 | //! 51 | //! // serialize 52 | //! packet.to_bytes().unwrap(); 53 | //! ``` 54 | 55 | use deku::bitvec::{BitVec, Msb0}; 56 | use deku::prelude::*; 57 | 58 | pub mod types; 59 | 60 | mod custom_read_write; 61 | mod modifier; 62 | 63 | mod fourty_eight; 64 | pub use fourty_eight::Cat48; 65 | 66 | mod thirty_four; 67 | pub use thirty_four::Cat34; 68 | 69 | pub mod data_item; 70 | mod fspec; 71 | 72 | /// Size of category + length in bytes 73 | const ASTERIX_HEADER_SIZE: u16 = 3; 74 | 75 | const FSPEC_IDENT: u8 = 0b0000_0001; 76 | 77 | #[derive(Debug, Default, PartialEq, DekuRead, DekuWrite)] 78 | #[deku(endian = "big")] 79 | pub struct AsterixPacket { 80 | /// Category of all `messages` 81 | pub category: u8, 82 | /// Total length of `AsterixPacket` 83 | #[deku(update = "Self::update_len(&mut self.messages)")] 84 | pub length: u16, 85 | /// Asterix Messages 86 | #[deku(bytes_read = "length - ASTERIX_HEADER_SIZE", ctx = "*category")] 87 | pub messages: Vec, 88 | } 89 | 90 | impl AsterixPacket { 91 | /// Update fspec and len 92 | pub fn finalize(&mut self) -> Result<(), DekuError> { 93 | for message in &mut self.messages { 94 | message.update_fspec(); 95 | } 96 | self.update() 97 | } 98 | 99 | /// Read all messages and return byte len 100 | fn update_len(messages: &mut [AsterixMessage]) -> u16 { 101 | let mut len: u16 = 0; 102 | for message in messages.iter_mut() { 103 | let mut bits: BitVec = BitVec::new(); 104 | message.write(&mut bits, (deku::ctx::Endian::Big, 0)).unwrap(); 105 | len += (bits.len() / 8) as u16 + ASTERIX_HEADER_SIZE 106 | } 107 | len 108 | } 109 | } 110 | 111 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 112 | #[deku(id = "category", ctx = "_: deku::ctx::Endian, category: u8")] 113 | /// Union of Asterix categories 114 | pub enum AsterixMessage { 115 | #[deku(id = "34")] 116 | Cat34(Cat34), 117 | #[deku(id = "48")] 118 | Cat48(Cat48), 119 | } 120 | 121 | impl AsterixMessage { 122 | /// Call `update_fpsec` of internal type 123 | pub fn update_fspec(&mut self) { 124 | match self { 125 | Self::Cat34(c) => c.update_fspec(), 126 | Self::Cat48(c) => c.update_fspec(), 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/modifier.rs: -------------------------------------------------------------------------------- 1 | pub fn groundspeed() -> f32 { 2 | 2_f32.powi(-14) 3 | } 4 | 5 | pub fn heading1() -> f32 { 6 | 360.0 / 2_f32.powi(16) 7 | } 8 | 9 | pub fn heading2() -> f32 { 10 | 360.0 / 2_f32.powi(12) 11 | } 12 | -------------------------------------------------------------------------------- /src/thirty_four.rs: -------------------------------------------------------------------------------- 1 | use crate::data_item::{ 2 | AntennaRotationSpeed, CollimationError, DataFilter, DataSourceIdentifier, GenericPolarWindow, 3 | MessageCountValues, MessageType, SectorNumber, SystemConfigurationAndStatus, 4 | SystemProcessingMode, ThreeDPositionOfDataSource, TimeOfDay, 5 | }; 6 | use crate::fspec::{add_fx, is_fspec, trim_fspec}; 7 | use crate::FSPEC_IDENT; 8 | use asterix_derive::UpdateFspec; 9 | use deku::prelude::*; 10 | 11 | /// Transmission of Monoradar Service Messages 12 | #[derive(Debug, Default, PartialEq, DekuRead, DekuWrite, UpdateFspec)] 13 | #[deku(endian = "big")] 14 | pub struct Cat34 { 15 | #[deku(until = "|b: &u8| *b & FSPEC_IDENT == 0")] 16 | pub fspec: Vec, 17 | /// FRN 1 18 | #[deku(skip, cond = "is_fspec(DataSourceIdentifier::FRN_34, fspec, 0)")] 19 | pub data_source_identifier: Option, 20 | /// FRN 2 21 | #[deku(skip, cond = "is_fspec(MessageType::FRN_34, fspec, 0)")] 22 | pub message_type: Option, 23 | /// FRN 3 24 | #[deku(skip, cond = "is_fspec(TimeOfDay::FRN_34, fspec, 0)")] 25 | pub time_of_day: Option, 26 | /// FRN 4 27 | #[deku(skip, cond = "is_fspec(SectorNumber::FRN_34, fspec, 0)")] 28 | pub sector_number: Option, 29 | /// FRN 5 30 | #[deku(skip, cond = "is_fspec(AntennaRotationSpeed::FRN_34, fspec, 0)")] 31 | pub antenna_rotation_speed: Option, 32 | /// FRN 6 33 | #[deku(skip, cond = "is_fspec(SystemConfigurationAndStatus::FRN_34, fspec, 0)")] 34 | pub system_configuration_and_status: Option, 35 | /// FRN 7 36 | #[deku(skip, cond = "is_fspec(SystemProcessingMode::FRN_34, fspec, 0)")] 37 | pub system_processing_mode: Option, 38 | /// FRN 8 39 | #[deku(skip, cond = "is_fspec(MessageCountValues::FRN_34, fspec, 1)")] 40 | pub message_count_values: Option, 41 | /// FRN 9 42 | #[deku(skip, cond = "is_fspec(GenericPolarWindow::FRN_34, fspec, 1)")] 43 | pub generic_polar_window: Option, 44 | /// FRN 10 45 | #[deku(skip, cond = "is_fspec(DataFilter::FRN_34, fspec, 1)")] 46 | pub data_filter: Option, 47 | /// FRN 11 48 | #[deku(skip, cond = "is_fspec(ThreeDPositionOfDataSource::FRN_34, fspec, 1)")] 49 | pub three_d_position_of_data_source: Option, 50 | /// FRN 12 51 | #[deku(skip, cond = "is_fspec(CollimationError::FRN_34, fspec, 1)")] 52 | pub collimation_error: Option, 53 | // FRN 13: Reserved Expansion Field 54 | // FRN 14: Special Purpose Field 55 | } 56 | -------------------------------------------------------------------------------- /src/types.rs: -------------------------------------------------------------------------------- 1 | //! Enums used for providing common meaning for bits in a `data_item` 2 | 3 | use deku::prelude::*; 4 | 5 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 6 | #[deku(type = "u8", bits = "3")] 7 | pub enum TYP { 8 | NoDetection = 0x00, 9 | SinglePSRDetection = 0x01, 10 | SingleSSRDetection = 0x02, 11 | SSRPlusPSRDetection = 0x03, 12 | SingleModeSAllCall = 0x04, 13 | SingleModeSRollCall = 0x05, 14 | ModeSAllCallPlusPSR = 0x06, 15 | ModeSRollCallPlusPSR = 0x07, 16 | } 17 | 18 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 19 | #[deku(type = "u8", bits = "1")] 20 | pub enum SIM { 21 | ActualTargetReport = 0x00, 22 | SimulatedTargetReport = 0x01, 23 | } 24 | 25 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 26 | #[deku(type = "u8", bits = "1")] 27 | pub enum RDP { 28 | ReportFromRDPChain1 = 0x00, 29 | ReportFromRDPChain2 = 0x01, 30 | } 31 | 32 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 33 | #[deku(type = "u8", bits = "1")] 34 | pub enum SPI { 35 | AbsenceOfSPI = 0x00, 36 | SpecialPositionIdentification = 0x01, 37 | } 38 | 39 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 40 | #[deku(type = "u8", bits = "1")] 41 | pub enum RAB { 42 | ReportFromAircraftTransponder = 0x00, 43 | ReportFromFieldMonitor = 0x01, 44 | } 45 | 46 | #[derive(Debug, PartialEq, Clone, Copy, DekuRead, DekuWrite)] 47 | #[deku(type = "u8", bits = "1")] 48 | pub enum FX { 49 | EndOfDataItem = 0x00, 50 | ExtensionIntoFirstExtent = 0x01, 51 | } 52 | 53 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 54 | #[deku(type = "u8", bits = "1")] 55 | pub enum V { 56 | CodeValidated = 0x00, 57 | CodeNotValidated = 0x01, 58 | } 59 | 60 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 61 | #[deku(type = "u8", bits = "1")] 62 | pub enum G { 63 | Default = 0x00, 64 | GarbledCode = 0x01, 65 | } 66 | 67 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 68 | #[deku(type = "u8", bits = "1")] 69 | pub enum L { 70 | Mode3CodeDerivedFromTheReplyOfTheTransponder = 0x00, 71 | Mode3CodeNotExtractedDuringTheLastScan = 0x01, 72 | } 73 | 74 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 75 | #[deku(type = "u8", bits = "1")] 76 | pub enum CNF { 77 | ConfirmedTrack = 0x00, 78 | TentativeTrack = 0x01, 79 | } 80 | 81 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 82 | #[deku(type = "u8", bits = "2")] 83 | pub enum RAD { 84 | CombinedTrack = 0x00, 85 | PSRTrack = 0x01, 86 | SSRModeSTrack = 0x02, 87 | Invalid = 0x03, 88 | } 89 | 90 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 91 | #[deku(type = "u8", bits = "1")] 92 | pub enum DOU { 93 | NormalConfidence = 0x00, 94 | LowConfidence = 0x01, 95 | } 96 | 97 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 98 | #[deku(type = "u8", bits = "1")] 99 | pub enum MAH { 100 | NoHorizontalManSensed = 0x00, 101 | HorizontalManSensed = 0x01, 102 | } 103 | 104 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 105 | #[deku(type = "u8", bits = "2")] 106 | pub enum CDM { 107 | Maintaining = 0x00, 108 | Climbing = 0x01, 109 | Descending = 0x02, 110 | Unknown = 0x03, 111 | } 112 | 113 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 114 | #[deku(type = "u8", bits = "1")] 115 | pub enum TRE { 116 | TrackStillAlive = 0x00, 117 | EndOfTrackLifetime = 0x01, 118 | } 119 | 120 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 121 | #[deku(type = "u8", bits = "1")] 122 | pub enum GHO { 123 | TrueTargetTrack = 0x00, 124 | GhostTargetTrack = 0x01, 125 | } 126 | 127 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 128 | #[deku(type = "u8", bits = "1")] 129 | pub enum SUP { 130 | No = 0x00, 131 | Yes = 0x01, 132 | } 133 | 134 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 135 | #[deku(type = "u8", bits = "1")] 136 | pub enum TCC { 137 | RadarPlanePlotTransformation = 0x00, 138 | SlantRangePlotTransformation = 0x01, 139 | } 140 | 141 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 142 | #[deku(type = "u8", bits = "3")] 143 | pub enum COM { 144 | NoCommunicationsSurveillanceOnly = 0x00, 145 | CommACommB = 0x01, 146 | CommACommBUplinkELM = 0x02, 147 | CommACommBUplinkELMDownlinkELM = 0x03, 148 | Top5TransponderCapability = 0x04, 149 | #[deku(id_pat = "0x05..=0x07")] 150 | NoAssigned, 151 | } 152 | 153 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 154 | #[deku(type = "u8", bits = "3")] 155 | pub enum STAT { 156 | NoAlertNoSPIAircraftAirborne = 0x00, 157 | NoAlertNoSPIAircraftOnGround = 0x01, 158 | AlertNoSPIAircraftAirborne = 0x02, 159 | AlertNoSPIAircraftOnGround = 0x03, 160 | AlertSPIAircraftAirborneOrOnGround = 0x04, 161 | NoAlertSPIAircraftAirborneOrOnGround = 0x05, 162 | NotAssigned = 0x06, 163 | Unknown = 0x07, 164 | } 165 | 166 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 167 | #[deku(type = "u8", bits = "1")] 168 | pub enum SI { 169 | SICodeCapable = 0x00, 170 | IICodeCapable = 0x01, 171 | } 172 | 173 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 174 | #[deku(type = "u8", bits = "1")] 175 | pub enum MSSC { 176 | No = 0x00, 177 | Yes = 0x01, 178 | } 179 | 180 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 181 | #[deku(type = "u8", bits = "1")] 182 | pub enum ARC { 183 | Resolution100ft = 0x00, 184 | Resolution25ft = 0x01, 185 | } 186 | 187 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 188 | #[deku(type = "u8", bits = "1")] 189 | pub enum AIC { 190 | No = 0x00, 191 | Yes = 0x01, 192 | } 193 | 194 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 195 | #[deku(type = "u8", bits = "8")] 196 | pub enum MTYPE { 197 | NorthMarker = 0x01, 198 | SectorCrossing = 0x02, 199 | GeographicaFiltering = 0x03, 200 | JammingStrobe = 0x04, 201 | } 202 | 203 | #[derive(Debug, PartialEq, Clone, Copy, DekuRead, DekuWrite)] 204 | #[deku(type = "u16", bits = "7")] 205 | pub enum CODE { 206 | #[deku(id = "0")] 207 | NotDefined, 208 | #[deku(id = "1")] 209 | MultipathReply, 210 | #[deku(id = "2")] 211 | ReplySidelobeInterrogationReception, 212 | #[deku(id = "3")] 213 | SplitPlot, 214 | #[deku(id = "4")] 215 | SecondTimeAroundReply, 216 | #[deku(id = "5")] 217 | Angel, 218 | #[deku(id = "6")] 219 | SlowMovingTarget, 220 | #[deku(id = "7")] 221 | FixedPSRPlot, 222 | #[deku(id = "8")] 223 | SlowPSRPlot, 224 | #[deku(id = "9")] 225 | LowQualityPSRPlot, 226 | #[deku(id = "10")] 227 | PhantomSSRPlot, 228 | #[deku(id = "11")] 229 | NonMatchingMode3ACode, 230 | #[deku(id = "12")] 231 | ModeCCodeModeSAbnormal, 232 | #[deku(id = "13")] 233 | TargetInClutter, 234 | #[deku(id = "14")] 235 | MaximumDopplerREsponseInZeroFilter, 236 | #[deku(id = "15")] 237 | TransponderAnomalyDetected, 238 | #[deku(id = "16")] 239 | DuplicatedOrIllegalModeSAircraftAddress, 240 | #[deku(id = "17")] 241 | ModeSErrorCorrectionApplied, 242 | #[deku(id = "18")] 243 | UndecodableModeCSCode, 244 | #[deku(id = "19")] 245 | Birds, 246 | #[deku(id = "20")] 247 | FlockOfBirds, 248 | #[deku(id = "21")] 249 | Mode1PresentOriginalReply, 250 | #[deku(id = "22")] 251 | Mode2PresentOriginalReply, 252 | #[deku(id = "23")] 253 | PlotCausedByWindTurbine, 254 | #[deku(id = "24")] 255 | Helicopter, 256 | #[deku(id = "25")] 257 | MaxiumumNumberInterrogationsSurveillance, 258 | #[deku(id = "26")] 259 | MaxiumumNumberInterrogationsBDS, 260 | #[deku(id = "27")] 261 | BDSOverlayIncoherence, 262 | #[deku(id = "28")] 263 | PotentialBDSSwapDetected, 264 | #[deku(id = "29")] 265 | TrackUpdateZenithalGap, 266 | #[deku(id = "30")] 267 | ModeSTrackReAquired, 268 | #[deku(id = "31")] 269 | DuplicatedMode5PairNoPinDetected, 270 | #[deku(id = "32")] 271 | WrongDFReplyFormatDetected, 272 | #[deku(id = "33")] 273 | TransponderAnomalyMs, 274 | #[deku(id = "34")] 275 | TransponderAnomalySI, 276 | #[deku(id = "35")] 277 | PotentialICConflict, 278 | #[deku(id = "36")] 279 | ICConflictDetectionPossible, 280 | #[deku(id_pat = "37..=63")] 281 | AMGAllocated(u16), 282 | #[deku(id_pat = "64..=127")] 283 | Manufacturer(u16), 284 | } 285 | 286 | #[derive(Debug, PartialEq, Clone, Copy, DekuRead, DekuWrite)] 287 | #[deku(type = "u8", bits = "1")] 288 | pub enum D { 289 | Valid = 0, 290 | Doubtful = 1, 291 | } 292 | 293 | #[derive(Debug, PartialEq, Clone, Copy, DekuRead, DekuWrite)] 294 | #[deku(type = "u8", bits = "1")] 295 | /// Operational Release Status of the System 296 | pub enum NOGO { 297 | SystemIsReleasedForOperationalUse = 0, 298 | OperationalUseOfSystemIsInhibited = 1, 299 | } 300 | 301 | #[derive(Debug, PartialEq, Clone, Copy, DekuRead, DekuWrite)] 302 | #[deku(type = "u8", bits = "1")] 303 | /// Radar Data Processor Chain Selection Status 304 | pub enum RDPC { 305 | RDPC1Selected = 0, 306 | RDPC2Selected = 1, 307 | } 308 | 309 | #[derive(Debug, PartialEq, Clone, Copy, DekuRead, DekuWrite)] 310 | #[deku(type = "u8", bits = "1")] 311 | /// Event to signal a reset/restart of the selected Radar Data Processor Chain, 312 | /// i.e. expect a new assignment of track numbers 313 | pub enum RDPR { 314 | DefaultSituation = 0, 315 | ResetOfRDPC = 1, 316 | } 317 | 318 | #[derive(Debug, PartialEq, Clone, Copy, DekuRead, DekuWrite)] 319 | #[deku(type = "u8", bits = "1")] 320 | /// Monitoring System Connected Status 321 | pub enum MSC { 322 | MonitoringSystemConnected = 0, 323 | MonitoringSystemDisconnected = 1, 324 | } 325 | 326 | #[derive(Debug, PartialEq, Clone, Copy, DekuRead, DekuWrite)] 327 | #[deku(type = "u8", bits = "1")] 328 | /// Time Source Validity 329 | pub enum TSV { 330 | Valid = 0, 331 | Invalid = 1, 332 | } 333 | 334 | #[derive(Debug, PartialEq, Clone, Copy, DekuRead, DekuWrite)] 335 | #[deku(type = "u8", bits = "1")] 336 | /// Selected Antenna 337 | pub enum ANT { 338 | Antenna1 = 0, 339 | Antenna2 = 1, 340 | } 341 | 342 | #[derive(Debug, PartialEq, Clone, Copy, DekuRead, DekuWrite)] 343 | #[deku(type = "u8", bits = "2")] 344 | /// Channel A/B Selection Status 345 | pub enum CHAB { 346 | NoChannelSelected = 0b00, 347 | ChannelAOnlySelected = 0b01, 348 | ChannelBOnlySelected = 0b10, 349 | DiversityMode = 0b11, 350 | } 351 | 352 | #[derive(Debug, PartialEq, Clone, Copy, DekuRead, DekuWrite)] 353 | #[deku(type = "u8", bits = "1")] 354 | /// Overload Condition 355 | pub enum OVL { 356 | NoOverload = 0, 357 | Overload = 1, 358 | } 359 | 360 | #[derive(Debug, PartialEq, Clone, Copy, DekuRead, DekuWrite)] 361 | #[deku(type = "u8", bits = "1")] 362 | /// Channel A/B selection status for Surveillance Co-ordination Function 363 | pub enum SCF { 364 | ChannelAInUse = 0, 365 | ChannelBInUse = 1, 366 | } 367 | 368 | #[derive(Debug, PartialEq, Clone, Copy, DekuRead, DekuWrite)] 369 | #[deku(type = "u8", bits = "1")] 370 | /// Channel A/B selection status for Data Link Function 371 | pub enum DLF { 372 | ChannelAInUse = 0, 373 | ChannelBInUse = 1, 374 | } 375 | 376 | #[derive(Debug, PartialEq, Clone, Copy, DekuRead, DekuWrite)] 377 | #[deku(type = "u8", bits = "3")] 378 | pub enum RED { 379 | NoReductionActive = 0b000, 380 | ReductionStep1Active = 0b001, 381 | ReductionStep2Active = 0b010, 382 | ReductionStep3Active = 0b011, 383 | ReductionStep4Active = 0b100, 384 | ReductionStep5Active = 0b101, 385 | ReductionStep6Active = 0b110, 386 | ReductionStep7Active = 0b111, 387 | } 388 | 389 | #[derive(Debug, PartialEq, Clone, Copy, DekuRead, DekuWrite)] 390 | #[deku(type = "u8", bits = "1")] 391 | pub enum POL { 392 | LinearPolarization = 0, 393 | CircularPolarization = 1, 394 | } 395 | 396 | #[derive(Debug, PartialEq, Clone, Copy, DekuRead, DekuWrite)] 397 | #[deku(type = "u8", bits = "1")] 398 | pub enum STC { 399 | STCMap1 = 0b00, 400 | STCMap2 = 0b01, 401 | STCMap3 = 0b10, 402 | STCMap4 = 0b11, 403 | } 404 | 405 | #[derive(Debug, PartialEq, Clone, Copy, DekuRead, DekuWrite)] 406 | #[deku(type = "u8", bits = "1")] 407 | pub enum CLU { 408 | Autonomous = 0, 409 | #[deku(id = "1")] 410 | NotAutonomous = 1, 411 | } 412 | 413 | #[derive(Debug, PartialEq, Clone, Copy, DekuRead, DekuWrite)] 414 | #[deku(type = "u8", bits = "5")] 415 | pub enum MessageCounterTYP { 416 | NoDetection = 0, 417 | SinglePSRTargetReports = 1, 418 | SingleSSRTargetReports = 2, 419 | SSRPSRTargetReports = 3, 420 | SingleAllCallTargetReports = 4, 421 | SingleRollCallTargetReports = 5, 422 | AllCallPSRModeSTargetReports = 6, 423 | RollCallPSRModeSTargetReports = 7, 424 | FilterForWeatherData = 8, 425 | FilterForJammingStrobe = 9, 426 | FilterPSRData = 10, 427 | FilterSSRModeSData = 11, 428 | FilterSSRModeSPSRData = 12, 429 | FilterForEnhancedSuveillanceData = 13, 430 | FilterForPSREnhancedSurveillance = 14, 431 | FilterForPSREnhancedSurveillancePlusSSRModeSDataNotInAreaOfPrimeInterest = 15, 432 | FilterForPSREnhancedSurveillancePlusAllSSRModeSData = 16, 433 | } 434 | 435 | #[derive(Debug, PartialEq, Clone, Copy, DekuRead, DekuWrite)] 436 | #[deku(type = "u8", bits = "8")] 437 | pub enum DataFilterTYP { 438 | InvalidValue = 0, 439 | FilterWeatherData = 1, 440 | FilterJammingStrobe = 2, 441 | FilterPSRData = 3, 442 | FilterSSRModeSData = 4, 443 | FilterSSRModeSPSRData = 5, 444 | EnhancedSurveillanceData = 6, 445 | FilterPSREnhancedSurveillanceData = 7, 446 | FilterPSREnhancedSurveillanceSSRModeSDataNotInAreaOfPrimeInterest = 8, 447 | FilterPSREnhancedSurveillanceAllSSRModeSData = 9, 448 | } 449 | 450 | #[derive(Debug, PartialEq, Clone, Copy, DekuRead, DekuWrite)] 451 | #[deku(type = "u8", bits = "1")] 452 | pub enum TST { 453 | RealTargetReport = 0, 454 | TestTargetReport = 1, 455 | } 456 | 457 | #[derive(Debug, PartialEq, Clone, Copy, DekuRead, DekuWrite)] 458 | #[deku(type = "u8", bits = "1")] 459 | pub enum ERR { 460 | NoExtendedRange = 0, 461 | ExtendedRangePresent = 1, 462 | } 463 | 464 | #[derive(Debug, PartialEq, Clone, Copy, DekuRead, DekuWrite)] 465 | #[deku(type = "u8", bits = "1")] 466 | pub enum XPP { 467 | NoXPulsePresent = 0, 468 | XPulsePresent = 1, 469 | } 470 | 471 | #[derive(Debug, PartialEq, Clone, Copy, DekuRead, DekuWrite)] 472 | #[deku(type = "u8", bits = "1")] 473 | pub enum ME { 474 | NoMilitaryEmergency = 0, 475 | MilitaryEmergency = 1, 476 | } 477 | 478 | #[derive(Debug, PartialEq, Clone, Copy, DekuRead, DekuWrite)] 479 | #[deku(type = "u8", bits = "1")] 480 | pub enum MI { 481 | NoMilitaryIdentification = 0, 482 | MilitaryIdentification = 1, 483 | } 484 | 485 | #[derive(Debug, PartialEq, Clone, Copy, DekuRead, DekuWrite)] 486 | #[deku(type = "u8", bits = "2")] 487 | pub enum FOEFRI { 488 | NoMode4Interrogation = 0b00, 489 | FriendlyTarget = 0b01, 490 | UnknownTarget = 0b10, 491 | NoReply = 0b11, 492 | } 493 | -------------------------------------------------------------------------------- /tests/public_api.rs: -------------------------------------------------------------------------------- 1 | use assert_hex::assert_eq_hex; 2 | use asterix::data_item::{ 3 | CodeFx, DataSourceIdentifier, HeightMeasuredBy3dRadar, MBData, MessageType, 4 | Mode3ACodeConfidenceIndicator, ModeCCodeAndConfidenceIndicator, SectorNumber, TimeOfDay, 5 | TrackQuality, WarningErrorConditionsTargetClass, 6 | }; 7 | use asterix::types::{ 8 | AIC, ARC, CDM, CNF, CODE, COM, DOU, FX, G, GHO, L, MAH, MSSC, MTYPE, RAB, RAD, RDP, SI, SIM, 9 | SPI, STAT, SUP, TCC, TRE, TYP, V, 10 | }; 11 | use asterix::{AsterixMessage, AsterixPacket, Cat34, Cat48}; 12 | use deku::{DekuContainerRead, DekuContainerWrite}; 13 | 14 | #[test] 15 | fn it_works() { 16 | let bytes = vec![ 17 | 0x30, 0x00, 0x30, 0xfd, 0xf7, 0x02, 0x19, 0xc9, 0x35, 0x6d, 0x4d, 0xa0, 0xc5, 0xaf, 0xf1, 18 | 0xe0, 0x02, 0x00, 0x05, 0x28, 0x3c, 0x66, 0x0c, 0x10, 0xc2, 0x36, 0xd4, 0x18, 19 | //0x20 in wireshark, but last 6 bits don't matter and will 0x00 by writer 20 | 0x00, 0x01, 0xc0, 0x78, 0x00, 0x31, 0xbc, 0x00, 0x00, 0x40, 0x0d, 0xeb, 0x07, 0xb9, 0x58, 21 | 0x2e, 0x41, 0x00, 0x20, 0xf5, 22 | ]; 23 | let (_, mut packet) = AsterixPacket::from_bytes((&bytes, 0)).unwrap(); 24 | 25 | assert_eq!(packet.category, 48); 26 | assert_eq!(packet.length, 48); 27 | 28 | // TODO check NONE values after more are implemented 29 | if let AsterixMessage::Cat48(ref mut message) = packet.messages[0] { 30 | assert_eq_hex!(message.fspec, &[0xfd, 0xf7, 0x02]); 31 | 32 | let data_source_identifier = message.data_source_identifier.as_ref().unwrap(); 33 | assert_eq!(data_source_identifier.sac, 25); 34 | assert_eq!(data_source_identifier.sic, 201); 35 | 36 | let time_of_day = message.time_of_day.as_ref().unwrap(); 37 | assert_eq!(time_of_day.time, 27354.602); 38 | 39 | let target_report_descriptor = message.target_report_descriptor.as_ref().unwrap(); 40 | assert_eq!(target_report_descriptor.typ, TYP::SingleModeSRollCall); 41 | assert_eq!(target_report_descriptor.sim, SIM::ActualTargetReport); 42 | assert_eq!(target_report_descriptor.rdp, RDP::ReportFromRDPChain1); 43 | assert_eq!(target_report_descriptor.spi, SPI::AbsenceOfSPI); 44 | assert_eq!(target_report_descriptor.rab, RAB::ReportFromAircraftTransponder); 45 | assert_eq!(target_report_descriptor.fx1, FX::EndOfDataItem); 46 | 47 | let measured_position_in_polar_coordinates = 48 | message.measured_position_in_polar_coordinates.as_ref().unwrap(); 49 | assert_eq!(measured_position_in_polar_coordinates.rho, 197.6836); 50 | assert_eq!(measured_position_in_polar_coordinates.theta, 340.13672); 51 | 52 | let mode_3_a_code_in_octal_representation = 53 | message.mode_3_a_code_in_octal_representation.as_ref().unwrap(); 54 | assert_eq!(mode_3_a_code_in_octal_representation.v, V::CodeValidated); 55 | assert_eq!(mode_3_a_code_in_octal_representation.g, G::Default); 56 | assert_eq!( 57 | mode_3_a_code_in_octal_representation.l, 58 | L::Mode3CodeDerivedFromTheReplyOfTheTransponder 59 | ); 60 | //TODO add squawk? 61 | 62 | let flight_level_in_binary_repre = message.flight_level_in_binary_repre.as_ref().unwrap(); 63 | assert_eq!(flight_level_in_binary_repre.v, V::CodeValidated); 64 | assert_eq!(flight_level_in_binary_repre.g, G::Default); 65 | assert_eq!(flight_level_in_binary_repre.flight_level, 330); 66 | 67 | let aircraft_address = message.aircraft_address.as_ref().unwrap(); 68 | assert_eq!(aircraft_address.address, 0x003c_660c); 69 | 70 | let aircraft_identification = message.aircraft_identification.as_ref().unwrap(); 71 | assert_eq!(aircraft_identification.identification, "DLH65A "); 72 | 73 | let mode_smb_data = message.mode_smb_data.as_ref().unwrap(); 74 | assert_eq!(mode_smb_data.count, 1); 75 | assert_eq_hex!( 76 | mode_smb_data.mb_data, 77 | vec![MBData { data: [0xc0, 0x78, 0x00, 0x31, 0xbc, 0x00, 0x00].to_vec() }] 78 | ); 79 | // TODO assert BDS1 80 | // TODO assert BDS2 81 | 82 | let track_number = message.track_number.as_ref().unwrap(); 83 | assert_eq!(track_number.number, 3563); 84 | 85 | let calculated_track_velocity = message.calculated_track_velocity.as_ref().unwrap(); 86 | assert_eq!(calculated_track_velocity.groundspeed, 0.120_666_504); 87 | assert_eq!(calculated_track_velocity.heading, 124.002_686); 88 | 89 | let track_status = message.track_status.as_ref().unwrap(); 90 | assert_eq!(track_status.cnf, CNF::ConfirmedTrack); 91 | assert_eq!(track_status.rad, RAD::SSRModeSTrack); 92 | assert_eq!(track_status.dou, DOU::NormalConfidence); 93 | assert_eq!(track_status.mah, MAH::NoHorizontalManSensed); 94 | assert_eq!(track_status.cdm, CDM::Maintaining); 95 | assert_eq!(track_status.fx1, FX::ExtensionIntoFirstExtent); 96 | assert_eq!(track_status.tre, Some(TRE::TrackStillAlive)); 97 | assert_eq!(track_status.gho, Some(GHO::TrueTargetTrack)); 98 | assert_eq!(track_status.sup, Some(SUP::No)); 99 | assert_eq!(track_status.tcc, Some(TCC::RadarPlanePlotTransformation)); 100 | assert_eq!(track_status.fx2, Some(FX::EndOfDataItem)); 101 | 102 | let communications_capability_flight_status = 103 | message.communications_capability_flight_status.as_ref().unwrap(); 104 | assert_eq!(communications_capability_flight_status.com, COM::CommACommB); 105 | assert_eq!( 106 | communications_capability_flight_status.stat, 107 | STAT::NoAlertNoSPIAircraftAirborne 108 | ); 109 | assert_eq!(communications_capability_flight_status.si, SI::SICodeCapable); 110 | assert_eq!(communications_capability_flight_status.mssc, MSSC::Yes); 111 | assert_eq!(communications_capability_flight_status.arc, ARC::Resolution25ft); 112 | assert_eq!(communications_capability_flight_status.aic, AIC::Yes); 113 | assert_eq!(communications_capability_flight_status.b1a, 1); 114 | assert_eq!(communications_capability_flight_status.b1b, 5); 115 | } else { 116 | unreachable!("Message is not CAT48"); 117 | } 118 | 119 | packet.finalize().unwrap(); 120 | 121 | // Confirm packet back to bytes 122 | assert_eq_hex!(packet.to_bytes(), Ok(bytes)); 123 | } 124 | 125 | #[test] 126 | /// Remove communications_capability_flight_status and update fspec, making sure to check if the 127 | /// fspec works as well as the communications_capability_flight_status is none 128 | fn it_works_only_two_fspec() { 129 | let bytes = vec![ 130 | 0x30, 0x00, 0x2d, 0xfd, 0xf6, 0x19, 0xc9, 0x35, 0x6d, 0x4d, 0xa0, 0xc5, 0xaf, 0xf1, 0xe0, 131 | 0x02, 0x00, 0x05, 0x28, 0x3c, 0x66, 0x0c, 0x10, 0xc2, 0x36, 0xd4, 0x18, 0x00, 0x01, 0xc0, 132 | 0x78, 0x00, 0x31, 0xbc, 0x00, 0x00, 0x40, 0x0d, 0xeb, 0x07, 0xb9, 0x58, 0x2e, 0x41, 0x00, 133 | ]; 134 | let (_, mut packet) = AsterixPacket::from_bytes((&bytes, 0)).unwrap(); 135 | 136 | assert_eq!(packet.category, 48); 137 | assert_eq!(packet.length, 45); 138 | 139 | if let AsterixMessage::Cat48(ref message) = packet.messages[0] { 140 | assert_eq!(message.fspec, &[0xfd, 0xf6]); 141 | // everything here is checked in the above test 142 | assert!(message.communications_capability_flight_status.is_none()); 143 | } else { 144 | unreachable!("Message is not CAT48"); 145 | } 146 | 147 | packet.finalize().unwrap(); 148 | 149 | // Confirm packet back to bytes 150 | assert_eq_hex!(packet.to_bytes(), Ok(bytes)); 151 | } 152 | 153 | #[test] 154 | fn third_packet() { 155 | let bytes = vec![ 156 | 0x30, 0x00, 0x37, 0xff, 0xff, 0x02, 0x19, 0x0d, 0x35, 0x6d, 0xee, 0xa0, 0xc2, 0xd3, 0x5b, 157 | 0x90, 0x04, 0xc3, 0x05, 0xa0, 0xe0, 0x56, 0x0b, 0xb8, 0x4b, 0xaa, 0xcd, 0x50, 0x86, 0x79, 158 | 0x51, 0x88, 0x00, 0x01, 0xc6, 0x56, 0x32, 0xb0, 0xa8, 0x00, 0x00, 0x40, 0x01, 0xe2, 0x4b, 159 | 0xf6, 0xc3, 0x04, 0x08, 0x1e, 0xbb, 0x73, 0x40, 0x20, 0xf5, 160 | ]; 161 | // TODO: Add CAT034 162 | let (_, mut packet) = AsterixPacket::from_bytes((&bytes, 0)).unwrap(); 163 | assert_eq!(packet.category, 48); 164 | assert_eq!(packet.length, 55); 165 | 166 | // TODO check NONE values after more are implemented 167 | if let AsterixMessage::Cat48(ref message) = packet.messages[0] { 168 | assert_eq_hex!(message.fspec, &[0xff, 0x0ff, 0x02]); 169 | 170 | let data_source_identifier = message.data_source_identifier.as_ref().unwrap(); 171 | assert_eq!(data_source_identifier.sac, 25); 172 | assert_eq!(data_source_identifier.sic, 13); 173 | 174 | let time_of_day = message.time_of_day.as_ref().unwrap(); 175 | assert_eq!(time_of_day.time, 27_355.86); 176 | 177 | let target_report_descriptor = message.target_report_descriptor.as_ref().unwrap(); 178 | assert_eq!(target_report_descriptor.typ, TYP::SingleModeSRollCall); 179 | assert_eq!(target_report_descriptor.sim, SIM::ActualTargetReport); 180 | assert_eq!(target_report_descriptor.rdp, RDP::ReportFromRDPChain1); 181 | assert_eq!(target_report_descriptor.spi, SPI::AbsenceOfSPI); 182 | assert_eq!(target_report_descriptor.rab, RAB::ReportFromAircraftTransponder); 183 | assert_eq!(target_report_descriptor.fx1, FX::EndOfDataItem); 184 | 185 | let measured_position_in_polar_coordinates = 186 | message.measured_position_in_polar_coordinates.as_ref().unwrap(); 187 | assert_eq!(measured_position_in_polar_coordinates.rho, 194.824_22); 188 | assert_eq!(measured_position_in_polar_coordinates.theta, 128.759_77); 189 | 190 | let mode_3_a_code_in_octal_representation = 191 | message.mode_3_a_code_in_octal_representation.as_ref().unwrap(); 192 | assert_eq!(mode_3_a_code_in_octal_representation.v, V::CodeValidated); 193 | assert_eq!(mode_3_a_code_in_octal_representation.g, G::Default); 194 | assert_eq!( 195 | mode_3_a_code_in_octal_representation.l, 196 | L::Mode3CodeDerivedFromTheReplyOfTheTransponder 197 | ); 198 | //TODO add squawk? 199 | 200 | let flight_level_in_binary_repre = message.flight_level_in_binary_repre.as_ref().unwrap(); 201 | assert_eq!(flight_level_in_binary_repre.v, V::CodeValidated); 202 | assert_eq!(flight_level_in_binary_repre.g, G::Default); 203 | assert_eq!(flight_level_in_binary_repre.flight_level, 360); 204 | 205 | let aircraft_address = message.aircraft_address.as_ref().unwrap(); 206 | assert_eq!(aircraft_address.address, 0x004b_aacd); 207 | 208 | let aircraft_identification = message.aircraft_identification.as_ref().unwrap(); 209 | assert_eq!(aircraft_identification.identification, "THY9TX "); 210 | 211 | let mode_smb_data = message.mode_smb_data.as_ref().unwrap(); 212 | assert_eq!(mode_smb_data.count, 1); 213 | assert_eq!( 214 | mode_smb_data.mb_data, 215 | vec![MBData { data: [0xc6, 0x56, 0x32, 0xb0, 0xa8, 0x00, 0x00].to_vec() }] 216 | ); 217 | assert_eq!(mode_smb_data.bds1, 4); 218 | assert_eq!(mode_smb_data.bds2, 0); 219 | 220 | let track_number = message.track_number.as_ref().unwrap(); 221 | assert_eq!(track_number.number, 482); 222 | 223 | let cal_pos_cartesian_coor = message.calculated_position_cartesian_coor.as_ref().unwrap(); 224 | assert_eq!(cal_pos_cartesian_coor.x, 151.921_88); 225 | assert_eq!(cal_pos_cartesian_coor.y, -121.96875); 226 | 227 | let calculated_track_velocity = message.calculated_track_velocity.as_ref().unwrap(); 228 | assert_eq!(calculated_track_velocity.groundspeed, 0.126_831_05); 229 | assert_eq!(calculated_track_velocity.heading, 263.600_46); 230 | 231 | let track_status = message.track_status.as_ref().unwrap(); 232 | assert_eq!(track_status.cnf, CNF::ConfirmedTrack); 233 | assert_eq!(track_status.rad, RAD::SSRModeSTrack); 234 | assert_eq!(track_status.dou, DOU::NormalConfidence); 235 | assert_eq!(track_status.mah, MAH::NoHorizontalManSensed); 236 | assert_eq!(track_status.cdm, CDM::Maintaining); 237 | assert_eq!(track_status.fx1, FX::EndOfDataItem); 238 | assert_eq!(track_status.tre, None); 239 | assert_eq!(track_status.gho, None); 240 | assert_eq!(track_status.sup, None); 241 | assert_eq!(track_status.tcc, None); 242 | assert_eq!(track_status.fx2, None); 243 | 244 | let communications_capability_flight_status = 245 | message.communications_capability_flight_status.as_ref().unwrap(); 246 | assert_eq!(communications_capability_flight_status.com, COM::CommACommB); 247 | assert_eq!( 248 | communications_capability_flight_status.stat, 249 | STAT::NoAlertNoSPIAircraftAirborne 250 | ); 251 | assert_eq!(communications_capability_flight_status.si, SI::SICodeCapable); 252 | assert_eq!(communications_capability_flight_status.mssc, MSSC::Yes); 253 | assert_eq!(communications_capability_flight_status.arc, ARC::Resolution25ft); 254 | assert_eq!(communications_capability_flight_status.aic, AIC::Yes); 255 | assert_eq!(communications_capability_flight_status.b1a, 1); 256 | assert_eq!(communications_capability_flight_status.b1b, 5); 257 | } else { 258 | unreachable!("Message is not CAT48"); 259 | } 260 | packet.finalize().unwrap(); 261 | assert_eq_hex!(packet.to_bytes(), Ok(bytes)); 262 | } 263 | 264 | #[test] 265 | fn test_34() { 266 | let bytes = vec![0x22, 0x00, 0x0b, 0xf0, 0x19, 0x0d, 0x02, 0x35, 0x6d, 0xfa, 0x60]; 267 | let (_, mut packet) = AsterixPacket::from_bytes((&bytes, 0)).unwrap(); 268 | 269 | assert_eq!(packet.category, 34); 270 | assert_eq!(packet.length, 11); 271 | 272 | // TODO check NONE values after more are implemented 273 | if let AsterixMessage::Cat34(ref message) = packet.messages[0] { 274 | assert_eq_hex!(message.fspec, &[0xf0]); 275 | 276 | let data_source_identifier = message.data_source_identifier.as_ref().unwrap(); 277 | assert_eq!(data_source_identifier.sac, 25); 278 | assert_eq!(data_source_identifier.sic, 13); 279 | 280 | let message_type = message.message_type.as_ref().unwrap(); 281 | assert_eq!(message_type.t, MTYPE::SectorCrossing); 282 | 283 | let time_of_day = message.time_of_day.as_ref().unwrap(); 284 | assert_eq!(time_of_day.time, 27355.953); 285 | 286 | let sector_number = message.sector_number.as_ref().unwrap(); 287 | assert_eq!(sector_number.num, 135); 288 | } else { 289 | unreachable!("Not Cat 34"); 290 | } 291 | packet.finalize().unwrap(); 292 | assert_eq_hex!(packet.to_bytes(), Ok(bytes)); 293 | } 294 | 295 | #[test] 296 | fn test_four_messages() { 297 | // Example of having multiple asterix messages being received in one packet, this requires one to 298 | // parse the first messages, and parsing until the rest.len() == 0 299 | let bytes = vec![ 300 | // Cat 048 301 | 0x30, 0x00, 0xb9, 0xe1, 0x93, 0x02, 0x19, 0x0d, 0x35, 0x64, 0x21, 0x00, 0x44, 0xd0, 0x74, 302 | 0x02, 0xda, 0x41, 0x80, 0x20, 0xf5, 0xff, 0xff, 0x02, 0x19, 0x0d, 0x35, 0x6e, 0x06, 0xa0, 303 | 0x2b, 0x4d, 0x65, 0x1e, 0x04, 0x18, 0x05, 0xa0, 0xe0, 0x56, 0x0c, 0xcf, 0x46, 0x92, 0xd1, 304 | 0x04, 0x51, 0x72, 0x09, 0x28, 0x20, 0x02, 0xc6, 0x50, 0x00, 0x30, 0x7c, 0x00, 0x00, 0x40, 305 | 0xf0, 0x09, 0xf7, 0x2f, 0xa0, 0x64, 0x02, 0x60, 0x02, 0xf9, 0x0d, 0x46, 0xee, 0xe5, 0x07, 306 | 0xdc, 0xe1, 0xb5, 0x40, 0x20, 0xfd, 0xff, 0xff, 0x02, 0x19, 0x0d, 0x35, 0x6d, 0xfd, 0xa0, 307 | 0x63, 0xd3, 0x61, 0x68, 0x0e, 0xaa, 0x05, 0xa0, 0xe0, 0x56, 0x0c, 0xc2, 0x3c, 0x64, 0x8a, 308 | 0x10, 0xc2, 0x36, 0xe7, 0x58, 0x20, 0x01, 0xc6, 0x50, 0x00, 0x30, 0xb8, 0x00, 0x00, 0x40, 309 | 0x05, 0x9b, 0x22, 0x10, 0xdb, 0x84, 0x07, 0xfc, 0xe2, 0xff, 0x40, 0x20, 0xf5, 0xff, 0xff, 310 | 0x02, 0x19, 0x0d, 0x35, 0x6e, 0x03, 0xa0, 0x6d, 0xa2, 0x64, 0x22, 0x09, 0xe5, 0x06, 0x40, 311 | 0xe0, 0x64, 0x0c, 0xc0, 0xa0, 0x22, 0xa3, 0x3b, 0x1c, 0x38, 0x0c, 0x58, 0x20, 0x01, 0xf1, 312 | 0x19, 0xe9, 0x32, 0x60, 0x04, 0x00, 0x60, 0x05, 0x90, 0x22, 0xa3, 0xd5, 0x83, 0x07, 0xeb, 313 | 0xe2, 0x78, 0x40, 0x20, 0xf6, // Cat 034 314 | 0x22, 0x00, 0x0b, 0xf0, 0x19, 0x0d, 0x02, 0x35, 0x6e, 0x0e, 0x68, 315 | ]; 316 | let (rest, mut packet) = AsterixPacket::from_bytes((&bytes, 0)).unwrap(); 317 | packet.finalize().unwrap(); 318 | assert_eq!(packet.category, 48); 319 | let (_, mut packet) = AsterixPacket::from_bytes(rest).unwrap(); 320 | packet.finalize().unwrap(); 321 | assert_eq!(packet.category, 34); 322 | } 323 | 324 | #[test] 325 | fn test_not_from_bytes() { 326 | let mut thirty_eight = Cat34::default(); 327 | thirty_eight.data_source_identifier = Some(DataSourceIdentifier { sac: 25, sic: 13 }); 328 | thirty_eight.message_type = Some(MessageType { t: MTYPE::SectorCrossing }); 329 | thirty_eight.time_of_day = Some(TimeOfDay { time: 27355.953 }); 330 | thirty_eight.sector_number = Some(SectorNumber { num: 135 }); 331 | 332 | let mut packet = AsterixPacket { 333 | category: 34, 334 | messages: vec![asterix::AsterixMessage::Cat34(thirty_eight)], 335 | ..AsterixPacket::default() 336 | }; 337 | packet.finalize().unwrap(); 338 | let exp_bytes = vec![0x22, 0x00, 0x0b, 0xf0, 0x19, 0x0d, 0x02, 0x35, 0x6d, 0xfa, 0x60]; 339 | assert_eq!(packet.to_bytes().unwrap(), exp_bytes) 340 | } 341 | 342 | // The following data items don't have pcap captures, and are my own testing 343 | 344 | #[test] 345 | fn test_48_track_quality() { 346 | let mut fourty_eight = Cat48::default(); 347 | fourty_eight.track_quality = Some(TrackQuality { 348 | horizontal_stddev: 0.0, 349 | vertical_stddev: 0.0, 350 | groundspeed_stddev: 0.0, 351 | heading_stddev: 0.0, 352 | }); 353 | let mut packet = AsterixPacket { 354 | category: 48, 355 | messages: vec![asterix::AsterixMessage::Cat48(fourty_eight)], 356 | ..AsterixPacket::default() 357 | }; 358 | packet.finalize().unwrap(); 359 | let exp_bytes = vec![0x30, 0x00, 0x0a, 0x01, 0x01, 0b1000_0000, 0x00, 0x00, 0x00, 0x00]; 360 | assert_eq!(packet.to_bytes().unwrap(), exp_bytes); 361 | let (_, exp_packet) = AsterixPacket::from_bytes((&exp_bytes, 0)).unwrap(); 362 | assert_eq!(packet, exp_packet); 363 | 364 | let mut fourty_eight = Cat48::default(); 365 | fourty_eight.track_quality = Some(TrackQuality { 366 | horizontal_stddev: 32000.0, 367 | vertical_stddev: 32000.0, 368 | groundspeed_stddev: 0.015_563_965, 369 | heading_stddev: 22.412_11, 370 | }); 371 | let mut packet = AsterixPacket { 372 | category: 48, 373 | messages: vec![asterix::AsterixMessage::Cat48(fourty_eight)], 374 | ..AsterixPacket::default() 375 | }; 376 | packet.finalize().unwrap(); 377 | let exp_bytes = vec![0x30, 0x00, 0x0a, 0x01, 0x01, 0b1000_0000, 0xfa, 0xfa, 0xff, 0xff]; 378 | assert_eq_hex!(packet.to_bytes().unwrap(), exp_bytes); 379 | let (_, exp_packet) = AsterixPacket::from_bytes((&exp_bytes, 0)).unwrap(); 380 | assert_eq_hex!(packet, exp_packet); 381 | } 382 | 383 | #[test] 384 | fn test_48_warning_error_con_target_class() { 385 | let mut fourty_eight = Cat48::default(); 386 | 387 | let warning = WarningErrorConditionsTargetClass { 388 | codefxs: vec![ 389 | CodeFx { code: CODE::Angel, fx: FX::ExtensionIntoFirstExtent }, 390 | CodeFx { code: CODE::Angel, fx: FX::EndOfDataItem }, 391 | ], 392 | }; 393 | 394 | fourty_eight.warning_error_con_target_class = Some(warning); 395 | let mut packet = AsterixPacket { 396 | category: 48, 397 | messages: vec![asterix::AsterixMessage::Cat48(fourty_eight)], 398 | ..AsterixPacket::default() 399 | }; 400 | packet.finalize().unwrap(); 401 | let exp_bytes = vec![0x30, 0x00, 0x08, 0x01, 0x01, 0b100_0000, 0x5 << 1 | 0x01, 0x5 << 1]; 402 | assert_eq_hex!(packet.to_bytes().unwrap(), exp_bytes); 403 | let (_, exp_packet) = AsterixPacket::from_bytes((&exp_bytes, 0)).unwrap(); 404 | assert_eq_hex!(packet, exp_packet); 405 | } 406 | 407 | #[test] 408 | fn test_48_mode_3a_code_confidence_indicator() { 409 | let mut fourty_eight = Cat48::default(); 410 | 411 | let confidence = Mode3ACodeConfidenceIndicator { reserved: 0, confidence: 0b0000_0001 }; 412 | fourty_eight.mode3a_code_confidence_indicator = Some(confidence); 413 | let mut packet = AsterixPacket { 414 | category: 48, 415 | messages: vec![asterix::AsterixMessage::Cat48(fourty_eight)], 416 | ..AsterixPacket::default() 417 | }; 418 | packet.finalize().unwrap(); 419 | let exp_bytes = vec![0x30, 0x00, 0x08, 0x01, 0x01, 0b10_0000, 0x00, 0x01]; 420 | assert_eq_hex!(packet.to_bytes().unwrap(), exp_bytes); 421 | let (_, exp_packet) = AsterixPacket::from_bytes((&exp_bytes, 0)).unwrap(); 422 | assert_eq_hex!(packet, exp_packet); 423 | } 424 | 425 | #[test] 426 | fn test_48_mode_c_code_confidence() { 427 | let mut fourty_eight = Cat48::default(); 428 | let confidence = ModeCCodeAndConfidenceIndicator { 429 | v: V::CodeValidated, 430 | g: G::Default, 431 | reserved0: 0, 432 | mode_c_gray_notation: 0x01, 433 | reserved1: 0, 434 | confidence: 0x01, 435 | }; 436 | fourty_eight.modec_code_and_confidence_indicator = Some(confidence); 437 | let mut packet = AsterixPacket { 438 | category: 48, 439 | messages: vec![asterix::AsterixMessage::Cat48(fourty_eight)], 440 | ..AsterixPacket::default() 441 | }; 442 | packet.finalize().unwrap(); 443 | let exp_bytes = vec![0x30, 0x00, 0x0a, 0x01, 0x01, 0b1_0000, 0x00, 0x01, 0x00, 0x01]; 444 | assert_eq_hex!(packet.to_bytes().unwrap(), exp_bytes); 445 | let (_, exp_packet) = AsterixPacket::from_bytes((&exp_bytes, 0)).unwrap(); 446 | assert_eq_hex!(packet, exp_packet); 447 | } 448 | 449 | #[test] 450 | fn test_48_height_3d() { 451 | let mut fourty_eight = Cat48::default(); 452 | let height = HeightMeasuredBy3dRadar { reserved: 0, height: 25 }; 453 | fourty_eight.height_measured_by_3d_radar = Some(height); 454 | let mut packet = AsterixPacket { 455 | category: 48, 456 | messages: vec![asterix::AsterixMessage::Cat48(fourty_eight)], 457 | ..AsterixPacket::default() 458 | }; 459 | packet.finalize().unwrap(); 460 | let exp_bytes = vec![0x30, 0x00, 0x08, 0x01, 0x01, 0b1000, 0x00, 0x01]; 461 | assert_eq_hex!(packet.to_bytes().unwrap(), exp_bytes); 462 | let (_, exp_packet) = AsterixPacket::from_bytes((&exp_bytes, 0)).unwrap(); 463 | assert_eq_hex!(packet, exp_packet); 464 | 465 | let mut fourty_eight = Cat48::default(); 466 | let height = HeightMeasuredBy3dRadar { reserved: 0, height: 37200 }; 467 | fourty_eight.height_measured_by_3d_radar = Some(height); 468 | let mut packet = AsterixPacket { 469 | category: 48, 470 | messages: vec![asterix::AsterixMessage::Cat48(fourty_eight)], 471 | ..AsterixPacket::default() 472 | }; 473 | packet.finalize().unwrap(); 474 | let exp_bytes = vec![0x30, 0x00, 0x08, 0x01, 0x01, 0b1000, 0x05, 0xd0]; 475 | assert_eq_hex!(packet.to_bytes().unwrap(), exp_bytes); 476 | let (_, exp_packet) = AsterixPacket::from_bytes((&exp_bytes, 0)).unwrap(); 477 | assert_eq_hex!(packet, exp_packet); 478 | } 479 | 480 | #[test] 481 | fn test_48_radial_dopplerspeed() { 482 | // test the first subfield 483 | let bytes = vec![0x30, 0x00, 0x09, 0x01, 0x01, 0b100, 0b1000_0000, 0b1000_0000, 0b0000_0001]; 484 | let (_, packet) = AsterixPacket::from_bytes((&bytes, 0)).unwrap(); 485 | assert_eq_hex!(packet.to_bytes().unwrap(), bytes); 486 | 487 | // test the second subfield 488 | let bytes = vec![ 489 | 0x30, 490 | 0x00, 491 | 0x0e, 492 | 0x01, 493 | 0x01, 494 | 0b100, 495 | 0b0100_0000, 496 | 0x01, 497 | 0x00, 498 | 0x01, 499 | 0x00, 500 | 0x01, 501 | 0x00, 502 | 0x01, 503 | ]; 504 | let (_, packet) = AsterixPacket::from_bytes((&bytes, 0)).unwrap(); 505 | assert_eq_hex!(packet.to_bytes().unwrap(), bytes); 506 | } 507 | 508 | #[test] 509 | fn test_acas_resolution() { 510 | let bytes = vec![ 511 | 0x30, 512 | 0x00, 513 | 0x0e, 514 | 0x01, 515 | 0x01, 516 | 0x01, 517 | 0b1000_0000, 518 | 0x01, 519 | 0x02, 520 | 0x03, 521 | 0x04, 522 | 0x05, 523 | 0x06, 524 | 0x07, 525 | ]; 526 | let (_, mut packet) = AsterixPacket::from_bytes((&bytes, 0)).unwrap(); 527 | packet.finalize().unwrap(); 528 | assert_eq_hex!(packet.to_bytes().unwrap(), bytes); 529 | 530 | if let AsterixMessage::Cat48(ref message) = packet.messages[0] { 531 | assert_eq_hex!(message.fspec, &[0x01, 0x01, 0x01, 0b1000_0000]); 532 | 533 | let field = message.acas_resolution_advisory_report.as_ref().unwrap(); 534 | assert_eq_hex!(&field.mb_data, &[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07]); 535 | } 536 | } 537 | 538 | #[test] 539 | fn test_mode1code_octal_representation() { 540 | let bytes = vec![0x30, 0x00, 0x08, 0x01, 0x01, 0x01, 0b0100_0000, 0b0000_0001]; 541 | let (_, mut packet) = AsterixPacket::from_bytes((&bytes, 0)).unwrap(); 542 | packet.finalize().unwrap(); 543 | assert_eq_hex!(packet.to_bytes().unwrap(), bytes); 544 | 545 | if let AsterixMessage::Cat48(ref message) = packet.messages[0] { 546 | assert_eq_hex!(message.fspec, &[0x01, 0x01, 0x01, 0b0100_0000]); 547 | 548 | let field = message.mode_1_code_octal_representation.as_ref().unwrap(); 549 | assert_eq!(field.v, V::CodeValidated); 550 | assert_eq!(field.g, G::Default); 551 | assert_eq!(field.l, L::Mode3CodeDerivedFromTheReplyOfTheTransponder); 552 | assert_eq!(field.data, 0x01); 553 | } 554 | } 555 | 556 | #[test] 557 | fn test_mode2code_octal_representation() { 558 | let bytes = vec![0x30, 0x00, 0x09, 0x01, 0x01, 0x01, 0b0010_0000, 0b0000_0000, 0b0000_0001]; 559 | let (_, mut packet) = AsterixPacket::from_bytes((&bytes, 0)).unwrap(); 560 | packet.finalize().unwrap(); 561 | assert_eq_hex!(packet.to_bytes().unwrap(), bytes); 562 | 563 | if let AsterixMessage::Cat48(ref message) = packet.messages[0] { 564 | assert_eq_hex!(message.fspec, &[0x01, 0x01, 0x01, 0b0010_0000]); 565 | 566 | let field = message.mode_2_code_octal_representation.as_ref().unwrap(); 567 | assert_eq!(field.v, V::CodeValidated); 568 | assert_eq!(field.g, G::Default); 569 | assert_eq!(field.l, L::Mode3CodeDerivedFromTheReplyOfTheTransponder); 570 | assert_eq!(field.data, 0x01); 571 | } 572 | } 573 | 574 | #[test] 575 | fn test_mode1codeconfidence() { 576 | let bytes = vec![0x30, 0x00, 0x08, 0x01, 0x01, 0x01, 0b0001_0000, 0x01]; 577 | let (_, mut packet) = AsterixPacket::from_bytes((&bytes, 0)).unwrap(); 578 | packet.finalize().unwrap(); 579 | assert_eq_hex!(packet.to_bytes().unwrap(), bytes); 580 | 581 | if let AsterixMessage::Cat48(ref message) = packet.messages[0] { 582 | assert_eq_hex!(message.fspec, &[0x01, 0x01, 0x01, 0b0001_0000]); 583 | 584 | let field = message.mode_1_code_confidence.as_ref().unwrap(); 585 | assert_eq!(field.data, 0x01); 586 | } 587 | } 588 | 589 | #[test] 590 | fn test_mode2codeconfidence() { 591 | let bytes = vec![0x30, 0x00, 0x09, 0x01, 0x01, 0x01, 0b0000_1000, 0x00, 0x01]; 592 | let (_, mut packet) = AsterixPacket::from_bytes((&bytes, 0)).unwrap(); 593 | packet.finalize().unwrap(); 594 | assert_eq_hex!(packet.to_bytes().unwrap(), bytes); 595 | 596 | if let AsterixMessage::Cat48(ref message) = packet.messages[0] { 597 | assert_eq_hex!(message.fspec, &[0x01, 0x01, 0x01, 0b0000_1000]); 598 | 599 | let field = message.mode_2_code_confidence.as_ref().unwrap(); 600 | assert_eq!(field.data, 0x01); 601 | } 602 | } 603 | 604 | #[test] 605 | fn test_34_antenna_rotation_speed() { 606 | let bytes = vec![0x22, 0x00, 0x06, 0b0000_1000, 0x00, 0x01]; 607 | let (_, mut packet) = AsterixPacket::from_bytes((&bytes, 0)).unwrap(); 608 | packet.finalize().unwrap(); 609 | assert_eq_hex!(packet.to_bytes().unwrap(), bytes); 610 | 611 | if let AsterixMessage::Cat34(ref message) = packet.messages[0] { 612 | assert_eq_hex!(message.fspec, &[0b0000_1000]); 613 | 614 | let field = message.antenna_rotation_speed.as_ref().unwrap(); 615 | assert_eq!(field.period, 1.0 / 128_f32); 616 | } 617 | } 618 | --------------------------------------------------------------------------------