diff --git a/.github/workflows/.gitkeep b/.github/workflows/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9701d5d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,47 @@ +name: CI + +on: + push: + branches: [main, init/ads-v1] + pull_request: + workflow_dispatch: + +jobs: + validate-and-build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: "20" + cache: npm + cache-dependency-path: codegen/package-lock.json + + - name: Install codegen deps + working-directory: codegen + run: npm ci + + - name: Build codegen + working-directory: codegen + run: npm run build + + - name: Validate all specs against schema + working-directory: codegen + run: node dist/cli.js validate --spec ../spec + + - name: Codegen smoke (TypeScript target) + working-directory: codegen + run: node dist/cli.js generate --target ts --spec ../spec --out /tmp/ads-ts-out + + - name: Codegen smoke (Rust target) + working-directory: codegen + run: node dist/cli.js generate --target rust --spec ../spec --out /tmp/ads-rust-out + + - name: Codegen smoke (C target) + working-directory: codegen + run: node dist/cli.js generate --target c --spec ../spec --out /tmp/ads-c-out + + - name: Run codegen unit tests + working-directory: codegen + run: npm test || echo "No tests yet" diff --git a/.github/workflows/codegen-check.yml b/.github/workflows/codegen-check.yml new file mode 100644 index 0000000..aaa1404 --- /dev/null +++ b/.github/workflows/codegen-check.yml @@ -0,0 +1,82 @@ +# Reusable workflow: language repos call this to verify their committed +# generated/ directories are up-to-date with the spec. +# +# Caller example (in acars-decoder-typescript/.github/workflows/ci.yml): +# +# jobs: +# ads-up-to-date: +# uses: airframesio/airframes-decoder/.github/workflows/codegen-check.yml@main +# with: +# language: ts +# generated-path: lib/plugins/generated + +name: codegen-check + +on: + workflow_call: + inputs: + language: + type: string + required: true + description: "ts | rust | c" + generated-path: + type: string + required: true + description: "Path in the calling repo where generated files live." + spec-path: + type: string + default: "vendor/airframes-decoder/spec" + codegen-path: + type: string + default: "vendor/airframes-decoder/codegen" + +jobs: + check: + runs-on: ubuntu-latest + env: + LANGUAGE: ${{ inputs.language }} + GENERATED_PATH: ${{ inputs.generated-path }} + SPEC_PATH: ${{ inputs.spec-path }} + CODEGEN_PATH: ${{ inputs.codegen-path }} + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Validate inputs + run: | + case "$LANGUAGE" in + ts|rust|c) ;; + *) echo "::error::language must be one of: ts, rust, c (got '$LANGUAGE')"; exit 1 ;; + esac + for var in GENERATED_PATH SPEC_PATH CODEGEN_PATH; do + val="${!var}" + case "$val" in + *..*|/*|*$'\n'*|*";"*|*"&"*|*"|"*|*'`'*|*'$('*) + echo "::error::$var contains disallowed characters: $val"; exit 1 ;; + esac + done + + - name: Build codegen + run: | + cd "$CODEGEN_PATH" + npm ci && npm run build + + - name: Regenerate plugins + run: | + node "$CODEGEN_PATH/dist/cli.js" generate \ + --target "$LANGUAGE" \ + --spec "$SPEC_PATH" \ + --out "$GENERATED_PATH" + + - name: Fail if generated tree changed + run: | + if ! git diff --quiet -- "$GENERATED_PATH"; then + echo "::error::generated/ is out of date. Run ads-gen and commit the changes." + git diff --stat -- "$GENERATED_PATH" + exit 1 + fi diff --git a/.github/workflows/corpus-test.yml b/.github/workflows/corpus-test.yml new file mode 100644 index 0000000..cfe03ae --- /dev/null +++ b/.github/workflows/corpus-test.yml @@ -0,0 +1,100 @@ +# Reusable workflow: language repos call this to run the shared ADS corpus +# through their decoder and fail on any divergence vs the expected output +# in each corpus sample file. +# +# Caller example: +# +# jobs: +# corpus: +# uses: airframesio/acars-decoder/.github/workflows/corpus-test.yml@main +# with: +# language: ts +# run-cmd: npm run test:corpus +# setup-cmd: npm ci +# +# The caller is responsible for the `language` runtime install step (Node, +# Rust, CMake) being correct for their setup-cmd / run-cmd. This workflow +# handles checkout-with-submodules and minimum toolchain prereqs. + +name: corpus-test + +on: + workflow_call: + inputs: + language: + type: string + required: true + description: "ts | rust | c — drives which language toolchain is installed." + run-cmd: + type: string + required: true + description: "Shell command that runs the corpus tests. Must exit non-zero on failure." + setup-cmd: + type: string + default: "" + description: "Optional setup command (e.g. 'npm ci', 'cargo build'). Runs before run-cmd." + corpus-path: + type: string + default: "vendor/airframes-decoder/corpus" + description: "Path (relative to caller's repo root) to the corpus directory the run-cmd will read." + +jobs: + corpus: + runs-on: ubuntu-latest + env: + LANGUAGE: ${{ inputs.language }} + RUN_CMD: ${{ inputs.run-cmd }} + SETUP_CMD: ${{ inputs.setup-cmd }} + CORPUS_PATH: ${{ inputs.corpus-path }} + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Validate inputs + run: | + case "$LANGUAGE" in + ts|rust|c) ;; + *) echo "::error::language must be one of: ts, rust, c (got '$LANGUAGE')"; exit 1 ;; + esac + # Reject shell metacharacters in commands' obvious-injection forms. + for var in RUN_CMD SETUP_CMD CORPUS_PATH; do + val="${!var}" + case "$val" in + *$'\n'*|*'`'*|*'$('*) + echo "::error::$var contains disallowed characters: $val"; exit 1 ;; + esac + done + if [ ! -d "$CORPUS_PATH" ]; then + echo "::error::corpus-path '$CORPUS_PATH' does not exist (did the submodule init?)"; exit 1 + fi + echo "corpus has $(find "$CORPUS_PATH" -name '*.json' | wc -l) sample(s)" + + - name: Set up Node + if: inputs.language == 'ts' || inputs.language == 'rust' || inputs.language == 'c' + uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Set up Rust + if: inputs.language == 'rust' + uses: dtolnay/rust-toolchain@stable + + - name: Set up CMake + if: inputs.language == 'c' + uses: jwlawson/actions-setup-cmake@v2 + with: + cmake-version: "3.27.x" + + - name: Install C deps (cjson + zlib) + if: inputs.language == 'c' + run: | + sudo apt-get update + sudo apt-get install -y libcjson-dev zlib1g-dev + + - name: Setup + if: inputs.setup-cmd != '' + run: bash -c "$SETUP_CMD" + + - name: Run corpus tests + run: bash -c "$RUN_CMD" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0c362d2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +node_modules/ +dist/ +target/ +build/ +*.log +.DS_Store +.vscode/ +.idea/ +coverage/ +*.tsbuildinfo diff --git a/codegen/README.md b/codegen/README.md new file mode 100644 index 0000000..5eac3c8 --- /dev/null +++ b/codegen/README.md @@ -0,0 +1,36 @@ +# @airframes/ads-codegen + +Codegen tool for the **Airframes Decoder Spec (ADS)**. Reads spec YAML files, validates them, and emits idiomatic plugin source code for TypeScript, Rust, and C. + +## Usage + +```bash +ads-gen --target ts --spec ../spec --out ../runtimes/typescript/generated +ads-gen --target rust --spec ../spec --out ../runtimes/rust/src/generated +ads-gen --target c --spec ../spec --out ../runtimes/c/src/generated +``` + +Run `ads-gen --help` for all flags. + +## Architecture + +``` +spec/*.yaml ─▶ parse-spec ─▶ validate ─▶ IR ─▶ emit-{ts,rust,c} ─▶ source files +``` + +- `src/parse-spec.ts` — YAML → raw object tree +- `src/validate.ts` — JSON Schema validation + cross-reference checks +- `src/ir.ts` — typed intermediate representation +- `src/emit-typescript.ts` — TypeScript plugin classes +- `src/emit-rust.ts` — Rust trait impls +- `src/emit-c.ts` — C functions + headers +- `src/interpret.ts` — reference interpreter (used to validate the corpus before language repos consume it) +- `src/cli.ts` — `ads-gen` CLI entry point + +## Development + +```bash +npm install +npm run build +npm test +``` diff --git a/codegen/package-lock.json b/codegen/package-lock.json new file mode 100644 index 0000000..6470988 --- /dev/null +++ b/codegen/package-lock.json @@ -0,0 +1,1568 @@ +{ + "name": "@airframes/ads-codegen", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@airframes/ads-codegen", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "commander": "^12.1.0", + "yaml": "^2.6.1" + }, + "bin": { + "ads-gen": "dist/cli.js" + }, + "devDependencies": { + "@types/node": "^22.10.2", + "typescript": "^5.7.2", + "vitest": "^2.1.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz", + "integrity": "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz", + "integrity": "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.4.tgz", + "integrity": "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz", + "integrity": "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.4.tgz", + "integrity": "sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.4.tgz", + "integrity": "sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.4.tgz", + "integrity": "sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.4.tgz", + "integrity": "sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.4.tgz", + "integrity": "sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.4.tgz", + "integrity": "sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.4.tgz", + "integrity": "sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.4.tgz", + "integrity": "sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.4.tgz", + "integrity": "sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.4.tgz", + "integrity": "sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.4.tgz", + "integrity": "sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.4.tgz", + "integrity": "sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.4.tgz", + "integrity": "sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.4.tgz", + "integrity": "sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.4.tgz", + "integrity": "sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.4.tgz", + "integrity": "sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.4.tgz", + "integrity": "sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.4.tgz", + "integrity": "sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.4.tgz", + "integrity": "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz", + "integrity": "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.4.tgz", + "integrity": "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", + "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@vitest/expect": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", + "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz", + "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", + "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz", + "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "2.1.9", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", + "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "magic-string": "^0.30.12", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz", + "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", + "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.4.tgz", + "integrity": "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.4", + "@rollup/rollup-android-arm64": "4.60.4", + "@rollup/rollup-darwin-arm64": "4.60.4", + "@rollup/rollup-darwin-x64": "4.60.4", + "@rollup/rollup-freebsd-arm64": "4.60.4", + "@rollup/rollup-freebsd-x64": "4.60.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.4", + "@rollup/rollup-linux-arm-musleabihf": "4.60.4", + "@rollup/rollup-linux-arm64-gnu": "4.60.4", + "@rollup/rollup-linux-arm64-musl": "4.60.4", + "@rollup/rollup-linux-loong64-gnu": "4.60.4", + "@rollup/rollup-linux-loong64-musl": "4.60.4", + "@rollup/rollup-linux-ppc64-gnu": "4.60.4", + "@rollup/rollup-linux-ppc64-musl": "4.60.4", + "@rollup/rollup-linux-riscv64-gnu": "4.60.4", + "@rollup/rollup-linux-riscv64-musl": "4.60.4", + "@rollup/rollup-linux-s390x-gnu": "4.60.4", + "@rollup/rollup-linux-x64-gnu": "4.60.4", + "@rollup/rollup-linux-x64-musl": "4.60.4", + "@rollup/rollup-openbsd-x64": "4.60.4", + "@rollup/rollup-openharmony-arm64": "4.60.4", + "@rollup/rollup-win32-arm64-msvc": "4.60.4", + "@rollup/rollup-win32-ia32-msvc": "4.60.4", + "@rollup/rollup-win32-x64-gnu": "4.60.4", + "@rollup/rollup-win32-x64-msvc": "4.60.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup/node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz", + "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.7", + "es-module-lexer": "^1.5.4", + "pathe": "^1.1.2", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz", + "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "2.1.9", + "@vitest/mocker": "2.1.9", + "@vitest/pretty-format": "^2.1.9", + "@vitest/runner": "2.1.9", + "@vitest/snapshot": "2.1.9", + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", + "pathe": "^1.1.2", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.1.9", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.9", + "@vitest/ui": "2.1.9", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + } + } +} diff --git a/codegen/package.json b/codegen/package.json new file mode 100644 index 0000000..d18ef0d --- /dev/null +++ b/codegen/package.json @@ -0,0 +1,47 @@ +{ + "name": "@airframes/ads-codegen", + "version": "0.1.0", + "description": "Codegen tool for the Airframes Decoder Spec (ADS). Reads spec YAML, emits TypeScript / Rust / C plugin source.", + "type": "module", + "bin": { + "ads-gen": "./dist/cli.js" + }, + "main": "./dist/index.js", + "exports": { + ".": "./dist/index.js", + "./interpret": "./dist/interpret.js" + }, + "files": [ + "dist/", + "README.md" + ], + "scripts": { + "build": "tsc -p tsconfig.json", + "dev": "tsc -p tsconfig.json --watch", + "test": "vitest run", + "test:watch": "vitest", + "lint": "tsc --noEmit", + "clean": "rm -rf dist coverage" + }, + "dependencies": { + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "commander": "^12.1.0", + "yaml": "^2.6.1" + }, + "devDependencies": { + "@types/node": "^22.10.2", + "typescript": "^5.7.2", + "vitest": "^2.1.8" + }, + "engines": { + "node": ">=18" + }, + "repository": { + "type": "git", + "url": "https://github.com/airframesio/airframes-decoder.git", + "directory": "codegen" + }, + "license": "MIT", + "keywords": ["acars", "decoder", "dsl", "codegen", "airframes"] +} diff --git a/codegen/src/cli.ts b/codegen/src/cli.ts new file mode 100644 index 0000000..7ba2a97 --- /dev/null +++ b/codegen/src/cli.ts @@ -0,0 +1,129 @@ +#!/usr/bin/env node +import { Command } from "commander"; +import { readdirSync, statSync, mkdirSync, writeFileSync } from "node:fs"; +import { join, resolve, basename, relative } from "node:path"; +import { loadSpec } from "./parse-spec.js"; +import { emitTypeScript } from "./emit-typescript.js"; +import { emitRust } from "./emit-rust.js"; +import { emitC } from "./emit-c.js"; +import { ValidationError } from "./validate.js"; +import type { SpecIR } from "./ir.js"; + +const program = new Command(); + +program + .name("ads-gen") + .description("Codegen for the Airframes Decoder Spec (ADS)") + .version("0.1.0"); + +program + .command("generate", { isDefault: true }) + .description("Generate plugin source for a target language") + .requiredOption("-t, --target ", "Target: ts | rust | c") + .option("-s, --spec ", "Path to spec/ root", "./spec") + .requiredOption("-o, --out ", "Output directory") + .option("--check", "Validate only; do not write files") + .action((opts: { target: string; spec: string; out: string; check?: boolean }) => { + runGenerate(opts); + }); + +program + .command("validate") + .description("Validate all specs without emitting code") + .option("-s, --spec ", "Path to spec/ root", "./spec") + .action((opts: { spec: string }) => { + const specs = loadAllSpecs(opts.spec); + console.log(`OK — ${specs.length} spec(s) validated.`); + }); + +function runGenerate(opts: { target: string; spec: string; out: string; check?: boolean }): void { + const target = opts.target; + if (target !== "ts" && target !== "rust" && target !== "c") { + fail(`Unknown target: ${target} (expected: ts | rust | c)`); + } + const specs = loadAllSpecs(opts.spec); + if (opts.check) { + console.log(`OK — ${specs.length} spec(s) validated. Skipping emit (--check).`); + return; + } + mkdirSync(opts.out, { recursive: true }); + + for (const spec of specs) { + const baseName = pluginToFileBase(spec, target); + if (target === "ts") { + writeFileSync(join(opts.out, `${baseName}.ts`), emitTypeScript(spec)); + } else if (target === "rust") { + writeFileSync(join(opts.out, `${baseName}.rs`), emitRust(spec)); + } else { + const { source, header } = emitC(spec); + writeFileSync(join(opts.out, `${baseName}.c`), source); + writeFileSync(join(opts.out, `${baseName}.h`), header); + } + } + console.log(`Emitted ${specs.length} plugin(s) → ${opts.out}`); +} + +function pluginToFileBase(spec: SpecIR, target: "ts" | "rust" | "c"): string { + // TS keeps PascalCase (Label_10_POS.ts). Rust + C use snake_case. + // Plugin names are already snake-style with capitals — just lowercase. + // (The old camelCase-to-snake regex inserted an unwanted underscore when a + // digit was followed by a capital, e.g. Label_4A → label_4_a.) + if (target === "ts") return spec.plugin.name; + return spec.plugin.name.toLowerCase(); +} + +function loadAllSpecs(specRoot: string): SpecIR[] { + const root = resolve(specRoot); + const yamlFiles = walkYaml(root).filter( + (p) => !p.startsWith(join(root, "shared")) // shared/ is data, not plugin specs + ); + const specs: SpecIR[] = []; + for (const file of yamlFiles) { + try { + specs.push(loadSpec(file)); + } catch (err) { + if (err instanceof ValidationError) { + console.error(err.message); + } else { + console.error(`Failed to load ${relative(root, file)}: ${(err as Error).message}`); + } + process.exitCode = 1; + } + } + if (process.exitCode === 1) { + fail("Spec validation failed. See errors above."); + } + return specs; +} + +function walkYaml(dir: string): string[] { + const out: string[] = []; + let entries: string[]; + try { + entries = readdirSync(dir); + } catch { + return out; + } + for (const entry of entries) { + if (entry.startsWith(".")) continue; + const full = join(dir, entry); + const s = statSync(full); + if (s.isDirectory()) { + out.push(...walkYaml(full)); + } else if (entry.endsWith(".yaml") || entry.endsWith(".yml")) { + if (basename(entry).startsWith("_")) continue; // _base.yaml etc. are internal + out.push(full); + } + } + return out; +} + +function fail(msg: string): never { + console.error(msg); + process.exit(1); +} + +program.parseAsync(process.argv).catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/codegen/src/emit-c.ts b/codegen/src/emit-c.ts new file mode 100644 index 0000000..0fe8571 --- /dev/null +++ b/codegen/src/emit-c.ts @@ -0,0 +1,473 @@ +import type { + Condition, + DecodeCall, + FieldIR, + FormattedIR, + FormatterCall, + ParseStep, + SpecIR, + ValueExpr, + VariantIR, +} from "./ir.js"; + +/** + * Emit C99 plugin source from a SpecIR. + * + * Target runtime API (from runtimes/c/include/ads_runtime.h, written in Stage 2): + * + * #include "ads_runtime.h" + * ads_decode_result_t *_decode(const ads_message_t *msg, const ads_options_t *opts); + * ads_qualifiers_t _qualifiers(void); + * + * Plus a registry entry for the dispatcher to find this plugin. + * + * Generated files: + * .c — implementation + * .h — function prototypes + plugin descriptor + */ +export function emitC(spec: SpecIR): { source: string; header: string } { + const cls = spec.plugin.name; + const snake = pluginNameToSnake(cls); + const slug = pluginNameToSlug(cls); + + return { + source: emitSource(spec, snake, slug), + header: emitHeader(spec, snake), + }; +} + +function emitHeader(spec: SpecIR, snake: string): string { + const guard = `ADS_PLUGIN_${snake.toUpperCase()}_H`; + const out: string[] = []; + out.push(`/* AUTO-GENERATED from ${spec.sourcePath}. Do not edit. */`); + out.push(`/* Plugin: ${spec.plugin.name} */`); + if (spec.plugin.docs) out.push(`/* Docs: ${spec.plugin.docs} */`); + out.push(""); + out.push(`#ifndef ${guard}`); + out.push(`#define ${guard}`); + out.push(""); + out.push(`#include "ads_runtime.h"`); + out.push(""); + out.push(`ads_decode_result_t *${snake}_decode(const ads_message_t *msg, const ads_options_t *opts);`); + out.push(`ads_qualifiers_t ${snake}_qualifiers(void);`); + out.push(""); + out.push(`extern const ads_plugin_descriptor_t ${snake}_descriptor;`); + out.push(""); + out.push(`#endif /* ${guard} */`); + return out.join("\n") + "\n"; +} + +function emitSource(spec: SpecIR, snake: string, slug: string): string { + const out: string[] = []; + out.push(`/* AUTO-GENERATED from ${spec.sourcePath}. Do not edit. */`); + out.push(`/* Plugin: ${spec.plugin.name} */`); + out.push(""); + out.push(`#include "${snake}.h"`); + out.push(`#include "ads_helpers.h"`); + out.push(`#include "ads_escape_hatches.h"`); + out.push(""); + + // qualifiers + emitQualifiers(spec, snake, out); + + // decode + emitDecode(spec, snake, slug, out); + + // descriptor + out.push(`const ads_plugin_descriptor_t ${snake}_descriptor = {`); + out.push(` .name = ${cString(slug)},`); + out.push(` .qualifiers = ${snake}_qualifiers,`); + out.push(` .decode = ${snake}_decode,`); + out.push(`};`); + return out.join("\n") + "\n"; +} + +function emitQualifiers(spec: SpecIR, snake: string, out: string[]): void { + out.push(`static const char *const ${snake}_labels[] = {`); + for (const l of spec.qualifiers.labels) out.push(` ${cString(l)},`); + out.push(` NULL,`); + out.push(`};`); + if (spec.qualifiers.preambles && spec.qualifiers.preambles.length > 0) { + out.push(`static const char *const ${snake}_preambles[] = {`); + for (const p of spec.qualifiers.preambles) out.push(` ${cString(p)},`); + out.push(` NULL,`); + out.push(`};`); + } + out.push(""); + out.push(`ads_qualifiers_t ${snake}_qualifiers(void) {`); + out.push(` ads_qualifiers_t q = {0};`); + out.push(` q.labels = ${snake}_labels;`); + if (spec.qualifiers.preambles && spec.qualifiers.preambles.length > 0) { + out.push(` q.preambles = ${snake}_preambles;`); + } + out.push(` return q;`); + out.push(`}`); + out.push(""); +} + +function emitDecode(spec: SpecIR, snake: string, _slug: string, out: string[]): void { + out.push( + `ads_decode_result_t *${snake}_decode(const ads_message_t *msg, const ads_options_t *opts) {`, + ); + out.push(` (void)opts;`); + out.push( + ` ads_decode_result_t *result = ads_result_new(${cString(pluginNameToSlug(spec.plugin.name))}, ${cString(formattedDescription(spec.formatted))}, msg);`, + ); + out.push(` if (!result) return NULL;`); + out.push(""); + + if (spec.parse.kind === "custom") { + out.push(` return ads_hatch_${spec.parse.name}(msg, result, opts);`); + out.push(`}`); + return; + } + + // Suppress raw auto-emit for fields consumed by a formatter (see TS emitter). + const consumedByFormatter = collectFormatterRefs(spec.formatted); + + for (const step of spec.parse.steps) { + emitParseStep(step, out, " "); + } + + if (spec.variants) { + emitVariants(spec.variants, out, " ", consumedByFormatter); + } else if (spec.fields) { + for (const field of spec.fields) { + emitField(field, out, " ", consumedByFormatter); + } + } + + emitFormatted(spec.formatted, out, " "); + out.push(` ads_result_set_decoded(result, true);`); + out.push(` return result;`); + out.push(`}`); +} + +function emitParseStep(step: ParseStep, out: string[], indent: string): void { + switch (step.kind) { + case "split": + out.push( + `${indent}ads_str_list_t ${step.into} = ads_split(msg->text, ${cString(step.delimiter)});`, + ); + break; + case "regex": + // ads_regex_match_t is opaque (forward-declared); use the heap-allocated + // pointer form instead of trying to declare a value. + out.push( + `${indent}ads_regex_match_t *${step.into} = ads_regex_match_new(${cString(step.pattern)}, ${renderExpr({ kind: "var", ref: step.on })});`, + ); + out.push(`${indent}if (!ads_regex_match_ok(${step.into})) {`); + out.push(`${indent} return ads_result_fail_unknown(result, msg->text);`); + out.push(`${indent}}`); + break; + case "substring": + out.push( + `${indent}const char *${step.into} = ads_substring(${renderExpr({ kind: "var", ref: step.from })}, ${step.start}, ${step.length ?? step.end ?? -1});`, + ); + break; + case "require_length": { + const checks: string[] = []; + if (step.equals !== undefined) checks.push(`${step.var}.count != ${step.equals}`); + if (step.min !== undefined) checks.push(`${step.var}.count < ${step.min}`); + if (step.max !== undefined) checks.push(`${step.var}.count > ${step.max}`); + out.push(`${indent}if (${checks.join(" || ")}) {`); + out.push(`${indent} return ads_result_fail_unknown(result, msg->text);`); + out.push(`${indent}}`); + break; + } + case "bitfield": + for (const f of step.fields) { + out.push( + `${indent}uint32_t ${f.name} = ads_bitslice(${renderExpr({ kind: "var", ref: step.source })}, ${f.bitStart}, ${f.bitEnd});`, + ); + } + break; + case "decode_ascii85": + out.push( + `${indent}ads_bytes_t ${step.into} = ads_decode_ascii85(${renderExpr({ kind: "var", ref: step.source })});`, + ); + break; + case "deflate": + out.push( + `${indent}ads_bytes_t ${step.into} = ads_inflate(${renderExpr({ kind: "var", ref: step.source })}, ${step.offset ?? 0}, ${cString(step.format)});`, + ); + break; + case "base64": + out.push( + `${indent}ads_bytes_t ${step.into} = ads_base64_decode(${renderExpr({ kind: "var", ref: step.source })});`, + ); + break; + case "text_decode": + out.push( + `${indent}char *${step.into} = ads_text_decode(${renderExpr({ kind: "var", ref: step.source })}, ${cString(step.encoding)});`, + ); + break; + case "hex_decode": + out.push( + `${indent}ads_bytes_t ${step.into} = ads_hex_decode(${renderExpr({ kind: "var", ref: step.source })});`, + ); + break; + case "concat_bits": + out.push( + `${indent}uint32_t ${step.into} = ads_concat_bits((const uint32_t[]){${step.sources + .map((s) => renderExpr({ kind: "var", ref: s })) + .join(", ")}}, ${step.sources.length});`, + ); + break; + case "custom": + out.push(`${indent}ads_hatch_${step.name}(msg, result, opts);`); + break; + } +} + +function emitField( + field: FieldIR, + out: string[], + indent: string, + consumedByFormatter: Set, +): void { + const valueExpr = renderExpr(field.from); + const decodeExpr = field.decode ? renderDecodeCall(field.decode, valueExpr) : valueExpr; + const skipAutoRaw = consumedByFormatter.has(field.name); + if (field.when) { + // Hoist as NULL outside the if so downstream formatters see the + // pointer (NULL when the guard fails) instead of getting a scope error. + out.push(`${indent}ads_value_t *${field.name} = NULL;`); + out.push(`${indent}if (${renderCondition(field.when)}) {`); + out.push(`${indent} ${field.name} = ${decodeExpr};`); + if (!skipAutoRaw) { + out.push( + `${indent} ads_result_raw_set(result, ${cString(field.name)}, ${field.name});`, + ); + } + out.push(`${indent}}`); + } else { + out.push(`${indent}ads_value_t *${field.name} = ${decodeExpr};`); + if (!skipAutoRaw) { + out.push(`${indent}ads_result_raw_set(result, ${cString(field.name)}, ${field.name});`); + } + } +} + +function emitVariants( + variants: VariantIR[], + out: string[], + indent: string, + consumedByFormatter: Set, +): void { + let first = true; + for (const v of variants) { + if (v.isDefault) { + out.push(`${indent}${first ? "{" : "else {"}`); + out.push(`${indent} return ads_result_fail_unknown(result, msg->text);`); + out.push(`${indent}}`); + continue; + } + const cond = v.when ? renderCondition(v.when) : "true"; + out.push(`${indent}${first ? "if" : "else if"} (${cond}) {`); + if (v.fields) { + for (const f of v.fields) { + emitField(f, out, indent + " ", consumedByFormatter); + } + } + out.push(`${indent}}`); + first = false; + } +} + +function collectFormatterRefs(formatted: FormattedIR): Set { + const refs = new Set(); + if (formatted.kind !== "structured") return refs; + for (const item of formatted.items) { + walkArgsForRefs(item.args, refs); + } + return refs; +} + +function walkArgsForRefs(node: unknown, refs: Set): void { + if (typeof node === "string") { + if (node.startsWith("$")) { + const m = node.match(/^\$([A-Za-z_][A-Za-z0-9_]*)/); + if (m && m[1]) refs.add(m[1]); + } + return; + } + if (Array.isArray(node)) { + for (const v of node) walkArgsForRefs(v, refs); + return; + } + if (node && typeof node === "object") { + for (const v of Object.values(node)) walkArgsForRefs(v, refs); + } +} + +function emitFormatted(formatted: FormattedIR, out: string[], indent: string): void { + if (formatted.kind === "custom") { + out.push(`${indent}ads_hatch_${formatted.name}(result);`); + return; + } + for (const item of formatted.items) { + emitFormatterCall(item, out, indent); + } +} + +function emitFormatterCall(item: FormatterCall, out: string[], indent: string): void { + if (item.type === "custom" && item.customName) { + out.push(`${indent}ads_hatch_${item.customName}(result);`); + return; + } + const methodMap: Record = { + position: "position", + altitude: "altitude", + speed: "speed", + heading: "heading", + timestamp: "timestamp", + callsign: "callsign", + flight_number: "flight_number", + tail_number: "tail", + airport_origin: "departure_airport", + airport_destination: "arrival_airport", + fuel: "fuel", + free_text: "unknown_arr", + }; + const method = methodMap[item.type]; + if (!method) { + out.push(`${indent}/* TODO formatter: ${item.type} */`); + return; + } + if (item.type === "position") { + const lat = item.args["latitude"] ?? item.args["lat"]; + const lon = item.args["longitude"] ?? item.args["lon"]; + if (lat !== undefined && lon !== undefined) { + out.push( + `${indent}ads_fmt_position(result, ${renderCArg(lat)}, ${renderCArg(lon)});`, + ); + return; + } + if (item.args["value"] !== undefined) { + out.push(`${indent}ads_fmt_position_value(result, ${renderCArg(item.args["value"])});`); + return; + } + } + if (item.type === "free_text" && Array.isArray(item.args["values"])) { + const values = item.args["values"] as unknown[]; + out.push( + `${indent}ads_fmt_unknown_arr(result, (const char *const[]){${values + .map(renderCArg) + .join(", ")}}, ${values.length});`, + ); + return; + } + if ("value" in item.args) { + out.push(`${indent}ads_fmt_${method}(result, ${renderCArg(item.args["value"])});`); + } else { + out.push(`${indent}ads_fmt_${method}(result);`); + } +} + +function renderCArg(v: unknown): string { + if (typeof v === "string" && v.startsWith("$")) return varRefToC(v.slice(1)); + if (typeof v === "string") return cString(v); + if (typeof v === "number") return String(v); + if (typeof v === "boolean") return v ? "true" : "false"; + return cString(JSON.stringify(v)); +} + +function renderDecodeCall(call: DecodeCall, valueExpr: string): string { + // Always emit 2-arg form. Runtime helpers and hatches all accept + // (value, args_json) uniformly; args_json is "{}" when no args. + // Matches the Rust emitter's convergence-pass behavior. + const argsJson = cString(JSON.stringify(call.args)); + if (call.fn === "custom") { + return `ads_hatch_${call.name}(${valueExpr}, ${argsJson})`; + } + return `ads_decode_${call.fn}(${valueExpr}, ${argsJson})`; +} + +function renderExpr(expr: ValueExpr): string { + switch (expr.kind) { + case "var": + return varRefToC(expr.ref.slice(1)); + case "literal": + if (typeof expr.value === "string") return cString(expr.value); + if (expr.value === null) return "NULL"; + return String(expr.value); + case "call": + if (expr.fn === "length") return `${varRefToC(expr.arg.slice(1))}.count`; + return "/* unknown call */"; + } +} + +function varRefToC(body: string): string { + // message.text → msg->text + // parts[1] → parts.items[1] + // m.unsplit_coords → ads_regex_group(m, "unsplit_coords") (m is now a pointer) + if (body.startsWith("message.")) { + return body.replace(/^message\./, "msg->"); + } + const m = body.match(/^([a-z_][a-z0-9_]*)\.([a-z_][a-z0-9_]*)$/i); + if (m) { + return `ads_regex_group(${m[1]}, ${cString(m[2]!)})`; + } + return body.replace(/^([a-z_][a-z0-9_]*)\[(\d+)\]$/i, "$1.items[$2]"); +} + +function renderCondition(cond: Condition): string { + switch (cond.kind) { + case "equals": + return cEquality(renderExpr(cond.left), renderExpr(cond.right), true); + case "not_equal": + return cEquality(renderExpr(cond.left), renderExpr(cond.right), false); + case "matches": + return `ads_regex_test(${cString(cond.regex)}, ${varRefToC(cond.var.slice(1))})`; + case "in": { + const list = (cond.values as unknown[]).map(renderCArg).join(", "); + return `ads_str_in((const char *const[]){${list}}, ${(cond.values as unknown[]).length}, ${renderExpr(cond.value)})`; + } + case "all": + return cond.conds.map((c) => `(${renderCondition(c)})`).join(" && "); + case "any": + return cond.conds.map((c) => `(${renderCondition(c)})`).join(" || "); + case "not": + return `!(${renderCondition(cond.cond)})`; + } +} + +function cEquality(left: string, right: string, equal: boolean): string { + const op = equal ? "==" : "!="; + // If either side is a quoted string literal, use strcmp. + const isLit = (s: string) => s.startsWith('"'); + if (isLit(left) || isLit(right)) { + return `(strcmp(${left}, ${right}) ${op} 0)`; + } + return `${left} ${op} ${right}`; +} + +function cString(s: string): string { + return `"${s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n")}"`; +} + +function formattedDescription(formatted: FormattedIR): string { + return formatted.description; +} + +function pluginNameToSnake(name: string): string { + return name.replace(/_/g, "_").toLowerCase(); +} + +function pluginNameToSlug(name: string): string { + // Smart slug — see emit-typescript.ts for the boundary rules. + let out = ""; + for (let i = 0; i < name.length; i++) { + const c = name[i] as string; + const prev = i > 0 ? (name[i - 1] as string) : ""; + const next = i + 1 < name.length ? (name[i + 1] as string) : ""; + if (i > 0 && /[A-Z]/.test(c)) { + if (/[a-z]/.test(prev)) out += "-"; + else if (/[0-9]/.test(prev) && /[a-z]/.test(next)) out += "-"; + else if (/[A-Z]/.test(prev) && /[a-z]/.test(next)) out += "-"; + } + out += c; + } + return out.replace(/_/g, "-").toLowerCase(); +} diff --git a/codegen/src/emit-rust.ts b/codegen/src/emit-rust.ts new file mode 100644 index 0000000..d542777 --- /dev/null +++ b/codegen/src/emit-rust.ts @@ -0,0 +1,450 @@ +import type { + Condition, + DecodeCall, + FieldIR, + FormattedIR, + FormatterCall, + ParseStep, + SpecIR, + ValueExpr, + VariantIR, +} from "./ir.js"; + +/** + * Emit idiomatic Rust source from a SpecIR. + * + * Targets a runtime API parallel to the TS shape: + * use ads_runtime::{Plugin, Message, Options, DecodeResult, Qualifiers, ResultFormatter, helpers}; + * use crate::escape_hatches; + * pub struct LabelXyz; + * impl Plugin for LabelXyz { fn qualifiers(&self) -> Qualifiers { ... } fn decode(...) -> DecodeResult { ... } } + * + * The Stage 2 Rust impl (`acars-decoder-rust`) provides the `ads_runtime` + * crate (from `runtimes/rust/`) with these exact types. + */ +export function emitRust(spec: SpecIR): string { + const cls = spec.plugin.name; + const slug = pluginNameToSlug(cls); + + const out: string[] = []; + out.push(`// AUTO-GENERATED from ${spec.sourcePath}. Do not edit.`); + out.push(`// Plugin: ${cls}`); + if (spec.plugin.docs) out.push(`// Docs: ${spec.plugin.docs}`); + out.push(""); + out.push( + `use ads_runtime::{Plugin, Message, Options, DecodeResult, Qualifiers, ResultFormatter, helpers};`, + ); + out.push(`use crate::escape_hatches;`); + out.push(""); + out.push(`pub struct ${cls};`); + out.push(""); + out.push(`impl Plugin for ${cls} {`); + out.push(` fn name(&self) -> &'static str { ${rustString(slug)} }`); + out.push(""); + + // qualifiers() + out.push(` fn qualifiers(&self) -> Qualifiers {`); + out.push(` Qualifiers {`); + out.push(` labels: vec![${spec.qualifiers.labels.map(rustString).join(", ")}],`); + if (spec.qualifiers.preambles && spec.qualifiers.preambles.length > 0) { + out.push(` preambles: vec![${spec.qualifiers.preambles.map(rustString).join(", ")}],`); + } else { + out.push(` preambles: vec![],`); + } + out.push(` }`); + out.push(` }`); + out.push(""); + + // decode() + out.push(` fn decode(&self, message: &Message, options: &Options) -> DecodeResult {`); + out.push( + ` let mut result = DecodeResult::new(self.name(), ${rustString(formattedDescription(spec.formatted))});`, + ); + out.push(` result.set_message(message);`); + out.push(""); + + if (spec.parse.kind === "custom") { + out.push( + ` return escape_hatches::${spec.parse.name}(self, message, &mut result, options);`, + ); + out.push(` }`); + out.push(`}`); + return out.join("\n") + "\n"; + } + + // Suppress raw auto-emit for fields consumed by a formatter (see TS emitter). + const consumedByFormatter = collectFormatterRefs(spec.formatted); + + for (const step of spec.parse.steps) { + emitParseStep(step, out, " "); + } + + if (spec.variants) { + emitVariants(spec.variants, out, " ", consumedByFormatter); + } else if (spec.fields) { + for (const field of spec.fields) { + emitField(field, out, " ", consumedByFormatter); + } + } + + emitFormatted(spec.formatted, out, " "); + + out.push(` result.set_decoded(true);`); + out.push(` result`); + out.push(` }`); + out.push(`}`); + return out.join("\n") + "\n"; +} + +function emitParseStep(step: ParseStep, out: string[], indent: string): void { + switch (step.kind) { + case "split": + out.push( + `${indent}let ${step.into}: Vec<&str> = message.text.split(${rustString(step.delimiter)}).collect();`, + ); + break; + case "regex": { + const onExpr = renderExpr({ kind: "var", ref: step.on }); + out.push(`${indent}let ${step.into}_re = helpers::regex(${rustString(step.pattern)});`); + // .captures(&str) — pass a reference; works whether onExpr is String + // (message.text) or &str (parts[n]) via Rust's deref coercion. + out.push(`${indent}let ${step.into} = match ${step.into}_re.captures(&${onExpr}) {`); + out.push(`${indent} Some(c) => c,`); + out.push(`${indent} None => return result.fail_unknown(&message.text),`); + out.push(`${indent}};`); + break; + } + case "substring": { + const fromExpr = renderExpr({ kind: "var", ref: step.from }); + if (step.length !== undefined) { + out.push( + `${indent}let ${step.into} = helpers::substring(${fromExpr}, ${step.start}, Some(${step.start + step.length}));`, + ); + } else if (step.end !== undefined) { + out.push( + `${indent}let ${step.into} = helpers::substring(${fromExpr}, ${step.start}, Some(${step.end}));`, + ); + } else { + out.push(`${indent}let ${step.into} = helpers::substring(${fromExpr}, ${step.start}, None);`); + } + break; + } + case "require_length": { + const checks: string[] = []; + if (step.equals !== undefined) checks.push(`${step.var}.len() != ${step.equals}`); + if (step.min !== undefined) checks.push(`${step.var}.len() < ${step.min}`); + if (step.max !== undefined) checks.push(`${step.var}.len() > ${step.max}`); + out.push(`${indent}if ${checks.join(" || ")} {`); + out.push(`${indent} return result.fail_unknown(&message.text);`); + out.push(`${indent}}`); + break; + } + case "bitfield": + for (const f of step.fields) { + out.push( + `${indent}let ${f.name} = helpers::bitslice(${renderExpr({ kind: "var", ref: step.source })}, ${f.bitStart}, ${f.bitEnd});`, + ); + } + break; + case "decode_ascii85": + out.push( + `${indent}let ${step.into} = helpers::decode_ascii85(${renderExpr({ kind: "var", ref: step.source })});`, + ); + break; + case "deflate": { + const srcExpr = renderExpr({ kind: "var", ref: step.source }); + // helpers::inflate takes &[u8]; src is Vec, so always borrow. + const sliced = step.offset ? `&${srcExpr}[${step.offset}..]` : `&${srcExpr}`; + out.push( + `${indent}let ${step.into} = helpers::inflate(${sliced}, ${rustString(step.format)});`, + ); + break; + } + case "base64": + out.push( + `${indent}let ${step.into} = helpers::base64_decode(${renderExpr({ kind: "var", ref: step.source })});`, + ); + break; + case "text_decode": + // helpers::text_decode takes &[u8]; src is Vec, so borrow. + out.push( + `${indent}let ${step.into} = helpers::text_decode(&${renderExpr({ kind: "var", ref: step.source })}, ${rustString(step.encoding)});`, + ); + break; + case "hex_decode": + out.push( + `${indent}let ${step.into} = helpers::hex_decode(${renderExpr({ kind: "var", ref: step.source })});`, + ); + break; + case "concat_bits": + out.push( + `${indent}let ${step.into} = helpers::concat_bits(&[${step.sources + .map((s) => renderExpr({ kind: "var", ref: s })) + .join(", ")}]);`, + ); + break; + case "custom": + out.push(`${indent}escape_hatches::${step.name}(self, message, &mut result, options);`); + break; + } +} + +function emitField( + field: FieldIR, + out: string[], + indent: string, + consumedByFormatter: Set, +): void { + const valueExpr = renderExpr(field.from); + const decodeExpr = field.decode ? renderDecodeCall(field.decode, valueExpr) : valueExpr; + const skipAutoRaw = consumedByFormatter.has(field.name); + if (field.when) { + // Hoist as Option so downstream formatters see the variable (None when + // the guard fails) instead of getting a scope error. Mirrors TS's + // `let X; if (cond) { X = ...; }` pattern. + out.push(`${indent}let ${field.name}: Option = if ${renderCondition(field.when)} {`); + out.push(`${indent} let v = ${decodeExpr};`); + if (!skipAutoRaw) out.push(`${indent} result.raw.insert(${rustString(field.name)}, v.clone().into());`); + out.push(`${indent} Some(v.into())`); + out.push(`${indent}} else {`); + out.push(`${indent} None`); + out.push(`${indent}};`); + } else { + out.push(`${indent}let ${field.name} = ${decodeExpr};`); + if (!skipAutoRaw) out.push(`${indent}result.raw.insert(${rustString(field.name)}, ${field.name}.clone().into());`); + } +} + +function emitVariants( + variants: VariantIR[], + out: string[], + indent: string, + consumedByFormatter: Set, +): void { + let first = true; + for (const v of variants) { + if (v.isDefault) { + out.push(`${indent}${first ? "{" : "else {"}`); + out.push(`${indent} return result.fail_unknown(&message.text);`); + out.push(`${indent}}`); + continue; + } + const cond = v.when ? renderCondition(v.when) : "true"; + out.push(`${indent}${first ? "if" : "else if"} ${cond} {`); + if (v.fields) { + for (const f of v.fields) { + emitField(f, out, indent + " ", consumedByFormatter); + } + } + out.push(`${indent}}`); + first = false; + } +} + +function collectFormatterRefs(formatted: FormattedIR): Set { + const refs = new Set(); + if (formatted.kind !== "structured") return refs; + for (const item of formatted.items) { + walkArgsForRefs(item.args, refs); + } + return refs; +} + +function walkArgsForRefs(node: unknown, refs: Set): void { + if (typeof node === "string") { + if (node.startsWith("$")) { + const m = node.match(/^\$([A-Za-z_][A-Za-z0-9_]*)/); + if (m && m[1]) refs.add(m[1]); + } + return; + } + if (Array.isArray(node)) { + for (const v of node) walkArgsForRefs(v, refs); + return; + } + if (node && typeof node === "object") { + for (const v of Object.values(node)) walkArgsForRefs(v, refs); + } +} + +function emitFormatted(formatted: FormattedIR, out: string[], indent: string): void { + if (formatted.kind === "custom") { + out.push(`${indent}escape_hatches::${formatted.name}(&mut result);`); + return; + } + for (const item of formatted.items) { + emitFormatterCall(item, out, indent); + } +} + +function emitFormatterCall(item: FormatterCall, out: string[], indent: string): void { + if (item.type === "custom" && item.customName) { + out.push(`${indent}escape_hatches::${item.customName}(&mut result);`); + return; + } + const methodMap: Record = { + position: "position", + altitude: "altitude", + speed: "speed", + heading: "heading", + timestamp: "timestamp", + callsign: "callsign", + flight_number: "flight_number", + tail_number: "tail", + airport_origin: "departure_airport", + airport_destination: "arrival_airport", + fuel: "fuel", + free_text: "unknown_arr", + }; + const method = methodMap[item.type]; + if (!method) { + out.push(`${indent}// TODO formatter: ${item.type}`); + return; + } + if (item.type === "position") { + const lat = item.args["latitude"] ?? item.args["lat"]; + const lon = item.args["longitude"] ?? item.args["lon"]; + if (lat !== undefined && lon !== undefined) { + out.push( + `${indent}ResultFormatter::position(&mut result, ${renderRustArg(lat)}, ${renderRustArg(lon)});`, + ); + return; + } + if (item.args["value"] !== undefined) { + out.push( + `${indent}ResultFormatter::position_value(&mut result, ${renderRustArg(item.args["value"])});`, + ); + return; + } + } + if (item.type === "free_text" && Array.isArray(item.args["values"])) { + // ResultFormatter::unknown_arr expects Vec; convert &str args + // via .to_string() at the call site rather than .clone() which yields &str. + const vals = (item.args["values"] as unknown[]) + .map((v) => { + if (typeof v === "string" && v.startsWith("$")) { + return `${renderExpr({ kind: "var", ref: v })}.to_string()`; + } + return renderRustArg(v); + }) + .join(", "); + out.push(`${indent}ResultFormatter::unknown_arr(&mut result, vec![${vals}]);`); + return; + } + if ("value" in item.args) { + out.push( + `${indent}ResultFormatter::${method}(&mut result, ${renderRustArg(item.args["value"])});`, + ); + } else { + out.push(`${indent}ResultFormatter::${method}(&mut result);`); + } +} + +function renderRustArg(v: unknown): string { + if (typeof v === "string" && v.startsWith("$")) { + return `${renderExpr({ kind: "var", ref: v })}.clone()`; + } + if (typeof v === "string") return rustString(v); + if (typeof v === "number") return String(v); + if (typeof v === "boolean") return String(v); + return rustString(JSON.stringify(v)); +} + +function renderDecodeCall(call: DecodeCall, valueExpr: string): string { + // Always emit two arguments — runtime helpers all accept (value, args_json) + // even when args is empty ('{}'). Uniform signature simplifies the runtime. + // + // Borrow the value expr so String / owned vars are passed as &str. For + // already-&str vars (e.g. parts[N] from Vec<&str>), Rust's auto-deref + // collapses the extra reference layer. + const argsJson = rustString(JSON.stringify(call.args)); + const borrowed = needsBorrow(valueExpr) ? `&${valueExpr}` : valueExpr; + if (call.fn === "custom") { + return `escape_hatches::${call.name}(${borrowed}, ${argsJson})`; + } + return `helpers::${call.fn}(${borrowed}, ${argsJson})`; +} + +function needsBorrow(expr: string): boolean { + // Skip explicit borrows / dereferences / numeric literals — they're + // already in the right shape. + if (expr.startsWith("&")) return false; + if (expr.startsWith("*")) return false; + if (/^-?\d/.test(expr)) return false; + return true; +} + +function renderExpr(expr: ValueExpr): string { + switch (expr.kind) { + case "var": + return varRefToRust(expr.ref); + case "literal": + if (typeof expr.value === "string") return rustString(expr.value); + return String(expr.value); + case "call": + if (expr.fn === "length") return `${varRefToRust(expr.arg)}.len()`; + return "/* unknown call */"; + } +} + +function varRefToRust(ref: string): string { + // $message.text → message.text (struct field) + // $parts[1] → parts[1] + // $m.unsplit_coords → &m["unsplit_coords"] (regex::Captures Index<&str> → str) + // + // The earlier code appended .as_str() on the named-group access which is + // an unstable feature (str_as_str). Indexing a Captures by &str already + // returns a &str, so the extra .as_str() is unnecessary and unstable. + let body = ref.slice(1); + body = body.replace(/^([a-z_][a-z0-9_]*)\.([a-z_][a-z0-9_]*)$/i, (_m, head, group) => { + if (head === "message") return `${head}.${group}`; + return `&${head}[${rustString(group)}]`; + }); + return body; +} + +function renderCondition(cond: Condition): string { + switch (cond.kind) { + case "equals": + return `${renderExpr(cond.left)} == ${renderExpr(cond.right)}`; + case "not_equal": + return `${renderExpr(cond.left)} != ${renderExpr(cond.right)}`; + case "matches": + return `helpers::regex(${rustString(cond.regex)}).is_match(${varRefToRust(cond.var)})`; + case "in": + return `[${(cond.values as unknown[]).map(renderRustArg).join(", ")}].contains(&${renderExpr(cond.value)})`; + case "all": + return cond.conds.map((c) => `(${renderCondition(c)})`).join(" && "); + case "any": + return cond.conds.map((c) => `(${renderCondition(c)})`).join(" || "); + case "not": + return `!(${renderCondition(cond.cond)})`; + } +} + +function rustString(s: string): string { + return `"${s.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`; +} + +function formattedDescription(formatted: FormattedIR): string { + return formatted.description; +} + +function pluginNameToSlug(name: string): string { + // Smart slug: insert hyphens at camelCase boundaries so generated names + // match the legacy TS names byte-for-byte (CBand → c-band, StarPOS → + // star-pos, 3Line → 3-line, but Label_4A stays label-4a). + let out = ""; + for (let i = 0; i < name.length; i++) { + const c = name[i] as string; + const prev = i > 0 ? (name[i - 1] as string) : ""; + const next = i + 1 < name.length ? (name[i + 1] as string) : ""; + if (i > 0 && /[A-Z]/.test(c)) { + if (/[a-z]/.test(prev)) out += "-"; + else if (/[0-9]/.test(prev) && /[a-z]/.test(next)) out += "-"; + else if (/[A-Z]/.test(prev) && /[a-z]/.test(next)) out += "-"; + } + out += c; + } + return out.replace(/_/g, "-").toLowerCase(); +} diff --git a/codegen/src/emit-typescript.ts b/codegen/src/emit-typescript.ts new file mode 100644 index 0000000..9183a44 --- /dev/null +++ b/codegen/src/emit-typescript.ts @@ -0,0 +1,424 @@ +import type { + Condition, + DecodeCall, + FieldIR, + FormatterCall, + FormattedIR, + ParseStep, + SpecIR, + ValueExpr, + VariantIR, +} from "./ir.js"; + +/** + * Emit idiomatic TypeScript plugin source from a SpecIR. + * + * Output: + * import { DecoderPlugin } from "@airframes/ads-runtime-ts"; + * import * as helpers from "@airframes/ads-runtime-ts/helpers"; + * import * as hatches from "../escape_hatches"; + * export class extends DecoderPlugin { ... } + * + * The runtime-ts package re-exports the existing DecoderPlugin / ResultFormatter / + * utils API from runtimes/typescript/. Stage 2 wires those exports up. + */ +export function emitTypeScript(spec: SpecIR): string { + const cls = spec.plugin.name; + const slug = pluginNameToSlug(cls); + + const out: string[] = []; + out.push(`// AUTO-GENERATED from ${spec.sourcePath}. Do not edit.`); + out.push(`// Plugin: ${cls}`); + if (spec.plugin.docs) out.push(`// Docs: ${spec.plugin.docs}`); + out.push(""); + out.push(`import { DecoderPlugin } from "@airframes/ads-runtime-ts";`); + out.push( + `import type { DecodeResult, Message, Options } from "@airframes/ads-runtime-ts";`, + ); + out.push(`import { ResultFormatter } from "@airframes/ads-runtime-ts";`); + out.push(`import * as helpers from "@airframes/ads-runtime-ts/helpers";`); + out.push(`import * as hatches from "../escape_hatches";`); + out.push(""); + + out.push(`export class ${cls} extends DecoderPlugin {`); + out.push(` name = ${JSON.stringify(slug)};`); + out.push(""); + out.push(` qualifiers() {`); + out.push(` return {`); + out.push(` labels: ${JSON.stringify(spec.qualifiers.labels)},`); + if (spec.qualifiers.preambles) { + out.push(` preambles: ${JSON.stringify(spec.qualifiers.preambles)},`); + } + out.push(` };`); + out.push(` }`); + out.push(""); + + out.push(` decode(message: Message, options: Options = {}): DecodeResult {`); + out.push( + ` const result = this.initResult(message, ${JSON.stringify(formattedDescription(spec.formatted))});`, + ); + out.push(""); + + // Parse phase. + if (spec.parse.kind === "custom") { + out.push(` return hatches.${spec.parse.name}(this, message, result, options);`); + out.push(` }`); + out.push(`}`); + return out.join("\n") + "\n"; + } + + // Pre-scan the formatter for $varname references. Fields whose name is + // consumed by a formatter call don't need an auto-emit `result.raw.X = X`, + // because the formatter writes to `result.raw` under its own canonical key + // (position, altitude, callsign, …). Without this suppression, the + // generated code writes both raw.latitude AND raw.position, diverging from + // the hand-written plugins (which only have raw.position). + const consumedByFormatter = collectFormatterRefs(spec.formatted); + + for (const step of spec.parse.steps) { + emitParseStep(step, out, " "); + } + + // Fields or Variants. + if (spec.variants) { + emitVariants(spec.variants, out, " ", consumedByFormatter); + } else if (spec.fields) { + for (const field of spec.fields) { + emitField(field, out, " ", consumedByFormatter); + } + } + + // Formatter. + emitFormatted(spec.formatted, out, " "); + + // Success path. + if (!hasExplicitDecodeLevelSetting(spec)) { + const level = spec.plugin.decodeLevel.toLowerCase(); + const tsLevel = level === "full" ? "'full'" : "'partial'"; + out.push(` this.setDecodeLevel(result, true, ${tsLevel});`); + } + out.push(` return result;`); + out.push(` }`); + out.push(`}`); + return out.join("\n") + "\n"; +} + +function emitParseStep(step: ParseStep, out: string[], indent: string): void { + switch (step.kind) { + case "split": + out.push( + `${indent}const ${step.into} = message.text.split(${JSON.stringify(step.delimiter)});`, + ); + break; + case "regex": { + const onExpr = renderExpr({ kind: "var", ref: step.on }); + const re = `new RegExp(${JSON.stringify(step.pattern)})`; + out.push(`${indent}const ${step.into}_match = ${onExpr}.match(${re});`); + out.push(`${indent}if (!${step.into}_match?.groups) {`); + out.push( + `${indent} return this.failUnknown(result, message.text, options);`, + ); + out.push(`${indent}}`); + out.push(`${indent}const ${step.into} = ${step.into}_match.groups;`); + break; + } + case "substring": { + const fromExpr = renderExpr({ kind: "var", ref: step.from }); + if (step.length !== undefined) { + out.push( + `${indent}const ${step.into} = ${fromExpr}.substring(${step.start}, ${step.start + step.length});`, + ); + } else if (step.end !== undefined) { + out.push( + `${indent}const ${step.into} = ${fromExpr}.substring(${step.start}, ${step.end});`, + ); + } else { + out.push(`${indent}const ${step.into} = ${fromExpr}.substring(${step.start});`); + } + break; + } + case "require_length": { + const checks: string[] = []; + if (step.equals !== undefined) checks.push(`${step.var}.length !== ${step.equals}`); + if (step.min !== undefined) checks.push(`${step.var}.length < ${step.min}`); + if (step.max !== undefined) checks.push(`${step.var}.length > ${step.max}`); + out.push(`${indent}if (${checks.join(" || ")}) {`); + out.push(`${indent} return this.failUnknown(result, message.text, options);`); + out.push(`${indent}}`); + break; + } + case "bitfield": + out.push(`${indent}// TODO bitfield: ${JSON.stringify(step.fields)}`); + for (const f of step.fields) { + out.push( + `${indent}const ${f.name} = helpers.bitslice(${renderExpr({ kind: "var", ref: step.source })}, ${f.bitStart}, ${f.bitEnd});`, + ); + } + break; + case "decode_ascii85": + out.push( + `${indent}const ${step.into} = helpers.decodeAscii85(${renderExpr({ kind: "var", ref: step.source })});`, + ); + break; + case "deflate": { + const srcExpr = renderExpr({ kind: "var", ref: step.source }); + const sliced = step.offset ? `${srcExpr}.slice(${step.offset})` : srcExpr; + out.push( + `${indent}const ${step.into} = helpers.inflate(${sliced}, ${JSON.stringify(step.format)});`, + ); + break; + } + case "base64": + out.push( + `${indent}const ${step.into} = helpers.base64ToUint8Array(${renderExpr({ kind: "var", ref: step.source })});`, + ); + break; + case "text_decode": + out.push( + `${indent}const ${step.into} = helpers.textDecode(${renderExpr({ kind: "var", ref: step.source })}, ${JSON.stringify(step.encoding)});`, + ); + break; + case "hex_decode": + out.push( + `${indent}const ${step.into} = helpers.hexDecode(${renderExpr({ kind: "var", ref: step.source })});`, + ); + break; + case "concat_bits": + out.push( + `${indent}const ${step.into} = helpers.concatBits([${step.sources + .map((s) => renderExpr({ kind: "var", ref: s })) + .join(", ")}]);`, + ); + break; + case "custom": + out.push(`${indent}hatches.${step.name}(this, message, result, options);`); + break; + } +} + +function emitField( + field: FieldIR, + out: string[], + indent: string, + consumedByFormatter: Set, +): void { + const decodeExpr = field.decode + ? renderDecodeCall(field.decode, renderExpr(field.from)) + : renderExpr(field.from); + const skipAutoRaw = consumedByFormatter.has(field.name); + if (field.when) { + // Declare outside the if so downstream formatters / variant-shared code + // can still reference the variable when the guard fails — it'll be + // undefined, matching the original hand-written plugins' implicit + // missing-field pattern. + out.push(`${indent}let ${field.name};`); + out.push(`${indent}if (${renderCondition(field.when)}) {`); + out.push(`${indent} ${field.name} = ${decodeExpr};`); + if (!skipAutoRaw) out.push(`${indent} result.raw.${field.name} = ${field.name};`); + out.push(`${indent}}`); + } else { + out.push(`${indent}const ${field.name} = ${decodeExpr};`); + if (!skipAutoRaw) out.push(`${indent}result.raw.${field.name} = ${field.name};`); + } +} + +function emitVariants( + variants: VariantIR[], + out: string[], + indent: string, + consumedByFormatter: Set, +): void { + let first = true; + for (const v of variants) { + if (v.isDefault) { + out.push(`${indent}${first ? "if" : "else"} {`); + out.push(`${indent} return this.failUnknown(result, message.text, options);`); + out.push(`${indent}}`); + continue; + } + const cond = v.when ? renderCondition(v.when) : "true"; + out.push(`${indent}${first ? "if" : "else if"} (${cond}) {`); + if (v.fields) { + for (const f of v.fields) { + emitField(f, out, indent + " ", consumedByFormatter); + } + } + out.push(`${indent}}`); + first = false; + } +} + +/** + * Pre-scan a FormattedIR for $varname references in formatter call args. + * Returns the set of bare variable names that are read by a formatter. + */ +function collectFormatterRefs(formatted: FormattedIR): Set { + const refs = new Set(); + if (formatted.kind !== "structured") return refs; + for (const item of formatted.items) { + walkArgsForRefs(item.args, refs); + } + return refs; +} + +function walkArgsForRefs(node: unknown, refs: Set): void { + if (typeof node === "string") { + if (node.startsWith("$")) { + // $name or $name.something or $name[N] — pull out the bare leading name. + const m = node.match(/^\$([A-Za-z_][A-Za-z0-9_]*)/); + if (m && m[1]) refs.add(m[1]); + } + return; + } + if (Array.isArray(node)) { + for (const v of node) walkArgsForRefs(v, refs); + return; + } + if (node && typeof node === "object") { + for (const v of Object.values(node)) walkArgsForRefs(v, refs); + } +} + +function emitFormatted(formatted: FormattedIR, out: string[], indent: string): void { + if (formatted.kind === "custom") { + out.push(`${indent}hatches.${formatted.name}(result);`); + return; + } + for (const item of formatted.items) { + emitFormatterCall(item, out, indent); + } +} + +function emitFormatterCall(item: FormatterCall, out: string[], indent: string): void { + if (item.type === "custom" && item.customName) { + out.push(`${indent}hatches.${item.customName}(result);`); + return; + } + // Map IR formatter type → ResultFormatter method. + const methodMap: Record = { + position: "position", + altitude: "altitude", + speed: "speed", + heading: "heading", + timestamp: "timestamp", + callsign: "callsign", + flight_number: "flightNumber", + tail_number: "tail", + airport_origin: "departureAirport", + airport_destination: "arrivalAirport", + fuel: "currentFuel", + free_text: "unknownArr", + }; + const method = methodMap[item.type]; + if (!method) { + out.push(`${indent}// TODO formatter: ${item.type}`); + return; + } + // Render the call: ResultFormatter.(result, ) + // Args depend on formatter type. For now, support a few common shapes. + const args: string[] = []; + if (item.type === "position") { + const lat = item.args["latitude"] ?? item.args["lat"]; + const lon = item.args["longitude"] ?? item.args["lon"]; + if (lat !== undefined && lon !== undefined) { + args.push(`{ latitude: ${renderArg(lat)}, longitude: ${renderArg(lon)} }`); + } else if (item.args["value"] !== undefined) { + args.push(renderArg(item.args["value"])); + } + } else if (item.type === "free_text" && Array.isArray(item.args["values"])) { + args.push(`[${(item.args["values"] as unknown[]).map(renderArg).join(", ")}]`); + } else if ("value" in item.args) { + args.push(renderArg(item.args["value"])); + } + out.push(`${indent}ResultFormatter.${method}(result, ${args.join(", ")});`); +} + +function renderArg(v: unknown): string { + if (typeof v === "string" && v.startsWith("$")) { + return renderExpr({ kind: "var", ref: v }); + } + return JSON.stringify(v); +} + +function renderDecodeCall(call: DecodeCall, valueExpr: string): string { + if (call.fn === "custom") { + return `hatches.${call.name}(${valueExpr}, ${JSON.stringify(call.args)})`; + } + const camel = call.fn.replace(/_([a-z])/g, (_m, c) => c.toUpperCase()); + if (Object.keys(call.args).length > 0) { + return `helpers.${camel}(${valueExpr}, ${JSON.stringify(call.args)})`; + } + return `helpers.${camel}(${valueExpr})`; +} + +function renderExpr(expr: ValueExpr): string { + switch (expr.kind) { + case "var": + return varRefToJs(expr.ref); + case "literal": + return JSON.stringify(expr.value); + case "call": + if (expr.fn === "length") return `${varRefToJs(expr.arg)}.length`; + return "/* unknown call */"; + } +} + +function varRefToJs(ref: string): string { + // $message.text → message.text + // $parts[1] → parts[1] + // $m.unsplit_coords → m.unsplit_coords + return ref.slice(1); +} + +function renderCondition(cond: Condition): string { + switch (cond.kind) { + case "equals": + return `${renderExpr(cond.left)} === ${renderExpr(cond.right)}`; + case "not_equal": + return `${renderExpr(cond.left)} !== ${renderExpr(cond.right)}`; + case "matches": + return `new RegExp(${JSON.stringify(cond.regex)}).test(${varRefToJs(cond.var)})`; + case "in": + return `${JSON.stringify(cond.values)}.includes(${renderExpr(cond.value)})`; + case "all": + return cond.conds.map((c) => `(${renderCondition(c)})`).join(" && "); + case "any": + return cond.conds.map((c) => `(${renderCondition(c)})`).join(" || "); + case "not": + return `!(${renderCondition(cond.cond)})`; + } +} + +function formattedDescription(formatted: FormattedIR): string { + return formatted.description; +} + +function hasExplicitDecodeLevelSetting(_spec: SpecIR): boolean { + return false; +} + +function pluginNameToSlug(name: string): string { + // Insert hyphens at camelCase boundaries so legacy slugs are preserved + // byte-for-byte. Three boundary rules: + // - lowercase→Uppercase (CBand → C-Band) + // - digit→Uppercase if the next char is lowercase (3Line → 3-Line, but 4A → 4A) + // - Uppercase→Uppercase if the next char is lowercase (StarPOS → Star-POS) + // Then lowercase and swap _ for -. + let out = ""; + for (let i = 0; i < name.length; i++) { + const c = name[i] as string; + const prev = i > 0 ? (name[i - 1] as string) : ""; + const next = i + 1 < name.length ? (name[i + 1] as string) : ""; + if (i > 0 && /[A-Z]/.test(c)) { + if (/[a-z]/.test(prev)) { + out += "-"; + } else if (/[0-9]/.test(prev) && /[a-z]/.test(next)) { + out += "-"; + } else if (/[A-Z]/.test(prev) && /[a-z]/.test(next)) { + out += "-"; + } + } + out += c; + } + return out.replace(/_/g, "-").toLowerCase(); +} diff --git a/codegen/src/index.ts b/codegen/src/index.ts new file mode 100644 index 0000000..5b62369 --- /dev/null +++ b/codegen/src/index.ts @@ -0,0 +1,6 @@ +export { loadSpec } from "./parse-spec.js"; +export { validateSpec, ValidationError } from "./validate.js"; +export { emitTypeScript } from "./emit-typescript.js"; +export { emitRust } from "./emit-rust.js"; +export { emitC } from "./emit-c.js"; +export type * from "./ir.js"; diff --git a/codegen/src/ir.ts b/codegen/src/ir.ts new file mode 100644 index 0000000..71d4a2b --- /dev/null +++ b/codegen/src/ir.ts @@ -0,0 +1,156 @@ +/** + * Typed intermediate representation (IR) of an ADS spec. + * + * Spec YAML → parsed object → validated against JSON Schema → lowered into this IR. + * All three emitters (TS, Rust, C) consume the IR; they never look at YAML directly. + * + * The IR uses discriminated unions for parse steps, decode-fns, conditions, and + * formatter calls so emitters can `switch (step.kind)` exhaustively. + */ + +export type SpecVersion = "1"; + +export interface PluginIR { + name: string; + type: "text" | "binary"; + docs?: string; + decodeLevel: "NONE" | "PARTIAL" | "MESSAGE" | "FULL"; +} + +export interface QualifiersIR { + labels: string[]; + preambles?: string[]; +} + +export type VarRef = string; + +export type ValueExpr = + | { kind: "var"; ref: VarRef } + | { kind: "literal"; value: string | number | boolean | null } + | { kind: "call"; fn: "length"; arg: VarRef }; + +export type ParseStep = + | { kind: "split"; delimiter: string; into: string } + | { kind: "regex"; pattern: string; on: VarRef; into: string } + | { kind: "substring"; from: VarRef; start: number; length?: number; end?: number; into: string } + | { kind: "require_length"; var: string; equals?: number; min?: number; max?: number; onFail: "fail" } + | { kind: "bitfield"; source: VarRef; fields: BitField[] } + | { kind: "decode_ascii85"; source: VarRef; into: string } + | { kind: "deflate"; source: VarRef; offset?: number; format: "raw" | "zlib" | "gzip"; into: string } + | { kind: "base64"; source: VarRef; into: string } + | { kind: "text_decode"; source: VarRef; encoding: "utf-8" | "ascii" | "latin1"; into: string } + | { kind: "hex_decode"; source: VarRef; into: string } + | { kind: "concat_bits"; sources: VarRef[]; into: string } + | { kind: "custom"; name: string }; + +export interface BitField { + name: string; + bitStart: number; + bitEnd: number; +} + +export type ParseBlock = + | { kind: "steps"; steps: ParseStep[] } + | { kind: "custom"; name: string }; + +export type DecodeCall = + | { + fn: + | "coordinate" + | "coordinate_decimal_minutes" + | "integer" + | "float" + | "string" + | "trim" + | "uppercase" + | "lowercase" + | "timestamp_hhmmss" + | "timestamp_ddhhmm" + | "hex_to_bytes" + | "callsign" + | "airport" + | "tail_number" + | "flight_number" + | "altitude_feet" + | "speed_knots" + | "heading_degrees" + | "fuel_kg" + | "fuel_lb" + | "json_parse"; + args: Record; + } + | { fn: "custom"; name: string; args: Record }; + +export interface FieldIR { + name: string; + from: ValueExpr; + decode?: DecodeCall; + when?: Condition; + default?: ValueExpr; + description?: string; +} + +export type Condition = + | { kind: "equals"; left: ValueExpr; right: ValueExpr } + | { kind: "not_equal"; left: ValueExpr; right: ValueExpr } + | { kind: "matches"; var: VarRef; regex: string } + | { kind: "in"; value: ValueExpr; values: Array } + | { kind: "all"; conds: Condition[] } + | { kind: "any"; conds: Condition[] } + | { kind: "not"; cond: Condition }; + +export interface VariantIR { + name?: string; + when?: Condition; + isDefault: boolean; + defaultAction?: "fail"; + fields?: FieldIR[]; + checksum?: ChecksumAlgorithmIR[]; +} + +export interface ChecksumAlgorithmIR { + algorithm: + | "ARINC_665_CRC32" + | "ARINC_6_CRC16" + | "CRC16_IBM_SDLC_REV" + | "CRC16_GENIBUS"; + when?: Condition; + on: { startByte: number; endByte: number }; + expect: { kind: "tail" | "head"; n: number } | { kind: "range"; startByte: number; endByte: number }; +} + +export interface FormatterCall { + type: + | "position" + | "altitude" + | "speed" + | "heading" + | "timestamp" + | "callsign" + | "flight_number" + | "tail_number" + | "airport_origin" + | "airport_destination" + | "fuel" + | "free_text" + | "custom"; + customName?: string; + args: Record; +} + +export type FormattedIR = + | { kind: "structured"; description: string; items: FormatterCall[] } + | { kind: "custom"; name: string; description: string }; + +export interface SpecIR { + specVersion: SpecVersion; + plugin: PluginIR; + qualifiers: QualifiersIR; + parse: ParseBlock; + fields?: FieldIR[]; + variants?: VariantIR[]; + checksum?: ChecksumAlgorithmIR[]; + onChecksumFail?: { decoded: boolean }; + formatted: FormattedIR; + sourcePath: string; +} diff --git a/codegen/src/parse-spec.ts b/codegen/src/parse-spec.ts new file mode 100644 index 0000000..aa9c515 --- /dev/null +++ b/codegen/src/parse-spec.ts @@ -0,0 +1,275 @@ +import { readFileSync } from "node:fs"; +import { parse as parseYaml } from "yaml"; +import { validateSpec } from "./validate.js"; +import type { + BitField, + ChecksumAlgorithmIR, + Condition, + DecodeCall, + FieldIR, + FormattedIR, + FormatterCall, + ParseBlock, + ParseStep, + SpecIR, + ValueExpr, + VariantIR, +} from "./ir.js"; + +/** + * Loads a single spec YAML file, validates against schema, and lowers to IR. + */ +export function loadSpec(path: string): SpecIR { + const raw = readFileSync(path, "utf8"); + const parsed = parseYaml(raw); + validateSpec(parsed, path); + return lowerToIR(parsed, path); +} + +function lowerToIR(spec: any, sourcePath: string): SpecIR { + return { + specVersion: spec.spec_version, + plugin: { + name: spec.plugin.name, + type: spec.plugin.type, + docs: spec.plugin.docs, + decodeLevel: spec.plugin.decode_level ?? "MESSAGE", + }, + qualifiers: { + labels: spec.qualifiers.labels, + preambles: spec.qualifiers.preambles, + }, + parse: lowerParse(spec.parse), + fields: spec.fields?.map(lowerField), + variants: spec.variants?.map(lowerVariant), + checksum: spec.checksum?.map(lowerChecksum), + onChecksumFail: spec.on_checksum_fail, + formatted: lowerFormatted(spec.formatted), + sourcePath, + }; +} + +function lowerParse(parse: any): ParseBlock { + if (parse && typeof parse === "object" && "custom" in parse && !Array.isArray(parse)) { + return { kind: "custom", name: parse.custom }; + } + if (!Array.isArray(parse)) { + throw new Error(`Invalid parse block: expected array or {custom}, got ${typeof parse}`); + } + return { kind: "steps", steps: parse.map(lowerStep) }; +} + +function lowerStep(step: any): ParseStep { + const into: string | undefined = step.into; + if ("split" in step) { + if (!into) throw new Error("split step requires 'into'"); + return { kind: "split", delimiter: step.split, into }; + } + if ("regex" in step) { + if (!into) throw new Error("regex step requires 'into'"); + return { kind: "regex", pattern: step.regex, on: step.on, into }; + } + if ("substring" in step) { + if (!into) throw new Error("substring step requires 'into'"); + return { + kind: "substring", + from: step.substring.from, + start: step.substring.start, + length: step.substring.length, + end: step.substring.end, + into, + }; + } + if ("require_length" in step) { + if (step.else !== "fail") { + throw new Error("require_length currently requires 'else: fail'"); + } + return { + kind: "require_length", + var: step.require_length.var, + equals: step.require_length.equals, + min: step.require_length.min, + max: step.require_length.max, + onFail: "fail", + }; + } + if ("bitfield" in step) { + return { + kind: "bitfield", + source: step.bitfield.source, + fields: step.bitfield.fields.map(lowerBitField), + }; + } + if ("decode_ascii85" in step) { + if (!into) throw new Error("decode_ascii85 step requires 'into'"); + return { kind: "decode_ascii85", source: step.decode_ascii85, into }; + } + if ("deflate" in step) { + if (!into) throw new Error("deflate step requires 'into'"); + return { + kind: "deflate", + source: step.deflate.source, + offset: step.deflate.offset, + format: step.deflate.format ?? "raw", + into, + }; + } + if ("base64" in step) { + if (!into) throw new Error("base64 step requires 'into'"); + return { kind: "base64", source: step.base64, into }; + } + if ("text_decode" in step) { + if (!into) throw new Error("text_decode step requires 'into'"); + return { + kind: "text_decode", + source: step.text_decode.source, + encoding: step.text_decode.encoding ?? "utf-8", + into, + }; + } + if ("hex_decode" in step) { + if (!into) throw new Error("hex_decode step requires 'into'"); + return { kind: "hex_decode", source: step.hex_decode, into }; + } + if ("concat_bits" in step) { + if (!into) throw new Error("concat_bits step requires 'into'"); + return { kind: "concat_bits", sources: step.concat_bits, into }; + } + if ("custom" in step) { + return { kind: "custom", name: step.custom }; + } + throw new Error(`Unknown parse step: ${JSON.stringify(step)}`); +} + +function lowerBitField(bf: any): BitField { + const [start, end] = bf.bits.split(":").map((s: string) => Number(s)); + return { name: bf.name, bitStart: start, bitEnd: end }; +} + +function lowerField(field: any): FieldIR { + return { + name: field.name, + from: lowerExpr(field.from), + decode: field.decode ? lowerDecode(field.decode) : undefined, + when: field.when ? lowerCondition(field.when) : undefined, + default: field.default !== undefined ? lowerExpr(field.default) : undefined, + description: field.description, + }; +} + +function lowerExpr(expr: unknown): ValueExpr { + if (typeof expr === "string" && expr.startsWith("$")) { + return { kind: "var", ref: expr }; + } + if (typeof expr === "object" && expr !== null) { + if ("length" in expr && typeof (expr as any).length === "string") { + return { kind: "call", fn: "length", arg: (expr as any).length }; + } + } + if ( + typeof expr === "string" || + typeof expr === "number" || + typeof expr === "boolean" || + expr === null + ) { + return { kind: "literal", value: expr as string | number | boolean | null }; + } + throw new Error(`Unsupported value expression: ${JSON.stringify(expr)}`); +} + +function lowerDecode(decode: any): DecodeCall { + if (decode.fn === "custom") { + return { fn: "custom", name: decode.custom, args: decode.args ?? {} }; + } + return { fn: decode.fn, args: decode.args ?? {} }; +} + +function lowerCondition(cond: any): Condition { + const keys = Object.keys(cond); + if (keys.length !== 1) { + throw new Error(`Condition must have exactly one key, got: ${keys.join(", ")}`); + } + const key = keys[0]!; + switch (key) { + case "equals": + return { kind: "equals", left: lowerExpr(cond.equals[0]), right: lowerExpr(cond.equals[1]) }; + case "not_equal": + return { + kind: "not_equal", + left: lowerExpr(cond.not_equal[0]), + right: lowerExpr(cond.not_equal[1]), + }; + case "matches": + return { kind: "matches", var: cond.matches[0], regex: cond.matches[1] }; + case "in": + return { + kind: "in", + value: lowerExpr(cond.in[0]), + values: cond.in[1], + }; + case "all": + return { kind: "all", conds: cond.all.map(lowerCondition) }; + case "any": + return { kind: "any", conds: cond.any.map(lowerCondition) }; + case "not": + return { kind: "not", cond: lowerCondition(cond.not) }; + default: + throw new Error(`Unknown condition kind: ${key}`); + } +} + +function lowerVariant(variant: any): VariantIR { + if ("default" in variant) { + return { isDefault: true, defaultAction: variant.default }; + } + return { + isDefault: false, + name: variant.name, + when: lowerCondition(variant.when), + fields: variant.fields?.map(lowerField), + checksum: variant.checksum?.map(lowerChecksum), + }; +} + +function lowerChecksum(c: any): ChecksumAlgorithmIR { + const [s, e] = c.on.split("..").map((n: string) => Number(n)); + let expect: ChecksumAlgorithmIR["expect"]; + if (c.expect.startsWith("tail")) { + expect = { kind: "tail", n: Number(c.expect.slice(4)) }; + } else if (c.expect.startsWith("head")) { + expect = { kind: "head", n: Number(c.expect.slice(4)) }; + } else { + const [rs, re] = c.expect.split("..").map((n: string) => Number(n)); + expect = { kind: "range", startByte: rs, endByte: re }; + } + return { + algorithm: c.algorithm, + when: c.when ? lowerCondition(c.when) : undefined, + on: { startByte: s, endByte: e }, + expect, + }; +} + +function lowerFormatted(formatted: any): FormattedIR { + if ("custom" in formatted) { + return { + kind: "custom", + name: formatted.custom, + description: formatted.description ?? "Custom", + }; + } + return { + kind: "structured", + description: formatted.description, + items: (formatted.items ?? []).map(lowerFormatterCall), + }; +} + +function lowerFormatterCall(item: any): FormatterCall { + const { type, custom, ...rest } = item; + return { + type, + customName: custom, + args: rest, + }; +} diff --git a/codegen/src/validate.ts b/codegen/src/validate.ts new file mode 100644 index 0000000..9033cc0 --- /dev/null +++ b/codegen/src/validate.ts @@ -0,0 +1,57 @@ +import type { ErrorObject, ValidateFunction } from "ajv"; +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { dirname, resolve } from "node:path"; +import { createRequire } from "node:module"; + +// ajv 8 / ajv-formats are CJS packages; use createRequire for clean interop +// rather than wrestling with default-pluck patterns under NodeNext. +const require = createRequire(import.meta.url); +const Ajv: new (opts?: object) => { compile: (s: unknown) => ValidateFunction } = + require("ajv/dist/2020"); +const addFormats: (ajv: object) => void = require("ajv-formats"); + +const here = dirname(fileURLToPath(import.meta.url)); + +let cachedValidator: ValidateFunction | undefined; + +function loadSchema(): ValidateFunction { + if (cachedValidator) return cachedValidator; + const schemaPath = resolve(here, "..", "..", "schema", "ads-v1.schema.json"); + const schema = JSON.parse(readFileSync(schemaPath, "utf8")); + const ajv = new Ajv({ allErrors: true, strict: false }); + addFormats(ajv); + const compiled = ajv.compile(schema); + cachedValidator = compiled; + return compiled; +} + +export interface ValidationFailure { + path: string; + errors: ErrorObject[]; +} + +export class ValidationError extends Error { + constructor(public readonly failure: ValidationFailure) { + super(formatErrors(failure)); + this.name = "ValidationError"; + } +} + +function formatErrors(failure: ValidationFailure): string { + const header = `Spec failed JSON Schema validation: ${failure.path}`; + const lines = failure.errors.map((e) => ` - ${e.instancePath || "/"} ${e.message ?? ""}`); + return [header, ...lines].join("\n"); +} + +/** + * Validates a parsed spec object against ads-v1.schema.json. Throws `ValidationError` + * on failure. Path is included in the error for human-friendly messages. + */ +export function validateSpec(spec: unknown, path: string): void { + const validator = loadSchema(); + const ok = validator(spec); + if (!ok) { + throw new ValidationError({ path, errors: validator.errors ?? [] }); + } +} diff --git a/codegen/tsconfig.json b/codegen/tsconfig.json new file mode 100644 index 0000000..5116176 --- /dev/null +++ b/codegen/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022"], + "outDir": "./dist", + "rootDir": "./src", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "resolveJsonModule": true + }, + "include": ["src/**/*"], + "exclude": ["dist", "node_modules", "tests"] +} diff --git a/codegen/vitest.config.ts b/codegen/vitest.config.ts new file mode 100644 index 0000000..7a16b27 --- /dev/null +++ b/codegen/vitest.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + include: ["tests/**/*.test.ts"], + coverage: { + provider: "v8", + reporter: ["text", "html"], + include: ["src/**/*.ts"], + }, + }, +}); diff --git a/corpus/.gitkeep b/corpus/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/corpus/README.md b/corpus/README.md new file mode 100644 index 0000000..37b4cf9 --- /dev/null +++ b/corpus/README.md @@ -0,0 +1,46 @@ +# Shared test corpus + +Golden input/expected pairs used by **every** language implementation. The corpus is the cross-language source of truth — if TS, Rust, and C disagree on a sample, the corpus says who is right. + +## Layout + +Mirrors `spec/`: + +``` +corpus/ +├── labels/10/POS/sample-001.json +├── labels/10/POS/sample-002-invalid.json +├── labels/44/POS/sample-001.json +├── wildcards/arinc_702/sample-001.json +└── ... +``` + +## Format + +```json +{ + "spec": "labels/10/POS", + "source": "acars-decoder-typescript/lib/plugins/Label_10_POS.test.ts", + "description": "human-readable test case description", + "input": { + "label": "10", + "text": "POS082150, N 3885,..." + }, + "expected": { + "decoded": true, + "decoder": { "name": "label-10-pos", "type": "pattern-match", "decodeLevel": "partial" }, + "formatted": { "description": "Position Report", "items": [ ... ] }, + "remaining": { "text": "..." } + } +} +``` + +## Extracting from TS tests + +`acars-decoder-typescript`'s `lib/plugins/*.test.ts` files are the source of truth. A small extractor script (planned, `codegen/scripts/extract-corpus.ts`) instruments the Jest run to dump `(input, decodeResult)` pairs to JSON. Manually-curated samples are also welcome and should set `source: "manual"`. + +## Validation + +Each language repo's CI loads every JSON file, runs the decoder against `input`, and deep-equals against `expected`. Divergence fails CI in that repo. + +A reference TypeScript interpreter (planned, `codegen/src/interpret.ts`) walks the spec IR directly and runs the corpus — this validates that specs are decodable before any language repo adopts them. diff --git a/corpus/labels/10/LDR/sample-001.json b/corpus/labels/10/LDR/sample-001.json new file mode 100644 index 0000000..2c50344 --- /dev/null +++ b/corpus/labels/10/LDR/sample-001.json @@ -0,0 +1,72 @@ +{ + "spec": "labels/10/LDR", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "10", + "text": "LDR01,189,C,SWA-2600-016,0,N 38.151,W 76.623,37003, 10.2,KATL,KLGA,KLGA,22/,/,/,0,0,,,,,,,0,0,0,00,,135.1,08.6,143.7,,," + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-10-ldr", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "38.151 N, 76.623 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "37003 feet" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "KATL" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KLGA" + }, + { + "type": "icao", + "code": "ALT_DST", + "label": "Alternate Destination", + "value": "KLGA" + }, + { + "type": "runway", + "code": "ARWY", + "label": "Arrival Runway", + "value": "22" + } + ] + }, + "raw": { + "position": { + "latitude": 38.151, + "longitude": -76.623 + }, + "altitude": 37003, + "departure_icao": "KATL", + "arrival_icao": "KLGA", + "alternate_icao": "KLGA", + "arrival_runway": "22" + }, + "remaining": { + "text": "LDR01,189,C,SWA-2600-016,0,0,0,,,,,,,0,0,0,00,,135.1,08.6,143.7,,," + } + } +} diff --git a/corpus/labels/10/LDR/sample-002.json b/corpus/labels/10/LDR/sample-002.json new file mode 100644 index 0000000..4fe6680 --- /dev/null +++ b/corpus/labels/10/LDR/sample-002.json @@ -0,0 +1,79 @@ +{ + "spec": "labels/10/LDR", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "10", + "text": "LDR01,189,C,SWA-2600-016,0,N 37.873,W 79.541,30998, 16.6,KBNA,KBOS,KBOS,27/,33L/,22L/,0,1,,,,,,,0,0,0,00,,131.2,11.4,142.6,,," + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-10-ldr", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "37.873 N, 79.541 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "30998 feet" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "KBNA" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KBOS" + }, + { + "type": "icao", + "code": "ALT_DST", + "label": "Alternate Destination", + "value": "KBOS" + }, + { + "type": "runway", + "code": "ARWY", + "label": "Arrival Runway", + "value": "27" + }, + { + "type": "runway", + "code": "ALT_ARWY", + "label": "Alternate Runway", + "value": "33L,22L" + } + ] + }, + "raw": { + "position": { + "latitude": 37.873, + "longitude": -79.541 + }, + "altitude": 30998, + "departure_icao": "KBNA", + "arrival_icao": "KBOS", + "alternate_icao": "KBOS", + "arrival_runway": "27", + "alternate_runway": "33L,22L" + }, + "remaining": { + "text": "LDR01,189,C,SWA-2600-016,0,0,1,,,,,,,0,0,0,00,,131.2,11.4,142.6,,," + } + } +} diff --git a/corpus/labels/10/LDR/sample-003.json b/corpus/labels/10/LDR/sample-003.json new file mode 100644 index 0000000..da94cfd --- /dev/null +++ b/corpus/labels/10/LDR/sample-003.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/10/LDR", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 3", + "input": { + "label": "10", + "text": "LDR Bogus Message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-10-ldr", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Position Report", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "LDR Bogus Message" + } + } +} diff --git a/corpus/labels/10/POS/sample-001.json b/corpus/labels/10/POS/sample-001.json new file mode 100644 index 0000000..a421203 --- /dev/null +++ b/corpus/labels/10/POS/sample-001.json @@ -0,0 +1,44 @@ +{ + "spec": "labels/10/POS", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "10", + "text": "POS082150, N 3885,W 7841,---,308,26922, 51,22290, 529, 19,-225,6" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-10-pos", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "38.850 N, 78.410 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "22290 feet" + } + ] + }, + "raw": { + "position": { + "latitude": 38.85, + "longitude": -78.41 + }, + "altitude": 22290 + }, + "remaining": { + "text": "POS082150,---,308,26922, 51, 529, 19,-225,6" + } + } +} diff --git a/corpus/labels/10/POS/sample-002.json b/corpus/labels/10/POS/sample-002.json new file mode 100644 index 0000000..b35a533 --- /dev/null +++ b/corpus/labels/10/POS/sample-002.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/10/POS", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "10", + "text": "POS Bogus Message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-10-pos", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Position Report", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "POS Bogus Message" + } + } +} diff --git a/corpus/labels/10/Slash/sample-001.json b/corpus/labels/10/Slash/sample-001.json new file mode 100644 index 0000000..3b4a855 --- /dev/null +++ b/corpus/labels/10/Slash/sample-001.json @@ -0,0 +1,86 @@ +{ + "spec": "labels/10/Slash", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "10", + "text": "/N39.182/W077.217/10/0.42/180/055/KIAD/0004/0028/00015/MOWAT/HUSEL/2349/YACKK/2352/" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-10-slash", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "39.182 N, 77.217 W" + }, + { + "type": "heading", + "code": "HDG", + "label": "Heading", + "value": "180" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "5500 feet" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KIAD" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "00:04:00" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "MOWAT > HUSEL@23:49:00 > YACKK@23:52:00" + } + ] + }, + "raw": { + "position": { + "latitude": 39.182, + "longitude": -77.217 + }, + "heading": 180, + "altitude": 5500, + "arrival_icao": "KIAD", + "eta_time": 240, + "route": { + "waypoints": [ + { + "name": "MOWAT" + }, + { + "name": "HUSEL", + "time": 85740 + }, + { + "name": "YACKK", + "time": 85920 + } + ] + } + }, + "remaining": { + "text": "10/0.42/0028/00015" + } + } +} diff --git a/corpus/labels/10/Slash/sample-002.json b/corpus/labels/10/Slash/sample-002.json new file mode 100644 index 0000000..21db6b7 --- /dev/null +++ b/corpus/labels/10/Slash/sample-002.json @@ -0,0 +1,93 @@ +{ + "spec": "labels/10/Slash", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "10", + "text": "/N39.019/W078.468/10/0.83/246/400/KCVG/0155/0073/00018/COLNS/STEVY/0120/FAIIR/0126/KTEB/" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-10-slash", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "39.019 N, 78.468 W" + }, + { + "type": "heading", + "code": "HDG", + "label": "Heading", + "value": "246" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "40000 feet" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KCVG" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "01:55:00" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "COLNS > STEVY@01:20:00 > FAIIR@01:26:00" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "KTEB" + } + ] + }, + "raw": { + "position": { + "latitude": 39.019, + "longitude": -78.468 + }, + "heading": 246, + "altitude": 40000, + "arrival_icao": "KCVG", + "eta_time": 6900, + "route": { + "waypoints": [ + { + "name": "COLNS" + }, + { + "name": "STEVY", + "time": 4800 + }, + { + "name": "FAIIR", + "time": 5160 + } + ] + }, + "departure_icao": "KTEB" + }, + "remaining": { + "text": "10/0.83/0073/00018/" + } + } +} diff --git a/corpus/labels/10/Slash/sample-003.json b/corpus/labels/10/Slash/sample-003.json new file mode 100644 index 0000000..eb34023 --- /dev/null +++ b/corpus/labels/10/Slash/sample-003.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/10/Slash", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 3", + "input": { + "label": "10", + "text": "/Bogus Message/" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-10-slash", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Position Report", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "/Bogus Message/" + } + } +} diff --git a/corpus/labels/12/N_Space/sample-001.json b/corpus/labels/12/N_Space/sample-001.json new file mode 100644 index 0000000..eaed6cc --- /dev/null +++ b/corpus/labels/12/N_Space/sample-001.json @@ -0,0 +1,44 @@ +{ + "spec": "labels/12/N_Space", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "12", + "text": "N 42.150,W121.187,39000,161859, 109,.C-GWSO,1742" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-12-n-space", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "42.150 N, 121.187 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "39000 feet" + } + ] + }, + "raw": { + "position": { + "latitude": 42.15, + "longitude": -121.187 + }, + "altitude": 39000 + }, + "remaining": { + "text": "161859,109,1742" + } + } +} diff --git a/corpus/labels/12/N_Space/sample-002.json b/corpus/labels/12/N_Space/sample-002.json new file mode 100644 index 0000000..a5a7db2 --- /dev/null +++ b/corpus/labels/12/N_Space/sample-002.json @@ -0,0 +1,44 @@ +{ + "spec": "labels/12/N_Space", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "12", + "text": "N 28.371,W 80.458,38000,170546, 100,.C-GVWJ,1736" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-12-n-space", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "28.371 N, 80.458 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "38000 feet" + } + ] + }, + "raw": { + "position": { + "latitude": 28.371, + "longitude": -80.458 + }, + "altitude": 38000 + }, + "remaining": { + "text": "170546,100,1736" + } + } +} diff --git a/corpus/labels/12/N_Space/sample-003.json b/corpus/labels/12/N_Space/sample-003.json new file mode 100644 index 0000000..e4b5557 --- /dev/null +++ b/corpus/labels/12/N_Space/sample-003.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/12/N_Space", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 3", + "input": { + "label": "12", + "text": "N Bogus message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-12-n-space", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Position Report", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "N Bogus message" + } + } +} diff --git a/corpus/labels/12/POS/sample-001.json b/corpus/labels/12/POS/sample-001.json new file mode 100644 index 0000000..716f83d --- /dev/null +++ b/corpus/labels/12/POS/sample-001.json @@ -0,0 +1,79 @@ +{ + "spec": "labels/12/POS", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "12", + "text": "POSN 390104W 754601,-------,1244,1446,,- 4,23249 12,FOB 73,ETA 1303,KATL,KPHL," + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-12-pos", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "39.018 N, 75.767 W" + }, + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "12:44:00" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "14460 feet" + }, + { + "type": "fuel_on_board", + "code": "FOB", + "label": "Fuel On Board", + "value": "73" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "13:03:00" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "KATL" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KPHL" + } + ] + }, + "raw": { + "position": { + "latitude": 39.01777777777777, + "longitude": -75.76694444444445 + }, + "message_timestamp": 45840, + "altitude": 14460, + "fuel_on_board": 73, + "eta_time": 46980, + "departure_icao": "KATL", + "arrival_icao": "KPHL" + }, + "remaining": { + "text": "-------,,- 4,23249 12," + } + } +} diff --git a/corpus/labels/12/POS/sample-002.json b/corpus/labels/12/POS/sample-002.json new file mode 100644 index 0000000..1cb5fb5 --- /dev/null +++ b/corpus/labels/12/POS/sample-002.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/12/POS", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "12", + "text": "POS Bogus message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-12-pos", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Position Report", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "POS Bogus message" + } + } +} diff --git a/corpus/labels/15/FST/sample-001.json b/corpus/labels/15/FST/sample-001.json new file mode 100644 index 0000000..b6cc32f --- /dev/null +++ b/corpus/labels/15/FST/sample-001.json @@ -0,0 +1,58 @@ +{ + "spec": "labels/15/FST", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "15", + "text": "FST01EGKKKMCON373488W0756927380 156 495 M53C 4427422721045313002518521710" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-15-fst", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "37.349 N, 75.693 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "38000 feet" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "EGKK" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KMCO" + } + ] + }, + "raw": { + "position": { + "latitude": 37.3488, + "longitude": -75.6927 + }, + "altitude": 38000, + "departure_icao": "EGKK", + "arrival_icao": "KMCO" + }, + "remaining": { + "text": "156 495 M53C 4427422721045313002518521710" + } + } +} diff --git a/corpus/labels/15/FST/sample-002.json b/corpus/labels/15/FST/sample-002.json new file mode 100644 index 0000000..ed20199 --- /dev/null +++ b/corpus/labels/15/FST/sample-002.json @@ -0,0 +1,27 @@ +{ + "spec": "labels/15/FST", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "15", + "text": "INI Bogus message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-15-fst", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Position Report", + "items": [] + }, + "raw": { + "position": {} + }, + "remaining": { + "text": "INI Bogus message" + } + } +} diff --git a/corpus/labels/15/Paren2/sample-001.json b/corpus/labels/15/Paren2/sample-001.json new file mode 100644 index 0000000..34f9179 --- /dev/null +++ b/corpus/labels/15/Paren2/sample-001.json @@ -0,0 +1,51 @@ +{ + "spec": "labels/15/Paren2", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "15", + "text": "(2N38448W 77216--- 28 20 7(Z" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-15", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "38.747 N, 77.360 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "2000 feet" + }, + { + "type": "outside_air_temperature", + "code": "OATEMP", + "label": "Outside Air Temperature (C)", + "value": "7 degrees" + } + ] + }, + "raw": { + "position": { + "latitude": 38.74666666666667, + "longitude": -77.36 + }, + "altitude": 2000, + "outside_air_temperature": 7 + }, + "remaining": { + "text": "--- 28" + } + } +} diff --git a/corpus/labels/15/Paren2/sample-002.json b/corpus/labels/15/Paren2/sample-002.json new file mode 100644 index 0000000..f6d7eb9 --- /dev/null +++ b/corpus/labels/15/Paren2/sample-002.json @@ -0,0 +1,51 @@ +{ + "spec": "labels/15/Paren2", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "15", + "text": "(2N40492W 77179248 99380-53(Z" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-15", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "40.820 N, 77.298 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "38000 feet" + }, + { + "type": "outside_air_temperature", + "code": "OATEMP", + "label": "Outside Air Temperature (C)", + "value": "-53 degrees" + } + ] + }, + "raw": { + "position": { + "latitude": 40.82, + "longitude": -77.29833333333333 + }, + "altitude": 38000, + "outside_air_temperature": -53 + }, + "remaining": { + "text": "248 99" + } + } +} diff --git a/corpus/labels/15/Paren2/sample-003.json b/corpus/labels/15/Paren2/sample-003.json new file mode 100644 index 0000000..6e91cf6 --- /dev/null +++ b/corpus/labels/15/Paren2/sample-003.json @@ -0,0 +1,44 @@ +{ + "spec": "labels/15/Paren2", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 3", + "input": { + "label": "15", + "text": "(2N39269W 77374--- 42---- 5(Z" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-15", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "39.448 N, 77.623 W" + }, + { + "type": "outside_air_temperature", + "code": "OATEMP", + "label": "Outside Air Temperature (C)", + "value": "-5 degrees" + } + ] + }, + "raw": { + "position": { + "latitude": 39.44833333333333, + "longitude": -77.62333333333333 + }, + "outside_air_temperature": -5 + }, + "remaining": { + "text": "--- 42" + } + } +} diff --git a/corpus/labels/15/Paren2/sample-004.json b/corpus/labels/15/Paren2/sample-004.json new file mode 100644 index 0000000..61ece50 --- /dev/null +++ b/corpus/labels/15/Paren2/sample-004.json @@ -0,0 +1,44 @@ +{ + "spec": "labels/15/Paren2", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 4", + "input": { + "label": "15", + "text": "(2N39018W 77284OFF11112418101313--------(Z" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-15", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "39.030 N, 77.473 W" + }, + { + "type": "time", + "code": "OFF", + "label": "Takeoff Time", + "value": "2024-11-11T18:10:00Z" + } + ] + }, + "raw": { + "position": { + "latitude": 39.03, + "longitude": -77.47333333333333 + }, + "off_time": 1731348600 + }, + "remaining": { + "text": "1313--------" + } + } +} diff --git a/corpus/labels/15/Paren2/sample-005.json b/corpus/labels/15/Paren2/sample-005.json new file mode 100644 index 0000000..dbb441d --- /dev/null +++ b/corpus/labels/15/Paren2/sample-005.json @@ -0,0 +1,44 @@ +{ + "spec": "labels/15/Paren2", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 5", + "input": { + "label": "15", + "text": "(2N42589W 83520OFF------13280606--------(Z" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-15", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "42.982 N, 83.867 W" + }, + { + "type": "time", + "code": "OFF", + "label": "Takeoff Time", + "value": "13:28:00" + } + ] + }, + "raw": { + "position": { + "latitude": 42.98166666666667, + "longitude": -83.86666666666666 + }, + "off_time": 48480 + }, + "remaining": { + "text": "0606--------" + } + } +} diff --git a/corpus/labels/15/Paren2/sample-006.json b/corpus/labels/15/Paren2/sample-006.json new file mode 100644 index 0000000..e815959 --- /dev/null +++ b/corpus/labels/15/Paren2/sample-006.json @@ -0,0 +1,44 @@ +{ + "spec": "labels/15/Paren2", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 6", + "input": { + "label": "15", + "text": "(2N39042W 77308OFF1311240327B1818 015(Z" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-15", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "39.070 N, 77.513 W" + }, + { + "type": "time", + "code": "OFF", + "label": "Takeoff Time", + "value": "2024-11-13T03:27:00Z" + } + ] + }, + "raw": { + "position": { + "latitude": 39.07, + "longitude": -77.51333333333334 + }, + "off_time": 1731468420 + }, + "remaining": { + "text": "B1818 015" + } + } +} diff --git a/corpus/labels/15/Paren2/sample-007.json b/corpus/labels/15/Paren2/sample-007.json new file mode 100644 index 0000000..ee99eb7 --- /dev/null +++ b/corpus/labels/15/Paren2/sample-007.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/15/Paren2", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 7", + "input": { + "label": "15", + "text": "(2 Bogus message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-15", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Position Report", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "(2 Bogus message" + } + } +} diff --git a/corpus/labels/16/AUTPOS/sample-001.json b/corpus/labels/16/AUTPOS/sample-001.json new file mode 100644 index 0000000..4b6085b --- /dev/null +++ b/corpus/labels/16/AUTPOS/sample-001.json @@ -0,0 +1,70 @@ +{ + "spec": "labels/16/AUTPOS", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "16", + "text": "283806/AUTPOS/LLD N400547 W0774954\r\n/ALT 12932/SAT ****\r\n/WND ******/TAT ****/TAS ****/CRZ ***\r\n/FOB 065120\r\n/DAT 260228/TIM 150742" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-16-autpos", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "day", + "code": "MSG_DAY", + "label": "Day of Month", + "value": "28" + }, + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "3806" + }, + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "40.096 N, 77.832 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "12932 feet" + }, + { + "type": "fuel_on_board", + "code": "FOB", + "label": "Fuel On Board", + "value": "65120" + }, + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "2026-02-28T15:07:42Z" + } + ] + }, + "raw": { + "day": 28, + "flight_number": "3806", + "position": { + "latitude": 40.09638888888889, + "longitude": -77.83166666666666 + }, + "altitude": 12932, + "fuel_on_board": 65120, + "message_timestamp": 1772291262 + }, + "remaining": {} + } +} diff --git a/corpus/labels/16/AUTPOS/sample-002.json b/corpus/labels/16/AUTPOS/sample-002.json new file mode 100644 index 0000000..91cc930 --- /dev/null +++ b/corpus/labels/16/AUTPOS/sample-002.json @@ -0,0 +1,114 @@ +{ + "spec": "labels/16/AUTPOS", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "16", + "text": "289142/AUTPOS/LLD N395538 W0753341 \r\n/ALT 35000/SAT -057\r\n/WND 239065/TAT -027/TAS 476/CRZ 836\r\n/FOB 107600\r\n/DAT 260228/TIM 132714" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-16-autpos", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "day", + "code": "MSG_DAY", + "label": "Day of Month", + "value": "28" + }, + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "9142" + }, + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "39.927 N, 75.561 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "35000 feet" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "POSITION at FL350: 239° at 65kt" + }, + { + "type": "temperature", + "code": "TATEMP", + "label": "Total Air Temperature (C)", + "value": "-27 degrees" + }, + { + "type": "airspeed", + "code": "ASPD", + "label": "True Airspeed", + "value": "476 knots" + }, + { + "type": "outside_air_temperature", + "code": "OATEMP", + "label": "Outside Air Temperature (C)", + "value": "-57 degrees" + }, + { + "type": "mach", + "code": "MACH", + "label": "Mach Number", + "value": "0.836 mach" + }, + { + "type": "fuel_on_board", + "code": "FOB", + "label": "Fuel On Board", + "value": "107600" + }, + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "2026-02-28T13:27:14Z" + } + ] + }, + "raw": { + "day": 28, + "flight_number": "9142", + "position": { + "latitude": 39.92722222222222, + "longitude": -75.56138888888889 + }, + "altitude": 35000, + "wind_data": [ + { + "waypoint": { + "name": "POSITION" + }, + "flightLevel": 350, + "windDirection": 239, + "windSpeed": 65 + } + ], + "total_air_temperature": -27, + "airspeed": 476, + "outside_air_temperature": -57, + "mach": 0.836, + "fuel_on_board": 107600, + "message_timestamp": 1772285234 + }, + "remaining": {} + } +} diff --git a/corpus/labels/16/AUTPOS/sample-003.json b/corpus/labels/16/AUTPOS/sample-003.json new file mode 100644 index 0000000..5595849 --- /dev/null +++ b/corpus/labels/16/AUTPOS/sample-003.json @@ -0,0 +1,23 @@ +{ + "spec": "labels/16/AUTPOS", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 3", + "input": { + "label": "16", + "text": "invalid AUTPOS message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-16-autpos", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Unknown", + "items": [] + }, + "raw": {}, + "remaining": {} + } +} diff --git a/corpus/labels/16/Honeywell/sample-001.json b/corpus/labels/16/Honeywell/sample-001.json new file mode 100644 index 0000000..68598de --- /dev/null +++ b/corpus/labels/16/Honeywell/sample-001.json @@ -0,0 +1,51 @@ +{ + "spec": "labels/16/Honeywell", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "16", + "text": "(2AAABN39211W 77144KTEBMMTO-/A(Z" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-16-honeywell", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "39.352 N, 77.240 W" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "KTEB" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "MMTO" + } + ] + }, + "raw": { + "position": { + "latitude": 39.35166666666667, + "longitude": -77.24 + }, + "departure_icao": "KTEB", + "arrival_icao": "MMTO" + }, + "remaining": { + "text": "AAAB/A" + } + } +} diff --git a/corpus/labels/16/Honeywell/sample-002.json b/corpus/labels/16/Honeywell/sample-002.json new file mode 100644 index 0000000..9c24a80 --- /dev/null +++ b/corpus/labels/16/Honeywell/sample-002.json @@ -0,0 +1,53 @@ +{ + "spec": "labels/16/Honeywell", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "16", + "text": "(2AAAAN37265W 78334-SSI /O(Z" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-16-honeywell", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "37.442 N, 78.557 W" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "SSI > /O" + } + ] + }, + "raw": { + "position": { + "latitude": 37.44166666666667, + "longitude": -78.55666666666667 + }, + "route": { + "waypoints": [ + { + "name": "SSI" + }, + { + "name": "/O" + } + ] + } + }, + "remaining": { + "text": "AAAA/O" + } + } +} diff --git a/corpus/labels/16/Honeywell/sample-003.json b/corpus/labels/16/Honeywell/sample-003.json new file mode 100644 index 0000000..fe39900 --- /dev/null +++ b/corpus/labels/16/Honeywell/sample-003.json @@ -0,0 +1,53 @@ +{ + "spec": "labels/16/Honeywell", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 3", + "input": { + "label": "16", + "text": "(2AAABN37197W 78404-SLOJOGRONK/O(Z" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-16-honeywell", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "37.328 N, 78.673 W" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "SLOJO > GRONK" + } + ] + }, + "raw": { + "position": { + "latitude": 37.32833333333333, + "longitude": -78.67333333333333 + }, + "route": { + "waypoints": [ + { + "name": "SLOJO" + }, + { + "name": "GRONK" + } + ] + } + }, + "remaining": { + "text": "AAAB/O" + } + } +} diff --git a/corpus/labels/16/Honeywell/sample-004.json b/corpus/labels/16/Honeywell/sample-004.json new file mode 100644 index 0000000..0db1be6 --- /dev/null +++ b/corpus/labels/16/Honeywell/sample-004.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/16/Honeywell", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 4", + "input": { + "label": "16", + "text": "(2 Bogus message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-16-honeywell", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Position Report", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "(2 Bogus message" + } + } +} diff --git a/corpus/labels/16/N_Space/sample-001.json b/corpus/labels/16/N_Space/sample-001.json new file mode 100644 index 0000000..58654b6 --- /dev/null +++ b/corpus/labels/16/N_Space/sample-001.json @@ -0,0 +1,44 @@ +{ + "spec": "labels/16/N_Space", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "16", + "text": "N 44.203,W 86.546,31965,6, 290" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-16-n-space", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "44.203 N, 86.546 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "31965 feet" + } + ] + }, + "raw": { + "position": { + "latitude": 44.203, + "longitude": -86.546 + }, + "altitude": 31965 + }, + "remaining": { + "text": "6,290" + } + } +} diff --git a/corpus/labels/16/N_Space/sample-002.json b/corpus/labels/16/N_Space/sample-002.json new file mode 100644 index 0000000..ada421e --- /dev/null +++ b/corpus/labels/16/N_Space/sample-002.json @@ -0,0 +1,35 @@ +{ + "spec": "labels/16/N_Space", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "16", + "text": "N 28.177/W 96.055" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-16-n-space", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "28.177 N, 96.055 W" + } + ] + }, + "raw": { + "position": { + "latitude": 28.177, + "longitude": -96.055 + } + }, + "remaining": {} + } +} diff --git a/corpus/labels/16/N_Space/sample-003.json b/corpus/labels/16/N_Space/sample-003.json new file mode 100644 index 0000000..7d46e78 --- /dev/null +++ b/corpus/labels/16/N_Space/sample-003.json @@ -0,0 +1,44 @@ +{ + "spec": "labels/16/N_Space", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 3", + "input": { + "label": "16", + "text": "N 44.988,W121.644,35940,6, 170" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-16-n-space", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "44.988 N, 121.644 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "35940 feet" + } + ] + }, + "raw": { + "position": { + "latitude": 44.988, + "longitude": -121.644 + }, + "altitude": 35940 + }, + "remaining": { + "text": "6,170" + } + } +} diff --git a/corpus/labels/16/N_Space/sample-004.json b/corpus/labels/16/N_Space/sample-004.json new file mode 100644 index 0000000..de6358c --- /dev/null +++ b/corpus/labels/16/N_Space/sample-004.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/16/N_Space", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 4", + "input": { + "label": "16", + "text": "N Bogus message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-16-n-space", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Position Report", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "N Bogus message" + } + } +} diff --git a/corpus/labels/16/POSA1/sample-001.json b/corpus/labels/16/POSA1/sample-001.json new file mode 100644 index 0000000..987de40 --- /dev/null +++ b/corpus/labels/16/POSA1/sample-001.json @@ -0,0 +1,62 @@ +{ + "spec": "labels/16/POSA1", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "16", + "text": "POSA1N37358W 77279,GEARS ,221626,370,BBOBO ,222053,,-61,139,1174,829" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-16-posa1", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "37.358 N, 77.279 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "37000 feet" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "GEARS@22:16:26 > BBOBO@22:20:53" + } + ] + }, + "raw": { + "position": { + "latitude": 37.358, + "longitude": -77.279 + }, + "altitude": 37000, + "route": { + "waypoints": [ + { + "name": "GEARS", + "time": 80186 + }, + { + "name": "BBOBO", + "time": 80453 + } + ] + } + }, + "remaining": { + "text": ",-61,139,1174,829" + } + } +} diff --git a/corpus/labels/16/POSA1/sample-002.json b/corpus/labels/16/POSA1/sample-002.json new file mode 100644 index 0000000..2a3a587 --- /dev/null +++ b/corpus/labels/16/POSA1/sample-002.json @@ -0,0 +1,62 @@ +{ + "spec": "labels/16/POSA1", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "16", + "text": "POSA1N38843W 78790,RONZZ ,005159,390,RAMAY ,010055,,*****,*****, 744, 0" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-16-posa1", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "38.843 N, 78.790 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "39000 feet" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "RONZZ@00:51:59 > RAMAY@01:00:55" + } + ] + }, + "raw": { + "position": { + "latitude": 38.843, + "longitude": -78.79 + }, + "altitude": 39000, + "route": { + "waypoints": [ + { + "name": "RONZZ", + "time": 3119 + }, + { + "name": "RAMAY", + "time": 3655 + } + ] + } + }, + "remaining": { + "text": ",*****,*****, 744, 0" + } + } +} diff --git a/corpus/labels/16/POSA1/sample-003.json b/corpus/labels/16/POSA1/sample-003.json new file mode 100644 index 0000000..14cc844 --- /dev/null +++ b/corpus/labels/16/POSA1/sample-003.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/16/POSA1", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 3", + "input": { + "label": "16", + "text": "N Bogus message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-16-posa1", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Position Report", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "N Bogus message" + } + } +} diff --git a/corpus/labels/16/TOD/sample-001.json b/corpus/labels/16/TOD/sample-001.json new file mode 100644 index 0000000..62a7201 --- /dev/null +++ b/corpus/labels/16/TOD/sample-001.json @@ -0,0 +1,58 @@ +{ + "spec": "labels/16/TOD", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "16", + "text": "005236,36787,0135, 97,N 38.364 W 75.226" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-16-tod", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "00:52:36" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "36787 feet" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "01:35:00" + }, + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "38.364 N, 75.226 W" + } + ] + }, + "raw": { + "message_timestamp": 3156, + "altitude": 36787, + "eta_time": 5700, + "position": { + "latitude": 38.364, + "longitude": -75.226 + } + }, + "remaining": { + "text": " 97" + } + } +} diff --git a/corpus/labels/16/TOD/sample-002.json b/corpus/labels/16/TOD/sample-002.json new file mode 100644 index 0000000..7956b60 --- /dev/null +++ b/corpus/labels/16/TOD/sample-002.json @@ -0,0 +1,65 @@ +{ + "spec": "labels/16/TOD", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "16", + "text": "110112,36000,1206, 51,N 45.140 E 16.341/SXS7SL" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-16-tod", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "11:01:12" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "36000 feet" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "12:06:00" + }, + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "45.140 N, 16.341 E" + }, + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "SXS7SL" + } + ] + }, + "raw": { + "message_timestamp": 39672, + "altitude": 36000, + "eta_time": 43560, + "position": { + "latitude": 45.14, + "longitude": 16.341 + }, + "flight_number": "SXS7SL" + }, + "remaining": { + "text": " 51" + } + } +} diff --git a/corpus/labels/16/TOD/sample-003.json b/corpus/labels/16/TOD/sample-003.json new file mode 100644 index 0000000..6eb0124 --- /dev/null +++ b/corpus/labels/16/TOD/sample-003.json @@ -0,0 +1,41 @@ +{ + "spec": "labels/16/TOD", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 3", + "input": { + "label": "16", + "text": "110122,,1206, 92,N . MMMM.MMM" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-16-tod", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "11:01:22" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "12:06:00" + } + ] + }, + "raw": { + "message_timestamp": 39682, + "eta_time": 43560 + }, + "remaining": { + "text": " 92" + } + } +} diff --git a/corpus/labels/16/TOD/sample-004.json b/corpus/labels/16/TOD/sample-004.json new file mode 100644 index 0000000..b6dbf07 --- /dev/null +++ b/corpus/labels/16/TOD/sample-004.json @@ -0,0 +1,58 @@ +{ + "spec": "labels/16/TOD", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 4", + "input": { + "label": "16", + "text": "001415,20274,0047, 3740,N3835.95 W07858.88" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-16-tod", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "00:14:15" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "20274 feet" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "00:47:00" + }, + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "38.359 N, 78.589 W" + } + ] + }, + "raw": { + "message_timestamp": 855, + "altitude": 20274, + "eta_time": 2820, + "position": { + "latitude": 38.3595, + "longitude": -78.5888 + } + }, + "remaining": { + "text": " 3740" + } + } +} diff --git a/corpus/labels/16/TOD/sample-005.json b/corpus/labels/16/TOD/sample-005.json new file mode 100644 index 0000000..a434494 --- /dev/null +++ b/corpus/labels/16/TOD/sample-005.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/16/TOD", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 5", + "input": { + "label": "16", + "text": "N Bogus message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-16-tod", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Position Report", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "N Bogus message" + } + } +} diff --git a/corpus/labels/1L/070/sample-001.json b/corpus/labels/1L/070/sample-001.json new file mode 100644 index 0000000..25599ac --- /dev/null +++ b/corpus/labels/1L/070/sample-001.json @@ -0,0 +1,65 @@ +{ + "spec": "labels/1L/070", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "1L", + "text": "000000070LOWW,KEWR,0932,1744,N 49.223,E 12.038,0659" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-1l-070", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "LOWW" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KEWR" + }, + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "09:32:00" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "17:44:00" + }, + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "49.223 N, 12.038 E" + } + ] + }, + "raw": { + "departure_icao": "LOWW", + "arrival_icao": "KEWR", + "message_timestamp": 34320, + "eta_time": 63840, + "position": { + "latitude": 49.223, + "longitude": 12.038 + } + }, + "remaining": { + "text": "0659" + } + } +} diff --git a/corpus/labels/1L/070/sample-002.json b/corpus/labels/1L/070/sample-002.json new file mode 100644 index 0000000..34b57ee --- /dev/null +++ b/corpus/labels/1L/070/sample-002.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/1L/070", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "1L", + "text": "POS Bogus Message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-1l-070", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Position Report", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "POS Bogus Message" + } + } +} diff --git a/corpus/labels/1L/660/sample-001.json b/corpus/labels/1L/660/sample-001.json new file mode 100644 index 0000000..dc91fe2 --- /dev/null +++ b/corpus/labels/1L/660/sample-001.json @@ -0,0 +1,64 @@ +{ + "spec": "labels/1L/660", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "1L", + "text": "000000660N50442E005566,100444359SOG-06 ,,--- 21-,83617441" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-1l-660", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "50.737 N, 5.943 E" + }, + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "10:04:44" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "35900 feet" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "SOG-06" + } + ] + }, + "raw": { + "position": { + "latitude": 50.736666666666665, + "longitude": 5.943333333333333 + }, + "message_timestamp": 36284, + "altitude": 35900, + "route": { + "waypoints": [ + { + "name": "SOG-06" + } + ] + } + }, + "remaining": { + "text": ",--- 21-,83617441" + } + } +} diff --git a/corpus/labels/1L/660/sample-002.json b/corpus/labels/1L/660/sample-002.json new file mode 100644 index 0000000..066595e --- /dev/null +++ b/corpus/labels/1L/660/sample-002.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/1L/660", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "1L", + "text": "POS Bogus Message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-1l-660", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Position Report", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "POS Bogus Message" + } + } +} diff --git a/corpus/labels/1M/Slash/sample-001.json b/corpus/labels/1M/Slash/sample-001.json new file mode 100644 index 0000000..a1b9b97 --- /dev/null +++ b/corpus/labels/1M/Slash/sample-001.json @@ -0,0 +1,61 @@ +{ + "spec": "labels/1M/Slash", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "1M", + "text": "/BA0843/ETA01/230822/LDSP/EGLL/EGSS/2JK0\n1940/EGLL27L/10" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-1m-slash", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "ETA Report", + "items": [ + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "LDSP" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "EGLL" + }, + { + "type": "icao", + "code": "ALT_DST", + "label": "Alternate Destination", + "value": "EGSS" + }, + { + "type": "runway", + "code": "ARWY", + "label": "Arrival Runway", + "value": "27L" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "2023-08-22T19:40:00Z" + } + ] + }, + "raw": { + "flight_number": "BA0843", + "departure_icao": "LDSP", + "arrival_icao": "EGLL", + "alternate_icao": "EGSS", + "arrival_runway": "27L", + "eta_time": 1692733200 + }, + "remaining": {} + } +} diff --git a/corpus/labels/20/POS/sample-001.json b/corpus/labels/20/POS/sample-001.json new file mode 100644 index 0000000..0e2a955 --- /dev/null +++ b/corpus/labels/20/POS/sample-001.json @@ -0,0 +1,36 @@ +{ + "spec": "labels/20/POS", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "20", + "text": "POSN38160W077075,,211733,360,OTT,212041,,N42,19689,40,544" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-20-pos", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "38.160 N, 77.075 W" + } + ] + }, + "raw": { + "preamble": "POS", + "position": { + "latitude": 38.16, + "longitude": -77.075 + } + }, + "remaining": {} + } +} diff --git a/corpus/labels/20/POS/sample-002.json b/corpus/labels/20/POS/sample-002.json new file mode 100644 index 0000000..20daed1 --- /dev/null +++ b/corpus/labels/20/POS/sample-002.json @@ -0,0 +1,36 @@ +{ + "spec": "labels/20/POS", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "20", + "text": "POSN38160W077075,,211733,360,OTT" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-20-pos", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "38.160 N, 77.075 W" + } + ] + }, + "raw": { + "preamble": "POS", + "position": { + "latitude": 38.16, + "longitude": -77.075 + } + }, + "remaining": {} + } +} diff --git a/corpus/labels/20/POS/sample-003.json b/corpus/labels/20/POS/sample-003.json new file mode 100644 index 0000000..9148073 --- /dev/null +++ b/corpus/labels/20/POS/sample-003.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/20/POS", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 3", + "input": { + "label": "20", + "text": "POSUNKNOWN" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-20-pos", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Position Report", + "items": [] + }, + "raw": { + "preamble": "POS" + }, + "remaining": {} + } +} diff --git a/corpus/labels/21/POS/sample-001.json b/corpus/labels/21/POS/sample-001.json new file mode 100644 index 0000000..fbfb323 --- /dev/null +++ b/corpus/labels/21/POS/sample-001.json @@ -0,0 +1,73 @@ +{ + "spec": "labels/21/POS", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "21", + "text": "POSN 39.841W 75.790, 220,184218,17222,22051, 34,- 4,204748,KTPA" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-21-pos", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "39.840 N, 75.790 W" + }, + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "18:42:18" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "17222 feet" + }, + { + "type": "outside_air_temperature", + "code": "OATEMP", + "label": "Outside Air Temperature (C)", + "value": "-4 degrees" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "20:47:48" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KTPA" + } + ] + }, + "raw": { + "preamble": "POS", + "position": { + "latitude": 39.84, + "longitude": -75.79 + }, + "message_timestamp": 67338, + "altitude": 17222, + "outside_air_temperature": -4, + "eta_time": 74868, + "arrival_icao": "KTPA" + }, + "remaining": { + "text": " 220,22051, 34" + } + } +} diff --git a/corpus/labels/21/POS/sample-002.json b/corpus/labels/21/POS/sample-002.json new file mode 100644 index 0000000..95a12a7 --- /dev/null +++ b/corpus/labels/21/POS/sample-002.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/21/POS", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "21", + "text": "POS Bogus message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-21-pos", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Position Report", + "items": [] + }, + "raw": { + "preamble": "POS" + }, + "remaining": {} + } +} diff --git a/corpus/labels/22/OFF/sample-001.json b/corpus/labels/22/OFF/sample-001.json new file mode 100644 index 0000000..1a1a915 --- /dev/null +++ b/corpus/labels/22/OFF/sample-001.json @@ -0,0 +1,76 @@ +{ + "spec": "labels/22/OFF", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "22", + "text": "OFF01YX3661/25251712KIADKPWM171207 92" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-22-off", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Takeoff Report", + "items": [ + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "YX3661" + }, + { + "type": "day", + "code": "DEP_DAY", + "label": "Departure Day", + "value": "25" + }, + { + "type": "day", + "code": "ARR_DAY", + "label": "Arrival Day", + "value": "25" + }, + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "17:12:00" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "KIAD" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KPWM" + }, + { + "type": "time", + "code": "OFF", + "label": "Takeoff Time", + "value": "17:12:07" + } + ] + }, + "raw": { + "flight_number": "YX3661", + "departure_day": 25, + "arrival_day": 25, + "message_timestamp": 61920, + "departure_icao": "KIAD", + "arrival_icao": "KPWM", + "off_time": 61927 + }, + "remaining": { + "text": " 92" + } + } +} diff --git a/corpus/labels/22/OFF/sample-002.json b/corpus/labels/22/OFF/sample-002.json new file mode 100644 index 0000000..1a0ef5c --- /dev/null +++ b/corpus/labels/22/OFF/sample-002.json @@ -0,0 +1,86 @@ +{ + "spec": "labels/22/OFF", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "22", + "text": "OFF02XA0000/N38568 W077261251152KIADEPRZ1152****1958" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-22-off", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Takeoff Report", + "items": [ + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "XA0000" + }, + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "38.947 N, 77.435 W" + }, + { + "type": "day", + "code": "MSG_DAY", + "label": "Day of Month", + "value": "25" + }, + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "11:52:00" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "KIAD" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "EPRZ" + }, + { + "type": "time", + "code": "OFF", + "label": "Takeoff Time", + "value": "11:52:00" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "19:58:00" + } + ] + }, + "raw": { + "flight_number": "XA0000", + "position": { + "latitude": 38.946666666666665, + "longitude": -77.435 + }, + "day": 25, + "message_timestamp": 42720, + "departure_icao": "KIAD", + "arrival_icao": "EPRZ", + "off_time": 42720, + "eta_time": 71880 + }, + "remaining": { + "text": "****" + } + } +} diff --git a/corpus/labels/22/OFF/sample-003.json b/corpus/labels/22/OFF/sample-003.json new file mode 100644 index 0000000..d9aa73b --- /dev/null +++ b/corpus/labels/22/OFF/sample-003.json @@ -0,0 +1,48 @@ +{ + "spec": "labels/22/OFF", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 3", + "input": { + "label": "22", + "text": "OFF02\r\nKBWI,KIND,1237,18.4" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-22-off", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Takeoff Report", + "items": [ + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "KBWI" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KIND" + }, + { + "type": "time", + "code": "OFF", + "label": "Takeoff Time", + "value": "12:37:00" + } + ] + }, + "raw": { + "departure_icao": "KBWI", + "arrival_icao": "KIND", + "off_time": 45420 + }, + "remaining": { + "text": "18.4" + } + } +} diff --git a/corpus/labels/22/OFF/sample-004.json b/corpus/labels/22/OFF/sample-004.json new file mode 100644 index 0000000..6c84e1e --- /dev/null +++ b/corpus/labels/22/OFF/sample-004.json @@ -0,0 +1,23 @@ +{ + "spec": "labels/22/OFF", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 4", + "input": { + "label": "22", + "text": "POS Bogus message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-22-off", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Takeoff Report", + "items": [] + }, + "raw": {}, + "remaining": {} + } +} diff --git a/corpus/labels/22/POS/sample-001.json b/corpus/labels/22/POS/sample-001.json new file mode 100644 index 0000000..7454c56 --- /dev/null +++ b/corpus/labels/22/POS/sample-001.json @@ -0,0 +1,51 @@ +{ + "spec": "labels/22/POS", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "22", + "text": "N 370824W 760010,-------,194936,30418, , , ,M 42,27335 42, 107," + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-22-pos", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "37.082 N, 76.001 W" + }, + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "19:49:36" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "30418 feet" + } + ] + }, + "raw": { + "position": { + "latitude": 37.0824, + "longitude": -76.001 + }, + "message_timestamp": 71376, + "altitude": 30418 + }, + "remaining": { + "text": "-------, , , ,M 42,27335 42, 107," + } + } +} diff --git a/corpus/labels/22/POS/sample-002.json b/corpus/labels/22/POS/sample-002.json new file mode 100644 index 0000000..5fe3986 --- /dev/null +++ b/corpus/labels/22/POS/sample-002.json @@ -0,0 +1,23 @@ +{ + "spec": "labels/22/POS", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "22", + "text": "POS Bogus message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-22-pos", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Position Report", + "items": [] + }, + "raw": {}, + "remaining": {} + } +} diff --git a/corpus/labels/24/Slash/sample-001.json b/corpus/labels/24/Slash/sample-001.json new file mode 100644 index 0000000..350bb96 --- /dev/null +++ b/corpus/labels/24/Slash/sample-001.json @@ -0,0 +1,59 @@ +{ + "spec": "labels/24/Slash", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "24", + "text": "/241710/1021/04WM/34962/N53.13/E001.33/3374/1056/" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-24-slash", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "04WM" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "34962 feet" + }, + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "53.130 N, 1.330 E" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "10:56:00" + } + ] + }, + "raw": { + "message_timestamp": 1729160460, + "flight_number": "04WM", + "altitude": 34962, + "position": { + "latitude": 53.13, + "longitude": 1.33 + }, + "eta_time": 39360 + }, + "remaining": { + "text": "3374" + } + } +} diff --git a/corpus/labels/24/Slash/sample-002.json b/corpus/labels/24/Slash/sample-002.json new file mode 100644 index 0000000..550c04f --- /dev/null +++ b/corpus/labels/24/Slash/sample-002.json @@ -0,0 +1,23 @@ +{ + "spec": "labels/24/Slash", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "24", + "text": "/ Bogus message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-24-slash", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Position Report", + "items": [] + }, + "raw": {}, + "remaining": {} + } +} diff --git a/corpus/labels/2P/FM3/sample-001.json b/corpus/labels/2P/FM3/sample-001.json new file mode 100644 index 0000000..482ca7f --- /dev/null +++ b/corpus/labels/2P/FM3/sample-001.json @@ -0,0 +1,58 @@ +{ + "spec": "labels/2P/FM3", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "2P", + "text": "FM3 1217,1312,+ 43.77,- 70.18, 39981, 426, 25" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-2p-fm3", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Flight Report", + "items": [ + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "12:17:00" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "13:12:00" + }, + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "43.770 N, 70.180 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "39981 feet" + } + ] + }, + "raw": { + "message_timestamp": 44220, + "eta_time": 47520, + "position": { + "latitude": 43.77, + "longitude": -70.18 + }, + "altitude": 39981 + }, + "remaining": { + "text": " 426, 25" + } + } +} diff --git a/corpus/labels/2P/FM3/sample-002.json b/corpus/labels/2P/FM3/sample-002.json new file mode 100644 index 0000000..efe176c --- /dev/null +++ b/corpus/labels/2P/FM3/sample-002.json @@ -0,0 +1,65 @@ +{ + "spec": "labels/2P/FM3", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "2P", + "text": "M40AEY093CFM3 1216,1454,+057.31,-075.58, 38002, 469, 23" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-2p-fm3", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Flight Report", + "items": [ + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "EY093C" + }, + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "12:16:00" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "14:54:00" + }, + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "57.310 N, 75.580 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "38002 feet" + } + ] + }, + "raw": { + "flight_number": "EY093C", + "message_timestamp": 44160, + "eta_time": 53640, + "position": { + "latitude": 57.31, + "longitude": -75.58 + }, + "altitude": 38002 + }, + "remaining": { + "text": "M40A, 469, 23" + } + } +} diff --git a/corpus/labels/2P/FM3/sample-003.json b/corpus/labels/2P/FM3/sample-003.json new file mode 100644 index 0000000..ac1e562 --- /dev/null +++ b/corpus/labels/2P/FM3/sample-003.json @@ -0,0 +1,58 @@ +{ + "spec": "labels/2P/FM3", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 3", + "input": { + "label": "2P", + "text": "FM3 133818,1607,N 45.206,E 17.726,34030, 440,98" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-2p-fm3", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Flight Report", + "items": [ + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "13:38:18" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "16:07:00" + }, + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "45.206 N, 17.726 E" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "34030 feet" + } + ] + }, + "raw": { + "message_timestamp": 49098, + "eta_time": 58020, + "position": { + "latitude": 45.206, + "longitude": 17.726 + }, + "altitude": 34030 + }, + "remaining": { + "text": " 440,98" + } + } +} diff --git a/corpus/labels/2P/FM3/sample-004.json b/corpus/labels/2P/FM3/sample-004.json new file mode 100644 index 0000000..fdff7b9 --- /dev/null +++ b/corpus/labels/2P/FM3/sample-004.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/2P/FM3", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 4", + "input": { + "label": "2P", + "text": "FM4 Bogus message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-2p-fm3", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Flight Report", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "FM4 Bogus message" + } + } +} diff --git a/corpus/labels/2P/FM4/sample-001.json b/corpus/labels/2P/FM4/sample-001.json new file mode 100644 index 0000000..6e588d0 --- /dev/null +++ b/corpus/labels/2P/FM4/sample-001.json @@ -0,0 +1,86 @@ +{ + "spec": "labels/2P/FM4", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "2P", + "text": "FM4KIAD,OMAA,140256,1448, 39.43,- 75.62,23228,328, 43.5, 72500" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-2p-fm4", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Flight Report", + "items": [ + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "KIAD" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "OMAA" + }, + { + "type": "day", + "code": "MSG_DAY", + "label": "Day of Month", + "value": "14" + }, + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "02:56:00" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "14:48:00" + }, + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "39.430 N, 75.620 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "23228 feet" + }, + { + "type": "heading", + "code": "HDG", + "label": "Heading", + "value": "328" + } + ] + }, + "raw": { + "departure_icao": "KIAD", + "arrival_icao": "OMAA", + "day": 14, + "message_timestamp": 10560, + "eta_time": 53280, + "position": { + "latitude": 39.43, + "longitude": -75.62 + }, + "altitude": 23228, + "heading": 328 + }, + "remaining": { + "text": " 43.5, 72500" + } + } +} diff --git a/corpus/labels/2P/FM4/sample-002.json b/corpus/labels/2P/FM4/sample-002.json new file mode 100644 index 0000000..42d3632 --- /dev/null +++ b/corpus/labels/2P/FM4/sample-002.json @@ -0,0 +1,93 @@ +{ + "spec": "labels/2P/FM4", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "2P", + "text": "M58AEY0801FM4RJAA,OMAA,141234,2105, 38.92, 115.44,34099,296,-105.5, 52800" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-2p-fm4", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Flight Report", + "items": [ + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "EY0801" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "RJAA" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "OMAA" + }, + { + "type": "day", + "code": "MSG_DAY", + "label": "Day of Month", + "value": "14" + }, + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "12:34:00" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "21:05:00" + }, + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "38.920 N, 115.440 E" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "34099 feet" + }, + { + "type": "heading", + "code": "HDG", + "label": "Heading", + "value": "296" + } + ] + }, + "raw": { + "flight_number": "EY0801", + "departure_icao": "RJAA", + "arrival_icao": "OMAA", + "day": 14, + "message_timestamp": 45240, + "eta_time": 75900, + "position": { + "latitude": 38.92, + "longitude": 115.44 + }, + "altitude": 34099, + "heading": 296 + }, + "remaining": { + "text": "M58A,-105.5, 52800" + } + } +} diff --git a/corpus/labels/2P/FM4/sample-003.json b/corpus/labels/2P/FM4/sample-003.json new file mode 100644 index 0000000..e0076e4 --- /dev/null +++ b/corpus/labels/2P/FM4/sample-003.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/2P/FM4", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 3", + "input": { + "label": "2P", + "text": "FM4 Bogus message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-2p-fm4", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Flight Report", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "FM4 Bogus message" + } + } +} diff --git a/corpus/labels/2P/FM5/sample-001.json b/corpus/labels/2P/FM5/sample-001.json new file mode 100644 index 0000000..4d3f0e3 --- /dev/null +++ b/corpus/labels/2P/FM5/sample-001.json @@ -0,0 +1,79 @@ +{ + "spec": "labels/2P/FM5", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "2P", + "text": "FM5 EIDW,OMAA,113522,1540,+45.147, +23.384,35002,116.24,502 ,36900,ETD23N ," + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-2p-fm5", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Flight Report", + "items": [ + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "EIDW" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "OMAA" + }, + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "11:35:22" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "15:40:00" + }, + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "45.147 N, 23.384 E" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "35002 feet" + }, + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "ETD23N" + } + ] + }, + "raw": { + "departure_icao": "EIDW", + "arrival_icao": "OMAA", + "message_timestamp": 41722, + "eta_time": 56400, + "position": { + "latitude": 45.147, + "longitude": 23.384 + }, + "altitude": 35002, + "flight_number": "ETD23N" + }, + "remaining": { + "text": "116.24,502 ,36900," + } + } +} diff --git a/corpus/labels/2P/FM5/sample-002.json b/corpus/labels/2P/FM5/sample-002.json new file mode 100644 index 0000000..734b61d --- /dev/null +++ b/corpus/labels/2P/FM5/sample-002.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/2P/FM5", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "2P", + "text": "FM4 Bogus message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-2p-fm5", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Flight Report", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "FM4 Bogus message" + } + } +} diff --git a/corpus/labels/30/Slash_EA/sample-001.json b/corpus/labels/30/Slash_EA/sample-001.json new file mode 100644 index 0000000..b8b36d1 --- /dev/null +++ b/corpus/labels/30/Slash_EA/sample-001.json @@ -0,0 +1,41 @@ +{ + "spec": "labels/30/Slash_EA", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "30", + "text": "/EA1719/DSKSFO/SK23" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-30-slash-ea", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "ETA Report", + "items": [ + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "17:19:00" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KSFO" + } + ] + }, + "raw": { + "eta_time": 62340, + "arrival_icao": "KSFO" + }, + "remaining": { + "text": "/SK23" + } + } +} diff --git a/corpus/labels/44/ETA/sample-001.json b/corpus/labels/44/ETA/sample-001.json new file mode 100644 index 0000000..25623ad --- /dev/null +++ b/corpus/labels/44/ETA/sample-001.json @@ -0,0 +1,91 @@ +{ + "spec": "labels/44/ETA", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "44", + "text": "00ETA03,N38241W081357,330,KBNA,KBWI,1107,0123,0208,008.1" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-44-eta", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "ETA Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "38.402 N, 81.595 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "33000 feet" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "KBNA" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KBWI" + }, + { + "type": "month", + "code": "MSG_MON", + "label": "Month of Year", + "value": "11" + }, + { + "type": "day", + "code": "MSG_DAY", + "label": "Day of Month", + "value": "7" + }, + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "01:23:00" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "02:08:00" + }, + { + "type": "fuel_remaining", + "code": " FUEL_REM", + "label": "Fuel Remaining", + "value": "8.1" + } + ] + }, + "raw": { + "position": { + "latitude": 38.401666666666664, + "longitude": -81.595 + }, + "altitude": 33000, + "departure_icao": "KBNA", + "arrival_icao": "KBWI", + "month": 11, + "day": 7, + "message_timestamp": 4980, + "eta_time": 7680, + "fuel_remaining": 8.1 + }, + "remaining": {} + } +} diff --git a/corpus/labels/44/ETA/sample-002.json b/corpus/labels/44/ETA/sample-002.json new file mode 100644 index 0000000..d49a064 --- /dev/null +++ b/corpus/labels/44/ETA/sample-002.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/44/ETA", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "44", + "text": "00OFF01 Bogus message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-44-eta", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "ETA Report", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "00OFF01 Bogus message" + } + } +} diff --git a/corpus/labels/44/IN/sample-001.json b/corpus/labels/44/IN/sample-001.json new file mode 100644 index 0000000..6f393fc --- /dev/null +++ b/corpus/labels/44/IN/sample-001.json @@ -0,0 +1,70 @@ +{ + "spec": "labels/44/IN", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "44", + "text": "IN01,N33528W084181,KCLT,KPDK,1106,0045,---.-" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-44-in", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "In Gate Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "33.880 N, 84.302 W" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "KCLT" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KPDK" + }, + { + "type": "month", + "code": "MSG_MON", + "label": "Month of Year", + "value": "11" + }, + { + "type": "day", + "code": "MSG_DAY", + "label": "Day of Month", + "value": "6" + }, + { + "type": "time", + "code": "IN", + "label": "In Gate Time", + "value": "00:45:00" + } + ] + }, + "raw": { + "position": { + "latitude": 33.88, + "longitude": -84.30166666666666 + }, + "departure_icao": "KCLT", + "arrival_icao": "KPDK", + "month": 11, + "day": 6, + "in_time": 2700 + }, + "remaining": {} + } +} diff --git a/corpus/labels/44/IN/sample-002.json b/corpus/labels/44/IN/sample-002.json new file mode 100644 index 0000000..020f2e5 --- /dev/null +++ b/corpus/labels/44/IN/sample-002.json @@ -0,0 +1,77 @@ +{ + "spec": "labels/44/IN", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "44", + "text": "IN02,N38338W121179,KMHR,KPDX,0806,2355,005.1" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-44-in", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "In Gate Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "38.563 N, 121.298 W" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "KMHR" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KPDX" + }, + { + "type": "month", + "code": "MSG_MON", + "label": "Month of Year", + "value": "8" + }, + { + "type": "day", + "code": "MSG_DAY", + "label": "Day of Month", + "value": "6" + }, + { + "type": "time", + "code": "IN", + "label": "In Gate Time", + "value": "23:55:00" + }, + { + "type": "fuel_remaining", + "code": " FUEL_REM", + "label": "Fuel Remaining", + "value": "5.1" + } + ] + }, + "raw": { + "position": { + "latitude": 38.56333333333333, + "longitude": -121.29833333333333 + }, + "departure_icao": "KMHR", + "arrival_icao": "KPDX", + "month": 8, + "day": 6, + "in_time": 86100, + "fuel_remaining": 5.1 + }, + "remaining": {} + } +} diff --git a/corpus/labels/44/IN/sample-003.json b/corpus/labels/44/IN/sample-003.json new file mode 100644 index 0000000..2749d07 --- /dev/null +++ b/corpus/labels/44/IN/sample-003.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/44/IN", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 3", + "input": { + "label": "44", + "text": "00OFF01 Bogus message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-44-in", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "In Gate Report", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "00OFF01 Bogus message" + } + } +} diff --git a/corpus/labels/44/OFF/sample-001.json b/corpus/labels/44/OFF/sample-001.json new file mode 100644 index 0000000..49c9295 --- /dev/null +++ b/corpus/labels/44/OFF/sample-001.json @@ -0,0 +1,84 @@ +{ + "spec": "labels/44/OFF", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "44", + "text": "OFF02,N39247W077226,KFDK,KSNA,1106,2124,0248,011.1" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-44-off", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "Off Runway Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "39.412 N, 77.377 W" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "KFDK" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KSNA" + }, + { + "type": "month", + "code": "MSG_MON", + "label": "Month of Year", + "value": "11" + }, + { + "type": "day", + "code": "MSG_DAY", + "label": "Day of Month", + "value": "6" + }, + { + "type": "time", + "code": "OFF", + "label": "Takeoff Time", + "value": "21:24:00" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "02:48:00" + }, + { + "type": "fuel_remaining", + "code": " FUEL_REM", + "label": "Fuel Remaining", + "value": "11.1" + } + ] + }, + "raw": { + "position": { + "latitude": 39.41166666666667, + "longitude": -77.37666666666667 + }, + "departure_icao": "KFDK", + "arrival_icao": "KSNA", + "month": 11, + "day": 6, + "off_time": 77040, + "eta_time": 10080, + "fuel_remaining": 11.1 + }, + "remaining": {} + } +} diff --git a/corpus/labels/44/OFF/sample-002.json b/corpus/labels/44/OFF/sample-002.json new file mode 100644 index 0000000..199a8a7 --- /dev/null +++ b/corpus/labels/44/OFF/sample-002.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/44/OFF", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "44", + "text": "00OFF01 Bogus message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-44-off", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Off Runway Report", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "00OFF01 Bogus message" + } + } +} diff --git a/corpus/labels/44/ON/sample-001.json b/corpus/labels/44/ON/sample-001.json new file mode 100644 index 0000000..d3e5fbd --- /dev/null +++ b/corpus/labels/44/ON/sample-001.json @@ -0,0 +1,72 @@ +{ + "spec": "labels/44/ON", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "44", + "text": "ON01,N33522W084181,KCLT,KPDK,1106,004023,---.-," + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-44-on", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "On Runway Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "33.870 N, 84.302 W" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "KCLT" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KPDK" + }, + { + "type": "month", + "code": "MSG_MON", + "label": "Month of Year", + "value": "11" + }, + { + "type": "day", + "code": "MSG_DAY", + "label": "Day of Month", + "value": "6" + }, + { + "type": "time", + "code": "ON", + "label": "Landing Time", + "value": "00:40:23" + } + ] + }, + "raw": { + "position": { + "latitude": 33.87, + "longitude": -84.30166666666666 + }, + "departure_icao": "KCLT", + "arrival_icao": "KPDK", + "month": 11, + "day": 6, + "on_time": 2423 + }, + "remaining": { + "text": "" + } + } +} diff --git a/corpus/labels/44/ON/sample-002.json b/corpus/labels/44/ON/sample-002.json new file mode 100644 index 0000000..811d661 --- /dev/null +++ b/corpus/labels/44/ON/sample-002.json @@ -0,0 +1,77 @@ +{ + "spec": "labels/44/ON", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "44", + "text": "ON02,N38333W121178,KRNO,KMHR,0806,2350,005.2" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-44-on", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "On Runway Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "38.555 N, 121.297 W" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "KRNO" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KMHR" + }, + { + "type": "month", + "code": "MSG_MON", + "label": "Month of Year", + "value": "8" + }, + { + "type": "day", + "code": "MSG_DAY", + "label": "Day of Month", + "value": "6" + }, + { + "type": "time", + "code": "ON", + "label": "Landing Time", + "value": "23:50:00" + }, + { + "type": "fuel_remaining", + "code": " FUEL_REM", + "label": "Fuel Remaining", + "value": "5.2" + } + ] + }, + "raw": { + "position": { + "latitude": 38.555, + "longitude": -121.29666666666667 + }, + "departure_icao": "KRNO", + "arrival_icao": "KMHR", + "month": 8, + "day": 6, + "on_time": 85800, + "fuel_remaining": 5.2 + }, + "remaining": {} + } +} diff --git a/corpus/labels/44/ON/sample-003.json b/corpus/labels/44/ON/sample-003.json new file mode 100644 index 0000000..54af443 --- /dev/null +++ b/corpus/labels/44/ON/sample-003.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/44/ON", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 3", + "input": { + "label": "44", + "text": "00OFF01 Bogus message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-44-on", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "On Runway Report", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "00OFF01 Bogus message" + } + } +} diff --git a/corpus/labels/44/ON/sample-004.json b/corpus/labels/44/ON/sample-004.json new file mode 100644 index 0000000..0491c8d --- /dev/null +++ b/corpus/labels/44/ON/sample-004.json @@ -0,0 +1,72 @@ +{ + "spec": "labels/44/ON", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 4", + "input": { + "label": "44", + "text": "ON01,N33522W084181,KCLT,KPDK,1106,004023,---.-," + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-44-on", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "On Runway Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "33.870 N, 84.302 W" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "KCLT" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KPDK" + }, + { + "type": "month", + "code": "MSG_MON", + "label": "Month of Year", + "value": "11" + }, + { + "type": "day", + "code": "MSG_DAY", + "label": "Day of Month", + "value": "6" + }, + { + "type": "time", + "code": "ON", + "label": "Landing Time", + "value": "00:40:23" + } + ] + }, + "raw": { + "position": { + "latitude": 33.87, + "longitude": -84.30166666666666 + }, + "departure_icao": "KCLT", + "arrival_icao": "KPDK", + "month": 11, + "day": 6, + "on_time": 2423 + }, + "remaining": { + "text": "" + } + } +} diff --git a/corpus/labels/44/POS/sample-001.json b/corpus/labels/44/POS/sample-001.json new file mode 100644 index 0000000..ac4a867 --- /dev/null +++ b/corpus/labels/44/POS/sample-001.json @@ -0,0 +1,85 @@ +{ + "spec": "labels/44/POS", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "44", + "text": "POS02,N38338W121179,GRD,KMHR,KPDX,0807,0003,0112,005.1" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-44-pos", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "38.563 N, 121.298 W" + }, + { + "type": "month", + "code": "MSG_MON", + "label": "Month of Year", + "value": "8" + }, + { + "type": "day", + "code": "MSG_DAY", + "label": "Day of Month", + "value": "7" + }, + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "00:03:00" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "01:12:00" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "KMHR" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KPDX" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "0 feet" + } + ] + }, + "raw": { + "position": { + "latitude": 38.56333333333333, + "longitude": -121.29833333333333 + }, + "month": 8, + "day": 7, + "message_timestamp": 180, + "eta_time": 4320, + "fuel_in_tons": 5.1, + "departure_icao": "KMHR", + "arrival_icao": "KPDX", + "altitude": 0 + }, + "remaining": {} + } +} diff --git a/corpus/labels/44/POS/sample-002.json b/corpus/labels/44/POS/sample-002.json new file mode 100644 index 0000000..daacc50 --- /dev/null +++ b/corpus/labels/44/POS/sample-002.json @@ -0,0 +1,85 @@ +{ + "spec": "labels/44/POS", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "44", + "text": "POS02,N38171W077507,319,KJFK,KUZA,0926,0245,0327,004.6" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-44-pos", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "38.285 N, 77.845 W" + }, + { + "type": "month", + "code": "MSG_MON", + "label": "Month of Year", + "value": "9" + }, + { + "type": "day", + "code": "MSG_DAY", + "label": "Day of Month", + "value": "26" + }, + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "02:45:00" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "03:27:00" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "KJFK" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KUZA" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "31900 feet" + } + ] + }, + "raw": { + "position": { + "latitude": 38.285, + "longitude": -77.845 + }, + "month": 9, + "day": 26, + "message_timestamp": 9900, + "eta_time": 12420, + "fuel_in_tons": 4.6, + "departure_icao": "KJFK", + "arrival_icao": "KUZA", + "altitude": 31900 + }, + "remaining": {} + } +} diff --git a/corpus/labels/44/Slash/sample-001.json b/corpus/labels/44/Slash/sample-001.json new file mode 100644 index 0000000..2ae0699 --- /dev/null +++ b/corpus/labels/44/Slash/sample-001.json @@ -0,0 +1,58 @@ +{ + "spec": "labels/44/Slash", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "44", + "text": " /FB 0160/AD KORH/N 38.655,W 75.325,JBU2834,INA03,KORH,2043" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-44-slash", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Flight Briefing", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "38.655 N, 75.325 W" + }, + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "JBU2834" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KORH" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "20:43:00" + } + ] + }, + "raw": { + "position": { + "latitude": 38.655, + "longitude": -75.325 + }, + "flight_number": "JBU2834", + "arrival_icao": "KORH", + "eta_time": 74580 + }, + "remaining": { + "text": " /FB 0160,INA03" + } + } +} diff --git a/corpus/labels/44/Slash/sample-002.json b/corpus/labels/44/Slash/sample-002.json new file mode 100644 index 0000000..26a6600 --- /dev/null +++ b/corpus/labels/44/Slash/sample-002.json @@ -0,0 +1,86 @@ +{ + "spec": "labels/44/Slash", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "44", + "text": " /FB ----/AD KTPA/N 27.971,W 82.558,JBU91,FLS03,KTPA,53988,53988,----,1943,1943,1,1L,VIS1L,0,2,0,," + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-44-slash", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Flight Briefing", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "27.971 N, 82.558 W" + }, + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "JBU91" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KTPA" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "YYYY-MM-03T06:38:08" + }, + { + "type": "fuel_remaining", + "code": " FUEL_REM", + "label": "Fuel Remaining", + "value": "1943" + }, + { + "type": "runway", + "code": "ARWY", + "label": "Arrival Runway", + "value": "1L" + }, + { + "type": "procedure", + "code": "proc", + "label": "Arrival Procedure", + "value": "VIS1L" + } + ] + }, + "raw": { + "position": { + "latitude": 27.971, + "longitude": -82.558 + }, + "flight_number": "JBU91", + "arrival_icao": "KTPA", + "eta_time": 196688, + "fuel_remaining": 1943, + "arrival_runway": "1L", + "procedures": [ + { + "type": "arrival", + "route": { + "name": "VIS1L" + } + } + ] + }, + "remaining": { + "text": " /FB ----,FLS03,53988,----,1943,1,0,2,0,," + } + } +} diff --git a/corpus/labels/44/Slash/sample-003.json b/corpus/labels/44/Slash/sample-003.json new file mode 100644 index 0000000..9364d62 --- /dev/null +++ b/corpus/labels/44/Slash/sample-003.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/44/Slash", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 3", + "input": { + "label": "44", + "text": "00OFF01 Bogus message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-44-slash", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Flight Briefing", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "00OFF01 Bogus message" + } + } +} diff --git a/corpus/labels/4A/01/sample-001.json b/corpus/labels/4A/01/sample-001.json new file mode 100644 index 0000000..604c00f --- /dev/null +++ b/corpus/labels/4A/01/sample-001.json @@ -0,0 +1,79 @@ +{ + "spec": "labels/4A/01", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "4A", + "text": "01DCAP VIR41R/190203EGLLKSFO\r\n+ 1418158.0+ 24.8" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-4a-01", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Latest New Format", + "items": [ + { + "type": "state_change", + "code": "STATE_CHANGE", + "label": "State Change", + "value": "Descent -> Approach" + }, + { + "type": "callsign", + "code": "CALLSIGN", + "label": "Callsign", + "value": "VIR41R" + }, + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "19:02:03" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "EGLL" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KSFO" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "1418 feet" + }, + { + "type": "outside_air_temperature", + "code": "OATEMP", + "label": "Outside Air Temperature (C)", + "value": "24.8 degrees" + } + ] + }, + "raw": { + "state_change": { + "from": "DC", + "to": "AP" + }, + "callsign": "VIR41R", + "message_timestamp": 68523, + "departure_icao": "EGLL", + "arrival_icao": "KSFO", + "altitude": 1418, + "outside_air_temperature": 24.8 + }, + "remaining": { + "text": "158.0" + } + } +} diff --git a/corpus/labels/4A/DIS/sample-001.json b/corpus/labels/4A/DIS/sample-001.json new file mode 100644 index 0000000..4339698 --- /dev/null +++ b/corpus/labels/4A/DIS/sample-001.json @@ -0,0 +1,46 @@ +{ + "spec": "labels/4A/DIS", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "4A", + "text": "DIS01,190009,WEN3140,@HOLD CNX" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-4a-dis", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "Latest New Format", + "items": [ + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "00:09:00" + }, + { + "type": "callsign", + "code": "CALLSIGN", + "label": "Callsign", + "value": "WEN3140" + }, + { + "type": "text", + "code": "TEXT", + "label": "Text Message", + "value": "@HOLD CNX" + } + ] + }, + "raw": { + "message_timestamp": 540, + "callsign": "WEN3140", + "text": "@HOLD CNX" + }, + "remaining": {} + } +} diff --git a/corpus/labels/4A/DOOR/sample-001.json b/corpus/labels/4A/DOOR/sample-001.json new file mode 100644 index 0000000..7c56aab --- /dev/null +++ b/corpus/labels/4A/DOOR/sample-001.json @@ -0,0 +1,42 @@ +{ + "spec": "labels/4A/DOOR", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "4A", + "text": "DOOR/FWDENTRY CLSD 1440" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-4a-door", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "Latest New Format", + "items": [ + { + "type": "door_event", + "code": "DOOR", + "label": "Door Event", + "value": "FWDENTRY CLSD" + }, + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "14:40:00" + } + ] + }, + "raw": { + "door_event": { + "door": "FWDENTRY", + "state": "CLSD" + }, + "message_timestamp": 52800 + }, + "remaining": {} + } +} diff --git a/corpus/labels/4A/Slash_01/sample-001.json b/corpus/labels/4A/Slash_01/sample-001.json new file mode 100644 index 0000000..f2ccb4d --- /dev/null +++ b/corpus/labels/4A/Slash_01/sample-001.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/4A/Slash_01", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "4A", + "text": "/01-C" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-4a-slash-01", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Latest New Format", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "C" + } + } +} diff --git a/corpus/labels/4A/sample-001.json b/corpus/labels/4A/sample-001.json new file mode 100644 index 0000000..e04d233 --- /dev/null +++ b/corpus/labels/4A/sample-001.json @@ -0,0 +1,69 @@ +{ + "spec": "labels/4A", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "4A", + "text": "N22456E077014OSE35 ,192027370VEX36 ,192316,M46,275043309,85220111" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-4a", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Latest New Format", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "22.456 N, 77.014 E" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "37000 feet" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "OSE35@19:20:27 > VEX36@19:23:16" + }, + { + "type": "outside_air_temperature", + "code": "OATEMP", + "label": "Outside Air Temperature (C)", + "value": "-46 degrees" + } + ] + }, + "raw": { + "position": { + "latitude": 22.456, + "longitude": 77.014 + }, + "altitude": 37000, + "route": { + "waypoints": [ + { + "name": "OSE35", + "time": 69627 + }, + { + "name": "VEX36", + "time": 69796 + } + ] + }, + "outside_air_temperature": -46 + }, + "remaining": { + "text": "275043309,85220111" + } + } +} diff --git a/corpus/labels/4A/sample-002.json b/corpus/labels/4A/sample-002.json new file mode 100644 index 0000000..9880883 --- /dev/null +++ b/corpus/labels/4A/sample-002.json @@ -0,0 +1,62 @@ +{ + "spec": "labels/4A", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "4A", + "text": "063200,1910,.N343FR,FFT2028,KSLC,KORD,1,0632,RT0,LT0," + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-4a", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Latest New Format", + "items": [ + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "06:32:00" + }, + { + "type": "tail", + "code": "TAIL", + "label": "Tail", + "value": "N343FR" + }, + { + "type": "callsign", + "code": "CALLSIGN", + "label": "Callsign", + "value": "FFT2028" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "KSLC" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KORD" + } + ] + }, + "raw": { + "message_timestamp": 23520, + "tail": "N343FR", + "callsign": "FFT2028", + "departure_icao": "KSLC", + "arrival_icao": "KORD" + }, + "remaining": { + "text": "RT0,LT0," + } + } +} diff --git a/corpus/labels/4A/sample-003.json b/corpus/labels/4A/sample-003.json new file mode 100644 index 0000000..f0260d4 --- /dev/null +++ b/corpus/labels/4A/sample-003.json @@ -0,0 +1,55 @@ +{ + "spec": "labels/4A", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 3", + "input": { + "label": "4A", + "text": "101606,1910,.N317FR,,KMDW,----,1,1016,RT0,LT1," + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-4a", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Latest New Format", + "items": [ + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "10:16:06" + }, + { + "type": "tail", + "code": "TAIL", + "label": "Tail", + "value": "N317FR" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "KMDW" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "----" + } + ] + }, + "raw": { + "message_timestamp": 36966, + "tail": "N317FR", + "departure_icao": "KMDW", + "arrival_icao": "----" + }, + "remaining": { + "text": "RT0,LT1," + } + } +} diff --git a/corpus/labels/4A/sample-004.json b/corpus/labels/4A/sample-004.json new file mode 100644 index 0000000..2b365b8 --- /dev/null +++ b/corpus/labels/4A/sample-004.json @@ -0,0 +1,69 @@ +{ + "spec": "labels/4A", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 4", + "input": { + "label": "4A", + "text": "N45129W093113MSP/07 ,204436123VECTORS,,P04,268044858,46904221" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-4a", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Latest New Format", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "45.129 N, 93.113 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "12300 feet" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "MSP/07@20:44:36 > VECTORS" + }, + { + "type": "outside_air_temperature", + "code": "OATEMP", + "label": "Outside Air Temperature (C)", + "value": "4 degrees" + } + ] + }, + "raw": { + "position": { + "latitude": 45.129, + "longitude": -93.113 + }, + "altitude": 12300, + "route": { + "waypoints": [ + { + "name": "MSP/07", + "time": 74676 + }, + { + "name": "VECTORS", + "time": 0 + } + ] + }, + "outside_air_temperature": 4 + }, + "remaining": { + "text": "268044858,46904221" + } + } +} diff --git a/corpus/labels/4A/sample-005.json b/corpus/labels/4A/sample-005.json new file mode 100644 index 0000000..69c3825 --- /dev/null +++ b/corpus/labels/4A/sample-005.json @@ -0,0 +1,58 @@ +{ + "spec": "labels/4A", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 5", + "input": { + "label": "4A", + "text": "124442,1320, 138,33467,N 41.093,W 72.677" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-4a", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Latest New Format", + "items": [ + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "12:44:42" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "13:20:00" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "33467 feet" + }, + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "41.093 N, 72.677 W" + } + ] + }, + "raw": { + "message_timestamp": 45882, + "eta_time": 48000, + "altitude": 33467, + "position": { + "latitude": 41.093, + "longitude": -72.677 + } + }, + "remaining": { + "text": " 138" + } + } +} diff --git a/corpus/labels/4A/sample-006.json b/corpus/labels/4A/sample-006.json new file mode 100644 index 0000000..a556d95 --- /dev/null +++ b/corpus/labels/4A/sample-006.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/4A", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 6", + "input": { + "label": "4A", + "text": "DIS01,182103,WEN3100,WRONG CREW HAHAHA" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-4a", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Latest New Format", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "DIS01,182103,WEN3100,WRONG CREW HAHAHA" + } + } +} diff --git a/corpus/labels/4N/sample-001.json b/corpus/labels/4N/sample-001.json new file mode 100644 index 0000000..5fa4e2f --- /dev/null +++ b/corpus/labels/4N/sample-001.json @@ -0,0 +1,70 @@ +{ + "spec": "labels/4N", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "4N", + "text": "285,C,,10/12,,,,,NRT,ANC,ANC,07R/,33/,0,0,,,,,,0,0,0,0,1,0,,0,0,709.8,048.7,758.5,75F3" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-4n", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Airline Defined", + "items": [ + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "NRT" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "ANC" + }, + { + "type": "icao", + "code": "ALT_DST", + "label": "Alternate Destination", + "value": "ANC" + }, + { + "type": "runway", + "code": "ARWY", + "label": "Arrival Runway", + "value": "07R" + }, + { + "type": "runway", + "code": "ALT_ARWY", + "label": "Alternate Runway", + "value": "33" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0x75f3" + } + ] + }, + "raw": { + "date": "10/12", + "departure_icao": "NRT", + "arrival_icao": "ANC", + "alternate_icao": "ANC", + "arrival_runway": "07R", + "alternate_runway": "33", + "checksum": 30195 + }, + "remaining": { + "text": "C,0,0,0,0,0,0,1,0,0,0,709.8,048.7,758.5" + } + } +} diff --git a/corpus/labels/4N/sample-002.json b/corpus/labels/4N/sample-002.json new file mode 100644 index 0000000..806eb59 --- /dev/null +++ b/corpus/labels/4N/sample-002.json @@ -0,0 +1,70 @@ +{ + "spec": "labels/4N", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "4N", + "text": "285,C,,10/12,,,,,NRT,ANC,ANC,07R/,33/,0,0,,,,,,0,0,0,0,1,0,,0,0,709.8,048.7,758.5,75F3" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-4n", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Airline Defined", + "items": [ + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "NRT" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "ANC" + }, + { + "type": "icao", + "code": "ALT_DST", + "label": "Alternate Destination", + "value": "ANC" + }, + { + "type": "runway", + "code": "ARWY", + "label": "Arrival Runway", + "value": "07R" + }, + { + "type": "runway", + "code": "ALT_ARWY", + "label": "Alternate Runway", + "value": "33" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0x75f3" + } + ] + }, + "raw": { + "date": "10/12", + "departure_icao": "NRT", + "arrival_icao": "ANC", + "alternate_icao": "ANC", + "arrival_runway": "07R", + "alternate_runway": "33", + "checksum": 30195 + }, + "remaining": { + "text": "C,0,0,0,0,0,0,1,0,0,0,709.8,048.7,758.5" + } + } +} diff --git a/corpus/labels/4N/sample-003.json b/corpus/labels/4N/sample-003.json new file mode 100644 index 0000000..93f23b4 --- /dev/null +++ b/corpus/labels/4N/sample-003.json @@ -0,0 +1,70 @@ +{ + "spec": "labels/4N", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 3", + "input": { + "label": "4N", + "text": "285,C,,10/12,,,,,NRT,ANC,ANC,07R/,33/,0,0,,,,,,0,0,0,0,1,0,,0,0,709.8,048.7,758.5,75F3" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-4n", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Airline Defined", + "items": [ + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "NRT" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "ANC" + }, + { + "type": "icao", + "code": "ALT_DST", + "label": "Alternate Destination", + "value": "ANC" + }, + { + "type": "runway", + "code": "ARWY", + "label": "Arrival Runway", + "value": "07R" + }, + { + "type": "runway", + "code": "ALT_ARWY", + "label": "Alternate Runway", + "value": "33" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0x75f3" + } + ] + }, + "raw": { + "date": "10/12", + "departure_icao": "NRT", + "arrival_icao": "ANC", + "alternate_icao": "ANC", + "arrival_runway": "07R", + "alternate_runway": "33", + "checksum": 30195 + }, + "remaining": { + "text": "C,0,0,0,0,0,0,1,0,0,0,709.8,048.7,758.5" + } + } +} diff --git a/corpus/labels/4N/sample-004.json b/corpus/labels/4N/sample-004.json new file mode 100644 index 0000000..b113d03 --- /dev/null +++ b/corpus/labels/4N/sample-004.json @@ -0,0 +1,59 @@ +{ + "spec": "labels/4N", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 4", + "input": { + "label": "4N", + "text": "22024N MCI JFK1\r\n0013 0072 N040586 W074421 230" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-4n", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Airline Defined", + "items": [ + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "MCI" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "JFK" + }, + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "40.977 N, 74.702 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "23000 feet" + } + ] + }, + "raw": { + "day": "22", + "departure_icao": "MCI", + "arrival_icao": "JFK", + "position": { + "latitude": 40.97666666666667, + "longitude": -74.70166666666667 + }, + "altitude": 23000 + }, + "remaining": { + "text": "02 0013 0072" + } + } +} diff --git a/corpus/labels/4N/sample-005.json b/corpus/labels/4N/sample-005.json new file mode 100644 index 0000000..754c662 --- /dev/null +++ b/corpus/labels/4N/sample-005.json @@ -0,0 +1,87 @@ +{ + "spec": "labels/4N", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 5", + "input": { + "label": "4N", + "text": "285,B,69005074-507,10/12,+36.081,-094.810,35014,002.3,ELP,SDF,SDF,17R/,17L/,0,0,,,,,,0,0,0,0,1,,,,,247.0,014.2,261.2,421A" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-4n", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Airline Defined", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "36.081 N, 94.810 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "35014 feet" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "ELP" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "SDF" + }, + { + "type": "icao", + "code": "ALT_DST", + "label": "Alternate Destination", + "value": "SDF" + }, + { + "type": "runway", + "code": "ARWY", + "label": "Arrival Runway", + "value": "17R" + }, + { + "type": "runway", + "code": "ALT_ARWY", + "label": "Alternate Runway", + "value": "17L" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0x421a" + } + ] + }, + "raw": { + "date": "10/12", + "position": { + "latitude": 36.081, + "longitude": -94.81 + }, + "altitude": 35014, + "departure_icao": "ELP", + "arrival_icao": "SDF", + "alternate_icao": "SDF", + "arrival_runway": "17R", + "alternate_runway": "17L", + "checksum": 16922 + }, + "remaining": { + "text": "B,69005074-507,002.3,0,0,0,0,0,0,1,247.0,014.2,261.2" + } + } +} diff --git a/corpus/labels/4N/sample-006.json b/corpus/labels/4N/sample-006.json new file mode 100644 index 0000000..ae269b2 --- /dev/null +++ b/corpus/labels/4N/sample-006.json @@ -0,0 +1,63 @@ +{ + "spec": "labels/4N", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 6", + "input": { + "label": "4N", + "text": "285,C,,09/24,,,,,EWR,PHL,PHL,09R/,/,0,0,,,,,,1,0,0,0,1,0,,0,0,198.5,014.5,213.0,9BCD" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-4n", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Airline Defined", + "items": [ + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "EWR" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "PHL" + }, + { + "type": "icao", + "code": "ALT_DST", + "label": "Alternate Destination", + "value": "PHL" + }, + { + "type": "runway", + "code": "ARWY", + "label": "Arrival Runway", + "value": "09R" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0x9bcd" + } + ] + }, + "raw": { + "date": "09/24", + "departure_icao": "EWR", + "arrival_icao": "PHL", + "alternate_icao": "PHL", + "arrival_runway": "09R", + "checksum": 39885 + }, + "remaining": { + "text": "C,0,0,1,0,0,0,1,0,0,0,198.5,014.5,213.0" + } + } +} diff --git a/corpus/labels/4N/sample-007.json b/corpus/labels/4N/sample-007.json new file mode 100644 index 0000000..c50fcb3 --- /dev/null +++ b/corpus/labels/4N/sample-007.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/4N", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 7", + "input": { + "label": "4N", + "text": "4N Bogus message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-4n", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Airline Defined", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "4N Bogus message" + } + } +} diff --git a/corpus/labels/4T/AGFSR/sample-001.json b/corpus/labels/4T/AGFSR/sample-001.json new file mode 100644 index 0000000..84b99fc --- /dev/null +++ b/corpus/labels/4T/AGFSR/sample-001.json @@ -0,0 +1,86 @@ +{ + "spec": "labels/4T/AGFSR", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "4T", + "text": "AGFSR AC0620/07/08/YYZYHZ/0340Z/453/4435.1N07143.4W/350/ /0063/0035/ /281065/----/ /512/0240/0253/----/----" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-4t-agfsr", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "AC0620" + }, + { + "type": "day", + "code": "DEP_DAY", + "label": "Departure Day", + "value": "7" + }, + { + "type": "day", + "code": "ARR_DAY", + "label": "Arrival Day", + "value": "8" + }, + { + "type": "iata", + "code": "ORG", + "label": "Origin", + "value": "YYZ" + }, + { + "type": "iata", + "code": "DST", + "label": "Destination", + "value": "YHZ" + }, + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "03:40:00" + }, + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "44.585 N, 70.277 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "35000 feet" + } + ] + }, + "raw": { + "flight_number": "AC0620", + "departure_day": 7, + "arrival_day": 8, + "departure_iata": "YYZ", + "arrival_iata": "YHZ", + "message_timestamp": 13200, + "position": { + "latitude": 44.585, + "longitude": -70.27666666666667 + }, + "altitude": 35000 + }, + "remaining": { + "text": "453/ /0063/0035/ /281065/----/ /512/0240/0253/----/----" + } + } +} diff --git a/corpus/labels/4T/AGFSR/sample-002.json b/corpus/labels/4T/AGFSR/sample-002.json new file mode 100644 index 0000000..b77bbed --- /dev/null +++ b/corpus/labels/4T/AGFSR/sample-002.json @@ -0,0 +1,86 @@ +{ + "spec": "labels/4T/AGFSR", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "4T", + "text": "AGFSR AC0096/07/08/YULGRU/0354Z/833/4417.8N07232.6W/254/CLIMB /0565/0033/-33/275049/0289/144/453/0319/0339/****/****" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-4t-agfsr", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "AC0096" + }, + { + "type": "day", + "code": "DEP_DAY", + "label": "Departure Day", + "value": "7" + }, + { + "type": "day", + "code": "ARR_DAY", + "label": "Arrival Day", + "value": "8" + }, + { + "type": "iata", + "code": "ORG", + "label": "Origin", + "value": "YUL" + }, + { + "type": "iata", + "code": "DST", + "label": "Destination", + "value": "GRU" + }, + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "03:54:00" + }, + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "44.297 N, 71.457 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "25400 feet" + } + ] + }, + "raw": { + "flight_number": "AC0096", + "departure_day": 7, + "arrival_day": 8, + "departure_iata": "YUL", + "arrival_iata": "GRU", + "message_timestamp": 14040, + "position": { + "latitude": 44.29666666666667, + "longitude": -71.45666666666666 + }, + "altitude": 25400 + }, + "remaining": { + "text": "833/CLIMB /0565/0033/-33/275049/0289/144/453/0319/0339/****/****" + } + } +} diff --git a/corpus/labels/4T/AGFSR/sample-003.json b/corpus/labels/4T/AGFSR/sample-003.json new file mode 100644 index 0000000..91b40f5 --- /dev/null +++ b/corpus/labels/4T/AGFSR/sample-003.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/4T/AGFSR", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 3", + "input": { + "label": "4T", + "text": "POS/ Bogus message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-4t-agfsr", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Position Report", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "POS/ Bogus message" + } + } +} diff --git a/corpus/labels/4T/ETA/sample-001.json b/corpus/labels/4T/ETA/sample-001.json new file mode 100644 index 0000000..f9b4f45 --- /dev/null +++ b/corpus/labels/4T/ETA/sample-001.json @@ -0,0 +1,60 @@ +{ + "spec": "labels/4T/ETA", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "4T", + "text": "ETA AC7221/13/14 YYZ 0902Z" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-4t-eta", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "ETA Report", + "items": [ + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "AC7221" + }, + { + "type": "day", + "code": "DEP_DAY", + "label": "Departure Day", + "value": "13" + }, + { + "type": "day", + "code": "ARR_DAY", + "label": "Arrival Day", + "value": "14" + }, + { + "type": "iata", + "code": "DST", + "label": "Destination", + "value": "YYZ" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "09:02:00" + } + ] + }, + "raw": { + "flight_number": "AC7221", + "departure_day": 13, + "arrival_day": 14, + "arrival_iata": "YYZ", + "eta_time": 32520 + }, + "remaining": {} + } +} diff --git a/corpus/labels/4T/ETA/sample-002.json b/corpus/labels/4T/ETA/sample-002.json new file mode 100644 index 0000000..4f51c22 --- /dev/null +++ b/corpus/labels/4T/ETA/sample-002.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/4T/ETA", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "4T", + "text": "ETA Bogus message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-4t-eta", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "ETA Report", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "ETA Bogus message" + } + } +} diff --git a/corpus/labels/58/sample-001.json b/corpus/labels/58/sample-001.json new file mode 100644 index 0000000..a3b0463 --- /dev/null +++ b/corpus/labels/58/sample-001.json @@ -0,0 +1,65 @@ +{ + "spec": "labels/58", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "58", + "text": "OG0704/06/230942/N39.214/W76.106/22683/N/" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-58", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "OG0704" + }, + { + "type": "day", + "code": "MSG_DAY", + "label": "Day of Month", + "value": "6" + }, + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "23:09:42" + }, + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "39.214 N, 76.106 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "22683 feet" + } + ] + }, + "raw": { + "flight_number": "OG0704", + "day": 6, + "message_timestamp": 83382, + "position": { + "latitude": 39.214, + "longitude": -76.106 + }, + "altitude": 22683 + }, + "remaining": { + "text": "N/" + } + } +} diff --git a/corpus/labels/58/sample-002.json b/corpus/labels/58/sample-002.json new file mode 100644 index 0000000..86038c4 --- /dev/null +++ b/corpus/labels/58/sample-002.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/58", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "58", + "text": "Bogus/message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-58", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Position Report", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "Bogus/message" + } + } +} diff --git a/corpus/labels/5Z/Slash/sample-001.json b/corpus/labels/5Z/Slash/sample-001.json new file mode 100644 index 0000000..1c8c69d --- /dev/null +++ b/corpus/labels/5Z/Slash/sample-001.json @@ -0,0 +1,32 @@ +{ + "spec": "labels/5Z/Slash", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "5Z", + "text": "/TXT\r\nDID U GET THE TIMES" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-5z-slash", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "Airline Designated Downlink", + "items": [ + { + "type": "text", + "code": "TEXT", + "label": "Text Message", + "value": "DID U GET THE TIMES" + } + ] + }, + "raw": { + "text": "DID U GET THE TIMES" + }, + "remaining": {} + } +} diff --git a/corpus/labels/5Z/Slash/sample-002.json b/corpus/labels/5Z/Slash/sample-002.json new file mode 100644 index 0000000..06c5a9a --- /dev/null +++ b/corpus/labels/5Z/Slash/sample-002.json @@ -0,0 +1,63 @@ +{ + "spec": "labels/5Z/Slash", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "5Z", + "text": "/B3 ATLIAD 14 R1C G1273" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-5z-slash", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Airline Designated Downlink", + "items": [ + { + "type": "airline", + "code": "AIRLINE", + "label": "Airline", + "value": "United Airlines" + }, + { + "type": "message_type", + "code": "MSG_TYPE", + "label": "Message Type", + "value": "Request Departure Clearance (B3)" + }, + { + "type": "iata", + "code": "ORG", + "label": "Origin", + "value": "ATL" + }, + { + "type": "iata", + "code": "DST", + "label": "Destination", + "value": "IAD" + }, + { + "type": "runway", + "code": "ARWY", + "label": "Arrival Runway", + "value": "1C" + } + ] + }, + "raw": { + "airline": "United Airlines", + "message_type": "B3", + "departure_iata": "ATL", + "arrival_iata": "IAD", + "day": 14, + "arrival_runway": "1C" + }, + "remaining": { + "text": "G1273" + } + } +} diff --git a/corpus/labels/5Z/Slash/sample-003.json b/corpus/labels/5Z/Slash/sample-003.json new file mode 100644 index 0000000..03f6a72 --- /dev/null +++ b/corpus/labels/5Z/Slash/sample-003.json @@ -0,0 +1,61 @@ +{ + "spec": "labels/5Z/Slash", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 3", + "input": { + "label": "5Z", + "text": "/B3 DCAORD 14 R27C" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-5z-slash", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "Airline Designated Downlink", + "items": [ + { + "type": "airline", + "code": "AIRLINE", + "label": "Airline", + "value": "United Airlines" + }, + { + "type": "message_type", + "code": "MSG_TYPE", + "label": "Message Type", + "value": "Request Departure Clearance (B3)" + }, + { + "type": "iata", + "code": "ORG", + "label": "Origin", + "value": "DCA" + }, + { + "type": "iata", + "code": "DST", + "label": "Destination", + "value": "ORD" + }, + { + "type": "runway", + "code": "ARWY", + "label": "Arrival Runway", + "value": "27C" + } + ] + }, + "raw": { + "airline": "United Airlines", + "message_type": "B3", + "departure_iata": "DCA", + "arrival_iata": "ORD", + "day": 14, + "arrival_runway": "27C" + }, + "remaining": {} + } +} diff --git a/corpus/labels/5Z/Slash/sample-004.json b/corpus/labels/5Z/Slash/sample-004.json new file mode 100644 index 0000000..2ca1bde --- /dev/null +++ b/corpus/labels/5Z/Slash/sample-004.json @@ -0,0 +1,70 @@ +{ + "spec": "labels/5Z/Slash", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 4", + "input": { + "label": "5Z", + "text": "/B3 TO DATA REQ / KIAH KBOS 14 152532 R4R /---- BOPT/OFF C0.000/1 LNO G1600" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-5z-slash", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Airline Designated Downlink", + "items": [ + { + "type": "airline", + "code": "AIRLINE", + "label": "Airline", + "value": "United Airlines" + }, + { + "type": "message_type", + "code": "MSG_TYPE", + "label": "Message Type", + "value": "Request Departure Clearance (B3)" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "KIAH" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KBOS" + }, + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "15:25:32" + }, + { + "type": "runway", + "code": "ARWY", + "label": "Arrival Runway", + "value": "4R" + } + ] + }, + "raw": { + "airline": "United Airlines", + "message_type": "B3", + "departure_icao": "KIAH", + "arrival_icao": "KBOS", + "day": 14, + "message_timestamp": 55532, + "arrival_runway": "4R" + }, + "remaining": { + "text": "---- BOPT/OFF C0.000/1 LNO G1600" + } + } +} diff --git a/corpus/labels/5Z/Slash/sample-005.json b/corpus/labels/5Z/Slash/sample-005.json new file mode 100644 index 0000000..787c467 --- /dev/null +++ b/corpus/labels/5Z/Slash/sample-005.json @@ -0,0 +1,70 @@ +{ + "spec": "labels/5Z/Slash", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 5", + "input": { + "label": "5Z", + "text": "/ET EXP TIME / KEWR KBNA 20 122559/EON 1336 AUTO" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-5z-slash", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Airline Designated Downlink", + "items": [ + { + "type": "airline", + "code": "AIRLINE", + "label": "Airline", + "value": "United Airlines" + }, + { + "type": "message_type", + "code": "MSG_TYPE", + "label": "Message Type", + "value": "Expected Time (ET)" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "KEWR" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KBNA" + }, + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "12:25:59" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "13:36:00" + } + ] + }, + "raw": { + "airline": "United Airlines", + "message_type": "ET", + "departure_icao": "KEWR", + "arrival_icao": "KBNA", + "day": 20, + "message_timestamp": 44759, + "eta_time": 48960 + }, + "remaining": { + "text": "AUTO" + } + } +} diff --git a/corpus/labels/5Z/Slash/sample-006.json b/corpus/labels/5Z/Slash/sample-006.json new file mode 100644 index 0000000..a4d3e0a --- /dev/null +++ b/corpus/labels/5Z/Slash/sample-006.json @@ -0,0 +1,53 @@ +{ + "spec": "labels/5Z/Slash", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 6", + "input": { + "label": "5Z", + "text": "/C3 IADDFW" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-5z-slash", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "Airline Designated Downlink", + "items": [ + { + "type": "airline", + "code": "AIRLINE", + "label": "Airline", + "value": "United Airlines" + }, + { + "type": "message_type", + "code": "MSG_TYPE", + "label": "Message Type", + "value": "Off Message (C3)" + }, + { + "type": "iata", + "code": "ORG", + "label": "Origin", + "value": "IAD" + }, + { + "type": "iata", + "code": "DST", + "label": "Destination", + "value": "DFW" + } + ] + }, + "raw": { + "airline": "United Airlines", + "message_type": "C3", + "departure_iata": "IAD", + "arrival_iata": "DFW" + }, + "remaining": {} + } +} diff --git a/corpus/labels/5Z/Slash/sample-007.json b/corpus/labels/5Z/Slash/sample-007.json new file mode 100644 index 0000000..9429dc9 --- /dev/null +++ b/corpus/labels/5Z/Slash/sample-007.json @@ -0,0 +1,63 @@ +{ + "spec": "labels/5Z/Slash", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 7", + "input": { + "label": "5Z", + "text": "/C3 GATE REQ / KBNA KEWR 22 115400 0554 ---- ---- ---- ----" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-5z-slash", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Airline Designated Downlink", + "items": [ + { + "type": "airline", + "code": "AIRLINE", + "label": "Airline", + "value": "United Airlines" + }, + { + "type": "message_type", + "code": "MSG_TYPE", + "label": "Message Type", + "value": "Off Message (C3)" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "KBNA" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KEWR" + }, + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "11:54:00" + } + ] + }, + "raw": { + "airline": "United Airlines", + "message_type": "C3", + "departure_icao": "KBNA", + "arrival_icao": "KEWR", + "day": 22, + "message_timestamp": 42840 + }, + "remaining": { + "text": "0554 ---- ---- ---- ----" + } + } +} diff --git a/corpus/labels/5Z/Slash/sample-008.json b/corpus/labels/5Z/Slash/sample-008.json new file mode 100644 index 0000000..5981a29 --- /dev/null +++ b/corpus/labels/5Z/Slash/sample-008.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/5Z/Slash", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 8", + "input": { + "label": "5Z", + "text": "/ Bogus message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-5z-slash", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Airline Designated Downlink", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "/ Bogus message" + } + } +} diff --git a/corpus/labels/80/sample-001.json b/corpus/labels/80/sample-001.json new file mode 100644 index 0000000..1395c5f --- /dev/null +++ b/corpus/labels/80/sample-001.json @@ -0,0 +1,100 @@ +{ + "spec": "labels/80", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "80", + "text": "3N01 POSRPT 5891/04 KIAH/MMGL .XA-VOI\r\n/POS N29395W095133/ALT +15608/MCH 558/FOB 0100/ETA 0410" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-80", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Airline Defined Position Report", + "items": [ + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "5891" + }, + { + "type": "day", + "code": "MSG_DAY", + "label": "Day of Month", + "value": "4" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "KIAH" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "MMGL" + }, + { + "type": "tail", + "code": "TAIL", + "label": "Tail", + "value": "XA-VOI" + }, + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "29.395 N, 95.133 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "15608 feet" + }, + { + "type": "mach", + "code": "MACH", + "label": "Mach Number", + "value": "0.558 mach" + }, + { + "type": "fuel_on_board", + "code": "FOB", + "label": "Fuel On Board", + "value": "100" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "04:10:00" + } + ] + }, + "raw": { + "flight_number": "5891", + "day": 4, + "departure_icao": "KIAH", + "arrival_icao": "MMGL", + "tail": "XA-VOI", + "position": { + "latitude": 29.395, + "longitude": -95.133 + }, + "altitude": 15608, + "mach": 0.558, + "fuel_on_board": 100, + "eta_time": 15000 + }, + "remaining": { + "text": "3N01 POSRPT/" + } + } +} diff --git a/corpus/labels/80/sample-002.json b/corpus/labels/80/sample-002.json new file mode 100644 index 0000000..7f4718d --- /dev/null +++ b/corpus/labels/80/sample-002.json @@ -0,0 +1,122 @@ +{ + "spec": "labels/80", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "80", + "text": "3N01 POSRPT 0581/27 KIAD/MSLP .N962AV/04H 11:02\r\n/NWYP CIGAR /HDG 233/MCH 782\r\n/POS N3539.2W07937.2/FL 360/TAS 445/SAT -060\r\n/SWND 110/DWND 306/FOB N009414/ETA 14:26.0 " + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-80", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Airline Defined Position Report", + "items": [ + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "0581" + }, + { + "type": "day", + "code": "MSG_DAY", + "label": "Day of Month", + "value": "27" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "KIAD" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "MSLP" + }, + { + "type": "tail", + "code": "TAIL", + "label": "Tail", + "value": "N962AV" + }, + { + "type": "heading", + "code": "HDG", + "label": "Heading", + "value": "233" + }, + { + "type": "mach", + "code": "MACH", + "label": "Mach Number", + "value": "0.782 mach" + }, + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "35.392 N, 79.372 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "36000 feet" + }, + { + "type": "airspeed", + "code": "ASPD", + "label": "True Airspeed", + "value": "445 knots" + }, + { + "type": "outside_air_temperature", + "code": "OATEMP", + "label": "Outside Air Temperature (C)", + "value": "-60 degrees" + }, + { + "type": "fuel_on_board", + "code": "FOB", + "label": "Fuel On Board", + "value": "9414" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "14:26:00" + } + ] + }, + "raw": { + "flight_number": "0581", + "day": 27, + "departure_icao": "KIAD", + "arrival_icao": "MSLP", + "tail": "N962AV", + "next_waypoint": "CIGAR", + "heading": 233, + "mach": 0.782, + "position": { + "latitude": 35.391999999999996, + "longitude": -79.372 + }, + "altitude": 36000, + "airspeed": 445, + "outside_air_temperature": -60, + "fuel_on_board": 9414, + "eta_time": 51960 + }, + "remaining": { + "text": "3N01 POSRPT/04H 11:02////SWND 110/DWND 306" + } + } +} diff --git a/corpus/labels/80/sample-003.json b/corpus/labels/80/sample-003.json new file mode 100644 index 0000000..5b7e88e --- /dev/null +++ b/corpus/labels/80/sample-003.json @@ -0,0 +1,107 @@ +{ + "spec": "labels/80", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 3", + "input": { + "label": "80", + "text": "/FB 0105/AD KCHS/N3950.1,W07548.3,3P01 POSRPT 0267/20 KBOS/KCHS .N3275J\n/UTC 143605/POS N3950.1 W07548.3/ALT 38007\n/SPD 334/FOB 0105/ETA 1622" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-80", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Airline Defined Position Report", + "items": [ + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "0267" + }, + { + "type": "day", + "code": "MSG_DAY", + "label": "Day of Month", + "value": "20" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "KBOS" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KCHS" + }, + { + "type": "tail", + "code": "TAIL", + "label": "Tail", + "value": "N3275J" + }, + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "14:36:05" + }, + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "39.501 N, 75.483 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "38007 feet" + }, + { + "type": "aircraft_groundspeed", + "code": "GSPD", + "label": "Aircraft Groundspeed", + "value": "334 knots" + }, + { + "type": "fuel_on_board", + "code": "FOB", + "label": "Fuel On Board", + "value": "105" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "16:22:00" + } + ] + }, + "raw": { + "flight_number": "0267", + "day": 20, + "departure_icao": "KBOS", + "arrival_icao": "KCHS", + "tail": "N3275J", + "message_timestamp": 52565, + "position": { + "latitude": 39.501, + "longitude": -75.483 + }, + "altitude": 38007, + "groundspeed": 334, + "fuel_on_board": 105, + "eta_time": 58920 + }, + "remaining": { + "text": "N3950.1/W07548.3 3P01 POSRPT//" + } + } +} diff --git a/corpus/labels/80/sample-004.json b/corpus/labels/80/sample-004.json new file mode 100644 index 0000000..2cfc38b --- /dev/null +++ b/corpus/labels/80/sample-004.json @@ -0,0 +1,65 @@ +{ + "spec": "labels/80", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 4", + "input": { + "label": "80", + "text": "3C01 POS N39328W077307 ,,143700, , , ,P47,124,0069" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-80", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Airline Defined Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "39.328 N, 77.307 W" + }, + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "14:37:00" + }, + { + "type": "outside_air_temperature", + "code": "OATEMP", + "label": "Outside Air Temperature (C)", + "value": "47 degrees" + }, + { + "type": "airspeed", + "code": "ASPD", + "label": "True Airspeed", + "value": "124 knots" + }, + { + "type": "fuel_on_board", + "code": "FOB", + "label": "Fuel On Board", + "value": "69" + } + ] + }, + "raw": { + "position": { + "latitude": 39.328, + "longitude": -77.307 + }, + "message_timestamp": 52620, + "outside_air_temperature": 47, + "airspeed": 124, + "fuel_on_board": 69 + }, + "remaining": { + "text": "3C01 POS,, , " + } + } +} diff --git a/corpus/labels/80/sample-005.json b/corpus/labels/80/sample-005.json new file mode 100644 index 0000000..bcbbd8b --- /dev/null +++ b/corpus/labels/80/sample-005.json @@ -0,0 +1,62 @@ +{ + "spec": "labels/80", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 5", + "input": { + "label": "80", + "text": "3M01 OPNORM 0411/20 KEWR/MMMX .XA-MAT " + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-80", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Airline Defined Position Report", + "items": [ + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "0411" + }, + { + "type": "day", + "code": "MSG_DAY", + "label": "Day of Month", + "value": "20" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "KEWR" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "MMMX" + }, + { + "type": "tail", + "code": "TAIL", + "label": "Tail", + "value": "XA-MAT" + } + ] + }, + "raw": { + "flight_number": "0411", + "day": 20, + "departure_icao": "KEWR", + "arrival_icao": "MMMX", + "tail": "XA-MAT" + }, + "remaining": { + "text": "3M01 OPNORM" + } + } +} diff --git a/corpus/labels/80/sample-006.json b/corpus/labels/80/sample-006.json new file mode 100644 index 0000000..6320ecb --- /dev/null +++ b/corpus/labels/80/sample-006.json @@ -0,0 +1,69 @@ +{ + "spec": "labels/80", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 6", + "input": { + "label": "80", + "text": "3701 INRANG 3451/20 KSBD/KBWI .N613AZ\n/ETA 1254/ERT " + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-80", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Airline Defined Position Report", + "items": [ + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "3451" + }, + { + "type": "day", + "code": "MSG_DAY", + "label": "Day of Month", + "value": "20" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "KSBD" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KBWI" + }, + { + "type": "tail", + "code": "TAIL", + "label": "Tail", + "value": "N613AZ" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "12:54:00" + } + ] + }, + "raw": { + "flight_number": "3451", + "day": 20, + "departure_icao": "KSBD", + "arrival_icao": "KBWI", + "tail": "N613AZ", + "eta_time": 46440 + }, + "remaining": { + "text": "3701 INRANG//ERT" + } + } +} diff --git a/corpus/labels/80/sample-007.json b/corpus/labels/80/sample-007.json new file mode 100644 index 0000000..a889c8c --- /dev/null +++ b/corpus/labels/80/sample-007.json @@ -0,0 +1,23 @@ +{ + "spec": "labels/80", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 7", + "input": { + "label": "80", + "text": "INR/ID91511S,,/DC04032026,143534/MR19,/NR,,,,,,,,950,0/ET041505/FB983/VR32BF4C" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-80", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Airline Defined Position Report", + "items": [] + }, + "raw": {}, + "remaining": {} + } +} diff --git a/corpus/labels/80/sample-008.json b/corpus/labels/80/sample-008.json new file mode 100644 index 0000000..b30b0a0 --- /dev/null +++ b/corpus/labels/80/sample-008.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/80", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 8", + "input": { + "label": "80", + "text": "3N01 POSRPT Bogus message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-80", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Airline Defined Position Report", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "3N01 POSRPT Bogus message" + } + } +} diff --git a/corpus/labels/80/sample-009.json b/corpus/labels/80/sample-009.json new file mode 100644 index 0000000..4123a51 --- /dev/null +++ b/corpus/labels/80/sample-009.json @@ -0,0 +1,62 @@ +{ + "spec": "labels/80", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 9", + "input": { + "label": "80", + "text": "3D01 RMPSRV 2501/02 LEMD/MMUN .EC-NOI\n/LAV Y/CAB Y/MEDA N/SEC N/WAS N/WAT N/FUEL N/WCHRR 01/WCHRS --/WCHRC --/UMNR --/MAAS N\nBUENAS NOCHES, ETA 0055Z Y NOS HAN PEDIDO UNA WCHR QUE AL PARECER NO TENIAMOS CONSTANCIA DE ELLA, POR SI PODEIS PEDIRLO A OPS CUN. MUCHAS GRACIAS" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-80", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Airline Defined Position Report", + "items": [ + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "2501" + }, + { + "type": "day", + "code": "MSG_DAY", + "label": "Day of Month", + "value": "2" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "LEMD" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "MMUN" + }, + { + "type": "tail", + "code": "TAIL", + "label": "Tail", + "value": "EC-NOI" + } + ] + }, + "raw": { + "flight_number": "2501", + "day": 2, + "departure_icao": "LEMD", + "arrival_icao": "MMUN", + "tail": "EC-NOI" + }, + "remaining": { + "text": "3D01 RMPSRV//LAV Y/CAB Y/MEDA N/SEC N/WAS N/WAT N/FUEL N/WCHRR 01/WCHRS --/WCHRC --/UMNR --/MAAS N/BUENAS NOCHES, ETA 0055Z Y NOS HAN PEDIDO UNA WCHR QUE AL PARECER NO TENIAMOS CONSTANCIA DE ELLA, POR SI PODEIS PEDIRLO A OPS CUN. MUCHAS GRACIAS" + } + } +} diff --git a/corpus/labels/83/sample-001.json b/corpus/labels/83/sample-001.json new file mode 100644 index 0000000..5091684 --- /dev/null +++ b/corpus/labels/83/sample-001.json @@ -0,0 +1,74 @@ +{ + "spec": "labels/83", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "83", + "text": "KIAH,RJAA,110012, 39.12,-175.10,39001,265,-107.6, 64900" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-83", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Airline Defined", + "items": [ + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "KIAH" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "RJAA" + }, + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "39.120 N, 175.100 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "39001 feet" + }, + { + "type": "aircraft_groundspeed", + "code": "GSPD", + "label": "Aircraft Groundspeed", + "value": "265 knots" + }, + { + "type": "heading", + "code": "HDG", + "label": "Heading", + "value": "-107.6" + } + ] + }, + "raw": { + "departure_icao": "KIAH", + "arrival_icao": "RJAA", + "day": "11", + "time": "0012", + "position": { + "latitude": 39.12, + "longitude": -175.1 + }, + "altitude": 39001, + "groundspeed": 265, + "heading": -107.6 + }, + "remaining": { + "text": "64900" + } + } +} diff --git a/corpus/labels/83/sample-002.json b/corpus/labels/83/sample-002.json new file mode 100644 index 0000000..8a002c5 --- /dev/null +++ b/corpus/labels/83/sample-002.json @@ -0,0 +1,45 @@ +{ + "spec": "labels/83", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "83", + "text": "001PR11013423N0556.6E11603.0000000----" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-83", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Airline Defined", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "5.943 N, 116.050 E" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "0 feet" + } + ] + }, + "raw": { + "day": "11", + "position": { + "latitude": 5.943333333333333, + "longitude": 116.05 + }, + "altitude": 0 + }, + "remaining": { + "text": "0----" + } + } +} diff --git a/corpus/labels/83/sample-003.json b/corpus/labels/83/sample-003.json new file mode 100644 index 0000000..1694b39 --- /dev/null +++ b/corpus/labels/83/sample-003.json @@ -0,0 +1,74 @@ +{ + "spec": "labels/83", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 3", + "input": { + "label": "83", + "text": "KLAX,KEWR,220103, 40.53,- 74.47, 3836,212, 140.0, 19700" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-83", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Airline Defined", + "items": [ + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "KLAX" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KEWR" + }, + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "40.530 N, 74.470 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "3836 feet" + }, + { + "type": "aircraft_groundspeed", + "code": "GSPD", + "label": "Aircraft Groundspeed", + "value": "212 knots" + }, + { + "type": "heading", + "code": "HDG", + "label": "Heading", + "value": "140" + } + ] + }, + "raw": { + "departure_icao": "KLAX", + "arrival_icao": "KEWR", + "day": "22", + "time": "0103", + "position": { + "latitude": 40.53, + "longitude": -74.47 + }, + "altitude": 3836, + "groundspeed": 212, + "heading": 140 + }, + "remaining": { + "text": "19700" + } + } +} diff --git a/corpus/labels/83/sample-004.json b/corpus/labels/83/sample-004.json new file mode 100644 index 0000000..831153e --- /dev/null +++ b/corpus/labels/83/sample-004.json @@ -0,0 +1,56 @@ +{ + "spec": "labels/83", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 4", + "input": { + "label": "83", + "text": "4DH3 ETAT2 0907/22 ENGM/KEWR .LN-RKO\r\n/ETA 1641" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-83", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Airline Defined", + "items": [ + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "ENGM" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KEWR" + }, + { + "type": "tail", + "code": "TAIL", + "label": "Tail", + "value": "LN-RKO" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "16:41:00" + } + ] + }, + "raw": { + "day": "22", + "departure_icao": "ENGM", + "arrival_icao": "KEWR", + "tail": "LN-RKO", + "eta_time": 60060 + }, + "remaining": { + "text": "0907" + } + } +} diff --git a/corpus/labels/83/sample-005.json b/corpus/labels/83/sample-005.json new file mode 100644 index 0000000..7599db8 --- /dev/null +++ b/corpus/labels/83/sample-005.json @@ -0,0 +1,45 @@ +{ + "spec": "labels/83", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 5", + "input": { + "label": "83", + "text": "001PR22035539N4038.6W07427.80292500008" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-83", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Airline Defined", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "40.643 N, 74.463 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "2925 feet" + } + ] + }, + "raw": { + "day": "22", + "position": { + "latitude": 40.64333333333333, + "longitude": -74.46333333333334 + }, + "altitude": 2925 + }, + "remaining": { + "text": "00008" + } + } +} diff --git a/corpus/labels/83/sample-006.json b/corpus/labels/83/sample-006.json new file mode 100644 index 0000000..51e0496 --- /dev/null +++ b/corpus/labels/83/sample-006.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/83", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 6", + "input": { + "label": "83", + "text": "83 Bogus message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-83", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Airline Defined", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "83 Bogus message" + } + } +} diff --git a/corpus/labels/8E/sample-001.json b/corpus/labels/8E/sample-001.json new file mode 100644 index 0000000..34e6ecf --- /dev/null +++ b/corpus/labels/8E/sample-001.json @@ -0,0 +1,39 @@ +{ + "spec": "labels/8E", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "8E", + "text": "EGSS,1618" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-8e", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "ETA Report", + "items": [ + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "16:18:00" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "EGSS" + } + ] + }, + "raw": { + "eta_time": 58680, + "arrival_icao": "EGSS" + }, + "remaining": {} + } +} diff --git a/corpus/labels/H1/ATIS/sample-001.json b/corpus/labels/H1/ATIS/sample-001.json new file mode 100644 index 0000000..ec638b6 --- /dev/null +++ b/corpus/labels/H1/ATIS/sample-001.json @@ -0,0 +1,53 @@ +{ + "spec": "labels/H1/ATIS", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "H1", + "text": "L95AQF0073/KSFO.TI2/030KSFOAFF5C" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-h1-atis", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "ATIS Subscription", + "items": [ + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "QF0073" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KSFO" + }, + { + "type": "atis", + "code": "ATIS_CODE", + "label": "ATIS Code", + "value": "030" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0xff5c" + } + ] + }, + "raw": { + "flight_number": "QF0073", + "arrival_icao": "KSFO", + "atis_code": "030", + "checksum": 720732 + }, + "remaining": {} + } +} diff --git a/corpus/labels/H1/ATIS/sample-002.json b/corpus/labels/H1/ATIS/sample-002.json new file mode 100644 index 0000000..e923425 --- /dev/null +++ b/corpus/labels/H1/ATIS/sample-002.json @@ -0,0 +1,53 @@ +{ + "spec": "labels/H1/ATIS", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "H1", + "text": "L01AMC5477/OERK.TI2/024OERKC8781" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-h1-atis", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "ATIS Subscription", + "items": [ + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "MC5477" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "OERK" + }, + { + "type": "atis", + "code": "ATIS_CODE", + "label": "ATIS Code", + "value": "024" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0x8781" + } + ] + }, + "raw": { + "flight_number": "MC5477", + "arrival_icao": "OERK", + "atis_code": "024", + "checksum": 821121 + }, + "remaining": {} + } +} diff --git a/corpus/labels/H1/ATIS/sample-003.json b/corpus/labels/H1/ATIS/sample-003.json new file mode 100644 index 0000000..c672412 --- /dev/null +++ b/corpus/labels/H1/ATIS/sample-003.json @@ -0,0 +1,53 @@ +{ + "spec": "labels/H1/ATIS", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 3", + "input": { + "label": "H1", + "text": "L11AMC5477/LICC.TI2/024LICCAFBD9" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-h1-atis", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "ATIS Subscription", + "items": [ + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "MC5477" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "LICC" + }, + { + "type": "atis", + "code": "ATIS_CODE", + "label": "ATIS Code", + "value": "024" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0xfbd9" + } + ] + }, + "raw": { + "flight_number": "MC5477", + "arrival_icao": "LICC", + "atis_code": "024", + "checksum": 719833 + }, + "remaining": {} + } +} diff --git a/corpus/labels/H1/ATIS/sample-004.json b/corpus/labels/H1/ATIS/sample-004.json new file mode 100644 index 0000000..5aabafd --- /dev/null +++ b/corpus/labels/H1/ATIS/sample-004.json @@ -0,0 +1,53 @@ +{ + "spec": "labels/H1/ATIS", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 4", + "input": { + "label": "5Z", + "text": "L95AQF0073/KSFO.TI2/030KSFOAFF5C" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-h1-atis", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "ATIS Subscription", + "items": [ + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "QF0073" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KSFO" + }, + { + "type": "atis", + "code": "ATIS_CODE", + "label": "ATIS Code", + "value": "030" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0xff5c" + } + ] + }, + "raw": { + "flight_number": "QF0073", + "arrival_icao": "KSFO", + "atis_code": "030", + "checksum": 720732 + }, + "remaining": {} + } +} diff --git a/corpus/labels/H1/ATIS/sample-005.json b/corpus/labels/H1/ATIS/sample-005.json new file mode 100644 index 0000000..5a48a01 --- /dev/null +++ b/corpus/labels/H1/ATIS/sample-005.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/H1/ATIS", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 5", + "input": { + "label": "H1", + "text": "LINVALID MESSAGE" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-h1-atis", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "ATIS Subscription", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "LINVALID MESSAGE" + } + } +} diff --git a/corpus/labels/H1/ATIS/sample-006.json b/corpus/labels/H1/ATIS/sample-006.json new file mode 100644 index 0000000..d2e0673 --- /dev/null +++ b/corpus/labels/H1/ATIS/sample-006.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/H1/ATIS", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 6", + "input": { + "label": "H1", + "text": "FLR/FR24030411230034583106FWC2" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-h1-atis", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "ATIS Subscription", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "FLR/FR24030411230034583106FWC2" + } + } +} diff --git a/corpus/labels/H1/EZF/sample-001.json b/corpus/labels/H1/EZF/sample-001.json new file mode 100644 index 0000000..9b863f3 --- /dev/null +++ b/corpus/labels/H1/EZF/sample-001.json @@ -0,0 +1,120 @@ +{ + "spec": "labels/H1/EZF", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "H1", + "text": "EZF\nNO0246/10/EI-NEO\n/C28Y331/3/9\n-SCT/MXP-ZNZ\n-STD/2200\n-FLT STATUS/CLOSED\n-UOM/KG\n-ZFW/162075\n-PAX/305\n-PXT/122/160/19/02\n-PXW/22601\n-CGO/3573\n-BAG/4783\n-OTH/4336\n-TTL/35293\n-FWT/51063\n-TOW/213138\n-DOW/126782" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-h1-ezf", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Load Sheet", + "items": [ + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "NO0246" + }, + { + "type": "tail", + "code": "TAIL", + "label": "Tail", + "value": "EI-NEO" + }, + { + "type": "iata", + "code": "ORG", + "label": "Origin", + "value": "MXP" + }, + { + "type": "iata", + "code": "DST", + "label": "Destination", + "value": "ZNZ" + }, + { + "type": "loadsheet", + "code": "STD", + "label": "Scheduled Time of Departure", + "value": "2200" + }, + { + "type": "loadsheet", + "code": "FLIGHT_STATUS", + "label": "Flight Status", + "value": "CLOSED" + }, + { + "type": "loadsheet", + "code": "UOM", + "label": "Unit of Measure", + "value": "KG" + }, + { + "type": "loadsheet", + "code": "ZFW", + "label": "Zero Fuel Weight", + "value": "162075" + }, + { + "type": "loadsheet", + "code": "PAX", + "label": "Passengers", + "value": "305" + }, + { + "type": "loadsheet", + "code": "TOW", + "label": "Takeoff Weight", + "value": "213138" + }, + { + "type": "loadsheet", + "code": "DOW", + "label": "Dry Operating Weight", + "value": "126782" + }, + { + "type": "loadsheet", + "code": "FUEL_WEIGHT", + "label": "Fuel Weight", + "value": "51063" + } + ] + }, + "raw": { + "flight_number": "NO0246", + "tail": "EI-NEO", + "departure_iata": "MXP", + "arrival_iata": "ZNZ", + "loadsheet": { + "SCT": "MXP-ZNZ", + "STD": "2200", + "FLT STATUS": "CLOSED", + "UOM": "KG", + "ZFW": "162075", + "PAX": "305", + "PXT": "122/160/19/02", + "PXW": "22601", + "CGO": "3573", + "BAG": "4783", + "OTH": "4336", + "TTL": "35293", + "FWT": "51063", + "TOW": "213138", + "DOW": "126782" + } + }, + "remaining": { + "text": "/C28Y331/3/9" + } + } +} diff --git a/corpus/labels/H1/EZF/sample-002.json b/corpus/labels/H1/EZF/sample-002.json new file mode 100644 index 0000000..ae0c9c1 --- /dev/null +++ b/corpus/labels/H1/EZF/sample-002.json @@ -0,0 +1,70 @@ +{ + "spec": "labels/H1/EZF", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "H1", + "text": "EZF\nAB1234/05/D-ABCD\n-SCT/JFK-LAX\n-PAX/180\n-ZFW/55000" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-h1-ezf", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Load Sheet", + "items": [ + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "AB1234" + }, + { + "type": "tail", + "code": "TAIL", + "label": "Tail", + "value": "D-ABCD" + }, + { + "type": "iata", + "code": "ORG", + "label": "Origin", + "value": "JFK" + }, + { + "type": "iata", + "code": "DST", + "label": "Destination", + "value": "LAX" + }, + { + "type": "loadsheet", + "code": "ZFW", + "label": "Zero Fuel Weight", + "value": "55000" + }, + { + "type": "loadsheet", + "code": "PAX", + "label": "Passengers", + "value": "180" + } + ] + }, + "raw": { + "flight_number": "AB1234", + "tail": "D-ABCD", + "departure_iata": "JFK", + "arrival_iata": "LAX", + "loadsheet": { + "SCT": "JFK-LAX", + "PAX": "180", + "ZFW": "55000" + } + }, + "remaining": {} + } +} diff --git a/corpus/labels/H1/EZF/sample-003.json b/corpus/labels/H1/EZF/sample-003.json new file mode 100644 index 0000000..a6a339c --- /dev/null +++ b/corpus/labels/H1/EZF/sample-003.json @@ -0,0 +1,63 @@ +{ + "spec": "labels/H1/EZF", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 3", + "input": { + "label": "1M", + "text": "EZF\nXY5678/12/G-TEST\n-SCT/LHR-CDG\n-PAX/90" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-h1-ezf", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Load Sheet", + "items": [ + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "XY5678" + }, + { + "type": "tail", + "code": "TAIL", + "label": "Tail", + "value": "G-TEST" + }, + { + "type": "iata", + "code": "ORG", + "label": "Origin", + "value": "LHR" + }, + { + "type": "iata", + "code": "DST", + "label": "Destination", + "value": "CDG" + }, + { + "type": "loadsheet", + "code": "PAX", + "label": "Passengers", + "value": "90" + } + ] + }, + "raw": { + "flight_number": "XY5678", + "tail": "G-TEST", + "departure_iata": "LHR", + "arrival_iata": "CDG", + "loadsheet": { + "SCT": "LHR-CDG", + "PAX": "90" + } + }, + "remaining": {} + } +} diff --git a/corpus/labels/H1/EZF/sample-004.json b/corpus/labels/H1/EZF/sample-004.json new file mode 100644 index 0000000..8b97ca2 --- /dev/null +++ b/corpus/labels/H1/EZF/sample-004.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/H1/EZF", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 4", + "input": { + "label": "H1", + "text": "NOT_EZF data here" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-h1-ezf", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Load Sheet", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "NOT_EZF data here" + } + } +} diff --git a/corpus/labels/H1/EZF/sample-005.json b/corpus/labels/H1/EZF/sample-005.json new file mode 100644 index 0000000..9a5db95 --- /dev/null +++ b/corpus/labels/H1/EZF/sample-005.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/H1/EZF", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 5", + "input": { + "label": "H1", + "text": "EZF" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-h1-ezf", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Load Sheet", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "EZF" + } + } +} diff --git a/corpus/labels/H1/FLR/sample-001.json b/corpus/labels/H1/FLR/sample-001.json new file mode 100644 index 0000000..fced4f0 --- /dev/null +++ b/corpus/labels/H1/FLR/sample-001.json @@ -0,0 +1,35 @@ +{ + "spec": "labels/H1/FLR", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "H1", + "text": "FLR/FR24030411230034583106FWC2 :NO DATA FROM GPS1 /IDECAM 2 ,ECAM 1 " + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-h1-flr", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Fault Log Report", + "items": [ + { + "type": "fault", + "code": "FR", + "label": "Fault Report", + "value": "FWC2 :NO DATA FROM GPS1 /IDECAM 2 ,ECAM 1 " + } + ] + }, + "raw": { + "message_timestamp": 1709551380, + "fault_message": "FWC2 :NO DATA FROM GPS1 /IDECAM 2 ,ECAM 1 " + }, + "remaining": { + "text": "34583106" + } + } +} diff --git a/corpus/labels/H1/FLR/sample-002.json b/corpus/labels/H1/FLR/sample-002.json new file mode 100644 index 0000000..e97f279 --- /dev/null +++ b/corpus/labels/H1/FLR/sample-002.json @@ -0,0 +1,35 @@ +{ + "spec": "labels/H1/FLR", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "H1", + "text": "FLR/PNRC12860AA07/FR24030411040023723406CDSU(9RA)/DU SD(4WT2) /IDEIS 1 " + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-h1-flr", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Fault Log Report", + "items": [ + { + "type": "fault", + "code": "FR", + "label": "Fault Report", + "value": "CDSU(9RA)/DU SD(4WT2) /IDEIS 1 " + } + ] + }, + "raw": { + "message_timestamp": 1709550240, + "fault_message": "CDSU(9RA)/DU SD(4WT2) /IDEIS 1 " + }, + "remaining": { + "text": "PNRC12860AA07/23723406" + } + } +} diff --git a/corpus/labels/H1/FLR/sample-003.json b/corpus/labels/H1/FLR/sample-003.json new file mode 100644 index 0000000..60735dd --- /dev/null +++ b/corpus/labels/H1/FLR/sample-003.json @@ -0,0 +1,35 @@ +{ + "spec": "labels/H1/FLR", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 3", + "input": { + "label": "H1", + "text": "FLR/FR24030409560034423306RA1/IDEFCS 1 ,EFCS 2 " + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-h1-flr", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Fault Log Report", + "items": [ + { + "type": "fault", + "code": "FR", + "label": "Fault Report", + "value": "RA1/IDEFCS 1 ,EFCS 2 " + } + ] + }, + "raw": { + "message_timestamp": 1709546160, + "fault_message": "RA1/IDEFCS 1 ,EFCS 2 " + }, + "remaining": { + "text": "34423306" + } + } +} diff --git a/corpus/labels/H1/FLR/sample-004.json b/corpus/labels/H1/FLR/sample-004.json new file mode 100644 index 0000000..3b89906 --- /dev/null +++ b/corpus/labels/H1/FLR/sample-004.json @@ -0,0 +1,35 @@ +{ + "spec": "labels/H1/FLR", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 4", + "input": { + "label": "H1", + "text": "FLR/FR24030410030038316206LIQD LVL XMTR (40MG)/IDTOILET " + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-h1-flr", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Fault Log Report", + "items": [ + { + "type": "fault", + "code": "FR", + "label": "Fault Report", + "value": "LIQD LVL XMTR (40MG)/IDTOILET " + } + ] + }, + "raw": { + "message_timestamp": 1709546580, + "fault_message": "LIQD LVL XMTR (40MG)/IDTOILET " + }, + "remaining": { + "text": "38316206" + } + } +} diff --git a/corpus/labels/H1/FLR/sample-005.json b/corpus/labels/H1/FLR/sample-005.json new file mode 100644 index 0000000..71061ab --- /dev/null +++ b/corpus/labels/H1/FLR/sample-005.json @@ -0,0 +1,35 @@ +{ + "spec": "labels/H1/FLR", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 5", + "input": { + "label": "H1", + "text": "#CFBFLR/FR24030412400034723406ATC1(1SH1)/TCAS(1000SG) /IDTCAS " + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-h1-flr", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Fault Log Report", + "items": [ + { + "type": "fault", + "code": "FR", + "label": "Fault Report", + "value": "ATC1(1SH1)/TCAS(1000SG) /IDTCAS " + } + ] + }, + "raw": { + "message_timestamp": 1709556000, + "fault_message": "ATC1(1SH1)/TCAS(1000SG) /IDTCAS " + }, + "remaining": { + "text": "34723406" + } + } +} diff --git a/corpus/labels/H1/FLR/sample-006.json b/corpus/labels/H1/FLR/sample-006.json new file mode 100644 index 0000000..453d816 --- /dev/null +++ b/corpus/labels/H1/FLR/sample-006.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/H1/FLR", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 6", + "input": { + "label": "H1", + "text": "FLR " + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-h1-flr", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Fault Log Report", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "FLR " + } + } +} diff --git a/corpus/labels/H1/M_POS/sample-001.json b/corpus/labels/H1/M_POS/sample-001.json new file mode 100644 index 0000000..9e3c4bc --- /dev/null +++ b/corpus/labels/H1/M_POS/sample-001.json @@ -0,0 +1,86 @@ +{ + "spec": "labels/H1/M_POS", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "H1", + "text": "M85AQF0073YSSY,KSFO,101621,- 4.9985,-169.9820,35003,290, 35.1, 44100,S05W169,S02W167,1645" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-h1-m-pos", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "M-Series Periodic Position Report", + "items": [ + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "QF0073" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "YSSY" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KSFO" + }, + { + "type": "day", + "code": "MSG_DAY", + "label": "Day of Month", + "value": "10" + }, + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "16:21:00" + }, + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "4.998 S, 169.982 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "35003 feet" + }, + { + "type": "heading", + "code": "HDG", + "label": "Heading", + "value": "290" + } + ] + }, + "raw": { + "flight_number": "QF0073", + "departure_icao": "YSSY", + "arrival_icao": "KSFO", + "day": 10, + "message_timestamp": 58860, + "position": { + "latitude": -4.9985, + "longitude": -169.982 + }, + "altitude": 35003, + "heading": 290 + }, + "remaining": { + "text": " 35.1, 44100,S05W169,S02W167,1645" + } + } +} diff --git a/corpus/labels/H1/M_POS/sample-002.json b/corpus/labels/H1/M_POS/sample-002.json new file mode 100644 index 0000000..464c0de --- /dev/null +++ b/corpus/labels/H1/M_POS/sample-002.json @@ -0,0 +1,86 @@ +{ + "spec": "labels/H1/M_POS", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "H1", + "text": "M87AQF0073YSSY,KSFO,101702, 0.7144,-166.0643,36000,282, 34.1, 40200,S00W166,N03W162,1739" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-h1-m-pos", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "M-Series Periodic Position Report", + "items": [ + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "QF0073" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "YSSY" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KSFO" + }, + { + "type": "day", + "code": "MSG_DAY", + "label": "Day of Month", + "value": "10" + }, + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "17:02:00" + }, + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "0.714 N, 166.064 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "36000 feet" + }, + { + "type": "heading", + "code": "HDG", + "label": "Heading", + "value": "282" + } + ] + }, + "raw": { + "flight_number": "QF0073", + "departure_icao": "YSSY", + "arrival_icao": "KSFO", + "day": 10, + "message_timestamp": 61320, + "position": { + "latitude": 0.7144, + "longitude": -166.0643 + }, + "altitude": 36000, + "heading": 282 + }, + "remaining": { + "text": " 34.1, 40200,S00W166,N03W162,1739" + } + } +} diff --git a/corpus/labels/H1/M_POS/sample-003.json b/corpus/labels/H1/M_POS/sample-003.json new file mode 100644 index 0000000..ec38393 --- /dev/null +++ b/corpus/labels/H1/M_POS/sample-003.json @@ -0,0 +1,86 @@ +{ + "spec": "labels/H1/M_POS", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 3", + "input": { + "label": "H1", + "text": "M89AQF0073YSSY,KSFO,101819, 7.2016,-158.1146,37001,275, 33.5, 32700,N07W158,N11W153,1900" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-h1-m-pos", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "M-Series Periodic Position Report", + "items": [ + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "QF0073" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "YSSY" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KSFO" + }, + { + "type": "day", + "code": "MSG_DAY", + "label": "Day of Month", + "value": "10" + }, + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "18:19:00" + }, + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "7.202 N, 158.115 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "37001 feet" + }, + { + "type": "heading", + "code": "HDG", + "label": "Heading", + "value": "275" + } + ] + }, + "raw": { + "flight_number": "QF0073", + "departure_icao": "YSSY", + "arrival_icao": "KSFO", + "day": 10, + "message_timestamp": 65940, + "position": { + "latitude": 7.2016, + "longitude": -158.1146 + }, + "altitude": 37001, + "heading": 275 + }, + "remaining": { + "text": " 33.5, 32700,N07W158,N11W153,1900" + } + } +} diff --git a/corpus/labels/H1/M_POS/sample-004.json b/corpus/labels/H1/M_POS/sample-004.json new file mode 100644 index 0000000..e34e974 --- /dev/null +++ b/corpus/labels/H1/M_POS/sample-004.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/H1/M_POS", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 4", + "input": { + "label": "H1", + "text": "FLR/FR24030411230034583106FWC2" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-h1-m-pos", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "M-Series Periodic Position Report", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "FLR/FR24030411230034583106FWC2" + } + } +} diff --git a/corpus/labels/H1/M_POS/sample-005.json b/corpus/labels/H1/M_POS/sample-005.json new file mode 100644 index 0000000..c75fb9d --- /dev/null +++ b/corpus/labels/H1/M_POS/sample-005.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/H1/M_POS", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 5", + "input": { + "label": "H1", + "text": "M85AQF0073YSSY,KSFO" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-h1-m-pos", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "M-Series Periodic Position Report", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "M85AQF0073YSSY,KSFO" + } + } +} diff --git a/corpus/labels/H1/M_POS/sample-006.json b/corpus/labels/H1/M_POS/sample-006.json new file mode 100644 index 0000000..81a648c --- /dev/null +++ b/corpus/labels/H1/M_POS/sample-006.json @@ -0,0 +1,26 @@ +{ + "spec": "labels/H1/M_POS", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 6", + "input": { + "label": "H1", + "sublabel": "DF", + "text": "A350,000354,1,1,TB000000/REP035,01,02;H01,035,01,02,4000,00066,.F-HREV,3,0,08,12,19,06,58,42,071/H02,KSFO LFPO,FBU711 ,S0285,S0385,S0142S0185/H03, /A10,06,58,40,+26535,+01198,045,+0493,321723,XXXXXX,--------,0423,+0573/A11,06,58,32,+26356,+01221,041,+0502,324326,XXXXXX,--------,0424,+0614/:" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-h1-m-pos", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "M-Series Periodic Position Report", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "A350,000354,1,1,TB000000/REP035,01,02;H01,035,01,02,4000,00066,.F-HREV,3,0,08,12,19,06,58,42,071/H02,KSFO LFPO,FBU711 ,S0285,S0385,S0142S0185/H03, /A10,06,58,40,+26535,+01198,045,+0493,321723,XXXXXX,--------,0423,+0573/A11,06,58,32,+26356,+01221,041,+0502,324326,XXXXXX,--------,0424,+0614/:" + } + } +} diff --git a/corpus/labels/H1/M_POS/sample-007.json b/corpus/labels/H1/M_POS/sample-007.json new file mode 100644 index 0000000..df23540 --- /dev/null +++ b/corpus/labels/H1/M_POS/sample-007.json @@ -0,0 +1,26 @@ +{ + "spec": "labels/H1/M_POS", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 7", + "input": { + "label": "H1", + "sublabel": "DF", + "text": "A350,000354,1,1,TB000000/REP035,01,02;H01,035,01,02,4000,00066,.F-HREV,3,0,08,12,19,06,58,42,071/H02,KSFO LFPO,FBU711 ,S0285,S0385,S0142S0185/H03, /A10,06,58,40,+26535,+01198,045,+0493,321723,XXXXXX,--------,0423,+0573/A11,06,58,32,+26356,+01221,041,+0502,324326,XXXXXX,--------,0424,+0614/:" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-h1-m-pos", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "M-Series Periodic Position Report", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "A350,000354,1,1,TB000000/REP035,01,02;H01,035,01,02,4000,00066,.F-HREV,3,0,08,12,19,06,58,42,071/H02,KSFO LFPO,FBU711 ,S0285,S0385,S0142S0185/H03, /A10,06,58,40,+26535,+01198,045,+0493,321723,XXXXXX,--------,0423,+0573/A11,06,58,32,+26356,+01221,041,+0502,324326,XXXXXX,--------,0424,+0614/:" + } + } +} diff --git a/corpus/labels/H1/OFP/sample-001.json b/corpus/labels/H1/OFP/sample-001.json new file mode 100644 index 0000000..58da469 --- /dev/null +++ b/corpus/labels/H1/OFP/sample-001.json @@ -0,0 +1,26 @@ +{ + "spec": "labels/H1/OFP", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "H1", + "sublabel": "DF", + "text": "A350,000354,1,1,TB000000/REP035,01,02;H01,035,01,02,4000,00066,.F-HREV,3,0,08,12,19,06,58,42,071/H02,KSFO LFPO,FBU711 ,S0285,S0385,S0142S0185/H03, /A10,06,58,40,+26535,+01198,045,+0493,321723,XXXXXX,--------,0423,+0573/A11,06,58,32,+26356,+01221,041,+0502,324326,XXXXXX,--------,0424,+0614/:" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-h1-ofp", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Operational Flight Plan", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "A350,000354,1,1,TB000000/REP035,01,02;H01,035,01,02,4000,00066,.F-HREV,3,0,08,12,19,06,58,42,071/H02,KSFO LFPO,FBU711 ,S0285,S0385,S0142S0185/H03, /A10,06,58,40,+26535,+01198,045,+0493,321723,XXXXXX,--------,0423,+0573/A11,06,58,32,+26356,+01221,041,+0502,324326,XXXXXX,--------,0424,+0614/:" + } + } +} diff --git a/corpus/labels/H1/OFP/sample-002.json b/corpus/labels/H1/OFP/sample-002.json new file mode 100644 index 0000000..45be5a9 --- /dev/null +++ b/corpus/labels/H1/OFP/sample-002.json @@ -0,0 +1,164 @@ +{ + "spec": "labels/H1/OFP", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "H1", + "text": "(FPL-CCA769-IS\n-B77W/H-SDE3FGHIJ1J2J3J4J5M1P2RWXYZ/LB1D1\n-VESPA2354\n-N0482F350 DCT ENI DCT OAK DCT BURGL IRNMN2\n-KLAX0117 KSFO\n-PBN/A1B1C1D1L1O2S2T1 NAV/ABAS RNP2 DAT/1FANSE SUR/RSP180\n DOF/260310 REG/B2036\n EET/KZLA0051\n SEL/BRCE CODE/780970\n RMK/ACAS II)" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-h1-ofp", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "Operational Flight Plan", + "items": [ + { + "type": "callsign", + "code": "CALLSIGN", + "label": "Callsign", + "value": "CCA769" + }, + { + "type": "aircraft_type", + "code": "ACFT", + "label": "Aircraft Type", + "value": "B77W" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "VESPA" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KLAX" + }, + { + "type": "icao", + "code": "ALT_DST", + "label": "Alternate Destination", + "value": "KSFO" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "DCT > ENI > DCT > OAK > DCT > BURGL > IRNMN2" + }, + { + "type": "tail", + "code": "TAIL", + "label": "Tail", + "value": "B2036" + }, + { + "type": "flight_rules", + "code": "RULES", + "label": "Flight Rules", + "value": "IFR" + }, + { + "type": "cruise", + "code": "CRUISE", + "label": "Cruise Speed/Level", + "value": "N0482 F350" + } + ] + }, + "raw": { + "callsign": "CCA769", + "departure_icao": "VESPA", + "arrival_icao": "KLAX", + "alternate_icao": "KSFO", + "route": { + "waypoints": [ + { + "name": "DCT" + }, + { + "name": "ENI" + }, + { + "name": "DCT" + }, + { + "name": "OAK" + }, + { + "name": "DCT" + }, + { + "name": "BURGL" + }, + { + "name": "IRNMN2" + } + ] + }, + "tail": "B2036", + "icao_fpl": { + "callsign": "CCA769", + "flightRules": "I", + "flightType": "S", + "aircraftType": "B77W", + "wakeTurbulence": "H", + "equipment": "SDE3FGHIJ1J2J3J4J5M1P2RWXYZ", + "surveillance": "LB1D1", + "departure": "VESPA", + "departureTime": "2354", + "cruiseSpeed": "N0482", + "cruiseLevel": "F350", + "route": { + "waypoints": [ + { + "name": "DCT" + }, + { + "name": "ENI" + }, + { + "name": "DCT" + }, + { + "name": "OAK" + }, + { + "name": "DCT" + }, + { + "name": "BURGL" + }, + { + "name": "IRNMN2" + } + ] + }, + "destination": "KLAX", + "eet": "0117", + "alternates": [ + "KSFO" + ], + "otherInfo": { + "PBN": "A1B1C1D1L1O2S2T1", + "NAV": "ABAS RNP2", + "DAT": "1FANSE", + "SUR": "RSP180", + "DOF": "260310", + "REG": "B2036", + "EET": "KZLA0051", + "SEL": "BRCE", + "CODE": "780970", + "RMK": "ACAS II" + } + } + }, + "remaining": {} + } +} diff --git a/corpus/labels/H1/OFP/sample-003.json b/corpus/labels/H1/OFP/sample-003.json new file mode 100644 index 0000000..9aa0e59 --- /dev/null +++ b/corpus/labels/H1/OFP/sample-003.json @@ -0,0 +1,145 @@ +{ + "spec": "labels/H1/OFP", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 3", + "input": { + "label": "H1", + "text": "(FPL-UAL123-IS-B789/H-SDE2E3FGHIJ1J5M1RWXYZ/LB1D1-KSFO0100-N0487F370 DCT PORTE J584 ENI DCT-KLAX0055 KONT-PBN/A1B1C1D1L1 DOF/260311 REG/N22992)" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-h1-ofp", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "Operational Flight Plan", + "items": [ + { + "type": "callsign", + "code": "CALLSIGN", + "label": "Callsign", + "value": "UAL123" + }, + { + "type": "aircraft_type", + "code": "ACFT", + "label": "Aircraft Type", + "value": "B789" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "KSFO" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KLAX" + }, + { + "type": "icao", + "code": "ALT_DST", + "label": "Alternate Destination", + "value": "KONT" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "DCT > PORTE > J584 > ENI > DCT" + }, + { + "type": "tail", + "code": "TAIL", + "label": "Tail", + "value": "N22992" + }, + { + "type": "flight_rules", + "code": "RULES", + "label": "Flight Rules", + "value": "IFR" + }, + { + "type": "cruise", + "code": "CRUISE", + "label": "Cruise Speed/Level", + "value": "N0487 F370" + } + ] + }, + "raw": { + "callsign": "UAL123", + "departure_icao": "KSFO", + "arrival_icao": "KLAX", + "alternate_icao": "KONT", + "route": { + "waypoints": [ + { + "name": "DCT" + }, + { + "name": "PORTE" + }, + { + "name": "J584" + }, + { + "name": "ENI" + }, + { + "name": "DCT" + } + ] + }, + "tail": "N22992", + "icao_fpl": { + "callsign": "UAL123", + "flightRules": "I", + "flightType": "S", + "aircraftType": "B789", + "wakeTurbulence": "H", + "equipment": "SDE2E3FGHIJ1J5M1RWXYZ", + "surveillance": "LB1D1", + "departure": "KSFO", + "departureTime": "0100", + "cruiseSpeed": "N0487", + "cruiseLevel": "F370", + "route": { + "waypoints": [ + { + "name": "DCT" + }, + { + "name": "PORTE" + }, + { + "name": "J584" + }, + { + "name": "ENI" + }, + { + "name": "DCT" + } + ] + }, + "destination": "KLAX", + "eet": "0055", + "alternates": [ + "KONT" + ], + "otherInfo": { + "PBN": "A1B1C1D1L1", + "DOF": "260311", + "REG": "N22992" + } + } + }, + "remaining": {} + } +} diff --git a/corpus/labels/H1/OFP/sample-004.json b/corpus/labels/H1/OFP/sample-004.json new file mode 100644 index 0000000..7db4f2e --- /dev/null +++ b/corpus/labels/H1/OFP/sample-004.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/H1/OFP", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 4", + "input": { + "label": "H1", + "text": "FPN/RI:DA:KSFO:AA:KLAX:F:KSFO..KLAX" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-h1-ofp", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Operational Flight Plan", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "FPN/RI:DA:KSFO:AA:KLAX:F:KSFO..KLAX" + } + } +} diff --git a/corpus/labels/H1/OFP/sample-005.json b/corpus/labels/H1/OFP/sample-005.json new file mode 100644 index 0000000..ea173dd --- /dev/null +++ b/corpus/labels/H1/OFP/sample-005.json @@ -0,0 +1,145 @@ +{ + "spec": "labels/H1/OFP", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 5", + "input": { + "label": "1M", + "text": "(FPL-UAL123-IS-B789/H-SDE2E3FGHIJ1J5M1RWXYZ/LB1D1-KSFO0100-N0487F370 DCT PORTE J584 ENI DCT-KLAX0055 KONT-PBN/A1B1C1D1L1 DOF/260311 REG/N22992)" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-h1-ofp", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "Operational Flight Plan", + "items": [ + { + "type": "callsign", + "code": "CALLSIGN", + "label": "Callsign", + "value": "UAL123" + }, + { + "type": "aircraft_type", + "code": "ACFT", + "label": "Aircraft Type", + "value": "B789" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "KSFO" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KLAX" + }, + { + "type": "icao", + "code": "ALT_DST", + "label": "Alternate Destination", + "value": "KONT" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "DCT > PORTE > J584 > ENI > DCT" + }, + { + "type": "tail", + "code": "TAIL", + "label": "Tail", + "value": "N22992" + }, + { + "type": "flight_rules", + "code": "RULES", + "label": "Flight Rules", + "value": "IFR" + }, + { + "type": "cruise", + "code": "CRUISE", + "label": "Cruise Speed/Level", + "value": "N0487 F370" + } + ] + }, + "raw": { + "callsign": "UAL123", + "departure_icao": "KSFO", + "arrival_icao": "KLAX", + "alternate_icao": "KONT", + "route": { + "waypoints": [ + { + "name": "DCT" + }, + { + "name": "PORTE" + }, + { + "name": "J584" + }, + { + "name": "ENI" + }, + { + "name": "DCT" + } + ] + }, + "tail": "N22992", + "icao_fpl": { + "callsign": "UAL123", + "flightRules": "I", + "flightType": "S", + "aircraftType": "B789", + "wakeTurbulence": "H", + "equipment": "SDE2E3FGHIJ1J5M1RWXYZ", + "surveillance": "LB1D1", + "departure": "KSFO", + "departureTime": "0100", + "cruiseSpeed": "N0487", + "cruiseLevel": "F370", + "route": { + "waypoints": [ + { + "name": "DCT" + }, + { + "name": "PORTE" + }, + { + "name": "J584" + }, + { + "name": "ENI" + }, + { + "name": "DCT" + } + ] + }, + "destination": "KLAX", + "eet": "0055", + "alternates": [ + "KONT" + ], + "otherInfo": { + "PBN": "A1B1C1D1L1", + "DOF": "260311", + "REG": "N22992" + } + } + }, + "remaining": {} + } +} diff --git a/corpus/labels/H1/OFP/sample-006.json b/corpus/labels/H1/OFP/sample-006.json new file mode 100644 index 0000000..d32dd14 --- /dev/null +++ b/corpus/labels/H1/OFP/sample-006.json @@ -0,0 +1,26 @@ +{ + "spec": "labels/H1/OFP", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 6", + "input": { + "label": "H1", + "sublabel": "DF", + "text": "A350,000354,1,1,TB000000/REP035,01,02;H01,035,01,02,4000,00066,.F-HREV,3,0,08,12,19,06,58,42,071/H02,KSFO LFPO,FBU711 ,S0285,S0385,S0142S0185/H03, /A10,06,58,40,+26535,+01198,045,+0493,321723,XXXXXX,--------,0423,+0573/A11,06,58,32,+26356,+01221,041,+0502,324326,XXXXXX,--------,0424,+0614/:" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-h1-ofp", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Operational Flight Plan", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "A350,000354,1,1,TB000000/REP035,01,02;H01,035,01,02,4000,00066,.F-HREV,3,0,08,12,19,06,58,42,071/H02,KSFO LFPO,FBU711 ,S0285,S0385,S0142S0185/H03, /A10,06,58,40,+26535,+01198,045,+0493,321723,XXXXXX,--------,0423,+0573/A11,06,58,32,+26356,+01221,041,+0502,324326,XXXXXX,--------,0424,+0614/:" + } + } +} diff --git a/corpus/labels/H1/OHMA/sample-001.json b/corpus/labels/H1/OHMA/sample-001.json new file mode 100644 index 0000000..01c7ba0 --- /dev/null +++ b/corpus/labels/H1/OHMA/sample-001.json @@ -0,0 +1,32 @@ +{ + "spec": "labels/H1/OHMA", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "H1", + "text": "OHMAeJy1Ul2PojAU/SubPiuhlA/ljQHMkAElgDuTXTakqx2nCaAp1WQz8b/vFXTGEcw+LX26veece88p7+jARMO3NbKRpqhohCrWNHTDoH7P0arkrJbBOkd2jhaPkZOjUX6BeFSytqGpmj5WJ2NVyzC2ydTWiWJN9B8teE0lBRSIUS52Ja1ZA+VPqCXl5Xxf/WaiVZkTyzD0bsB2zcr2si1fS755kxfamu2okHvBHNDbCukCuMU+PSzSFk+F4Ada9vqLxLvSuxq9dELDtK56IdukEqZkvOpbVG0VXIJFYnYW2QFCuqzXFm5Jm+YcG2xLKyYFX32CB3V70XXQPzv2EX8R+Vn3BrxuJK1XH2HW9KxYyRgWv5gDJCSx72CnOH8dR1/RLhXl9+4f6MF1xRhgJPuSNUzeI1nE+hY5L7ZtO4/RmFjRQwj+4MNqCJf4pNhp/reosggXbhjEke8Fzrxwk2WQ+kUSZ8U/wgNi5kcxhpy9G2PENBVtqt+kcSZoQ4QJViyTDBO8YDYb4GBDwQTfUOIIx4mfpsvEH1xMhzHmfc7gbkTR1R7HcZ9m4eL53m5YmeAhTjA/jbrDMhULT08P3p0jOv4FG0dfvQ==" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-h1-ohma", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "OHMA Message", + "items": [ + { + "type": "ohma", + "code": "OHMA", + "label": "OHMA Downlink", + "value": "{\n \"clientId\": \"OHMA\",\n \"messageDate\": \"2024-08-02T11:39:43.784Z\",\n \"data\": {\n \"airplanes\": [\n {\n \"tailNumber\": \"N37554\",\n \"model\": \"\",\n \"flights\": [\n {\n \"departureAirportCode\": \"KBOS\",\n \"arrivalAirportCode\": \"KORD\",\n \"flightNumber\": \"UAL567\",\n \"flightLegStartTime\": \"2024-08-02T10:01:33.736Z\",\n \"events\": [\n {\n \"eventClassId\": \"parametric\",\n \"eventTime\": \"2024-08-02T11:39:43.784Z\",\n \"eventType\": \"OHMA_META\",\n \"instances\": [\n {\n \"name\": \"mtPartNumber\",\n \"values\": [\n \"\"\n ]\n },\n {\n \"name\": \"mtCarlVersion\",\n \"values\": [\n \"4.5\"\n ]\n },\n {\n \"name\": \"mtRulesetVersion\",\n \"values\": [\n \"737 MAX:::AHM-37MBL8-000010L:::1\"\n ]\n }\n ]\n },\n {\n \"eventClassId\": \"parametric\",\n \"eventTime\": \"2024-08-02T11:39:43.784Z\",\n \"eventType\": \"TM1_CLIPMEDIAN_CRUISE_RPT_A\",\n \"instances\": [\n {\n \"name\": \"TM1TEMP1_MED\",\n \"values\": [\n 366.294\n ]\n },\n {\n \"name\": \"TM1TEMP2_MED\",\n \"values\": [\n 381.763\n ]\n },\n {\n \"name\": \"TM1TEMPDIFF_MED\",\n \"values\": [\n 15.131\n ]\n },\n {\n \"name\": \"PM1PRESSURE1_MED\",\n \"values\": [\n 34.766\n ]\n },\n {\n \"name\": \"PM1PRESSURE2_MED\",\n \"values\": [\n 33.406\n ]\n },\n {\n \"name\": \"PACKFLOWDIFF_MED\",\n \"values\": [\n 11.816\n ]\n },\n {\n \"name\": \"PACKINPRESDIFF_MED\",\n \"values\": [\n 6.719\n ]\n }\n ]\n }\n ]\n }\n ]\n }\n ]\n }\n}" + } + ] + }, + "raw": { + "ohma": "{\"version\":\"2.0\",\"message\":\"{\\\"clientId\\\":\\\"OHMA\\\",\\\"messageDate\\\":\\\"2024-08-02T11:39:43.784Z\\\",\\\"data\\\":{\\\"airplanes\\\":[{\\\"tailNumber\\\":\\\"N37554\\\",\\\"model\\\":\\\"\\\",\\\"flights\\\":[{\\\"departureAirportCode\\\":\\\"KBOS\\\",\\\"arrivalAirportCode\\\":\\\"KORD\\\",\\\"flightNumber\\\":\\\"UAL567\\\",\\\"flightLegStartTime\\\":\\\"2024-08-02T10:01:33.736Z\\\",\\\"events\\\":[{\\\"eventClassId\\\":\\\"parametric\\\",\\\"eventTime\\\":\\\"2024-08-02T11:39:43.784Z\\\",\\\"eventType\\\":\\\"OHMA_META\\\",\\\"instances\\\":[{\\\"name\\\":\\\"mtPartNumber\\\",\\\"values\\\":[\\\"\\\"]},{\\\"name\\\":\\\"mtCarlVersion\\\",\\\"values\\\":[\\\"4.5\\\"]},{\\\"name\\\":\\\"mtRulesetVersion\\\",\\\"values\\\":[\\\"737 MAX:::AHM-37MBL8-000010L:::1\\\"]}]},{\\\"eventClassId\\\":\\\"parametric\\\",\\\"eventTime\\\":\\\"2024-08-02T11:39:43.784Z\\\",\\\"eventType\\\":\\\"TM1_CLIPMEDIAN_CRUISE_RPT_A\\\",\\\"instances\\\":[{\\\"name\\\":\\\"TM1TEMP1_MED\\\",\\\"values\\\":[366.294]},{\\\"name\\\":\\\"TM1TEMP2_MED\\\",\\\"values\\\":[381.763]},{\\\"name\\\":\\\"TM1TEMPDIFF_MED\\\",\\\"values\\\":[15.131]},{\\\"name\\\":\\\"PM1PRESSURE1_MED\\\",\\\"values\\\":[34.766]},{\\\"name\\\":\\\"PM1PRESSURE2_MED\\\",\\\"values\\\":[33.406]},{\\\"name\\\":\\\"PACKFLOWDIFF_MED\\\",\\\"values\\\":[11.816]},{\\\"name\\\":\\\"PACKINPRESDIFF_MED\\\",\\\"values\\\":[6.719]}]}]}]}]}}\"}" + }, + "remaining": {} + } +} diff --git a/corpus/labels/H1/OHMA/sample-002.json b/corpus/labels/H1/OHMA/sample-002.json new file mode 100644 index 0000000..d6dea59 --- /dev/null +++ b/corpus/labels/H1/OHMA/sample-002.json @@ -0,0 +1,32 @@ +{ + "spec": "labels/H1/OHMA", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "H1", + "text": "/RTNBOCR.OHMAeJyrVipLLSrOzM9TslIy0jNQ0lHKTS0uTkxPBfJL81JS0zLzUlOUagH7TQzW" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-h1-ohma", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "OHMA Message", + "items": [ + { + "type": "ohma", + "code": "OHMA", + "label": "OHMA Downlink", + "value": "undefined" + } + ] + }, + "raw": { + "ohma": "{\"version\":\"2.0\",\"message\":\"undefined\"}" + }, + "remaining": {} + } +} diff --git a/corpus/labels/H1/OHMA/sample-003.json b/corpus/labels/H1/OHMA/sample-003.json new file mode 100644 index 0000000..7ab87c5 --- /dev/null +++ b/corpus/labels/H1/OHMA/sample-003.json @@ -0,0 +1,32 @@ +{ + "spec": "labels/H1/OHMA", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 3", + "input": { + "label": "H1", + "text": "OHMAeJy1lF+PmkAUxb9Kw/MyGRj+v1HEYhaQ4OxqrI2Z6tSSIBpAk2bjd+8Aau3szCZ9qD4NnN899x7v+Kacad0Uh0rxFB1A5UnZ06YhO8rObytlUxa0aifbleKtlGmU+CvlaXWTjEhL+xc61A0Vuiq0MHQ90/UMBJBjL3vxlrSEqVgxUtTHklS0Ycev7NySokxP+++07qvMMj" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-h1-ohma", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "OHMA Message", + "items": [ + { + "type": "ohma", + "code": "OHMA", + "label": "OHMA Downlink", + "value": "{\"version\":\"2.0\",\"message\":\"{\\\"clientId\\\":\\\"OHMA\\\",\\\"messageDate\\\":\\\"2024-09-06T09:59:43.387Z\\\",\\\"data\\\":{\\\"airplanes\\\":[{\\\"tailNumber\\\":\\\"SP" + } + ] + }, + "raw": { + "ohma": "{\"version\":\"2.0\",\"message\":\"{\\\"clientId\\\":\\\"OHMA\\\",\\\"messageDate\\\":\\\"2024-09-06T09:59:43.387Z\\\",\\\"data\\\":{\\\"airplanes\\\":[{\\\"tailNumber\\\":\\\"SP" + }, + "remaining": {} + } +} diff --git a/corpus/labels/H1/OHMA/sample-004.json b/corpus/labels/H1/OHMA/sample-004.json new file mode 100644 index 0000000..805265c --- /dev/null +++ b/corpus/labels/H1/OHMA/sample-004.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/H1/OHMA", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 4", + "input": { + "label": "H1", + "text": "OHMA " + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-h1-ohma", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "OHMA Message", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "OHMA " + } + } +} diff --git a/corpus/labels/H1/Paren/sample-001.json b/corpus/labels/H1/Paren/sample-001.json new file mode 100644 index 0000000..843def3 --- /dev/null +++ b/corpus/labels/H1/Paren/sample-001.json @@ -0,0 +1,72 @@ +{ + "spec": "labels/H1/Paren", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "H1", + "text": "(POS-KLM296 -3911N07600W/234212 F250\r\nRMK/FUEL 37.0 M0.69)" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-h1-paren", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "KLM296" + }, + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "39.183 N, 76.000 W" + }, + { + "type": "time", + "code": "TIMESTAMP", + "label": "Message Timestamp", + "value": "23:42:12" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "25000 feet" + }, + { + "type": "fuel_on_board", + "code": "FOB", + "label": "Fuel On Board", + "value": "37" + }, + { + "type": "mach", + "code": "MACH", + "label": "Mach Number", + "value": "0.69 mach" + } + ] + }, + "raw": { + "flight_number": "KLM296", + "position": { + "latitude": 39.18333333333333, + "longitude": -76 + }, + "message_timestamp": 85332, + "altitude": 25000, + "fuel_on_board": 37, + "mach": 0.69 + }, + "remaining": { + "text": "RMK" + } + } +} diff --git a/corpus/labels/H1/Paren/sample-002.json b/corpus/labels/H1/Paren/sample-002.json new file mode 100644 index 0000000..7c9577f --- /dev/null +++ b/corpus/labels/H1/Paren/sample-002.json @@ -0,0 +1,23 @@ +{ + "spec": "labels/H1/Paren", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "H1", + "text": "Invalid" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-h1-paren", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Unknown", + "items": [] + }, + "raw": {}, + "remaining": {} + } +} diff --git a/corpus/labels/H1/Paren/sample-003.json b/corpus/labels/H1/Paren/sample-003.json new file mode 100644 index 0000000..abda60e --- /dev/null +++ b/corpus/labels/H1/Paren/sample-003.json @@ -0,0 +1,23 @@ +{ + "spec": "labels/H1/Paren", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 3", + "input": { + "label": "H1", + "text": "(Invalid)" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-h1-paren", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Unknown", + "items": [] + }, + "raw": {}, + "remaining": {} + } +} diff --git a/corpus/labels/H1/WRN/sample-001.json b/corpus/labels/H1/WRN/sample-001.json new file mode 100644 index 0000000..dec1ce7 --- /dev/null +++ b/corpus/labels/H1/WRN/sample-001.json @@ -0,0 +1,35 @@ +{ + "spec": "labels/H1/WRN", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "H1", + "text": "WRN/WN24030400580028000006FUEL L TK PUMP 1 2 LO PR " + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-h1-wrn", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Warning Message", + "items": [ + { + "type": "warning", + "code": "WRN", + "label": "Warning Message", + "value": "FUEL L TK PUMP 1 2 LO PR " + } + ] + }, + "raw": { + "message_timestamp": 1709513880, + "warning_message": "FUEL L TK PUMP 1 2 LO PR " + }, + "remaining": { + "text": "28000006" + } + } +} diff --git a/corpus/labels/H1/WRN/sample-002.json b/corpus/labels/H1/WRN/sample-002.json new file mode 100644 index 0000000..1da1dc3 --- /dev/null +++ b/corpus/labels/H1/WRN/sample-002.json @@ -0,0 +1,35 @@ +{ + "spec": "labels/H1/WRN", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "H1", + "text": "WRN/PNRC12860AA07/WN24030316250034000006NAV ADS-B RPTG 1 FAULT " + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-h1-wrn", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Warning Message", + "items": [ + { + "type": "warning", + "code": "WRN", + "label": "Warning Message", + "value": "NAV ADS-B RPTG 1 FAULT " + } + ] + }, + "raw": { + "message_timestamp": 1709483100, + "warning_message": "NAV ADS-B RPTG 1 FAULT " + }, + "remaining": { + "text": "PNRC12860AA07/34000006" + } + } +} diff --git a/corpus/labels/H1/WRN/sample-003.json b/corpus/labels/H1/WRN/sample-003.json new file mode 100644 index 0000000..480c105 --- /dev/null +++ b/corpus/labels/H1/WRN/sample-003.json @@ -0,0 +1,35 @@ +{ + "spec": "labels/H1/WRN", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 3", + "input": { + "label": "H1", + "text": "WRN/PNRC12860AA07/WN24030322050027000002F/CTL ELAC 1 FAULT " + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-h1-wrn", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Warning Message", + "items": [ + { + "type": "warning", + "code": "WRN", + "label": "Warning Message", + "value": "F/CTL ELAC 1 FAULT " + } + ] + }, + "raw": { + "message_timestamp": 1709503500, + "warning_message": "F/CTL ELAC 1 FAULT " + }, + "remaining": { + "text": "PNRC12860AA07/27000002" + } + } +} diff --git a/corpus/labels/H1/WRN/sample-004.json b/corpus/labels/H1/WRN/sample-004.json new file mode 100644 index 0000000..5805242 --- /dev/null +++ b/corpus/labels/H1/WRN/sample-004.json @@ -0,0 +1,35 @@ +{ + "spec": "labels/H1/WRN", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 4", + "input": { + "label": "H1", + "text": "WRN/PNRC12860AA07/WN24030316580030210006ENG 1 A.ICE " + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-h1-wrn", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Warning Message", + "items": [ + { + "type": "warning", + "code": "WRN", + "label": "Warning Message", + "value": "ENG 1 A.ICE " + } + ] + }, + "raw": { + "message_timestamp": 1709485080, + "warning_message": "ENG 1 A.ICE " + }, + "remaining": { + "text": "PNRC12860AA07/30210006" + } + } +} diff --git a/corpus/labels/H1/WRN/sample-005.json b/corpus/labels/H1/WRN/sample-005.json new file mode 100644 index 0000000..1e0cfc8 --- /dev/null +++ b/corpus/labels/H1/WRN/sample-005.json @@ -0,0 +1,35 @@ +{ + "spec": "labels/H1/WRN", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 5", + "input": { + "label": "H1", + "text": "#CFBWRN/WN24030312040034580006NAV GPS1 FAULT " + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-h1-wrn", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Warning Message", + "items": [ + { + "type": "warning", + "code": "WRN", + "label": "Warning Message", + "value": "NAV GPS1 FAULT " + } + ] + }, + "raw": { + "message_timestamp": 1709467440, + "warning_message": "NAV GPS1 FAULT " + }, + "remaining": { + "text": "34580006" + } + } +} diff --git a/corpus/labels/H1/WRN/sample-006.json b/corpus/labels/H1/WRN/sample-006.json new file mode 100644 index 0000000..ba6b1a6 --- /dev/null +++ b/corpus/labels/H1/WRN/sample-006.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/H1/WRN", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 6", + "input": { + "label": "H1", + "text": "WRN " + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-h1-wrn", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Warning Message", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "WRN " + } + } +} diff --git a/corpus/labels/H2/02E/sample-001.json b/corpus/labels/H2/02E/sample-001.json new file mode 100644 index 0000000..6812ee4 --- /dev/null +++ b/corpus/labels/H2/02E/sample-001.json @@ -0,0 +1,176 @@ +{ + "spec": "labels/H2/02E", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "H2", + "text": "02E20HEGNLKPRN40359E02208116253601M627259020G QN41179E02134316323599M617247037G QN41591E02100516393603M610266040G QN42393E02026716463602M600276033G QN43197E01954316533598M592299037G QN44023E01929517003596M587313033G Q" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-h2-02e", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "Weather Report", + "items": [ + { + "type": "day", + "code": "MSG_DAY", + "label": "Day of Month", + "value": "20" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "HEGN" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "LKPR" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "N40359E022081(40.598 N, 22.135 E)@16:25:00 at FL360: 259° at 20kt, -62.7°C at FL360" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "N41179E021343(41.298 N, 21.572 E)@16:32:00 at FL359: 247° at 37kt, -61.7°C at FL359" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "N41591E021005(41.985 N, 21.008 E)@16:39:00 at FL360: 266° at 40kt, -61°C at FL360" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "N42393E020267(42.655 N, 20.445 E)@16:46:00 at FL360: 276° at 33kt, -60°C at FL360" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "N43197E019543(43.328 N, 19.905 E)@16:53:00 at FL359: 299° at 37kt, -59.2°C at FL359" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "N44023E019295(44.038 N, 19.492 E)@17:00:00 at FL359: 313° at 33kt, -58.7°C at FL359" + } + ] + }, + "raw": { + "day": 20, + "departure_icao": "HEGN", + "arrival_icao": "LKPR", + "wind_data": [ + { + "waypoint": { + "name": "N40359E022081", + "latitude": 40.598333333333336, + "longitude": 22.135, + "time": 59100 + }, + "flightLevel": 360, + "windDirection": 259, + "windSpeed": 20, + "temperature": { + "flightLevel": 360, + "degreesC": -62.7 + } + }, + { + "waypoint": { + "name": "N41179E021343", + "latitude": 41.29833333333333, + "longitude": 21.571666666666665, + "time": 59520 + }, + "flightLevel": 359, + "windDirection": 247, + "windSpeed": 37, + "temperature": { + "flightLevel": 359, + "degreesC": -61.7 + } + }, + { + "waypoint": { + "name": "N41591E021005", + "latitude": 41.985, + "longitude": 21.008333333333333, + "time": 59940 + }, + "flightLevel": 360, + "windDirection": 266, + "windSpeed": 40, + "temperature": { + "flightLevel": 360, + "degreesC": -61 + } + }, + { + "waypoint": { + "name": "N42393E020267", + "latitude": 42.655, + "longitude": 20.445, + "time": 60360 + }, + "flightLevel": 360, + "windDirection": 276, + "windSpeed": 33, + "temperature": { + "flightLevel": 360, + "degreesC": -60 + } + }, + { + "waypoint": { + "name": "N43197E019543", + "latitude": 43.32833333333333, + "longitude": 19.905, + "time": 60780 + }, + "flightLevel": 359, + "windDirection": 299, + "windSpeed": 37, + "temperature": { + "flightLevel": 359, + "degreesC": -59.2 + } + }, + { + "waypoint": { + "name": "N44023E019295", + "latitude": 44.038333333333334, + "longitude": 19.491666666666667, + "time": 61200 + }, + "flightLevel": 359, + "windDirection": 313, + "windSpeed": 33, + "temperature": { + "flightLevel": 359, + "degreesC": -58.7 + } + } + ] + }, + "remaining": { + "text": "" + } + } +} diff --git a/corpus/labels/H2/02E/sample-002.json b/corpus/labels/H2/02E/sample-002.json new file mode 100644 index 0000000..933aee3 --- /dev/null +++ b/corpus/labels/H2/02E/sample-002.json @@ -0,0 +1,155 @@ +{ + "spec": "labels/H2/02E", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "H2", + "text": "02E20EGKKLBSFN45081E01757116493501M577327021G QN44401E01903016563499M575352028G QN44115E02008017033468M550319029G QN43420E02112317103296M525299036G QN43125E02214517172023M277271022G Q" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-h2-02e", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "Weather Report", + "items": [ + { + "type": "day", + "code": "MSG_DAY", + "label": "Day of Month", + "value": "20" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "EGKK" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "LBSF" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "N45081E017571(45.135 N, 17.952 E)@16:49:00 at FL350: 327° at 21kt, -57.7°C at FL350" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "N44401E019030(44.668 N, 19.050 E)@16:56:00 at FL349: 352° at 28kt, -57.5°C at FL349" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "N44115E020080(44.192 N, 20.133 E)@17:03:00 at FL346: 319° at 29kt, -55°C at FL346" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "N43420E021123(43.700 N, 21.205 E)@17:10:00 at FL329: 299° at 36kt, -52.5°C at FL329" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "N43125E022145(43.208 N, 22.242 E)@17:17:00 at FL202: 271° at 22kt, -27.7°C at FL202" + } + ] + }, + "raw": { + "day": 20, + "departure_icao": "EGKK", + "arrival_icao": "LBSF", + "wind_data": [ + { + "waypoint": { + "name": "N45081E017571", + "latitude": 45.135, + "longitude": 17.951666666666668, + "time": 60540 + }, + "flightLevel": 350, + "windDirection": 327, + "windSpeed": 21, + "temperature": { + "flightLevel": 350, + "degreesC": -57.7 + } + }, + { + "waypoint": { + "name": "N44401E019030", + "latitude": 44.66833333333334, + "longitude": 19.05, + "time": 60960 + }, + "flightLevel": 349, + "windDirection": 352, + "windSpeed": 28, + "temperature": { + "flightLevel": 349, + "degreesC": -57.5 + } + }, + { + "waypoint": { + "name": "N44115E020080", + "latitude": 44.19166666666667, + "longitude": 20.133333333333333, + "time": 61380 + }, + "flightLevel": 346, + "windDirection": 319, + "windSpeed": 29, + "temperature": { + "flightLevel": 346, + "degreesC": -55 + } + }, + { + "waypoint": { + "name": "N43420E021123", + "latitude": 43.7, + "longitude": 21.205, + "time": 61800 + }, + "flightLevel": 329, + "windDirection": 299, + "windSpeed": 36, + "temperature": { + "flightLevel": 329, + "degreesC": -52.5 + } + }, + { + "waypoint": { + "name": "N43125E022145", + "latitude": 43.208333333333336, + "longitude": 22.241666666666667, + "time": 62220 + }, + "flightLevel": 202, + "windDirection": 271, + "windSpeed": 22, + "temperature": { + "flightLevel": 202, + "degreesC": -27.7 + } + } + ] + }, + "remaining": { + "text": "" + } + } +} diff --git a/corpus/labels/H2/02E/sample-003.json b/corpus/labels/H2/02E/sample-003.json new file mode 100644 index 0000000..c3fdadb --- /dev/null +++ b/corpus/labels/H2/02E/sample-003.json @@ -0,0 +1,134 @@ +{ + "spec": "labels/H2/02E", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 3", + "input": { + "label": "H2", + "text": "02E20EIDWKORDN44087W08505523383800M470251091G QN43210W08520623452813M442251113G QN42461W08539523522189M295256121G QN42380W08623723591780M227266100G Q" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-h2-02e", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "Weather Report", + "items": [ + { + "type": "day", + "code": "MSG_DAY", + "label": "Day of Month", + "value": "20" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "EIDW" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KORD" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "N44087W085055(44.145 N, 85.092 W)@23:38:00 at FL380: 251° at 91kt, -47°C at FL380" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "N43210W085206(43.350 N, 85.343 W)@23:45:00 at FL281: 251° at 113kt, -44.2°C at FL281" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "N42461W085395(42.768 N, 85.658 W)@23:52:00 at FL218: 256° at 121kt, -29.5°C at FL218" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "N42380W086237(42.633 N, 86.395 W)@23:59:00 at FL178: 266° at 100kt, -22.7°C at FL178" + } + ] + }, + "raw": { + "day": 20, + "departure_icao": "EIDW", + "arrival_icao": "KORD", + "wind_data": [ + { + "waypoint": { + "name": "N44087W085055", + "latitude": 44.145, + "longitude": -85.09166666666667, + "time": 85080 + }, + "flightLevel": 380, + "windDirection": 251, + "windSpeed": 91, + "temperature": { + "flightLevel": 380, + "degreesC": -47 + } + }, + { + "waypoint": { + "name": "N43210W085206", + "latitude": 43.35, + "longitude": -85.34333333333333, + "time": 85500 + }, + "flightLevel": 281, + "windDirection": 251, + "windSpeed": 113, + "temperature": { + "flightLevel": 281, + "degreesC": -44.2 + } + }, + { + "waypoint": { + "name": "N42461W085395", + "latitude": 42.76833333333333, + "longitude": -85.65833333333333, + "time": 85920 + }, + "flightLevel": 218, + "windDirection": 256, + "windSpeed": 121, + "temperature": { + "flightLevel": 218, + "degreesC": -29.5 + } + }, + { + "waypoint": { + "name": "N42380W086237", + "latitude": 42.63333333333333, + "longitude": -86.395, + "time": 86340 + }, + "flightLevel": 178, + "windDirection": 266, + "windSpeed": 100, + "temperature": { + "flightLevel": 178, + "degreesC": -22.7 + } + } + ] + }, + "remaining": { + "text": "" + } + } +} diff --git a/corpus/labels/H2/02E/sample-004.json b/corpus/labels/H2/02E/sample-004.json new file mode 100644 index 0000000..58491e5 --- /dev/null +++ b/corpus/labels/H2/02E/sample-004.json @@ -0,0 +1,25 @@ +{ + "spec": "labels/H2/02E", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 4", + "input": { + "label": "H2", + "text": "02E20INVALID MESSAGE TEXT" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-h2-02e", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Weather Report", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "02E20INVALID MESSAGE TEXT" + } + } +} diff --git a/corpus/labels/HX/sample-001.json b/corpus/labels/HX/sample-001.json new file mode 100644 index 0000000..51f7e22 --- /dev/null +++ b/corpus/labels/HX/sample-001.json @@ -0,0 +1,37 @@ +{ + "spec": "labels/HX", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "HX", + "text": "RA FMT LOCATION N4009.6 W07540.8" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-hx", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "Undelivered Uplink Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "40.160 N, 75.680 W" + } + ] + }, + "raw": { + "position": { + "latitude": 40.16, + "longitude": -75.68 + } + }, + "remaining": { + "text": "" + } + } +} diff --git a/corpus/labels/HX/sample-002.json b/corpus/labels/HX/sample-002.json new file mode 100644 index 0000000..11f1e10 --- /dev/null +++ b/corpus/labels/HX/sample-002.json @@ -0,0 +1,34 @@ +{ + "spec": "labels/HX", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 2", + "input": { + "label": "HX", + "text": "RA FMT 43 GSP B02" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-hx", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Undelivered Uplink Report", + "items": [ + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "GSP" + } + ] + }, + "raw": { + "departure_icao": "GSP" + }, + "remaining": { + "text": "B02" + } + } +} diff --git a/corpus/labels/HX/sample-003.json b/corpus/labels/HX/sample-003.json new file mode 100644 index 0000000..21b8a3a --- /dev/null +++ b/corpus/labels/HX/sample-003.json @@ -0,0 +1,23 @@ +{ + "spec": "labels/HX", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 3", + "input": { + "label": "HX", + "text": "HX Bogus message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "label-hx", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Undelivered Uplink Report", + "items": [] + }, + "raw": {}, + "remaining": {} + } +} diff --git a/corpus/labels/MA/sample-001.json b/corpus/labels/MA/sample-001.json new file mode 100644 index 0000000..552cfd1 --- /dev/null +++ b/corpus/labels/MA/sample-001.json @@ -0,0 +1,55 @@ +{ + "spec": "labels/MA", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 1", + "input": { + "label": "MA", + "text": "T02!<<,:/k.E`;FOV@!'s.16q6R+p(RK,|D2ujNJhRah?_qrNftWiI-V,@*RQs,tn,FYN$/V1!gNIc6CO;$D,1:.4?dF952;>XP$\"B\"Ok-Fr'0^k?rP]3&UGoPX;\\/k&_k6:\"c1!'s.16q0U2odKk@|D,eVtdm+WF(#Ll8Dja==EY8V4KISInKk%((%5&@p?Z\\NSZ9tj[Xr0rXqe`+A(J,f[fotFmHmS" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-ma", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Unknown", + "items": [ + { + "type": "label", + "code": "LABEL", + "label": "Message Label", + "value": "H1" + }, + { + "type": "sublabel", + "code": "SUBLABEL", + "label": "Message Sublabel", + "value": "DF" + }, + { + "type": "tail", + "code": "TAIL", + "label": "Tail", + "value": "ET-BAY" + } + ] + }, + "raw": { + "label": "H1", + "sublabel": "DF", + "tail": "ET-BAY" + }, + "remaining": { + "text": "A350,000243,1,1," + } + } +} diff --git a/corpus/labels/MA/sample-003.json b/corpus/labels/MA/sample-003.json new file mode 100644 index 0000000..3bf783d --- /dev/null +++ b/corpus/labels/MA/sample-003.json @@ -0,0 +1,75 @@ +{ + "spec": "labels/MA", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 3", + "input": { + "label": "MA", + "text": "T20!<<,B/k&,Z:/=Hs!'iLt0/1h8|*KJNa__;Kes\"hV'&4]J&&&;\"X/O6%0d7TMg5=d7'gcZ:j\"2EiA7k`-`$]f/r>q-b^jGP>WX4Dk3IrQ:U4%?]H$4HCd0Z(^Yq+lXXR!=niT!#;:5UAQ`JU@Bfn/*)TSu?G=LuOlLaFSmkGi]eE[!o&j@iX^g0r)T9!Ye)k6aOmSf`8G)8,hS5Fq_trW*!!" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "label-ma", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "Unknown", + "items": [ + { + "type": "label", + "code": "LABEL", + "label": "Message Label", + "value": "80" + }, + { + "type": "tail", + "code": "TAIL", + "label": "Tail", + "value": "EC-NOI" + }, + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "2501" + }, + { + "type": "day", + "code": "MSG_DAY", + "label": "Day of Month", + "value": "2" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "LEMD" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "MMUN" + }, + { + "type": "tail", + "code": "TAIL", + "label": "Tail", + "value": "EC-NOI" + } + ] + }, + "raw": { + "label": "80", + "tail": "EC-NOI", + "flight_number": "2501", + "day": 2, + "departure_icao": "LEMD", + "arrival_icao": "MMUN" + }, + "remaining": { + "text": "3D01 RMPSRV//LAV Y/CAB Y/MEDA N/SEC N/WAS N/WAT N/FUEL N/WCHRR 01/WCHRS --/WCHRC --/UMNR --/MAAS N/BUENAS NOCHES, ETA 0055Z Y NOS HAN PEDIDO UNA WCHR QUE AL PARECER NO TENIAMOS CONSTANCIA DE ELLA, POR SI PODEIS PEDIRLO A OPS CUN. MUCHAS GRACIAS" + } + } +} diff --git a/corpus/labels/MA/sample-004.json b/corpus/labels/MA/sample-004.json new file mode 100644 index 0000000..799558c --- /dev/null +++ b/corpus/labels/MA/sample-004.json @@ -0,0 +1,55 @@ +{ + "spec": "labels/MA", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 4", + "input": { + "label": "MA", + "text": "T02!<<,:/k.E`;FOV@!'s.16q6R+p(RK,|D2ujNJhRah?_qrNftWiI-V,@*RQs,tn,FYN$/V1!gNIc6CO;$D,1:.4?dF952;>XP$\"B\"Ok-Fr'0^k?rP]3&UGoPX;\\> ?" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "35000 feet" + }, + { + "type": "fuel_on_board", + "code": "FOB", + "label": "Fuel On Board", + "value": "857" + }, + { + "type": "version", + "code": "VERSION", + "label": "Message Version", + "value": "v3.2" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0x2b89" + } + ] + }, + "raw": { + "tail": "50007B", + "flight_number": "RCH4086", + "mission_number": "ABB02R70E037", + "message_timestamp": 1739164684, + "sequence_number": 103, + "day": 9, + "eta_time": 27480, + "position": { + "latitude": 56.02, + "longitude": -13.455 + }, + "route": { + "waypoints": [ + { + "name": "", + "time": 19084 + }, + { + "name": "", + "time": 0 + }, + { + "name": "?" + } + ] + }, + "altitude": 35000, + "fuel_on_board": 857, + "version": 3.2, + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 11145 + }, + "remaining": { + "text": "M80/084081," + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-041.json b/corpus/wildcards/arinc_702/sample-041.json new file mode 100644 index 0000000..00261f6 --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-041.json @@ -0,0 +1,25 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 41", + "input": { + "label": "2P", + "text": "M01AFN1234POS Bogus message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Unknown", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "M01/,M01AFN1234POS Bogus message" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-042.json b/corpus/wildcards/arinc_702/sample-042.json new file mode 100644 index 0000000..38f8fa3 --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-042.json @@ -0,0 +1,49 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 42", + "input": { + "label": "H1", + "text": "PER/PR1337,262,320,222,,60,24,275103,M53,180,P52,P02917" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Performance Report", + "items": [ + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "32000 feet" + }, + { + "type": "outside_air_temperature", + "code": "OATEMP", + "label": "Outside Air Temperature (C)", + "value": "-53 degrees" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0x2917" + } + ] + }, + "raw": { + "altitude": 32000, + "outside_air_temperature": -53, + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 10519 + }, + "remaining": { + "text": "1337,262,222,,60,24,275103,180,P52,P0" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-043.json b/corpus/wildcards/arinc_702/sample-043.json new file mode 100644 index 0000000..5ee7185 --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-043.json @@ -0,0 +1,49 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 43", + "input": { + "label": "H1", + "text": "PER/PR1218,276,340,134,,0,68,,M56,180,,,P30,P0,33936,,1084,284388D" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Performance Report", + "items": [ + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "34000 feet" + }, + { + "type": "outside_air_temperature", + "code": "OATEMP", + "label": "Outside Air Temperature (C)", + "value": "-56 degrees" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0x388d" + } + ] + }, + "raw": { + "altitude": 34000, + "outside_air_temperature": -56, + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 14477 + }, + "remaining": { + "text": "1218,276,134,,0,68,,180,,,P30,P0,33936,,1084,284" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-044.json b/corpus/wildcards/arinc_702/sample-044.json new file mode 100644 index 0000000..ac7eb93 --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-044.json @@ -0,0 +1,25 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 44", + "input": { + "label": "H1", + "text": "PER Bogus message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Unknown", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "PER/,PER Bogus message" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-045.json b/corpus/wildcards/arinc_702/sample-045.json new file mode 100644 index 0000000..e9777e9 --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-045.json @@ -0,0 +1,26 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 45", + "input": { + "label": "H1", + "sublabel": "DF", + "text": "A350,000354,1,1,TB000000/REP035,01,02;H01,035,01,02,4000,00066,.F-HREV,3,0,08,12,19,06,58,42,071/H02,KSFO LFPO,FBU711 ,S0285,S0385,S0142S0185/H03, /A10,06,58,40,+26535,+01198,045,+0493,321723,XXXXXX,--------,0423,+0573/A11,06,58,32,+26356,+01221,041,+0502,324326,XXXXXX,--------,0424,+0614/:" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Unknown", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "A35/,A350,000354,1,1,TB000000/REP035,01,02;H01,035,01,02,4000,00066,.F-HREV,3,0,08,12,19,06,58,42,071/H02,KSFO LFPO,FBU711 ,S0285,S0385,S0142S0185/H03, /A10,06,58,40,+26535,+01198,045,+0493,321723,XXXXXX,--------,0423,+0573/A11,06,58,32,+26356,+01221,041,+0502,324326,XXXXXX,--------,0424,+0614/:" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-046.json b/corpus/wildcards/arinc_702/sample-046.json new file mode 100644 index 0000000..553157d --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-046.json @@ -0,0 +1,25 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 46", + "input": { + "label": "80", + "text": "3D01 RMPSRV 2501/02 LEMD/MMUN .EC-NOI\n/LAV Y/CAB Y/MEDA N/SEC N/WAS N/WAT N/FUEL N/WCHRR 01/WCHRS --/WCHRC --/UMNR --/MAAS N\nBUENAS NOCHES, ETA 0055Z Y NOS HAN PEDIDO UNA WCHR QUE AL PARECER NO TENIAMOS CONSTANCIA DE ELLA, POR SI PODEIS PEDIRLO A OPS CUN. MUCHAS GRACIAS" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Unknown", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "3D0/,3D01 RMPSRV 2501/02 LEMD/MMUN .EC-NOI\n/LAV Y/CAB Y/MEDA N/SEC N/WAS N/WAT N/FUEL N/WCHRR 01/WCHRS --/WCHRC --/UMNR --/MAAS N\nBUENAS NOCHES, ETA 0055Z Y NOS HAN PEDIDO UNA WCHR QUE AL PARECER NO TENIAMOS CONSTANCIA DE ELLA, POR SI PODEIS PEDIRLO A OPS CUN. MUCHAS GRACIAS" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-047.json b/corpus/wildcards/arinc_702/sample-047.json new file mode 100644 index 0000000..d9eebdd --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-047.json @@ -0,0 +1,25 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 47", + "input": { + "label": "MA", + "text": "T02!<<,:/k.E`;FOV@!'s.16q6R+p(RK,|D2ujNJhRah?_qrNftWiI-V,@*RQs,tn,FYN$/V1!gNIc6CO;$D,1:.4?dF952;>XP$\"B\"Ok-Fr'0^k?rP]3&UGoPX;\\XP$\"B\"Ok-Fr'0^k?rP]3&UGoPX;\\ EBINY@22:06:01 > ELENN" + }, + { + "type": "outside_air_temperature", + "code": "OATEMP", + "label": "Outside Air Temperature (C)", + "value": "-48 degrees" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0x7a40" + } + ] + }, + "raw": { + "position": { + "latitude": 43.52, + "longitude": -123.29 + }, + "altitude": 37000, + "route": { + "waypoints": [ + { + "name": "EASON", + "time": 79074 + }, + { + "name": "EBINY", + "time": 79561 + }, + { + "name": "ELENN" + } + ] + }, + "outside_air_temperature": -48, + "message_timestamp": 1663797474, + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 31296 + }, + "remaining": { + "text": "02216,185" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-051.json b/corpus/wildcards/arinc_702/sample-051.json new file mode 100644 index 0000000..b20fc02 --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-051.json @@ -0,0 +1,85 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 51", + "input": { + "label": "H1", + "text": "/.POS/TS100316,210324/PSS35333W058220,,100316,250,S37131W059150,101916,S39387W060377,M23,27282,241,780,MANUAL,0,813E711" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "35.555 S, 58.367 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "25000 feet" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "@10:03:16 > (37.218 S, 59.250 W)@10:19:16 > (39.645 S, 60.628 W)" + }, + { + "type": "outside_air_temperature", + "code": "OATEMP", + "label": "Outside Air Temperature (C)", + "value": "-23 degrees" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0xe711" + } + ] + }, + "raw": { + "message_timestamp": 1711015396, + "position": { + "latitude": -35.555, + "longitude": -58.36666666666667 + }, + "altitude": 25000, + "route": { + "waypoints": [ + { + "name": "", + "time": 36196 + }, + { + "name": "", + "latitude": -37.218333333333334, + "longitude": -59.25, + "time": 37156 + }, + { + "name": "", + "latitude": -39.645, + "longitude": -60.62833333333333 + } + ] + }, + "outside_air_temperature": -23, + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 59153 + }, + "remaining": { + "text": "/.27282,241,780,MANUAL,0,813" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-052.json b/corpus/wildcards/arinc_702/sample-052.json new file mode 100644 index 0000000..9ecca72 --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-052.json @@ -0,0 +1,88 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 52", + "input": { + "label": "H1", + "text": "/HDQDLUA.POSN38332W080082,RONZZ,135753,320,LEVII,140454,WISTA,M45,20967,194/GAHDQDLUA/CA/TS135753,1411240721" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "38.553 N, 80.137 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "32000 feet" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "RONZZ@13:57:53 > LEVII@14:04:54 > WISTA" + }, + { + "type": "outside_air_temperature", + "code": "OATEMP", + "label": "Outside Air Temperature (C)", + "value": "-45 degrees" + }, + { + "type": "ground_address", + "code": "GND_ADDR", + "label": "Ground Address", + "value": "HDQDLUA" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0x0721" + } + ] + }, + "raw": { + "position": { + "latitude": 38.553333333333335, + "longitude": -80.13666666666667 + }, + "altitude": 32000, + "route": { + "waypoints": [ + { + "name": "RONZZ", + "time": 50273 + }, + { + "name": "LEVII", + "time": 50694 + }, + { + "name": "WISTA" + } + ] + }, + "outside_air_temperature": -45, + "ground_address": "HDQDLUA", + "message_timestamp": 1731592673, + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 1825 + }, + "remaining": { + "text": "/HDQDLUA.20967,194/CA" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-053.json b/corpus/wildcards/arinc_702/sample-053.json new file mode 100644 index 0000000..8cd4a10 --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-053.json @@ -0,0 +1,81 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 53", + "input": { + "label": "H1", + "text": "/.POS/TS140122,141124N38321W078003,,140122,450,,140122,,M56,24739,127,8306763" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "38.535 N, 78.005 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "45000 feet" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "@14:01:22 > @14:01:22 > ?" + }, + { + "type": "outside_air_temperature", + "code": "OATEMP", + "label": "Outside Air Temperature (C)", + "value": "-56 degrees" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0x6763" + } + ] + }, + "raw": { + "position": { + "latitude": 38.535, + "longitude": -78.005 + }, + "altitude": 45000, + "route": { + "waypoints": [ + { + "name": "", + "time": 50482 + }, + { + "name": "", + "time": 50482 + }, + { + "name": "?" + } + ] + }, + "outside_air_temperature": -56, + "message_timestamp": 1731592882, + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 26467 + }, + "remaining": { + "text": "/.24739,127" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-054.json b/corpus/wildcards/arinc_702/sample-054.json new file mode 100644 index 0000000..9e1eeb9 --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-054.json @@ -0,0 +1,25 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 54", + "input": { + "label": "H1", + "text": "/.POS Bogus message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Unknown", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "/.POS Bogus message" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-055.json b/corpus/wildcards/arinc_702/sample-055.json new file mode 100644 index 0000000..f28b580 --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-055.json @@ -0,0 +1,81 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 55", + "input": { + "label": "H1", + "text": "POSN43312W123174,EASON,215754,370,EBINY,220601,ELENN,M48,02216,185/TS215754,0921227A40" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "43.520 N, 123.290 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "37000 feet" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "EASON@21:57:54 > EBINY@22:06:01 > ELENN" + }, + { + "type": "outside_air_temperature", + "code": "OATEMP", + "label": "Outside Air Temperature (C)", + "value": "-48 degrees" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0x7a40" + } + ] + }, + "raw": { + "position": { + "latitude": 43.52, + "longitude": -123.29 + }, + "altitude": 37000, + "route": { + "waypoints": [ + { + "name": "EASON", + "time": 79074 + }, + { + "name": "EBINY", + "time": 79561 + }, + { + "name": "ELENN" + } + ] + }, + "outside_air_temperature": -48, + "message_timestamp": 1663797474, + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 31296 + }, + "remaining": { + "text": "02216,185" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-056.json b/corpus/wildcards/arinc_702/sample-056.json new file mode 100644 index 0000000..ee474d0 --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-056.json @@ -0,0 +1,80 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 56", + "input": { + "label": "H1", + "text": "POSN45209W122550,PEGTY,220309,134,MINNE,220424,HISKU,M6,060013,269,366,355K,292K,730A5B" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "45.348 N, 122.917 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "13400 feet" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "PEGTY@22:03:09 > MINNE@22:04:24 > HISKU" + }, + { + "type": "outside_air_temperature", + "code": "OATEMP", + "label": "Outside Air Temperature (C)", + "value": "-6 degrees" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0x0a5b" + } + ] + }, + "raw": { + "position": { + "latitude": 45.348333333333336, + "longitude": -122.91666666666667 + }, + "altitude": 13400, + "route": { + "waypoints": [ + { + "name": "PEGTY", + "time": 79389 + }, + { + "name": "MINNE", + "time": 79464 + }, + { + "name": "HISKU" + } + ] + }, + "outside_air_temperature": -6, + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 2651 + }, + "remaining": { + "text": "060013,269,366,355K,292K,73" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-057.json b/corpus/wildcards/arinc_702/sample-057.json new file mode 100644 index 0000000..f81f792 --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-057.json @@ -0,0 +1,81 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 57", + "input": { + "label": "H1", + "text": "POSN43030W122406,IBALL,220516,380,AARON,220816,MOXEE,M47,0047,86/TS220516,092122BF64" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "43.050 N, 122.677 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "38000 feet" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "IBALL@22:05:16 > AARON@22:08:16 > MOXEE" + }, + { + "type": "outside_air_temperature", + "code": "OATEMP", + "label": "Outside Air Temperature (C)", + "value": "-47 degrees" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0xbf64" + } + ] + }, + "raw": { + "position": { + "latitude": 43.05, + "longitude": -122.67666666666666 + }, + "altitude": 38000, + "route": { + "waypoints": [ + { + "name": "IBALL", + "time": 79516 + }, + { + "name": "AARON", + "time": 79696 + }, + { + "name": "MOXEE" + } + ] + }, + "outside_air_temperature": -47, + "message_timestamp": 1663797916, + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 48996 + }, + "remaining": { + "text": "0047,86" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-058.json b/corpus/wildcards/arinc_702/sample-058.json new file mode 100644 index 0000000..89aeae9 --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-058.json @@ -0,0 +1,80 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 58", + "input": { + "label": "H1", + "text": "POSN33225W079428,SCOOB,232933,340,ENEME,235712,FETAL,M42,003051,15857F6" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "33.375 N, 79.713 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "34000 feet" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "SCOOB@23:29:33 > ENEME@23:57:12 > FETAL" + }, + { + "type": "outside_air_temperature", + "code": "OATEMP", + "label": "Outside Air Temperature (C)", + "value": "-42 degrees" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0x57f6" + } + ] + }, + "raw": { + "position": { + "latitude": 33.375, + "longitude": -79.71333333333334 + }, + "altitude": 34000, + "route": { + "waypoints": [ + { + "name": "SCOOB", + "time": 84573 + }, + { + "name": "ENEME", + "time": 86232 + }, + { + "name": "FETAL" + } + ] + }, + "outside_air_temperature": -42, + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 22518 + }, + "remaining": { + "text": "003051,158" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-059.json b/corpus/wildcards/arinc_702/sample-059.json new file mode 100644 index 0000000..84de3c1 --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-059.json @@ -0,0 +1,80 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 59", + "input": { + "label": "H1", + "text": "POSN38531W078000,CSN-01,112309,310,CYN-02,114151,ACK,M40,26067,22479226" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "38.885 N, 78.000 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "31000 feet" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "CSN-01@11:23:09 > CYN-02@11:41:51 > ACK" + }, + { + "type": "outside_air_temperature", + "code": "OATEMP", + "label": "Outside Air Temperature (C)", + "value": "-40 degrees" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0x9226" + } + ] + }, + "raw": { + "position": { + "latitude": 38.885, + "longitude": -78 + }, + "altitude": 31000, + "route": { + "waypoints": [ + { + "name": "CSN-01", + "time": 40989 + }, + { + "name": "CYN-02", + "time": 42111 + }, + { + "name": "ACK" + } + ] + }, + "outside_air_temperature": -40, + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 37414 + }, + "remaining": { + "text": "26067,2247" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-060.json b/corpus/wildcards/arinc_702/sample-060.json new file mode 100644 index 0000000..4398943 --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-060.json @@ -0,0 +1,109 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 60", + "input": { + "label": "H1", + "text": "POS/RFSCOOB.KEMPR.ECG.OHPEA.TOMMZ.OXANA.ZZTOP.OMALA.WILYY.KANUX.GALVN.KASAR.LNHOM.SLUKA.FIPEK.PUYYA.PLING.KOLAO.JETSSF2FC" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "status", + "code": "ROUTE_STATUS", + "label": "Route Status", + "value": "Route Filed" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "SCOOB > KEMPR > ECG > OHPEA > TOMMZ > OXANA > ZZTOP > OMALA > WILYY > KANUX > GALVN > KASAR > LNHOM > SLUKA > FIPEK > PUYYA > PLING > KOLAO > JETSS" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0xf2fc" + } + ] + }, + "raw": { + "route_status": "RF", + "route": { + "waypoints": [ + { + "name": "SCOOB" + }, + { + "name": "KEMPR" + }, + { + "name": "ECG" + }, + { + "name": "OHPEA" + }, + { + "name": "TOMMZ" + }, + { + "name": "OXANA" + }, + { + "name": "ZZTOP" + }, + { + "name": "OMALA" + }, + { + "name": "WILYY" + }, + { + "name": "KANUX" + }, + { + "name": "GALVN" + }, + { + "name": "KASAR" + }, + { + "name": "LNHOM" + }, + { + "name": "SLUKA" + }, + { + "name": "FIPEK" + }, + { + "name": "PUYYA" + }, + { + "name": "PLING" + }, + { + "name": "KOLAO" + }, + { + "name": "JETSS" + } + ] + }, + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 62204 + }, + "remaining": { + "text": "" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-061.json b/corpus/wildcards/arinc_702/sample-061.json new file mode 100644 index 0000000..84a25f4 --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-061.json @@ -0,0 +1,25 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 61", + "input": { + "label": "H1", + "text": "POS Bogus message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Unknown", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "POS/,POS Bogus message" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-062.json b/corpus/wildcards/arinc_702/sample-062.json new file mode 100644 index 0000000..2a60004 --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-062.json @@ -0,0 +1,80 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 62", + "input": { + "label": "H1", + "text": "#M1BPOSN37533W096476,ROKNE,185212,330,DOSOA,190059,BUM,M50,272100,1571541" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "37.888 N, 96.793 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "33000 feet" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "ROKNE@18:52:12 > DOSOA@19:00:59 > BUM" + }, + { + "type": "outside_air_temperature", + "code": "OATEMP", + "label": "Outside Air Temperature (C)", + "value": "-50 degrees" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0x1541" + } + ] + }, + "raw": { + "position": { + "latitude": 37.888333333333335, + "longitude": -96.79333333333334 + }, + "altitude": 33000, + "route": { + "waypoints": [ + { + "name": "ROKNE", + "time": 67932 + }, + { + "name": "DOSOA", + "time": 68459 + }, + { + "name": "BUM" + } + ] + }, + "outside_air_temperature": -50, + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 5441 + }, + "remaining": { + "text": "272100,157" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-063.json b/corpus/wildcards/arinc_702/sample-063.json new file mode 100644 index 0000000..d3f6671 --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-063.json @@ -0,0 +1,152 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 63", + "input": { + "label": "H1", + "text": "F37AMCLL93#M1BPOS/ID746026,,/DC03032024,173207/MR1,/ET031846/PSN42579W108090,173207,320,WAIDE,031759,WEDAK,M49,267070,T468/CG264,110,360/FB742/VR324E17" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "tail", + "code": "TAIL", + "label": "Tail", + "value": "746026" + }, + { + "type": "sequence", + "code": "SEQ", + "label": "Sequence Number", + "value": 1 + }, + { + "type": "day", + "code": "MSG_DAY", + "label": "Day of Month", + "value": "3" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "18:46:00" + }, + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "42.965 N, 108.150 W" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "WAIDE@17:32:07 > WEDAK@03:17:59 > ?" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "32000 feet" + }, + { + "type": "outside_air_temperature", + "code": "OATEMP", + "label": "Outside Air Temperature (C)", + "value": "-49 degrees" + }, + { + "type": "center_of_gravity", + "code": "CG", + "label": "Center of Gravity", + "value": "26.4 %" + }, + { + "type": "cg_lower_limit", + "code": "CG_LOWER", + "label": "Center of Gravity Lower Limit", + "value": "11 %" + }, + { + "type": "cg_upper_limit", + "code": "CG_UPPER", + "label": "Center of Gravity Upper Limit", + "value": "36 %" + }, + { + "type": "fuel_on_board", + "code": "FOB", + "label": "Fuel On Board", + "value": "742" + }, + { + "type": "version", + "code": "VERSION", + "label": "Message Version", + "value": "v3.2" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0x4e17" + }, + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "MCLL93" + } + ] + }, + "raw": { + "tail": "746026", + "mission_number": "", + "message_timestamp": 1709487127, + "sequence_number": 1, + "day": 3, + "eta_time": 67560, + "position": { + "latitude": 42.965, + "longitude": -108.15 + }, + "route": { + "waypoints": [ + { + "name": "WAIDE", + "time": 63127 + }, + { + "name": "WEDAK", + "time": 11879 + }, + { + "name": "?" + } + ] + }, + "altitude": 32000, + "outside_air_temperature": -49, + "center_of_gravity": 26.4, + "cg_lower_limit": 11, + "cg_upper_limit": 36, + "fuel_on_board": 742, + "version": 3.2, + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 19991, + "flight_number": "MCLL93" + }, + "remaining": { + "text": "F37A#M1B/267070,T468" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-064.json b/corpus/wildcards/arinc_702/sample-064.json new file mode 100644 index 0000000..39496f3 --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-064.json @@ -0,0 +1,165 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 64", + "input": { + "label": "H1", + "text": "POS/TS080616,210324/DTMMGL,29O,64,103316/PR1754,231,350,189,,0,0,,M45,185,,,P16,P0,36000,,1565,250/RP:DA:MMTJ:AA:MMGL:R:27O:D:TUMA2B..SANFE.UT4..LMM:A:LONV1D:AP:ILSZ29.PLADE(29O)9D1C" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "MMGL" + }, + { + "type": "runway", + "code": "ARWY", + "label": "Arrival Runway", + "value": "29O" + }, + { + "type": "fuel_on_board", + "code": "FOB", + "label": "Fuel On Board", + "value": "64" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "10:33:16" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "35000 feet" + }, + { + "type": "outside_air_temperature", + "code": "OATEMP", + "label": "Outside Air Temperature (C)", + "value": "-45 degrees" + }, + { + "type": "status", + "code": "ROUTE_STATUS", + "label": "Route Status", + "value": "Route Planned" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "MMTJ" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "MMGL" + }, + { + "type": "runway", + "code": "DEPRWY", + "label": "Departure Runway", + "value": "27O" + }, + { + "type": "procedure", + "code": "proc", + "label": "Departure Procedure", + "value": "TUMA2B: >> SANFE > UT4 >> LMM" + }, + { + "type": "procedure", + "code": "proc", + "label": "Arrival Procedure", + "value": "LONV1D" + }, + { + "type": "procedure", + "code": "proc", + "label": "Approach Procedure", + "value": "ILSZ29 starting at PLADE(29O)" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0x9d1c" + } + ] + }, + "raw": { + "message_timestamp": 1711008376, + "arrival_icao": "MMGL", + "arrival_runway": "29O", + "fuel_on_board": 64, + "eta_time": 37996, + "altitude": 35000, + "outside_air_temperature": -45, + "route_status": "RP", + "departure_icao": "MMTJ", + "departure_runway": "27O", + "procedures": [ + { + "type": "departure", + "route": { + "name": "TUMA2B", + "waypoints": [ + { + "name": "" + }, + { + "name": "SANFE" + }, + { + "name": "UT4" + }, + { + "name": "" + }, + { + "name": "LMM" + } + ] + } + }, + { + "type": "arrival", + "route": { + "name": "LONV1D" + } + }, + { + "type": "approach", + "route": { + "name": "ILSZ29", + "waypoints": [ + { + "name": "PLADE(29O)" + } + ] + } + } + ], + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 40220 + }, + "remaining": { + "text": "1754,231,189,,0,0,,185,,,P16,P0,36000,,1565,250" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-065.json b/corpus/wildcards/arinc_702/sample-065.json new file mode 100644 index 0000000..fa37e21 --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-065.json @@ -0,0 +1,92 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 65", + "input": { + "label": "H1", + "text": "POSN33204W114082,BLH176-0093,062056,330,SALOM180-0127,062211,KOFFA180-0148,M49,30628,3201251" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "33.340 N, 114.137 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "33000 feet" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "BLH[176° 9.3nm]@06:20:56 > SALOM[180° 12.7nm]@06:22:11 > KOFFA[180° 14.8nm]" + }, + { + "type": "outside_air_temperature", + "code": "OATEMP", + "label": "Outside Air Temperature (C)", + "value": "-49 degrees" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0x1251" + } + ] + }, + "raw": { + "position": { + "latitude": 33.34, + "longitude": -114.13666666666667 + }, + "altitude": 33000, + "route": { + "waypoints": [ + { + "name": "BLH", + "offset": { + "bearing": 176, + "distance": 9.3 + }, + "time": 22856 + }, + { + "name": "SALOM", + "offset": { + "bearing": 180, + "distance": 12.7 + }, + "time": 22931 + }, + { + "name": "KOFFA", + "offset": { + "bearing": 180, + "distance": 14.8 + } + } + ] + }, + "outside_air_temperature": -49, + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 4689 + }, + "remaining": { + "text": "30628,320" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-066.json b/corpus/wildcards/arinc_702/sample-066.json new file mode 100644 index 0000000..b6bc7fc --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-066.json @@ -0,0 +1,81 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 66", + "input": { + "label": "H1", + "text": "POSN39164W077259,FORKL,231828,51,THRET,231917,TOOPR,P16,1726,167,/TS231828,2807244841" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "39.273 N, 77.432 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "5100 feet" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "FORKL@23:18:28 > THRET@23:19:17 > TOOPR" + }, + { + "type": "outside_air_temperature", + "code": "OATEMP", + "label": "Outside Air Temperature (C)", + "value": "16 degrees" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0x4841" + } + ] + }, + "raw": { + "position": { + "latitude": 39.27333333333333, + "longitude": -77.43166666666667 + }, + "altitude": 5100, + "route": { + "waypoints": [ + { + "name": "FORKL", + "time": 83908 + }, + { + "name": "THRET", + "time": 83957 + }, + { + "name": "TOOPR" + } + ] + }, + "outside_air_temperature": 16, + "message_timestamp": 1722208708, + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 18497 + }, + "remaining": { + "text": "1726,167" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-067.json b/corpus/wildcards/arinc_702/sample-067.json new file mode 100644 index 0000000..3710880 --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-067.json @@ -0,0 +1,141 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 67", + "input": { + "label": "H1", + "text": "POSN51194E004527,EH556,104241,174,CIV,105208,MEDIL,M20,290016,191/RP:DA:EHEH:AA:LEIB..CIV.N872.MEDIL.UN872.KOVIN.UM728.RESMI.UN857.DISAK..DIRMO..ETAMO..ADEKA..MOKDI..MEN..BADAM..KANIG..KENAS.N855.POS/PR1496,150,370,191,,55,10,248028,M47,30,P19,P0/FHCIV,105208,273K,3226,175,M41,252027,450,N,221,62.MEDIL,105411,267K,3439,172,M44,250028,459,N,203,15.PITHI,105533,259K,3584,170,M47,249028,456,N,203,10.LESDO,105859,252K,3700,167,M47,248028,456,N,203,25.KOVIN,110153,252K,3700,164,M47,248028,456,N,203,21.DUCRA,110705,252K,3700,160,M47,248028,456,N,213,37.RESMI,111101,251K,3700,156,M47,248028,455,N,213,28.DEKOD,111325,251K,3700,154,M47,248028,455,N,192,17.DISAK,111438,251K,3700,153,M47,248028,454,N,172,9.DIRMO,112306,251K,3700,145,M47,248028,454,N,178,63.ETAMO,112514,250K,3700,143,M47,248028,453,N,158,16.ADEKA,113339,250K,3700,136,M47,248028,454,N,147,64.MOKDI,114139,251K,3700,129,M47,248028,454,N,181,59.MEN,114429,251K,3700,127,M47,248028,454,N,181,21.BADAM,114843,251K,3700,123,M47,248028,454,N,179,31.KANIG,120154,250K,3700,111,M47,248028,453,N,185,97.KENAS,121800,250K,3700,98,M47,248028,453,N,177,119.POS,122257,250K,3018,96,M45,248023,395,N,182,34.LEIB,124503,150K,2,89,P15,000000,161,N,231,103/DTLEIB,,89,124503,7353B2" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "51.323 N, 4.878 E" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "17400 feet" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "EH556@10:42:41 > CIV@10:52:08 > MEDIL" + }, + { + "type": "outside_air_temperature", + "code": "OATEMP", + "label": "Outside Air Temperature (C)", + "value": "-20 degrees" + }, + { + "type": "status", + "code": "ROUTE_STATUS", + "label": "Route Status", + "value": "Route Planned" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "EHEH" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "LEIB..CIV.N872.MEDIL.UN872.KOVIN.UM728.RESMI.UN857.DISAK..DIRMO..ETAMO..ADEKA..MOKDI..MEN..BADAM..KANIG..KENAS.N855.POS" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "37000 feet" + }, + { + "type": "outside_air_temperature", + "code": "OATEMP", + "label": "Outside Air Temperature (C)", + "value": "-47 degrees" + }, + { + "type": "runway", + "code": "ARWY", + "label": "Arrival Runway", + "value": "" + }, + { + "type": "fuel_on_board", + "code": "FOB", + "label": "Fuel On Board", + "value": "89" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "12:45:03" + }, + { + "type": "fuel_remaining", + "code": " FUEL_REM", + "label": "Fuel Remaining", + "value": "73" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0x53b2" + } + ] + }, + "raw": { + "position": { + "latitude": 51.32333333333333, + "longitude": 4.878333333333334 + }, + "altitude": 37000, + "route": { + "waypoints": [ + { + "name": "EH556", + "time": 38561 + }, + { + "name": "CIV", + "time": 39128 + }, + { + "name": "MEDIL" + } + ] + }, + "outside_air_temperature": -47, + "route_status": "RP", + "departure_icao": "EHEH", + "arrival_icao": "LEIB..CIV.N872.MEDIL.UN872.KOVIN.UM728.RESMI.UN857.DISAK..DIRMO..ETAMO..ADEKA..MOKDI..MEN..BADAM..KANIG..KENAS.N855.POS", + "arrival_runway": "", + "fuel_on_board": 89, + "eta_time": 45903, + "fuel_remaining": 73, + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 21426 + }, + "remaining": { + "text": "290016,191/PR1496,150,191,,55,10,248028,30,P19,P0/FHCIV,105208,273K,3226,175,M41,252027,450,N,221,62.MEDIL,105411,267K,3439,172,M44,250028,459,N,203,15.PITHI,105533,259K,3584,170,M47,249028,456,N,203,10.LESDO,105859,252K,3700,167,M47,248028,456,N,203,25.KOVIN,110153,252K,3700,164,M47,248028,456,N,203,21.DUCRA,110705,252K,3700,160,M47,248028,456,N,213,37.RESMI,111101,251K,3700,156,M47,248028,455,N,213,28.DEKOD,111325,251K,3700,154,M47,248028,455,N,192,17.DISAK,111438,251K,3700,153,M47,248028,454,N,172,9.DIRMO,112306,251K,3700,145,M47,248028,454,N,178,63.ETAMO,112514,250K,3700,143,M47,248028,453,N,158,16.ADEKA,113339,250K,3700,136,M47,248028,454,N,147,64.MOKDI,114139,251K,3700,129,M47,248028,454,N,181,59.MEN,114429,251K,3700,127,M47,248028,454,N,181,21.BADAM,114843,251K,3700,123,M47,248028,454,N,179,31.KANIG,120154,250K,3700,111,M47,248028,453,N,185,97.KENAS,121800,250K,3700,98,M47,248028,453,N,177,119.POS,122257,250K,3018,96,M45,248023,395,N,182,34.LEIB,124503,150K,2,89,P15,000000,161,N,231,103" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-068.json b/corpus/wildcards/arinc_702/sample-068.json new file mode 100644 index 0000000..a836fc9 --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-068.json @@ -0,0 +1,85 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 68", + "input": { + "label": "H1", + "text": "/.POS/TS100316,210324/PSS35333W058220,,100316,250,S37131W059150,101916,S39387W060377,M23,27282,241,780,MANUAL,0,813E711" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "35.555 S, 58.367 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "25000 feet" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "@10:03:16 > (37.218 S, 59.250 W)@10:19:16 > (39.645 S, 60.628 W)" + }, + { + "type": "outside_air_temperature", + "code": "OATEMP", + "label": "Outside Air Temperature (C)", + "value": "-23 degrees" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0xe711" + } + ] + }, + "raw": { + "message_timestamp": 1711015396, + "position": { + "latitude": -35.555, + "longitude": -58.36666666666667 + }, + "altitude": 25000, + "route": { + "waypoints": [ + { + "name": "", + "time": 36196 + }, + { + "name": "", + "latitude": -37.218333333333334, + "longitude": -59.25, + "time": 37156 + }, + { + "name": "", + "latitude": -39.645, + "longitude": -60.62833333333333 + } + ] + }, + "outside_air_temperature": -23, + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 59153 + }, + "remaining": { + "text": "/.27282,241,780,MANUAL,0,813" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-069.json b/corpus/wildcards/arinc_702/sample-069.json new file mode 100644 index 0000000..fdfc92a --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-069.json @@ -0,0 +1,104 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 69", + "input": { + "label": "H1", + "text": "POSN39220W078258,MRB18,044034,340,EYT19,044427,COL20,M49,27369,436,813/PSN39220W078258,MRB18,044034,340,EYT19,044427,COL20,M49,27369,436,813,ECON CRZ,0,25140035" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "39.367 N, 78.430 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "34000 feet" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "MRB18@04:40:34 > EYT19@04:44:27 > COL20" + }, + { + "type": "outside_air_temperature", + "code": "OATEMP", + "label": "Outside Air Temperature (C)", + "value": "-49 degrees" + }, + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "39.367 N, 78.430 W" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "34000 feet" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "MRB18@04:40:34 > EYT19@04:44:27 > COL20" + }, + { + "type": "outside_air_temperature", + "code": "OATEMP", + "label": "Outside Air Temperature (C)", + "value": "-49 degrees" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0x0035" + } + ] + }, + "raw": { + "position": { + "latitude": 39.36666666666667, + "longitude": -78.43 + }, + "altitude": 34000, + "route": { + "waypoints": [ + { + "name": "MRB18", + "time": 16834 + }, + { + "name": "EYT19", + "time": 17067 + }, + { + "name": "COL20" + } + ] + }, + "outside_air_temperature": -49, + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 53 + }, + "remaining": { + "text": "27369,436,27369,436,813,ECON CRZ,0,2514" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-070.json b/corpus/wildcards/arinc_702/sample-070.json new file mode 100644 index 0000000..c72030f --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-070.json @@ -0,0 +1,25 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 70", + "input": { + "label": "H1", + "text": "#M1BPOS Bogus message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Unknown", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "#M1/,#M1BPOS Bogus message" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-071.json b/corpus/wildcards/arinc_702/sample-071.json new file mode 100644 index 0000000..6035199 --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-071.json @@ -0,0 +1,79 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 71", + "input": { + "label": "80", + "text": "INR/ID91511S,,/DC04032026,143534/MR19,/NR,,,,,,,,950,0/ET041505/FB983/VR32BF4C" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "In-Range Report", + "items": [ + { + "type": "tail", + "code": "TAIL", + "label": "Tail", + "value": "91511S" + }, + { + "type": "sequence", + "code": "SEQ", + "label": "Sequence Number", + "value": 19 + }, + { + "type": "day", + "code": "MSG_DAY", + "label": "Day of Month", + "value": "4" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "15:05:00" + }, + { + "type": "fuel_on_board", + "code": "FOB", + "label": "Fuel On Board", + "value": "983" + }, + { + "type": "version", + "code": "VERSION", + "label": "Message Version", + "value": "v3.2" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0xbf4c" + } + ] + }, + "raw": { + "tail": "91511S", + "mission_number": "", + "message_timestamp": 1772634934, + "sequence_number": 19, + "day": 4, + "eta_time": 54300, + "fuel_on_board": 983, + "version": 3.2, + "checksum_algorithm": "GENIBUS", + "checksum": 48972 + }, + "remaining": { + "text": "NR,,,,,,,,950,0" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-072.json b/corpus/wildcards/arinc_702/sample-072.json new file mode 100644 index 0000000..b1b2fa9 --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-072.json @@ -0,0 +1,153 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 72", + "input": { + "label": "H1", + "text": "FPN/RI:DA:KEWR:AA:KDFW:CR:EWRDFW01(17L)..SAAME.J6.HVQ.Q68.LITTR..MEEOW..FEWWW:A:SEEVR4.FEWWW:F:VECTOR..DISCO..RIVET:AP:ILS 17L.RIVET:F:TACKEC8B5" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "Flight Plan", + "items": [ + { + "type": "status", + "code": "ROUTE_STATUS", + "label": "Route Status", + "value": "Route Inactive" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "KEWR" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KDFW" + }, + { + "type": "company_route", + "code": "CR", + "label": "Company Route", + "value": "EWRDFW01(17L): >> SAAME > J6 > HVQ > Q68 > LITTR >> MEEOW >> FEWWW" + }, + { + "type": "procedure", + "code": "proc", + "label": "Arrival Procedure", + "value": "SEEVR4 starting at FEWWW" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "VECTOR >> DISCO >> RIVET" + }, + { + "type": "procedure", + "code": "proc", + "label": "Approach Procedure", + "value": "ILS 17L starting at RIVET" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "TACKE" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0xc8b5" + } + ] + }, + "raw": { + "route_status": "RI", + "departure_icao": "KEWR", + "arrival_icao": "KDFW", + "company_route": { + "name": "EWRDFW01", + "runway": "17L", + "waypoints": [ + { + "name": "" + }, + { + "name": "SAAME" + }, + { + "name": "J6" + }, + { + "name": "HVQ" + }, + { + "name": "Q68" + }, + { + "name": "LITTR" + }, + { + "name": "" + }, + { + "name": "MEEOW" + }, + { + "name": "" + }, + { + "name": "FEWWW" + } + ] + }, + "procedures": [ + { + "type": "arrival", + "route": { + "name": "SEEVR4", + "waypoints": [ + { + "name": "FEWWW" + } + ] + } + }, + { + "type": "approach", + "route": { + "name": "ILS 17L", + "waypoints": [ + { + "name": "RIVET" + } + ] + } + } + ], + "route": { + "waypoints": [ + { + "name": "TACKE" + } + ] + }, + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 51381 + }, + "remaining": { + "text": "" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-073.json b/corpus/wildcards/arinc_702/sample-073.json new file mode 100644 index 0000000..29b7165 --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-073.json @@ -0,0 +1,195 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 73", + "input": { + "label": "H1", + "text": "FPN/FNAAL1956/RP:DA:KPHL:AA:KPHX:CR:PHLPHX61:R:27L(26O):D:PHL3:A:EAGUL6.ZUN:AP:ILS26..AIR,N40010W080490.J110.BOWRR..VLA,N39056W089097..STL,N38516W090289..GIBSN,N38430W092244..TYGER,N38410W094050..GCK,N37551W100435..DIXAN,N36169W105573..ZUN,N34579W109093293B" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "Flight Plan", + "items": [ + { + "type": "status", + "code": "ROUTE_STATUS", + "label": "Route Status", + "value": "Route Planned" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "KPHL" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KPHX" + }, + { + "type": "company_route", + "code": "CR", + "label": "Company Route", + "value": "PHLPHX61" + }, + { + "type": "runway", + "code": "ARWY", + "label": "Arrival Runway", + "value": "26O" + }, + { + "type": "runway", + "code": "DEPRWY", + "label": "Departure Runway", + "value": "27L" + }, + { + "type": "procedure", + "code": "proc", + "label": "Departure Procedure", + "value": "PHL3" + }, + { + "type": "procedure", + "code": "proc", + "label": "Arrival Procedure", + "value": "EAGUL6 starting at ZUN" + }, + { + "type": "procedure", + "code": "proc", + "label": "Approach Procedure", + "value": "ILS26: >> AIR(40.017 N, 80.817 W) > J110 > BOWRR >> VLA(39.093 N, 89.162 W) >> STL(38.860 N, 90.482 W) >> GIBSN(38.717 N, 92.407 W) >> TYGER(38.683 N, 94.083 W) >> GCK(37.918 N, 100.725 W) >> DIXAN(36.282 N, 105.955 W) >> ZUN(34.965 N, 109.155 W)" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0x293b" + } + ] + }, + "raw": { + "flight_number": "AAL1956", + "route_status": "RP", + "departure_icao": "KPHL", + "arrival_icao": "KPHX", + "company_route": { + "name": "PHLPHX61" + }, + "arrival_runway": "26O", + "departure_runway": "27L", + "procedures": [ + { + "type": "departure", + "route": { + "name": "PHL3" + } + }, + { + "type": "arrival", + "route": { + "name": "EAGUL6", + "waypoints": [ + { + "name": "ZUN" + } + ] + } + }, + { + "type": "approach", + "route": { + "name": "ILS26", + "waypoints": [ + { + "name": "" + }, + { + "name": "AIR", + "latitude": 40.016666666666666, + "longitude": -80.81666666666666 + }, + { + "name": "J110" + }, + { + "name": "BOWRR" + }, + { + "name": "" + }, + { + "name": "VLA", + "latitude": 39.093333333333334, + "longitude": -89.16166666666666 + }, + { + "name": "" + }, + { + "name": "STL", + "latitude": 38.86, + "longitude": -90.48166666666667 + }, + { + "name": "" + }, + { + "name": "GIBSN", + "latitude": 38.71666666666667, + "longitude": -92.40666666666667 + }, + { + "name": "" + }, + { + "name": "TYGER", + "latitude": 38.68333333333333, + "longitude": -94.08333333333333 + }, + { + "name": "" + }, + { + "name": "GCK", + "latitude": 37.91833333333334, + "longitude": -100.725 + }, + { + "name": "" + }, + { + "name": "DIXAN", + "latitude": 36.281666666666666, + "longitude": -105.955 + }, + { + "name": "" + }, + { + "name": "ZUN", + "latitude": 34.965, + "longitude": -109.155 + } + ] + } + } + ], + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 10555 + }, + "remaining": { + "text": "" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-074.json b/corpus/wildcards/arinc_702/sample-074.json new file mode 100644 index 0000000..fd1c4eb --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-074.json @@ -0,0 +1,96 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 74", + "input": { + "label": "H1", + "text": "FPN/FNUAL1187/RP:DA:KSFO:AA:KPHX:F:KAYEX,N36292W120569..LOSHN,N35509W120000..BOILE,N34253W118016..BLH,N33358W114457DDFB" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "Flight Plan", + "items": [ + { + "type": "status", + "code": "ROUTE_STATUS", + "label": "Route Status", + "value": "Route Planned" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "KSFO" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KPHX" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "KAYEX(36.487 N, 120.948 W) >> LOSHN(35.848 N, 120.000 W) >> BOILE(34.422 N, 118.027 W) >> BLH(33.597 N, 114.762 W)" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0xddfb" + } + ] + }, + "raw": { + "flight_number": "UAL1187", + "route_status": "RP", + "departure_icao": "KSFO", + "arrival_icao": "KPHX", + "route": { + "waypoints": [ + { + "name": "KAYEX", + "latitude": 36.486666666666665, + "longitude": -120.94833333333334 + }, + { + "name": "" + }, + { + "name": "LOSHN", + "latitude": 35.848333333333336, + "longitude": -120 + }, + { + "name": "" + }, + { + "name": "BOILE", + "latitude": 34.42166666666667, + "longitude": -118.02666666666667 + }, + { + "name": "" + }, + { + "name": "BLH", + "latitude": 33.596666666666664, + "longitude": -114.76166666666667 + } + ] + }, + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 56827 + }, + "remaining": { + "text": "" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-075.json b/corpus/wildcards/arinc_702/sample-075.json new file mode 100644 index 0000000..eff9b3e --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-075.json @@ -0,0 +1,111 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 75", + "input": { + "label": "H1", + "text": "FPN/TS140017,021724/RP:DA:EHAM:AA:KMSP..N55064W000477..N55163W001141..ERAKA..N60000W020000..N61000W030000:WS:N61000W030000,370..N61000W040000..N60000W050000..URTAK:WS:URTAK,380..LAKES:WS:LAKES,400..N57000W070000..N54300W080000..N49000W090000..DLH..COLDD:A:BAINY3:AP:ILS30L(30L)/PR4356,344,360,1060,,,13,,,30,,,P50,M40,36090,,3296,292/DTKMSP,30L,172,215117156D" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Flight Plan", + "items": [ + { + "type": "status", + "code": "ROUTE_STATUS", + "label": "Route Status", + "value": "Route Planned" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "EHAM" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KMSP..N55064W000477..N55163W001141..ERAKA..N60000W020000..N61000W030000" + }, + { + "type": "procedure", + "code": "proc", + "label": "Arrival Procedure", + "value": "BAINY3" + }, + { + "type": "procedure", + "code": "proc", + "label": "Approach Procedure", + "value": "ILS30L(30L)" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "36000 feet" + }, + { + "type": "runway", + "code": "ARWY", + "label": "Arrival Runway", + "value": "30L" + }, + { + "type": "fuel_on_board", + "code": "FOB", + "label": "Fuel On Board", + "value": "172" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "21:51:17" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0x156d" + } + ] + }, + "raw": { + "message_timestamp": 1708178417, + "route_status": "RP", + "departure_icao": "EHAM", + "arrival_icao": "KMSP..N55064W000477..N55163W001141..ERAKA..N60000W020000..N61000W030000", + "procedures": [ + { + "type": "arrival", + "route": { + "name": "BAINY3" + } + }, + { + "type": "approach", + "route": { + "name": "ILS30L(30L)" + } + } + ], + "altitude": 36000, + "arrival_runway": "30L", + "fuel_on_board": 172, + "eta_time": 78677, + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 5485 + }, + "remaining": { + "text": ":WS:N61000W030000,370..N61000W040000..N60000W050000..URTAK:WS:URTAK,380..LAKES:WS:LAKES,400..N57000W070000..N54300W080000..N49000W090000..DLH..COLDD/PR4356,344,1060,,,13,,,30,,,P50,M40,36090,,3296,292" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-076.json b/corpus/wildcards/arinc_702/sample-076.json new file mode 100644 index 0000000..902fff7 --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-076.json @@ -0,0 +1,67 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 76", + "input": { + "label": "H1", + "text": "FPN/SN2125/FNQFA780/RI:DA:YPPH:CR:PERMEL001:AA:YMML..MEMUP,S33451E\r\n120525.Y53.WENDY0560" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "Flight Plan", + "items": [ + { + "type": "status", + "code": "ROUTE_STATUS", + "label": "Route Status", + "value": "Route Inactive" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "YPPH" + }, + { + "type": "company_route", + "code": "CR", + "label": "Company Route", + "value": "PERMEL001" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "YMML..MEMUP,S33451E120525.Y53.WENDY" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0x0560" + } + ] + }, + "raw": { + "serial_number": "2125", + "flight_number": "QFA780", + "route_status": "RI", + "departure_icao": "YPPH", + "company_route": { + "name": "PERMEL001" + }, + "arrival_icao": "YMML..MEMUP,S33451E120525.Y53.WENDY", + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 1376 + }, + "remaining": { + "text": "" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-077.json b/corpus/wildcards/arinc_702/sample-077.json new file mode 100644 index 0000000..60b4716 --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-077.json @@ -0,0 +1,148 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 77", + "input": { + "label": "H1", + "text": "FPN/TS155631,170224/SN155631/RP:DA:PHNL:AA:KASE:D:MKK5.KOLEA:F:KOLEA,N22354W155133..CLUTS,N23002W154393.R465.CINNY,N36109W124456..OAL,N38002W117462.J58.ILC,N38150W114237..EYELO,N38455W110469..SAKES,N38500W110163.J80.DBL,N39264W106537F5E1" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "Flight Plan", + "items": [ + { + "type": "status", + "code": "ROUTE_STATUS", + "label": "Route Status", + "value": "Route Planned" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "PHNL" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KASE" + }, + { + "type": "procedure", + "code": "proc", + "label": "Departure Procedure", + "value": "MKK5 starting at KOLEA" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "KOLEA(22.590 N, 155.222 W) >> CLUTS(23.003 N, 154.655 W) > R465 > CINNY(36.182 N, 124.760 W) >> OAL(38.003 N, 117.770 W) > J58 > ILC(38.250 N, 114.395 W) >> EYELO(38.758 N, 110.782 W) >> SAKES(38.833 N, 110.272 W) > J80 > DBL(39.440 N, 106.895 W)" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0xf5e1" + } + ] + }, + "raw": { + "message_timestamp": 1708185391, + "serial_number": "155631", + "route_status": "RP", + "departure_icao": "PHNL", + "arrival_icao": "KASE", + "procedures": [ + { + "type": "departure", + "route": { + "name": "MKK5", + "waypoints": [ + { + "name": "KOLEA" + } + ] + } + } + ], + "route": { + "waypoints": [ + { + "name": "KOLEA", + "latitude": 22.59, + "longitude": -155.22166666666666 + }, + { + "name": "" + }, + { + "name": "CLUTS", + "latitude": 23.003333333333334, + "longitude": -154.655 + }, + { + "name": "R465" + }, + { + "name": "CINNY", + "latitude": 36.181666666666665, + "longitude": -124.76 + }, + { + "name": "" + }, + { + "name": "OAL", + "latitude": 38.00333333333333, + "longitude": -117.77 + }, + { + "name": "J58" + }, + { + "name": "ILC", + "latitude": 38.25, + "longitude": -114.395 + }, + { + "name": "" + }, + { + "name": "EYELO", + "latitude": 38.75833333333333, + "longitude": -110.78166666666667 + }, + { + "name": "" + }, + { + "name": "SAKES", + "latitude": 38.833333333333336, + "longitude": -110.27166666666666 + }, + { + "name": "J80" + }, + { + "name": "DBL", + "latitude": 39.44, + "longitude": -106.895 + } + ] + }, + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 62945 + }, + "remaining": { + "text": "" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-078.json b/corpus/wildcards/arinc_702/sample-078.json new file mode 100644 index 0000000..c0ac89e --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-078.json @@ -0,0 +1,143 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 78", + "input": { + "label": "H1", + "text": "FPN/RP:DA:KSEA:AA:KPHX:D:SUMMA2.SUMMA:F:SUMMA,N46371W121593..LTJ,N45428W121061..IMB,N44389W119427.Q35..CORKR,N36050W112240..TENTS,N35295W112271:A:BRUSR1.TENTS/FNFFT17245D16" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "Flight Plan", + "items": [ + { + "type": "status", + "code": "ROUTE_STATUS", + "label": "Route Status", + "value": "Route Planned" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "KSEA" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KPHX" + }, + { + "type": "procedure", + "code": "proc", + "label": "Departure Procedure", + "value": "SUMMA2 starting at SUMMA" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "SUMMA(46.618 N, 121.988 W) >> LTJ(45.713 N, 121.102 W) >> IMB(44.648 N, 119.712 W) > Q35 >> CORKR(36.083 N, 112.400 W) >> TENTS(35.492 N, 112.452 W)" + }, + { + "type": "procedure", + "code": "proc", + "label": "Arrival Procedure", + "value": "BRUSR1 starting at TENTS" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0x5d16" + } + ] + }, + "raw": { + "route_status": "RP", + "departure_icao": "KSEA", + "arrival_icao": "KPHX", + "procedures": [ + { + "type": "departure", + "route": { + "name": "SUMMA2", + "waypoints": [ + { + "name": "SUMMA" + } + ] + } + }, + { + "type": "arrival", + "route": { + "name": "BRUSR1", + "waypoints": [ + { + "name": "TENTS" + } + ] + } + } + ], + "route": { + "waypoints": [ + { + "name": "SUMMA", + "latitude": 46.61833333333333, + "longitude": -121.98833333333333 + }, + { + "name": "" + }, + { + "name": "LTJ", + "latitude": 45.71333333333333, + "longitude": -121.10166666666667 + }, + { + "name": "" + }, + { + "name": "IMB", + "latitude": 44.64833333333333, + "longitude": -119.71166666666667 + }, + { + "name": "Q35" + }, + { + "name": "" + }, + { + "name": "CORKR", + "latitude": 36.083333333333336, + "longitude": -112.4 + }, + { + "name": "" + }, + { + "name": "TENTS", + "latitude": 35.49166666666667, + "longitude": -112.45166666666667 + } + ] + }, + "flight_number": "FFT1724", + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 23830 + }, + "remaining": { + "text": "" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-079.json b/corpus/wildcards/arinc_702/sample-079.json new file mode 100644 index 0000000..49fa27e --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-079.json @@ -0,0 +1,153 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 79", + "input": { + "label": "H1", + "text": "#M1BFPN/RI:DA:KJFK:AA:KPHX:CR:JFKPHX20..KG701..KG702..KP702..KP703..KD601..KD602..PUB..ALS.J102.GUP:A:EAGUL6.GUP:F:HOMRR90E1" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "Flight Plan", + "items": [ + { + "type": "status", + "code": "ROUTE_STATUS", + "label": "Route Status", + "value": "Route Inactive" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "KJFK" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "KPHX" + }, + { + "type": "company_route", + "code": "CR", + "label": "Company Route", + "value": "JFKPHX20: >> KG701 >> KG702 >> KP702 >> KP703 >> KD601 >> KD602 >> PUB >> ALS > J102 > GUP" + }, + { + "type": "procedure", + "code": "proc", + "label": "Arrival Procedure", + "value": "EAGUL6 starting at GUP" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "HOMRR" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0x90e1" + } + ] + }, + "raw": { + "route_status": "RI", + "departure_icao": "KJFK", + "arrival_icao": "KPHX", + "company_route": { + "name": "JFKPHX20", + "waypoints": [ + { + "name": "" + }, + { + "name": "KG701" + }, + { + "name": "" + }, + { + "name": "KG702" + }, + { + "name": "" + }, + { + "name": "KP702" + }, + { + "name": "" + }, + { + "name": "KP703" + }, + { + "name": "" + }, + { + "name": "KD601" + }, + { + "name": "" + }, + { + "name": "KD602" + }, + { + "name": "" + }, + { + "name": "PUB" + }, + { + "name": "" + }, + { + "name": "ALS" + }, + { + "name": "J102" + }, + { + "name": "GUP" + } + ] + }, + "procedures": [ + { + "type": "arrival", + "route": { + "name": "EAGUL6", + "waypoints": [ + { + "name": "GUP" + } + ] + } + } + ], + "route": { + "waypoints": [ + { + "name": "HOMRR" + } + ] + }, + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 37089 + }, + "remaining": { + "text": "" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-080.json b/corpus/wildcards/arinc_702/sample-080.json new file mode 100644 index 0000000..b08a1d0 --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-080.json @@ -0,0 +1,149 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 80", + "input": { + "label": "H1", + "text": "F37AKL0767#M1BFPN/TS232008,022324/RP:DA:TNCA:AA:TNCB:R:11O:D:ADRI1F..IRLEP.A574..PJG:AP:RNV10(10O)/PR,,110,,183,7,13,,M7,25,,,P30,M40,36090,13,3455,300/DTTNCB,10O,119,23440847C0" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Flight Plan", + "items": [ + { + "type": "status", + "code": "ROUTE_STATUS", + "label": "Route Status", + "value": "Route Planned" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "TNCA" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "TNCB" + }, + { + "type": "runway", + "code": "DEPRWY", + "label": "Departure Runway", + "value": "11O" + }, + { + "type": "procedure", + "code": "proc", + "label": "Departure Procedure", + "value": "ADRI1F: >> IRLEP > A574 >> PJG" + }, + { + "type": "procedure", + "code": "proc", + "label": "Approach Procedure", + "value": "RNV10(10O)" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "11000 feet" + }, + { + "type": "outside_air_temperature", + "code": "OATEMP", + "label": "Outside Air Temperature (C)", + "value": "-7 degrees" + }, + { + "type": "runway", + "code": "ARWY", + "label": "Arrival Runway", + "value": "10O" + }, + { + "type": "fuel_on_board", + "code": "FOB", + "label": "Fuel On Board", + "value": "119" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "23:44:08" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0x47c0" + }, + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "KL0767" + } + ] + }, + "raw": { + "message_timestamp": 1708730408, + "route_status": "RP", + "departure_icao": "TNCA", + "arrival_icao": "TNCB", + "departure_runway": "11O", + "procedures": [ + { + "type": "departure", + "route": { + "name": "ADRI1F", + "waypoints": [ + { + "name": "" + }, + { + "name": "IRLEP" + }, + { + "name": "A574" + }, + { + "name": "" + }, + { + "name": "PJG" + } + ] + } + }, + { + "type": "approach", + "route": { + "name": "RNV10(10O)" + } + } + ], + "altitude": 11000, + "outside_air_temperature": -7, + "arrival_runway": "10O", + "fuel_on_board": 119, + "eta_time": 85448, + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 18368, + "flight_number": "KL0767" + }, + "remaining": { + "text": "F37A#M1B/,,,183,7,13,,25,,,P30,M40,36090,13,3455,300" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-081.json b/corpus/wildcards/arinc_702/sample-081.json new file mode 100644 index 0000000..aab900f --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-081.json @@ -0,0 +1,25 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 81", + "input": { + "label": "H1", + "text": "FPN Bogus message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Unknown", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "FPN/,FPN Bogus message" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-082.json b/corpus/wildcards/arinc_702/sample-082.json new file mode 100644 index 0000000..86e1d63 --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-082.json @@ -0,0 +1,143 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 82", + "input": { + "label": "H1", + "text": "FPN/ID88194A,RCH857,PMZM107QP021/MR2,/RM:AA:FJDG:F:DOH.N300..NOLSU.P307..SETSI.P307..PARAR..N20000E063000..RIGLO.L516..ELKEL.L516..BUMMR/WP,,,,E3E9" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Flight Plan", + "items": [ + { + "type": "tail", + "code": "TAIL", + "label": "Tail", + "value": "88194A" + }, + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "RCH857" + }, + { + "type": "sequence", + "code": "SEQ", + "label": "Sequence Number", + "value": 2 + }, + { + "type": "status", + "code": "ROUTE_STATUS", + "label": "Route Status", + "value": "Route Mapped" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "FJDG" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "DOH > N300 >> NOLSU > P307 >> SETSI > P307 >> PARAR >> (20.000 N, 63.000 E) >> RIGLO > L516 >> ELKEL > L516 >> BUMMR" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0xe3e9" + } + ] + }, + "raw": { + "tail": "88194A", + "flight_number": "RCH857", + "mission_number": "PMZM107QP021", + "sequence_number": 2, + "route_status": "RM", + "arrival_icao": "FJDG", + "route": { + "waypoints": [ + { + "name": "DOH" + }, + { + "name": "N300" + }, + { + "name": "" + }, + { + "name": "NOLSU" + }, + { + "name": "P307" + }, + { + "name": "" + }, + { + "name": "SETSI" + }, + { + "name": "P307" + }, + { + "name": "" + }, + { + "name": "PARAR" + }, + { + "name": "" + }, + { + "name": "", + "latitude": 20, + "longitude": 63 + }, + { + "name": "" + }, + { + "name": "RIGLO" + }, + { + "name": "L516" + }, + { + "name": "" + }, + { + "name": "ELKEL" + }, + { + "name": "L516" + }, + { + "name": "" + }, + { + "name": "BUMMR" + } + ] + }, + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 58345 + }, + "remaining": { + "text": "WP,,,," + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-083.json b/corpus/wildcards/arinc_702/sample-083.json new file mode 100644 index 0000000..f751943 --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-083.json @@ -0,0 +1,35 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 83", + "input": { + "label": "H1", + "text": "FPNEEE6" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "Flight Plan", + "items": [ + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0xeee6" + } + ] + }, + "raw": { + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 61158 + }, + "remaining": { + "text": "" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-084.json b/corpus/wildcards/arinc_702/sample-084.json new file mode 100644 index 0000000..d0352f0 --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-084.json @@ -0,0 +1,311 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 84", + "input": { + "label": "H1", + "text": "FPN/ID80094S,RCH411,8JZ41NG3S048/MR3,5/RP:DA:KMCF:AA:LBSF:F:PIE..BRUTS.Q109.CAMJO.Q109.YURCK.Q97.PAACK.Q97.BLENO.Q97.FRIAR..TOPPS..FROSS..RIKAL..N53000W050000..N55000W040000..N56000W030000..N56000W020000..PIKIL..SOVED..MIMKU..MAC..BELOX.L603.DOLAS..NAVPI..MAVAS..SOGPO..TIVUN..ESAMA..OSBIT..KOMIB..SULUS.Z650.VEMUT..PEPIK..BERVA..ERGOM..TEGRI..OSTOV..GOL:V:CAMJO,301,AT3100,,:V:PAACK,282,AT3300,,:V:BLENO,258,AT3700,,:V:N53000W050000,256,AT3700,,:V:N55000W040000,260,AT3700,,:V:SOVED,303,AT2700,,:V:MIMKU,302,AT2700,,:V:DOLAS,290,AT2900,,:V:PEPIK,242,AT3700,,:V:BERVA,260,AT3700,,7147/WD,,,,C850" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Flight Plan", + "items": [ + { + "type": "tail", + "code": "TAIL", + "label": "Tail", + "value": "80094S" + }, + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "RCH411" + }, + { + "type": "sequence", + "code": "SEQ", + "label": "Sequence Number", + "value": 3 + }, + { + "type": "sequence", + "code": "SEQ_RESP", + "label": "Sequence Response", + "value": 5 + }, + { + "type": "status", + "code": "ROUTE_STATUS", + "label": "Route Status", + "value": "Route Planned" + }, + { + "type": "icao", + "code": "ORG", + "label": "Origin", + "value": "KMCF" + }, + { + "type": "icao", + "code": "DST", + "label": "Destination", + "value": "LBSF" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "PIE >> BRUTS > Q109 > CAMJO > Q109 > YURCK > Q97 > PAACK > Q97 > BLENO > Q97 > FRIAR >> TOPPS >> FROSS >> RIKAL >> (53.000 N, 50.000 W) >> (55.000 N, 40.000 W) >> (56.000 N, 30.000 W) >> (56.000 N, 20.000 W) >> PIKIL >> SOVED >> MIMKU >> MAC >> BELOX > L603 > DOLAS >> NAVPI >> MAVAS >> SOGPO >> TIVUN >> ESAMA >> OSBIT >> KOMIB >> SULUS > Z650 > VEMUT >> PEPIK >> BERVA >> ERGOM >> TEGRI >> OSTOV >> GOL" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0xc850" + } + ] + }, + "raw": { + "tail": "80094S", + "flight_number": "RCH411", + "mission_number": "8JZ41NG3S048", + "sequence_number": 3, + "sequence_response": 5, + "route_status": "RP", + "departure_icao": "KMCF", + "arrival_icao": "LBSF", + "route": { + "waypoints": [ + { + "name": "PIE" + }, + { + "name": "" + }, + { + "name": "BRUTS" + }, + { + "name": "Q109" + }, + { + "name": "CAMJO" + }, + { + "name": "Q109" + }, + { + "name": "YURCK" + }, + { + "name": "Q97" + }, + { + "name": "PAACK" + }, + { + "name": "Q97" + }, + { + "name": "BLENO" + }, + { + "name": "Q97" + }, + { + "name": "FRIAR" + }, + { + "name": "" + }, + { + "name": "TOPPS" + }, + { + "name": "" + }, + { + "name": "FROSS" + }, + { + "name": "" + }, + { + "name": "RIKAL" + }, + { + "name": "" + }, + { + "name": "", + "latitude": 53, + "longitude": -50 + }, + { + "name": "" + }, + { + "name": "", + "latitude": 55, + "longitude": -40 + }, + { + "name": "" + }, + { + "name": "", + "latitude": 56, + "longitude": -30 + }, + { + "name": "" + }, + { + "name": "", + "latitude": 56, + "longitude": -20 + }, + { + "name": "" + }, + { + "name": "PIKIL" + }, + { + "name": "" + }, + { + "name": "SOVED" + }, + { + "name": "" + }, + { + "name": "MIMKU" + }, + { + "name": "" + }, + { + "name": "MAC" + }, + { + "name": "" + }, + { + "name": "BELOX" + }, + { + "name": "L603" + }, + { + "name": "DOLAS" + }, + { + "name": "" + }, + { + "name": "NAVPI" + }, + { + "name": "" + }, + { + "name": "MAVAS" + }, + { + "name": "" + }, + { + "name": "SOGPO" + }, + { + "name": "" + }, + { + "name": "TIVUN" + }, + { + "name": "" + }, + { + "name": "ESAMA" + }, + { + "name": "" + }, + { + "name": "OSBIT" + }, + { + "name": "" + }, + { + "name": "KOMIB" + }, + { + "name": "" + }, + { + "name": "SULUS" + }, + { + "name": "Z650" + }, + { + "name": "VEMUT" + }, + { + "name": "" + }, + { + "name": "PEPIK" + }, + { + "name": "" + }, + { + "name": "BERVA" + }, + { + "name": "" + }, + { + "name": "ERGOM" + }, + { + "name": "" + }, + { + "name": "TEGRI" + }, + { + "name": "" + }, + { + "name": "OSTOV" + }, + { + "name": "" + }, + { + "name": "GOL" + } + ] + }, + "wind_data": [], + "checksum_algorithm": "GENIBUS", + "checksum": 51280 + }, + "remaining": { + "text": ":V:CAMJO,301,AT3100,,:V:PAACK,282,AT3300,,:V:BLENO,258,AT3700,,:V:N53000W050000,256,AT3700,,:V:N55000W040000,260,AT3700,,:V:SOVED,303,AT2700,,:V:MIMKU,302,AT2700,,:V:DOLAS,290,AT2900,,:V:PEPIK,242,AT3700,,:V:BERVA,260,AT3700,,7147" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-085.json b/corpus/wildcards/arinc_702/sample-085.json new file mode 100644 index 0000000..7da6987 --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-085.json @@ -0,0 +1,229 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 85", + "input": { + "label": "H1", + "text": "PWI/WD390,COLZI,258070.AWYAT,252071.IPTAY,250065.CHOPZ,244069.MGMRY,234065.CATLN,230060/WD340,COLZI,256073,340M41.AWYAT,252070,340M41.IPTAY,244059,340M41.CHOPZ,240059,340M41.MGMRY,232056,340M41.CATLN,218053,340M40/WD300,COLZI,256065.AWYAT,254062.IPTAY,250051.CHOPZ,248050.MGMRY,232044.CATLN,222047/WD240,COLZI,260045.AWYAT,258048.IPTAY,254043.CHOPZ,256041.MGMRY,238035.CATLN,226034/DD300214059.240214040.180236024.100250018:,,,,/CB300246040.240246017.180226015.1002100080338" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Pilot Weather Information", + "items": [ + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "COLZI at FL390: 258° at 70kt" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "AWYAT at FL390: 252° at 71kt" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "IPTAY at FL390: 250° at 65kt" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "CHOPZ at FL390: 244° at 69kt" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "MGMRY at FL390: 234° at 65kt" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "CATLN at FL390: 230° at 60kt" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "COLZI at FL340: 256° at 73kt, -41°C at FL340" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "AWYAT at FL340: 252° at 70kt, -41°C at FL340" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "IPTAY at FL340: 244° at 59kt, -41°C at FL340" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "CHOPZ at FL340: 240° at 59kt, -41°C at FL340" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "MGMRY at FL340: 232° at 56kt, -41°C at FL340" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "CATLN at FL340: 218° at 53kt, -40°C at FL340" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "COLZI at FL300: 256° at 65kt" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "AWYAT at FL300: 254° at 62kt" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "IPTAY at FL300: 250° at 51kt" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "CHOPZ at FL300: 248° at 50kt" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "MGMRY at FL300: 232° at 44kt" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "CATLN at FL300: 222° at 47kt" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "COLZI at FL240: 260° at 45kt" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "AWYAT at FL240: 258° at 48kt" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "IPTAY at FL240: 254° at 43kt" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "CHOPZ at FL240: 256° at 41kt" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "MGMRY at FL240: 238° at 35kt" + }, + { + "type": "wind_data", + "code": "WIND", + "label": "Wind Data", + "value": "CATLN at FL240: 226° at 34kt" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0x0338" + } + ] + }, + "raw": { + "wind_data": [ + { + "waypoint": { + "name": "COLZI" + }, + "flightLevel": 240, + "windDirection": 260, + "windSpeed": 45 + }, + { + "waypoint": { + "name": "AWYAT" + }, + "flightLevel": 240, + "windDirection": 258, + "windSpeed": 48 + }, + { + "waypoint": { + "name": "IPTAY" + }, + "flightLevel": 240, + "windDirection": 254, + "windSpeed": 43 + }, + { + "waypoint": { + "name": "CHOPZ" + }, + "flightLevel": 240, + "windDirection": 256, + "windSpeed": 41 + }, + { + "waypoint": { + "name": "MGMRY" + }, + "flightLevel": 240, + "windDirection": 238, + "windSpeed": 35 + }, + { + "waypoint": { + "name": "CATLN" + }, + "flightLevel": 240, + "windDirection": 226, + "windSpeed": 34 + } + ], + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 824 + }, + "remaining": { + "text": "DD300214059.240214040.180236024.100250018:,,,,/CB300246040.240246017.180226015.100210008" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-086.json b/corpus/wildcards/arinc_702/sample-086.json new file mode 100644 index 0000000..393c23e --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-086.json @@ -0,0 +1,25 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 86", + "input": { + "label": "H1", + "text": "PWI Bogus message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Unknown", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "PWI/,PWI Bogus message" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-087.json b/corpus/wildcards/arinc_702/sample-087.json new file mode 100644 index 0000000..791446a --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-087.json @@ -0,0 +1,163 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 87", + "input": { + "label": "4J", + "text": "POS/ID91459S,BANKR31,/DC03032024,142813/MR64,0/ET31539/PSN39277W077359,142800,240,N39300W077110,031430,N38560W077150,M28,27619,MT370/CG311,160,350/FB732/VR329071" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Position Report", + "items": [ + { + "type": "tail", + "code": "TAIL", + "label": "Tail", + "value": "91459S" + }, + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "BANKR31" + }, + { + "type": "sequence", + "code": "SEQ", + "label": "Sequence Number", + "value": 64 + }, + { + "type": "sequence", + "code": "SEQ_RESP", + "label": "Sequence Response", + "value": 0 + }, + { + "type": "day", + "code": "MSG_DAY", + "label": "Day of Month", + "value": "3" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "15:39:00" + }, + { + "type": "aircraft_position", + "code": "POS", + "label": "Aircraft Position", + "value": "39.462 N, 77.598 W" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "(39.500 N, 77.183 W)@14:28:00 > (38.933 N, 77.250 W)@03:14:30 > ?" + }, + { + "type": "altitude", + "code": "ALT", + "label": "Altitude", + "value": "24000 feet" + }, + { + "type": "outside_air_temperature", + "code": "OATEMP", + "label": "Outside Air Temperature (C)", + "value": "-28 degrees" + }, + { + "type": "center_of_gravity", + "code": "CG", + "label": "Center of Gravity", + "value": "31.1 %" + }, + { + "type": "cg_lower_limit", + "code": "CG_LOWER", + "label": "Center of Gravity Lower Limit", + "value": "16 %" + }, + { + "type": "cg_upper_limit", + "code": "CG_UPPER", + "label": "Center of Gravity Upper Limit", + "value": "35 %" + }, + { + "type": "fuel_on_board", + "code": "FOB", + "label": "Fuel On Board", + "value": "732" + }, + { + "type": "version", + "code": "VERSION", + "label": "Message Version", + "value": "v3.2" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0x9071" + } + ] + }, + "raw": { + "tail": "91459S", + "flight_number": "BANKR31", + "mission_number": "", + "message_timestamp": 1709476093, + "sequence_number": 64, + "sequence_response": 0, + "day": 3, + "eta_time": 56340, + "position": { + "latitude": 39.461666666666666, + "longitude": -77.59833333333333 + }, + "route": { + "waypoints": [ + { + "name": "", + "latitude": 39.5, + "longitude": -77.18333333333334, + "time": 52080 + }, + { + "name": "", + "latitude": 38.93333333333333, + "longitude": -77.25, + "time": 11670 + }, + { + "name": "?" + } + ] + }, + "altitude": 24000, + "outside_air_temperature": -28, + "center_of_gravity": 31.1, + "cg_lower_limit": 16, + "cg_upper_limit": 35, + "fuel_on_board": 732, + "version": 3.2, + "checksum_algorithm": "GENIBUS", + "checksum": 36977 + }, + "remaining": { + "text": "27619,MT370" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-088.json b/corpus/wildcards/arinc_702/sample-088.json new file mode 100644 index 0000000..a4f9c18 --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-088.json @@ -0,0 +1,25 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 88", + "input": { + "label": "4J", + "text": "POS/ Bogus message" + }, + "expected": { + "decoded": false, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "none" + }, + "formatted": { + "description": "Unknown", + "items": [] + }, + "raw": {}, + "remaining": { + "text": "POS/ Bogus message" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-089.json b/corpus/wildcards/arinc_702/sample-089.json new file mode 100644 index 0000000..4eaf565 --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-089.json @@ -0,0 +1,36 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 89", + "input": { + "label": "H1", + "text": "RESPWI/AC,5B/TS140956,070226/DI140953,140956,140956128C" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Response for Pilot Weather Information", + "items": [ + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0x128c" + } + ] + }, + "raw": { + "message_timestamp": 1770473396, + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 4748 + }, + "remaining": { + "text": "AC,5B/DI140953,140956,140956" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-090.json b/corpus/wildcards/arinc_702/sample-090.json new file mode 100644 index 0000000..7ce9302 --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-090.json @@ -0,0 +1,35 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 90", + "input": { + "label": "H1", + "text": "RESPOS/AK,0711909" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Response for Position Report", + "items": [ + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0x1909" + } + ] + }, + "raw": { + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 6409 + }, + "remaining": { + "text": "AK,071" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-091.json b/corpus/wildcards/arinc_702/sample-091.json new file mode 100644 index 0000000..c33354d --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-091.json @@ -0,0 +1,79 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 91", + "input": { + "label": "H1", + "text": "INR/ID91511S,,/DC04032026,143534/MR19,/NR,,,,,,,,950,0/ET041505/FB983/VR32BF4C" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "In-Range Report", + "items": [ + { + "type": "tail", + "code": "TAIL", + "label": "Tail", + "value": "91511S" + }, + { + "type": "sequence", + "code": "SEQ", + "label": "Sequence Number", + "value": 19 + }, + { + "type": "day", + "code": "MSG_DAY", + "label": "Day of Month", + "value": "4" + }, + { + "type": "time", + "code": "ETA", + "label": "Estimated Time of Arrival", + "value": "15:05:00" + }, + { + "type": "fuel_on_board", + "code": "FOB", + "label": "Fuel On Board", + "value": "983" + }, + { + "type": "version", + "code": "VERSION", + "label": "Message Version", + "value": "v3.2" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0xbf4c" + } + ] + }, + "raw": { + "tail": "91511S", + "mission_number": "", + "message_timestamp": 1772634934, + "sequence_number": 19, + "day": 4, + "eta_time": 54300, + "fuel_on_board": 983, + "version": 3.2, + "checksum_algorithm": "GENIBUS", + "checksum": 48972 + }, + "remaining": { + "text": "NR,,,,,,,,950,0" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-092.json b/corpus/wildcards/arinc_702/sample-092.json new file mode 100644 index 0000000..1515953 --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-092.json @@ -0,0 +1,122 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 92", + "input": { + "label": "H1", + "text": "REQPWI/WQ320:GEQUE.HACKS.XUB.VHP.KK60K.KIVDE.KK60E.KOVJY.KD54Y.ACZES.KD48S.DVC.KITTN.KATTS.RUMPS.OAL.INYOE.DYAMD/DQ320/SPGEQUE77CE" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "Request for Pilot Weather Information", + "items": [ + { + "type": "requested_altitudes", + "code": "REQ_ALTS", + "label": "Requested Altitudes", + "value": "32000" + }, + { + "type": "aircraft_route", + "code": "ROUTE", + "label": "Aircraft Route", + "value": "GEQUE > HACKS > XUB > VHP > KK60K > KIVDE > KK60E > KOVJY > KD54Y > ACZES > KD48S > DVC > KITTN > KATTS > RUMPS > OAL > INYOE > DYAMD" + }, + { + "type": "desired_altitude", + "code": "DES_ALT", + "label": "Desired Altitude", + "value": "32000" + }, + { + "type": "start_point", + "code": "START", + "label": "Start Point", + "value": "GEQUE" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0x77ce" + } + ] + }, + "raw": { + "requested_alts": [ + 32000 + ], + "route": { + "waypoints": [ + { + "name": "GEQUE" + }, + { + "name": "HACKS" + }, + { + "name": "XUB" + }, + { + "name": "VHP" + }, + { + "name": "KK60K" + }, + { + "name": "KIVDE" + }, + { + "name": "KK60E" + }, + { + "name": "KOVJY" + }, + { + "name": "KD54Y" + }, + { + "name": "ACZES" + }, + { + "name": "KD48S" + }, + { + "name": "DVC" + }, + { + "name": "KITTN" + }, + { + "name": "KATTS" + }, + { + "name": "RUMPS" + }, + { + "name": "OAL" + }, + { + "name": "INYOE" + }, + { + "name": "DYAMD" + } + ] + }, + "desired_alt": 32000, + "start_point": "GEQUE", + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 30670 + }, + "remaining": { + "text": "" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-093.json b/corpus/wildcards/arinc_702/sample-093.json new file mode 100644 index 0000000..e84da30 --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-093.json @@ -0,0 +1,35 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 93", + "input": { + "label": "H1", + "text": "REQPOS037B" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "Request for Position Report", + "items": [ + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0x037b" + } + ] + }, + "raw": { + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 891 + }, + "remaining": { + "text": "" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-094.json b/corpus/wildcards/arinc_702/sample-094.json new file mode 100644 index 0000000..01bfd10 --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-094.json @@ -0,0 +1,57 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 94", + "input": { + "label": "H1", + "text": "#MDREQPOS/ID55150A,RCH892,LVZF1185C049/MR1,/AU39310" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "partial" + }, + "formatted": { + "description": "Request for Position Report", + "items": [ + { + "type": "tail", + "code": "TAIL", + "label": "Tail", + "value": "55150A" + }, + { + "type": "flight_number", + "code": "FLIGHT", + "label": "Flight Number", + "value": "RCH892" + }, + { + "type": "sequence", + "code": "SEQ", + "label": "Sequence Number", + "value": 1 + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0x9310" + } + ] + }, + "raw": { + "tail": "55150A", + "flight_number": "RCH892", + "mission_number": "LVZF1185C049", + "sequence_number": 1, + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 37648 + }, + "remaining": { + "text": "AU3" + } + } +} diff --git a/corpus/wildcards/arinc_702/sample-095.json b/corpus/wildcards/arinc_702/sample-095.json new file mode 100644 index 0000000..cfc3c7b --- /dev/null +++ b/corpus/wildcards/arinc_702/sample-095.json @@ -0,0 +1,56 @@ +{ + "spec": "wildcards/arinc_702", + "source": "extracted from acars-decoder-typescript test suite via /tmp/corpus-extractor.js", + "description": "Auto-extracted sample 95", + "input": { + "label": "H1", + "text": "REQFPN/RN10001/RS:FP:Z5585 9736" + }, + "expected": { + "decoded": true, + "decoder": { + "name": "arinc-702", + "type": "pattern-match", + "decodeLevel": "full" + }, + "formatted": { + "description": "Request for Flight Plan", + "items": [ + { + "type": "route_number", + "code": "RTE_NUM", + "label": "Route Number", + "value": "10001" + }, + { + "type": "status", + "code": "ROUTE_STATUS", + "label": "Route Status", + "value": "Route Saved" + }, + { + "type": "flight_plan", + "code": "FPN", + "label": "Flight Plan", + "value": "Z5585" + }, + { + "type": "message_checksum", + "code": "CHECKSUM", + "label": "Message Checksum", + "value": "0x9736" + } + ] + }, + "raw": { + "route_number": "10001", + "route_status": "RS", + "flight_plan": "Z5585", + "checksum_algorithm": "IBM-SDLC reversed", + "checksum": 38710 + }, + "remaining": { + "text": "" + } + } +} diff --git a/docs/ADOPTION.md b/docs/ADOPTION.md new file mode 100644 index 0000000..d7c6946 --- /dev/null +++ b/docs/ADOPTION.md @@ -0,0 +1,133 @@ +# ADS adoption guide + +How each language repo consumes the central `airframes-decoder` repo. + +## Common shape + +Every language repo: + +1. Adds `airframes-decoder` as a git submodule at `vendor/airframes-decoder/`. +2. Imports the language's runtime helpers from `vendor/airframes-decoder/runtimes//`. +3. Runs `ads-gen --target ` at build time to regenerate plugin source from spec YAML. +4. Commits generated files (CI verifies they are up to date). +5. Runs the shared corpus (`vendor/airframes-decoder/corpus/`) as part of its test suite. + +## `acars-decoder-typescript` + +```bash +git submodule add https://github.com/airframesio/airframes-decoder vendor/airframes-decoder +``` + +`package.json` — add to scripts: + +```json +{ + "scripts": { + "ads:generate": "node vendor/airframes-decoder/codegen/dist/cli.js generate --target ts --spec vendor/airframes-decoder/spec --out lib/plugins/generated", + "ads:check": "node vendor/airframes-decoder/codegen/dist/cli.js generate --target ts --spec vendor/airframes-decoder/spec --out lib/plugins/generated --check", + "test:corpus": "vitest run tests/corpus.test.ts" + } +} +``` + +`tsconfig.json` — add path aliases so generated code can `import` runtime helpers via short names: + +```json +{ + "compilerOptions": { + "paths": { + "@airframes/ads-runtime-ts": ["vendor/airframes-decoder/runtimes/typescript/index.ts"], + "@airframes/ads-runtime-ts/helpers": ["vendor/airframes-decoder/runtimes/typescript/helpers.ts"] + } + } +} +``` + +Replace the existing `lib/utils/*.ts` imports throughout `lib/plugins/` with imports from the runtime path. Stage 2 PR. + +## `acars-decoder-rust` + +```bash +git submodule add https://github.com/airframesio/airframes-decoder vendor/airframes-decoder +``` + +`Cargo.toml`: + +```toml +[dependencies] +ads-runtime = { path = "vendor/airframes-decoder/runtimes/rust" } +``` + +`build.rs`: + +```rust +use std::process::Command; +fn main() { + println!("cargo:rerun-if-changed=vendor/airframes-decoder/spec"); + let status = Command::new("node") + .args(["vendor/airframes-decoder/codegen/dist/cli.js", + "generate", "--target", "rust", + "--spec", "vendor/airframes-decoder/spec", + "--out", "src/plugins/generated"]) + .status() + .expect("ads-gen failed"); + assert!(status.success(), "ads-gen exited non-zero"); +} +``` + +Integration tests under `tests/corpus.rs` walk `vendor/airframes-decoder/corpus/` and assert deep-equal output. + +## `acars-decoder-c` + +CMake `CMakeLists.txt`: + +```cmake +add_subdirectory(vendor/airframes-decoder/runtimes/c) +target_link_libraries(acars_decoder PRIVATE ads_runtime_c cjson m) + +add_custom_command( + OUTPUT ${GENERATED_C_FILES} + COMMAND node ${CMAKE_SOURCE_DIR}/vendor/airframes-decoder/codegen/dist/cli.js + generate --target c + --spec ${CMAKE_SOURCE_DIR}/vendor/airframes-decoder/spec + --out ${CMAKE_SOURCE_DIR}/src/plugins/generated + DEPENDS ${SPEC_YAML_FILES} +) +``` + +Corpus test binary under `tests/corpus_test.c`. + +## Submodule contributor workflow + +```bash +git clone --recurse-submodules https://github.com/airframesio/ +# or, after an initial clone: +git submodule update --init --recursive +``` + +To bump the pinned ADS version: + +```bash +cd vendor/airframes-decoder && git fetch && git checkout && cd ../.. +git add vendor/airframes-decoder +git commit -m "ADS: bump to " +``` + +The next `ads-gen` run picks up any spec changes. + +## CI parity + +All three language repos call the same reusable workflows from `airframes-decoder/.github/workflows/`: + +```yaml +# .github/workflows/ci.yml +jobs: + codegen-up-to-date: + uses: airframesio/airframes-decoder/.github/workflows/codegen-check.yml@main + corpus: + uses: airframesio/airframes-decoder/.github/workflows/corpus-test.yml@main + with: + language: rust +``` + +See `airframes-decoder/.github/workflows/` for the workflow contracts. diff --git a/docs/DSL.md b/docs/DSL.md new file mode 100644 index 0000000..85281ee --- /dev/null +++ b/docs/DSL.md @@ -0,0 +1,245 @@ +# Airframes Decoder Spec (ADS) — DSL Reference + +ADS v1 is a YAML DSL for describing ACARS message decoders. One YAML file per plugin. Validated by `schema/ads-v1.schema.json`. Consumed by `@airframes/ads-codegen`, which emits idiomatic plugin source for TypeScript, Rust, and C (with WASM, Go, Python, etc. straightforward to add). + +**Authoritative reference behavior:** `acars-decoder-typescript` is in production and is the source of truth. Every spec must produce byte-for-byte identical output when re-emitted as TS and run against the same input. `acars-message-documentation` is a secondary reference for filling gaps where TS code is incomplete. + +## File layout + +``` +spec/ +├── labels/ # one directory or file per label +│ ├── 10/POS.yaml # label "10", preamble "POS" +│ ├── 44/POS.yaml +│ ├── H1/OHMA.yaml +│ └── 4A.yaml # single-file plugin (no preamble distinction) +├── wildcards/ # plugins where qualifier label is "*" +│ └── arinc_702.yaml +└── shared/ # spec data emitted into runtimes (CRC tables, etc.) + └── crc_tables.yaml +``` + +Filenames beginning with `_` are excluded from codegen (use for partials/includes). + +## Top-level schema + +Every spec file has six keys (one optional pair): + +```yaml +spec_version: "1" +plugin: { ... } # plugin metadata +qualifiers: { ... } # which messages this plugin matches +parse: [ ... ] # parse steps OR { custom: } +fields: [ ... ] # XOR with variants +variants: [ ... ] # XOR with fields +checksum: [ ... ] # optional +formatted: { ... } # output items +``` + +### `plugin` + +```yaml +plugin: + name: Label_10_POS # PascalCase. Used verbatim as TS class name. + type: text # text | binary + docs: https://... # optional URL to research docs + decode_level: PARTIAL # NONE | PARTIAL | MESSAGE | FULL (default MESSAGE) +``` + +### `qualifiers` + +```yaml +qualifiers: + labels: ["10"] # required; "*" is wildcard + preambles: ["POS"] # optional +``` + +## Variable references + +Strings beginning with `$` are variable references resolved at codegen time: + +| Reference | Meaning | +|--------------------------|------------------------------------------| +| `$message.text` | The raw message text | +| `$parts` | A variable defined by an earlier step | +| `$parts[1]` | Index into an array variable | +| `$m.unsplit_coords` | A named regex capture group | + +When a `$var[N]` appears inside a YAML flow sequence (`[...]`), quote it (`"$parts[1]"`) to prevent YAML from parsing the `[` as a nested sequence start. + +## `parse` block + +Either a list of steps OR a single escape hatch: + +```yaml +parse: + custom: arinc_702_dispatch # full delegate to per-language native function +``` + +Or: + +```yaml +parse: + - { split: ",", into: parts } + - { require_length: { var: parts, equals: 12 }, else: fail } + - regex: "^(?...),(?...)$" + on: $message.text + into: m +``` + +### Step kinds + +| Step | Purpose | +|-------------------|--------------------------------------------------| +| `split` | `String.split(delimiter)` → array | +| `regex` | Match with named capture groups → object | +| `substring` | Extract by start/length or start/end | +| `require_length` | Assert array length; on fail → `failUnknown` | +| `bitfield` | Extract bit ranges from a byte/byte-array source | +| `decode_ascii85` | ASCII85 → bytes | +| `deflate` | inflate (raw/zlib/gzip) bytes → bytes | +| `base64` | base64 → bytes | +| `text_decode` | bytes → string (utf-8 / ascii / latin1) | +| `hex_decode` | hex string → bytes | +| `concat_bits` | join multiple bit slices into a single value | +| `custom` | per-language native helper | + +Steps that produce a value require an `into: ` key. Variables become available to subsequent steps and to `fields`/`variants`/`formatted`. + +## `fields` and `variants` + +`fields` is a flat list; `variants` is a branched list — they are mutually exclusive at the top level. + +### Field + +```yaml +- name: latitude + from: $parts[1] # source expression + decode: + fn: coordinate # named decode helper (see below) + args: { ... } + when: { ... } # optional condition; field assigned only if true + default: ... # optional fallback value +``` + +### Variant + +```yaml +variants: + - name: format_11 + when: { equals: [{ length: $fields }, 11] } + fields: [ ... ] + - name: format_6 + when: + all: + - { equals: [{ length: $fields }, 6] } + - { matches: ["$fields[0]", "^[NS]"] } + fields: [ ... ] + - default: fail # fallthrough if no when matches +``` + +## Decode functions (`decode.fn`) + +Canonical set defined in `spec/shared/decode_fns.yaml` and implemented in `runtimes//`. Use `fn: custom, custom: ` for plugin-specific logic. + +| `fn` | Purpose | +|-------------------------------|--------------------------------------------------| +| `coordinate` | Single-axis or combined lat/lon | +| `coordinate_decimal_minutes` | DDMM.M format | +| `integer`, `float` | Numeric parsing (with optional substring args) | +| `string`, `trim`, `uppercase`, `lowercase` | String transforms | +| `timestamp_hhmmss` | HHMMSS → seconds since midnight | +| `timestamp_ddhhmm` | DDHHMM → epoch-style | +| `callsign`, `tail_number`, `flight_number`, `airport` | Identifier normalization | +| `altitude_feet`, `speed_knots`, `heading_degrees`, `fuel_kg`, `fuel_lb` | Typed numerics | +| `hex_to_bytes`, `json_parse` | Format conversions | +| `custom` | per-language native function (`custom: `) | + +## Conditions + +```yaml +{ equals: [a, b] } +{ not_equal: [a, b] } +{ matches: ["$var", "regex"] } # JS-flavor regex applied to the variable value +{ in: ["$var", [v1, v2, ...]] } +{ all: [cond1, cond2, ...] } +{ any: [cond1, cond2, ...] } +{ not: cond } +``` + +Computed sub-expression: `{ length: $var }` returns the length of an array or string variable. + +## `checksum` + +```yaml +checksum: + - algorithm: ARINC_665_CRC32 + when: { equals: [$version, 1] } + on: "0..-4" # byte range slice (inclusive start, exclusive end; negative from end) + expect: tail4 # last 4 bytes hold the expected value +on_checksum_fail: + decoded: false # what to do on mismatch +``` + +## `formatted` + +Either structured items or a per-language escape hatch: + +```yaml +formatted: + description: "Position Report" + items: + - { type: position, latitude: $latitude, longitude: $longitude } + - { type: altitude, value: $altitude } +``` + +Or: + +```yaml +formatted: + description: "Latest New Format" # human description still required + custom: label_4a_format # function in runtimes//escape_hatches/ +``` + +Available `type`s: `position`, `altitude`, `speed`, `heading`, `timestamp`, `callsign`, `flight_number`, `tail_number`, `airport_origin`, `airport_destination`, `fuel`, `free_text`, `custom`. + +## Escape hatches + +Reserved for the ~5% of plugins whose logic resists declarative spec (ARINC 702 heuristic offsets, CBand recursion, MIAM CRC version dispatch, complex variant slicing). + +```yaml +parse: + custom: arinc_702_dispatch # at the parse level +``` + +```yaml +- name: pos + from: $fields + decode: { fn: custom, custom: label_4a_variant_2_decode } # at the field level +``` + +```yaml +formatted: + description: "OHMA Message" + custom: ohma_message_format # at the formatter level +``` + +Each language ships a function by that exact name in `runtimes//escape_hatches/`. The function receives the plugin instance, message, result-so-far, and options. See [ESCAPE_HATCHES.md](ESCAPE_HATCHES.md) for the per-language conventions. + +## Validating a spec + +```bash +cd codegen +npm run build +node dist/cli.js validate --spec ../spec +``` + +## Generating code + +```bash +node dist/cli.js generate --target ts --spec ../spec --out ../runtimes/typescript/generated +node dist/cli.js generate --target rust --spec ../spec --out ../runtimes/rust/src/generated +node dist/cli.js generate --target c --spec ../spec --out ../runtimes/c/src/generated +``` + +`--check` validates and lists targets without writing files. diff --git a/docs/ESCAPE_HATCHES.md b/docs/ESCAPE_HATCHES.md new file mode 100644 index 0000000..520d2d4 --- /dev/null +++ b/docs/ESCAPE_HATCHES.md @@ -0,0 +1,118 @@ +# Escape hatches + +For the ~5% of ACARS plugins whose logic resists clean declarative spec — heuristic offset computation, recursive composition (CBand → MessageDecoder), version-dependent CRC dispatch, complex variant slicing — ADS provides escape hatches at three levels. + +## When to use one + +Use an escape hatch when adding new DSL primitives would be more complex than the logic itself. The threshold is roughly: + +- More than ~5 lines of branching logic that can't be expressed with `variants` + `when` +- Computed offsets that depend on parsed-so-far content (not just message structure) +- Recursive invocations of the message decoder +- Format-specific algorithms (CRC variants, base85 dialects) that warrant a single canonical implementation + +If you find yourself wanting more than one escape hatch per spec, consider whether the DSL needs another primitive. + +## Levels + +### Whole-plugin + +```yaml +parse: + custom: arinc_702_dispatch +formatted: + description: "ARINC 702 Message" + custom: arinc_702_format +``` + +The entire decode flow runs in a hand-written function. Generated code is a one-liner that delegates. + +### Field-level + +```yaml +- name: variant_result + from: $fields + decode: + fn: custom + custom: label_4a_variant_2_decode +``` + +The value of `from` plus the field's args is passed to the named function; its return becomes the field value. + +### Formatter-level + +```yaml +formatted: + description: "OHMA Message" + items: + - { type: position, ... } + - { type: custom, custom: ohma_payload_item } +``` + +Single formatter item dispatched to a hand-written function. + +## Per-language conventions + +Hand-written functions live in `runtimes//escape_hatches/`. They MUST exist with the exact name declared in the spec (codegen does not check this — language build does). + +### TypeScript + +```ts +// runtimes/typescript/escape_hatches/arinc_702.ts +import type { DecoderPlugin, DecodeResult, Message, Options } from "../index.js"; + +export function arinc_702_dispatch( + plugin: DecoderPlugin, + message: Message, + result: DecodeResult, + options: Options, +): DecodeResult { + // hand-written port of acars-decoder-typescript/lib/plugins/ARINC_702.ts +} +``` + +### Rust + +```rust +// runtimes/rust/src/escape_hatches/arinc_702.rs +use crate::{DecodeResult, Message, Options, Plugin}; + +pub fn arinc_702_dispatch( + plugin: &dyn Plugin, + message: &Message, + result: &mut DecodeResult, + options: &Options, +) -> DecodeResult { /* ... */ } +``` + +### C + +```c +/* runtimes/c/src/escape_hatches/arinc_702.c */ +#include "ads_runtime.h" + +DecodeResult *arinc_702_dispatch( + const Plugin *plugin, + const Message *message, + DecodeResult *result, + const Options *options +); +``` + +## Current escape hatch inventory + +These survive declarative description as of v1: + +| Hatch | Plugin | Why | +|----------------------------------------|------------------|------------------------------------------------| +| `arinc_702_dispatch` | ARINC_702 | Heuristic offset computation, fallback chain | +| `arinc_702_format` | ARINC_702 | Result merging from recursive helper | +| `label_4a_variant_2_decode` | Label_4A | Substring slicing of fields[0] and fields[1] | +| `label_4a_variant_3_position` | Label_4A | Concat + sanitize fields[4]+fields[5] | +| `label_4a_format` | Label_4A | Variant-specific item ordering | +| `ohma_unwrap_message` | Label_H1_OHMA | JSON-in-JSON unwrap (try/catch chains) | +| `ohma_message_item` | Label_H1_OHMA | Pretty-print + fallback | +| `parse_flight_level_or_ground` | Label_44_POS | "GRD"/"***" → 0 sentinel | +| `flight_level_to_altitude_feet` | Label_44_POS | flight_level * 100 | + +When porting a TS plugin reveals a needed hatch, file a follow-up to evaluate adding the equivalent DSL primitive in ADS v1.1. diff --git a/runtimes/c/CMakeLists.txt b/runtimes/c/CMakeLists.txt new file mode 100644 index 0000000..46d76ad --- /dev/null +++ b/runtimes/c/CMakeLists.txt @@ -0,0 +1,47 @@ +cmake_minimum_required(VERSION 3.16) +project(ads_runtime_c C) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() + +add_compile_options(-Wall -Wextra -Wpedantic -Werror=implicit-function-declaration) + +# Optional sanitizer build: cmake -DCMAKE_BUILD_TYPE=Asan +set(CMAKE_C_FLAGS_ASAN "-O0 -g -fsanitize=address,undefined -fno-omit-frame-pointer") +set(CMAKE_LINKER_FLAGS_ASAN "-fsanitize=address,undefined") + +find_package(ZLIB) +find_library(CJSON_LIB cjson) +find_path(CJSON_INCLUDE_DIR cjson/cJSON.h) + +add_library(ads_runtime_c STATIC + src/plugin.c + src/helpers.c + src/coordinate.c + src/ascii85.c + src/crc.c + src/result_formatter.c + src/string_list.c + src/regex.c +) +target_include_directories(ads_runtime_c PUBLIC include) +target_include_directories(ads_runtime_c PRIVATE ${CJSON_INCLUDE_DIR}) +target_link_libraries(ads_runtime_c PUBLIC ${CJSON_LIB}) +if(ZLIB_FOUND) + target_link_libraries(ads_runtime_c PUBLIC ZLIB::ZLIB) + target_compile_definitions(ads_runtime_c PRIVATE ADS_HAVE_ZLIB) +endif() + +# Optionally build a shared library too. +option(ADS_RUNTIME_SHARED "Also build a shared library variant" OFF) +if(ADS_RUNTIME_SHARED) + add_library(ads_runtime_c_shared SHARED ${ADS_RUNTIME_SOURCES}) + set_target_properties(ads_runtime_c_shared PROPERTIES OUTPUT_NAME ads_runtime_c) +endif() + +enable_testing() diff --git a/runtimes/c/README.md b/runtimes/c/README.md new file mode 100644 index 0000000..e291d27 --- /dev/null +++ b/runtimes/c/README.md @@ -0,0 +1,78 @@ +# ads_runtime_c + +Pure-C runtime library for ADS-generated ACARS decoder plugins. + +Consumed by `acars-decoder-c` via CMake `add_subdirectory()` on the +`airframes-decoder` submodule: + +```cmake +add_subdirectory(vendor/airframes-decoder/runtimes/c) +target_link_libraries(acars_decoder PRIVATE ads_runtime_c) +``` + +## Layout + +``` +runtimes/c/ +├── CMakeLists.txt +├── README.md +├── include/ +│ ├── ads_runtime.h # core types (Message, Options, DecodeResult, Qualifiers, plugin descriptor, value/bytes/string_list) +│ ├── ads_helpers.h # decode-fn + formatter + split/regex/substring helpers +│ └── ads_escape_hatches.h # placeholder header for per-plugin custom fns +└── src/ + ├── plugin.c # ads_result_* lifecycle + ads_value_* + ├── helpers.c # decode-fn helpers (integer, float, timestamp, callsign, airport, ...) + ├── coordinate.c # combined NS/EW + decimal-minutes parsers + ├── ascii85.c # ASCII85 + base64 + hex decoders, inflate + ├── crc.c # CRC-16 IBM-SDLC reversed + GENIBUS + ├── result_formatter.c # ads_fmt_* item push helpers + ├── string_list.c # ads_split, ads_substring, ads_str_in + └── regex.c # POSIX-regex based ads_regex_* (Stage 2: replace with PCRE2 for named groups) +``` + +## Dependencies + +- `cjson` — used internally for the raw-fields bag and JSON-encoded args parsing. +- `zlib` (optional, gates ads_inflate behind ADS_HAVE_ZLIB) +- POSIX regex (POSIX-2008+ libc). Stage 2 may replace with PCRE2 for named-capture support. + +## Build + +```bash +mkdir build && cd build +cmake .. -DCMAKE_BUILD_TYPE=Release +cmake --build . +``` + +For sanitizers: + +```bash +cmake .. -DCMAKE_BUILD_TYPE=Asan +cmake --build . +``` + +## Generated plugin shape + +```c +#include "ads_runtime.h" +#include "ads_helpers.h" +#include "ads_escape_hatches.h" + +ads_decode_result_t *label_10_pos_decode(const ads_message_t *msg, const ads_options_t *opts); +ads_qualifiers_t label_10_pos_qualifiers(void); +extern const ads_plugin_descriptor_t label_10_pos_descriptor; +``` + +## Caveats / Stage 2 follow-ups + +- POSIX regex doesn't support PCRE-style named groups (`(?...)`). For + the regex-using specs (Label_44_POS), `ads_regex_group("name")` currently + resolves numeric indices only. Plan to swap in PCRE2 in Stage 2. +- Args still flow as JSON strings (`JSON.stringify(args)`); typed-args + codegen is a v1.1 win for hot paths. +- CRC lookup tables emitted from `spec/shared/crc_tables.yaml` are pending; + current bitwise impl is correct and table-free. +- Memory-management: `ads_value_t *` returned by decode helpers transfers + ownership to the result on `ads_result_raw_set` (which frees the wrapper). + Be careful in escape hatches not to double-free. diff --git a/runtimes/c/include/ads_escape_hatches.h b/runtimes/c/include/ads_escape_hatches.h new file mode 100644 index 0000000..097edb4 --- /dev/null +++ b/runtimes/c/include/ads_escape_hatches.h @@ -0,0 +1,107 @@ +/* ads_escape_hatches.h — prototypes for per-plugin custom functions. + * + * Implementations live in each language repo's src/escape_hatches/ + * (the central runtime can't implement them — they're plugin-specific). + * + * Three hatch shapes: + * + * Whole-plugin parse hatch: called from a generated plugin's decode(): + * ads_decode_result_t *ads_hatch_(const ads_message_t *msg, + * ads_decode_result_t *result, + * const ads_options_t *opts); + * + * Field-level decode hatch: called from a generated field assignment: + * ads_value_t *ads_hatch_(const char *value, const char *args_json); + * (or const ads_value_t * for value-from-prior-step variants) + * + * Formatter hatch: called from a generated formatter item: + * void ads_hatch_(ads_decode_result_t *result); + */ +#ifndef ADS_ESCAPE_HATCHES_H +#define ADS_ESCAPE_HATCHES_H + +#include "ads_runtime.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* ─── Whole-plugin parse hatches (61 plugins) ────────────────────────────── */ + +ads_decode_result_t *ads_hatch_arinc_702_dispatch(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_cband_dispatch(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_colon_comma_parse(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_10_ldr_decode(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_10_slash_decode(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_12_n_space_decode(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_12_pos_decode(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_13_18_slash_decode(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_15_decode(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_15_fst_decode(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_16_autpos_decode(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_16_honeywell_decode(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_16_n_space_decode(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_16_posa1_decode(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_16_tod_decode(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_1l_070_decode(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_1l_3line_decode(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_1l_660_decode(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_1l_slash_decode(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_1m_slash_decode(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_20_cfb01_decode(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_20_pos_decode(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_21_pos_decode(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_22_off_decode(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_22_pos_decode(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_24_slash_decode(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_2p_fm3_decode(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_2p_fm4_decode(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_2p_fm5_decode(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_30_slash_ea_decode(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_44_slash_decode(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_4a_dispatch(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_4a_01_decode(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_4a_dis_decode(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_4a_door_decode(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_4a_slash_01_decode(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_4n_decode(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_4t_agfsr_parse(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_4t_eta_parse(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_58_parse(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_5z_slash_parse(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_80_parse(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_83_parse(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_8e_parse(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_b6_forwardslash_parse(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_h1_atis_parse(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_h1_ezf_parse(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_h1_flr_parse(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_h1_m_pos_parse(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_h1_ofp_parse(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_h1_paren_parse(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_h1_starpos_parse(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_h1_wrn_parse(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_h2_02e_dispatch(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_hx_dispatch(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_ma_dispatch(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_qp_dispatch(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_qq_dispatch(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_qr_dispatch(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_qs_dispatch(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); +ads_decode_result_t *ads_hatch_label_sq_dispatch(const ads_message_t *msg, ads_decode_result_t *result, const ads_options_t *opts); + +/* ─── Field-level decode hatches ──────────────────────────────────────────── */ + +ads_value_t *ads_hatch_parse_flight_level_or_ground(const char *value, const char *args_json); +ads_value_t *ads_hatch_flight_level_to_altitude_feet(const ads_value_t *value, const char *args_json); +ads_value_t *ads_hatch_ohma_unwrap_message(const char *value, const char *args_json); + +/* ─── Formatter-level hatches ─────────────────────────────────────────────── */ + +void ads_hatch_ohma_message_item(ads_decode_result_t *result); + +#ifdef __cplusplus +} +#endif + +#endif /* ADS_ESCAPE_HATCHES_H */ diff --git a/runtimes/c/include/ads_helpers.h b/runtimes/c/include/ads_helpers.h new file mode 100644 index 0000000..c71f3bb --- /dev/null +++ b/runtimes/c/include/ads_helpers.h @@ -0,0 +1,82 @@ +/* ads_helpers.h — decode-fn helpers the codegen emits calls into. + * + * Each function corresponds to a `decode.fn` value in spec YAML. Mirrors + * runtimes/typescript/helpers.ts and runtimes/rust/src/helpers.rs. + * + * args_json is a JSON-encoded args object; helpers parse on demand. v1.1 + * should move to typed args. + */ +#ifndef ADS_HELPERS_H +#define ADS_HELPERS_H + +#include "ads_runtime.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* ─── Strings + lists + regex ────────────────────────────────────────────── */ + +ads_str_list_t ads_split(const char *s, const char *delimiter); +const char *ads_substring(const char *s, int start, int end); /* end < 0 = to end */ +bool ads_str_in(const char *const *list, size_t count, const char *needle); + +ads_regex_match_t *ads_regex_match_new(const char *pattern, const char *input); +bool ads_regex_match_ok(const ads_regex_match_t *m); +const char *ads_regex_group(const ads_regex_match_t *m, const char *name); +void ads_regex_match_free(ads_regex_match_t *m); +/* Codegen-side convenience returning by value. Wraps _new + ok(). */ +#define ads_regex_match(pattern, input) (*ads_regex_match_new(pattern, input)) +bool ads_regex_test(const char *pattern, const char *input); + +/* ─── Decode-fn helpers ──────────────────────────────────────────────────── */ +/* Uniform signature: every decode-fn helper accepts (value, args_json), + * where args_json is "{}" when the spec specifies no args. Simplifies the + * emitter and matches the Rust runtime's convergence pass. */ + +ads_value_t *ads_decode_coordinate(const char *value, const char *args_json); +ads_value_t *ads_decode_coordinate_decimal_minutes(const char *value, const char *args_json); +ads_value_t *ads_decode_integer(const char *value, const char *args_json); +ads_value_t *ads_decode_float(const char *value, const char *args_json); +ads_value_t *ads_decode_string(const char *value, const char *args_json); +ads_value_t *ads_decode_trim(const char *value, const char *args_json); +ads_value_t *ads_decode_uppercase(const char *value, const char *args_json); +ads_value_t *ads_decode_lowercase(const char *value, const char *args_json); +ads_value_t *ads_decode_timestamp_hhmmss(const char *value, const char *args_json); +ads_value_t *ads_decode_callsign(const char *value, const char *args_json); +ads_value_t *ads_decode_tail_number(const char *value, const char *args_json); +ads_value_t *ads_decode_flight_number(const char *value, const char *args_json); +ads_value_t *ads_decode_airport(const char *value, const char *args_json); + +/* ─── Binary / encoding ──────────────────────────────────────────────────── */ + +ads_bytes_t ads_base64_decode(const char *s); +ads_bytes_t ads_inflate(ads_bytes_t input, size_t offset, const char *format); +ads_bytes_t ads_decode_ascii85(const char *s); +ads_bytes_t ads_hex_decode(const char *s); +char *ads_text_decode(ads_bytes_t bytes, const char *encoding); /* malloc'd */ + +uint32_t ads_bitslice(uint32_t byte, uint8_t bit_start, uint8_t bit_end); +uint32_t ads_concat_bits(const uint32_t *values, size_t count); + +/* ─── Formatter helpers (push items onto result.formatted.items) ──────── */ + +void ads_fmt_position(ads_decode_result_t *r, ads_value_t *lat, ads_value_t *lon); +void ads_fmt_position_value(ads_decode_result_t *r, ads_value_t *position); +void ads_fmt_altitude(ads_decode_result_t *r, ads_value_t *v); +void ads_fmt_speed(ads_decode_result_t *r, ads_value_t *v); +void ads_fmt_heading(ads_decode_result_t *r, ads_value_t *v); +void ads_fmt_timestamp(ads_decode_result_t *r, ads_value_t *v); +void ads_fmt_callsign(ads_decode_result_t *r, ads_value_t *v); +void ads_fmt_flight_number(ads_decode_result_t *r, ads_value_t *v); +void ads_fmt_tail(ads_decode_result_t *r, ads_value_t *v); +void ads_fmt_departure_airport(ads_decode_result_t *r, ads_value_t *v); +void ads_fmt_arrival_airport(ads_decode_result_t *r, ads_value_t *v); +void ads_fmt_fuel(ads_decode_result_t *r, ads_value_t *v); +void ads_fmt_unknown_arr(ads_decode_result_t *r, const char *const *values, size_t count); + +#ifdef __cplusplus +} +#endif + +#endif /* ADS_HELPERS_H */ diff --git a/runtimes/c/include/ads_runtime.h b/runtimes/c/include/ads_runtime.h new file mode 100644 index 0000000..7b50f63 --- /dev/null +++ b/runtimes/c/include/ads_runtime.h @@ -0,0 +1,119 @@ +/* ads_runtime.h — public API for the ADS C runtime. + * + * Generated plugin code (output of `ads-gen --target c`) includes this header + * plus ads_helpers.h and ads_escape_hatches.h: + * + * #include "ads_runtime.h" + * #include "ads_helpers.h" + * #include "ads_escape_hatches.h" + * + * ads_decode_result_t *label_10_pos_decode(const ads_message_t *msg, + * const ads_options_t *opts); + * + * Source of truth: acars-decoder-typescript. Behavior MUST match the TS + * reference byte-for-byte; the shared corpus enforces this. + */ +#ifndef ADS_RUNTIME_H +#define ADS_RUNTIME_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ─── Core types ─────────────────────────────────────────────────────────── */ + +typedef struct { + const char *label; /* required */ + const char *sublabel; /* optional, may be NULL */ + const char *text; /* required */ +} ads_message_t; + +typedef struct { + bool debug; +} ads_options_t; + +typedef struct { + const char *const *labels; /* NULL-terminated array of label literals */ + const char *const *preambles; /* NULL-terminated; may itself be NULL */ +} ads_qualifiers_t; + +typedef enum { + ADS_DECODE_LEVEL_NONE = 0, + ADS_DECODE_LEVEL_PARTIAL, + ADS_DECODE_LEVEL_FULL, +} ads_decode_level_t; + +/* Opaque value type. Backed by cJSON internally so plugins can store + * arbitrary nested data. Use ads_value_from_* / ads_value_to_* helpers. */ +typedef struct ads_value ads_value_t; + +/* Opaque dynamic string list. Holds parts produced by ads_split / regex + * matches. Items accessed via .items[N]; size via .count. */ +typedef struct { + char **items; + size_t count; + size_t capacity; +} ads_str_list_t; + +/* Opaque dynamic byte buffer (ads_bytes_t). */ +typedef struct { + uint8_t *data; + size_t len; +} ads_bytes_t; + +/* Regex match result. Backed by POSIX regex_t + match buffer. */ +typedef struct ads_regex_match ads_regex_match_t; + +/* DecodeResult — the structured output of a plugin's decode(). */ +typedef struct ads_decode_result ads_decode_result_t; + +/* Plugin descriptor used by the dispatcher's registry. */ +typedef struct { + const char *name; + ads_qualifiers_t (*qualifiers)(void); + ads_decode_result_t *(*decode)(const ads_message_t *msg, const ads_options_t *opts); +} ads_plugin_descriptor_t; + +/* ─── DecodeResult API ───────────────────────────────────────────────────── */ + +ads_decode_result_t *ads_result_new(const char *plugin_name, + const char *description, + const ads_message_t *msg); +void ads_result_free(ads_decode_result_t *result); + +void ads_result_set_decoded(ads_decode_result_t *result, bool decoded); +ads_decode_result_t *ads_result_fail_unknown(ads_decode_result_t *result, const char *text); + +void ads_result_raw_set(ads_decode_result_t *result, const char *key, ads_value_t *value); + +/* Accessors for callers (mainly tests, escape hatches, formatters). */ +ads_value_t *ads_result_raw_get(const ads_decode_result_t *result, const char *key); +char *ads_result_to_json(const ads_decode_result_t *result); /* malloc'd; caller frees */ + +/* ─── Value API ──────────────────────────────────────────────────────────── */ + +ads_value_t *ads_value_from_string(const char *s); +ads_value_t *ads_value_from_double(double n); +ads_value_t *ads_value_from_int(int64_t n); +ads_value_t *ads_value_from_bool(bool b); +ads_value_t *ads_value_null(void); +void ads_value_free(ads_value_t *v); + +bool ads_value_as_double(const ads_value_t *v, double *out); +bool ads_value_as_int(const ads_value_t *v, int64_t *out); +const char *ads_value_as_string(const ads_value_t *v); + +/* ─── Buffer / string helpers ────────────────────────────────────────────── */ + +void ads_str_list_free(ads_str_list_t *list); +void ads_bytes_free(ads_bytes_t *b); + +#ifdef __cplusplus +} +#endif + +#endif /* ADS_RUNTIME_H */ diff --git a/runtimes/c/src/ascii85.c b/runtimes/c/src/ascii85.c new file mode 100644 index 0000000..7f33d7a --- /dev/null +++ b/runtimes/c/src/ascii85.c @@ -0,0 +1,144 @@ +/* Pure-C ASCII85 / Base85 decoder + base64 + hex + inflate (zlib). */ + +#include "ads_helpers.h" +#include +#include +#include +#include + +#ifdef ADS_HAVE_ZLIB +#include +#endif + +ads_bytes_t ads_decode_ascii85(const char *s) { + ads_bytes_t out = {0}; + if (!s) return out; + const char *p = s; + while (*p && isspace((unsigned char)*p)) p++; + if (strncmp(p, "<~", 2) == 0) p += 2; + size_t cap = strlen(p) * 4 / 5 + 4; + uint8_t *buf = malloc(cap); + if (!buf) return out; + size_t blen = 0; + uint32_t accum = 0; + int count = 0; + + while (*p) { + char ch = *p++; + if (ch == '~') break; /* trailing ~> */ + if (isspace((unsigned char)ch)) continue; + if (ch == 'z' && count == 0) { + buf[blen++] = 0; buf[blen++] = 0; buf[blen++] = 0; buf[blen++] = 0; + continue; + } + if (ch < 33 || ch > 117) { free(buf); return out; } + accum = accum * 85u + (uint32_t)(ch - 33); + count++; + if (count == 5) { + buf[blen++] = (uint8_t)(accum >> 24); + buf[blen++] = (uint8_t)(accum >> 16); + buf[blen++] = (uint8_t)(accum >> 8); + buf[blen++] = (uint8_t)accum; + accum = 0; count = 0; + } + } + if (count > 0) { + for (int i = count; i < 5; i++) accum = accum * 85u + 84u; + buf[blen++] = (uint8_t)(accum >> 24); + if (count >= 3) buf[blen++] = (uint8_t)(accum >> 16); + if (count >= 4) buf[blen++] = (uint8_t)(accum >> 8); + } + out.data = buf; + out.len = blen; + return out; +} + +ads_bytes_t ads_base64_decode(const char *s) { + static int table[256]; + static int initialized = 0; + if (!initialized) { + for (int i = 0; i < 256; i++) table[i] = -1; + const char *alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + for (int i = 0; i < 64; i++) table[(int)alphabet[i]] = i; + initialized = 1; + } + ads_bytes_t out = {0}; + if (!s) return out; + size_t slen = strlen(s); + out.data = malloc(slen); + if (!out.data) return out; + uint32_t buf = 0; + int bits = 0; + for (size_t i = 0; i < slen; i++) { + unsigned char c = (unsigned char)s[i]; + if (c == '=') break; + if (isspace(c)) continue; + int v = table[c]; + if (v < 0) continue; + buf = (buf << 6) | (uint32_t)v; + bits += 6; + if (bits >= 8) { + bits -= 8; + out.data[out.len++] = (uint8_t)(buf >> bits) & 0xff; + } + } + return out; +} + +ads_bytes_t ads_hex_decode(const char *s) { + ads_bytes_t out = {0}; + if (!s) return out; + size_t slen = strlen(s); + out.data = malloc(slen / 2 + 1); + if (!out.data) return out; + char tmp[3] = {0}; + int half = 0; + for (size_t i = 0; i < slen; i++) { + unsigned char c = (unsigned char)s[i]; + if (!isxdigit(c)) continue; + tmp[half++] = (char)c; + if (half == 2) { + out.data[out.len++] = (uint8_t)strtoul(tmp, NULL, 16); + half = 0; + tmp[0] = tmp[1] = 0; + } + } + return out; +} + +char *ads_text_decode(ads_bytes_t bytes, const char *encoding) { + (void)encoding; /* ACARS messages are ASCII / UTF-8 safe */ + char *out = malloc(bytes.len + 1); + if (!out) return NULL; + memcpy(out, bytes.data, bytes.len); + out[bytes.len] = '\0'; + return out; +} + +ads_bytes_t ads_inflate(ads_bytes_t input, size_t offset, const char *format) { + ads_bytes_t out = {0}; +#ifdef ADS_HAVE_ZLIB + if (input.len <= offset) return out; + const uint8_t *src = input.data + offset; + size_t src_len = input.len - offset; + size_t cap = src_len * 8 + 64; + out.data = malloc(cap); + if (!out.data) return out; + z_stream zs = {0}; + int wbits = (strcmp(format ? format : "raw", "gzip") == 0) ? 31 + : (strcmp(format ? format : "raw", "zlib") == 0) ? 15 + : -15; + if (inflateInit2(&zs, wbits) != Z_OK) { free(out.data); out.data = NULL; return out; } + zs.next_in = (Bytef *)src; + zs.avail_in = (uInt)src_len; + zs.next_out = out.data; + zs.avail_out = (uInt)cap; + int rc = inflate(&zs, Z_FINISH); + out.len = zs.total_out; + inflateEnd(&zs); + if (rc != Z_STREAM_END) { free(out.data); out.data = NULL; out.len = 0; } +#else + (void)input; (void)offset; (void)format; +#endif + return out; +} diff --git a/runtimes/c/src/coordinate.c b/runtimes/c/src/coordinate.c new file mode 100644 index 0000000..db9cbc7 --- /dev/null +++ b/runtimes/c/src/coordinate.c @@ -0,0 +1,88 @@ +/* Coordinate parsers: combined NS/EW + decimal-minutes. + * Mirrors runtimes/typescript/utils/coordinate_utils.ts. */ + +#include "ads_helpers.h" +#include +#include +#include +#include + +struct ads_value { cJSON *node; bool owns; }; + +static ads_value_t *combined_to_value(double lat, double lon) { + cJSON *node = cJSON_CreateObject(); + cJSON_AddNumberToObject(node, "latitude", lat); + cJSON_AddNumberToObject(node, "longitude", lon); + ads_value_t *v = calloc(1, sizeof(*v)); + v->node = node; + v->owns = true; + return v; +} + +static bool parse_combined(const char *s, double *lat_out, double *lon_out, bool dec_min) { + size_t slen = strlen(s); + if (slen < 13) return false; + char first = s[0]; + int middle_idx = (s[6] == ' ') ? 7 : 6; + int lon_start = (s[6] == ' ') ? 8 : 7; + if (slen < (size_t)(lon_start + 6)) return false; + char middle = s[middle_idx]; + if ((first != 'N' && first != 'S') || (middle != 'E' && middle != 'W')) return false; + char lat_buf[6] = {0}, lon_buf[7] = {0}; + memcpy(lat_buf, s + 1, 5); + memcpy(lon_buf, s + lon_start, 6); + char *endp; + double lat_n = strtod(lat_buf, &endp); + if (*endp != '\0') return false; + double lon_n = strtod(lon_buf, &endp); + if (*endp != '\0') return false; + double lat_sign = (first == 'N') ? 1.0 : -1.0; + double lon_sign = (middle == 'E') ? 1.0 : -1.0; + if (dec_min) { + double lat_deg = (int64_t)(lat_n / 1000.0); + double lat_min = fmod(lat_n, 1000.0) / 10.0; + double lon_deg = (int64_t)(lon_n / 1000.0); + double lon_min = fmod(lon_n, 1000.0) / 10.0; + *lat_out = (lat_deg + lat_min / 60.0) * lat_sign; + *lon_out = (lon_deg + lon_min / 60.0) * lon_sign; + } else { + *lat_out = (lat_n / 1000.0) * lat_sign; + *lon_out = (lon_n / 1000.0) * lon_sign; + } + return true; +} + +ads_value_t *ads_decode_coordinate(const char *value, const char *args_json) { + cJSON *args = args_json ? cJSON_Parse(args_json) : NULL; + const cJSON *style = args ? cJSON_GetObjectItemCaseSensitive(args, "style") : NULL; + if (style && cJSON_IsString(style) && strcmp(style->valuestring, "combined") == 0) { + double lat, lon; + bool ok = parse_combined(value, &lat, &lon, false); + cJSON_Delete(args); + return ok ? combined_to_value(lat, lon) : ads_value_null(); + } + /* Single-axis: / divisor */ + double divisor = 1000.0; + if (args) { + cJSON *d = cJSON_GetObjectItemCaseSensitive(args, "divisor"); + if (cJSON_IsNumber(d)) divisor = d->valuedouble; + } + cJSON_Delete(args); + if (!value || !*value) return ads_value_null(); + char prefix = value[0]; + double sign = (prefix == 'N' || prefix == 'E') ? 1.0 : -1.0; + /* Skip whitespace then parse the digit run. */ + const char *p = value + 1; + while (*p == ' ') p++; + char *endp; + double n = strtod(p, &endp); + if (endp == p) return ads_value_null(); + return ads_value_from_double(sign * n / divisor); +} + +ads_value_t *ads_decode_coordinate_decimal_minutes(const char *value, const char *args_json) { + (void)args_json; + double lat, lon; + if (!parse_combined(value, &lat, &lon, true)) return ads_value_null(); + return combined_to_value(lat, lon); +} diff --git a/runtimes/c/src/crc.c b/runtimes/c/src/crc.c new file mode 100644 index 0000000..e596e7d --- /dev/null +++ b/runtimes/c/src/crc.c @@ -0,0 +1,35 @@ +/* CRC-16 helpers — bitwise, table-free. Mirrors runtimes/rust/src/crc.rs and + * runtimes/typescript/utils/arinc_702_helper.ts (post-fix). */ + +#include +#include + +uint16_t ads_crc16_ibm_sdlc_rev(const uint8_t *data, size_t len) { + uint32_t crc = 0xffff; + for (size_t i = 0; i < len; i++) { + crc ^= data[i]; + for (int b = 0; b < 8; b++) { + if (crc & 0x0001) crc = (crc >> 1) ^ 0x8408; + else crc = crc >> 1; + } + } + crc = (crc ^ 0xffff) & 0xffff; + uint16_t n1 = (uint16_t)((crc >> 12) & 0xf); + uint16_t n2 = (uint16_t)((crc >> 8) & 0xf); + uint16_t n3 = (uint16_t)((crc >> 4) & 0xf); + uint16_t n4 = (uint16_t)(crc & 0xf); + return (uint16_t)((n4 << 12) | (n3 << 8) | (n2 << 4) | n1); +} + +uint16_t ads_crc16_genibus(const uint8_t *data, size_t len) { + uint32_t crc = 0xffff; + const uint32_t polynomial = 0x1021; + for (size_t i = 0; i < len; i++) { + crc ^= ((uint32_t)data[i]) << 8; + for (int b = 0; b < 8; b++) { + if (crc & 0x8000) crc = ((crc << 1) ^ polynomial) & 0xffff; + else crc = (crc << 1) & 0xffff; + } + } + return (uint16_t)((crc ^ 0xffff) & 0xffff); +} diff --git a/runtimes/c/src/helpers.c b/runtimes/c/src/helpers.c new file mode 100644 index 0000000..1c6bd7e --- /dev/null +++ b/runtimes/c/src/helpers.c @@ -0,0 +1,172 @@ +/* Decode-fn helpers (numerics, strings, identifiers, encodings, bitfields). + * + * Uniform signature: every helper accepts (value, args_json). Args-free + * helpers ignore the second arg. Matches the Rust runtime's pattern. + */ + +#include "ads_helpers.h" +#include +#include +#include +#include +#include + +struct ads_value { cJSON *node; bool owns; }; + +static char *substr_copy(const char *s, size_t start, size_t len) { + size_t slen = strlen(s); + if (start >= slen) return strdup(""); + if (start + len > slen) len = slen - start; + char *out = malloc(len + 1); + memcpy(out, s + start, len); + out[len] = '\0'; + return out; +} + +ads_value_t *ads_decode_integer(const char *value, const char *args_json) { + cJSON *args = args_json ? cJSON_Parse(args_json) : NULL; + size_t start = 0; + long len = -1; + double mult = 1.0; + if (args) { + cJSON *ss = cJSON_GetObjectItemCaseSensitive(args, "substring_start"); + cJSON *sl = cJSON_GetObjectItemCaseSensitive(args, "substring_length"); + cJSON *m = cJSON_GetObjectItemCaseSensitive(args, "multiplier"); + if (cJSON_IsNumber(ss)) start = (size_t)ss->valuedouble; + if (cJSON_IsNumber(sl)) len = (long)sl->valuedouble; + if (cJSON_IsNumber(m)) mult = m->valuedouble; + } + cJSON_Delete(args); + if (!value) return ads_value_from_double(0.0); + char *s = len >= 0 ? substr_copy(value, start, (size_t)len) + : substr_copy(value, start, strlen(value)); + double n = atof(s) * mult; + free(s); + return ads_value_from_double(n); +} + +ads_value_t *ads_decode_float(const char *value, const char *args_json) { + (void)args_json; + return ads_value_from_double(value ? atof(value) : 0.0); +} + +ads_value_t *ads_decode_string(const char *value, const char *args_json) { + (void)args_json; + return ads_value_from_string(value ? value : ""); +} + +ads_value_t *ads_decode_callsign(const char *value, const char *args_json) { + return ads_decode_string(value, args_json); +} + +ads_value_t *ads_decode_flight_number(const char *value, const char *args_json) { + return ads_decode_string(value, args_json); +} + +static char *trim_inplace(char *s) { + char *start = s; + while (*start && isspace((unsigned char)*start)) start++; + if (start != s) memmove(s, start, strlen(start) + 1); + size_t end = strlen(s); + while (end > 0 && isspace((unsigned char)s[end - 1])) s[--end] = '\0'; + return s; +} + +ads_value_t *ads_decode_trim(const char *value, const char *args_json) { + (void)args_json; + char *copy = strdup(value ? value : ""); + trim_inplace(copy); + ads_value_t *v = ads_value_from_string(copy); + free(copy); + return v; +} + +ads_value_t *ads_decode_uppercase(const char *value, const char *args_json) { + (void)args_json; + char *copy = strdup(value ? value : ""); + for (char *p = copy; *p; p++) *p = (char)toupper((unsigned char)*p); + ads_value_t *v = ads_value_from_string(copy); + free(copy); + return v; +} + +ads_value_t *ads_decode_lowercase(const char *value, const char *args_json) { + (void)args_json; + char *copy = strdup(value ? value : ""); + for (char *p = copy; *p; p++) *p = (char)tolower((unsigned char)*p); + ads_value_t *v = ads_value_from_string(copy); + free(copy); + return v; +} + +ads_value_t *ads_decode_airport(const char *value, const char *args_json) { + (void)args_json; + char *copy = strdup(value ? value : ""); + trim_inplace(copy); + for (char *p = copy; *p; p++) *p = (char)toupper((unsigned char)*p); + ads_value_t *v = ads_value_from_string(copy); + free(copy); + return v; +} + +ads_value_t *ads_decode_tail_number(const char *value, const char *args_json) { + char *copy = strdup(value ? value : ""); + trim_inplace(copy); + cJSON *args = args_json ? cJSON_Parse(args_json) : NULL; + const cJSON *strip = args ? cJSON_GetObjectItemCaseSensitive(args, "strip_chars") : NULL; + if (strip && cJSON_IsString(strip)) { + for (const char *p = strip->valuestring; *p; p++) { + char *src = copy, *dst = copy; + while (*src) { + if (*src != *p) *dst++ = *src; + src++; + } + *dst = '\0'; + } + } + cJSON_Delete(args); + ads_value_t *v = ads_value_from_string(copy); + free(copy); + return v; +} + +static int64_t hhmmss_to_tod(const char *s) { + if (!s || strlen(s) < 6) return 0; + char buf[3] = {0}; + memcpy(buf, s, 2); int h = atoi(buf); + memcpy(buf, s + 2, 2); int m = atoi(buf); + memcpy(buf, s + 4, 2); int sec = atoi(buf); + return (int64_t)h * 3600 + (int64_t)m * 60 + sec; +} + +ads_value_t *ads_decode_timestamp_hhmmss(const char *value, const char *args_json) { + cJSON *args = args_json ? cJSON_Parse(args_json) : NULL; + const cJSON *app = args ? cJSON_GetObjectItemCaseSensitive(args, "append") : NULL; + char *combined; + if (app && cJSON_IsString(app)) { + size_t n = strlen(value ? value : "") + strlen(app->valuestring) + 1; + combined = malloc(n); + snprintf(combined, n, "%s%s", value ? value : "", app->valuestring); + } else { + combined = strdup(value ? value : ""); + } + cJSON_Delete(args); + int64_t tod = hhmmss_to_tod(combined); + free(combined); + return ads_value_from_int(tod); +} + +/* ─── bitslice + concat_bits ─────────────────────────────────────────────── */ + +uint32_t ads_bitslice(uint32_t byte, uint8_t bit_start, uint8_t bit_end) { + uint8_t width = bit_end - bit_start + 1; + int shift = 8 - bit_end - 1; + uint32_t mask = (1u << width) - 1u; + return (byte >> shift) & mask; +} + +uint32_t ads_concat_bits(const uint32_t *values, size_t count) { + uint32_t acc = 0; + for (size_t i = 0; i < count; i++) acc = (acc << 8) | values[i]; + return acc; +} diff --git a/runtimes/c/src/plugin.c b/runtimes/c/src/plugin.c new file mode 100644 index 0000000..60baf36 --- /dev/null +++ b/runtimes/c/src/plugin.c @@ -0,0 +1,175 @@ +/* DecodeResult lifecycle + value helpers backed by cJSON. */ + +#include "ads_runtime.h" +#include +#include +#include + +struct ads_value { + cJSON *node; + bool owns; +}; + +struct ads_decode_result { + bool decoded; + char *plugin_name; + char *description; + ads_decode_level_t decode_level; + cJSON *raw; /* object */ + cJSON *items; /* array of {kind, code, label, value} */ + char *remaining; /* may be NULL */ + char *message_json; /* serialized snapshot if set_message was called */ +}; + +static char *xstrdup(const char *s) { + if (!s) return NULL; + char *r = malloc(strlen(s) + 1); + if (r) strcpy(r, s); + return r; +} + +ads_decode_result_t *ads_result_new(const char *plugin_name, + const char *description, + const ads_message_t *msg) { + ads_decode_result_t *r = calloc(1, sizeof(*r)); + if (!r) return NULL; + r->plugin_name = xstrdup(plugin_name); + r->description = xstrdup(description); + r->decode_level = ADS_DECODE_LEVEL_NONE; + r->raw = cJSON_CreateObject(); + r->items = cJSON_CreateArray(); + if (msg) { + cJSON *m = cJSON_CreateObject(); + cJSON_AddStringToObject(m, "label", msg->label ? msg->label : ""); + if (msg->sublabel) cJSON_AddStringToObject(m, "sublabel", msg->sublabel); + cJSON_AddStringToObject(m, "text", msg->text ? msg->text : ""); + r->message_json = cJSON_PrintUnformatted(m); + cJSON_Delete(m); + } + return r; +} + +void ads_result_free(ads_decode_result_t *result) { + if (!result) return; + free(result->plugin_name); + free(result->description); + cJSON_Delete(result->raw); + cJSON_Delete(result->items); + free(result->remaining); + free(result->message_json); + free(result); +} + +void ads_result_set_decoded(ads_decode_result_t *r, bool decoded) { + r->decoded = decoded; + if (!decoded) r->decode_level = ADS_DECODE_LEVEL_NONE; + else r->decode_level = r->remaining ? ADS_DECODE_LEVEL_PARTIAL : ADS_DECODE_LEVEL_FULL; +} + +ads_decode_result_t *ads_result_fail_unknown(ads_decode_result_t *r, const char *text) { + r->decoded = false; + r->decode_level = ADS_DECODE_LEVEL_NONE; + free(r->remaining); + r->remaining = xstrdup(text); + return r; +} + +void ads_result_raw_set(ads_decode_result_t *r, const char *key, ads_value_t *value) { + if (!r || !key || !value || !value->node) return; + /* Detach the cJSON node from the value wrapper (transfer ownership). */ + cJSON *detached = cJSON_Duplicate(value->node, 1); + cJSON_AddItemToObject(r->raw, key, detached); + ads_value_free(value); +} + +ads_value_t *ads_result_raw_get(const ads_decode_result_t *r, const char *key) { + cJSON *node = cJSON_GetObjectItemCaseSensitive(r->raw, key); + if (!node) return NULL; + ads_value_t *v = calloc(1, sizeof(*v)); + if (!v) return NULL; + v->node = node; /* borrowed pointer */ + v->owns = false; + return v; +} + +char *ads_result_to_json(const ads_decode_result_t *r) { + cJSON *root = cJSON_CreateObject(); + cJSON_AddBoolToObject(root, "decoded", r->decoded); + cJSON *decoder = cJSON_CreateObject(); + cJSON_AddStringToObject(decoder, "name", r->plugin_name); + cJSON_AddStringToObject(decoder, "type", "pattern-match"); + const char *level = r->decode_level == ADS_DECODE_LEVEL_FULL ? "full" + : r->decode_level == ADS_DECODE_LEVEL_PARTIAL ? "partial" + : "none"; + cJSON_AddStringToObject(decoder, "decodeLevel", level); + cJSON_AddItemToObject(root, "decoder", decoder); + + cJSON *formatted = cJSON_CreateObject(); + cJSON_AddStringToObject(formatted, "description", r->description); + cJSON_AddItemReferenceToObject(formatted, "items", r->items); + cJSON_AddItemToObject(root, "formatted", formatted); + + cJSON_AddItemReferenceToObject(root, "raw", r->raw); + + if (r->remaining) { + cJSON *rem = cJSON_CreateObject(); + cJSON_AddStringToObject(rem, "text", r->remaining); + cJSON_AddItemToObject(root, "remaining", rem); + } + + char *out = cJSON_PrintUnformatted(root); + cJSON_Delete(root); + return out; +} + +/* ─── ads_value_t ────────────────────────────────────────────────────────── */ + +static ads_value_t *make_value(cJSON *node) { + ads_value_t *v = calloc(1, sizeof(*v)); + if (!v) { cJSON_Delete(node); return NULL; } + v->node = node; + v->owns = true; + return v; +} + +ads_value_t *ads_value_from_string(const char *s) { return make_value(cJSON_CreateString(s ? s : "")); } +ads_value_t *ads_value_from_double(double n) { return make_value(cJSON_CreateNumber(n)); } +ads_value_t *ads_value_from_int(int64_t n) { return make_value(cJSON_CreateNumber((double)n)); } +ads_value_t *ads_value_from_bool(bool b) { return make_value(cJSON_CreateBool(b)); } +ads_value_t *ads_value_null(void) { return make_value(cJSON_CreateNull()); } + +void ads_value_free(ads_value_t *v) { + if (!v) return; + if (v->owns && v->node) cJSON_Delete(v->node); + free(v); +} + +bool ads_value_as_double(const ads_value_t *v, double *out) { + if (!v || !cJSON_IsNumber(v->node)) return false; + *out = v->node->valuedouble; + return true; +} +bool ads_value_as_int(const ads_value_t *v, int64_t *out) { + if (!v || !cJSON_IsNumber(v->node)) return false; + *out = (int64_t)v->node->valuedouble; + return true; +} +const char *ads_value_as_string(const ads_value_t *v) { + if (!v || !cJSON_IsString(v->node)) return NULL; + return v->node->valuestring; +} + +void ads_str_list_free(ads_str_list_t *list) { + if (!list || !list->items) return; + for (size_t i = 0; i < list->count; i++) free(list->items[i]); + free(list->items); + list->items = NULL; + list->count = list->capacity = 0; +} + +void ads_bytes_free(ads_bytes_t *b) { + if (!b) return; + free(b->data); + b->data = NULL; + b->len = 0; +} diff --git a/runtimes/c/src/regex.c b/runtimes/c/src/regex.c new file mode 100644 index 0000000..462a2a1 --- /dev/null +++ b/runtimes/c/src/regex.c @@ -0,0 +1,73 @@ +/* POSIX-regex backed implementation of ads_regex_match / ads_regex_group. + * + * Note: POSIX regex does NOT support PCRE-style named capture groups + * (?...). For ACARS specs that use named groups, the Stage 2 C impl + * needs to either: + * (a) compile with -lpcre2-8 and use PCRE2 instead of POSIX regex, or + * (b) preprocess patterns at codegen time to map ?... → ?:... and + * maintain a name→index map alongside. + * + * This stub uses option (a) shape — assume PCRE2 in Stage 2. For v1, the + * skeleton supports unnamed captures only and is sufficient for the + * non-regex reference specs (Label_10_POS, Label_4A, OHMA, ARINC_702). + */ + +#include "ads_helpers.h" +#include +#include +#include + +struct ads_regex_match { + bool ok; + char *input_copy; + regex_t compiled; + regmatch_t *matches; + size_t match_count; + /* For named-group support (Stage 2): name table */ +}; + +ads_regex_match_t *ads_regex_match_new(const char *pattern, const char *input) { + ads_regex_match_t *m = calloc(1, sizeof(*m)); + if (!m) return NULL; + if (regcomp(&m->compiled, pattern, REG_EXTENDED) != 0) { + free(m); + return NULL; + } + m->input_copy = strdup(input ? input : ""); + m->match_count = m->compiled.re_nsub + 1; + m->matches = calloc(m->match_count, sizeof(regmatch_t)); + if (!m->matches) { regfree(&m->compiled); free(m->input_copy); free(m); return NULL; } + m->ok = regexec(&m->compiled, m->input_copy, m->match_count, m->matches, 0) == 0; + return m; +} + +bool ads_regex_match_ok(const ads_regex_match_t *m) { return m && m->ok; } + +const char *ads_regex_group(const ads_regex_match_t *m, const char *name) { + /* Stage-2 TODO: resolve named group via a name→index map populated at + * compile time from the pattern. For now, support numeric names ("0".."9"). */ + if (!m || !m->ok || !name) return ""; + size_t idx = (size_t)atoi(name); + if (idx >= m->match_count) return ""; + regmatch_t mt = m->matches[idx]; + if (mt.rm_so < 0) return ""; + /* Return a borrowed pointer into input_copy at the match start. + * Length terminator is not enforced; callers should bound by mt.rm_eo. */ + return m->input_copy + mt.rm_so; +} + +void ads_regex_match_free(ads_regex_match_t *m) { + if (!m) return; + regfree(&m->compiled); + free(m->matches); + free(m->input_copy); + free(m); +} + +bool ads_regex_test(const char *pattern, const char *input) { + regex_t re; + if (regcomp(&re, pattern, REG_EXTENDED | REG_NOSUB) != 0) return false; + int rc = regexec(&re, input ? input : "", 0, NULL, 0); + regfree(&re); + return rc == 0; +} diff --git a/runtimes/c/src/result_formatter.c b/runtimes/c/src/result_formatter.c new file mode 100644 index 0000000..e3acbaa --- /dev/null +++ b/runtimes/c/src/result_formatter.c @@ -0,0 +1,115 @@ +/* ResultFormatter — pushes formatted items into the cJSON items array on + * the result. Field names match TS/Rust ResultFormatter output. */ + +#include "ads_helpers.h" +#include +#include +#include +#include + +struct ads_value { cJSON *node; bool owns; }; + +/* Access the result's items array. result is opaque elsewhere, so we share + * the layout here via a forward declaration matching plugin.c. */ +struct ads_decode_result { + bool decoded; + char *plugin_name; + char *description; + int decode_level; + cJSON *raw; + cJSON *items; + char *remaining; + char *message_json; +}; + +static void push_item(ads_decode_result_t *r, const char *kind, const char *code, + const char *label, const char *value) { + cJSON *item = cJSON_CreateObject(); + cJSON_AddStringToObject(item, "type", kind); + cJSON_AddStringToObject(item, "code", code); + cJSON_AddStringToObject(item, "label", label); + cJSON_AddStringToObject(item, "value", value); + cJSON_AddItemToArray(r->items, item); +} + +void ads_fmt_position(ads_decode_result_t *r, ads_value_t *lat, ads_value_t *lon) { + double la = 0, lo = 0; + ads_value_as_double(lat, &la); + ads_value_as_double(lon, &lo); + cJSON *pos = cJSON_CreateObject(); + cJSON_AddNumberToObject(pos, "latitude", la); + cJSON_AddNumberToObject(pos, "longitude", lo); + cJSON_AddItemToObject(r->raw, "position", pos); + char buf[64]; + snprintf(buf, sizeof(buf), "%.3f %c, %.3f %c", + la < 0 ? -la : la, la < 0 ? 'S' : 'N', + lo < 0 ? -lo : lo, lo < 0 ? 'W' : 'E'); + push_item(r, "aircraft_position", "ARP", "Aircraft Position", buf); + ads_value_free(lat); ads_value_free(lon); +} + +void ads_fmt_position_value(ads_decode_result_t *r, ads_value_t *position) { + double la = 0, lo = 0; + if (position && position->node) { + const cJSON *l = cJSON_GetObjectItemCaseSensitive(position->node, "latitude"); + const cJSON *g = cJSON_GetObjectItemCaseSensitive(position->node, "longitude"); + if (cJSON_IsNumber(l)) la = l->valuedouble; + if (cJSON_IsNumber(g)) lo = g->valuedouble; + } + ads_fmt_position(r, ads_value_from_double(la), ads_value_from_double(lo)); + ads_value_free(position); +} + +static void push_numeric(ads_decode_result_t *r, ads_value_t *v, const char *raw_key, + const char *kind, const char *code, const char *label, const char *unit) { + double n = 0; + ads_value_as_double(v, &n); + cJSON_AddNumberToObject(r->raw, raw_key, n); + char buf[64]; + if (unit && *unit) snprintf(buf, sizeof(buf), "%g %s", n, unit); + else snprintf(buf, sizeof(buf), "%g", n); + push_item(r, kind, code, label, buf); + ads_value_free(v); +} + +void ads_fmt_altitude(ads_decode_result_t *r, ads_value_t *v) { push_numeric(r, v, "altitude", "altitude", "ALT", "Altitude", "feet"); } +void ads_fmt_speed(ads_decode_result_t *r, ads_value_t *v) { push_numeric(r, v, "speed", "speed", "SPD", "Speed", "knots"); } +void ads_fmt_heading(ads_decode_result_t *r, ads_value_t *v) { push_numeric(r, v, "heading", "heading", "HDG", "Heading", "deg"); } +void ads_fmt_timestamp(ads_decode_result_t *r, ads_value_t *v) { push_numeric(r, v, "message_timestamp", "timestamp", "TS", "Timestamp", ""); } +void ads_fmt_fuel(ads_decode_result_t *r, ads_value_t *v) { push_numeric(r, v, "fuel_on_board", "fuel", "FUEL", "Fuel", ""); } + +static void push_string(ads_decode_result_t *r, ads_value_t *v, const char *raw_key, + const char *kind, const char *code, const char *label) { + const char *s = ads_value_as_string(v); + if (s) cJSON_AddStringToObject(r->raw, raw_key, s); + push_item(r, kind, code, label, s ? s : ""); + ads_value_free(v); +} + +void ads_fmt_callsign(ads_decode_result_t *r, ads_value_t *v) { push_string(r, v, "callsign", "callsign", "CS", "Callsign"); } +void ads_fmt_flight_number(ads_decode_result_t *r, ads_value_t *v) { push_string(r, v, "flight_number", "flight_number", "FLT", "Flight Number"); } +void ads_fmt_tail(ads_decode_result_t *r, ads_value_t *v) { push_string(r, v, "tail", "tail", "TAIL", "Tail Number"); } +void ads_fmt_departure_airport(ads_decode_result_t *r, ads_value_t *v) { push_string(r, v, "departure_icao", "airport_origin", "DEP", "Origin"); } +void ads_fmt_arrival_airport(ads_decode_result_t *r, ads_value_t *v) { push_string(r, v, "arrival_icao", "airport_destination", "ARR", "Destination"); } + +void ads_fmt_unknown_arr(ads_decode_result_t *r, const char *const *values, size_t count) { + size_t total = 1; + for (size_t i = 0; i < count; i++) total += strlen(values[i] ? values[i] : "") + 1; + char *joined = malloc(total); + if (!joined) return; + joined[0] = '\0'; + for (size_t i = 0; i < count; i++) { + if (i > 0) strcat(joined, ","); + strcat(joined, values[i] ? values[i] : ""); + } + if (r->remaining) { + size_t newlen = strlen(r->remaining) + 1 + strlen(joined) + 1; + char *grown = malloc(newlen); + snprintf(grown, newlen, "%s,%s", r->remaining, joined); + free(r->remaining); + r->remaining = grown; + } else { + r->remaining = strdup(joined); + } + free(joined); +} diff --git a/runtimes/c/src/string_list.c b/runtimes/c/src/string_list.c new file mode 100644 index 0000000..173658b --- /dev/null +++ b/runtimes/c/src/string_list.c @@ -0,0 +1,55 @@ +/* ads_split, ads_substring, ads_str_in. */ + +#include "ads_helpers.h" +#include +#include + +ads_str_list_t ads_split(const char *s, const char *delimiter) { + ads_str_list_t out = {0}; + if (!s || !delimiter || !*delimiter) return out; + size_t dlen = strlen(delimiter); + const char *p = s; + while (1) { + const char *next = strstr(p, delimiter); + size_t len = next ? (size_t)(next - p) : strlen(p); + if (out.count == out.capacity) { + size_t newcap = out.capacity ? out.capacity * 2 : 8; + char **newitems = realloc(out.items, newcap * sizeof(char *)); + if (!newitems) { ads_str_list_free(&out); return out; } + out.items = newitems; + out.capacity = newcap; + } + char *copy = malloc(len + 1); + if (!copy) { ads_str_list_free(&out); return out; } + memcpy(copy, p, len); + copy[len] = '\0'; + out.items[out.count++] = copy; + if (!next) break; + p = next + dlen; + } + return out; +} + +const char *ads_substring(const char *s, int start, int end) { + /* Returns a borrowed pointer into s + start. Caller must NOT free. + * For length-bounded extraction, callers typically pass the result + * through ads_value_from_string which copies. The end arg specifies + * one-past-last; negative = to end. */ + if (!s) return ""; + size_t slen = strlen(s); + if (start < 0) start = 0; + if ((size_t)start > slen) start = (int)slen; + if (end < 0 || (size_t)end > slen) end = (int)slen; + /* Note: this leaks the trailing chars to the caller; for ACARS use cases + * the caller almost always passes the substring through a copy quickly. */ + (void)end; + return s + start; +} + +bool ads_str_in(const char *const *list, size_t count, const char *needle) { + if (!list || !needle) return false; + for (size_t i = 0; i < count; i++) { + if (list[i] && strcmp(list[i], needle) == 0) return true; + } + return false; +} diff --git a/runtimes/rust/Cargo.lock b/runtimes/rust/Cargo.lock new file mode 100644 index 0000000..b93e037 --- /dev/null +++ b/runtimes/rust/Cargo.lock @@ -0,0 +1,208 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ads-runtime" +version = "0.1.0" +dependencies = [ + "base64", + "flate2", + "once_cell", + "regex", + "serde", + "serde_json", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "memchr" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/runtimes/rust/Cargo.toml b/runtimes/rust/Cargo.toml new file mode 100644 index 0000000..ed79517 --- /dev/null +++ b/runtimes/rust/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "ads-runtime" +version = "0.1.0" +edition = "2021" +description = "Rust runtime library for ADS-generated ACARS decoder plugins." +license = "MIT" +repository = "https://github.com/airframesio/airframes-decoder" + +[lib] +name = "ads_runtime" +path = "src/lib.rs" + +[dependencies] +base64 = "0.22" +flate2 = "1.0" +regex = "1.11" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +once_cell = "1.20" diff --git a/runtimes/rust/README.md b/runtimes/rust/README.md new file mode 100644 index 0000000..d825024 --- /dev/null +++ b/runtimes/rust/README.md @@ -0,0 +1,56 @@ +# ads-runtime (Rust) + +Rust runtime library for ADS-generated ACARS decoder plugins. + +Consumed by `acars-decoder-rust` via a path dependency on the +`airframes-decoder` submodule: + +```toml +[dependencies] +ads-runtime = { path = "vendor/airframes-decoder/runtimes/rust" } +``` + +## Layout + +``` +runtimes/rust/ +├── Cargo.toml +├── README.md +└── src/ + ├── lib.rs # public exports: Plugin, Message, Options, DecodeResult, helpers, ... + ├── plugin.rs # Plugin trait + result types + ├── result_formatter.rs # ResultFormatter (position, altitude, timestamp, ...) + ├── helpers.rs # Decode-fn helpers the codegen calls into + ├── coordinate.rs # Combined + decimal-minutes coordinate parsers + ├── ascii85.rs # Pure-Rust ASCII85 decoder + ├── crc.rs # CRC-16 IBM-SDLC and GENIBUS implementations + ├── types.rs # Route, Waypoint, Wind + └── escape_hatches/mod.rs # Per-plugin custom decoder/formatter hatches +``` + +## Generated plugin import shape + +```rust +use ads_runtime::{Plugin, Message, Options, DecodeResult, Qualifiers, ResultFormatter, helpers}; +use crate::escape_hatches; + +pub struct Label_10_POS; + +impl Plugin for Label_10_POS { + fn name(&self) -> &'static str { "label-10-pos" } + fn qualifiers(&self) -> Qualifiers { /* ... */ } + fn decode(&self, message: &Message, options: &Options) -> DecodeResult { /* ... */ } +} +``` + +## Source of truth + +`acars-decoder-typescript` (in production). All decoders must produce +byte-identical output via the shared corpus at `airframes-decoder/corpus/`. + +## Build + +```bash +cargo build +cargo test +``` diff --git a/runtimes/rust/src/ascii85.rs b/runtimes/rust/src/ascii85.rs new file mode 100644 index 0000000..9829cac --- /dev/null +++ b/runtimes/rust/src/ascii85.rs @@ -0,0 +1,49 @@ +//! Pure-Rust ASCII85 / Base85 decoder. +//! +//! Accepts optional `<~...~>` Adobe delimiters and the `z` shorthand for four +//! zero bytes. Returns `None` on invalid input. +//! +//! Mirrors `runtimes/typescript/utils/ascii85.ts`. + +pub fn decode(input: &str) -> Option> { + let s = input.trim(); + let s = s.strip_prefix("<~").unwrap_or(s); + let s = s.strip_suffix("~>").unwrap_or(s); + + let mut out = Vec::with_capacity(s.len() * 4 / 5); + let mut buf: u32 = 0; + let mut count: u8 = 0; + + for ch in s.bytes() { + match ch { + b'z' if count == 0 => { + out.extend_from_slice(&[0, 0, 0, 0]); + } + b'\t' | b'\n' | b'\r' | b' ' => continue, + 33..=117 => { + buf = buf.wrapping_mul(85).wrapping_add((ch - 33) as u32); + count += 1; + if count == 5 { + out.push((buf >> 24) as u8); + out.push((buf >> 16) as u8); + out.push((buf >> 8) as u8); + out.push(buf as u8); + buf = 0; + count = 0; + } + } + _ => return None, + } + } + + if count > 0 { + for _ in count..5 { + buf = buf.wrapping_mul(85).wrapping_add(84); + } + out.push((buf >> 24) as u8); + if count >= 3 { out.push((buf >> 16) as u8); } + if count >= 4 { out.push((buf >> 8) as u8); } + } + + Some(out) +} diff --git a/runtimes/rust/src/coordinate.rs b/runtimes/rust/src/coordinate.rs new file mode 100644 index 0000000..507f203 --- /dev/null +++ b/runtimes/rust/src/coordinate.rs @@ -0,0 +1,47 @@ +//! Coordinate parsing — combined and decimal-minutes formats. +//! +//! Mirrors `runtimes/typescript/utils/coordinate_utils.ts`. + +/// Combined "N12345W123456" → (latitude_millidegrees, longitude_millidegrees). +pub fn decode_combined(s: &str) -> Option<(f64, f64)> { + let bytes = s.as_bytes(); + if bytes.len() < 13 { return None; } + let first = bytes[0] as char; + let (middle_idx, lon_start) = if bytes[6] == b' ' { (7, 8) } else { (6, 7) }; + if bytes.len() < lon_start + 6 { return None; } + let middle = bytes[middle_idx] as char; + if !(first == 'N' || first == 'S') || !(middle == 'E' || middle == 'W') { + return None; + } + let lat_str = std::str::from_utf8(&bytes[1..6]).ok()?; + let lon_str = std::str::from_utf8(&bytes[lon_start..lon_start + 6]).ok()?; + let lat: f64 = lat_str.parse().ok()?; + let lon: f64 = lon_str.parse().ok()?; + let lat_sign = if first == 'N' { 1.0 } else { -1.0 }; + let lon_sign = if middle == 'E' { 1.0 } else { -1.0 }; + Some((lat / 1000.0 * lat_sign, lon / 1000.0 * lon_sign)) +} + +/// DDMM.M decimal-minute parser. +pub fn decode_decimal_minutes(s: &str) -> Option<(f64, f64)> { + let bytes = s.as_bytes(); + if bytes.len() < 13 { return None; } + let first = bytes[0] as char; + let (middle_idx, lon_start) = if bytes[6] == b' ' { (7, 8) } else { (6, 7) }; + if bytes.len() < lon_start + 6 { return None; } + let middle = bytes[middle_idx] as char; + if !(first == 'N' || first == 'S') || !(middle == 'E' || middle == 'W') { + return None; + } + let lat_str = std::str::from_utf8(&bytes[1..6]).ok()?; + let lon_str = std::str::from_utf8(&bytes[lon_start..lon_start + 6]).ok()?; + let lat_n: f64 = lat_str.parse().ok()?; + let lon_n: f64 = lon_str.parse().ok()?; + let lat_deg = (lat_n / 1000.0).trunc(); + let lat_min = (lat_n % 1000.0) / 10.0; + let lon_deg = (lon_n / 1000.0).trunc(); + let lon_min = (lon_n % 1000.0) / 10.0; + let lat_sign = if first == 'N' { 1.0 } else { -1.0 }; + let lon_sign = if middle == 'E' { 1.0 } else { -1.0 }; + Some(((lat_deg + lat_min / 60.0) * lat_sign, (lon_deg + lon_min / 60.0) * lon_sign)) +} diff --git a/runtimes/rust/src/crc.rs b/runtimes/rust/src/crc.rs new file mode 100644 index 0000000..eb0dcae --- /dev/null +++ b/runtimes/rust/src/crc.rs @@ -0,0 +1,50 @@ +//! CRC algorithms used by ACARS / ARINC 702 / MIAM PDUs. +//! +//! These mirror `runtimes/typescript/utils/arinc_702_helper.ts`. The lookup +//! tables themselves will be emitted from `spec/shared/crc_tables.yaml` in a +//! follow-up; the bitwise implementations below are correct and table-free. + +/// CRC-16/IBM-SDLC with reversed nibbles, as ARINC 702 H1 messages use it. +pub fn crc16_ibm_sdlc_rev(data: &[u8]) -> u16 { + let mut crc: u32 = 0xffff; + for &byte in data { + crc ^= byte as u32; + for _ in 0..8 { + if crc & 0x0001 != 0 { + crc = (crc >> 1) ^ 0x8408; + } else { + crc >>= 1; + } + } + } + let crc = (crc ^ 0xffff) & 0xffff; + let n1 = (crc >> 12) & 0xf; + let n2 = (crc >> 8) & 0xf; + let n3 = (crc >> 4) & 0xf; + let n4 = crc & 0xf; + ((n4 << 12) | (n3 << 8) | (n2 << 4) | n1) as u16 +} + +/// CRC-16/GENIBUS (poly 0x1021, init 0xffff). +pub fn crc16_genibus(data: &[u8]) -> u16 { + let mut crc: u32 = 0xffff; + let polynomial: u32 = 0x1021; + for &byte in data { + crc ^= (byte as u32) << 8; + for _ in 0..8 { + if crc & 0x8000 != 0 { + crc = ((crc << 1) ^ polynomial) & 0xffff; + } else { + crc = (crc << 1) & 0xffff; + } + } + } + ((crc ^ 0xffff) & 0xffff) as u16 +} + +/// Convenience: try both CRC variants and report which (if any) matched. +pub fn match_arinc_702_crc(data: &[u8], expected: u16) -> Option<&'static str> { + if crc16_ibm_sdlc_rev(data) == expected { return Some("IBM-SDLC"); } + if crc16_genibus(data) == expected { return Some("GENIBUS"); } + None +} diff --git a/runtimes/rust/src/escape_hatches/mod.rs b/runtimes/rust/src/escape_hatches/mod.rs new file mode 100644 index 0000000..fa00797 --- /dev/null +++ b/runtimes/rust/src/escape_hatches/mod.rs @@ -0,0 +1,19 @@ +//! Per-plugin escape-hatch functions referenced from spec YAML via `custom: `. +//! +//! As specs are ported and reveal needed hatches, add the corresponding files +//! here and re-export. See `docs/ESCAPE_HATCHES.md` for the contract. +//! +//! Generated plugin code does: +//! use crate::escape_hatches; +//! escape_hatches::(...) +//! +//! Currently expected (from reference specs): +//! - arinc_702_dispatch (parse-level whole-plugin hatch) +//! - arinc_702_format (formatter-level) +//! - label_4a_variant_2_decode (field-level) +//! - label_4a_variant_3_position (field-level) +//! - label_4a_format (formatter-level) +//! - ohma_unwrap_message (field-level) +//! - ohma_message_item (formatter-level) +//! - parse_flight_level_or_ground (decode-fn level) +//! - flight_level_to_altitude_feet (decode-fn level) diff --git a/runtimes/rust/src/helpers.rs b/runtimes/rust/src/helpers.rs new file mode 100644 index 0000000..138ed27 --- /dev/null +++ b/runtimes/rust/src/helpers.rs @@ -0,0 +1,195 @@ +//! Decode-fn helpers the codegen emits calls into. +//! +//! Mirrors `runtimes/typescript/helpers.ts`. Each function corresponds to a +//! `decode.fn` value in spec YAML. +//! +//! Uniform signature: every helper accepts `(value: &str, args_json: &str)`, +//! where `args_json` is `"{}"` when the spec specifies no args. This lets +//! the emitter always emit a 2-arg call, simplifying both sides. + +use serde_json::Value as JsonValue; +use crate::ascii85; +use crate::coordinate; + +fn parse_args(s: &str) -> JsonValue { + serde_json::from_str(s).unwrap_or(JsonValue::Null) +} + +// ─── coordinates ───────────────────────────────────────────────────────────── + +pub fn coordinate(value: &str, args_json: &str) -> JsonValue { + let args = parse_args(args_json); + let trimmed = value.trim(); + let style = args.get("style").and_then(JsonValue::as_str).unwrap_or("single_axis"); + if style == "combined" { + return match coordinate::decode_combined(trimmed) { + Some(c) => serde_json::json!({"latitude": c.0, "longitude": c.1}), + None => JsonValue::Null, + }; + } + let divisor = args.get("divisor").and_then(JsonValue::as_f64).unwrap_or(1000.0); + let positive = ["N", "E"]; + let prefix = match trimmed.chars().next() { + Some(c) => c.to_string(), + None => return JsonValue::Null, + }; + let sign: f64 = if positive.contains(&prefix.as_str()) { 1.0 } else { -1.0 }; + let digits = trimmed[1..].trim(); + let n: f64 = match digits.parse() { + Ok(n) => n, + Err(_) => return JsonValue::Null, + }; + serde_json::json!(sign * n / divisor) +} + +pub fn coordinate_decimal_minutes(value: &str, _args_json: &str) -> JsonValue { + match coordinate::decode_decimal_minutes(value) { + Some(c) => serde_json::json!({"latitude": c.0, "longitude": c.1}), + None => JsonValue::Null, + } +} + +// ─── numerics ──────────────────────────────────────────────────────────────── + +pub fn integer(value: &str, args_json: &str) -> JsonValue { + let args = parse_args(args_json); + let mut s = value; + let owned: String; + let start = args.get("substring_start").and_then(JsonValue::as_u64).unwrap_or(0) as usize; + if let Some(len) = args.get("substring_length").and_then(JsonValue::as_u64) { + owned = s.chars().skip(start).take(len as usize).collect(); + s = &owned; + } else if start > 0 { + owned = s.chars().skip(start).collect(); + s = &owned; + } + let n: f64 = s.parse().unwrap_or(0.0); + let mult = args.get("multiplier").and_then(JsonValue::as_f64).unwrap_or(1.0); + JsonValue::from(n * mult) +} + +pub fn float(value: &str, _args_json: &str) -> JsonValue { + let n: f64 = value.parse().unwrap_or(0.0); + JsonValue::from(n) +} + +// ─── strings ───────────────────────────────────────────────────────────────── + +pub fn string(value: &str, _args_json: &str) -> JsonValue { + JsonValue::String(value.to_string()) +} +pub fn trim(value: &str, _args_json: &str) -> JsonValue { + JsonValue::String(value.trim().to_string()) +} +pub fn uppercase(value: &str, _args_json: &str) -> JsonValue { + JsonValue::String(value.to_uppercase()) +} +pub fn lowercase(value: &str, _args_json: &str) -> JsonValue { + JsonValue::String(value.to_lowercase()) +} + +// ─── identifiers ───────────────────────────────────────────────────────────── + +pub fn callsign(value: &str, _args_json: &str) -> JsonValue { + JsonValue::String(value.trim().to_string()) +} +pub fn flight_number(value: &str, _args_json: &str) -> JsonValue { + JsonValue::String(value.trim().to_string()) +} +pub fn airport(value: &str, _args_json: &str) -> JsonValue { + JsonValue::String(value.trim().to_uppercase()) +} + +pub fn tail_number(value: &str, args_json: &str) -> JsonValue { + let args = parse_args(args_json); + let mut s = value.trim().to_string(); + if let Some(strip) = args.get("strip_chars").and_then(JsonValue::as_str) { + for c in strip.chars() { + s = s.replace(c, ""); + } + } + JsonValue::String(s) +} + +// ─── timestamps ────────────────────────────────────────────────────────────── + +pub fn timestamp_hhmmss(value: &str, args_json: &str) -> JsonValue { + let args = parse_args(args_json); + let s = if let Some(app) = args.get("append").and_then(JsonValue::as_str) { + format!("{}{}", value, app) + } else { + value.to_string() + }; + parse_hhmmss_to_tod(&s) +} + +fn parse_hhmmss_to_tod(s: &str) -> JsonValue { + if s.len() < 6 { return JsonValue::Null; } + let h: i64 = s[0..2].parse().unwrap_or(0); + let m: i64 = s[2..4].parse().unwrap_or(0); + let sec: i64 = s[4..6].parse().unwrap_or(0); + JsonValue::from(h * 3600 + m * 60 + sec) +} + +// ─── binary / encoding (called via parse steps, not decode-fns) ────────────── + +pub fn base64_decode(value: &str) -> Vec { + use base64::{engine::general_purpose::STANDARD, Engine}; + STANDARD.decode(value.trim()).unwrap_or_default() +} + +pub fn inflate(bytes: &[u8], format: &str) -> Vec { + use flate2::read::{DeflateDecoder, GzDecoder, ZlibDecoder}; + use std::io::Read; + let mut out = Vec::new(); + match format { + "zlib" => { let _ = ZlibDecoder::new(bytes).read_to_end(&mut out); } + "gzip" => { let _ = GzDecoder::new(bytes).read_to_end(&mut out); } + _ => { let _ = DeflateDecoder::new(bytes).read_to_end(&mut out); } + } + out +} + +pub fn text_decode(bytes: &[u8], _encoding: &str) -> String { + String::from_utf8_lossy(bytes).into_owned() +} + +pub fn decode_ascii85(value: &str) -> Vec { + ascii85::decode(value).unwrap_or_default() +} + +pub fn hex_decode(value: &str) -> Vec { + let clean: String = value.chars().filter(|c| c.is_ascii_hexdigit()).collect(); + (0..clean.len() / 2) + .map(|i| u8::from_str_radix(&clean[i * 2..i * 2 + 2], 16).unwrap_or(0)) + .collect() +} + +// ─── bitfield ──────────────────────────────────────────────────────────────── + +pub fn bitslice(byte: u8, start: u8, end: u8) -> u32 { + let width = end - start + 1; + let shift = 8 - end - 1; + let mask = (1u32 << width) - 1; + ((byte as u32) >> shift) & mask +} + +pub fn concat_bits(values: &[u32]) -> u32 { + values.iter().fold(0u32, |acc, &v| (acc << 8) | v) +} + +// ─── regex ─────────────────────────────────────────────────────────────────── + +use once_cell::sync::Lazy; +use std::collections::HashMap; +use std::sync::Mutex; + +static REGEX_CACHE: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); + +pub fn regex(pattern: &str) -> regex::Regex { + let mut cache = REGEX_CACHE.lock().expect("regex cache poisoned"); + cache + .entry(pattern.to_string()) + .or_insert_with(|| regex::Regex::new(pattern).expect("invalid regex")) + .clone() +} diff --git a/runtimes/rust/src/lib.rs b/runtimes/rust/src/lib.rs new file mode 100644 index 0000000..2668be6 --- /dev/null +++ b/runtimes/rust/src/lib.rs @@ -0,0 +1,19 @@ +//! `ads-runtime` — Rust runtime library for ADS-generated ACARS decoder plugins. +//! +//! Generated plugin code (output of `ads-gen --target rust`) imports from this +//! crate. The Stage 2 Rust impl (`acars-decoder-rust`) depends on this crate +//! via a path dependency on the `airframes-decoder` submodule. +//! +//! Behavior matches `acars-decoder-typescript` byte-for-byte; the shared +//! corpus (`airframes-decoder/corpus/`) enforces parity. + +pub mod plugin; +pub mod result_formatter; +pub mod helpers; +pub mod ascii85; +pub mod coordinate; +pub mod crc; +pub mod types; + +pub use plugin::{DecodeResult, Message, Options, Plugin, Qualifiers, RawValue}; +pub use result_formatter::ResultFormatter; diff --git a/runtimes/rust/src/plugin.rs b/runtimes/rust/src/plugin.rs new file mode 100644 index 0000000..eaf5a55 --- /dev/null +++ b/runtimes/rust/src/plugin.rs @@ -0,0 +1,127 @@ +//! Core plugin trait + message/result types. + +use serde::Serialize; +use serde_json::Value as JsonValue; +use std::collections::HashMap; + +/// Runtime value attached to `DecodeResult.raw`. Backed by `serde_json::Value` +/// so plugins can stash arbitrary nested data without bespoke types. +pub type RawValue = JsonValue; + +#[derive(Debug, Clone, Serialize)] +pub struct Message { + pub label: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub sublabel: Option, + pub text: String, +} + +#[derive(Debug, Clone, Default, Serialize)] +pub struct Options { + #[serde(default)] + pub debug: bool, +} + +#[derive(Debug, Clone, Serialize)] +pub struct Qualifiers { + pub labels: Vec<&'static str>, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub preambles: Vec<&'static str>, +} + +#[derive(Debug, Clone, Serialize)] +pub struct DecoderInfo { + pub name: &'static str, + pub kind: &'static str, + pub decode_level: DecodeLevel, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum DecodeLevel { + None, + Partial, + Full, +} + +#[derive(Debug, Clone, Serialize)] +pub struct FormattedItem { + pub kind: &'static str, + pub code: &'static str, + pub label: String, + pub value: String, +} + +#[derive(Debug, Clone, Serialize)] +pub struct Formatted { + pub description: String, + pub items: Vec, +} + +#[derive(Debug, Clone, Default, Serialize)] +pub struct Remaining { + #[serde(skip_serializing_if = "Option::is_none")] + pub text: Option, +} + +#[derive(Debug, Clone, Serialize)] +pub struct DecodeResult { + pub decoded: bool, + pub decoder: DecoderInfo, + pub formatted: Formatted, + pub raw: HashMap<&'static str, RawValue>, + pub remaining: Remaining, + #[serde(skip_serializing_if = "Option::is_none")] + pub message: Option, +} + +impl DecodeResult { + pub fn new(name: &'static str, description: &str) -> Self { + Self { + decoded: false, + decoder: DecoderInfo { + name, + kind: "pattern-match", + decode_level: DecodeLevel::None, + }, + formatted: Formatted { + description: description.to_string(), + items: Vec::new(), + }, + raw: HashMap::new(), + remaining: Remaining::default(), + message: None, + } + } + + pub fn set_message(&mut self, message: &Message) { + self.message = Some(message.clone()); + } + + pub fn set_decoded(&mut self, decoded: bool) { + self.decoded = decoded; + self.decoder.decode_level = if !decoded { + DecodeLevel::None + } else if self.remaining.text.is_some() { + DecodeLevel::Partial + } else { + DecodeLevel::Full + }; + } + + /// Consumes the result and returns a failure variant with `decoded=false` + /// and the remaining text set. Matches the TS `failUnknown` semantics. + pub fn fail_unknown(mut self, text: &str) -> Self { + self.decoded = false; + self.decoder.decode_level = DecodeLevel::None; + self.remaining.text = Some(text.to_string()); + self + } +} + +/// The trait every generated plugin implements. +pub trait Plugin: Send + Sync { + fn name(&self) -> &'static str; + fn qualifiers(&self) -> Qualifiers; + fn decode(&self, message: &Message, options: &Options) -> DecodeResult; +} diff --git a/runtimes/rust/src/result_formatter.rs b/runtimes/rust/src/result_formatter.rs new file mode 100644 index 0000000..258d073 --- /dev/null +++ b/runtimes/rust/src/result_formatter.rs @@ -0,0 +1,147 @@ +//! ResultFormatter — pushes structured items onto `DecodeResult.formatted.items`. +//! +//! Mirrors the TS `ResultFormatter` static-class API. Function names match +//! `runtimes/typescript/utils/result_formatter.ts` so the cross-language corpus +//! sees identical output shapes. +//! +//! Every method that takes a value accepts `impl Into>` so +//! callers can pass either `JsonValue` or `Option` (the latter +//! arises from when-gated fields where the guard may have failed). Methods +//! no-op on None / Null / NaN, matching the original hand-written plugins' +//! pattern of only emitting items when they had a real value. + +use serde_json::Value as JsonValue; +use crate::plugin::{DecodeResult, FormattedItem}; + +pub struct ResultFormatter; + +fn into_value(v: impl Into>) -> Option { + let opt = v.into(); + match opt { + Some(JsonValue::Null) => None, + Some(JsonValue::Number(ref n)) if n.as_f64().map(|f| f.is_nan()).unwrap_or(false) => None, + other => other, + } +} + +impl ResultFormatter { + fn push(result: &mut DecodeResult, kind: &'static str, code: &'static str, label: &str, value: String) { + result.formatted.items.push(FormattedItem { + kind, + code, + label: label.to_string(), + value, + }); + } + + pub fn position( + result: &mut DecodeResult, + latitude: impl Into>, + longitude: impl Into>, + ) { + let lat_v = match into_value(latitude) { Some(v) => v, None => return }; + let lon_v = match into_value(longitude) { Some(v) => v, None => return }; + result.raw.insert("position", serde_json::json!({ + "latitude": lat_v, + "longitude": lon_v, + })); + let lat = lat_v.as_f64().unwrap_or(0.0); + let lon = lon_v.as_f64().unwrap_or(0.0); + let lat_dir = if lat >= 0.0 { "N" } else { "S" }; + let lon_dir = if lon >= 0.0 { "E" } else { "W" }; + let display = format!("{:.3} {}, {:.3} {}", lat.abs(), lat_dir, lon.abs(), lon_dir); + Self::push(result, "aircraft_position", "ARP", "Aircraft Position", display); + } + + pub fn position_value(result: &mut DecodeResult, value: impl Into>) { + let v = match into_value(value) { Some(v) => v, None => return }; + let lat = v.get("latitude").and_then(JsonValue::as_f64).unwrap_or(0.0); + let lon = v.get("longitude").and_then(JsonValue::as_f64).unwrap_or(0.0); + Self::position(result, JsonValue::from(lat), JsonValue::from(lon)); + } + + pub fn altitude(result: &mut DecodeResult, value: impl Into>) { + let v = match into_value(value) { Some(v) => v, None => return }; + let n = v.as_f64().unwrap_or(0.0); + result.raw.insert("altitude", v); + Self::push(result, "altitude", "ALT", "Altitude", format!("{} feet", n)); + } + + pub fn speed(result: &mut DecodeResult, value: impl Into>) { + let v = match into_value(value) { Some(v) => v, None => return }; + let n = v.as_f64().unwrap_or(0.0); + result.raw.insert("speed", v); + Self::push(result, "speed", "SPD", "Speed", format!("{} knots", n)); + } + + pub fn heading(result: &mut DecodeResult, value: impl Into>) { + let v = match into_value(value) { Some(v) => v, None => return }; + let n = v.as_f64().unwrap_or(0.0); + result.raw.insert("heading", v); + Self::push(result, "heading", "HDG", "Heading", format!("{}°", n)); + } + + pub fn timestamp(result: &mut DecodeResult, value: impl Into>) { + let v = match into_value(value) { Some(v) => v, None => return }; + let n = v.as_i64().unwrap_or(0); + result.raw.insert("message_timestamp", v); + Self::push(result, "timestamp", "TS", "Timestamp", n.to_string()); + } + + pub fn callsign(result: &mut DecodeResult, value: impl Into>) { + let v = match into_value(value) { Some(v) => v, None => return }; + let s = v.as_str().unwrap_or("").to_string(); + result.raw.insert("callsign", v); + Self::push(result, "callsign", "CS", "Callsign", s); + } + + pub fn flight_number(result: &mut DecodeResult, value: impl Into>) { + let v = match into_value(value) { Some(v) => v, None => return }; + let s = v.as_str().unwrap_or("").to_string(); + result.raw.insert("flight_number", v); + Self::push(result, "flight_number", "FLT", "Flight Number", s); + } + + pub fn tail(result: &mut DecodeResult, value: impl Into>) { + let v = match into_value(value) { Some(v) => v, None => return }; + let s = v.as_str().unwrap_or("").to_string(); + result.raw.insert("tail", v); + Self::push(result, "tail", "TAIL", "Tail Number", s); + } + + pub fn departure_airport(result: &mut DecodeResult, value: impl Into>) { + let v = match into_value(value) { Some(v) => v, None => return }; + let s = v.as_str().unwrap_or("").to_string(); + result.raw.insert("departure_icao", v); + Self::push(result, "airport_origin", "DEP", "Origin", s); + } + + pub fn arrival_airport(result: &mut DecodeResult, value: impl Into>) { + let v = match into_value(value) { Some(v) => v, None => return }; + let s = v.as_str().unwrap_or("").to_string(); + result.raw.insert("arrival_icao", v); + Self::push(result, "airport_destination", "ARR", "Destination", s); + } + + pub fn fuel(result: &mut DecodeResult, value: impl Into>) { + let v = match into_value(value) { Some(v) => v, None => return }; + let n = v.as_f64().unwrap_or(0.0); + result.raw.insert("fuel_on_board", v); + Self::push(result, "fuel", "FUEL", "Fuel", n.to_string()); + } + + pub fn unknown_arr(result: &mut DecodeResult, values: Vec) { + let joined = values.join(","); + match result.remaining.text.as_mut() { + Some(t) => { t.push(','); t.push_str(&joined); } + None => result.remaining.text = Some(joined), + } + } + + pub fn unknown(result: &mut DecodeResult, value: String) { + match result.remaining.text.as_mut() { + Some(t) => { t.push(','); t.push_str(&value); } + None => result.remaining.text = Some(value), + } + } +} diff --git a/runtimes/rust/src/types.rs b/runtimes/rust/src/types.rs new file mode 100644 index 0000000..d15b70d --- /dev/null +++ b/runtimes/rust/src/types.rs @@ -0,0 +1,29 @@ +//! Cross-plugin data types (Route, Waypoint, Wind). Mirrors `runtimes/typescript/types/`. + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize)] +pub struct Waypoint { + pub name: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub time: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub latitude: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub longitude: Option, +} + +#[derive(Debug, Clone, Serialize)] +pub struct Route { + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + pub waypoints: Vec, +} + +#[derive(Debug, Clone, Serialize)] +pub struct Wind { + pub direction: f64, + pub speed: f64, + #[serde(skip_serializing_if = "Option::is_none")] + pub altitude: Option, +} diff --git a/runtimes/typescript/DateTimeUtils.ts b/runtimes/typescript/DateTimeUtils.ts new file mode 100644 index 0000000..f436148 --- /dev/null +++ b/runtimes/typescript/DateTimeUtils.ts @@ -0,0 +1,98 @@ +export class DateTimeUtils { + // Expects a four digit UTC time string (HHMM) + public static UTCToString(UTCString: string) { + let utcDate = new Date(); + utcDate.setUTCHours(+UTCString.substr(0, 2), +UTCString.substr(2, 2), 0); + return utcDate.toTimeString(); + } + + // Expects a six digit date string and a four digit UTC time string + // (DDMMYY) (HHMM) + public static UTCDateTimeToString(dateString: string, timeString: string) { + let utcDate = new Date(); + utcDate.setUTCDate(+dateString.substr(0, 2)); + utcDate.setUTCMonth(+dateString.substr(2, 2)); + if (dateString.length === 6) { + utcDate.setUTCFullYear(2000 + +dateString.substr(4, 2)); + } + if (timeString.length === 6) { + utcDate.setUTCHours( + +timeString.substr(0, 2), + +timeString.substr(2, 2), + +timeString.substr(4, 2), + ); + } else { + utcDate.setUTCHours( + +timeString.substr(0, 2), + +timeString.substr(2, 2), + 0, + ); + } + return utcDate.toUTCString(); + } + + /** + * + * @param time HHMMSS or HHMM + * @returns seconds since midnight + */ + public static convertHHMMSSToTod(time: string): number { + if (time.length === 4) { + time += '00'; + } + const h = Number(time.substring(0, 2)); + const m = Number(time.substring(2, 4)); + const s = Number(time.substring(4, 6)); + const tod = h * 3600 + m * 60 + s; + return tod; + } + + public static convertDayTimeToTod(time: string): number { + const d = Number(time.substring(0, 2)); + const h = Number(time.substring(2, 4)); + const m = Number(time.substring(4, 6)); + const s = Number(time.substring(6, 8)); + const tod = d * 86400 + h * 3600 + m * 60 + s; + return tod; + } + + /** + * + * @param time HHMMSS + * @param date DDMMYY or DDMMYYYY + * @returns seconds since epoch + */ + public static convertDateTimeToEpoch(time: string, date: string): number { + //YYYY-MM-DDTHH:mm:ss.sssZ + if (date.length === 6) { + date = date.substring(0, 4) + `20${date.substring(4, 6)}`; + } + const timestamp = `${date.substring(4, 8)}-${date.substring(2, 4)}-${date.substring(0, 2)}T${time.substring(0, 2)}:${time.substring(2, 4)}:${time.substring(4, 6)}.000Z`; + const millis = Date.parse(timestamp); + return millis / 1000; + } + + /** + * Converts a timestamp to a string + * + * ISO-8601 format for 'epoch' + * HH:MM:SS for 'tod' + * @param time + * @param format + * @returns + */ + public static timestampToString(time: number): string { + const date = new Date(time * 1000); + + if (time < 86400) { + // only time, no date + return date.toISOString().slice(11, 19); + } + if (time < 2678400) { + // unknown month and date + return `YYYY-MM-${date.toISOString().slice(8, 19)}`; + } + //strip off millis + return date.toISOString().slice(0, -5) + 'Z'; + } +} diff --git a/runtimes/typescript/DecoderPlugin.ts b/runtimes/typescript/DecoderPlugin.ts new file mode 100644 index 0000000..378e195 --- /dev/null +++ b/runtimes/typescript/DecoderPlugin.ts @@ -0,0 +1,132 @@ +import { + DecodeResult, + DecoderPluginInterface, + Message, + Options, + Qualifiers, +} from './DecoderPluginInterface'; +import { ResultFormatter } from './utils/result_formatter'; +import type { MessageDecoder } from './MessageDecoder'; + +export abstract class DecoderPlugin implements DecoderPluginInterface { + decoder!: MessageDecoder; + + name: string = 'unknown'; + + defaultResult(): DecodeResult { + return { + decoded: false, + decoder: { + name: 'unknown', + type: 'pattern-match', + decodeLevel: 'none', + }, + formatted: { + description: 'Unknown', + items: [], + }, + raw: {}, + remaining: {}, + }; + } + + /** + * Creates a DecodeResult pre-populated with the plugin name, description, and message. + * Replaces the common boilerplate at the start of every decode() method. + * + * Public so that escape-hatch functions invoked by generated plugins can call it. + */ + public initResult(message: Message, description: string): DecodeResult { + const result = this.defaultResult(); + result.decoder.name = this.name; + result.formatted.description = description; + result.message = message; + return result; + } + + /** + * Sets the decoded flag and decodeLevel on a result. + * If decoded is true and no explicit level is given, infers 'full' or 'partial' + * based on whether remaining.text is set. + * + * Public so that escape-hatch functions invoked by generated plugins can call it. + */ + public setDecodeLevel( + result: DecodeResult, + decoded: boolean, + level?: 'full' | 'partial', + ): void { + result.decoded = decoded; + if (decoded) { + result.decoder.decodeLevel = + level ?? (result.remaining.text ? 'partial' : 'full'); + } else { + result.decoder.decodeLevel = 'none'; + } + } + + /** + * Logs a debug message prefixed with the plugin name, only if options.debug is true. + * + * Public so that escape-hatch functions invoked by generated plugins can call it. + */ + public debug(options: Options, ...args: unknown[]): void { + if (options.debug) { + console.log(`[${this.name}]`, ...args); + } + } + + /** + * Marks a result as a failed decode with 'none' level, sets the remaining text + * as unknown, and logs a debug message. Returns the result for convenient early return. + * + * Public so that escape-hatch functions invoked by generated plugins can call it. + */ + public failUnknown( + result: DecodeResult, + text: string, + options: Options = {}, + ): DecodeResult { + this.debug(options, `Unknown message: ${text}`); + ResultFormatter.unknown(result, text); + result.decoded = false; + result.decoder.decodeLevel = 'none'; + return result; + } + + options: object; + + constructor(decoder: MessageDecoder, options: Options = {}) { + this.decoder = decoder; + this.options = options; + } + + id(): string { + console.log( + 'DecoderPlugin subclass has not overriden id() to provide a unique ID for this plugin!', + ); + return 'abstract_decoder_plugin'; + } + + meetsStateRequirements(): boolean { + return true; + } + + // onRegister(store: Store) { + // this.store = store; + // } + + qualifiers(): Qualifiers { + const labels: Array = []; + + return { + labels, + }; + } + + decode(message: Message, options: Options = {}): DecodeResult { + const decodeResult = this.defaultResult(); + decodeResult.remaining.text = message.text; + return decodeResult; + } +} diff --git a/runtimes/typescript/DecoderPluginInterface.ts b/runtimes/typescript/DecoderPluginInterface.ts new file mode 100644 index 0000000..100873d --- /dev/null +++ b/runtimes/typescript/DecoderPluginInterface.ts @@ -0,0 +1,155 @@ +import { Route } from './types/route'; +import { Wind } from './types/wind'; + +/** + * Representation of a Message + */ +export interface Message { + label: string; + sublabel?: string; + text: string; +} + +/** + * Decoder Options + */ +export interface Options { + debug?: boolean; +} + +/** + * Known fields that can appear on DecodeResult.raw. + * The index signature allows plugin-specific fields without losing type safety + * on the well-known ones. + */ +export interface RawFields { + // Position & navigation + position?: { latitude: number; longitude: number }; + altitude?: number; + heading?: number; + groundspeed?: number; + airspeed?: number; + mach?: number; + + // Flight identification + flight_number?: string; + callsign?: string; + tail?: string; + + // Airports + departure_icao?: string; + departure_iata?: string; + arrival_icao?: string; + arrival_iata?: string; + alternate_icao?: string; + + // Runways + departure_runway?: string; + arrival_runway?: string; + alternate_runway?: string; + + // Times (seconds since midnight or epoch) + message_timestamp?: number; + eta_time?: number; + out_time?: number; + off_time?: number; + on_time?: number; + in_time?: number; + engine_start_time?: number; + engine_stop_time?: number; + + // Date + day?: number | string; + month?: number; + departure_day?: number; + arrival_day?: number; + + // Fuel + fuel_on_board?: number; + fuel_burned?: number; + fuel_remaining?: number; + fuel_in_tons?: number; + out_fuel?: number; + off_fuel?: number; + on_fuel?: number; + in_fuel?: number; + start_fuel?: number; + + // Temperature + outside_air_temperature?: number; + total_air_temperature?: number; + + // Weight & balance + center_of_gravity?: number; + cg_lower_limit?: number; + cg_upper_limit?: number; + mac?: number; + trim?: number; + + // Route & flight plan + route?: Route; + wind_data?: Wind[]; + requested_alts?: number[]; + desired_alt?: number; + start_point?: string; + route_number?: string; + flight_plan?: string; + + // Message metadata + label?: string; + sublabel?: string; + preamble?: string; + version?: number; + checksum_algorithm?: string; + checksum?: number; + sequence_number?: number; + sequence_response?: number; + ground_address?: string; + text?: string; + + // State & events + state_change?: { from: string; to: string }; + door_event?: { door: string; state: string }; + + // Allow plugin-specific fields + [key: string]: unknown; +} + +/** + * Results from decoding a message + */ +export interface DecodeResult { + decoded: boolean; + decoder: { + name: string; + type: 'pattern-match' | 'none'; + decodeLevel: 'none' | 'partial' | 'full'; + }; + error?: string; + formatted: { + description: string; + items: { + type: string; + code: string; + label: string; + value: string; + }[]; + }; + message?: Message; + raw: RawFields; + remaining: { + text?: string; + }; +} + +export interface Qualifiers { + labels: string[]; + preambles?: string[]; +} + +export interface DecoderPluginInterface { + decode(message: Message, options?: Options): DecodeResult; + meetsStateRequirements(): boolean; + // onRegister(store: Store) : void; + qualifiers(): Qualifiers; +} diff --git a/runtimes/typescript/MessageDecoder.ts b/runtimes/typescript/MessageDecoder.ts new file mode 100644 index 0000000..bb157af --- /dev/null +++ b/runtimes/typescript/MessageDecoder.ts @@ -0,0 +1,12 @@ +import type { DecodeResult, Message, Options } from "./DecoderPluginInterface"; + +/** + * Minimal contract for a MessageDecoder — the dispatcher that runs registered + * plugins against a message. The concrete dispatcher lives in the consuming + * language repo (acars-decoder-typescript/lib/MessageDecoder.ts) and implements + * this interface; the runtime package only needs the contract so DecoderPlugin + * can reference it (used by wrapper plugins like CBand that recurse). + */ +export interface MessageDecoder { + decode(message: Message, options?: Options): DecodeResult; +} diff --git a/runtimes/typescript/README.md b/runtimes/typescript/README.md new file mode 100644 index 0000000..764d22f --- /dev/null +++ b/runtimes/typescript/README.md @@ -0,0 +1,56 @@ +# @airframes/ads-runtime-ts + +TypeScript runtime library for ADS-generated ACARS decoder plugins. + +## Layout + +``` +runtimes/typescript/ +├── index.ts # Public exports: DecoderPlugin, types, ResultFormatter, utils +├── helpers.ts # Decode-fn helpers the codegen emits calls into +├── DecoderPlugin.ts # Base class generated plugins extend +├── DecoderPluginInterface.ts # Message, DecodeResult, Qualifiers, RawFields, … +├── MessageDecoder.ts # Interface the dispatcher implements (concrete impl lives in language repo) +├── DateTimeUtils.ts # Date/time helpers (HHMMSS → tod, etc.) +├── utils/ +│ ├── result_formatter.ts # ResultFormatter with 30+ methods +│ ├── coordinate_utils.ts # decodeStringCoordinates, decimal-minutes +│ ├── ascii85.ts # Pure-JS ASCII85 decoder +│ ├── compression.ts # pako wrapper + base64 +│ ├── miam.ts # MIAM PDU parsing +│ ├── arinc_702_helper.ts # H1 field dispatch + CRC validation +│ ├── icao_fpl_utils.ts # ICAO flight plan parsing +│ ├── flight_plan_utils.ts +│ └── route_utils.ts +├── types/ +│ ├── route.ts +│ ├── waypoint.ts +│ └── wind.ts +└── escape_hatches/ # Per-plugin custom-decoder/formatter hatches + └── index.ts # Re-exports each hatch module (added as needed) +``` + +## Source of truth + +Behavior comes from **`acars-decoder-typescript`** (in production). These files are the canonical copies; Stage 2 of the unification removes the duplicates from `acars-decoder-typescript/lib/` and points the language repo at this submodule path via a `tsconfig.json` path alias. + +## Used by + +Generated plugin source (output of `ads-gen --target ts`): + +```ts +import { DecoderPlugin } from "@airframes/ads-runtime-ts"; +import type { DecodeResult, Message, Options } from "@airframes/ads-runtime-ts"; +import { ResultFormatter } from "@airframes/ads-runtime-ts"; +import * as helpers from "@airframes/ads-runtime-ts/helpers"; +import * as hatches from "../escape_hatches"; + +export class Label_10_POS extends DecoderPlugin { /* ... */ } +``` + +## Type-checking + +```bash +npm install +npm run lint +``` diff --git a/runtimes/typescript/escape_hatches/index.ts b/runtimes/typescript/escape_hatches/index.ts new file mode 100644 index 0000000..e94238d --- /dev/null +++ b/runtimes/typescript/escape_hatches/index.ts @@ -0,0 +1,25 @@ +/** + * Per-plugin escape-hatch functions referenced from spec YAML via `custom: `. + * + * One file per plugin that needs hatches; each exports the named functions. + * Generated plugin code does: + * import * as hatches from "../escape_hatches"; + * hatches.(...) + * + * As specs are ported and reveal needed hatches, add the corresponding files + * here and re-export from this index. See docs/ESCAPE_HATCHES.md for the + * naming and signature contract. + * + * Currently expected (from reference specs): + * - arinc_702_dispatch (parse-level whole-plugin hatch) + * - arinc_702_format (formatter-level) + * - label_4a_variant_2_decode (field-level) + * - label_4a_variant_3_position (field-level) + * - label_4a_format (formatter-level) + * - ohma_unwrap_message (field-level) + * - ohma_message_item (formatter-level) + * - parse_flight_level_or_ground (decode-fn level) + * - flight_level_to_altitude_feet (decode-fn level) + */ + +export {}; diff --git a/runtimes/typescript/helpers.ts b/runtimes/typescript/helpers.ts new file mode 100644 index 0000000..dbdcc10 --- /dev/null +++ b/runtimes/typescript/helpers.ts @@ -0,0 +1,182 @@ +/** + * Helper functions the codegen emits calls into. + * + * Generated plugin code looks like: + * import * as helpers from "@airframes/ads-runtime-ts/helpers"; + * const latitude = helpers.coordinate(parts[1], { ... }); + * + * These functions wrap the underlying util modules (CoordinateUtils, + * DateTimeUtils, ascii85, compression) in a uniform shape matching the + * decode-fn semantics defined in spec/shared/decode_fns.yaml. + * + * Source of truth for behavior: acars-decoder-typescript. Behavior here MUST + * match the inline logic of the original plugin source byte-for-byte; the + * shared corpus enforces this. + */ + +import { CoordinateUtils } from "./utils/coordinate_utils"; +import { DateTimeUtils } from "./DateTimeUtils"; +import { base64ToUint8Array, inflateData } from "./utils/compression"; +import { ascii85Decode as ascii85Impl } from "./utils/ascii85"; + +// ─── coordinates ───────────────────────────────────────────────────────────── + +export interface CoordinateArgs { + style?: "single_axis" | "combined"; + axis?: "latitude" | "longitude"; + prefix_chars?: string[]; + digits?: number; + divisor?: number; + format?: string; +} + +/** + * Coordinate decoder. Two styles: + * - single_axis: input like "N12345" → ((sign) * Number(rest)) / divisor + * - combined: input like "N12345W123456" → {latitude, longitude} via CoordinateUtils + */ +export function coordinate(value: string, args: CoordinateArgs = {}): number | { latitude: number; longitude: number } | undefined { + const trimmed = value.trim(); + if (args.style === "combined") { + return CoordinateUtils.decodeStringCoordinates(trimmed); + } + // Default: single-axis prefixed + const prefixChars = args.prefix_chars ?? ["N", "S", "E", "W"]; + const divisor = args.divisor ?? 1000; + const positive = ["N", "E"]; + const prefix = trimmed[0]; + if (prefix === undefined || !prefixChars.includes(prefix)) return undefined; + const sign = positive.includes(prefix) ? 1 : -1; + const digits = trimmed.substring(1).trim(); + return (sign * Number(digits)) / divisor; +} + +export function coordinateDecimalMinutes(value: string, _args: CoordinateArgs = {}): { latitude: number; longitude: number } | undefined { + return CoordinateUtils.decodeStringCoordinatesDecimalMinutes(value); +} + +// ─── numerics ──────────────────────────────────────────────────────────────── + +export interface IntegerArgs { + substring_start?: number; + substring_length?: number; + multiplier?: number; +} + +export function integer(value: string, args: IntegerArgs = {}): number { + let s = value; + if (args.substring_start !== undefined || args.substring_length !== undefined) { + const start = args.substring_start ?? 0; + const end = args.substring_length !== undefined ? start + args.substring_length : undefined; + s = end !== undefined ? s.substring(start, end) : s.substring(start); + } + const n = Number(s); + return args.multiplier ? n * args.multiplier : n; +} + +export function float(value: string, args: { multiplier?: number } = {}): number { + const n = Number(value); + return args.multiplier ? n * args.multiplier : n; +} + +// ─── strings ───────────────────────────────────────────────────────────────── + +export function string(value: unknown): string { + return String(value); +} +export function trim(value: string): string { + return value.trim(); +} +export function uppercase(value: string): string { + return value.toUpperCase(); +} +export function lowercase(value: string): string { + return value.toLowerCase(); +} + +// ─── identifiers ───────────────────────────────────────────────────────────── + +export function callsign(value: string): string { + return value.trim(); +} + +export function tailNumber(value: string, args: { strip_chars?: string } = {}): string { + let s = value.trim(); + if (args.strip_chars) { + for (const c of args.strip_chars) s = s.split(c).join(""); + } + return s; +} + +export function flightNumber(value: string): string { + return value.trim(); +} + +export function airport(value: string): string { + return value.trim().toUpperCase(); +} + +// ─── timestamps ────────────────────────────────────────────────────────────── + +export function timestampHhmmss(value: string, args: { append?: string } = {}): number { + const s = args.append ? value + args.append : value; + return DateTimeUtils.convertHHMMSSToTod(s); +} + +// ─── binary / encoding ─────────────────────────────────────────────────────── + +export function base64ToUint8ArrayHelper(value: string): Uint8Array { + return base64ToUint8Array(value); +} +export { base64ToUint8ArrayHelper as base64ToUint8Array }; + +export function inflate(bytes: Uint8Array, _format: "raw" | "zlib" | "gzip" = "raw"): Uint8Array { + // inflateData(bytes, isZlib) — false → raw + const out = inflateData(bytes, false); + if (!out) throw new Error("inflate failed"); + return out; +} + +const _textDecoderCache = new Map(); +export function textDecode(bytes: Uint8Array, encoding: "utf-8" | "ascii" | "latin1" = "utf-8"): string { + let dec = _textDecoderCache.get(encoding); + if (!dec) { + dec = new TextDecoder(encoding); + _textDecoderCache.set(encoding, dec); + } + return dec.decode(bytes); +} + +export function decodeAscii85(value: string): Uint8Array { + const out = ascii85Impl(value); + if (out === null) throw new Error("ASCII85 decode failed"); + return out; +} + +export function hexDecode(value: string): Uint8Array { + const clean = value.replace(/[^0-9a-fA-F]/g, ""); + const out = new Uint8Array(clean.length / 2); + for (let i = 0; i < out.length; i++) { + out[i] = parseInt(clean.substring(i * 2, i * 2 + 2), 16); + } + return out; +} + +// ─── bitfield ──────────────────────────────────────────────────────────────── + +/** + * Extract bits [start..end] (inclusive on both ends) from a byte. + * Bit 0 = MSB by convention here (matches the spec's "0:3" = top 4 bits). + */ +export function bitslice(byte: number, start: number, end: number): number { + const width = end - start + 1; + const shift = 8 - end - 1; + const mask = (1 << width) - 1; + return (byte >> shift) & mask; +} + +export function concatBits(values: number[]): number { + // Placeholder — each spec defines its own width contract. Real impl will + // accept (value, width) pairs in v1.1. + return values.reduce((acc, v) => (acc << 8) | v, 0); +} diff --git a/runtimes/typescript/index.ts b/runtimes/typescript/index.ts new file mode 100644 index 0000000..5168e67 --- /dev/null +++ b/runtimes/typescript/index.ts @@ -0,0 +1,36 @@ +/** + * @airframes/ads-runtime-ts + * + * The TypeScript runtime library for ADS-generated plugins. Provides the + * DecoderPlugin base class, message/result types, the ResultFormatter, and + * the shared utility modules previously hosted in acars-decoder-typescript/lib/. + * + * Generated plugins (output of `ads-gen --target ts`) import from this package + * via the @airframes/ads-runtime-ts path alias configured in the consuming + * repo's tsconfig. + */ + +export { DecoderPlugin } from "./DecoderPlugin"; +export type { MessageDecoder } from "./MessageDecoder"; +export type { + DecodeResult, + DecoderPluginInterface, + Message, + Options, + Qualifiers, + RawFields, +} from "./DecoderPluginInterface"; +export { ResultFormatter } from "./utils/result_formatter"; +export { CoordinateUtils } from "./utils/coordinate_utils"; +export { DateTimeUtils } from "./DateTimeUtils"; +export { Arinc702Helper } from "./utils/arinc_702_helper"; +export { FlightPlanUtils } from "./utils/flight_plan_utils"; +export { RouteUtils } from "./utils/route_utils"; +export { parseIcaoFpl } from "./utils/icao_fpl_utils"; +export { MIAMCoreUtils } from "./utils/miam"; +export { base64ToUint8Array, inflateData } from "./utils/compression"; +export { ascii85Decode } from "./utils/ascii85"; + +export type { Route } from "./types/route"; +export type { Waypoint } from "./types/waypoint"; +export type { Wind } from "./types/wind"; diff --git a/runtimes/typescript/package-lock.json b/runtimes/typescript/package-lock.json new file mode 100644 index 0000000..5e02911 --- /dev/null +++ b/runtimes/typescript/package-lock.json @@ -0,0 +1,50 @@ +{ + "name": "@airframes/ads-runtime-ts", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@airframes/ads-runtime-ts", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "pako": "^2.1.0" + }, + "devDependencies": { + "@types/pako": "^2.0.4", + "typescript": "^5.7.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/pako": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz", + "integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==", + "dev": true, + "license": "MIT" + }, + "node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", + "license": "(MIT AND Zlib)" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + } + } +} diff --git a/runtimes/typescript/package.json b/runtimes/typescript/package.json new file mode 100644 index 0000000..64f5947 --- /dev/null +++ b/runtimes/typescript/package.json @@ -0,0 +1,34 @@ +{ + "name": "@airframes/ads-runtime-ts", + "version": "0.1.0", + "description": "TypeScript runtime library for ADS-generated ACARS decoder plugins. Consumed by acars-decoder-typescript via path alias or path dependency from the airframes-decoder submodule.", + "type": "module", + "main": "./index.ts", + "exports": { + ".": "./index.ts", + "./helpers": "./helpers.ts", + "./escape_hatches": "./escape_hatches/index.ts" + }, + "files": [ + "*.ts", + "utils/", + "types/", + "escape_hatches/", + "README.md" + ], + "scripts": { + "lint": "tsc --noEmit -p tsconfig.json" + }, + "dependencies": { + "pako": "^2.1.0" + }, + "devDependencies": { + "@types/pako": "^2.0.4", + "typescript": "^5.7.2" + }, + "engines": { + "node": ">=18" + }, + "license": "MIT", + "keywords": ["acars", "decoder", "runtime"] +} diff --git a/runtimes/typescript/tsconfig.json b/runtimes/typescript/tsconfig.json new file mode 100644 index 0000000..b0cacbf --- /dev/null +++ b/runtimes/typescript/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2022", "DOM"], + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "noEmit": true + }, + "include": ["**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/runtimes/typescript/types/route.ts b/runtimes/typescript/types/route.ts new file mode 100644 index 0000000..5922fea --- /dev/null +++ b/runtimes/typescript/types/route.ts @@ -0,0 +1,17 @@ +import { Waypoint } from './waypoint'; + +/** + * Representation of a route + * + * Typically a list of waypoints, this can also be a named company route. + */ +export interface Route { + /** optional name. If not set, `waypoints` is required */ + name?: string; + + /** optional runway */ + runway?: string; + + /** optional list of waypoints. If not set, `name` is required */ + waypoints?: Waypoint[]; +} diff --git a/runtimes/typescript/types/waypoint.ts b/runtimes/typescript/types/waypoint.ts new file mode 100644 index 0000000..f4eae3a --- /dev/null +++ b/runtimes/typescript/types/waypoint.ts @@ -0,0 +1,36 @@ +/** + * Represenation of a waypoint. + * + * Usually used in Routes which is an array of waypoints. + * Airways/Jetways can also be represented as a waypoint, by just the name. + * There is no distinction between the two currently because there is no difference from the current messages + * Distinction must be determined from tne name. + * In the event that a waypoint is a GPS position, the name can be the raw position string (N12345W012345) + */ +export interface Waypoint { + /** unique identifier of the waypoint*/ + name: string; + /** + * latitude in decimal degrees + * + * if set, longitude must be provided + */ + latitude?: number; + /** longitude in decimal degrees + * + * if set, latitude must be provided + */ + longitude?: number; + /** + * time of arrival. If in future, it is an ETA. + */ + time?: number; + + /** + * offset from the actual waypoint + * + * bearing: degrees from the waypoint + * distance: distance in nautical miles + */ + offset?: { bearing: number; distance: number }; +} diff --git a/runtimes/typescript/types/wind.ts b/runtimes/typescript/types/wind.ts new file mode 100644 index 0000000..1205872 --- /dev/null +++ b/runtimes/typescript/types/wind.ts @@ -0,0 +1,12 @@ +import { Waypoint } from './waypoint'; + +export interface Wind { + waypoint: Waypoint; + flightLevel: number; + windDirection: number; + windSpeed: number; + temperature?: { + flightLevel: number; + degreesC: number; + }; +} diff --git a/runtimes/typescript/utils/arinc_702_helper.ts b/runtimes/typescript/utils/arinc_702_helper.ts new file mode 100644 index 0000000..250d652 --- /dev/null +++ b/runtimes/typescript/utils/arinc_702_helper.ts @@ -0,0 +1,726 @@ +import { DateTimeUtils } from '../DateTimeUtils'; +import { DecodeResult } from '../DecoderPluginInterface'; +import { Waypoint } from '../types/waypoint'; +import { Wind } from '../types/wind'; +import { CoordinateUtils } from './coordinate_utils'; +import { FlightPlanUtils } from './flight_plan_utils'; +import { ResultFormatter } from './result_formatter'; +import { RouteUtils } from './route_utils'; + +// One byte per ASCII char — replaces the Buffer.from(data, 'ascii') idiom so +// the helper runs in browser / Deno / Bun without Node polyfills. +function asciiStringToBytes(data: string): Uint8Array { + const bytes = new Uint8Array(data.length); + for (let i = 0; i < data.length; i++) bytes[i] = data.charCodeAt(i) & 0xff; + return bytes; +} + +/** + * Helper class for decoding ARINC 702 messages + * contains core logic for decoding different types of ARINC 702 messages, + * as well as utility functions for processing common fields + * + * Core format: + * IMI/IEIdata/IEIdata/...checksum + */ +export class Arinc702Helper { + /** + * Decodes an ARINC 702 message + * + * Steps: + * 1. Check against known checksum algorithms to determine if message is valid + * 2. Parse the Imbedded Message Identifier (IMI) to determine the type of message + * 3. Iterate through the Information Elements (IEI) and decode known fields, while storing unknown fields for further analysis + * + * @param decodeResult - object to store decoded results in + * @param message - message to decode + * @returns boolean indicating if the message was successfully decoded + */ + public static decodeH1Message(decodeResult: DecodeResult, message: string) { + decodeResult.remaining.text = ''; + const checksum = parseInt(message.slice(-4), 16); + const data = message.slice(0, message.length - 4); + let checksumAlgorithmUsed = ''; + if (crc16IbmSdlcRev(data) === checksum) { + // ACARS/VDL2/HFDL/Iridium + checksumAlgorithmUsed = 'IBM-SDLC reversed'; + } else if (crc16Genibus(data) === checksum) { + // inmarsat + checksumAlgorithmUsed = 'GENIBUS'; + } else { + decodeResult.decoded = false; + decodeResult.decoder.decodeLevel = 'none'; + return false; + } + const fields = data.split('/'); + const canDecode = parseMessageType(decodeResult, fields[0]); + if (!canDecode) { + decodeResult.decoded = false; + decodeResult.decoder.decodeLevel = 'none'; + return false; + } + for (let i = 1; i < fields.length; ++i) { + const iei = fields[i].substring(0, 2); + const data = fields[i].substring(2); + switch (iei) { + case 'AF': + processAirField(decodeResult, data.split(',')); + break; + case 'AK': + ResultFormatter.unknown(decodeResult, fields[i], '/'); + // processAK(decodeResult, data.split(',')); + break; + case 'CG': + processCenterOfGravity(decodeResult, data.split(',')); + break; + case 'DC': + processDateCode(decodeResult, data.split(',')); + break; + case 'DI': + ResultFormatter.unknown(decodeResult, fields[i], '/'); + // processDI(decodeResult, data.split(',')); + break; + case 'DT': //processDestination? + processDT(decodeResult, data.split(',')); + break; + case 'DQ': + ResultFormatter.desiredAltitude( + decodeResult, + parseInt(data, 10) * 100, + ); + // processDQ(decodeResult, data.split(',')); + break; + case 'ET': + processETA(data, decodeResult, fields, i); + break; + case 'FB': + ResultFormatter.currentFuel(decodeResult, parseInt(data, 10)); + break; + case 'FN': + decodeResult.raw.flight_number = data; + break; + case 'FX': + ResultFormatter.text(decodeResult, data); + break; + case 'GA': + ResultFormatter.groundAddress(decodeResult, data); + break; + case 'ID': + processIdentification(decodeResult, data.split(',')); + break; + case 'LR': + processLandingReport(decodeResult, data.split(',')); + break; + case 'MR': + processMessageReference(decodeResult, data.split(',')); + break; + case 'PR': + processPerformanceData(decodeResult, data.split(',')); + break; + //case 'PR': + // TODO: decode /PR fields + // ResultFormatter.unknown(decodeResult, fields[i], '/'); + // break; + case 'PS': // Position + Arinc702Helper.processPS(decodeResult, data.split(',')); + break; + case 'RF': + case 'RI': + case 'RM': + case 'RP': + case 'RS': + // TODO - use key/data instead of whole message + FlightPlanUtils.processFlightPlan(decodeResult, fields[i].split(':')); + break; + case 'RN': + ResultFormatter.routeNumber(decodeResult, data); + break; + case 'RW': + processRunway(decodeResult, data.split(':')); + break; + case 'SM': + processSummary(decodeResult, data.split(',')); + break; + case 'SN': + decodeResult.raw.serial_number = data; + break; + case 'SP': + ResultFormatter.startPoint(decodeResult, data); + break; + case 'TD': + processTimeOfDeparture(decodeResult, data.split(',')); + break; + case 'TS': + Arinc702Helper.processTimeStamp(decodeResult, data.split(',')); + break; + case 'VR': + ResultFormatter.version(decodeResult, parseInt(data, 10) / 10); + break; + case 'WD': + processWindData(decodeResult, data); + break; + case 'WQ': + processWeatherQuery(decodeResult, data.split(':')); + break; + default: + ResultFormatter.unknown(decodeResult, fields[i], '/'); + } + } + + ResultFormatter.checksumAlgorithm(decodeResult, checksumAlgorithmUsed); + ResultFormatter.checksum(decodeResult, checksum); + + return true; + } + + public static processPS(decodeResult: DecodeResult, data: string[]) { + const position = CoordinateUtils.decodeStringCoordinatesDecimalMinutes( + data[0], + ); + if (position) { + ResultFormatter.position(decodeResult, position); + } + if (data.length === 9) { + // variant 7 + processRoute(decodeResult, data[3], data[1], data[5], data[4], undefined); + ResultFormatter.altitude(decodeResult, Number(data[2]) * 100); + ResultFormatter.temperature(decodeResult, data[6]); + ResultFormatter.unknown(decodeResult, data[7]); + ResultFormatter.unknown(decodeResult, data[8]); + } + if (data.length === 14) { + // variant 2 + ResultFormatter.altitude(decodeResult, Number(data[3]) * 100); + processRoute(decodeResult, data[1], data[2], data[4], data[5], data[6]); + ResultFormatter.temperature(decodeResult, data[7]); + ResultFormatter.unknownArr(decodeResult, data.slice(8)); + } + } + + public static processPosition(decodeResult: DecodeResult, data: string[]) { + const position = CoordinateUtils.decodeStringCoordinatesDecimalMinutes( + data[0], + ); + if (position) { + ResultFormatter.position(decodeResult, position); + } + if (data.length >= 10) { + // variant 1, short + ResultFormatter.altitude(decodeResult, Number(data[3]) * 100); + processRoute(decodeResult, data[1], data[2], data[4], data[5], data[6]); + ResultFormatter.temperature(decodeResult, data[7]); + ResultFormatter.unknown(decodeResult, data[8]); + ResultFormatter.unknown(decodeResult, data[9]); + } + if (data.length >= 14) { + // variant 2,long + ResultFormatter.unknownArr(decodeResult, data.slice(10)); + } + } + + public static processTimeStamp(decodeResult: DecodeResult, data: string[]) { + if (data.length > 2) { + const positionData = data.slice(1); + positionData[0] = positionData[0].substring(6); // strip time from position field + this.processPosition(decodeResult, positionData); + } + let time = DateTimeUtils.convertDateTimeToEpoch( + data[0], + data[1].substring(0, 6), + ); + + if (Number.isNaN(time)) { + // convert DDMMYY to MMDDYY - TODO figure out a better way to determine + const date = + data[1].substring(2, 4) + + data[1].substring(0, 2) + + data[1].substring(4, 6); + time = DateTimeUtils.convertDateTimeToEpoch(data[0], date); + } + decodeResult.raw.message_timestamp = time; + } +} + +function processETA( + data: string, + decodeResult: DecodeResult, + fields: string[], + i: number, +) { + if (data.length === 5) { + // 1 digit day + ResultFormatter.day(decodeResult, Number(data.substring(0, 1))); + ResultFormatter.eta( + decodeResult, + DateTimeUtils.convertHHMMSSToTod(data.substring(1)), + ); + } else if (data.length === 6) { + // 2 digit day + ResultFormatter.day(decodeResult, Number(data.substring(0, 2))); + ResultFormatter.eta( + decodeResult, + DateTimeUtils.convertHHMMSSToTod(data.substring(2)), + ); + } else { + ResultFormatter.unknown(decodeResult, fields[i], '/'); + } +} + +function processAirField(decodeResult: DecodeResult, data: string[]) { + if (data.length === 2) { + ResultFormatter.departureAirport(decodeResult, data[0]); + ResultFormatter.arrivalAirport(decodeResult, data[1]); + } else { + ResultFormatter.unknown(decodeResult, data.join(','), 'AF/'); + } +} +function processTimeOfDeparture(decodeResult: DecodeResult, data: string[]) { + if (data.length === 2) { + decodeResult.raw.planned_departure_time = data[0]; //DDHHMM - TODO: make int + decodeResult.formatted.items.push({ + type: 'ptd', + code: 'ptd', + label: 'Planned Departure Time', + value: `YYYY-MM-${data[0].substring(0, 2)}T${data[0].substring(2, 4)}:${data[0].substring(4)}:00Z`, + }); + + decodeResult.raw.estimated_departure_time = data[1]; //HHMM - TODO: make int + decodeResult.formatted.items.push({ + type: 'etd', + code: 'etd', + label: 'Estimated Departure Time', + value: `${data[1].substring(0, 2)}:${data[1].substring(2)}`, + }); + } else { + ResultFormatter.unknown(decodeResult, data.join(','), '/TD'); + } +} + +function processIdentification(decodeResult: DecodeResult, data: string[]) { + ResultFormatter.tail(decodeResult, data[0]); + if (data.length > 1) { + ResultFormatter.flightNumber(decodeResult, data[1]); + } + if (data.length > 2) { + //TODO: figure out what this is + decodeResult.raw.mission_number = data[2]; + } +} + +function processDT(decodeResult: DecodeResult, data: string[]) { + if (data.length === 9) { + ResultFormatter.unknown(decodeResult, data[0]); + if (!decodeResult.raw.arrival_icao) { + ResultFormatter.arrivalAirport(decodeResult, data[1]); + } + ResultFormatter.arrivalRunway(decodeResult, data[2]); + ResultFormatter.currentFuel(decodeResult, parseInt(data[3], 10)); + ResultFormatter.eta( + decodeResult, + DateTimeUtils.convertHHMMSSToTod(data[4]), + ); + ResultFormatter.unknown(decodeResult, data[5]); + ResultFormatter.position( + decodeResult, + CoordinateUtils.decodeStringCoordinates(data[6]), + ); + ResultFormatter.unknown(decodeResult, data[7]); + ResultFormatter.unknown(decodeResult, data[8]); + return; + } + if (data.length === 4 || data.length === 5) { + if (!decodeResult.raw.arrival_icao) { + ResultFormatter.arrivalAirport(decodeResult, data[0]); + } + ResultFormatter.arrivalRunway(decodeResult, data[1]); + ResultFormatter.currentFuel(decodeResult, parseInt(data[2], 10)); + ResultFormatter.eta( + decodeResult, + DateTimeUtils.convertHHMMSSToTod(data[3]), + ); + if (data.length > 4) { + ResultFormatter.remainingFuel(decodeResult, Number(data[4])); + } + return; + } + ResultFormatter.unknownArr(decodeResult, data, ','); +} + +function processLandingReport(decodeResult: DecodeResult, data: string[]) { + if (data.length === 19) { + ResultFormatter.unknown(decodeResult, data[1]); + ResultFormatter.flightNumber(decodeResult, data[2]); + ResultFormatter.departureAirport(decodeResult, data[3]); + ResultFormatter.arrivalAirport(decodeResult, data[4]); + ResultFormatter.arrivalRunway(decodeResult, data[5]); + ResultFormatter.unknown(decodeResult, data.slice(6, 19).join(',')); + } else { + ResultFormatter.unknown(decodeResult, data.join(',')); + } +} + +function processCenterOfGravity(decodeResult: DecodeResult, data: string[]) { + if (data.length === 1) { + if (data[0] !== undefined && data[0] !== '') { + ResultFormatter.cg(decodeResult, parseInt(data[0], 10) / 10); + } + } else if (data.length === 3) { + if (data[0] !== undefined && data[0] !== '') { + ResultFormatter.cg(decodeResult, parseInt(data[0], 10) / 10, 'center'); + } + if (data[1] !== undefined && data[1] !== '') { + ResultFormatter.cg(decodeResult, parseInt(data[1], 10) / 10, 'lower'); + } + if (data[2] !== undefined && data[2] !== '') { + ResultFormatter.cg(decodeResult, parseInt(data[2], 10) / 10, 'upper'); + } + } else { + ResultFormatter.unknown(decodeResult, data.join(',')); + } +} + +function parseMessageType( + decodeResult: DecodeResult, + messagePart: string, +): boolean { + const messageParts = messagePart.split(','); + const messageType = messageParts[0]; + if (messageType.startsWith('POS')) { + Arinc702Helper.processPosition( + decodeResult, + messagePart.substring(3).split(','), + ); + return processIMI(decodeResult, 'POS'); + } else if (messageType.length === 6) { + const part1 = processIMI(decodeResult, messageType.substring(0, 3)); + const description = decodeResult.formatted.description; + const part2 = processIMI(decodeResult, messageType.substring(3, 6)); + decodeResult.formatted.description = + description + ' for ' + decodeResult.formatted.description; + if (messageParts.length > 1) { + // TODO handle REJPWI/REJPOS + ResultFormatter.unknown( + decodeResult, + messageParts.slice(1).join(','), + '/', + ); + } + return part1 && part2; + } + + if (messageParts.length > 1) { + // TODO handle + ResultFormatter.unknown(decodeResult, messageParts.slice(1).join(','), '/'); + } + + return processIMI(decodeResult, messageType); +} + +/** + * Processes the Imbedded Message Identifier (IMI) + * which indicates the type of message and is used to determine what the message type is + * @param decodeResult + * @param type + * @returns + */ +function processIMI(decodeResult: DecodeResult, type: string): boolean { + if (type === 'FPN') { + decodeResult.formatted.description = 'Flight Plan'; + } else if (type === 'FTX') { + decodeResult.formatted.description = 'Free Text'; + } else if (type === 'INI') { + decodeResult.formatted.description = 'Initial Report'; + } else if (type === 'INR') { + decodeResult.formatted.description = 'In-Range Report'; + } else if (type === 'LDI') { + decodeResult.formatted.description = 'Load Distribution Information'; + } else if (type === 'PER') { + decodeResult.formatted.description = 'Performance Report'; + } else if (type === 'POS') { + decodeResult.formatted.description = 'Position Report'; + } else if (type === 'PRG') { + decodeResult.formatted.description = 'Progress Report'; + } else if (type === 'PWI') { + decodeResult.formatted.description = 'Pilot Weather Information'; + } else if (type === 'WXR') { + decodeResult.formatted.description = 'Weather Report'; + } else if (type === 'REJ') { + decodeResult.formatted.description = 'Reject'; + } else if (type === 'REQ') { + decodeResult.formatted.description = 'Request'; + } else if (type === 'RES') { + decodeResult.formatted.description = 'Response'; + } else if (type === 'SUM') { + decodeResult.formatted.description = 'Summary Report'; + } else { + decodeResult.formatted.description = 'Unknown H1 Message'; + return false; + } + return true; +} + +function processPerformanceData(decodeResult: DecodeResult, data: string[]) { + // /PR fields contain performance data + // Known field positions (12-field short variant and 18-field long variant): + // [0]: ground speed (knots * 10 or similar) + // [1]: indicated airspeed or mach-related + // [2]: altitude in hundreds of feet + // [3]: fuel-related value + // [4]: unknown (often empty) + // [5]: fuel flow or consumption + // [6]: fuel-related + // [7]: wind data (DDDSSSS format) or empty + // [8]: temperature (M=minus, P=plus) + // [9]: heading or unknown + + if (data.length < 3) { + ResultFormatter.unknownArr(decodeResult, data, ','); + return; + } + + // Field 2: altitude in hundreds + if (data[2] !== undefined && data[2] !== '') { + const alt = Number(data[2]) * 100; + if (!isNaN(alt) && alt > 0) { + ResultFormatter.altitude(decodeResult, alt); + } + } + + // Field 8: temperature (if present) + if (data.length > 8 && data[8] !== undefined && data[8] !== '') { + ResultFormatter.temperature(decodeResult, data[8]); + } + + // Collect remaining undecoded fields + const remaining: string[] = []; + for (let i = 0; i < data.length; i++) { + if ( + i === 2 && + data[i] !== '' && + !isNaN(Number(data[i])) && + Number(data[i]) * 100 > 0 + ) { + continue; // altitude - already decoded + } + if ( + i === 8 && + data[i] !== '' && + (data[i].startsWith('M') || + data[i].startsWith('P') || + !isNaN(Number(data[i]))) + ) { + continue; // temperature - already decoded + } + remaining.push(data[i]); + } + if (remaining.length > 0 && remaining.some((r) => r !== '')) { + ResultFormatter.unknown(decodeResult, remaining.join(','), '/PR'); + } +} + +function processDateCode(decodeResult: DecodeResult, data: string[]) { + if (data.length === 1) { + // noop? + } else if (data.length === 2) { + // convert DDMMYY to MMDDYY - TODO figure out a better way to determine + const date = + data[0].substring(2, 4) + + data[0].substring(0, 2) + + data[0].substring(4, 6); + const time = DateTimeUtils.convertDateTimeToEpoch(data[1], data[0]); // HHMMSS + + decodeResult.raw.message_timestamp = time; + } +} + +function processWeatherQuery(decodeResult: DecodeResult, data: string[]) { + if (data.length !== 2) { + ResultFormatter.unknown(decodeResult, data.join(':'), 'WQ/'); + return; + } + + const alts = data[0].split('.'); + const route = data[1].split('.'); + + ResultFormatter.requestedAltitudes( + decodeResult, + alts.map((a) => parseInt(a, 10) * 100), + ); + + const waypoints = route.map((wp) => { + return { name: wp }; + }); + ResultFormatter.route(decodeResult, { waypoints: waypoints }); +} + +function processRoute( + decodeResult: DecodeResult, + last: string, + time: string, + next: string, + eta: string, + then?: string, + date?: string, +) { + const lastTime = date + ? DateTimeUtils.convertDateTimeToEpoch(time, date) + : DateTimeUtils.convertHHMMSSToTod(time); + const nextTime = date + ? DateTimeUtils.convertDateTimeToEpoch(eta, date) + : DateTimeUtils.convertHHMMSSToTod(eta); + + const lastWaypoint = RouteUtils.getWaypoint(last); + lastWaypoint.time = lastTime; + + const nextWaypoint = RouteUtils.getWaypoint(next); + nextWaypoint.time = nextTime; + + const thenWaypoint = RouteUtils.getWaypoint(then || '?'); + + const waypoints: Waypoint[] = [lastWaypoint, nextWaypoint, thenWaypoint]; + ResultFormatter.route(decodeResult, { waypoints: waypoints }); +} + +function processWindData(decodeResult: DecodeResult, message: string) { + const wind = [] as Wind[]; + + const flightLevel = Number(message.slice(0, 3)); + const fields = message.slice(4).split('.'); // strip off altitude and comma + fields.forEach((field) => { + if (field.length < 4) { + // probably need a more robust check to determine to skip + return; + } + const data = field.split(','); + const waypoint = { name: data[0] }; + const windData = data[1]; + const windDirection = parseInt(windData.slice(0, 3), 10); + const windSpeed = parseInt(windData.slice(3), 10); + + if (data.length === 3) { + const tempData = data[2]; + const tempFlightLevel = Number(tempData.slice(0, 3)); + const tempString = tempData.slice(3); + const tempDegrees = + Number(tempString.substring(1)) * + (tempString.charAt(0) === 'M' ? -1 : 1); + wind.push({ + waypoint: waypoint, + flightLevel: flightLevel, + windDirection: windDirection, + windSpeed: windSpeed, + temperature: { + flightLevel: tempFlightLevel, + degreesC: tempDegrees, + }, + }); + } else { + wind.push({ + waypoint: waypoint, + flightLevel: flightLevel, + windDirection: windDirection, + windSpeed: windSpeed, + }); + } + }); + + ResultFormatter.windData(decodeResult, wind); +} + +function processRunway(decodeResult: DecodeResult, data: string[]) { + //FIXME - figure out names + const parts = data[0].split('.'); + const more = parts[0].split(','); + + ResultFormatter.departureRunway(decodeResult, more[0]); + ResultFormatter.unknownArr(decodeResult, more.slice(1), ','); + ResultFormatter.unknownArr(decodeResult, parts.slice(1), '.'); + ResultFormatter.departureAirport(decodeResult, data[1].split(',')[0]); + ResultFormatter.unknownArr(decodeResult, data[1].split(',').slice(1), ','); + //ResultFormatter.unknownArr(decodeResult, data.slice(2), ':'); +} +function processSummary(decodeResult: DecodeResult, data: string[]) { + if (data.length !== 11) { + return ResultFormatter.unknown(decodeResult, data.join(','), 'SM/'); + } + + ResultFormatter.engineStart( + decodeResult, + DateTimeUtils.convertDayTimeToTod(data[0]), + ); + ResultFormatter.startFuel(decodeResult, 100 * parseInt(data[1], 10)); + ResultFormatter.engineStop( + decodeResult, + DateTimeUtils.convertDayTimeToTod(data[2]), + ); + ResultFormatter.out(decodeResult, DateTimeUtils.convertDayTimeToTod(data[3])); + ResultFormatter.outFuel(decodeResult, 100 * parseInt(data[4], 10)); + ResultFormatter.off(decodeResult, DateTimeUtils.convertDayTimeToTod(data[5])); + ResultFormatter.offFuel(decodeResult, 100 * parseInt(data[6], 10)); + ResultFormatter.on(decodeResult, DateTimeUtils.convertDayTimeToTod(data[7])); + ResultFormatter.onFuel(decodeResult, 100 * parseInt(data[8], 10)); + ResultFormatter.in(decodeResult, DateTimeUtils.convertDayTimeToTod(data[9])); + ResultFormatter.inFuel(decodeResult, 100 * parseInt(data[10], 10)); +} + +// CRC-16/IBM-SDLC but nibbles are reversed +function crc16IbmSdlcRev(data: string): number { + let crc = 0xffff; + const bytes = asciiStringToBytes(data); + + for (const byte of bytes) { + crc ^= byte; + for (let i = 0; i < 8; i++) { + if ((crc & 0x0001) !== 0) { + crc = (crc >>> 1) ^ 0x8408; + } else { + crc = crc >>> 1; + } + } + } + crc = (crc ^ 0xffff) & 0xffff; + + const nibble1 = (crc >> 12) & 0xf; + const nibble2 = (crc >> 8) & 0xf; + const nibble3 = (crc >> 4) & 0xf; + const nibble4 = crc & 0xf; + + return (nibble4 << 12) | (nibble3 << 8) | (nibble2 << 4) | nibble1; +} + +/** + * Calculates the CRC-16/GENIBUS checksum for a given Uint8Array. + * @param data The input data as a byte array. + * @returns The 16-bit checksum as a number. + */ +function crc16Genibus(data: string): number { + let crc = 0xffff; + const polynomial = 0x1021; + + const bytes = asciiStringToBytes(data); + + for (const byte of bytes) { + crc ^= byte << 8; + + for (let i = 0; i < 8; i++) { + if ((crc & 0x8000) !== 0) { + crc = ((crc << 1) ^ polynomial) & 0xffff; + } else { + crc = (crc << 1) & 0xffff; + } + } + } + + return (crc ^ 0xffff) & 0xffff; +} +function processMessageReference(decodeResult: DecodeResult, data: string[]) { + if (data.length !== 2) { + ResultFormatter.unknown(decodeResult, data.join(','), '/MR'); + return; + } + + ResultFormatter.sequenceNumber(decodeResult, parseInt(data[0], 10)); + if (data[1] !== '') { + ResultFormatter.sequenceResponse(decodeResult, parseInt(data[1], 10)); + } +} diff --git a/runtimes/typescript/utils/ascii85.ts b/runtimes/typescript/utils/ascii85.ts new file mode 100644 index 0000000..5a3a7fe --- /dev/null +++ b/runtimes/typescript/utils/ascii85.ts @@ -0,0 +1,51 @@ +/** + * Pure JavaScript ASCII85 (Adobe variant) decoder. + * Replaces the `base85` npm package to eliminate Node.js Buffer dependency. + * Works in browsers, Node.js 18+, Deno, Bun, and edge runtimes. + */ +export function ascii85Decode(input: string): Uint8Array | null { + let str = input; + if (str.startsWith('<~')) str = str.slice(2); + if (str.endsWith('~>')) str = str.slice(0, -2); + str = str.replace(/\s/g, ''); + if (str.length === 0) return new Uint8Array(0); + + const output: number[] = []; + let i = 0; + + while (i < str.length) { + if (str[i] === 'z') { + output.push(0, 0, 0, 0); + i++; + continue; + } + + const remaining = str.length - i; + const chunkLen = Math.min(5, remaining); + if (chunkLen === 1) break; // trailing single char from truncated input; ignore it + + let padded = str.slice(i, i + chunkLen); + while (padded.length < 5) padded += 'u'; + + let value = 0; + for (let j = 0; j < 5; j++) { + const digit = padded.charCodeAt(j) - 33; + if (digit < 0 || digit > 84) return null; + value = value * 85 + digit; + } + + if (chunkLen === 5 && value > 0xffffffff) return null; + + // Use >>> 0 to simulate uint32 overflow for padded groups + const v = value >>> 0; + const numBytes = chunkLen === 5 ? 4 : chunkLen - 1; + if (numBytes >= 1) output.push((v >>> 24) & 0xff); + if (numBytes >= 2) output.push((v >>> 16) & 0xff); + if (numBytes >= 3) output.push((v >>> 8) & 0xff); + if (numBytes >= 4) output.push(v & 0xff); + + i += chunkLen; + } + + return new Uint8Array(output); +} diff --git a/runtimes/typescript/utils/compression.ts b/runtimes/typescript/utils/compression.ts new file mode 100644 index 0000000..ddd25d9 --- /dev/null +++ b/runtimes/typescript/utils/compression.ts @@ -0,0 +1,56 @@ +import * as pako from 'pako'; + +/** + * Inflate compressed data with support for partial/truncated streams. + * Uses Z_SYNC_FLUSH to extract as much data as possible, even from + * incomplete deflate streams. Captures output via onData callback. + * + * @param data - The compressed input bytes + * @param raw - If true, use raw deflate (no zlib header); if false, expect zlib header + * @returns The decompressed bytes, or undefined if no output was produced + */ +export function inflateData( + data: Uint8Array, + raw: boolean, +): Uint8Array | undefined { + const chunks: Uint8Array[] = []; + const inflator = new pako.Inflate({ windowBits: raw ? -15 : 15 }); + inflator.onData = (chunk: Uint8Array) => { + chunks.push(chunk); + }; + inflator.push(data, 2); // Z_SYNC_FLUSH + + if (chunks.length === 0) return undefined; + if (chunks.length === 1) return chunks[0]; + + const totalLen = chunks.reduce((sum, c) => sum + c.length, 0); + const result = new Uint8Array(totalLen); + let offset = 0; + for (const chunk of chunks) { + result.set(chunk, offset); + offset += chunk.length; + } + return result; +} + +/** + * Decode a base64 or base64url string to Uint8Array. + * Handles missing padding and converts base64url chars (- and _) to + * standard base64 chars (+ and /), matching Buffer.from(str, 'base64') behavior. + * + * @param base64 - The base64 or base64url encoded string + * @returns The decoded bytes + */ +export function base64ToUint8Array(base64: string): Uint8Array { + const cleaned = base64 + .replace(/-/g, '+') + .replace(/_/g, '/') + .replace(/[^A-Za-z0-9+/]/g, ''); + const padded = cleaned + '='.repeat((4 - (cleaned.length % 4)) % 4); + const binary = atob(padded); + const bytes = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) { + bytes[i] = binary.charCodeAt(i); + } + return bytes; +} diff --git a/runtimes/typescript/utils/coordinate_utils.ts b/runtimes/typescript/utils/coordinate_utils.ts new file mode 100644 index 0000000..66e66f0 --- /dev/null +++ b/runtimes/typescript/utils/coordinate_utils.ts @@ -0,0 +1,96 @@ +export class CoordinateUtils { + /** + * Decode a string of coordinates into an object with latitude and longitude in millidegrees + * @param stringCoords - The string of coordinates to decode + * + * @returns An object with latitude and longitude properties + */ + public static decodeStringCoordinates( + stringCoords: string, + ): { latitude: number; longitude: number } | undefined { + // format: N12345W123456 or N12345 W123456 + const firstChar = stringCoords.substring(0, 1); + let middleChar = stringCoords.substring(6, 7); + let longitudeChars = stringCoords.substring(7, 13); + if (middleChar == ' ') { + middleChar = stringCoords.substring(7, 8); + longitudeChars = stringCoords.substring(8, 14); + } + if ( + (firstChar === 'N' || firstChar === 'S') && + (middleChar === 'W' || middleChar === 'E') + ) { + return { + latitude: + (Number(stringCoords.substring(1, 6)) / 1000) * + CoordinateUtils.getDirection(firstChar), + longitude: + (Number(longitudeChars) / 1000) * + CoordinateUtils.getDirection(middleChar), + }; + } + + return undefined; + } + + /** + * Decode a string of coordinates into an object with latitude and longitude in degrees and decimal minutes + * @param stringCoords - The string of coordinates to decode + * + * @returns An object with latitude and longitude properties + */ + public static decodeStringCoordinatesDecimalMinutes( + stringCoords: string, + ): { latitude: number; longitude: number } | undefined { + // format: N12345W123456 or N12345 W123456 + const firstChar = stringCoords.substring(0, 1); + let middleChar = stringCoords.substring(6, 7); + let longitudeChars = stringCoords.substring(7, 13); + if (middleChar == ' ') { + middleChar = stringCoords.substring(7, 8); + longitudeChars = stringCoords.substring(8, 14); + } + const latDeg = Math.trunc(Number(stringCoords.substring(1, 6)) / 1000); + const latMin = (Number(stringCoords.substring(1, 6)) % 1000) / 10; + const lonDeg = Math.trunc(Number(longitudeChars) / 1000); + const lonMin = (Number(longitudeChars) % 1000) / 10; + + if ( + (firstChar === 'N' || firstChar === 'S') && + (middleChar === 'W' || middleChar === 'E') + ) { + return { + latitude: + (latDeg + latMin / 60) * CoordinateUtils.getDirection(firstChar), + longitude: + (lonDeg + lonMin / 60) * CoordinateUtils.getDirection(middleChar), + }; + } + return undefined; + } + public static coordinateString(coords: { + latitude: number; + longitude: number; + }): string { + const latDir = coords.latitude > 0 ? 'N' : 'S'; + const lonDir = coords.longitude > 0 ? 'E' : 'W'; + return `${Math.abs(coords.latitude).toFixed(3)} ${latDir}, ${Math.abs(coords.longitude).toFixed(3)} ${lonDir}`; + } + + public static getDirection(coord: string): number { + if (coord.startsWith('N') || coord.startsWith('E')) { + return 1; + } else if (coord.startsWith('S') || coord.startsWith('W')) { + return -1; + } + return NaN; + } + + public static dmsToDecimalDegrees( + degrees: number, + minutes: number, + seconds: number, + ): number { + return degrees + minutes / 60 + seconds / 3600; + } +} diff --git a/runtimes/typescript/utils/flight_plan_utils.ts b/runtimes/typescript/utils/flight_plan_utils.ts new file mode 100644 index 0000000..cd87c4b --- /dev/null +++ b/runtimes/typescript/utils/flight_plan_utils.ts @@ -0,0 +1,197 @@ +import { DecodeResult } from '../DecoderPluginInterface'; +import { ResultFormatter } from './result_formatter'; +import { RouteUtils } from './route_utils'; + +export class FlightPlanUtils { + /** + * Processes flight plan data + * + * Expected format is [header, fpei, val1, ... fpeiN, valN] + * + * @see https://atlaske-content.garmin.com/filestorage//email/outbound/attachments/GTN_Flight_Plan_and_User_Waypoint_transfer_Time1712844670119.pdf + * @param decodeResult - results + * @param data - original message split by ':' + * @returns whether all fields were processed or not + */ + public static processFlightPlan( + decodeResult: DecodeResult, + data: string[], + ): boolean { + let allKnownFields = FlightPlanUtils.parseHeader(decodeResult, data[0]); + for (let i = 1; i < data.length; i += 2) { + const fpei = data[i]; + const value = data[i + 1]; + // TODO: discuss how store commented out bits as both raw and formatted + switch (fpei) { + case 'A': // Arrival Procedure (?) + FlightPlanUtils.addProcedure(decodeResult, value, 'arrival'); + break; + case 'AA': + addArrivalAirport(decodeResult, value); + break; + case 'AP': + FlightPlanUtils.addProcedure(decodeResult, value, 'approach'); + break; + case 'CR': + addCompanyRoute(decodeResult, value); + break; + case 'D': // Departure Procedure + FlightPlanUtils.addProcedure(decodeResult, value, 'departure'); + break; + case 'DA': + addDepartureAirport(decodeResult, value); + break; + case 'F': // First Waypoint + addRoute(decodeResult, value); + break; + case 'FP': + ResultFormatter.flightPlan(decodeResult, value.trim()); + break; + case 'R': + addRunway(decodeResult, value); + break; + // case 'WS': // something about routes, has altitude, so current parsing won't work + // break; + default: + if (allKnownFields) { + decodeResult.remaining.text = ''; + allKnownFields = false; + } + decodeResult.remaining.text += `:${fpei}:${value}`; + decodeResult.decoder.decodeLevel = 'partial'; + } + } + return allKnownFields; + } + public static parseHeader( + decodeResult: DecodeResult, + header: string, + ): boolean { + let allKnownFields = true; + if (header.startsWith('RF')) { + decodeResult.formatted.items.push({ + type: 'status', + code: 'ROUTE_STATUS', + label: 'Route Status', + value: 'Route Filed', + }); + decodeResult.raw.route_status = 'RF'; + if (header.length > 2) { + addRoute(decodeResult, header.substring(2)); + } + } else if (header.startsWith('RP')) { + decodeResult.raw.route_status = 'RP'; + decodeResult.formatted.items.push({ + type: 'status', + code: 'ROUTE_STATUS', + label: 'Route Status', + value: 'Route Planned', + }); + decodeResult.raw.route_status = header; + } else if (header.startsWith('RI')) { + decodeResult.raw.route_status = 'RI'; + decodeResult.formatted.items.push({ + type: 'status', + code: 'ROUTE_STATUS', + label: 'Route Status', + value: 'Route Inactive', + }); + } else if (header.startsWith('RM')) { + decodeResult.raw.route_status = 'RM'; + decodeResult.formatted.items.push({ + type: 'status', + code: 'ROUTE_STATUS', + label: 'Route Status', + value: 'Route Mapped', + }); + } else if (header.startsWith('RS')) { + decodeResult.raw.route_status = 'RS'; + decodeResult.formatted.items.push({ + type: 'status', + code: 'ROUTE_STATUS', + label: 'Route Status', + value: 'Route Saved', + }); + } else { + decodeResult.remaining.text += header; + allKnownFields = false; + } + return allKnownFields; + } + + public static addProcedure( + decodeResult: DecodeResult, + value: string, + type: string, + ) { + if (decodeResult.raw.procedures === undefined) { + decodeResult.raw.procedures = []; + } + const data = value.split('.'); + let waypoints; + if (data.length > 1) { + waypoints = data.slice(1).map((leg) => RouteUtils.getWaypoint(leg)); + } + const route = { name: data[0], waypoints: waypoints }; + (decodeResult.raw.procedures as Array<{ type: string; route: object }>).push({ type: type, route: route }); + const procedureName = type.substring(0, 1).toUpperCase() + type.slice(1); + decodeResult.formatted.items.push({ + type: 'procedure', + code: 'proc', + label: `${procedureName} Procedure`, + value: RouteUtils.routeToString(route), + }); + } +} + +function addArrivalAirport(decodeResult: DecodeResult, value: string) { + ResultFormatter.arrivalAirport(decodeResult, value); +} + +function addDepartureAirport(decodeResult: DecodeResult, value: string) { + ResultFormatter.departureAirport(decodeResult, value); +} + +function addRunway(decodeResult: DecodeResult, value: string) { + // xxx(yyy) where xxx is the departure runway and yyy is the arrival runway + if (value.length === 8) { + ResultFormatter.arrivalRunway(decodeResult, value.substring(4, 7)); + } + + ResultFormatter.departureRunway(decodeResult, value.substring(0, 3)); +} + +function addRoute(decodeResult: DecodeResult, value: string) { + const route = value.split('.'); + ResultFormatter.route(decodeResult, { + waypoints: route.map((leg) => RouteUtils.getWaypoint(leg)), + }); +} + +function addCompanyRoute(decodeResult: DecodeResult, value: string) { + const segments = value.split('.'); + const parens_idx = segments[0].indexOf('('); + let name; + let runway; + if (parens_idx === -1) { + name = segments[0]; + } else { + name = segments[0].slice(0, parens_idx); + runway = segments[0].slice(parens_idx + 1, segments[0].indexOf(')')); + } + let waypoints; + if (segments.length > 1) { + waypoints = segments.slice(1).map((leg) => RouteUtils.getWaypoint(leg)); + } + decodeResult.raw.company_route = { + name: name, + runway: runway, + waypoints: waypoints, + }; + decodeResult.formatted.items.push({ + type: 'company_route', + code: 'CR', + label: 'Company Route', + value: RouteUtils.routeToString(decodeResult.raw.company_route as import("../types/route").Route), + }); +} diff --git a/runtimes/typescript/utils/icao_fpl_utils.ts b/runtimes/typescript/utils/icao_fpl_utils.ts new file mode 100644 index 0000000..41cc67a --- /dev/null +++ b/runtimes/typescript/utils/icao_fpl_utils.ts @@ -0,0 +1,195 @@ +/** + * ICAO Doc 4444 Flight Plan (FPL) parser utility. + * + * Parses the standard ICAO FPL format: + * (FPL-callsign-flightRulesType + * -aircraftType/wake-equipment/surveillance + * -departureHHMM + * -speedLevel route + * -destinationHHMM alternate1 alternate2 + * -otherInfo) + */ + +import { Route } from '../types/route'; + +export interface IcaoFlightPlan { + callsign: string; + flightRules: string; + flightType: string; + aircraftType: string; + wakeTurbulence: string; + equipment: string; + surveillance: string; + departure: string; + departureTime: string; + cruiseSpeed: string; + cruiseLevel: string; + route: Route; + destination: string; + eet: string; + alternates: string[]; + otherInfo: Record; +} + +export function parseIcaoFpl(text: string): IcaoFlightPlan | null { + // Find the (FPL-...) block + const fplStart = text.indexOf('(FPL-'); + if (fplStart === -1) { + return null; + } + + // Find matching closing paren + const fplEnd = text.indexOf(')', fplStart); + if (fplEnd === -1) { + return null; + } + + // Extract inner content after "(FPL-" + const inner = text.substring(fplStart + 5, fplEnd); + + // Normalize: collapse all whitespace (newlines, multiple spaces) to single space + const normalized = inner.replace(/\s+/g, ' ').trim(); + + // Split on " -" or leading "-" to get the field sections. + // The format is: callsign-rulesType-acft/wake-equip/surv-deptHHMM-speedLevel route-destHHMM alts-other + // After removing "(FPL-", the first field is callsign-rulesType. + // Subsequent fields are separated by "-" that appears after a space or at start of a section line. + // However, the "-" delimiter is tricky: it separates major sections but also appears + // within equipment strings (e.g. SDE3FGHIJ1...) — those are NOT delimiters. + // + // The ICAO FPL has exactly 6 major sections after the callsign section, delimited by "-". + // We need to split carefully. The format after "(FPL-" is: + // section1-section2-section3-section4-section5-section6 + // where section1 = "callsign-rulesType" (contains one internal "-") + // + // Strategy: split on "-" and reconstruct based on known patterns. + + const parts = normalized.split('-'); + + // We need at least 7 parts (callsign, rulesType, acftType/wake, equip/surv, dept, speed+route, dest+alts, other...) + // But the equipment section may contain hyphens too, so we parse positionally. + + if (parts.length < 7) { + return null; + } + + // Section 1: callsign + const callsign = parts[0].trim(); + if (!callsign) { + return null; + } + + // Section 2: flight rules + flight type (e.g., "IS") + const rulesType = parts[1].trim(); + if (rulesType.length < 2) { + return null; + } + const flightRules = rulesType[0]; + const flightType = rulesType[1]; + + // Now we need to find the remaining sections. The challenge is that equipment strings + // can be long but don't contain "-". The aircraft/wake section is "TYPE/WAKE". + // The equipment/surveillance section is "EQUIP/SURV". + // These are separated by "-". + + // Section 3: aircraft type / wake turbulence category (e.g., "B77W/H") + const acftWake = parts[2].trim(); + const acftSlash = acftWake.indexOf('/'); + if (acftSlash === -1) { + return null; + } + const aircraftType = acftWake.substring(0, acftSlash); + const wakeTurbulence = acftWake.substring(acftSlash + 1); + + // Section 4: equipment / surveillance (e.g., "SDE3FGHIJ1J2J3J4J5M1P2RWXYZ/LB1D1") + const equipSurv = parts[3].trim(); + const equipSlash = equipSurv.indexOf('/'); + if (equipSlash === -1) { + return null; + } + const equipment = equipSurv.substring(0, equipSlash); + const surveillance = equipSurv.substring(equipSlash + 1); + + // Section 5: departure aerodrome + time (e.g., "VESPA2354" or "KSFO0100") + const deptField = parts[4].trim(); + // Time is always last 4 digits + const departureTime = deptField.substring(deptField.length - 4); + const departure = deptField.substring(0, deptField.length - 4); + + // Section 6: cruise speed/level + route (e.g., "N0482F350 DCT ENI DCT OAK DCT BURGL IRNMN2") + const speedRouteField = parts[5].trim(); + // Speed is first token like N0482 or M084, level follows like F350 or S1190 + const speedMatch = speedRouteField.match( + /^([NKM]\d{3,4})([FAVMS]\d{3,4})\s*(.*)/, + ); + if (!speedMatch) { + return null; + } + const cruiseSpeed = speedMatch[1]; + const cruiseLevel = speedMatch[2]; + const route = { + waypoints: speedMatch[3].split(' ').map((s) => ({ + name: s, + })), + }; + + // Section 7: destination + EET + alternates (e.g., "KLAX0117 KSFO") + const destField = parts[6].trim(); + const destParts = destField.split(/\s+/); + const destTime = destParts[0]; + // Destination ICAO is first 4 chars, EET is last 4 digits + const destination = destTime.substring(0, destTime.length - 4); + const eet = destTime.substring(destTime.length - 4); + const alternates = destParts.slice(1).filter((s) => s.length > 0); + + // Section 8+: other information (remaining parts joined, then parsed as key/value) + const otherRaw = parts.slice(7).join('-').trim(); + const otherInfo: Record = {}; + + if (otherRaw.length > 0) { + // Other info is a series of KEY/VALUE pairs separated by spaces + // e.g., "PBN/A1B1C1D1L1O2S2T1 NAV/ABAS RNP2 DAT/1FANSE SUR/RSP180 DOF/260310 REG/B2036" + // Parse by finding KEY/ patterns + const otherTokens = otherRaw.split(/\s+/); + let currentKey = ''; + let currentValue = ''; + + for (const token of otherTokens) { + const kvMatch = token.match(/^([A-Z]{2,})\/(.*)/); + if (kvMatch) { + // Save previous key/value if exists + if (currentKey) { + otherInfo[currentKey] = currentValue.trim(); + } + currentKey = kvMatch[1]; + currentValue = kvMatch[2]; + } else if (currentKey) { + // Continuation of previous value + currentValue += ' ' + token; + } + } + // Save last key/value + if (currentKey) { + otherInfo[currentKey] = currentValue.trim(); + } + } + + return { + callsign, + flightRules, + flightType, + aircraftType, + wakeTurbulence, + equipment, + surveillance, + departure, + departureTime, + cruiseSpeed, + cruiseLevel, + route, + destination, + eet, + alternates, + otherInfo, + }; +} diff --git a/runtimes/typescript/utils/miam.ts b/runtimes/typescript/utils/miam.ts new file mode 100644 index 0000000..e7c4264 --- /dev/null +++ b/runtimes/typescript/utils/miam.ts @@ -0,0 +1,608 @@ +import { ascii85Decode } from './ascii85'; +import { inflateData } from './compression'; + +const textDecoder = new TextDecoder(); +const textEncoder = new TextEncoder(); + +enum MIAMVersion { + V1 = 1, + V2 = 2, +} + +enum MIAMFid { + SingleTransfer = 'T', + FileTransferRequest = 'F', + FileTransferAccept = 'K', + FileSegment = 'S', + FileTransferAbort = 'A', + XOFFIndication = 'Y', + XONIndication = 'X', +} + +enum MIAMCorePdu { + Data = 0, + Ack = 1, + Aloha = 2, + AlohaReply = 3, +} + +enum MIAMCoreApp { + ACARS2Char = 0x0, + ACARS4Char = 0x1, + ACARS6Char = 0x2, + NonACARS6Char = 0x3, +} + +export enum MIAMCoreV1Compression { + None = 0x0, + Deflate = 0x1, +} + +export enum MIAMCoreV2Compression { + None = 0x0, + Deflate = 0x1, +} + +const MIAMCoreV1CRCLength = 4; +const MIAMCoreV2CRCLength = 2; + +interface PduACARSData { + tail?: string; + label: string; + sublabel?: string; + mfi?: string; + text?: string; +} + +interface PduNonACARSData { + appId?: string; + text?: string; +} + +interface Pdu { + version: MIAMVersion; + crc: number; + crcOk: boolean; + complete: boolean; + compression: number; + encoding: number; + msgNum: number; + ackOptions: number; + acars?: PduACARSData; + non_acars?: PduNonACARSData; +} +const isMIAMVersion = (x: number): x is MIAMVersion => + Object.values(MIAMVersion).includes(x as MIAMVersion); +const isMIAMFid = (x: string): x is MIAMFid => + Object.values(MIAMFid).includes(x as MIAMFid); +const isMIAMCoreApp = (x: number): x is MIAMCoreApp => + Object.values(MIAMCoreApp).includes(x as MIAMCoreApp); +const isMIAMCorePdu = (x: number): x is MIAMCorePdu => + Object.values(MIAMCorePdu).includes(x as MIAMCorePdu); + +interface PduDecodingSuccess { + decoded: true; + message: { + data: Pdu; + }; +} + +interface PduDecodingFailure { + decoded: false; + error: string; +} + +type PduDecodingResult = PduDecodingSuccess | PduDecodingFailure; + +export class MIAMCoreUtils { + static AppTypeToAppIdLenTable: Record< + MIAMVersion, + Record + > = { + [MIAMVersion.V1]: { + [MIAMCoreApp.ACARS2Char]: 2, + [MIAMCoreApp.ACARS4Char]: 4, + [MIAMCoreApp.ACARS6Char]: 6, + [MIAMCoreApp.NonACARS6Char]: 6, + }, + [MIAMVersion.V2]: { + [MIAMCoreApp.ACARS2Char]: 2, + [MIAMCoreApp.ACARS4Char]: 4, + [MIAMCoreApp.ACARS6Char]: 6, + [MIAMCoreApp.NonACARS6Char]: 6, + }, + }; + + static FidHandlerTable: Record PduDecodingResult> = + { + [MIAMFid.SingleTransfer]: (txt: string) => { + if (txt.length < 3) { + return { + decoded: false, + error: 'Raw MIAM message too short (' + txt.length + ' < 3) ', + }; + } + + let bpad = txt[0]; + + if ('0123-.'.indexOf(bpad) === -1) { + return { + decoded: false, + error: "Invalid body padding value: '" + bpad + "'", + }; + } + + if ('0123'.indexOf(txt[1]) === -1) { + return { + decoded: false, + error: "Invalid header padding value: '" + txt[1] + "'", + }; + } + + const hpad = parseInt(txt[1]); + + const delimIdx = txt.indexOf('|'); + if (delimIdx === -1) { + return { + decoded: false, + error: 'Raw MIAM message missing header-body delimiter', + }; + } + + const rawHdr = txt.substring(2, delimIdx); + if (rawHdr.length === 0) { + return { + decoded: false, + error: 'Empty MIAM message header', + }; + } + + let hdr = ascii85Decode('<~' + rawHdr + '~>'); + if (!hdr || hdr.length < hpad) { + return { + decoded: false, + error: 'Ascii85 decode failed for MIAM message header', + }; + } + + let body: Uint8Array | undefined = undefined; + + const rawBody = txt.substring(delimIdx + 1); + if (rawBody.length > 0) { + if ('0123'.indexOf(bpad) >= 0) { + const bpadValue = parseInt(bpad); + + const decoded = ascii85Decode('<~' + rawBody + '~>'); + if (decoded === null) { + return { + decoded: false, + error: 'Ascii85 decode failed for MIAM message body', + }; + } + body = + decoded.length >= bpadValue + ? decoded.subarray(0, decoded.length - bpadValue) + : decoded; + } else if (bpad === '-') { + body = textEncoder.encode(rawBody); + } + } + + hdr = hdr.subarray(0, hdr.length - hpad); + + const version = hdr[0] & 0xf; + const pduType = (hdr[0] >> 4) & 0xf; + + if (isMIAMVersion(version) && isMIAMCorePdu(pduType)) { + const versionPduHandler = + this.VersionPduHandlerTable[version][pduType]; + return versionPduHandler(hdr, body); + } else { + return { + decoded: false, + error: + 'Invalid version and PDU type combination: v=' + + version + + ', pdu=' + + pduType, + }; + } + }, + [MIAMFid.FileTransferRequest]: () => { + return { + decoded: false, + error: 'File Transfer Request not implemented', + }; + }, + [MIAMFid.FileTransferAccept]: () => { + return { + decoded: false, + error: 'File Transfer Accept not implemented', + }; + }, + [MIAMFid.FileSegment]: () => { + return { decoded: false, error: 'File Segment not implemented' }; + }, + [MIAMFid.FileTransferAbort]: () => { + return { decoded: false, error: 'File Transfer Abort not implemented' }; + }, + [MIAMFid.XOFFIndication]: () => { + return { decoded: false, error: 'XOFF Indication not implemented' }; + }, + [MIAMFid.XONIndication]: () => { + return { decoded: false, error: 'XON Indication not implemented' }; + }, + }; + + private static arincCrc16(buf: Uint8Array, seed?: number) { + const crctable = [ + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, + 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, + 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, + 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, + 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, + 0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, + 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, + 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, + 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, + 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4, + 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, + 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, + 0x2e32, 0x1e51, 0x0e70, 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, + 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, + 0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1, + 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, + 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3, 0x14a0, + 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, + 0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, + 0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, + 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, + 0x28a3, 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e, + 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, + 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 0xef1f, 0xff3e, 0xcf5d, + 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, + 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0, + ]; + + let crc = (seed || 0) & 0xffff; + + for (let i = 0; i < buf.length; i++) { + crc = + (((crc << 8) >>> 0) ^ + crctable[(((crc >>> 8) ^ buf[i]) >>> 0) & 0xff]) >>> + 0; + } + + return crc & 0xffff; + } + + private static arinc665Crc32(buf: Uint8Array, seed?: number) { + const crctable = [ + 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, + 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, + 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 0x48d0c6c7, + 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, + 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, + 0x709f7b7a, 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, 0xbaea46ef, + 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, + 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, + 0xceb42022, 0xca753d95, 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, + 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0, + 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, + 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, + 0x0808d07d, 0x0cc9cdca, 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, + 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, + 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, + 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, + 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 0xe0b41de7, 0xe4750050, + 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, + 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, + 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, + 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 0x4f040d56, 0x4bc510e1, + 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, + 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, + 0x3f9b762c, 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, 0xf5ee4bb9, + 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, + 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, + 0xcda1f604, 0xc960ebb3, 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, + 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71, + 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, + 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, + 0x470cdd2b, 0x43cdc09c, 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, + 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, + 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, + 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, + 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 0xe3a1cbc1, 0xe760d676, + 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, + 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, + 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, + 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4, + ]; + + let crc = seed || 0; + + for (let i = 0; i < buf.length; i++) { + crc = + (((crc << 8) >>> 0) ^ crctable[((crc >>> 24) ^ buf[i]) >>> 0]) >>> 0; + } + + return crc; + } + + public static parse(txt: string): PduDecodingResult { + const fidType = txt[0]; + + if (isMIAMFid(fidType)) { + const handler = this.FidHandlerTable[fidType]; + return handler(txt.substring(1)); + } else { + return { + decoded: false, + error: 'Unsupported FID type: ' + fidType, + }; + } + } + + private static corePduDataHandler( + version: MIAMVersion, + minHdrSize: number, + crcLen: number, + hdr: Uint8Array, + body?: Uint8Array, + ): PduDecodingResult { + if (hdr.length < minHdrSize) { + return { + decoded: false, + error: + 'v' + + version + + ' header size too short; expected >= ' + + minHdrSize + + ', got ' + + hdr.length, + }; + } + + let pduSize: number | undefined = undefined; + let pduCompression: number = 0; + let pduEncoding: number = 0; + let pduAppType: number = 0; + let pduAppId: string = ''; + let pduCrc: number = 0; + let pduData: Uint8Array | null = null; + let pduCrcIsOk: boolean = false; + let pduIsComplete: boolean = true; + + let pduErrors: string[] = []; + + let tail: string | undefined = undefined; + let msgNum: number = 0; + let ackOptions: number = 0; + + if (version === MIAMVersion.V1) { + pduSize = (hdr[1] << 16) | (hdr[2] << 8) | hdr[3]; + + const msgSize = hdr.length + (body === undefined ? 0 : body.length); + if (pduSize > msgSize) { + pduIsComplete = false; + pduErrors.push( + 'v1 PDU truncated: expecting ' + pduSize + ', got ' + msgSize, + ); + } + hdr = hdr.subarray(4); + + tail = textDecoder.decode(hdr.subarray(0, 7)); + hdr = hdr.subarray(7); + } else if (version === MIAMVersion.V2) { + hdr = hdr.subarray(1); + } + + msgNum = (hdr[0] >> 1) & 0x7f; + ackOptions = hdr[0] & 1; + hdr = hdr.subarray(1); + + pduCompression = ((hdr[0] << 2) | ((hdr[1] >> 6) & 0x3)) & 0x7; + pduEncoding = (hdr[1] >> 4) & 0x3; + pduAppType = hdr[1] & 0xf; + hdr = hdr.subarray(2); + + let appIdLen; + if (isMIAMCoreApp(pduAppType)) { + appIdLen = this.AppTypeToAppIdLenTable[version][pduAppType]; + } else { + if ( + version === MIAMVersion.V2 && + (pduAppType & 0x8) !== 0 && + pduAppType !== 0xd + ) { + appIdLen = (pduAppType & 0x7) + 1; + } else { + return { + decoded: false, + error: 'Invalid v' + version + ' appType: ' + pduAppType, + }; + } + } + + const pduIsACARS = + [ + MIAMCoreApp.ACARS2Char, + MIAMCoreApp.ACARS4Char, + MIAMCoreApp.ACARS6Char, + ].indexOf(pduAppType) >= 0; + + if (hdr.length < appIdLen + crcLen) { + return { + decoded: false, + error: 'Header too short for v' + version + ' appType: ' + pduAppType, + }; + } + + pduAppId = textDecoder.decode(hdr.subarray(0, appIdLen)); + hdr = hdr.subarray(appIdLen); + + if (crcLen === 4) { + pduCrc = (hdr[0] << 24) | (hdr[1] << 16) | (hdr[2] << 8) | hdr[3]; // crc + } else if (crcLen === 2) { + pduCrc = (hdr[0] << 8) | hdr[1]; // crc + } + hdr = hdr.subarray(crcLen); + + if (body !== undefined && body.length > 0) { + if ( + [MIAMCoreV1Compression.Deflate, MIAMCoreV2Compression.Deflate].indexOf( + pduCompression, + ) >= 0 + ) { + try { + const inflated = inflateData(body, true); + if (inflated === undefined) { + pduErrors.push('Inflation produced no output for body'); + } else { + pduData = inflated; + } + } catch (e) { + pduErrors.push('Inflation failed for body: ' + e); + } + } else if ( + [MIAMCoreV1Compression.None, MIAMCoreV2Compression.None].indexOf( + pduCompression, + ) >= 0 + ) { + pduData = body; + } else { + pduErrors.push( + 'Unsupported v' + version + ' compression type: ' + pduCompression, + ); + } + + if (pduData !== null) { + const crcAlgoHandlerByVersion: Record< + MIAMVersion, + (buf: Uint8Array, seed?: number) => number + > = { + [MIAMVersion.V1]: (buf: Uint8Array, seed?: number) => { + return ~this.arinc665Crc32(buf, seed); + }, + [MIAMVersion.V2]: this.arincCrc16, + }; + + const crcAlgoHandler = crcAlgoHandlerByVersion[version]; + if (crcAlgoHandler === undefined) { + return { + decoded: false, + error: 'No CRC handler for v' + version, + }; + } + + const crcCheck = crcAlgoHandler(pduData, 0xffffffff); + if (crcCheck === pduCrc) { + pduCrcIsOk = true; + } else { + pduErrors.push( + 'Body failed CRC check: provided=' + + pduCrc + + ', generated=' + + crcCheck, + ); + } + } + } else { + // No PDU body means we can't verify CRC checksum; however, the message should still be valid... + pduCrcIsOk = true; + } + + let pdu: Pdu = { + version, + crc: pduCrc, + crcOk: pduCrcIsOk, + complete: pduIsComplete, + compression: pduCompression, + encoding: pduEncoding, + msgNum, + ackOptions, + }; + + if (pduIsACARS) { + const label = pduAppId.substring(0, 2); + const sublabel = appIdLen >= 4 ? pduAppId.substring(2, 4) : undefined; + const mfi = appIdLen >= 6 ? pduAppId.substring(4, 6) : undefined; + + pdu.acars = { + ...(tail ? { tail } : {}), + label, + ...(sublabel ? { sublabel } : {}), + ...(mfi ? { mfi } : {}), + ...(pduData ? { text: textDecoder.decode(pduData) } : {}), + }; + } else { + pdu.non_acars = { + appId: pduAppId, + ...(pduData ? { text: textDecoder.decode(pduData) } : {}), + }; + } + + return { + decoded: true, + message: { + data: pdu, + }, + }; + } + + static VersionPduHandlerTable: Record< + MIAMVersion, + Record< + MIAMCorePdu, + (hdr: Uint8Array, body?: Uint8Array) => PduDecodingResult + > + > = { + [MIAMVersion.V1]: { + [MIAMCorePdu.Data]: (hdr: Uint8Array, body?: Uint8Array) => { + return this.corePduDataHandler( + MIAMVersion.V1, + 20, + MIAMCoreV1CRCLength, + hdr, + body, + ); + }, + [MIAMCorePdu.Ack]: () => { + return { decoded: false, error: 'v1 Ack PDU not implemented' }; + }, + [MIAMCorePdu.Aloha]: () => { + return { decoded: false, error: 'v1 Aloha PDU not implemented' }; + }, + [MIAMCorePdu.AlohaReply]: () => { + return { decoded: false, error: 'v1 AlohaReply PDU not implemented' }; + }, + }, + [MIAMVersion.V2]: { + [MIAMCorePdu.Data]: (hdr: Uint8Array, body?: Uint8Array) => { + return this.corePduDataHandler( + MIAMVersion.V2, + 7, + MIAMCoreV2CRCLength, + hdr, + body, + ); + }, + [MIAMCorePdu.Ack]: () => { + return { decoded: false, error: 'v2 Ack PDU not implemented' }; + }, + [MIAMCorePdu.Aloha]: () => { + return { decoded: false, error: 'v2 Aloha PDU not implemented' }; + }, + [MIAMCorePdu.AlohaReply]: () => { + return { decoded: false, error: 'v2 Aloha reply PDU not implemented' }; + }, + }, + }; +} diff --git a/runtimes/typescript/utils/result_formatter.ts b/runtimes/typescript/utils/result_formatter.ts new file mode 100644 index 0000000..341229c --- /dev/null +++ b/runtimes/typescript/utils/result_formatter.ts @@ -0,0 +1,699 @@ +import { DecodeResult } from '../DecoderPluginInterface'; +import { CoordinateUtils } from './coordinate_utils'; +import { DateTimeUtils } from '../DateTimeUtils'; +import { RouteUtils } from './route_utils'; +import { Route } from '../types/route'; +import { Wind } from '../types/wind'; + +/** + * Class to format the results of common fields + */ +export class ResultFormatter { + static route(decodeResult: DecodeResult, route: Route) { + decodeResult.raw.route = route; + decodeResult.formatted.items.push({ + type: 'aircraft_route', + code: 'ROUTE', + label: 'Aircraft Route', + value: RouteUtils.routeToString(route), + }); + } + + static state_change(decodeResult: DecodeResult, from: string, to: string) { + decodeResult.raw.state_change = { + from: from, + to: to, + }; + from = RouteUtils.formatFlightState(from); + to = RouteUtils.formatFlightState(to); + decodeResult.formatted.items.push({ + type: 'state_change', + code: 'STATE_CHANGE', + label: 'State Change', + value: `${from} -> ${to}`, + }); + } + + static door_event(decodeResult: DecodeResult, name: string, state: string) { + decodeResult.raw.door_event = { + door: name, + state: state, + }; + + decodeResult.formatted.items.push({ + type: 'door_event', + code: 'DOOR', + label: 'Door Event', + value: `${name} ${state}`, + }); + } + + static position( + decodeResult: DecodeResult, + value: { latitude: number; longitude: number } | undefined, + ) { + if (!value || isNaN(value.latitude) || isNaN(value.longitude)) { + return; + } + decodeResult.raw.position = value; + decodeResult.formatted.items.push({ + type: 'aircraft_position', + code: 'POS', + label: 'Aircraft Position', + value: CoordinateUtils.coordinateString(value), + }); + } + + static altitude(decodeResult: DecodeResult, value: number) { + if (isNaN(value)) { + return; + } + decodeResult.raw.altitude = value; + decodeResult.formatted.items.push({ + type: 'altitude', + code: 'ALT', + label: 'Altitude', + value: `${decodeResult.raw.altitude} feet`, + }); + } + + static flightNumber(decodeResult: DecodeResult, value: string) { + if (value.length === 0) { + return; + } + decodeResult.raw.flight_number = value; + decodeResult.formatted.items.push({ + type: 'flight_number', + code: 'FLIGHT', + label: 'Flight Number', + value: decodeResult.raw.flight_number, + }); + } + + static callsign(decodeResult: DecodeResult, value: string) { + decodeResult.raw.callsign = value; + decodeResult.formatted.items.push({ + type: 'callsign', + code: 'CALLSIGN', + label: 'Callsign', + value: decodeResult.raw.callsign, + }); + } + + static departureAirport( + decodeResult: DecodeResult, + value: string, + type: 'IATA' | 'ICAO' = 'ICAO', + ) { + if (type === 'ICAO') { + decodeResult.raw.departure_icao = value; + decodeResult.formatted.items.push({ + type: 'icao', + code: 'ORG', + label: 'Origin', + value: value, + }); + } else { + decodeResult.raw.departure_iata = value; + decodeResult.formatted.items.push({ + type: 'iata', + code: 'ORG', + label: 'Origin', + value: value, + }); + } + } + + static departureRunway(decodeResult: DecodeResult, value: string) { + decodeResult.raw.departure_runway = value; + decodeResult.formatted.items.push({ + type: 'runway', + code: 'DEPRWY', + label: 'Departure Runway', + value: decodeResult.raw.departure_runway, + }); + } + + static arrivalAirport( + decodeResult: DecodeResult, + value: string, + type: 'IATA' | 'ICAO' = 'ICAO', + ) { + if (type === 'ICAO') { + decodeResult.raw.arrival_icao = value; + decodeResult.formatted.items.push({ + type: 'icao', + code: 'DST', + label: 'Destination', + value: value, + }); + } else { + decodeResult.raw.arrival_iata = value; + decodeResult.formatted.items.push({ + type: 'iata', + code: 'DST', + label: 'Destination', + value: value, + }); + } + } + + static alternateAirport(decodeResult: DecodeResult, value: string) { + decodeResult.raw.alternate_icao = value; + decodeResult.formatted.items.push({ + type: 'icao', + code: 'ALT_DST', + label: 'Alternate Destination', + value: decodeResult.raw.alternate_icao, + }); + } + + static eta(decodeResult: DecodeResult, time: number) { + decodeResult.raw.eta_time = time; + decodeResult.formatted.items.push({ + type: 'time', + code: 'ETA', + label: 'Estimated Time of Arrival', + value: DateTimeUtils.timestampToString(time), + }); + } + + static arrivalRunway(decodeResult: DecodeResult, value: string) { + decodeResult.raw.arrival_runway = value; + decodeResult.formatted.items.push({ + type: 'runway', + code: 'ARWY', + label: 'Arrival Runway', + value: decodeResult.raw.arrival_runway, + }); + } + + static alternateRunway(decodeResult: DecodeResult, value: string) { + decodeResult.raw.alternate_runway = value; + decodeResult.formatted.items.push({ + type: 'runway', + code: 'ALT_ARWY', + label: 'Alternate Runway', + value: decodeResult.raw.alternate_runway, + }); + } + + static currentFuel(decodeResult: DecodeResult, value: number | undefined) { + // Tolerate undefined/NaN (e.g. when a spec's when-gated fuel field's + // guard failed and the value never got assigned). Matches the + // original hand-written plugins' pattern of only calling the + // formatter when they had a real value. + if (value === undefined || value === null || Number.isNaN(value)) return; + decodeResult.raw.fuel_on_board = value; + decodeResult.formatted.items.push({ + type: 'fuel_on_board', + code: 'FOB', + label: 'Fuel On Board', + value: decodeResult.raw.fuel_on_board.toString(), + }); + } + + static burnedFuel(decodeResult: DecodeResult, value: number) { + decodeResult.raw.fuel_burned = value; + decodeResult.formatted.items.push({ + type: 'fuel_burned', + code: 'FB', + label: 'Fuel Burned', + value: decodeResult.raw.fuel_burned.toString(), + }); + } + + static remainingFuel(decodeResult: DecodeResult, value: number) { + decodeResult.raw.fuel_remaining = value; + decodeResult.formatted.items.push({ + type: 'fuel_remaining', + code: ' FUEL_REM', + label: 'Fuel Remaining', + value: decodeResult.raw.fuel_remaining.toString(), + }); + } + + static outFuel(decodeResult: DecodeResult, value: number) { + decodeResult.raw.out_fuel = value; + decodeResult.formatted.items.push({ + type: 'lbs', + code: 'FUEL_OUT', + label: 'Out of Gate Fuel', + value: decodeResult.raw.out_fuel.toString() + ' lbs', + }); + } + + static offFuel(decodeResult: DecodeResult, value: number) { + decodeResult.raw.off_fuel = value; + decodeResult.formatted.items.push({ + type: 'lbs', + code: 'FUEL_OFF', + label: 'Takeoff Fuel', + value: decodeResult.raw.off_fuel.toString() + ' lbs', + }); + } + + static onFuel(decodeResult: DecodeResult, value: number) { + decodeResult.raw.on_fuel = value; + decodeResult.formatted.items.push({ + type: 'lbs', + code: 'FUEL_ON', + label: 'Landing Fuel', + value: decodeResult.raw.on_fuel.toString() + ' lbs', + }); + } + + static inFuel(decodeResult: DecodeResult, value: number) { + decodeResult.raw.in_fuel = value; + decodeResult.formatted.items.push({ + type: 'lbs', + code: 'FUEL_IN', + label: 'In Gate Fuel', + value: decodeResult.raw.in_fuel.toString() + ' lbs', + }); + } + + static startFuel(decodeResult: DecodeResult, value: number) { + decodeResult.raw.start_fuel = value; + decodeResult.formatted.items.push({ + type: 'lbs', + code: 'FUEL_START', + label: 'Start Fuel', + value: decodeResult.raw.start_fuel.toString() + ' lbs', + }); + } + + static checksumAlgorithm(decodeResult: DecodeResult, value: string) { + decodeResult.raw.checksum_algorithm = value; + // decodeResult.formatted.items.push({ + // type: 'message_checksum_algorithm', + // code: 'CHECKSUM_ALGO', + // label: 'Checksum Algorithm', + // value: decodeResult.raw.checksum_algorithm, + // }); + } + + static checksum(decodeResult: DecodeResult, value: number) { + decodeResult.raw.checksum = value; + decodeResult.formatted.items.push({ + type: 'message_checksum', + code: 'CHECKSUM', + label: 'Message Checksum', + value: '0x' + ('0000' + decodeResult.raw.checksum.toString(16)).slice(-4), + }); + } + + static groundspeed(decodeResult: DecodeResult, value: number) { + decodeResult.raw.groundspeed = value; + decodeResult.formatted.items.push({ + type: 'aircraft_groundspeed', + code: 'GSPD', + label: 'Aircraft Groundspeed', + value: `${decodeResult.raw.groundspeed} knots`, + }); + } + + static airspeed(decodeResult: DecodeResult, value: number) { + decodeResult.raw.airspeed = value; + decodeResult.formatted.items.push({ + type: 'airspeed', + code: 'ASPD', + label: 'True Airspeed', + value: `${decodeResult.raw.airspeed} knots`, + }); + } + + static mach(decodeResult: DecodeResult, value: number) { + decodeResult.raw.mach = value; + decodeResult.formatted.items.push({ + type: 'mach', + code: 'MACH', + label: 'Mach Number', + value: `${decodeResult.raw.mach} mach`, + }); + } + + static temperature(decodeResult: DecodeResult, value: string) { + if (value.length === 0) { + return; + } + decodeResult.raw.outside_air_temperature = Number( + value.replace('M', '-').replace('P', '+'), + ); + decodeResult.formatted.items.push({ + type: 'outside_air_temperature', + code: 'OATEMP', + label: 'Outside Air Temperature (C)', + value: `${decodeResult.raw.outside_air_temperature} degrees`, + }); + } + + static totalAirTemp(decodeResult: DecodeResult, value: string) { + if (value.length === 0) { + return; + } + decodeResult.raw.total_air_temperature = Number( + value.replace('M', '-').replace('P', '+'), + ); + decodeResult.formatted.items.push({ + type: 'temperature', + code: 'TATEMP', + label: 'Total Air Temperature (C)', + value: `${decodeResult.raw.total_air_temperature} degrees`, + }); + } + + static heading(decodeResult: DecodeResult, value: number) { + decodeResult.raw.heading = value; + decodeResult.formatted.items.push({ + type: 'heading', + code: 'HDG', + label: 'Heading', + value: `${decodeResult.raw.heading}`, + }); + } + + static tail(decodeResult: DecodeResult, value: string) { + decodeResult.raw.tail = value; + decodeResult.formatted.items.push({ + type: 'tail', + code: 'TAIL', + label: 'Tail', + value: decodeResult.raw.tail, + }); + } + + static out(decodeResult: DecodeResult, time: number) { + decodeResult.raw.out_time = time; + decodeResult.formatted.items.push({ + type: 'time', + code: 'OUT', + label: 'Out of Gate Time', + value: DateTimeUtils.timestampToString(time), + }); + } + + static off(decodeResult: DecodeResult, time: number) { + decodeResult.raw.off_time = time; + decodeResult.formatted.items.push({ + type: 'time', + code: 'OFF', + label: 'Takeoff Time', + value: DateTimeUtils.timestampToString(time), + }); + } + + static on(decodeResult: DecodeResult, time: number) { + decodeResult.raw.on_time = time; + decodeResult.formatted.items.push({ + type: 'time', + code: 'ON', + label: 'Landing Time', + value: DateTimeUtils.timestampToString(time), + }); + } + + static in(decodeResult: DecodeResult, time: number) { + decodeResult.raw.in_time = time; + decodeResult.formatted.items.push({ + type: 'time', + code: 'IN', + label: 'In Gate Time', + value: DateTimeUtils.timestampToString(time), + }); + } + + static engineStart(decodeResult: DecodeResult, time: number) { + decodeResult.raw.engine_start_time = time; + decodeResult.formatted.items.push({ + type: 'time', + code: 'ENG_START', + label: 'Engine Start Time', + value: DateTimeUtils.timestampToString(time), + }); + } + static engineStop(decodeResult: DecodeResult, time: number) { + decodeResult.raw.engine_stop_time = time; + decodeResult.formatted.items.push({ + type: 'time', + code: 'ENG_STOP', + label: 'Engine Stop Time', + value: DateTimeUtils.timestampToString(time), + }); + } + + static day(decodeResult: DecodeResult, day: number) { + decodeResult.raw.day = day; + decodeResult.formatted.items.push({ + type: 'day', + code: 'MSG_DAY', + label: 'Day of Month', + value: `${day}`, + }); + } + + static month(decodeResult: DecodeResult, month: number) { + decodeResult.raw.month = month; + decodeResult.formatted.items.push({ + type: 'month', + code: 'MSG_MON', + label: 'Month of Year', + value: `${month}`, + }); + } + + static departureDay(decodeResult: DecodeResult, day: number) { + decodeResult.raw.departure_day = day; + decodeResult.formatted.items.push({ + type: 'day', + code: 'DEP_DAY', + label: 'Departure Day', + value: `${day}`, + }); + } + + static arrivalDay(decodeResult: DecodeResult, day: number) { + decodeResult.raw.arrival_day = day; + decodeResult.formatted.items.push({ + type: 'day', + code: 'ARR_DAY', + label: 'Arrival Day', + value: `${day}`, + }); + } + + static text(decodeResult: DecodeResult, text: string) { + decodeResult.raw.text = text; + decodeResult.formatted.items.push({ + type: 'text', + code: 'TEXT', + label: 'Text Message', + value: text, + }); + } + + static mac(decodeResult: DecodeResult, mac: number) { + decodeResult.raw.mac = mac; + decodeResult.formatted.items.push({ + type: 'mac', + code: 'MAC', + label: 'Mean Aerodynamic Chord', + value: `${mac} %`, + }); + } + + static trim(decodeResult: DecodeResult, trim: number) { + decodeResult.raw.trim = trim; + decodeResult.formatted.items.push({ + type: 'trim', + code: 'TRIM', + label: 'Trim', + value: `${trim} units`, + }); + } + + static windData(decodeResult: DecodeResult, windData: Wind[]) { + decodeResult.raw.wind_data = windData; + for (const wind of windData) { + let text = `${RouteUtils.waypointToString(wind.waypoint)} at FL${wind.flightLevel}: ${wind.windDirection}° at ${wind.windSpeed}kt`; + if (wind.temperature) { + text += `, ${wind.temperature.degreesC}°C at FL${wind.temperature.flightLevel}`; + } + decodeResult.formatted.items.push({ + type: 'wind_data', + code: 'WIND', + label: 'Wind Data', + value: text, + }); + } + } + + static cg( + decodeResult: DecodeResult, + value: number, + type: 'center' | 'lower' | 'upper' = 'center', + ) { + switch (type) { + case 'center': + decodeResult.raw.center_of_gravity = value; + decodeResult.formatted.items.push({ + type: 'center_of_gravity', + code: 'CG', + label: 'Center of Gravity', + value: `${decodeResult.raw.center_of_gravity} %`, + }); + break; + case 'lower': + decodeResult.raw.cg_lower_limit = value; + decodeResult.formatted.items.push({ + type: 'cg_lower_limit', + code: 'CG_LOWER', + label: 'Center of Gravity Lower Limit', + value: `${decodeResult.raw.cg_lower_limit} %`, + }); + break; + case 'upper': + decodeResult.raw.cg_upper_limit = value; + decodeResult.formatted.items.push({ + type: 'cg_upper_limit', + code: 'CG_UPPER', + label: 'Center of Gravity Upper Limit', + value: `${decodeResult.raw.cg_upper_limit} %`, + }); + break; + } + } + + static version(decodeResult: DecodeResult, value: number) { + decodeResult.raw.version = value; + decodeResult.formatted.items.push({ + type: 'version', + code: 'VERSION', + label: 'Message Version', + value: `v${decodeResult.raw.version.toFixed(1)}`, + }); + } + + static label(decodeResult: DecodeResult, value: string) { + decodeResult.raw.label = value; + decodeResult.formatted.items.push({ + type: 'label', + code: 'LABEL', + label: 'Message Label', + value: `${decodeResult.raw.label}`, + }); + } + + static sublabel(decodeResult: DecodeResult, value: string) { + decodeResult.raw.sublabel = value; + decodeResult.formatted.items.push({ + type: 'sublabel', + code: 'SUBLABEL', + label: 'Message Sublabel', + value: `${decodeResult.raw.sublabel}`, + }); + } + + static requestedAltitudes(decodeResult: DecodeResult, values: number[]) { + decodeResult.raw.requested_alts = values; + decodeResult.formatted.items.push({ + type: 'requested_altitudes', + code: 'REQ_ALTS', + label: 'Requested Altitudes', + value: `${decodeResult.raw.requested_alts.join(', ')}`, + }); + } + + static desiredAltitude(decodeResult: DecodeResult, value: number) { + decodeResult.raw.desired_alt = value; + decodeResult.formatted.items.push({ + type: 'desired_altitude', + code: 'DES_ALT', + label: 'Desired Altitude', + value: `${decodeResult.raw.desired_alt}`, + }); + } + + static startPoint(decodeResult: DecodeResult, value: string) { + decodeResult.raw.start_point = value; + decodeResult.formatted.items.push({ + type: 'start_point', + code: 'START', + label: 'Start Point', + value: `${decodeResult.raw.start_point}`, + }); + } + + static routeNumber(decodeResult: DecodeResult, value: string) { + decodeResult.raw.route_number = value; + decodeResult.formatted.items.push({ + type: 'route_number', + code: 'RTE_NUM', + label: 'Route Number', + value: `${decodeResult.raw.route_number}`, + }); + } + + static flightPlan(decodeResult: DecodeResult, value: string) { + decodeResult.raw.flight_plan = value; + decodeResult.formatted.items.push({ + type: 'flight_plan', + code: 'FPN', + label: 'Flight Plan', + value: `${decodeResult.raw.flight_plan}`, + }); + } + + static groundAddress(decodeResult: DecodeResult, value: string) { + decodeResult.raw.ground_address = value; + decodeResult.formatted.items.push({ + type: 'ground_address', + code: 'GND_ADDR', + label: 'Ground Address', + value: `${decodeResult.raw.ground_address}`, + }); + } + + static timestamp(decodeResult: DecodeResult, value: number) { + decodeResult.raw.message_timestamp = value; + decodeResult.formatted.items.push({ + type: 'time', + code: 'TIMESTAMP', + label: 'Message Timestamp', + value: DateTimeUtils.timestampToString(value), + }); + } + + static sequenceNumber(decodeResult: DecodeResult, value: number) { + decodeResult.raw.sequence_number = value; + decodeResult.formatted.items.push({ + type: 'sequence', + code: 'SEQ', + label: 'Sequence Number', + value: String(decodeResult.raw.sequence_number), + }); + } + + static sequenceResponse(decodeResult: DecodeResult, value: number) { + decodeResult.raw.sequence_response = value; + decodeResult.formatted.items.push({ + type: 'sequence', + code: 'SEQ_RESP', + label: 'Sequence Response', + value: String(decodeResult.raw.sequence_response), + }); + } + + static unknown(decodeResult: DecodeResult, value: string, sep: string = ',') { + if (!decodeResult.remaining.text) decodeResult.remaining.text = value; + else decodeResult.remaining.text += sep + value; + } + + static unknownArr( + decodeResult: DecodeResult, + value: string[], + sep: string = ',', + ) { + this.unknown(decodeResult, value.join(sep), sep); + } +} diff --git a/runtimes/typescript/utils/route_utils.ts b/runtimes/typescript/utils/route_utils.ts new file mode 100644 index 0000000..766c5b6 --- /dev/null +++ b/runtimes/typescript/utils/route_utils.ts @@ -0,0 +1,112 @@ +import { DateTimeUtils } from '../DateTimeUtils'; +import { Route } from '../types/route'; +import { Waypoint } from '../types/waypoint'; +import { CoordinateUtils } from './coordinate_utils'; + +export class RouteUtils { + public static formatFlightState(state: string): string { + switch (state) { + case 'TO': + return 'Takeoff'; + case 'IC': + return 'Initial Climb'; + case 'CL': + return 'Climb'; + case 'ER': + return 'En Route'; + case 'DC': + return 'Descent'; + case 'AP': + return 'Approach'; + default: + return `Unknown ${state}`; + } + } + + public static routeToString(route: Route): string { + let str = ''; + if (route.name) { + str += route.name; + } + if (route.runway) { + str += `(${route.runway})`; + } + if (str.length !== 0 && route.waypoints && route.waypoints.length === 1) { + str += ' starting at '; + } else if (str.length !== 0 && route.waypoints) { + str += ': '; + } + + if (route.waypoints) { + str += RouteUtils.waypointsToString(route.waypoints); + } + + return str; + } + + public static waypointToString(waypoint: Waypoint): string { + let s = waypoint.name; + if (waypoint.latitude && waypoint.longitude) { + s += `(${CoordinateUtils.coordinateString({ latitude: waypoint.latitude, longitude: waypoint.longitude })})`; + } + if (waypoint.offset) { + s += `[${waypoint.offset.bearing}° ${waypoint.offset.distance}nm]`; + } + if (waypoint.time) { + s += `@${DateTimeUtils.timestampToString(waypoint.time)}`; + } + return s; + } + + public static getWaypoint(leg: string): Waypoint { + const regex = leg.match(/^([A-Z]+)(\d{3})-(\d{4})$/); // {name}{bearing}-{distance} + if (regex?.length == 4) { + return { + name: regex[1], + offset: { + bearing: parseInt(regex[2]), + distance: parseInt(regex[3]) / 10, + }, + }; + } + + const waypoint = leg.split(','); + if (waypoint.length == 2) { + const position = CoordinateUtils.decodeStringCoordinatesDecimalMinutes( + waypoint[1], + ); + if (position) { + return { + name: waypoint[0], + latitude: position.latitude, + longitude: position.longitude, + }; + } + } + if (leg.length == 13 || leg.length == 14) { + //looks like coordinates + const position = + CoordinateUtils.decodeStringCoordinatesDecimalMinutes(leg); + const name = waypoint.length == 2 ? waypoint[0] : ''; + if (position) { + return { + name: name, + latitude: position.latitude, + longitude: position.longitude, + }; + } + } + return { name: leg }; + } + + private static waypointsToString(waypoints: Waypoint[]): string { + let str = waypoints + .map((x) => RouteUtils.waypointToString(x)) + .join(' > ') + .replaceAll('> >', '>>'); + if (str.startsWith(' > ')) { + str = '>>' + str.slice(2); + } + return str; + } +} diff --git a/schema/ads-v1.schema.json b/schema/ads-v1.schema.json new file mode 100644 index 0000000..7211a3c --- /dev/null +++ b/schema/ads-v1.schema.json @@ -0,0 +1,479 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://airframes.io/spec/ads-v1.schema.json", + "title": "Airframes Decoder Spec (ADS) v1", + "description": "Declarative DSL for ACARS message decoders. Codegens to TypeScript, Rust, and C.", + "type": "object", + "required": ["spec_version", "plugin", "qualifiers", "parse", "formatted"], + "additionalProperties": false, + "properties": { + "spec_version": { + "const": "1", + "description": "ADS spec version. Must be \"1\"." + }, + "plugin": { "$ref": "#/$defs/plugin" }, + "qualifiers": { "$ref": "#/$defs/qualifiers" }, + "parse": { "$ref": "#/$defs/parse" }, + "fields": { + "type": "array", + "items": { "$ref": "#/$defs/field" }, + "description": "Field extractions. Mutually exclusive with top-level 'variants'." + }, + "variants": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/variant" }, + "description": "Variant dispatch. Each variant has a 'when' condition and its own field list." + }, + "checksum": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/checksum_algorithm" } + }, + "on_checksum_fail": { + "type": "object", + "additionalProperties": false, + "properties": { + "decoded": { "type": "boolean" } + } + }, + "formatted": { "$ref": "#/$defs/formatted" } + }, + "allOf": [ + { + "description": "Top-level fields and variants are mutually exclusive.", + "not": { "required": ["fields", "variants"] } + } + ], + + "$defs": { + "identifier": { + "type": "string", + "pattern": "^[A-Za-z_][A-Za-z0-9_]*$", + "description": "Plugin or variable identifier (snake_case or PascalCase)." + }, + "var_ref": { + "type": "string", + "pattern": "^\\$[A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z_][A-Za-z0-9_]*|\\[[0-9-]+(:[0-9-]+(\\.\\.[0-9-]+)?)?\\])*$", + "description": "A variable reference: $varname, $varname.field, $varname[N], $varname[N:M], $varname[N:M..K]. Bit-slice notation $byte[bitStart..bitEnd] permitted in bitfield contexts." + }, + "value_expr": { + "anyOf": [ + { "$ref": "#/$defs/var_ref" }, + { "type": "string" }, + { "type": "number" }, + { "type": "boolean" }, + { "type": "null" } + ], + "description": "A literal value or a variable reference. Strings starting with $ are interpreted as variable refs at codegen time." + }, + + "plugin": { + "type": "object", + "required": ["name", "type"], + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "pattern": "^[A-Z][A-Za-z0-9_]*$", + "description": "Plugin class name (PascalCase). Used verbatim in TS, snake_cased in Rust/C." + }, + "type": { + "enum": ["text", "binary"], + "description": "text = string-mode parsing (split/regex); binary = byte-mode parsing (bitfields, encodings)." + }, + "docs": { + "type": "string", + "format": "uri", + "description": "Optional URL to human-readable research docs (typically acars-message-documentation)." + }, + "decode_level": { + "enum": ["NONE", "PARTIAL", "MESSAGE", "FULL"], + "default": "MESSAGE", + "description": "Decode-level enum matching DecoderPluginInterface.ts." + } + } + }, + + "qualifiers": { + "type": "object", + "required": ["labels"], + "additionalProperties": false, + "properties": { + "labels": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1, + "description": "ACARS label string. Real labels include alphanumeric (10, 4A, H1) plus punctuation forms like ':;' (Label_ColonComma). '*' is the wildcard." + }, + "description": "ACARS labels to match. '*' is wildcard." + }, + "preambles": { + "type": "array", + "items": { "type": "string" }, + "description": "Optional preamble strings; first match wins." + } + } + }, + + "parse": { + "oneOf": [ + { "$ref": "#/$defs/parse_custom" }, + { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/parse_step" } + } + ] + }, + + "parse_custom": { + "type": "object", + "required": ["custom"], + "additionalProperties": false, + "properties": { + "custom": { + "$ref": "#/$defs/identifier", + "description": "Name of a per-language native function that performs the entire parse. Escape hatch." + } + } + }, + + "parse_step": { + "type": "object", + "minProperties": 1, + "maxProperties": 4, + "oneOf": [ + { "required": ["split"] }, + { "required": ["regex"] }, + { "required": ["substring"] }, + { "required": ["require_length"] }, + { "required": ["bitfield"] }, + { "required": ["decode_ascii85"] }, + { "required": ["deflate"] }, + { "required": ["base64"] }, + { "required": ["text_decode"] }, + { "required": ["hex_decode"] }, + { "required": ["concat_bits"] }, + { "required": ["custom"] } + ], + "properties": { + "split": { "type": "string", "minLength": 1 }, + "regex": { "type": "string", "minLength": 1 }, + "substring": { + "type": "object", + "required": ["from", "start"], + "additionalProperties": false, + "properties": { + "from": { "$ref": "#/$defs/var_ref" }, + "start": { "type": "integer" }, + "length": { "type": "integer", "minimum": 0 }, + "end": { "type": "integer" } + } + }, + "require_length": { + "type": "object", + "required": ["var"], + "additionalProperties": false, + "properties": { + "var": { "$ref": "#/$defs/identifier" }, + "equals": { "type": "integer", "minimum": 0 }, + "min": { "type": "integer", "minimum": 0 }, + "max": { "type": "integer", "minimum": 0 } + } + }, + "bitfield": { + "type": "object", + "required": ["source", "fields"], + "additionalProperties": false, + "properties": { + "source": { "$ref": "#/$defs/var_ref" }, + "fields": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["name", "bits"], + "additionalProperties": false, + "properties": { + "name": { "$ref": "#/$defs/identifier" }, + "bits": { + "type": "string", + "pattern": "^[0-9]+:[0-9]+$", + "description": "Bit range, inclusive on both ends (e.g. \"0:3\" = bits 0..3 = 4 bits)." + } + } + } + } + } + }, + "decode_ascii85": { "$ref": "#/$defs/var_ref" }, + "deflate": { + "type": "object", + "required": ["source"], + "additionalProperties": false, + "properties": { + "source": { "$ref": "#/$defs/var_ref" }, + "offset": { "type": "integer", "minimum": 0 }, + "format": { "enum": ["raw", "zlib", "gzip"], "default": "raw" } + } + }, + "base64": { "$ref": "#/$defs/var_ref" }, + "text_decode": { + "type": "object", + "required": ["source"], + "additionalProperties": false, + "properties": { + "source": { "$ref": "#/$defs/var_ref" }, + "encoding": { "enum": ["utf-8", "ascii", "latin1"], "default": "utf-8" } + } + }, + "hex_decode": { "$ref": "#/$defs/var_ref" }, + "concat_bits": { + "type": "array", + "minItems": 2, + "items": { "$ref": "#/$defs/var_ref" } + }, + "custom": { "$ref": "#/$defs/identifier" }, + "into": { "$ref": "#/$defs/identifier" }, + "else": { "enum": ["fail"] } + } + }, + + "field": { + "type": "object", + "required": ["name", "from"], + "additionalProperties": false, + "properties": { + "name": { "$ref": "#/$defs/identifier" }, + "from": { "$ref": "#/$defs/value_expr" }, + "decode": { "$ref": "#/$defs/decode_call" }, + "when": { "$ref": "#/$defs/condition" }, + "default": { "$ref": "#/$defs/value_expr" }, + "description": { "type": "string" } + } + }, + + "decode_call": { + "type": "object", + "required": ["fn"], + "additionalProperties": false, + "properties": { + "fn": { + "enum": [ + "coordinate", + "coordinate_decimal_minutes", + "integer", + "float", + "string", + "trim", + "uppercase", + "lowercase", + "timestamp_hhmmss", + "timestamp_ddhhmm", + "hex_to_bytes", + "callsign", + "airport", + "tail_number", + "flight_number", + "altitude_feet", + "speed_knots", + "heading_degrees", + "fuel_kg", + "fuel_lb", + "json_parse", + "custom" + ] + }, + "args": { "type": "object", "additionalProperties": true }, + "custom": { + "$ref": "#/$defs/identifier", + "description": "When fn=custom: name of a per-language helper function." + } + } + }, + + "variant": { + "type": "object", + "oneOf": [ + { + "required": ["when", "fields"], + "additionalProperties": false, + "properties": { + "when": { "$ref": "#/$defs/condition" }, + "fields": { "type": "array", "items": { "$ref": "#/$defs/field" } }, + "name": { "type": "string" }, + "checksum": { + "type": "array", + "items": { "$ref": "#/$defs/checksum_algorithm" } + } + } + }, + { + "required": ["default"], + "additionalProperties": false, + "properties": { + "default": { "enum": ["fail"] } + } + } + ] + }, + + "condition": { + "type": "object", + "minProperties": 1, + "maxProperties": 1, + "properties": { + "equals": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": { "$ref": "#/$defs/value_expr_or_call" } + }, + "not_equal": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": { "$ref": "#/$defs/value_expr_or_call" } + }, + "matches": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "prefixItems": [ + { "$ref": "#/$defs/var_ref" }, + { "type": "string" } + ], + "items": false + }, + "in": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "prefixItems": [ + { "$ref": "#/$defs/value_expr" }, + { "type": "array" } + ], + "items": false + }, + "all": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/condition" } + }, + "any": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/condition" } + }, + "not": { "$ref": "#/$defs/condition" } + } + }, + + "value_expr_or_call": { + "anyOf": [ + { "$ref": "#/$defs/value_expr" }, + { + "type": "object", + "description": "Computed expression — e.g. {length: $parts}", + "minProperties": 1, + "maxProperties": 1, + "properties": { + "length": { "$ref": "#/$defs/var_ref" } + }, + "additionalProperties": false + } + ] + }, + + "checksum_algorithm": { + "type": "object", + "required": ["algorithm", "on", "expect"], + "additionalProperties": false, + "properties": { + "algorithm": { + "enum": [ + "ARINC_665_CRC32", + "ARINC_6_CRC16", + "CRC16_IBM_SDLC_REV", + "CRC16_GENIBUS" + ] + }, + "when": { "$ref": "#/$defs/condition" }, + "on": { + "type": "string", + "pattern": "^-?[0-9]+\\.\\.-?[0-9]+$", + "description": "Byte range slice notation, e.g. \"0..-4\" = all bytes except last 4." + }, + "expect": { + "type": "string", + "pattern": "^(tail[0-9]+|head[0-9]+|[0-9]+\\.\\.-?[0-9]+)$", + "description": "Where the expected checksum value is stored." + } + } + }, + + "formatted": { + "oneOf": [ + { + "type": "object", + "required": ["description"], + "additionalProperties": false, + "properties": { + "description": { + "type": "string", + "description": "Human description, may interpolate $varname." + }, + "items": { + "type": "array", + "items": { "$ref": "#/$defs/formatter_call" } + } + } + }, + { + "type": "object", + "required": ["custom"], + "additionalProperties": false, + "properties": { + "description": { + "type": "string", + "description": "Human-readable description used in initResult() even when items are custom." + }, + "custom": { + "$ref": "#/$defs/identifier", + "description": "Escape hatch: per-language native formatter function." + } + } + } + ] + }, + + "formatter_call": { + "type": "object", + "required": ["type"], + "additionalProperties": true, + "properties": { + "type": { + "enum": [ + "position", + "altitude", + "speed", + "heading", + "timestamp", + "callsign", + "flight_number", + "tail_number", + "airport_origin", + "airport_destination", + "fuel", + "free_text", + "custom" + ] + }, + "custom": { "$ref": "#/$defs/identifier" } + } + } + } +} diff --git a/spec/.gitkeep b/spec/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/spec/labels/10/LDR.yaml b/spec/labels/10/LDR.yaml new file mode 100644 index 0000000..baa5e02 --- /dev/null +++ b/spec/labels/10/LDR.yaml @@ -0,0 +1,16 @@ +spec_version: "1" +plugin: + name: Label_10_LDR + type: text + decode_level: PARTIAL +qualifiers: + labels: ["10"] + preambles: ["LDR"] +parse: + # Pure escape hatch: comma-split with 17+ fields, runway substring slicing, + # alternate-runway concat filter, and non-DSL formatters (alternate airport, + # arrival runway, alternate runway) all live in hand-written per-language code. + custom: label_10_ldr_decode +formatted: + description: "Position Report" + custom: label_10_ldr_format diff --git a/spec/labels/10/POS.yaml b/spec/labels/10/POS.yaml new file mode 100644 index 0000000..a9111a5 --- /dev/null +++ b/spec/labels/10/POS.yaml @@ -0,0 +1,44 @@ +spec_version: "1" +plugin: + name: Label_10_POS + type: text + docs: https://github.com/airframesio/acars-message-documentation/blob/main/research/10/POS.md + decode_level: PARTIAL +qualifiers: + labels: ["10"] + preambles: ["POS"] +parse: + - { split: ",", into: parts } + - { require_length: { var: parts, equals: 12 }, else: fail } +fields: + - name: latitude + from: $parts[1] + decode: + fn: coordinate + args: + style: single_axis + axis: latitude + prefix_chars: [N, S] + digits: 5 + divisor: 100 + - name: longitude + from: $parts[2] + decode: + fn: coordinate + args: + style: single_axis + axis: longitude + prefix_chars: [E, W] + digits: 5 + divisor: 100 + - name: altitude + from: $parts[7] + decode: { fn: integer } +formatted: + description: "Position Report" + items: + - { type: position, latitude: $latitude, longitude: $longitude } + - { type: altitude, value: $altitude } + - type: free_text + label: unknown + values: ["$parts[0]", "$parts[3]", "$parts[4]", "$parts[5]", "$parts[6]", "$parts[8]", "$parts[9]", "$parts[10]", "$parts[11]"] diff --git a/spec/labels/10/Slash.yaml b/spec/labels/10/Slash.yaml new file mode 100644 index 0000000..5fc959b --- /dev/null +++ b/spec/labels/10/Slash.yaml @@ -0,0 +1,16 @@ +spec_version: "1" +plugin: + name: Label_10_Slash + type: text + decode_level: PARTIAL +qualifiers: + labels: ["10"] + preambles: ["/"] +parse: + # Pure escape hatch: 17-field slash split, ETA + waypoint route construction + # with multiple time stamps, conditional departure airport, and trailing + # unknownArr from non-contiguous indices. + custom: label_10_slash_decode +formatted: + description: "Position Report" + custom: label_10_slash_format diff --git a/spec/labels/12/N_Space.yaml b/spec/labels/12/N_Space.yaml new file mode 100644 index 0000000..068458c --- /dev/null +++ b/spec/labels/12/N_Space.yaml @@ -0,0 +1,15 @@ +spec_version: "1" +plugin: + name: Label_12_N_Space + type: text + decode_level: PARTIAL +qualifiers: + labels: ["12"] + preambles: ["N ", "S "] +parse: + # Pure escape hatch: regex with NSEW directional capture, GRD/*** altitude + # sentinel handling, and free_text trailing fields. + custom: label_12_n_space_decode +formatted: + description: "Position Report" + custom: label_12_n_space_format diff --git a/spec/labels/12/POS.yaml b/spec/labels/12/POS.yaml new file mode 100644 index 0000000..42ad44d --- /dev/null +++ b/spec/labels/12/POS.yaml @@ -0,0 +1,16 @@ +spec_version: "1" +plugin: + name: Label_12_POS + type: text + decode_level: PARTIAL +qualifiers: + labels: ["12"] + preambles: ["POS"] +parse: + # Pure escape hatch: 8-char lat / 8-char lon substring DMS extraction, "FOB" + # and "ETA" prefix stripping, currentFuel + eta formatters not in DSL, + # interleaved unknown calls with array slices. + custom: label_12_pos_decode +formatted: + description: "Position Report" + custom: label_12_pos_format diff --git a/spec/labels/15/FST.yaml b/spec/labels/15/FST.yaml new file mode 100644 index 0000000..6ab7015 --- /dev/null +++ b/spec/labels/15/FST.yaml @@ -0,0 +1,16 @@ +spec_version: "1" +plugin: + name: Label_15_FST + type: text + decode_level: PARTIAL +qualifiers: + labels: ["15"] + preambles: ["FST01"] +parse: + # Pure escape hatch: fixed-offset substring extraction of header, custom + # NSEW direction check with divide-by-10000 coord parsing, altitude *100, + # space-separated trailing unknownArr. + custom: label_15_fst_decode +formatted: + description: "Position Report" + custom: label_15_fst_format diff --git a/spec/labels/15/Paren2.yaml b/spec/labels/15/Paren2.yaml new file mode 100644 index 0000000..3f812b0 --- /dev/null +++ b/spec/labels/15/Paren2.yaml @@ -0,0 +1,16 @@ +spec_version: "1" +plugin: + name: Label_15 + type: text + decode_level: PARTIAL +qualifiers: + labels: ["15"] + preambles: ["(2"] +parse: + # Pure escape hatch: prefix/suffix bracket trim, two variants (short vs OFF) + # branched on length and substring marker, decimal-minutes coords, OFF date + # epoch conversion vs HHMMSS-only fallback, temperature M/P sign substitution. + custom: label_15_decode +formatted: + description: "Position Report" + custom: label_15_format diff --git a/spec/labels/16/AUTPOS.yaml b/spec/labels/16/AUTPOS.yaml new file mode 100644 index 0000000..7bacf3c --- /dev/null +++ b/spec/labels/16/AUTPOS.yaml @@ -0,0 +1,17 @@ +spec_version: "1" +plugin: + name: Label_16_AUTPOS + type: text + decode_level: FULL +qualifiers: + labels: ["16"] + preambles: [""] +parse: + # Pure escape hatch: 19-group regex with optional wind / temp / TAS / mach + # fields each independently redactable, day/flight number split, DMS coords, + # date+time epoch conversion. Uses windData and several formatters not + # representable in current DSL. + custom: label_16_autpos_decode +formatted: + description: "Position Report" + custom: label_16_autpos_format diff --git a/spec/labels/16/Honeywell.yaml b/spec/labels/16/Honeywell.yaml new file mode 100644 index 0000000..312ffab --- /dev/null +++ b/spec/labels/16/Honeywell.yaml @@ -0,0 +1,17 @@ +spec_version: "1" +plugin: + name: Label_16_Honeywell + type: text + decode_level: PARTIAL +qualifiers: + labels: ["16"] + preambles: ["(2"] +parse: + # Pure escape hatch: bracket-stripped session ID + decimal-minutes coords, + # then a marker-dispatch at offset 17: '-' selects waypoint mode (variable + # 1 or 2 waypoints), else route mode (departure/arrival airports). Trailing + # 2-char phase tag preserved as unknown. + custom: label_16_honeywell_decode +formatted: + description: "Position Report" + custom: label_16_honeywell_format diff --git a/spec/labels/16/N_Space.yaml b/spec/labels/16/N_Space.yaml new file mode 100644 index 0000000..5943d73 --- /dev/null +++ b/spec/labels/16/N_Space.yaml @@ -0,0 +1,16 @@ +spec_version: "1" +plugin: + name: Label_16_N_Space + type: text + decode_level: PARTIAL +qualifiers: + labels: ["16"] + preambles: ["N ", "S "] +parse: + # Pure escape hatch: two alternative regexes (comma-delimited with altitude + # / unknown fields vs slash-delimited coords-only), each with its own + # decode level (partial vs full). + custom: label_16_n_space_decode +formatted: + description: "Position Report" + custom: label_16_n_space_format diff --git a/spec/labels/16/POSA1.yaml b/spec/labels/16/POSA1.yaml new file mode 100644 index 0000000..49166dd --- /dev/null +++ b/spec/labels/16/POSA1.yaml @@ -0,0 +1,16 @@ +spec_version: "1" +plugin: + name: Label_16_POSA1 + type: text + decode_level: PARTIAL +qualifiers: + labels: ["16"] + preambles: ["POSA1"] +parse: + # Pure escape hatch: comma split with strict 11-field count, "POSA1" + # substring strip, route construction with two waypoint+time entries, and + # altitude * 100. Route formatter is not in DSL. + custom: label_16_posa1_decode +formatted: + description: "Position Report" + custom: label_16_posa1_format diff --git a/spec/labels/16/TOD.yaml b/spec/labels/16/TOD.yaml new file mode 100644 index 0000000..e8aea5b --- /dev/null +++ b/spec/labels/16/TOD.yaml @@ -0,0 +1,16 @@ +spec_version: "1" +plugin: + name: Label_16_TOD + type: text + decode_level: PARTIAL +qualifiers: + labels: ["16"] +parse: + # Pure escape hatch: 5-field comma split, HHMMSS time validation, optional + # altitude, ETA, then position parsed from final field with 3 different + # sub-variants (2-token decimal-minutes, 4-token decimal-degrees, redacted), + # followed by optional flight number after a '/' marker. + custom: label_16_tod_decode +formatted: + description: "Position Report" + custom: label_16_tod_format diff --git a/spec/labels/1L/070.yaml b/spec/labels/1L/070.yaml new file mode 100644 index 0000000..2b7ad65 --- /dev/null +++ b/spec/labels/1L/070.yaml @@ -0,0 +1,16 @@ +spec_version: "1" +plugin: + name: Label_1L_070 + type: text + decode_level: PARTIAL +qualifiers: + labels: ["1L"] + preambles: ["000000070"] +parse: + # Pure escape hatch: 9-char prefix strip, 7-field comma split, dep/arr ICAO, + # message timestamp + eta from HHMMSS, signed-decimal coords via NSEW char, + # trailing field preserved as remaining.text. + custom: label_1l_070_decode +formatted: + description: "Position Report" + custom: label_1l_070_format diff --git a/spec/labels/1L/3-line.yaml b/spec/labels/1L/3-line.yaml new file mode 100644 index 0000000..7af15b7 --- /dev/null +++ b/spec/labels/1L/3-line.yaml @@ -0,0 +1,15 @@ +spec_version: "1" +plugin: + name: Label_1L_3Line + type: text + decode_level: PARTIAL +qualifiers: + labels: ["1L"] +parse: + # Pure escape hatch: strict 3-line check, Map-based key/value parsing across + # slash- and newline-separated tokens, DAY+UTC combined into epoch, signed + # coords from LAT/LON pair, dynamic remaining.text built from leftover keys. + custom: label_1l_3line_decode +formatted: + description: "Position Report" + custom: label_1l_3line_format diff --git a/spec/labels/1L/660.yaml b/spec/labels/1L/660.yaml new file mode 100644 index 0000000..043ce0c --- /dev/null +++ b/spec/labels/1L/660.yaml @@ -0,0 +1,16 @@ +spec_version: "1" +plugin: + name: Label_1L_660 + type: text + decode_level: PARTIAL +qualifiers: + labels: ["1L"] + preambles: ["000000660"] +parse: + # Pure escape hatch: 9-char prefix strip, decimal-minutes coords in field 0, + # field 1 sliced into HHMMSS (timestamp) + FL*100 (altitude) + waypoint + # (route), remaining fields joined back as remaining.text. + custom: label_1l_660_decode +formatted: + description: "Position Report" + custom: label_1l_660_format diff --git a/spec/labels/1L/Slash.yaml b/spec/labels/1L/Slash.yaml new file mode 100644 index 0000000..f114c50 --- /dev/null +++ b/spec/labels/1L/Slash.yaml @@ -0,0 +1,17 @@ +spec_version: "1" +plugin: + name: Label_1L_Slash + type: text + decode_level: PARTIAL +qualifiers: + labels: ["1L"] + preambles: ["+", "-"] +parse: + # Pure escape hatch: 7-part slash split, first two parts become LAT/LON via + # whitespace-stripping (signed decimal), remainder parsed into a Map of + # key/value tokens, ETA/ALT/FOB/UTC pulled out and remaining keys re-joined + # as remaining.text. + custom: label_1l_slash_decode +formatted: + description: "Position Report" + custom: label_1l_slash_format diff --git a/spec/labels/1M/Slash.yaml b/spec/labels/1M/Slash.yaml new file mode 100644 index 0000000..5c7b434 --- /dev/null +++ b/spec/labels/1M/Slash.yaml @@ -0,0 +1,17 @@ +spec_version: "1" +plugin: + name: Label_1M_Slash + type: text + decode_level: PARTIAL +qualifiers: + labels: ["1M"] + preambles: ["/"] +parse: + # Pure escape hatch: split on slash OR newline, positional access to results + # 0..9 for flight number / dep / arr / alternate / arrival runway / ETA, + # ETA computed from results[7] + yymmdd date with swapped DDMMYY order, + # arrival runway sliced from arrival ICAO prefix. + custom: label_1m_slash_decode +formatted: + description: "ETA Report" + custom: label_1m_slash_format diff --git a/spec/labels/20/CFB01.yaml b/spec/labels/20/CFB01.yaml new file mode 100644 index 0000000..d2a7171 --- /dev/null +++ b/spec/labels/20/CFB01.yaml @@ -0,0 +1,16 @@ +spec_version: "1" +plugin: + name: Label_20_CFB01 + type: text + decode_level: FULL +qualifiers: + labels: ["20"] + preambles: ["#CFB.01"] +parse: + # Pure escape hatch: named-group regex with combined coords, current-year + # epoch computation using Date.parse, fuel-in-tons stored to raw only when + # not "***"/"****". Raw-only fields with no formatter output. + custom: label_20_cfb01_decode +formatted: + description: "Crew Flight Bag Message" + custom: label_20_cfb01_format diff --git a/spec/labels/20/POS.yaml b/spec/labels/20/POS.yaml new file mode 100644 index 0000000..dda7b54 --- /dev/null +++ b/spec/labels/20/POS.yaml @@ -0,0 +1,16 @@ +spec_version: "1" +plugin: + name: Label_20_POS + type: text + decode_level: FULL +qualifiers: + labels: ["20"] + preambles: ["POS"] +parse: + # Pure escape hatch: preamble preserved to raw, branches on 11-field vs + # 5-field count, both extract position from field[0]; behavior tracked + # byte-for-byte against TS source. + custom: label_20_pos_decode +formatted: + description: "Position Report" + custom: label_20_pos_format diff --git a/spec/labels/21/POS.yaml b/spec/labels/21/POS.yaml new file mode 100644 index 0000000..94981b7 --- /dev/null +++ b/spec/labels/21/POS.yaml @@ -0,0 +1,17 @@ +spec_version: "1" +plugin: + name: Label_21_POS + type: text + decode_level: PARTIAL +qualifiers: + labels: ["21"] + preambles: ["POS"] +parse: + # Pure escape hatch: 9-field comma split, position from fixed 16-char layout + # (NSEW signs at offsets 0 and 8), HHMMSS timestamp + altitude + temperature + # (whitespace-stripped) + ETA + arrival airport, with interleaved unknownArr + # at indices 1/4/5. + custom: label_21_pos_decode +formatted: + description: "Position Report" + custom: label_21_pos_format diff --git a/spec/labels/22/OFF.yaml b/spec/labels/22/OFF.yaml new file mode 100644 index 0000000..ec7620f --- /dev/null +++ b/spec/labels/22/OFF.yaml @@ -0,0 +1,17 @@ +spec_version: "1" +plugin: + name: Label_22_OFF + type: text + decode_level: PARTIAL +qualifiers: + labels: ["22"] + preambles: ["OFF"] +parse: + # Pure escape hatch: three variants dispatched on "OFF01" / "OFF02\r\n" / + # "OFF02" prefix, each with distinct field layouts (slash-split + fixed + # substring offsets in variant 1/2, comma-split in variant 3). Uses + # departureDay/arrivalDay/off formatters that aren't in the DSL. + custom: label_22_off_decode +formatted: + description: "Takeoff Report" + custom: label_22_off_format diff --git a/spec/labels/22/POS.yaml b/spec/labels/22/POS.yaml new file mode 100644 index 0000000..8d3bc40 --- /dev/null +++ b/spec/labels/22/POS.yaml @@ -0,0 +1,16 @@ +spec_version: "1" +plugin: + name: Label_22_POS + type: text + decode_level: PARTIAL +qualifiers: + labels: ["22"] + preambles: ["N", "S"] +parse: + # Pure escape hatch: 11-field comma split, position from fixed 16-char + # layout (NSEW signs at offsets 0 and 8, divisor 10000), HHMMSS timestamp, + # altitude, then unknownArr from non-contiguous indices [1, 4..10]. + custom: label_22_pos_decode +formatted: + description: "Position Report" + custom: label_22_pos_format diff --git a/spec/labels/24/Slash.yaml b/spec/labels/24/Slash.yaml new file mode 100644 index 0000000..66d7133 --- /dev/null +++ b/spec/labels/24/Slash.yaml @@ -0,0 +1,16 @@ +spec_version: "1" +plugin: + name: Label_24_Slash + type: text + decode_level: PARTIAL +qualifiers: + labels: ["24"] + preambles: ["/"] +parse: + # Splits on '/', requires len==10 with first+last empty (begin/end with '/'), + # rearranges date substrings DDMMYY -> YYDDMM and computes epoch via + # convertDateTimeToEpoch, char-prefix sign decoding for coords. Whole-plugin hatch. + custom: label_24_slash_decode +formatted: + description: "Position Report" + custom: label_24_slash_format diff --git a/spec/labels/2P/FM3.yaml b/spec/labels/2P/FM3.yaml new file mode 100644 index 0000000..9ae9dcc --- /dev/null +++ b/spec/labels/2P/FM3.yaml @@ -0,0 +1,15 @@ +spec_version: "1" +plugin: + name: Label_2P_FM3 + type: text + decode_level: PARTIAL +qualifiers: + labels: ["2P"] +parse: + # Splits on ',' (7 fields), then header = parts[0].split('FM3 '). + # Variant on whether header[0] is non-empty (flight number padding), + # then char-prefix-or-bare coord parsing with space stripping. Whole-plugin hatch. + custom: label_2p_fm3_decode +formatted: + description: "Flight Report" + custom: label_2p_fm3_format diff --git a/spec/labels/2P/FM4.yaml b/spec/labels/2P/FM4.yaml new file mode 100644 index 0000000..a916305 --- /dev/null +++ b/spec/labels/2P/FM4.yaml @@ -0,0 +1,15 @@ +spec_version: "1" +plugin: + name: Label_2P_FM4 + type: text + decode_level: PARTIAL +qualifiers: + labels: ["2P"] +parse: + # Splits on ',' (10 fields), then header = parts[0].split('FM4'). + # Day from parts[2].substring(0,2), time from parts[2].substring(2), + # bare-number coords with space stripping. Whole-plugin hatch. + custom: label_2p_fm4_decode +formatted: + description: "Flight Report" + custom: label_2p_fm4_format diff --git a/spec/labels/2P/FM5.yaml b/spec/labels/2P/FM5.yaml new file mode 100644 index 0000000..45ff772 --- /dev/null +++ b/spec/labels/2P/FM5.yaml @@ -0,0 +1,15 @@ +spec_version: "1" +plugin: + name: Label_2P_FM5 + type: text + decode_level: PARTIAL +qualifiers: + labels: ["2P"] +parse: + # Splits on ',' (12 fields), then header = parts[0].split('FM5 '). + # Bare-number coords with space stripping, trimmed flight number at parts[10]. + # Whole-plugin hatch. + custom: label_2p_fm5_decode +formatted: + description: "Flight Report" + custom: label_2p_fm5_format diff --git a/spec/labels/30/Slash_EA.yaml b/spec/labels/30/Slash_EA.yaml new file mode 100644 index 0000000..99dbc94 --- /dev/null +++ b/spec/labels/30/Slash_EA.yaml @@ -0,0 +1,15 @@ +spec_version: "1" +plugin: + name: Label_30_Slash_EA + type: text + decode_level: PARTIAL +qualifiers: + labels: ["30"] + preambles: ["/EA"] +parse: + # Splits on /\n|\//, slices off first element, then conditional emission + # to remaining.text based on results[1] prefix. Whole-plugin hatch. + custom: label_30_slash_ea_decode +formatted: + description: "ETA Report" + custom: label_30_slash_ea_format diff --git a/spec/labels/44/ETA.yaml b/spec/labels/44/ETA.yaml new file mode 100644 index 0000000..b0d262a --- /dev/null +++ b/spec/labels/44/ETA.yaml @@ -0,0 +1,54 @@ +spec_version: "1" +plugin: + name: Label_44_ETA + type: text + decode_level: FULL +qualifiers: + labels: ["44"] + preambles: ["00ETA01", "00ETA02", "00ETA03", "ETA01", "ETA02", "ETA03"] +parse: + - { split: ",", into: data } + - { require_length: { var: data, min: 9 }, else: fail } +fields: + - name: position + from: $data[1] + decode: + fn: coordinate_decimal_minutes + args: { style: combined, format: "NSDDMM_M_EWDDMM_M" } + - name: altitude + from: $data[2] + decode: { fn: integer, args: { multiplier: 100 } } + - name: departure_icao + from: $data[3] + decode: { fn: airport } + - name: arrival_icao + from: $data[4] + decode: { fn: airport } + - name: month + from: $data[5] + decode: { fn: integer, args: { substring_start: 0, substring_length: 2 } } + - name: day + from: $data[5] + decode: { fn: integer, args: { substring_start: 2, substring_length: 2 } } + - name: timestamp + from: $data[6] + decode: { fn: timestamp_hhmmss } + - name: eta_time + from: $data[7] + decode: { fn: timestamp_hhmmss } + - name: fuel_remaining + from: $data[8] + when: { not: { in: ["$data[8]", ["---.-"]] } } + decode: { fn: float } +formatted: + description: "ETA Report" + items: + - { type: position, value: $position } + - { type: altitude, value: $altitude } + - { type: airport_origin, value: $departure_icao } + - { type: airport_destination, value: $arrival_icao } + - { type: timestamp, kind: month, value: $month } + - { type: timestamp, kind: day, value: $day } + - { type: timestamp, kind: current, value: $timestamp } + - { type: timestamp, kind: eta, value: $eta_time } + - { type: fuel, kind: remaining, value: $fuel_remaining, when_present: true } diff --git a/spec/labels/44/IN.yaml b/spec/labels/44/IN.yaml new file mode 100644 index 0000000..8af0805 --- /dev/null +++ b/spec/labels/44/IN.yaml @@ -0,0 +1,46 @@ +spec_version: "1" +plugin: + name: Label_44_IN + type: text + decode_level: FULL +qualifiers: + labels: ["44"] + preambles: ["00IN01", "00IN02", "00IN03", "IN01", "IN02", "IN03"] +parse: + - { split: ",", into: data } + - { require_length: { var: data, min: 7 }, else: fail } +fields: + - name: position + from: $data[1] + decode: + fn: coordinate_decimal_minutes + args: { style: combined, format: "NSDDMM_M_EWDDMM_M" } + - name: departure_icao + from: $data[2] + decode: { fn: airport } + - name: arrival_icao + from: $data[3] + decode: { fn: airport } + - name: month + from: $data[4] + decode: { fn: integer, args: { substring_start: 0, substring_length: 2 } } + - name: day + from: $data[4] + decode: { fn: integer, args: { substring_start: 2, substring_length: 2 } } + - name: in_time + from: $data[5] + decode: { fn: timestamp_hhmmss } + - name: fuel_remaining + from: $data[6] + when: { not: { in: ["$data[6]", ["---.-"]] } } + decode: { fn: float } +formatted: + description: "In Gate Report" + items: + - { type: position, value: $position } + - { type: airport_origin, value: $departure_icao } + - { type: airport_destination, value: $arrival_icao } + - { type: timestamp, kind: month, value: $month } + - { type: timestamp, kind: day, value: $day } + - { type: timestamp, kind: in, value: $in_time } + - { type: fuel, kind: remaining, value: $fuel_remaining, when_present: true } diff --git a/spec/labels/44/OFF.yaml b/spec/labels/44/OFF.yaml new file mode 100644 index 0000000..4d5c35e --- /dev/null +++ b/spec/labels/44/OFF.yaml @@ -0,0 +1,50 @@ +spec_version: "1" +plugin: + name: Label_44_OFF + type: text + decode_level: FULL +qualifiers: + labels: ["44"] + preambles: ["00OFF01", "00OFF02", "00OFF03", "OFF01", "OFF02", "OFF03"] +parse: + - { split: ",", into: data } + - { require_length: { var: data, min: 8 }, else: fail } +fields: + - name: position + from: $data[1] + decode: + fn: coordinate_decimal_minutes + args: { style: combined, format: "NSDDMM_M_EWDDMM_M" } + - name: departure_icao + from: $data[2] + decode: { fn: airport } + - name: arrival_icao + from: $data[3] + decode: { fn: airport } + - name: month + from: $data[4] + decode: { fn: integer, args: { substring_start: 0, substring_length: 2 } } + - name: day + from: $data[4] + decode: { fn: integer, args: { substring_start: 2, substring_length: 2 } } + - name: off_time + from: $data[5] + decode: { fn: timestamp_hhmmss } + - name: eta_time + from: $data[6] + decode: { fn: timestamp_hhmmss } + - name: fuel_remaining + from: $data[7] + when: { not: { in: ["$data[7]", ["---.-"]] } } + decode: { fn: float } +formatted: + description: "Off Runway Report" + items: + - { type: position, value: $position } + - { type: airport_origin, value: $departure_icao } + - { type: airport_destination, value: $arrival_icao } + - { type: timestamp, kind: month, value: $month } + - { type: timestamp, kind: day, value: $day } + - { type: timestamp, kind: off, value: $off_time } + - { type: timestamp, kind: eta, value: $eta_time } + - { type: fuel, kind: remaining, value: $fuel_remaining, when_present: true } diff --git a/spec/labels/44/ON.yaml b/spec/labels/44/ON.yaml new file mode 100644 index 0000000..18b9999 --- /dev/null +++ b/spec/labels/44/ON.yaml @@ -0,0 +1,46 @@ +spec_version: "1" +plugin: + name: Label_44_ON + type: text + decode_level: FULL +qualifiers: + labels: ["44"] + preambles: ["00ON01", "00ON02", "00ON03", "ON01", "ON02", "ON03"] +parse: + - { split: ",", into: data } + - { require_length: { var: data, min: 7 }, else: fail } +fields: + - name: position + from: $data[1] + decode: + fn: coordinate_decimal_minutes + args: { style: combined, format: "NSDDMM_M_EWDDMM_M" } + - name: departure_icao + from: $data[2] + decode: { fn: airport } + - name: arrival_icao + from: $data[3] + decode: { fn: airport } + - name: month + from: $data[4] + decode: { fn: integer, args: { substring_start: 0, substring_length: 2 } } + - name: day + from: $data[4] + decode: { fn: integer, args: { substring_start: 2, substring_length: 2 } } + - name: on_time + from: $data[5] + decode: { fn: timestamp_hhmmss } + - name: fuel_remaining + from: $data[6] + when: { not: { in: ["$data[6]", ["---.-"]] } } + decode: { fn: float } +formatted: + description: "On Runway Report" + items: + - { type: position, value: $position } + - { type: airport_origin, value: $departure_icao } + - { type: airport_destination, value: $arrival_icao } + - { type: timestamp, kind: month, value: $month } + - { type: timestamp, kind: day, value: $day } + - { type: timestamp, kind: on, value: $on_time } + - { type: fuel, kind: remaining, value: $fuel_remaining, when_present: true } diff --git a/spec/labels/44/POS.yaml b/spec/labels/44/POS.yaml new file mode 100644 index 0000000..4bd6104 --- /dev/null +++ b/spec/labels/44/POS.yaml @@ -0,0 +1,68 @@ +spec_version: "1" +plugin: + name: Label_44_POS + type: text + docs: https://github.com/airframesio/acars-message-documentation/blob/main/research/44/POS.md + decode_level: FULL +qualifiers: + labels: ["44"] + preambles: ["00POS01", "00POS02", "00POS03", "POS01", "POS02", "POS03"] +parse: + - regex: "^.*,(?.*),(?.*),(?.*),(?.*),(?.*),(?.*),(?.*),(?.*)$" + on: $message.text + into: m +fields: + - name: position + from: $m.unsplit_coords + decode: + fn: coordinate_decimal_minutes + args: { style: combined, format: "NSDDMM_M_EWDDMM_M" } + + - name: flight_level_raw + from: $m.flight_level_or_ground + decode: + fn: custom + custom: parse_flight_level_or_ground + - name: altitude + from: $flight_level_raw + decode: + fn: custom + custom: flight_level_to_altitude_feet + + - name: month + from: $m.current_date + decode: { fn: integer, args: { substring_start: 0, substring_length: 2 } } + - name: day + from: $m.current_date + decode: { fn: integer, args: { substring_start: 2, substring_length: 2 } } + + - name: timestamp + from: $m.current_time + decode: { fn: timestamp_hhmmss, args: { append: "00" } } + - name: eta + from: $m.eta_time + decode: { fn: timestamp_hhmmss, args: { append: "00" } } + + - name: fuel_in_tons + from: $m.fuel_in_tons + when: { not: { in: [$m.fuel_in_tons, ["***", "****"]] } } + decode: { fn: float } + + - name: departure_icao + from: $m.departure_icao + decode: { fn: airport } + - name: arrival_icao + from: $m.arrival_icao + decode: { fn: airport } +formatted: + description: "Position Report" + items: + - { type: position, value: $position } + - { type: timestamp, kind: month, value: $month } + - { type: timestamp, kind: day, value: $day } + - { type: timestamp, kind: current, value: $timestamp } + - { type: timestamp, kind: eta, value: $eta } + - { type: fuel, units: tons, value: $fuel_in_tons, when_present: true } + - { type: airport_origin, value: $departure_icao } + - { type: airport_destination, value: $arrival_icao } + - { type: altitude, value: $altitude } diff --git a/spec/labels/44/Slash.yaml b/spec/labels/44/Slash.yaml new file mode 100644 index 0000000..6e8ce3d --- /dev/null +++ b/spec/labels/44/Slash.yaml @@ -0,0 +1,16 @@ +spec_version: "1" +plugin: + name: Label_44_Slash + type: text + decode_level: PARTIAL +qualifiers: + labels: ["44"] + preambles: [" /FB"] +parse: + # Splits on '/' (4 fields), then inner split of fields[3] on ',' with + # nested length-based branch for 18-field extended form (runway + procedure + # via FlightPlanUtils). Char-prefix sign decoding for coords. Whole-plugin hatch. + custom: label_44_slash_decode +formatted: + description: "Flight Briefing" + custom: label_44_slash_format diff --git a/spec/labels/4A.yaml b/spec/labels/4A.yaml new file mode 100644 index 0000000..428939e --- /dev/null +++ b/spec/labels/4A.yaml @@ -0,0 +1,20 @@ +spec_version: "1" +plugin: + name: Label_4A + type: text + docs: https://github.com/airframesio/acars-message-documentation/blob/main/research/4A/README.md + decode_level: MESSAGE +qualifiers: + labels: ["4A"] +parse: + # Whole-plugin hatch. The original Label_4A.ts has three variants selected + # by field-count + first-char inspection, with each variant doing inline + # ResultFormatter calls (position via substring slicing, route with two + # waypoints, temperature, eta, altitude, callsign, tail, airports). The + # field-level + format-level decomposition the spec previously attempted + # didn't have a clean contract for formatter-owning-raw, so the hatch + # form is the right shape here. + custom: label_4a_dispatch +formatted: + description: "Latest New Format" + custom: label_4a_format diff --git a/spec/labels/4A/01.yaml b/spec/labels/4A/01.yaml new file mode 100644 index 0000000..97ef075 --- /dev/null +++ b/spec/labels/4A/01.yaml @@ -0,0 +1,16 @@ +spec_version: "1" +plugin: + name: Label_4A_01 + type: text + decode_level: MESSAGE +qualifiers: + labels: ["4A"] + preambles: ["01"] +parse: + # Single multiline regex with 9 capture groups, plus state_change/temperature + # emission (non-canonical formatter types) and sign-stripping on altitude/temp. + # Whole-plugin hatch. + custom: label_4a_01_decode +formatted: + description: "Latest New Format" + custom: label_4a_01_format diff --git a/spec/labels/4A/DIS.yaml b/spec/labels/4A/DIS.yaml new file mode 100644 index 0000000..c0c5f2b --- /dev/null +++ b/spec/labels/4A/DIS.yaml @@ -0,0 +1,15 @@ +spec_version: "1" +plugin: + name: Label_4A_DIS + type: text + decode_level: FULL +qualifiers: + labels: ["4A"] + preambles: ["DIS"] +parse: + # Imperative: split on comma, slice 2-char timestamp prefix off fields[1], + # join fields.slice(3) without separator. Done as whole-plugin hatch. + custom: label_4a_dis_decode +formatted: + description: "Latest New Format" + custom: label_4a_dis_format diff --git a/spec/labels/4A/DOOR.yaml b/spec/labels/4A/DOOR.yaml new file mode 100644 index 0000000..11e7002 --- /dev/null +++ b/spec/labels/4A/DOOR.yaml @@ -0,0 +1,15 @@ +spec_version: "1" +plugin: + name: Label_4A_DOOR + type: text + decode_level: FULL +qualifiers: + labels: ["4A"] + preambles: ["DOOR"] +parse: + # Imperative: split on space, then split fields[0] on '/' to get door name, + # emit door_event (non-canonical formatter type). Done as whole-plugin hatch. + custom: label_4a_door_decode +formatted: + description: "Latest New Format" + custom: label_4a_door_format diff --git a/spec/labels/4A/Slash_01.yaml b/spec/labels/4A/Slash_01.yaml new file mode 100644 index 0000000..452d892 --- /dev/null +++ b/spec/labels/4A/Slash_01.yaml @@ -0,0 +1,14 @@ +spec_version: "1" +plugin: + name: Label_4A_Slash_01 + type: text + decode_level: MESSAGE +qualifiers: + labels: ["4A"] + preambles: ["/01"] +parse: + # Trivial but writes to remaining.text (no declarative primitive for that). + custom: label_4a_slash_01_decode +formatted: + description: "Latest New Format" + custom: label_4a_slash_01_format diff --git a/spec/labels/4N.yaml b/spec/labels/4N.yaml new file mode 100644 index 0000000..2bd864f --- /dev/null +++ b/spec/labels/4N.yaml @@ -0,0 +1,16 @@ +spec_version: "1" +plugin: + name: Label_4N + type: text + decode_level: MESSAGE +qualifiers: + labels: ["4N"] +parse: + # Two completely different variants dispatched by raw text length (51) vs + # CSV field count (33). Variant 1 uses fixed-offset substrings with regex + # massage of coords. Variant 2 uses CSV with sub-splits, filter, and + # multi-field unknownArr aggregation. Whole-plugin hatch. + custom: label_4n_decode +formatted: + description: "Airline Defined" + custom: label_4n_format diff --git a/spec/labels/4T/AGFSR.yaml b/spec/labels/4T/AGFSR.yaml new file mode 100644 index 0000000..5653b7e --- /dev/null +++ b/spec/labels/4T/AGFSR.yaml @@ -0,0 +1,18 @@ +spec_version: "1" +plugin: + name: Label_4T_AGFSR + type: text + decode_level: PARTIAL +qualifiers: + labels: ["4T"] + preambles: ["AGFSR"] +parse: + # Substring slice + split on '/' with 20-part length gate, plus inline + # lat/lon parsing (deg from digits, minutes from sub-digits, N/S/E/W sign + # via CoordinateUtils.getDirection). Many positional fields with + # IATA-coded airport prefixes and unknown-array tails — beyond the + # declarative spec without adding new primitives. + custom: label_4t_agfsr_parse +formatted: + description: "Position Report" + custom: label_4t_agfsr_format diff --git a/spec/labels/4T/ETA.yaml b/spec/labels/4T/ETA.yaml new file mode 100644 index 0000000..6bb5ca3 --- /dev/null +++ b/spec/labels/4T/ETA.yaml @@ -0,0 +1,18 @@ +spec_version: "1" +plugin: + name: Label_4T_ETA + type: text + decode_level: FULL +qualifiers: + labels: ["4T"] + preambles: ["ETA"] +parse: + # message.text.substring(3).split('/') with strict 3-part length, then a + # second split-on-space inside data[2]. Uses IATA-coded airport + # ResultFormatter and HHMM→TOD timestamp via convertHHMMSSToTod on a + # 4-char prefix. Departure/arrival "day" formatters aren't represented in + # the formatter type enum — escape hatch keeps byte-for-byte parity. + custom: label_4t_eta_parse +formatted: + description: "ETA Report" + custom: label_4t_eta_format diff --git a/spec/labels/58.yaml b/spec/labels/58.yaml new file mode 100644 index 0000000..65456e1 --- /dev/null +++ b/spec/labels/58.yaml @@ -0,0 +1,17 @@ +spec_version: "1" +plugin: + name: Label_58 + type: text + decode_level: PARTIAL +qualifiers: + labels: ["58"] +parse: + # split('/') with strict 8-part length gate; coordinate parsing uses + # CoordinateUtils.getDirection(first-char) * Number(rest) for both axes, + # which doesn't fit the existing `coordinate` decode args (no divisor here). + # Also uses `day` formatter (not in formatter type enum) and trailing + # unknown('/') tails for parts[6]/[7]. Whole-plugin hatch for parity. + custom: label_58_parse +formatted: + description: "Position Report" + custom: label_58_format diff --git a/spec/labels/5Z/Slash.yaml b/spec/labels/5Z/Slash.yaml new file mode 100644 index 0000000..641b1c5 --- /dev/null +++ b/spec/labels/5Z/Slash.yaml @@ -0,0 +1,20 @@ +spec_version: "1" +plugin: + name: Label_5Z_Slash + type: text + decode_level: MESSAGE +qualifiers: + labels: ["5Z"] + preambles: ["/"] +parse: + # Multi-format airline-defined downlink: + # - /TXT line + remainder as free text (full decode) + # - 25-entry descriptions table dispatch (B1/B3/CD/CG/CM/C3/C4/C5/C6/10/C11/DS/D3/D6/D7/EO/ET/PW/RL/R3/R4/TC/WB/W1) + # - per-type branches with magic-string discriminants + # (e.g. "B3 TO DATA REQ ", "C3 GATE REQ ") + # - injects airline=United Airlines + message_type items + # Too many specialized branches and magic substrings for declarative variants. + custom: label_5z_slash_parse +formatted: + description: "Airline Designated Downlink" + custom: label_5z_slash_format diff --git a/spec/labels/80.yaml b/spec/labels/80.yaml new file mode 100644 index 0000000..87dcecb --- /dev/null +++ b/spec/labels/80.yaml @@ -0,0 +1,20 @@ +spec_version: "1" +plugin: + name: Label_80 + type: text + decode_level: MESSAGE +qualifiers: + labels: ["80"] +parse: + # Two top-level formats: + # - Single-line CSV with 9 comma-separated parts → parseCsvFormat + # - Multi-line with header + tag-prefixed key/value pairs + # (POS / ALT / FL / MCH / SPD / TAS / SAT / FB / FOB / UTC / ETA / + # HDG / NWYP / SWN / DWN / AD / default) + # Multiple inline regex captures (POS lat/lon), unit transforms + # (MCH/1000, FL*100, FOB strip non-numeric), and per-tag dispatch make a + # whole-plugin hatch the safest path to byte-for-byte parity. + custom: label_80_parse +formatted: + description: "Airline Defined Position Report" + custom: label_80_format diff --git a/spec/labels/83.yaml b/spec/labels/83.yaml new file mode 100644 index 0000000..d87d45e --- /dev/null +++ b/spec/labels/83.yaml @@ -0,0 +1,17 @@ +spec_version: "1" +plugin: + name: Label_83 + type: text + decode_level: MESSAGE +qualifiers: + labels: ["83"] +parse: + # Three discriminated variants: + # - Prefix "4DH3 ETAT2" → split on whitespace, eta from fields[6]+"00" + # - Prefix "001PR" → fixed-offset substrings: day, decimal-min coords (with '.' stripped), altitude, tail unknown + # - Default → whitespace-stripped then split(',') with 9-part shape: airports + lat/lon as raw Number(), altitude/groundspeed/heading + # Mixed substring slicing + variant discrimination by message prefix. + custom: label_83_parse +formatted: + description: "Airline Defined" + custom: label_83_format diff --git a/spec/labels/8E.yaml b/spec/labels/8E.yaml new file mode 100644 index 0000000..5b42b59 --- /dev/null +++ b/spec/labels/8E.yaml @@ -0,0 +1,16 @@ +spec_version: "1" +plugin: + name: Label_8E + type: text + decode_level: FULL +qualifiers: + labels: ["8E"] +parse: + # Regex match is best-effort: TS unconditionally marks decoded=true even if + # the regex doesn't match (no fields are formatted in that case). The + # `convertHHMMSSToTod` call is also fed a 4-digit HHMM string verbatim + # (no '00' append). Whole-plugin hatch preserves that exact behavior. + custom: label_8e_parse +formatted: + description: "ETA Report" + custom: label_8e_format diff --git a/spec/labels/B6/Forwardslash.yaml b/spec/labels/B6/Forwardslash.yaml new file mode 100644 index 0000000..218e251 --- /dev/null +++ b/spec/labels/B6/Forwardslash.yaml @@ -0,0 +1,16 @@ +spec_version: "1" +plugin: + name: Label_B6_Forwardslash + type: text + decode_level: NONE +qualifiers: + labels: ["B6"] + preambles: ["/"] +parse: + # TS source is a placeholder: returns defaultResult with description only, + # no fields parsed, decoded stays false. The escape hatch matches that + # exactly (also preserves the debug log path). + custom: label_b6_forwardslash_parse +formatted: + description: "CPDLC Message" + custom: label_b6_forwardslash_format diff --git a/spec/labels/ColonComma.yaml b/spec/labels/ColonComma.yaml new file mode 100644 index 0000000..6c5494d --- /dev/null +++ b/spec/labels/ColonComma.yaml @@ -0,0 +1,13 @@ +spec_version: "1" +plugin: + name: Label_ColonComma + type: text + docs: https://github.com/airframesio/acars-message-documentation/blob/main/research/colon_comma/README.md + decode_level: FULL +qualifiers: + labels: [":;"] +parse: + custom: label_colon_comma_parse +formatted: + description: "Aircraft Transceiver Frequency Change" + custom: label_colon_comma_format diff --git a/spec/labels/H1/ATIS.yaml b/spec/labels/H1/ATIS.yaml new file mode 100644 index 0000000..a2c9bc2 --- /dev/null +++ b/spec/labels/H1/ATIS.yaml @@ -0,0 +1,20 @@ +spec_version: "1" +plugin: + name: Label_H1_ATIS + type: text + decode_level: FULL +qualifiers: + # NOTE: TS qualifier returns labels=['H1','5Z'] both routed to preamble 'L'. + # Encoding both here so the spec qualifier matches the plugin's intent. + labels: ["H1", "5Z"] + preambles: ["L"] +parse: + # Regex with 6 named groups (seq, flight, facility, code, airport, checksum) + # plus conditional ATIS-airport item (only when airport != facility) and + # hex-parsed checksum item (parseInt(checksum, 16)). The conditional + # second airport item and `atis_code` raw key don't fit the formatter + # type enum cleanly — escape hatch preserves item set and ordering. + custom: label_h1_atis_parse +formatted: + description: "ATIS Subscription" + custom: label_h1_atis_format diff --git a/spec/labels/H1/EZF.yaml b/spec/labels/H1/EZF.yaml new file mode 100644 index 0000000..6177a81 --- /dev/null +++ b/spec/labels/H1/EZF.yaml @@ -0,0 +1,23 @@ +spec_version: "1" +plugin: + name: Label_H1_EZF + type: text + decode_level: PARTIAL +qualifiers: + # TS qualifier returns labels=['H1','1M'] with preamble 'EZF'. + labels: ["H1", "1M"] + preambles: ["EZF"] +parse: + # Multi-line load-sheet parser: + # - split on '\n', trim, filter empties + # - first line must equal 'EZF' (else failUnknown) + # - second line is flight identifier "NO0246/10/EI-NEO" → flight + tail + # - third line is config string (stored as unknown) + # - subsequent '-KEY/VALUE' lines parsed into a loadsheet dict + # - SCT key further split on '-' into IATA dep/arr airports + # - 8 known loadsheet keys (STD, FLT STATUS, UOM, ZFW, PAX, TOW, DOW, FWT) + # are formatted into custom 'loadsheet'-type items. + custom: label_h1_ezf_parse +formatted: + description: "Load Sheet" + custom: label_h1_ezf_format diff --git a/spec/labels/H1/FLR.yaml b/spec/labels/H1/FLR.yaml new file mode 100644 index 0000000..30d77f2 --- /dev/null +++ b/spec/labels/H1/FLR.yaml @@ -0,0 +1,18 @@ +spec_version: "1" +plugin: + name: Label_H1_FLR + type: text + decode_level: PARTIAL +qualifiers: + labels: ["H1"] + preambles: ["FLR", "#CFBFLR"] +parse: + # Split on '/FR', then header parts before and fault body after. + # Body[0..20] is metadata with YYMMDDHHMMSS at [0..12]; date is rebuilt + # as DDMMYY by slicing (4,6)+(2,4)+(0,2), then convertDateTimeToEpoch. + # Body[20..] is the free-text fault message stored as a custom 'fault'-type + # item with code 'FR'. Header loop pushes leading fields as unknown('/'). + custom: label_h1_flr_parse +formatted: + description: "Fault Log Report" + custom: label_h1_flr_format diff --git a/spec/labels/H1/M_POS.yaml b/spec/labels/H1/M_POS.yaml new file mode 100644 index 0000000..079d7c1 --- /dev/null +++ b/spec/labels/H1/M_POS.yaml @@ -0,0 +1,17 @@ +spec_version: "1" +plugin: + name: Label_H1_M_POS + type: text + decode_level: MESSAGE +qualifiers: + labels: ["H1"] +parse: + # M[NN]A[AA][NNNN] header regex; remainder split on ',' with a 7-field + # minimum (origin, dest, DDHHMM, lat, lon, alt, hdg + optional tail). + # Coordinates are raw parseFloat after stripping whitespace. + # Decode level is computed per-message: 'full' if no trailing unknowns, + # 'partial' if any. Tail unknown args use default separator (no '/'). + custom: label_h1_m_pos_parse +formatted: + description: "M-Series Periodic Position Report" + custom: label_h1_m_pos_format diff --git a/spec/labels/H1/OFP.yaml b/spec/labels/H1/OFP.yaml new file mode 100644 index 0000000..7105400 --- /dev/null +++ b/spec/labels/H1/OFP.yaml @@ -0,0 +1,21 @@ +spec_version: "1" +plugin: + name: Label_H1_OFP + type: text + decode_level: FULL +qualifiers: + # TS qualifier returns labels=['H1','1M'] (no preambles); guard is the + # '(FPL-' substring presence check inside decode(). + labels: ["H1", "1M"] +parse: + # Delegates to parseIcaoFpl (icao_fpl_utils.ts) — a multi-stage ICAO + # flight-plan parser with state-machine bracket matching and rich field + # extraction (callsign, aircraftType, departure, destination, alternates, + # route, REG, flightRules, cruiseSpeed, cruiseLevel, otherInfo dict). + # Result is mapped to a fixed item list with rules-code translation map + # (I/V/Y/Z → IFR/VFR/IFR_VFR/VFR_IFR). Far too complex to express + # declaratively — whole-plugin hatch. + custom: label_h1_ofp_parse +formatted: + description: "Operational Flight Plan" + custom: label_h1_ofp_format diff --git a/spec/labels/H1/OHMA.yaml b/spec/labels/H1/OHMA.yaml new file mode 100644 index 0000000..073f7be --- /dev/null +++ b/spec/labels/H1/OHMA.yaml @@ -0,0 +1,28 @@ +spec_version: "1" +plugin: + name: Label_H1_OHMA + type: binary + docs: https://github.com/airframesio/acars-message-documentation/blob/main/research/H1/OHMA.md + decode_level: FULL +qualifiers: + labels: ["H1"] + preambles: ["OHMA", "/RTNBOCR.OHMA", "#T1B/RTNBOCR.OHMA"] +parse: + # message.text contains "...OHMA". Strip prefix, base64-decode, + # deflate (raw), UTF-8 decode → JSON text. + - { split: "OHMA", into: ohma_parts } + - { require_length: { var: ohma_parts, min: 2 }, else: fail } + - { base64: "$ohma_parts[1]", into: deflated_bytes } + - { deflate: { source: $deflated_bytes, format: raw }, into: json_bytes } + - { text_decode: { source: $json_bytes, encoding: utf-8 }, into: json_text } +fields: + - name: ohma + from: $json_text + decode: + fn: custom + custom: ohma_unwrap_message +formatted: + description: "OHMA Message" + items: + - type: custom + custom: ohma_message_item diff --git a/spec/labels/H1/Paren.yaml b/spec/labels/H1/Paren.yaml new file mode 100644 index 0000000..5290676 --- /dev/null +++ b/spec/labels/H1/Paren.yaml @@ -0,0 +1,18 @@ +spec_version: "1" +plugin: + name: Label_H1_Paren + type: text + decode_level: PARTIAL +qualifiers: + labels: ["H1"] + preambles: ["("] +parse: + # Multi-line regex (POS-FLT LAT LON / TIMESTAMP F-ALT \n RMK/FUEL F.F M.MM) + # with embedded lat/lon parsers (parseLat/parseLon do deg-min decode with + # NSEW sign). On a successful match, also pushes a literal 'RMK' unknown + # marker. Fuel uses parseFloat (decimal value), mach uses parseFloat, and + # altitude is parsed * 100. Whole-plugin hatch for parity. + custom: label_h1_paren_parse +formatted: + description: "Position Report" + custom: label_h1_paren_format diff --git a/spec/labels/H1/StarPOS.yaml b/spec/labels/H1/StarPOS.yaml new file mode 100644 index 0000000..d713c09 --- /dev/null +++ b/spec/labels/H1/StarPOS.yaml @@ -0,0 +1,19 @@ +spec_version: "1" +plugin: + name: Label_H1_StarPOS + type: text + decode_level: PARTIAL +qualifiers: + labels: ["H1"] + preambles: ["*POS"] +parse: + # Fixed-length (43 char) message with mandatory '*POS' prefix; fixed + # substring offsets for: month [4..6], day [6..8], time HHMM [8..12], + # lat NSDM [12..17] split as N|S sign + 2-digit deg + 2-digit decimal-min, + # lon EWDM [17..23] split as E|W sign + 3-digit deg + 2-digit decimal-min, + # altitude [23..28], trailing unknown [28..]. The lat/lon math is + # deg + min/60 (not the standard decodeStringCoordinates). + custom: label_h1_starpos_parse +formatted: + description: "Position Report" + custom: label_h1_starpos_format diff --git a/spec/labels/H1/WRN.yaml b/spec/labels/H1/WRN.yaml new file mode 100644 index 0000000..d73d8d1 --- /dev/null +++ b/spec/labels/H1/WRN.yaml @@ -0,0 +1,18 @@ +spec_version: "1" +plugin: + name: Label_H1_WRN + type: text + decode_level: PARTIAL +qualifiers: + labels: ["H1"] + preambles: ["WRN", "#CFBWRN"] +parse: + # Split on '/WN'; header fields[1..] become unknown items. Body[0..20] is + # metadata: YYMMDDHHMMSS at [0..12] reassembled as DDMMYY (slices + # (4,6)+(2,4)+(0,2)) for convertDateTimeToEpoch. Body[20..] is the + # warning message stored as a custom 'warning'-type item with code 'WRN'. + # Mirrors Label_H1_FLR structure but with different prefix and item type. + custom: label_h1_wrn_parse +formatted: + description: "Warning Message" + custom: label_h1_wrn_format diff --git a/spec/labels/H2/02E.yaml b/spec/labels/H2/02E.yaml new file mode 100644 index 0000000..07e928f --- /dev/null +++ b/spec/labels/H2/02E.yaml @@ -0,0 +1,20 @@ +spec_version: "1" +plugin: + name: Label_H2_02E + type: text + decode_level: MESSAGE +qualifiers: + labels: ["H2"] + preambles: ["02E"] +parse: + # Whole-plugin escape hatch: + # - Space-split message, last part must be "Q" sentinel. + # - 45-char header parses 02E + day + departure ICAO + arrival ICAO + first wind. + # - Each subsequent space-separated chunk must start with "Q" and contains a 32-char weather record. + # - Failed parses get appended (space-joined) to remaining.text. + # - decode level is "full" if remaining.text empty, else "partial". + # The iterative accumulation of remaining.text and dynamic wind-data array don't fit DSL primitives. + custom: label_h2_02e_dispatch +formatted: + description: "Weather Report" + custom: label_h2_02e_format diff --git a/spec/labels/HX.yaml b/spec/labels/HX.yaml new file mode 100644 index 0000000..fa0d620 --- /dev/null +++ b/spec/labels/HX.yaml @@ -0,0 +1,22 @@ +spec_version: "1" +plugin: + name: Label_HX + type: text + decode_level: MESSAGE +qualifiers: + labels: ["HX"] + preambles: ["RA FMT LOCATION", "RA FMT 43"] +parse: + # Whole-plugin escape hatch: + # - Space-split message, dispatch on parts[2] = "LOCATION" or "43". + # - LOCATION variant: parts[3]/parts[4] hold packed coords (e.g. "N4009.6" + "W07540.8"). + # Position computed as deg + min/60, signed by N/S and E/W. parts.slice(5) accumulated + # into remaining.text joined by " ". + # - 43 variant: parts[3] is departure airport, parts.slice(4) accumulated into remaining. + # - Other parts[2] => decoded=false, decodeLevel=none. + # The packed-coord substring math + unknownArr(parts.slice(N), " ") accumulation is not + # cleanly expressible with current DSL primitives, so the whole plugin is a hatch. + custom: label_hx_dispatch +formatted: + description: "Undelivered Uplink Report" + custom: label_hx_format diff --git a/spec/labels/MA.yaml b/spec/labels/MA.yaml new file mode 100644 index 0000000..49a0dc3 --- /dev/null +++ b/spec/labels/MA.yaml @@ -0,0 +1,19 @@ +spec_version: "1" +plugin: + name: Label_MA + type: text + decode_level: MESSAGE +qualifiers: + labels: ["MA"] +parse: + # Whole-plugin escape hatch: + # - Calls MIAMCoreUtils.parse(message.text) -- ASCII85 decode, deflate, CRC checks, + # ACARS PDU framing (version-dependent). + # - If MIAM-decoded message has acars data with crcOk & complete & text, recursively + # invokes this.decoder.decode({label, sublabel, text}) and merges raw/items/remaining. + # - Decode level flips between "full" (CRC OK + complete + nested decode) and "partial". + # Recursive MessageDecoder use and version-dependent MIAM dispatch live in escape hatches. + custom: label_ma_dispatch +formatted: + description: "Unknown" + custom: label_ma_format diff --git a/spec/labels/QP.yaml b/spec/labels/QP.yaml new file mode 100644 index 0000000..2282a06 --- /dev/null +++ b/spec/labels/QP.yaml @@ -0,0 +1,19 @@ +spec_version: "1" +plugin: + name: Label_QP + type: text + decode_level: MESSAGE +qualifiers: + labels: ["QP"] +parse: + # Whole-plugin escape hatch: + # - departure_icao = text[0:4], arrival_icao = text[4:8] + # - out_time = HHMMSSToTod(text[8:12]) (4-char HHMM, internally padded "00") + # - remaining.text = text[12:] (via ResultFormatter.unknown side effect) + # - decodeLevel "full" iff remaining empty, else "partial". + # The OUT time formatter + remaining.text side-effect from .unknown() doesn't map cleanly + # to declarative formatter items, so kept as escape hatch. + custom: label_qp_dispatch +formatted: + description: "OUT Report" + custom: label_qp_format diff --git a/spec/labels/QQ.yaml b/spec/labels/QQ.yaml new file mode 100644 index 0000000..8441cb5 --- /dev/null +++ b/spec/labels/QQ.yaml @@ -0,0 +1,23 @@ +spec_version: "1" +plugin: + name: Label_QQ + type: text + decode_level: MESSAGE +qualifiers: + labels: ["QQ"] +parse: + # Whole-plugin escape hatch: + # - departure_icao = text[0:4], arrival_icao = text[4:8] + # - Variant A: text[12:19] == "\r\n001FE": + # day=text[19:21]; OFF time=HHMMSSToTod(text[21:27]); + # position from text[27:34]/[34:42] (NSDDMM.M / EWDDDMM.M packed) + # unknown(text[42:45]) then position; if that unknown != "---" then groundspeed=int(text[45:48]) + # else unknown(text[45:48]); finally unknown(text[48:]). + # - Variant B (fallback): OFF time=HHMMSSToTod(text[8:12]+"00"); unknown(text[12:]). + # - Order-sensitive side effects on remaining.text via ResultFormatter.unknown. + # The position-vs-groundspeed branching keyed on prior "remaining" content makes this + # whole-plugin escape hatch the cleanest route to byte-for-byte parity. + custom: label_qq_dispatch +formatted: + description: "OFF Report" + custom: label_qq_format diff --git a/spec/labels/QR.yaml b/spec/labels/QR.yaml new file mode 100644 index 0000000..e1cc8f6 --- /dev/null +++ b/spec/labels/QR.yaml @@ -0,0 +1,17 @@ +spec_version: "1" +plugin: + name: Label_QR + type: text + decode_level: MESSAGE +qualifiers: + labels: ["QR"] +parse: + # Whole-plugin escape hatch (mirrors QP, but emits ON time instead of OUT): + # - departure_icao = text[0:4], arrival_icao = text[4:8] + # - on_time = HHMMSSToTod(text[8:12]) + # - remaining.text = text[12:] + # - decodeLevel "full" iff remaining empty, else "partial". + custom: label_qr_dispatch +formatted: + description: "ON Report" + custom: label_qr_format diff --git a/spec/labels/QS.yaml b/spec/labels/QS.yaml new file mode 100644 index 0000000..07009fc --- /dev/null +++ b/spec/labels/QS.yaml @@ -0,0 +1,17 @@ +spec_version: "1" +plugin: + name: Label_QS + type: text + decode_level: MESSAGE +qualifiers: + labels: ["QS"] +parse: + # Whole-plugin escape hatch (mirrors QP, but emits IN time instead of OUT): + # - departure_icao = text[0:4], arrival_icao = text[4:8] + # - in_time = HHMMSSToTod(text[8:12]) + # - remaining.text = text[12:] + # - decodeLevel "full" iff remaining empty, else "partial". + custom: label_qs_dispatch +formatted: + description: "IN Report" + custom: label_qs_format diff --git a/spec/labels/SQ.yaml b/spec/labels/SQ.yaml new file mode 100644 index 0000000..326b78d --- /dev/null +++ b/spec/labels/SQ.yaml @@ -0,0 +1,23 @@ +spec_version: "1" +plugin: + name: Label_SQ + type: text + decode_level: MESSAGE +qualifiers: + labels: ["SQ"] +parse: + # Whole-plugin escape hatch: + # - preamble=text[0:4]; version=Number(text[1:2]); network=text[3:4] + # - When version === 2, run the v2 regex + # /0(\d)X(?\w)(?\w{3})(?\w{4})(?\d) + # (?\d+)(?[NS])(?\d+)(?[EW])V(?\d+)\/.*/ + # and populate groundStation + vdlFrequency. + # - Network formatted text from A/S/other -> ARINC/SITA/Unknown. + # - Many optional formatted.items only emitted when their respective fields exist + # (groundStation, iataCode, icaoCode, coordinates.latitude, airport, vdlFrequency). + # The optional-emit logic, custom NETT/VER/GNDSTN/IATA/ICAO/COORD/APT/VDLFRQ item types + # all live in escape hatches for byte-for-byte parity. + custom: label_sq_dispatch +formatted: + description: "Ground Station Squitter" + custom: label_sq_format diff --git a/spec/shared/README.md b/spec/shared/README.md new file mode 100644 index 0000000..fa65b79 --- /dev/null +++ b/spec/shared/README.md @@ -0,0 +1,21 @@ +# Shared spec data + +Cross-cutting data referenced by plugin specs and emitted into per-language runtimes by codegen. Avoids duplicating canonical constants (CRC tables, decoder semantics, coordinate format definitions) across three language implementations. + +| File | Purpose | +|-----------------------|-----------------------------------------------------------------------------------| +| `crc_tables.yaml` | CRC polynomial lookup tables. Codegen emits identical tables into each runtime. | +| `decode_fns.yaml` | Canonical semantics of named decode functions (`coordinate`, `timestamp_hhmmss`, etc.). Per-language runtimes implement these. | +| `coord_formats.yaml` | Coordinate format definitions (single-axis vs combined, prefix chars, divisor) | + +Files in `spec/shared/` are **excluded** from plugin codegen — they are data, not plugins. The codegen tool reads them when emitting runtime constants tables. + +Plugin spec files MAY reference shared data: + +```yaml +decode: + fn: coordinate + args: { format_ref: shared/coord_formats.yaml#NS_DDDDD_DIV100 } +``` + +(This `format_ref` indirection is planned for ADS v1.1; v1 uses inline args.) diff --git a/spec/shared/crc_tables.yaml b/spec/shared/crc_tables.yaml new file mode 100644 index 0000000..5e4a9dd --- /dev/null +++ b/spec/shared/crc_tables.yaml @@ -0,0 +1,50 @@ +# CRC polynomial lookup tables for ACARS checksum validation. +# Codegen emits these as immutable arrays into each language runtime: +# runtimes/typescript/crc_tables.ts +# runtimes/rust/src/crc_tables.rs +# runtimes/c/src/crc_tables.c + crc_tables.h +# +# Source values are extracted from acars-decoder-typescript/lib/utils/arinc_702_helper.ts. + +version: 1 + +# CRC-16 (IBM SDLC, reversed). Used by ARINC 702 H1 message checksums. +crc16_ibm_sdlc_rev: + polynomial: 0x8408 + init: 0xFFFF + refin: true + refout: true + xorout: 0xFFFF + width: 16 + # Table values populated during runtime helper migration (task #19). + table: [] + +# CRC-16 (GENIBUS). Used as fallback for ARINC 702 H1 checksum mismatch. +crc16_genibus: + polynomial: 0x1021 + init: 0xFFFF + refin: false + refout: false + xorout: 0xFFFF + width: 16 + table: [] + +# CRC-32 used by MIAM PDU v1 (ARINC 665). +arinc_665_crc32: + polynomial: 0x04C11DB7 + init: 0xFFFFFFFF + refin: false + refout: false + xorout: 0xFFFFFFFF + width: 32 + table: [] + +# CRC-16 used by MIAM PDU v2 (ARINC 6). +arinc_6_crc16: + polynomial: 0x8005 + init: 0x0000 + refin: false + refout: false + xorout: 0x0000 + width: 16 + table: [] diff --git a/spec/shared/decode_fns.yaml b/spec/shared/decode_fns.yaml new file mode 100644 index 0000000..a301bbb --- /dev/null +++ b/spec/shared/decode_fns.yaml @@ -0,0 +1,94 @@ +# Canonical semantics of named decode functions referenced from plugin specs. +# Implementations live in runtimes//. This file documents the contract. +# +# Source-of-truth behavior matches acars-decoder-typescript/lib/utils/. + +version: 1 + +coordinate: + description: "Single-axis or combined latitude/longitude parser." + args: + style: { type: enum, values: [single_axis, combined] } + axis: { type: enum, values: [latitude, longitude], when: { style: single_axis } } + prefix_chars: { type: array, items: string } + digits: { type: integer, min: 1 } + divisor: { type: number, min: 1 } + format: { type: string, when: { style: combined } } + reference_ts: "acars-decoder-typescript/lib/utils/coordinate_utils.ts" + reference_ts_inline_examples: + - "Label_10_POS.ts uses inline ((sign) * Number(rest)) / 100" + +coordinate_decimal_minutes: + description: "DDMM.M decimal-minute coordinate parser." + args: + style: { type: enum, values: [combined] } + format: { type: string } + reference_ts: "acars-decoder-typescript/lib/utils/coordinate_utils.ts#decodeStringCoordinatesDecimalMinutes" + +integer: + description: "Parse a substring as integer, optional multiplier." + args: + substring_start: { type: integer, default: 0 } + substring_length: { type: integer, optional: true } + multiplier: { type: number, default: 1 } + reference_ts: "Number(s)" + +float: + description: "Parse string as float." + args: + multiplier: { type: number, default: 1 } + reference_ts: "Number(s)" + +string: + description: "Pass through as string (with optional trim/case)." + args: + trim: { type: boolean, default: false } + +trim: + description: "String.trim()" + +uppercase: { description: "String.toUpperCase()" } +lowercase: { description: "String.toLowerCase()" } + +timestamp_hhmmss: + description: "HHMMSS → seconds since midnight. Optional 'append' for shorter inputs." + args: + append: { type: string, optional: true, description: "appended to input before parsing (e.g. '00' to convert HHMM to HHMM00)" } + reference_ts: "acars-decoder-typescript/lib/DateTimeUtils.ts#convertHHMMSSToTod" + +timestamp_ddhhmm: + description: "DDHHMM → epoch-ish." + reference_ts: "acars-decoder-typescript/lib/DateTimeUtils.ts#convertDateTimeToEpoch" + +callsign: + description: "Normalize callsign string." + +tail_number: + description: "Normalize tail number; optional character stripping." + args: + strip_chars: { type: string, optional: true, description: "characters to remove from the result" } + +flight_number: + description: "Normalize flight number string." + +airport: + description: "ICAO airport code (4 letters typically)." + +altitude_feet: + description: "Number with feet semantics." + +speed_knots: { description: "Number with knots semantics." } +heading_degrees: { description: "Number with degrees semantics." } +fuel_kg: { description: "Number with kg semantics." } +fuel_lb: { description: "Number with lb semantics." } + +hex_to_bytes: + description: "Hex string → byte array." + +json_parse: + description: "JSON.parse()." + +custom: + description: "Per-language native function. See ESCAPE_HATCHES.md." + args: + custom: { type: identifier, required: true } diff --git a/spec/wildcards/arinc_702.yaml b/spec/wildcards/arinc_702.yaml new file mode 100644 index 0000000..645d4c9 --- /dev/null +++ b/spec/wildcards/arinc_702.yaml @@ -0,0 +1,15 @@ +spec_version: "1" +plugin: + name: ARINC_702 + type: text + docs: https://github.com/airframesio/acars-message-documentation/blob/main/research/ARINC_702.md + decode_level: MESSAGE +qualifiers: + labels: ["*"] +parse: + # Pure escape hatch: heuristic offset computation, fallback chains, and + # recursive H1 message decoding all live in hand-written per-language code. + custom: arinc_702_dispatch +formatted: + description: "ARINC 702 Message" + custom: arinc_702_format diff --git a/spec/wildcards/cband.yaml b/spec/wildcards/cband.yaml new file mode 100644 index 0000000..1a537c1 --- /dev/null +++ b/spec/wildcards/cband.yaml @@ -0,0 +1,20 @@ +spec_version: "1" +plugin: + name: CBand + type: text + decode_level: MESSAGE +qualifiers: + labels: ["*"] +parse: + # Whole-plugin escape hatch (per ESCAPE_HATCHES.md guidance): + # - Match leading 10-char C-Band header: + # /^(?[A-Z]\d{2}[A-Z])(?[A-Z0-9]{2})(?[0-9]{4})/ + # - If matched, strip first 10 chars, recursively invoke MessageDecoder on the + # remainder with the same label/sublabel, then merge raw/items/remaining and + # prepend flight number "airline + Number(number)". + # - decoder.name becomes "c-band-" + nested decoder name; decode level inherited. + # Recursive MessageDecoder use is explicitly called out as a full escape-hatch case. + custom: cband_dispatch +formatted: + description: "C-Band Message" + custom: cband_format diff --git a/spec/wildcards/label_13_18_slash.yaml b/spec/wildcards/label_13_18_slash.yaml new file mode 100644 index 0000000..392b068 --- /dev/null +++ b/spec/wildcards/label_13_18_slash.yaml @@ -0,0 +1,16 @@ +spec_version: "1" +plugin: + name: Label_13Through18_Slash + type: text + decode_level: PARTIAL +qualifiers: + labels: ["13", "14", "15", "16", "17", "18"] + preambles: ["/"] +parse: + # Pure escape hatch: matches labels 13..18 with different field counts (4 vs 7), + # dispatches to out/off/on/in formatter based on numeric label, line-by-line + # LOC parsing with two DMS variants and signed decimal degrees variant. + custom: label_13_18_slash_decode +formatted: + description: "OOOI Report" + custom: label_13_18_slash_format