From 2a5a00baee6ef532b07d6da940e6e1ce6bbbb09f Mon Sep 17 00:00:00 2001 From: stephenlclarke Date: Wed, 10 Dec 2025 15:59:25 +0000 Subject: [PATCH 01/14] chore: bump to 0.3.0, align layout, and ship pcap2fix in releases --- .github/workflows/rust.yml | 32 ++- Cargo.lock | 92 ++++++- Cargo.toml | 6 +- Makefile | 6 +- README.md | 323 ++++++++++++++++++++---- pcap2fix/Cargo.toml | 18 ++ pcap2fix/src/main.rs | 495 +++++++++++++++++++++++++++++++++++++ src/decoder/display.rs | 91 ++++--- src/decoder/layout.rs | 15 ++ src/decoder/mod.rs | 1 + src/decoder/prettifier.rs | 280 +++++++++++++++++++-- src/decoder/tag_lookup.rs | 340 +++++++++++++++++++++---- src/decoder/validator.rs | 144 ++++++++++- 13 files changed, 1676 insertions(+), 167 deletions(-) create mode 100644 pcap2fix/Cargo.toml create mode 100644 pcap2fix/src/main.rs create mode 100644 src/decoder/layout.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 59bb478..a160e4e 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -120,7 +120,7 @@ jobs: export CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_RUSTFLAGS="-C target-feature=+crt-static" fi cargo fmt --all - cargo build --release --locked --target ${{ matrix.target }} + cargo build --release --locked --workspace --target ${{ matrix.target }} env: RUSTFLAGS: "" - name: Stage artifact @@ -128,11 +128,23 @@ jobs: run: | mkdir -p dist cp "${{ matrix.artifact_path }}" "dist/fixdecoder-${{ matrix.asset_suffix }}${{ matrix.ext }}" + pcap_path="target/${{ matrix.target }}/release/pcap2fix${{ matrix.ext }}" + if [ -f "$pcap_path" ]; then + cp "$pcap_path" "dist/pcap2fix-${{ matrix.asset_suffix }}${{ matrix.ext }}" + else + echo "pcap2fix binary not found at $pcap_path" >&2 + exit 1 + fi - name: Upload artifact uses: actions/upload-artifact@v4 with: name: fixdecoder-${{ matrix.asset_suffix }} path: dist/fixdecoder-${{ matrix.asset_suffix }}${{ matrix.ext }} + - name: Upload pcap2fix artifact + uses: actions/upload-artifact@v4 + with: + name: pcap2fix-${{ matrix.asset_suffix }} + path: dist/pcap2fix-${{ matrix.asset_suffix }}${{ matrix.ext }} quality: name: Lint & Coverage @@ -321,24 +333,26 @@ jobs: - name: Download build artifacts uses: actions/download-artifact@v4 with: - pattern: fixdecoder-* + pattern: "*" path: dist merge-multiple: true - name: Rename artifacts with version run: | ver="${{ steps.version.outputs.version }}" shopt -s nullglob - for f in dist/fixdecoder-*; do + for f in dist/fixdecoder-* dist/pcap2fix-*; do base="${f##*/}" case "$base" in - fixdecoder-*.exe) - suffix="${base#fixdecoder-}" + fixdecoder-*.exe|pcap2fix-*.exe) + prefix="${base%%-*}" + suffix="${base#${prefix}-}" suffix="${suffix%.exe}" - mv "$f" "dist/fixdecoder-${ver}.${suffix}.exe" + mv "$f" "dist/${prefix}-${ver}.${suffix}.exe" ;; - fixdecoder-*) - suffix="${base#fixdecoder-}" - mv "$f" "dist/fixdecoder-${ver}.${suffix}" + fixdecoder-*|pcap2fix-*) + prefix="${base%%-*}" + suffix="${base#${prefix}-}" + mv "$f" "dist/${prefix}-${ver}.${suffix}" ;; esac done diff --git a/Cargo.lock b/Cargo.lock index 6fe0709..2aa74f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -76,6 +76,12 @@ version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "assert_cmd" version = "2.1.1" @@ -164,6 +170,12 @@ dependencies = [ "windows-link", ] +[[package]] +name = "circular" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fc239e0f6cb375d2402d48afb92f76f5404fd1df208a41930ec81eda078bea" + [[package]] name = "clap" version = "4.5.53" @@ -286,6 +298,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "etherparse" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21696e6dfe1057a166a042c6d27b89a46aad2ee1003e6e1e03c49d54fd3270d7" +dependencies = [ + "arrayvec", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -300,7 +321,7 @@ checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" [[package]] name = "fixdecoder" -version = "0.2.1" +version = "0.3.0" dependencies = [ "anyhow", "assert_cmd", @@ -410,6 +431,12 @@ version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "nix" version = "0.30.1" @@ -422,6 +449,16 @@ dependencies = [ "libc", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "normalize-line-endings" version = "0.3.0" @@ -464,6 +501,30 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "pcap-parser" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b79dfb40aef938ed2082c9ae9443f4eba21b79c1a9d6cfa071f5c2bd8d829491" +dependencies = [ + "circular", + "nom", + "rusticata-macros", +] + +[[package]] +name = "pcap2fix" +version = "0.1.0" +dependencies = [ + "anyhow", + "assert_cmd", + "clap", + "etherparse", + "pcap-parser", + "predicates", + "thiserror", +] + [[package]] name = "predicates" version = "3.1.3" @@ -595,6 +656,15 @@ dependencies = [ "semver", ] +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + [[package]] name = "rustix" version = "1.1.2" @@ -702,6 +772,26 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-ident" version = "1.0.22" diff --git a/Cargo.toml b/Cargo.toml index ec03add..92bb7cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fixdecoder" -version = "0.2.1" +version = "0.3.0" edition = "2024" [dependencies] @@ -23,3 +23,7 @@ tempfile = "3.10" [build-dependencies] rustc_version = "0.4" + +[workspace] +members = [".", "pcap2fix"] +resolver = "2" diff --git a/Makefile b/Makefile index d4672c1..1550f3d 100644 --- a/Makefile +++ b/Makefile @@ -13,10 +13,10 @@ prepare: @cargo run --quiet --bin generate_sensitive_tags >/dev/null build: prepare - @bash -lc 'source $(CI_SCRIPT) && ensure_build_metadata && cargo fmt --all && cargo build' + @bash -lc 'source $(CI_SCRIPT) && ensure_build_metadata && cargo fmt --all && cargo build --workspace' build-release: prepare - @bash -lc 'source $(CI_SCRIPT) && ensure_build_metadata && cargo fmt --all && cargo build --release' + @bash -lc 'source $(CI_SCRIPT) && ensure_build_metadata && cargo fmt --all && cargo build --workspace --release' @python3 ci/update_readme.py .PHONY: update-readme @@ -28,7 +28,7 @@ scan: prepare source $(CI_SCRIPT) && \ ensure_build_metadata && \ cargo fmt --all --check && \ - cargo clippy --all-targets -- -D warnings && \ + cargo clippy --workspace --all-targets -- -D warnings && \ mkdir -p target/coverage && \ if command -v cargo-audit >/dev/null 2>&1; then \ echo "Running cargo-audit (text output)"; \ diff --git a/README.md b/README.md index ab931ca..c96c1b2 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ The utility behaves like the `cat` utility in `Linux`, except as it reads the in ```bash -fixdecoder v0.2.1 (branch:develop, commit:02b044e) [rust:1.91.1] +fixdecoder v0.2.1 (branch:develop, commit:20e9407) [rust:1.91.1] FIX protocol utility - Dictionary lookup, file decoder, validator & prettifier Usage: fixdecoder [OPTIONS] [FILE]... @@ -197,7 +197,9 @@ Resolving deltas: 100% (201/201), done. Then build it. Debug version with clippy and code coverage ```bash -❯ make build scan coverage +❯ make clean build scan coverage build-release + Cleaning [ ] 0.00% + Removed 29068 files, 2.8GiB total >> Ensuring Rust toolchain and coverage tools @@ -205,92 +207,303 @@ Then build it. Debug version with clippy and code coverage info: component 'llvm-tools' for target 'aarch64-apple-darwin' is up to date >> Ensuring FIX XML specs are present - Compiling fixdecoder v0.2.0 (/Users/sclarke/github/fixdecoder2) -warning: fixdecoder@0.2.0: Building fixdecoder 0.2.0 (branch:develop, commit:d7de4f4) [rust:1.91.1] - Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.92s - Compiling fixdecoder v0.2.0 (/Users/sclarke/github/fixdecoder2) -warning: fixdecoder@0.2.0: Building fixdecoder 0.2.0 (branch:develop, commit:d7de4f4) [rust:1.91.1] - Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.71s + Compiling fixdecoder v0.2.1 (/Users/sclarke/github/fixdecoder2) +warning: fixdecoder@0.2.1: Building fixdecoder v0.2.1 (branch:develop, commit:02b044e) [rust:1.91.1] + Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.59s + Checking memchr v2.7.6 + Checking bitflags v2.10.0 + Checking regex-syntax v0.8.8 + Checking anstyle v1.0.13 + Checking cfg-if v1.0.4 + Checking libc v0.2.178 + Compiling rustix v1.1.2 + Checking crossbeam-utils v0.8.21 + Checking num-traits v0.2.19 + Checking utf8parse v0.2.2 + Checking objc2-encode v4.1.0 + Checking colorchoice v1.0.4 + Checking anstyle-query v1.1.5 + Checking anstyle-parse v0.2.7 + Checking is_terminal_polyfill v1.70.2 + Checking serde_core v1.0.228 + Checking clap_lex v0.7.6 + Checking core-foundation-sys v0.8.7 + Checking strsim v0.11.1 + Checking anstream v0.6.21 + Checking objc2 v0.6.3 + Checking aho-corasick v1.1.4 + Checking crossbeam-epoch v0.9.18 + Compiling fixdecoder v0.2.1 (/Users/sclarke/github/fixdecoder2) + Compiling getrandom v0.3.4 + Checking iana-time-zone v0.1.64 + Checking clap_builder v4.5.53 + Checking predicates-core v1.0.9 + Checking once_cell v1.21.3 + Checking crossbeam-deque v0.8.6 + Checking either v1.15.0 + Checking chrono v0.4.42 + Checking rayon-core v1.13.0 + Checking float-cmp v0.10.0 + Checking anyhow v1.0.100 + Checking errno v0.3.14 + Checking nix v0.30.1 + Checking rayon v1.11.0 + Checking roxmltree v0.21.1 + Compiling assert_cmd v2.1.1 + Checking block2 v0.6.2 + Checking normalize-line-endings v0.3.0 + Checking termtree v0.5.1 + Checking difflib v0.4.0 + Checking wait-timeout v0.2.1 + Checking regex-automata v0.4.13 + Checking predicates-tree v1.0.12 + Checking fastrand v2.3.0 + Checking dispatch2 v0.3.0 + Checking clap v4.5.53 + Checking serde v1.0.228 + Checking ctrlc v3.5.1 +warning: fixdecoder@0.2.1: Building fixdecoder v0.2.1 (branch:develop, commit:02b044e) [rust:1.91.1] + Checking terminal_size v0.4.3 + Checking tempfile v3.23.0 + Checking quick-xml v0.36.2 + Checking regex v1.12.2 + Checking bstr v1.12.1 + Checking predicates v3.1.3 + Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.87s +Running cargo-audit (text output) Fetching advisory database from `https://github.com/RustSec/advisory-db.git` Loaded 884 security advisories (from /Users/sclarke/.cargo/advisory-db) Updating crates.io index - Scanning Cargo.lock for vulnerabilities (110 crate dependencies) -Crate: atty -Version: 0.2.14 -Warning: unmaintained -Title: `atty` is unmaintained -Date: 2024-09-25 -ID: RUSTSEC-2024-0375 -URL: https://rustsec.org/advisories/RUSTSEC-2024-0375 -Dependency tree: -atty 0.2.14 -└── fixdecoder 0.2.0 - -Crate: atty -Version: 0.2.14 -Warning: unsound -Title: Potential unaligned read -Date: 2021-07-04 -ID: RUSTSEC-2021-0145 -URL: https://rustsec.org/advisories/RUSTSEC-2021-0145 - -warning: 2 allowed warnings found + Scanning Cargo.lock for vulnerabilities (105 crate dependencies) +Running cargo-audit (JSON) → target/coverage/rustsec.json +Converting RustSec report to Sonar generic issues (target/coverage/sonar-generic-issues.json) info: cargo-llvm-cov currently setting cfg(coverage); you can opt-out it by passing --no-cfg-coverage - Compiling fixdecoder v0.2.0 (/Users/sclarke/github/fixdecoder2) -warning: fixdecoder@0.2.0: Building fixdecoder 0.2.0 (branch:develop, commit:d7de4f4) [rust:1.91.1] - Finished `test` profile [unoptimized + debuginfo] target(s) in 1.98s - Running unittests src/main.rs (target/llvm-cov-target/debug/deps/fixdecoder-f3e1ddf320589917) - -running 27 tests + Compiling libc v0.2.178 + Compiling proc-macro2 v1.0.103 + Compiling unicode-ident v1.0.22 + Compiling memchr v2.7.6 + Compiling quote v1.0.42 + Compiling autocfg v1.5.0 + Compiling bitflags v2.10.0 + Compiling crossbeam-utils v0.8.21 + Compiling objc2 v0.6.3 + Compiling anstyle v1.0.13 + Compiling regex-syntax v0.8.8 + Compiling cfg-if v1.0.4 + Compiling utf8parse v0.2.2 + Compiling rustix v1.1.2 + Compiling cfg_aliases v0.2.1 + Compiling objc2-encode v4.1.0 + Compiling serde_core v1.0.228 + Compiling nix v0.30.1 + Compiling num-traits v0.2.19 + Compiling aho-corasick v1.1.4 + Compiling anstyle-parse v0.2.7 + Compiling is_terminal_polyfill v1.70.2 + Compiling serde v1.0.228 + Compiling semver v1.0.27 + Compiling rayon-core v1.13.0 + Compiling anstyle-query v1.1.5 + Compiling colorchoice v1.0.4 + Compiling anstream v0.6.21 + Compiling rustc_version v0.4.1 + Compiling regex-automata v0.4.13 + Compiling syn v2.0.111 + Compiling block2 v0.6.2 + Compiling clap_lex v0.7.6 + Compiling crossbeam-epoch v0.9.18 + Compiling anyhow v1.0.100 + Compiling crossbeam-deque v0.8.6 + Compiling heck v0.5.0 + Compiling core-foundation-sys v0.8.7 + Compiling strsim v0.11.1 + Compiling iana-time-zone v0.1.64 + Compiling fixdecoder v0.2.1 (/Users/sclarke/github/fixdecoder2) + Compiling clap_builder v4.5.53 + Compiling errno v0.3.14 + Compiling dispatch2 v0.3.0 + Compiling either v1.15.0 + Compiling getrandom v0.3.4 + Compiling predicates-core v1.0.9 + Compiling once_cell v1.21.3 + Compiling regex v1.12.2 + Compiling chrono v0.4.42 + Compiling rayon v1.11.0 + Compiling float-cmp v0.10.0 + Compiling terminal_size v0.4.3 + Compiling roxmltree v0.21.1 + Compiling difflib v0.4.0 + Compiling termtree v0.5.1 + Compiling assert_cmd v2.1.1 + Compiling ctrlc v3.5.1 + Compiling serde_derive v1.0.228 + Compiling clap_derive v4.5.49 + Compiling normalize-line-endings v0.3.0 + Compiling predicates v3.1.3 + Compiling predicates-tree v1.0.12 + Compiling bstr v1.12.1 + Compiling wait-timeout v0.2.1 + Compiling fastrand v2.3.0 + Compiling tempfile v3.23.0 +warning: fixdecoder@0.2.1: Building fixdecoder v0.2.1 (branch:develop, commit:02b044e) [rust:1.91.1] + Compiling clap v4.5.53 + Compiling quick-xml v0.36.2 + Finished `test` profile [unoptimized + debuginfo] target(s) in 7.42s + Running unittests src/main.rs (target/llvm-cov-target/debug/deps/fixdecoder-835cbd2851e2e0ab) + +running 72 tests +test decoder::display::tests::layout_stats_produces_layout ... ok +test decoder::display::tests::pad_ansi_extends_to_requested_width ... ok +test decoder::display::tests::print_enum_outputs_coloured_enum ... ok +test decoder::display::tests::collect_sorted_values_orders_by_enum ... ok +test decoder::display::tests::collect_group_layout_counts_nested_components ... ok +test decoder::display::tests::print_field_renders_required_indicator ... ok +test decoder::display::tests::tag_and_message_cells_include_expected_text ... ok +test decoder::display::tests::compute_values_layout_uses_max_entry ... ok +test decoder::display::tests::compute_message_layout_counts_header_and_trailer ... ok +test decoder::display::tests::terminal_width_is_positive ... ok +test decoder::display::tests::visible_len_ignores_escape_sequences ... ok +test decoder::display::tests::print_enum_columns_respects_layout_columns ... ok +test decoder::display::tests::visible_width_ignores_ansi_sequences ... ok +test decoder::display::tests::render_component_prints_matching_msg_type_enum_only ... ok +test decoder::display::tests::render_message_includes_header_and_trailer ... ok +test decoder::display::tests::write_with_padding_adds_spaces ... ok +test decoder::display::tests::cached_layout_is_reused_for_component ... ok +test decoder::prettifier::tests::read_line_with_follow_returns_zero_on_eof ... ok +test decoder::prettifier::tests::trim_line_endings_strips_crlf ... ok +test decoder::prettifier::tests::header_and_trailer_are_repositioned_when_out_of_place ... ok test decoder::schema::tests::parse_simple_vec ... ok -test decoder::schema::tests::parse_message_fields ... ok test decoder::schema::tests::parse_message_with_components ... ok -test decoder::tag_lookup::tests::detects_schema_from_default_appl_ver_id ... ok -test decoder::summary::tests::terminal_status_from_non_exec_report_updates_header ... ok +test decoder::schema::tests::parse_message_fields ... ok +test decoder::summary::tests::build_summary_row_includes_bn_headers ... ok +test decoder::summary::tests::display_instrument_formats_side_and_symbol ... ok +test decoder::summary::tests::date_diff_days_returns_none_when_incomplete ... ok +test decoder::summary::tests::extract_date_part_handles_timestamp ... ok +test decoder::summary::tests::flow_label_skips_leading_unknown ... ok +test decoder::summary::tests::preferred_settlement_date_prefers_primary_then_secondary ... ok test decoder::summary::tests::links_orders_using_order_id_and_cl_ord_id ... ok -test decoder::summary::tests::render_outputs_state_headline ... ok +test decoder::summary::tests::render_record_header_includes_id_and_instrument ... ok +test decoder::summary::tests::resolve_key_prefers_alias_then_ids ... ok +test decoder::summary::tests::state_path_deduplicates_consecutive_states ... ok +test decoder::summary::tests::terminal_status_from_non_exec_report_updates_header ... ok +test decoder::tag_lookup::tests::detects_schema_from_default_appl_ver_id ... ok +test decoder::summary::tests::collects_states_for_single_order ... ok +test decoder::prettifier::tests::prettify_orders_without_msg_type_header_first ... ok +test decoder::prettifier::tests::build_tag_order_respects_annotations_and_trailer ... ok +test decoder::prettifier::tests::prettify_includes_missing_tag_annotations_once ... ok test decoder::validator::tests::allows_repeating_group_tags ... ok test decoder::validator::tests::detects_body_length_mismatch ... ok test decoder::validator::tests::detects_checksum_mismatch ... ok test decoder::validator::tests::missing_msg_type_still_reports_length_and_tag ... ok test fix::obfuscator::tests::reset_starts_aliases_over ... ok +test tests::add_flag_args_sets_flags ... ok +test tests::add_entity_arg_defaults_to_true_when_missing_value ... ok +test tests::dictionary_key_includes_service_pack ... ok +test tests::dictionary_source_prefers_custom_entry ... ok +test tests::final_exit_code_marks_interrupt ... ok +test tests::build_cli_parses_follow_and_summary_flags ... ok +test tests::normalise_fix_key_handles_variants ... ok test tests::invalid_fix_version_errors ... ok +test tests::parse_colour_rejects_invalid ... ok +test tests::parse_colour_recognises_yes_no ... ok +test tests::parse_delimiter_accepts_hex ... ok +test tests::parse_delimiter_accepts_literal ... ok +test tests::parse_delimiter_rejects_empty ... ok +test tests::resolve_input_files_defaults_to_stdin ... ok +test tests::resolve_input_files_preserves_inputs ... ok test tests::valid_fix_version_passes ... ok test tests::version_str_is_cached ... ok test tests::version_string_matches_components ... ok test decoder::prettifier::tests::validation_inserts_missing_tags ... ok -test decoder::prettifier::tests::validation_skips_valid_messages ... ok -test decoder::prettifier::tests::prettify_orders_without_msg_type_header_first ... ok test decoder::prettifier::tests::validation_only_outputs_invalid_messages ... ok -test decoder::prettifier::tests::header_and_trailer_are_repositioned_when_out_of_place ... ok -test decoder::prettifier::tests::prettify_includes_missing_tag_annotations_once ... ok +test decoder::prettifier::tests::validation_skips_valid_messages ... ok +test decoder::summary::tests::absorb_fields_sets_block_notice_specifics ... ok test decoder::summary::tests::bn_message_sets_state_and_spot_price ... ok -test decoder::summary::tests::collects_states_for_single_order ... ok +test decoder::summary::tests::render_outputs_state_headline ... ok +test decoder::summary::tests::absorb_fields_sets_core_values ... ok test decoder::tag_lookup::tests::load_dictionary_respects_override_key ... ok test decoder::tag_lookup::tests::override_uses_fallback_dictionary_for_missing_tags ... ok test decoder::tag_lookup::tests::warns_and_falls_back_on_unknown_override ... ok -test result: ok. 27 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.01s +test result: ok. 72 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.99s - Running unittests src/bin/generate_sensitive_tags.rs (target/llvm-cov-target/debug/deps/generate_sensitive_tags-1dc73cddc48f727b) + Running unittests src/bin/generate_sensitive_tags.rs (target/llvm-cov-target/debug/deps/generate_sensitive_tags-bcdddcbc14128afb) running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s - Running tests/cli.rs (target/llvm-cov-target/debug/deps/cli-44905c680ca51135) + Running tests/cli.rs (target/llvm-cov-target/debug/deps/cli-94133c405ce02098) running 5 tests +test decodes_message_from_file_path ... ok +test summary_mode_outputs_order_summary ... ok test decodes_single_message_from_stdin ... ok test validation_reports_missing_fields ... ok -test summary_mode_outputs_order_summary ... ok -test decodes_message_from_file_path ... ok test override_is_honoured_with_fallback ... ok -test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.78s +test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.67s Finished report saved to target/coverage/coverage.xml + Compiling proc-macro2 v1.0.103 + Compiling unicode-ident v1.0.22 + Compiling quote v1.0.42 + Compiling libc v0.2.178 + Compiling crossbeam-utils v0.8.21 + Compiling bitflags v2.10.0 + Compiling objc2 v0.6.3 + Compiling memchr v2.7.6 + Compiling serde_core v1.0.228 + Compiling objc2-encode v4.1.0 + Compiling cfg_aliases v0.2.1 + Compiling autocfg v1.5.0 + Compiling utf8parse v0.2.2 + Compiling nix v0.30.1 + Compiling anstyle-parse v0.2.7 + Compiling semver v1.0.27 + Compiling num-traits v0.2.19 + Compiling is_terminal_polyfill v1.70.2 + Compiling colorchoice v1.0.4 + Compiling anstyle v1.0.13 + Compiling serde v1.0.228 + Compiling anstyle-query v1.1.5 + Compiling rayon-core v1.13.0 + Compiling rustix v1.1.2 + Compiling anstream v0.6.21 + Compiling rustc_version v0.4.1 + Compiling crossbeam-epoch v0.9.18 + Compiling crossbeam-deque v0.8.6 + Compiling aho-corasick v1.1.4 + Compiling core-foundation-sys v0.8.7 + Compiling regex-syntax v0.8.8 + Compiling clap_lex v0.7.6 + Compiling block2 v0.6.2 + Compiling strsim v0.11.1 + Compiling anyhow v1.0.100 + Compiling cfg-if v1.0.4 + Compiling heck v0.5.0 + Compiling syn v2.0.111 + Compiling regex-automata v0.4.13 + Compiling clap_builder v4.5.53 + Compiling errno v0.3.14 + Compiling dispatch2 v0.3.0 + Compiling iana-time-zone v0.1.64 + Compiling fixdecoder v0.2.1 (/Users/sclarke/github/fixdecoder2) + Compiling either v1.15.0 + Compiling rayon v1.11.0 + Compiling chrono v0.4.42 +warning: fixdecoder@0.2.1: Building fixdecoder v0.2.1 (branch:develop, commit:02b044e) [rust:1.91.1] + Compiling terminal_size v0.4.3 + Compiling ctrlc v3.5.1 + Compiling roxmltree v0.21.1 + Compiling once_cell v1.21.3 + Compiling regex v1.12.2 + Compiling serde_derive v1.0.228 + Compiling clap_derive v4.5.49 + Compiling clap v4.5.53 + Compiling quick-xml v0.36.2 + Finished `release` profile [optimized] target(s) in 9.17s ``` Build the release version @@ -317,6 +530,16 @@ fixdecoder 0.2.0 (branch:develop, commit:7a2d535) [rust:1.91.1] git clone git@github.com:stephenlclarke/fixdecoder2.git ``` +# PCAP → FIX filter (`pcap2fix`) + +The workspace includes a helper that reassembles TCP streams from PCAP data and emits FIX messages to stdout so you can pipe them into `fixdecoder`. + +- Build: `cargo build -p pcap2fix` (also built via `make build`). +- Offline: `pcap2fix --input capture.pcap | fixdecoder` +- Live (needs tcpdump/dumpcap): `tcpdump -i eth0 -w - 'tcp port 9876' | pcap2fix --port 9876 | fixdecoder` +- Delimiter defaults to SOH; override with `--delimiter`. +- Flow buffers are capped (size + idle timeout) to avoid runaway memory during long captures. + # Technical Notes on the use of the `--summary` flag - As messages stream by, the decoder builds one “record” per order (keyed by OrderID/ClOrdID/OrigClOrdID). diff --git a/pcap2fix/Cargo.toml b/pcap2fix/Cargo.toml new file mode 100644 index 0000000..e1c015b --- /dev/null +++ b/pcap2fix/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "pcap2fix" +version = "0.1.0" +edition = "2021" +license = "AGPL-3.0-only" +description = "PCAP to FIX stream filter: reassembles TCP payloads and emits FIX messages" +repository = "https://github.com/stephenlclarke/fixdecoder2" + +[dependencies] +anyhow = "1.0" +clap = { version = "4.5", features = ["derive"] } +pcap-parser = { version = "0.14", features = ["data"] } +etherparse = "0.15" +thiserror = "1.0" + +[dev-dependencies] +assert_cmd = "2.0" +predicates = "3.1" diff --git a/pcap2fix/src/main.rs b/pcap2fix/src/main.rs new file mode 100644 index 0000000..4e857d7 --- /dev/null +++ b/pcap2fix/src/main.rs @@ -0,0 +1,495 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// Minimal PCAP-to-FIX filter: reads PCAP (file or stdin), reassembles TCP +// streams, and emits FIX messages separated by the chosen delimiter. + +use anyhow::{anyhow, Context, Result}; +use clap::Parser; +use etherparse::{NetSlice, SlicedPacket, TransportSlice}; +use pcap_parser::data::{get_packetdata, PacketData, ETHERTYPE_IPV4, ETHERTYPE_IPV6}; +use pcap_parser::pcapng::Block; +use pcap_parser::traits::{PcapNGPacketBlock, PcapReaderIterator}; +use pcap_parser::{create_reader, Linktype, PcapBlockOwned}; +use std::collections::HashMap; +use std::fs::File; +use std::io::{self, Write}; +use std::net::Ipv4Addr; +use std::time::{Duration, Instant}; +use thiserror::Error; + +#[derive(Parser, Debug)] +#[command(author, version, about)] +struct Args { + /// PCAP file path or "-" for stdin + #[arg(short, long, default_value = "-")] + input: String, + /// TCP port filter (optional). If omitted, all ports are considered. + #[arg(short = 'p', long)] + port: Option, + /// Message delimiter. Accepts "SOH", literal char, or hex like \x01. + #[arg(short = 'd', long, default_value = "SOH")] + delimiter: String, + /// Max bytes to buffer per flow before eviction + #[arg(long, default_value = "1048576")] + max_flow_bytes: usize, + /// Idle timeout for flows (seconds) + #[arg(long, default_value = "60")] + idle_timeout: u64, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +struct FlowKey { + src: Ipv4Addr, + dst: Ipv4Addr, + sport: u16, + dport: u16, + // direction handled by seq tracking in FlowState +} + +#[derive(Debug)] +struct FlowState { + next_seq: Option, + buffer: Vec, + last_seen: Instant, +} + +impl Default for FlowState { + fn default() -> Self { + FlowState { + next_seq: None, + buffer: Vec::new(), + last_seen: Instant::now(), + } + } +} + +#[derive(Error, Debug)] +enum ReassemblyError { + #[error("flow exceeded max buffer")] + Overflow, +} + +fn main() -> Result<()> { + let args = Args::parse(); + let delimiter = parse_delimiter(&args.delimiter)?; + let mut reader = open_reader(&args.input)?; + + let mut flows: HashMap = HashMap::new(); + let idle = Duration::from_secs(args.idle_timeout); + let mut stdout = io::BufWriter::new(io::stdout().lock()); + let mut scratch = Vec::new(); + let mut legacy_linktype = None; + let mut idb_linktypes: HashMap = HashMap::new(); + let mut next_if_id: u32 = 0; + + loop { + match reader.next() { + Ok((offset, block)) => { + { + match block { + PcapBlockOwned::LegacyHeader(hdr) => { + legacy_linktype = Some(hdr.network); + } + PcapBlockOwned::Legacy(b) => { + let linktype = legacy_linktype.unwrap_or(Linktype::ETHERNET); + if let Some(packet) = + get_packetdata(b.data, linktype, b.caplen as usize) + { + if let Err(err) = handle_packet_data( + packet, + args.port, + delimiter, + args.max_flow_bytes, + &mut flows, + &mut stdout, + ) { + eprintln!("warn: skipping packet: {err}"); + } + } + } + PcapBlockOwned::NG(block) => match block { + Block::SectionHeader(_) => { + idb_linktypes.clear(); + next_if_id = 0; + } + Block::InterfaceDescription(idb) => { + idb_linktypes.insert(next_if_id, idb.linktype); + next_if_id += 1; + } + Block::EnhancedPacket(epb) => { + if let Some(linktype) = idb_linktypes.get(&epb.if_id) { + if let Some(packet) = get_packetdata( + epb.packet_data(), + *linktype, + epb.caplen as usize, + ) { + if let Err(err) = handle_packet_data( + packet, + args.port, + delimiter, + args.max_flow_bytes, + &mut flows, + &mut stdout, + ) { + eprintln!("warn: skipping packet: {err}"); + } + } + } + } + Block::SimplePacket(spb) => { + if let Some(linktype) = idb_linktypes.get(&0) { + if let Some(packet) = get_packetdata( + spb.packet_data(), + *linktype, + spb.origlen as usize, + ) { + if let Err(err) = handle_packet_data( + packet, + args.port, + delimiter, + args.max_flow_bytes, + &mut flows, + &mut stdout, + ) { + eprintln!("warn: skipping packet: {err}"); + } + } + } + } + _ => {} + }, + } + } + reader.consume(offset); + evict_idle(&mut flows, idle); + } + Err(pcap_parser::PcapError::Eof) => break, + Err(pcap_parser::PcapError::Incomplete) => { + // need more data + reader + .refill() + .map_err(|e| anyhow!("failed to refill reader: {e}"))?; + } + Err(e) => return Err(anyhow!("pcap parse error: {e}")), + } + } + + // flush any trailing message fragments (best effort) + for flow in flows.values_mut() { + flush_complete_messages(&mut flow.buffer, delimiter, &mut scratch, &mut stdout)?; + } + stdout.flush()?; + Ok(()) +} + +fn open_reader(path: &str) -> Result> { + if path == "-" { + let stdin = io::stdin(); + create_reader(65536, stdin).map_err(|e| anyhow!("failed to create reader: {e}")) + } else { + let file = File::open(path).with_context(|| format!("open pcap {path}"))?; + create_reader(65536, file).map_err(|e| anyhow!("failed to create reader: {e}")) + } +} + +fn parse_delimiter(raw: &str) -> Result { + if raw.eq_ignore_ascii_case("SOH") { + return Ok(0x01); + } + if let Some(hex) = raw.strip_prefix("\\x").or_else(|| raw.strip_prefix("0x")) { + let val = + u8::from_str_radix(hex, 16).map_err(|_| anyhow!("invalid hex delimiter: {raw}"))?; + return Ok(val); + } + if raw.len() == 1 { + return Ok(raw.as_bytes()[0]); + } + Err(anyhow!( + "delimiter must be SOH, hex (\\x01), or single byte" + )) +} + +fn handle_packet_data( + packet: PacketData<'_>, + port_filter: Option, + delimiter: u8, + max_flow_bytes: usize, + flows: &mut HashMap, + out: &mut W, +) -> Result<()> { + match packet { + PacketData::L2(data) => { + let sliced = SlicedPacket::from_ethernet(data).map_err(|e| anyhow!("parse: {e:?}"))?; + handle_sliced_packet(sliced, port_filter, delimiter, max_flow_bytes, flows, out) + } + PacketData::L3(ethertype, data) + if ethertype == ETHERTYPE_IPV4 || ethertype == ETHERTYPE_IPV6 => + { + let sliced = SlicedPacket::from_ip(data).map_err(|e| anyhow!("parse: {e:?}"))?; + handle_sliced_packet(sliced, port_filter, delimiter, max_flow_bytes, flows, out) + } + _ => Ok(()), + } +} + +fn handle_sliced_packet( + sliced: SlicedPacket<'_>, + port_filter: Option, + delimiter: u8, + max_flow_bytes: usize, + flows: &mut HashMap, + out: &mut W, +) -> Result<()> { + let (ip, tcp) = match (sliced.net, sliced.transport) { + (Some(NetSlice::Ipv4(ip)), Some(TransportSlice::Tcp(tcp))) => (ip, tcp), + _ => return Ok(()), + }; + if let Some(p) = port_filter { + if tcp.source_port() != p && tcp.destination_port() != p { + return Ok(()); + } + } + + let payload = tcp.payload(); + if payload.is_empty() { + return Ok(()); + } + + let header = ip.header(); + let key = FlowKey { + src: header.source_addr(), + dst: header.destination_addr(), + sport: tcp.source_port(), + dport: tcp.destination_port(), + }; + + let seq = tcp.sequence_number(); + let flow = flows.entry(key).or_default(); + flow.last_seen = Instant::now(); + + reassemble_and_emit(flow, seq, payload, delimiter, max_flow_bytes, out) +} + +fn reassemble_and_emit( + flow: &mut FlowState, + seq: u32, + payload: &[u8], + delimiter: u8, + max_flow_bytes: usize, + out: &mut W, +) -> Result<()> { + let expected = flow.next_seq.unwrap_or(seq); + + if seq == expected { + flow.buffer.extend_from_slice(payload); + flow.next_seq = Some(seq.wrapping_add(payload.len() as u32)); + } else if seq > expected { + // out-of-order future segment: skip for now + return Ok(()); + } else { + // retransmit or overlap + let end = seq.wrapping_add(payload.len() as u32); + if end <= expected { + // fully duplicate + return Ok(()); + } + let overlap = (expected - seq) as usize; + flow.buffer.extend_from_slice(&payload[overlap..]); + flow.next_seq = Some(expected.wrapping_add(payload.len() as u32 - overlap as u32)); + } + + if flow.buffer.len() > max_flow_bytes { + flow.buffer.clear(); + return Err(ReassemblyError::Overflow.into()); + } + + let mut scratch = Vec::new(); + flush_complete_messages(&mut flow.buffer, delimiter, &mut scratch, out)?; + Ok(()) +} + +fn flush_complete_messages( + buffer: &mut Vec, + delimiter: u8, + scratch: &mut Vec, + out: &mut W, +) -> Result<()> { + let mut cursor = 0; + while let Some(rel_end) = find_message_end(&buffer[cursor..], delimiter) { + let end = cursor + rel_end; + scratch.clear(); + scratch.extend_from_slice(&buffer[cursor..=end]); + scratch.push(b'\n'); // newline so each FIX message prints on its own line + out.write_all(scratch)?; + cursor = end + 1; + } + if cursor > 0 { + buffer.drain(0..cursor); + } + Ok(()) +} + +fn find_message_end(buffer: &[u8], delimiter: u8) -> Option { + // Need at least "8=..|9=..|" plus checksum ("10=000|") + if buffer.len() < 16 { + return None; + } + let begin_end = buffer.iter().position(|b| *b == delimiter)?; + let body_len_field_start = begin_end + 1; + let body_len_end = body_len_field_start + + buffer[body_len_field_start..] + .iter() + .position(|b| *b == delimiter)?; // include delimiter + if body_len_end <= body_len_field_start + 1 { + return None; + } + if !buffer[body_len_field_start..].starts_with(b"9=") { + return None; + } + let body_len_bytes = &buffer[body_len_field_start + 2..body_len_end]; + let body_len: usize = parse_decimal(body_len_bytes)?; + let body_start = body_len_end + 1; + let body_end = body_start.checked_add(body_len)?; + // checksum starts immediately after body + if body_end + 7 > buffer.len() { + return None; + } + if !buffer.get(body_end..)?.starts_with(b"10=") { + return None; + } + let checksum_val = buffer.get(body_end + 3..body_end + 6)?; + if checksum_val.iter().any(|b| !b.is_ascii_digit()) { + return None; + } + let end_delim_idx = body_end + 6; + if *buffer.get(end_delim_idx)? != delimiter { + return None; + } + Some(end_delim_idx) +} + +fn parse_decimal(bytes: &[u8]) -> Option { + let mut val: usize = 0; + for b in bytes { + if !b.is_ascii_digit() { + return None; + } + val = val.checked_mul(10)?; + val = val.checked_add((b - b'0') as usize)?; + } + Some(val) +} +fn evict_idle(flows: &mut HashMap, idle: Duration) { + let now = Instant::now(); + flows.retain(|_, state| now.duration_since(state.last_seen) < idle); +} + +#[cfg(test)] +mod tests { + use super::*; + + fn build_fix_message(body: &str, delim: u8) -> Vec { + let mut msg = Vec::new(); + let d = delim as char; + let body_len = body.len(); + msg.extend_from_slice(format!("8=FIX.4.4{d}9={body_len}{d}").as_bytes()); + msg.extend_from_slice(body.as_bytes()); + let checksum: u8 = msg.iter().fold(0u16, |acc, b| acc + *b as u16) as u8; + msg.extend_from_slice(format!("10={:03}{}", checksum, d).as_bytes()); + msg + } + + #[test] + fn parse_delimiter_variants() { + assert_eq!(parse_delimiter("SOH").unwrap(), 0x01); + assert_eq!(parse_delimiter("\\x02").unwrap(), 0x02); + assert_eq!(parse_delimiter("0x03").unwrap(), 0x03); + assert_eq!(parse_delimiter("|").unwrap(), b'|'); + } + + #[test] + fn reassembly_appends_in_order() { + let mut flow = FlowState::default(); + let mut out = Vec::new(); + let message = build_fix_message("35=0\u{0001}", 0x01); + let (part1, rest) = message.split_at(10); + let (part2, part3) = rest.split_at(8); + + reassemble_and_emit(&mut flow, 10, part1, 0x01, 1024, &mut out).unwrap(); + reassemble_and_emit( + &mut flow, + 10 + part1.len() as u32, + part2, + 0x01, + 1024, + &mut out, + ) + .unwrap(); + assert!(out.is_empty(), "no complete message yet"); + reassemble_and_emit( + &mut flow, + 10 + (part1.len() + part2.len()) as u32, + part3, + 0x01, + 1024, + &mut out, + ) + .unwrap(); + let text = String::from_utf8(out).unwrap(); + assert!(text.contains("8=FIX.4.4")); + assert!(text.ends_with('\n')); + } + + #[test] + fn flushes_full_messages_only() { + let mut buf = build_fix_message("35=0\u{0001}", 0x01); + buf.extend_from_slice(b"extra"); + let mut out = Vec::new(); + let mut scratch = Vec::new(); + flush_complete_messages(&mut buf, 0x01, &mut scratch, &mut out).unwrap(); + let mut expected = build_fix_message("35=0\u{0001}", 0x01); + expected.push(b'\n'); + assert_eq!(out, expected); + assert_eq!(buf.as_slice(), b"extra"); + } + + #[test] + fn retransmit_is_ignored() { + let mut flow = FlowState::default(); + let mut out = Vec::new(); + reassemble_and_emit(&mut flow, 1, b"ABC", b'|', 1024, &mut out).unwrap(); + reassemble_and_emit(&mut flow, 1, b"ABC", b'|', 1024, &mut out).unwrap(); + assert!(flow.buffer.starts_with(b"ABC")); + } + + #[test] + fn out_of_order_future_segment_is_skipped() { + let mut flow = FlowState::default(); + let mut out = Vec::new(); + reassemble_and_emit(&mut flow, 5, b"first", b'|', 1024, &mut out).unwrap(); + // future seq skipped + reassemble_and_emit(&mut flow, 20, b"second", b'|', 1024, &mut out).unwrap(); + assert_eq!(flow.buffer, b"first"); + } + + #[test] + fn flush_complete_messages_emits_and_retains_tail() { + let mut buf = Vec::new(); + let msg1 = build_fix_message("35=0|", b'|'); + let msg2 = build_fix_message("35=1|", b'|'); + buf.extend_from_slice(&msg1); + buf.extend_from_slice(&msg2); + buf.extend_from_slice(b"partial"); + let mut scratch = Vec::new(); + let mut out = Vec::new(); + flush_complete_messages(&mut buf, b'|', &mut scratch, &mut out).unwrap(); + let expected_out = { + let mut v = msg1.clone(); + v.push(b'\n'); + v.extend_from_slice(&msg2); + v.push(b'\n'); + v + }; + assert_eq!(out, expected_out); + assert_eq!(buf, b"partial"); + } +} diff --git a/src/decoder/display.rs b/src/decoder/display.rs index d26c2ed..482b7f8 100644 --- a/src/decoder/display.rs +++ b/src/decoder/display.rs @@ -7,6 +7,7 @@ //! The module-level comment keeps the tone informal yet informative. use crate::decoder::colours::{ColourPalette, palette}; +use crate::decoder::layout::{NEST_INDENT, TAG_WIDTH}; use crate::decoder::schema::{ ComponentNode, Field, FieldNode, GroupNode, MessageNode, SchemaTree, Value, }; @@ -148,7 +149,7 @@ pub(crate) fn pad_ansi(text: &str, width: usize) -> String { /// Tiny helper that implements `Display` for indentation without building /// temporary `String`s. #[derive(Clone, Copy)] -struct Indent(usize); +pub(crate) struct Indent(usize); impl fmt::Display for Indent { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -156,7 +157,7 @@ impl fmt::Display for Indent { } } -fn indent(level: usize) -> Indent { +pub(crate) fn indent(level: usize) -> Indent { Indent(level) } @@ -274,6 +275,20 @@ mod tests { }); let aux_field = sample_field_node(false); + let group_count_field = Arc::new(Field { + name: "Nested".into(), + number: 200, + field_type: "NUMINGROUP".into(), + values: Vec::new(), + values_wrapper: ValuesWrapper::default(), + }); + let allocs_count_field = Arc::new(Field { + name: "Allocs".into(), + number: 201, + field_type: "NUMINGROUP".into(), + values: Vec::new(), + values_wrapper: ValuesWrapper::default(), + }); let group_field = sample_field_node(true); let component = ComponentNode { @@ -330,6 +345,8 @@ mod tests { let mut fields = BTreeMap::new(); fields.insert(msg_type_field.name.clone(), msg_type_field.clone()); fields.insert(aux_field.field.name.clone(), aux_field.field.clone()); + fields.insert(group_count_field.name.clone(), group_count_field.clone()); + fields.insert(allocs_count_field.name.clone(), allocs_count_field.clone()); let mut components = BTreeMap::new(); components.insert(header.name.clone(), header); @@ -410,8 +427,8 @@ mod tests { assert!(s.contains("Header")); assert!(s.contains("Message: ")); assert!(s.contains("Body")); - assert!(s.contains("Group:")); assert!(s.contains("Allocs")); + assert!(s.contains("201")); // group count tag number assert!(s.contains("Trailer")); } @@ -513,7 +530,7 @@ fn print_field( ) -> io::Result<()> { writeln!( out, - "{}{}{:4}{}: {}{}{} ({}{}{}){}", + "{}{}{:width$}{}: {}{}{} ({}{}{}){}", indent(indent_level), colours.tag, field.field.number, @@ -524,7 +541,8 @@ fn print_field( colours.value, field.field.field_type, colours.reset, - format_required(field.required, colours) + format_required(field.required, colours), + width = TAG_WIDTH ) } @@ -829,12 +847,17 @@ impl<'a, 'b, W: Write> RenderContext<'a, 'b, W> { colours.reset )?; - self.print_field_collection(&msg.fields, indent_level, shared_style)?; + self.print_field_collection(&msg.fields, indent_level + 2, shared_style)?; for component in &msg.components { - self.render_component_with_style(Some(msg), component, indent_level, shared_style)?; + self.render_component_with_style( + Some(msg), + component, + indent_level + NEST_INDENT, + shared_style, + )?; } for group in &msg.groups { - self.render_group_with_style(group, indent_level, shared_style)?; + self.render_group_with_style(group, indent_level + NEST_INDENT, shared_style)?; } if include_trailer && let Some(trailer) = self.schema.components.get("Trailer") { @@ -877,18 +900,18 @@ impl<'a, 'b, W: Write> RenderContext<'a, 'b, W> { )?; for field in &component.fields { - print_field(self.out, field, indent_level + 4, colours)?; + print_field(self.out, field, indent_level + NEST_INDENT, colours)?; if self.verbose { - self.print_enums_for_field(field, msg, indent_level + 6, style)?; + self.print_enums_for_field(field, msg, indent_level + NEST_INDENT + 2, style)?; } } for sub in &component.components { - self.render_component_with_style(msg, sub, indent_level + 4, style)?; + self.render_component_with_style(msg, sub, indent_level + NEST_INDENT, style)?; } for group in &component.groups { - self.render_group_with_style(group, indent_level + 4, style)?; + self.render_group_with_style(group, indent_level + NEST_INDENT, style)?; } Ok(()) } @@ -911,24 +934,32 @@ impl<'a, 'b, W: Write> RenderContext<'a, 'b, W> { style }; let colours = style.colours(); - writeln!( - self.out, - "{}Group: {}{}{}{}", - indent(indent_level), - colours.name, - group.name, - colours.reset, - format_required(group.required, colours) - )?; + if let Some(count_field) = self.schema.fields.get(&group.name) { + let count_node = FieldNode { + required: group.required, + field: count_field.clone(), + }; + print_field(self.out, &count_node, indent_level, colours)?; + } else { + writeln!( + self.out, + "{}Group: {}{}{}{}", + indent(indent_level), + colours.name, + group.name, + colours.reset, + format_required(group.required, colours) + )?; + } - self.print_field_collection(&group.fields, indent_level + 4, style)?; + self.print_field_collection(&group.fields, indent_level + NEST_INDENT, style)?; for component in &group.components { - self.render_component_with_style(None, component, indent_level + 4, style)?; + self.render_component_with_style(None, component, indent_level + NEST_INDENT, style)?; } for sub_group in &group.groups { - self.render_group_with_style(sub_group, indent_level + 4, style)?; + self.render_group_with_style(sub_group, indent_level + NEST_INDENT, style)?; } Ok(()) } @@ -1265,22 +1296,22 @@ fn collect_component_layout( indent_level: usize, stats: &mut LayoutStats, ) { - collect_fields_layout(&component.fields, indent_level + 6, stats); + collect_fields_layout(&component.fields, indent_level + NEST_INDENT + 2, stats); for sub in &component.components { - collect_component_layout(sub, indent_level + 4, stats); + collect_component_layout(sub, indent_level + NEST_INDENT, stats); } for group in &component.groups { - collect_group_layout(group, indent_level + 4, stats); + collect_group_layout(group, indent_level + NEST_INDENT, stats); } } fn collect_group_layout(group: &GroupNode, indent_level: usize, stats: &mut LayoutStats) { - collect_fields_layout(&group.fields, indent_level + 6, stats); + collect_fields_layout(&group.fields, indent_level + NEST_INDENT + 2, stats); for component in &group.components { - collect_component_layout(component, indent_level + 4, stats); + collect_component_layout(component, indent_level + NEST_INDENT, stats); } for sub in &group.groups { - collect_group_layout(sub, indent_level + 4, stats); + collect_group_layout(sub, indent_level + NEST_INDENT, stats); } } diff --git a/src/decoder/layout.rs b/src/decoder/layout.rs new file mode 100644 index 0000000..d281316 --- /dev/null +++ b/src/decoder/layout.rs @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// SPDX-FileCopyrightText: 2025 Steve Clarke - https://xyzzy.tools +// +// Shared layout constants for FIX rendering across prettifier and schema display. + +/// Width used when printing tag numbers (right-aligned). +pub const TAG_WIDTH: usize = 4; +/// Base indent applied to top-level prettifier fields. +pub const BASE_INDENT: usize = 2; +/// Indent increment for nested components/groups. +pub const NEST_INDENT: usize = 4; +/// Column offset to align group separators under the first parenthesis of the field name. +pub const NAME_TEXT_OFFSET: usize = TAG_WIDTH + 1; +/// Indent applied to entries inside a repeating group (relative to the group's own indent). +pub const ENTRY_FIELD_INDENT: usize = TAG_WIDTH + 1; diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 43f3bb1..9d4fd7f 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -4,6 +4,7 @@ pub mod colours; pub mod display; pub mod fixparser; +pub mod layout; pub mod prettifier; pub mod schema; pub mod summary; diff --git a/src/decoder/prettifier.rs b/src/decoder/prettifier.rs index d6d2f8a..3b01023 100644 --- a/src/decoder/prettifier.rs +++ b/src/decoder/prettifier.rs @@ -2,12 +2,16 @@ // SPDX-FileCopyrightText: 2025 Steve Clarke - https://xyzzy.tools use crate::decoder::colours::{disable_colours, palette}; -use crate::decoder::display::{pad_ansi, terminal_width, visible_width}; +use crate::decoder::display::{indent, pad_ansi, terminal_width, visible_width}; use crate::decoder::fixparser::{FieldValue, parse_fix}; +use crate::decoder::layout::{BASE_INDENT, ENTRY_FIELD_INDENT, NAME_TEXT_OFFSET}; use crate::decoder::summary::OrderSummary; #[cfg(test)] use crate::decoder::tag_lookup::MessageDef; -use crate::decoder::tag_lookup::{FixTagLookup, load_dictionary_with_override}; +use crate::decoder::tag_lookup::{ + FixTagLookup, GroupSpec as MessageDefGroupSpec, MessageDef as LookupMessageDef, + load_dictionary_with_override, +}; use crate::decoder::validator; use crate::fix; use once_cell::sync::Lazy; @@ -65,33 +69,186 @@ pub fn prettify_with_report( let fields = parse_fix(msg); let annotations = report.map(|r| &r.tag_errors); - let mut tag_buckets = bucket_fields(&fields); - let ordered_tags = build_tag_order(&fields, dict, annotations); + let mut seen_tags = HashSet::new(); + let msg_def = fields + .iter() + .find(|f| f.tag == 35) + .and_then(|f| dict.message_def(&f.value)); + let renderer = msg_def.map(|def| GroupRenderer { + dict, + annotations, + colours: &colours, + msg_def: def, + fields: &fields, + }); - for tag in ordered_tags { - if let Some(bucket) = tag_buckets.get_mut(&tag) { - while let Some(field) = bucket.pop_front() { - write_field_line(&mut output, dict, field, annotations, &colours); - } - } else if let Some(errs) = annotations - .and_then(|ann| ann.get(&tag)) - .filter(|errs| !errs.is_empty()) + let mut idx = 0; + while idx < fields.len() { + let field = &fields[idx]; + seen_tags.insert(field.tag); + if let Some(render) = renderer.as_ref() + && let Some(spec) = render.msg_def.groups.get(&field.tag) { - write_missing_line(&mut output, dict, tag, errs, &colours); + let consumed = render.render_group(&mut output, idx, spec, BASE_INDENT); + idx += consumed.max(1); + } else { + write_field_line(&mut output, dict, field, annotations, &colours, BASE_INDENT); + idx += 1; } } - // Emit any remaining fields that were not covered by ordered_tags. - for bucket in tag_buckets.values_mut() { - while let Some(field) = bucket.pop_front() { - write_field_line(&mut output, dict, field, annotations, &colours); + if let Some(ann) = annotations { + for (tag, errs) in ann { + if seen_tags.contains(tag) || errs.is_empty() { + continue; + } + write_missing_line(&mut output, dict, *tag, errs, &colours); } } output } +struct GroupRenderer<'a> { + dict: &'a FixTagLookup, + annotations: Option<&'a std::collections::HashMap>>, + colours: &'a crate::decoder::colours::ColourPalette, + msg_def: &'a LookupMessageDef, + fields: &'a [FieldValue], +} + +impl<'a> GroupRenderer<'a> { + fn write_field(&self, output: &mut String, field: &FieldValue, indent_spaces: usize) { + write_field_line( + output, + self.dict, + field, + self.annotations, + self.colours, + indent_spaces, + ); + } + + fn render_group( + &self, + output: &mut String, + start_idx: usize, + spec: &MessageDefGroupSpec, + indent_spaces: usize, + ) -> usize { + let mut consumed = 0usize; + let mut entries = 0usize; + let expected = self.fields[start_idx] + .value + .parse::() + .unwrap_or_default(); + self.write_field(output, &self.fields[start_idx], indent_spaces); + let mut idx = start_idx + 1; + while idx < self.fields.len() && entries < expected { + if self.fields[idx].tag != spec.delim { + if self.msg_def.group_membership.get(&self.fields[idx].tag) == Some(&spec.count_tag) + { + self.write_field( + output, + &self.fields[idx], + indent_spaces + ENTRY_FIELD_INDENT, + ); + idx += 1; + consumed = idx - start_idx; + continue; + } + break; + } + let entry_consumed = + self.render_group_entry(output, idx, spec, indent_spaces, entries + 1); + idx += entry_consumed; + entries += 1; + consumed = idx - start_idx; + } + + if entries != expected { + if let Some(errs) = self + .annotations + .and_then(|ann| ann.get(&spec.count_tag)) + .filter(|errs| !errs.is_empty()) + { + write_missing_line(output, self.dict, spec.count_tag, errs, self.colours); + } else { + output.push_str(&format!( + "{}{}Warning:{} NumInGroup {} ({}) declared {}, found {}\n", + indent(indent_spaces + 2), + self.colours.error, + self.colours.reset, + spec.count_tag, + spec.name, + expected, + entries + )); + } + } + consumed + } + + fn render_group_entry( + &self, + output: &mut String, + start_idx: usize, + spec: &MessageDefGroupSpec, + indent_spaces: usize, + entry_idx: usize, + ) -> usize { + let entry_label = format!("Group {}", entry_idx); + let dash_count = 60usize.saturating_sub(entry_label.len()); + let dashes = "-".repeat(dash_count); + let dash_start_col = indent_spaces + NAME_TEXT_OFFSET; + let label_indent = dash_start_col.saturating_sub(entry_label.len()); + output.push_str(&format!( + "{}{}{}{}{}\n", + indent(label_indent), + entry_label, + self.colours.error, + dashes, + self.colours.reset + )); + let mut idx = start_idx; + let mut last_pos = -1isize; + while idx < self.fields.len() { + let tag = self.fields[idx].tag; + if tag == spec.delim && idx != start_idx { + break; + } + if let Some(nested) = spec.nested.get(&tag) { + let nested_consumed = + self.render_group(output, idx, nested, indent_spaces + ENTRY_FIELD_INDENT); + idx += nested_consumed.max(1); + continue; + } + if let Some(pos) = spec.entry_pos.get(&tag).copied() { + if (pos as isize) < last_pos + && let Some(errs) = self + .annotations + .and_then(|ann| ann.get(&tag)) + .filter(|errs| !errs.is_empty()) + { + write_missing_line(output, self.dict, tag, errs, self.colours); + } + last_pos = pos as isize; + self.write_field( + output, + &self.fields[idx], + indent_spaces + ENTRY_FIELD_INDENT, + ); + idx += 1; + } else { + break; + } + } + idx - start_idx + } +} + /// Bucket each field by tag so repeat occurrences can be emitted in order. +#[allow(dead_code)] fn bucket_fields( fields: &[FieldValue], ) -> std::collections::HashMap> { @@ -106,6 +263,7 @@ fn bucket_fields( /// Build the emission order of tags using the message definition when known, falling back /// to a header-first order when MsgType is absent, and appending tags referenced in /// validation annotations. +#[allow(dead_code)] fn build_tag_order( fields: &[FieldValue], dict: &FixTagLookup, @@ -144,10 +302,12 @@ fn build_tag_order( final_order } +#[allow(dead_code)] fn canonical_header_tags() -> &'static [u32; 7] { &[8u32, 9, 35, 49, 56, 34, 52] } +#[allow(dead_code)] fn trailer_tags(dict: &FixTagLookup) -> Vec { let order = dict.trailer_tags(); if order.is_empty() { @@ -157,6 +317,7 @@ fn trailer_tags(dict: &FixTagLookup) -> Vec { } } +#[allow(dead_code)] fn collect_trailer_tags(fields: &[FieldValue], trailer_set: &HashSet) -> HashSet { fields .iter() @@ -173,6 +334,7 @@ fn message_field_order(fields: &[FieldValue], dict: &FixTagLookup) -> Option Vec { let mut base = vec![8, 9, 35]; for f in fields { @@ -183,11 +345,13 @@ fn fallback_field_order(fields: &[FieldValue]) -> Vec { base } +#[allow(dead_code)] fn dedup_order(order: Vec) -> Vec { let mut seen = HashSet::new(); order.into_iter().filter(|tag| seen.insert(*tag)).collect() } +#[allow(dead_code)] fn base_message_order( fields: &[FieldValue], dict: &FixTagLookup, @@ -207,6 +371,7 @@ fn base_message_order( deduped } +#[allow(dead_code)] fn append_annotation_tags( final_order: &mut Vec, annotations: &std::collections::HashMap>, @@ -228,6 +393,7 @@ fn append_annotation_tags( } } +#[allow(dead_code)] fn append_message_fields( fields: &[FieldValue], final_order: &mut Vec, @@ -246,6 +412,7 @@ fn append_message_fields( } } +#[allow(dead_code)] fn append_trailer_tags( final_order: &mut Vec, trailer_order: &[u32], @@ -329,6 +496,7 @@ fn write_field_line( field: &crate::decoder::fixparser::FieldValue, annotations: Option<&std::collections::HashMap>>, colours: &crate::decoder::colours::ColourPalette, + indent_spaces: usize, ) { let tag_errors: Option<&Vec> = annotations.and_then(|ann| ann.get(&field.tag)); let tag_colour = if tag_errors.is_some() { @@ -346,7 +514,8 @@ fn write_field_line( let name_section = format!("{}({}){}", colours.name, name_coloured, colours.reset); let desc = dict.enum_description(field.tag, &field.value); output.push_str(&format!( - " {}{:4}{} {}: {}{}{}", + "{}{}{:4}{} {}: {}{}{}", + indent(indent_spaces), tag_colour, field.tag, colours.reset, @@ -386,7 +555,8 @@ fn write_missing_line( errors.join(", ") }; output.push_str(&format!( - " {}{:4}{} ({}{}{}): {}{}{}\n", + "{}{}{:4}{} ({}{}{}): {}{}{}\n", + indent(BASE_INDENT), colours.error, tag, colours.reset, @@ -797,6 +967,8 @@ fn test_lookup_with_order(field_order: Vec) -> FixTagLookup { _msg_type: "X".to_string(), field_order, required: Vec::new(), + groups: HashMap::new(), + group_membership: HashMap::new(), }, ); FixTagLookup::new_for_tests(messages) @@ -805,6 +977,7 @@ fn test_lookup_with_order(field_order: Vec) -> FixTagLookup { #[cfg(test)] mod tests { use super::*; + use crate::decoder::schema::FixDictionary; use crate::decoder::tag_lookup::load_dictionary; use crate::decoder::validator; use crate::fix; @@ -816,6 +989,73 @@ mod tests { static TEST_GUARD: once_cell::sync::Lazy> = once_cell::sync::Lazy::new(|| Mutex::new(())); + fn small_group_lookup() -> FixTagLookup { + let xml = r#" + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+"#; + let dict = FixDictionary::from_xml(xml).expect("tiny dictionary parses"); + FixTagLookup::from_dictionary(&dict, "TEST") + } + + #[test] + fn prettify_aligns_group_entries_without_header() { + let _lock = TEST_GUARD.lock().unwrap(); + disable_output_colours(); + let dict = small_group_lookup(); + let msg = format!( + "8=FIX.4.4{SOH}35=W{SOH}268=2{SOH}269=0{SOH}270=12.34{SOH}269=1{SOH}270=56.78{SOH}10=000{SOH}" + ); + let rendered = prettify_with_report(&msg, &dict, None); + assert!( + !rendered.contains("Group: NoMDEntries"), + "group header line should be omitted: {rendered}" + ); + let count_line = rendered + .lines() + .find(|l| l.contains("NoMDEntries")) + .expect("count tag line present"); + let group_line = rendered + .lines() + .find(|l| l.contains("Group 1")) + .expect("group entry label present"); + let paren_col = count_line.find('(').expect("open paren present"); + let dash_col = group_line.find('-').expect("dashes present"); + assert_eq!( + dash_col, paren_col, + "group separator should align under '('" + ); + } + #[test] fn validation_only_outputs_invalid_messages() { let _lock = TEST_GUARD.lock().unwrap(); @@ -989,6 +1229,8 @@ mod tests { _msg_type: "X".to_string(), field_order: vec![8, 9, 35, 55], required: Vec::new(), + groups: HashMap::new(), + group_membership: HashMap::new(), }, ); let dict = FixTagLookup::new_for_tests(messages); diff --git a/src/decoder/tag_lookup.rs b/src/decoder/tag_lookup.rs index c44863e..e799454 100644 --- a/src/decoder/tag_lookup.rs +++ b/src/decoder/tag_lookup.rs @@ -14,6 +14,19 @@ pub struct MessageDef { pub _msg_type: String, pub field_order: Vec, pub required: Vec, + pub groups: HashMap, + pub group_membership: HashMap, +} + +#[derive(Debug, Clone)] +pub struct GroupSpec { + pub name: String, + pub count_tag: u32, + pub delim: u32, + pub entry_order: Vec, + pub entry_pos: HashMap, + pub entry_tag_set: HashSet, + pub nested: HashMap, } #[derive(Debug, Default, Clone)] @@ -24,6 +37,7 @@ pub struct FixTagLookup { field_types: Arc>, messages: Arc>, repeatable_tags: Arc>, + #[allow(dead_code)] trailer_order: Arc>, fallback: Option>, fallback_role: Option, @@ -77,7 +91,7 @@ impl FixTagLookup { component_map.insert(trailer.name.clone(), trailer); let messages = build_message_defs(&dict.messages, &component_map, &name_to_tag); - let repeatable_tags = collect_repeatable_tags(&dict.messages, &component_map, &name_to_tag); + let repeatable_tags = collect_repeatable_from_specs(&messages); let mut trailer_order = Vec::new(); let mut stack = Vec::new(); append_component_fields( @@ -325,10 +339,7 @@ pub fn load_dictionary_with_override(msg: &str, override_key: Option<&str>) -> A if Arc::ptr_eq(&dict, &fallback) { return dict; } - let mut merged = (*dict).clone(); - merged.fallback = Some(fallback); - merged.fallback_role = Some(FallbackKind::DetectedOverride); - let merged = Arc::new(merged); + let merged = merge_with_fallback(&dict, fallback, FallbackKind::DetectedOverride); if let Ok(mut guard) = LOOKUPS.write() { guard.insert(combo_key, merged.clone()); } @@ -347,6 +358,17 @@ fn warn_override_miss() { OVERRIDE_MISS.store(true, Ordering::Relaxed); } +fn merge_with_fallback( + primary: &Arc, + fallback: Arc, + role: FallbackKind, +) -> Arc { + let mut merged: FixTagLookup = (**primary).clone(); + merged.fallback = Some(fallback); + merged.fallback_role = Some(role); + Arc::new(merged) +} + #[cfg(test)] pub fn reset_override_warn() { OVERRIDE_MISS.store(false, Ordering::Relaxed); @@ -400,6 +422,7 @@ fn build_message_defs( let mut map = HashMap::new(); for msg in &messages.items { let (field_order, required) = expand_message_fields(msg, components, name_to_tag, true); + let (groups, membership) = collect_group_specs(&msg.groups, components, name_to_tag); map.insert( msg.msg_type.clone(), MessageDef { @@ -407,6 +430,8 @@ fn build_message_defs( _msg_type: msg.msg_type.clone(), field_order, required, + groups, + group_membership: membership, }, ); } @@ -536,44 +561,90 @@ fn dedupe(values: &mut Vec) { values.retain(|v| seen.insert(*v)); } -fn collect_repeatable_tags( - messages: &MessageContainer, +fn collect_group_specs( + groups: &[GroupDef], components: &HashMap, name_to_tag: &HashMap, -) -> HashSet { - let mut repeatable = HashSet::new(); - let mut component_stack = HashSet::new(); - - for message in &messages.items { - for component in &message.components { - collect_component_repeatables( - &component.name, - components, - name_to_tag, - &mut repeatable, - &mut component_stack, - ); +) -> (HashMap, HashMap) { + let mut specs = HashMap::new(); + let mut membership = HashMap::new(); + let mut stack = HashSet::new(); + for group in groups { + if let Some(spec) = build_group_spec(group, components, name_to_tag, &mut stack) { + membership.extend(collect_memberships(&spec, spec.count_tag)); + specs.insert(spec.count_tag, spec); } - for group in &message.groups { - collect_group_repeatables( - group, - components, - name_to_tag, - &mut repeatable, - &mut component_stack, - ); + } + // also scan groups reachable via components referenced in the message + for comp in components.values() { + for group in &comp.groups { + if let Some(spec) = build_group_spec(group, components, name_to_tag, &mut stack) { + membership.extend(collect_memberships(&spec, spec.count_tag)); + specs.entry(spec.count_tag).or_insert(spec); + } } } + (specs, membership) +} - repeatable +fn build_group_spec( + group: &GroupDef, + components: &HashMap, + name_to_tag: &HashMap, + stack: &mut HashSet, +) -> Option { + let count_tag = *name_to_tag.get(&group.name)?; + let delim = group + .fields + .first() + .and_then(|f| name_to_tag.get(&f.name)) + .copied() + .unwrap_or(count_tag); + let mut order = Vec::new(); + let mut required = Vec::new(); + append_field_refs(&group.fields, name_to_tag, &mut order, &mut required); + + let mut nested = HashMap::new(); + for comp in &group.components { + append_component_fields_for_spec( + &comp.name, + components, + name_to_tag, + stack, + &mut order, + &mut required, + &mut nested, + ); + } + for sub in &group.groups { + if let Some(spec) = build_group_spec(sub, components, name_to_tag, stack) { + order.push(spec.count_tag); + nested.insert(spec.count_tag, spec); + } + } + + dedupe(&mut order); + let entry_tag_set: HashSet = order.iter().copied().collect(); + let entry_pos: HashMap = order.iter().enumerate().map(|(i, t)| (*t, i)).collect(); + Some(GroupSpec { + name: group.name.clone(), + count_tag, + delim, + entry_order: order, + entry_pos, + entry_tag_set, + nested, + }) } -fn collect_component_repeatables( +fn append_component_fields_for_spec( name: &str, components: &HashMap, name_to_tag: &HashMap, - repeatable: &mut HashSet, stack: &mut HashSet, + order: &mut Vec, + required: &mut Vec, + nested: &mut HashMap, ) { if !stack.insert(name.to_string()) { return; @@ -583,47 +654,166 @@ fn collect_component_repeatables( return; }; - for group in &comp.groups { - collect_group_repeatables(group, components, name_to_tag, repeatable, stack); + append_field_refs(&comp.fields, name_to_tag, order, required); + for sub_comp in &comp.components { + append_component_fields_for_spec( + &sub_comp.name, + components, + name_to_tag, + stack, + order, + required, + nested, + ); } - for child in &comp.components { - collect_component_repeatables(&child.name, components, name_to_tag, repeatable, stack); + for group in &comp.groups { + if let Some(spec) = build_group_spec(group, components, name_to_tag, stack) { + order.push(spec.count_tag); + nested.insert(spec.count_tag, spec); + } } stack.remove(name); } -fn collect_group_repeatables( - group: &GroupDef, - components: &HashMap, - name_to_tag: &HashMap, - repeatable: &mut HashSet, - stack: &mut HashSet, -) { - if let Some(tag) = name_to_tag.get(&group.name) { - repeatable.insert(*tag); +fn collect_memberships(spec: &GroupSpec, owner: u32) -> HashMap { + let mut map = HashMap::new(); + for tag in &spec.entry_tag_set { + map.insert(*tag, owner); } - for field in &group.fields { - if let Some(tag) = name_to_tag.get(&field.name) { - repeatable.insert(*tag); - } + for nested in spec.nested.values() { + map.insert(nested.count_tag, nested.count_tag); + map.extend(collect_memberships(nested, nested.count_tag)); } - for comp in &group.components { - collect_component_repeatables(&comp.name, components, name_to_tag, repeatable, stack); + map +} + +fn collect_repeatable_from_specs(messages: &HashMap) -> HashSet { + fn walk(spec: &GroupSpec, acc: &mut HashSet) { + acc.insert(spec.count_tag); + for tag in &spec.entry_tag_set { + acc.insert(*tag); + } + for nested in spec.nested.values() { + walk(nested, acc); + } } - for sub in &group.groups { - collect_group_repeatables(sub, components, name_to_tag, repeatable, stack); + + let mut repeatable = HashSet::new(); + for msg in messages.values() { + for spec in msg.groups.values() { + walk(spec, &mut repeatable); + } } + repeatable } #[cfg(test)] mod tests { use super::*; + use crate::decoder::schema::FixDictionary; use once_cell::sync::Lazy; - use std::sync::Mutex; + use std::sync::{Arc, Mutex}; static LOOKUP_TEST_GUARD: Lazy> = Lazy::new(|| Mutex::new(())); + struct LookupCacheGuard { + originals: Vec<(String, Option>)>, + } + + impl LookupCacheGuard { + fn new(keys: &[&str]) -> Self { + let mut originals = Vec::new(); + if let Ok(guard) = LOOKUPS.read() { + for key in keys { + originals.push(((*key).to_string(), guard.get(*key).cloned())); + } + } else { + for key in keys { + originals.push(((*key).to_string(), None)); + } + } + Self { originals } + } + } + + impl Drop for LookupCacheGuard { + fn drop(&mut self) { + if let Ok(mut guard) = LOOKUPS.write() { + for (key, original) in &self.originals { + match original { + Some(existing) => { + guard.insert(key.clone(), existing.clone()); + } + None => { + guard.remove(key); + } + } + } + } + for (key, _) in &self.originals { + clear_override_cache_for(key); + } + } + } + + fn small_override_dictionary() -> FixDictionary { + let xml = r#" + +
+ +
+ + + + + + + + + + + + + + + + +
+"#; + FixDictionary::from_xml(xml).expect("override test dictionary parses") + } + + fn small_detected_dictionary() -> FixDictionary { + let xml = r#" + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+"#; + FixDictionary::from_xml(xml).expect("detected test dictionary parses") + } + #[test] fn detects_schema_from_default_appl_ver_id() { let _lock = LOOKUP_TEST_GUARD.lock().unwrap(); @@ -661,7 +851,12 @@ mod tests { #[test] fn override_uses_fallback_dictionary_for_missing_tags() { let _lock = LOOKUP_TEST_GUARD.lock().unwrap(); + let _cache_guard = LookupCacheGuard::new(&["FIX44", "FIX50SP2"]); reset_override_warn(); + register_dictionary("FIX44", &small_override_dictionary()); + register_dictionary("FIX50SP2", &small_detected_dictionary()); + clear_override_cache_for("FIX44"); + clear_override_cache_for("FIX50SP2"); let msg = "8=FIXT.1.1\u{0001}35=0\u{0001}1128=9\u{0001}10=000\u{0001}"; let dict = load_dictionary_with_override(msg, Some("FIX44")); assert_eq!( @@ -674,4 +869,43 @@ mod tests { "successful fallback should not trigger override warning flag" ); } + + #[test] + fn repeatable_tags_include_nested_groups() { + let _lock = LOOKUP_TEST_GUARD.lock().unwrap(); + let xml = r#" + +
+ + + + + + + + + + + + + + + + + + + + + + + +
+"#; + let dict = FixDictionary::from_xml(xml).expect("dictionary parses"); + let lookup = FixTagLookup::from_dictionary(&dict, "TEST"); + assert!(lookup.is_repeatable(900), "outer group count tag tracked"); + assert!(lookup.is_repeatable(901), "outer field repeatable"); + assert!(lookup.is_repeatable(910), "nested group count tag tracked"); + assert!(lookup.is_repeatable(911), "nested field repeatable"); + } } diff --git a/src/decoder/validator.rs b/src/decoder/validator.rs index d9ffeb6..30585ae 100644 --- a/src/decoder/validator.rs +++ b/src/decoder/validator.rs @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: 2025 Steve Clarke - https://xyzzy.tools use crate::decoder::fixparser::{FieldValue, parse_fix}; -use crate::decoder::tag_lookup::{FixTagLookup, MessageDef}; +use crate::decoder::tag_lookup::{FixTagLookup, GroupSpec as MessageDefGroupSpec, MessageDef}; use chrono::{NaiveDate, NaiveDateTime, NaiveTime}; use once_cell::sync::Lazy; use regex::Regex; @@ -55,6 +55,12 @@ pub fn validate_fix_message(msg: &str, dict: &FixTagLookup) -> ValidationReport &msg_def.field_order, &mut tag_errors, )); + errors.extend(validate_repeating_groups( + &fields, + msg_def, + dict, + &mut tag_errors, + )); } errors.extend(validate_checksum_field(msg, &field_map, &mut tag_errors)); @@ -202,6 +208,142 @@ fn validate_field_ordering( errors } +fn validate_repeating_groups( + fields: &[FieldValue], + msg_def: &MessageDef, + dict: &FixTagLookup, + tag_errors: &mut HashMap>, +) -> Vec { + let mut errors = Vec::new(); + let mut idx = 0; + while idx < fields.len() { + let tag = fields[idx].tag; + if let Some(spec) = msg_def.groups.get(&tag) { + let (consumed, mut errs) = + validate_group_instance(fields, idx, spec, msg_def, dict, tag_errors); + errors.append(&mut errs); + idx += consumed; + } else { + if let Some(owner) = msg_def.group_membership.get(&tag) { + let err = format!( + "Tag {} ({}) appears outside of repeating group {}", + tag, + dict.field_name(tag), + owner + ); + errors.push(err.clone()); + tag_errors.entry(tag).or_default().push(err); + } + idx += 1; + } + } + errors +} + +fn validate_group_instance( + fields: &[FieldValue], + start_idx: usize, + spec: &MessageDefGroupSpec, + msg_def: &MessageDef, + dict: &FixTagLookup, + tag_errors: &mut HashMap>, +) -> (usize, Vec) { + let mut errors = Vec::new(); + let count = fields[start_idx] + .value + .parse::() + .unwrap_or_else(|_| { + let err = format!( + "Invalid NumInGroup value '{}' for tag {}", + fields[start_idx].value, spec.count_tag + ); + errors.push(err.clone()); + tag_errors + .entry(spec.count_tag) + .or_default() + .push(err.clone()); + 0 + }); + let mut entries = 0usize; + let mut idx = start_idx + 1; + + while idx < fields.len() && entries < count { + if fields[idx].tag != spec.delim { + if msg_def.group_membership.get(&fields[idx].tag) == Some(&spec.count_tag) { + let err = format!( + "Expected group delimiter tag {} before tag {}", + spec.delim, fields[idx].tag + ); + errors.push(err.clone()); + tag_errors.entry(fields[idx].tag).or_default().push(err); + idx += 1; + continue; + } else { + break; + } + } + let (consumed, mut errs) = + validate_group_entry(fields, idx, spec, msg_def, dict, tag_errors); + errors.append(&mut errs); + idx += consumed; + entries += 1; + } + + if entries != count { + let err = format!( + "NumInGroup {} declared {}, but {} instance(s) found", + spec.count_tag, count, entries + ); + errors.push(err.clone()); + tag_errors.entry(spec.count_tag).or_default().push(err); + } + (idx - start_idx, errors) +} + +fn validate_group_entry( + fields: &[FieldValue], + start_idx: usize, + spec: &MessageDefGroupSpec, + msg_def: &MessageDef, + dict: &FixTagLookup, + tag_errors: &mut HashMap>, +) -> (usize, Vec) { + let mut errors = Vec::new(); + let mut idx = start_idx; + let mut last_pos = -1isize; + while idx < fields.len() { + let tag = fields[idx].tag; + if tag == spec.delim && idx != start_idx { + break; + } + if let Some(nested) = spec.nested.get(&tag) { + let (consumed, mut errs) = + validate_group_instance(fields, idx, nested, msg_def, dict, tag_errors); + errors.append(&mut errs); + idx += consumed; + continue; + } + if let Some(pos) = spec.entry_order.iter().position(|t| *t == tag) { + if (pos as isize) < last_pos { + let err = format!( + "Tag {} ({}) out of order within repeating group {}", + tag, + dict.field_name(tag), + spec.count_tag + ); + errors.push(err.clone()); + tag_errors.entry(tag).or_default().push(err); + } + last_pos = pos as isize; + idx += 1; + } else { + // Tag does not belong to this group; stop so parent can handle it. + break; + } + } + (idx - start_idx, errors) +} + fn validate_checksum_field( msg: &str, field_map: &HashMap, From 5502ea1b257d8e4a5b79ad52c986704136b82996 Mon Sep 17 00:00:00 2001 From: stephenlclarke Date: Wed, 10 Dec 2025 16:25:48 +0000 Subject: [PATCH 02/14] fix: restore repeating-group separator alignment and include pcap2fix in CI --- Makefile | 9 ++++++--- README.md | 2 +- src/decoder/prettifier.rs | 15 ++++++++++++--- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 1550f3d..4bf304e 100644 --- a/Makefile +++ b/Makefile @@ -32,9 +32,9 @@ scan: prepare mkdir -p target/coverage && \ if command -v cargo-audit >/dev/null 2>&1; then \ echo "Running cargo-audit (text output)"; \ - cargo audit || true; \ + cargo audit --no-fetch || true; \ echo "Running cargo-audit (JSON) → target/coverage/rustsec.json"; \ - cargo audit --json > target/coverage/rustsec.json || true; \ + cargo audit --no-fetch --json > target/coverage/rustsec.json || true; \ echo "Converting RustSec report to Sonar generic issues (target/coverage/sonar-generic-issues.json)"; \ python3 ci/convert_rustsec_to_sonar.py target/coverage/rustsec.json target/coverage/sonar-generic-issues.json || true; \ else \ @@ -48,7 +48,10 @@ coverage: build ensure_build_metadata && \ mkdir -p target/coverage && \ cargo llvm-cov clean --workspace >/dev/null 2>&1 || true; \ - cargo llvm-cov --workspace --cobertura \ + cargo llvm-cov \ + --package fixdecoder \ + --package pcap2fix \ + --cobertura \ --ignore-filename-regex "src/fix/sensitive.rs|src/bin/generate_sensitive_tags.rs" \ --output-path target/coverage/coverage.xml \ ' diff --git a/README.md b/README.md index c96c1b2..903c34c 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ The utility behaves like the `cat` utility in `Linux`, except as it reads the in ```bash -fixdecoder v0.2.1 (branch:develop, commit:20e9407) [rust:1.91.1] +fixdecoder 0.3.0 (branch:develop, commit:2a5a00b) [rust:1.91.1] FIX protocol utility - Dictionary lookup, file decoder, validator & prettifier Usage: fixdecoder [OPTIONS] [FILE]... diff --git a/src/decoder/prettifier.rs b/src/decoder/prettifier.rs index 3b01023..8dc32e3 100644 --- a/src/decoder/prettifier.rs +++ b/src/decoder/prettifier.rs @@ -148,6 +148,14 @@ impl<'a> GroupRenderer<'a> { if self.fields[idx].tag != spec.delim { if self.msg_def.group_membership.get(&self.fields[idx].tag) == Some(&spec.count_tag) { + if entries == 0 { + let entry_consumed = + self.render_group_entry(output, idx, spec, indent_spaces, entries + 1); + idx += entry_consumed; + entries += 1; + consumed = idx - start_idx; + continue; + } self.write_field( output, &self.fields[idx], @@ -203,7 +211,7 @@ impl<'a> GroupRenderer<'a> { let dash_start_col = indent_spaces + NAME_TEXT_OFFSET; let label_indent = dash_start_col.saturating_sub(entry_label.len()); output.push_str(&format!( - "{}{}{}{}{}\n", + "{}{} {}{}{}\n", indent(label_indent), entry_label, self.colours.error, @@ -1051,8 +1059,9 @@ mod tests { let paren_col = count_line.find('(').expect("open paren present"); let dash_col = group_line.find('-').expect("dashes present"); assert_eq!( - dash_col, paren_col, - "group separator should align under '('" + dash_col, + paren_col + 1, + "group separator should start one space after '(' anchor" ); } From d5014bbc344b53f031e0690cd76c16292503263f Mon Sep 17 00:00:00 2001 From: stephenlclarke Date: Wed, 10 Dec 2025 16:38:30 +0000 Subject: [PATCH 03/14] ci: add PR build/test job and enable sonar PR analysis --- .github/workflows/rust.yml | 83 +++++++++++++++++++++++++++++++++++--- 1 file changed, 78 insertions(+), 5 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index a160e4e..26e9eb8 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -9,6 +9,11 @@ on: - develop tags: - "v*" + pull_request: + branches: + - main + - master + - develop permissions: contents: read @@ -150,8 +155,8 @@ jobs: name: Lint & Coverage runs-on: ubuntu-latest needs: dictionaries - # Run on branch pushes for fast feedback (debug build + coverage); skip tags. - if: github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/') + # Run on branch pushes and PRs for fast feedback (debug build + coverage); skip tags. + if: (github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/')) || github.event_name == 'pull_request' steps: - uses: actions/checkout@v4 - name: Compute lockfile hash @@ -233,12 +238,72 @@ jobs: path: target/clippy.json if-no-files-found: warn + pr-check: + name: PR Build & Test (slim) + runs-on: ubuntu-latest + needs: dictionaries + if: github.event_name == 'pull_request' + steps: + - uses: actions/checkout@v4 + - name: Compute lockfile hash + id: lockfile-hash + shell: bash + run: | + if command -v python3 >/dev/null 2>&1; then + python3 ci/compute_lockfile_hash.py Cargo.lock + elif command -v python >/dev/null 2>&1; then + python ci/compute_lockfile_hash.py Cargo.lock + else + echo "Python is required to compute the lockfile hash." >&2 + exit 1 + fi + - name: Cache Rust toolchain and cargo bin + uses: actions/cache@v4 + with: + path: | + ~/.rustup + ~/.cargo/bin + key: prcheck-rust-${{ runner.os }}-${{ hashFiles('rust-toolchain*', 'Cargo.lock') }} + restore-keys: | + prcheck-rust-${{ runner.os }}- + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + with: + components: clippy,rustfmt + - name: Download FIX specs artifact + uses: actions/download-artifact@v4 + with: + name: fix-specs + path: resources + - name: Cache cargo registry and target + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: prcheck-${{ runner.os }}-cargo-${{ steps.lockfile-hash.outputs.hash }} + restore-keys: | + prcheck-${{ runner.os }}-cargo- + - name: Prepare build inputs + shell: bash + run: | + source ci/ci_helper.sh + ensure_build_metadata + cargo run --quiet --bin generate_sensitive_tags >/dev/null + - name: Format check + run: cargo fmt --all --check + - name: Clippy (warnings only) + run: cargo clippy --workspace --all-targets -- -D warnings + - name: Cargo test (workspace) + run: cargo test --workspace --locked --all-targets -- --nocapture + sonar: name: Sonar (coverage import) runs-on: ubuntu-latest needs: [quality] - # Run for branch pushes (not tags) when a token is available. - if: github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/') + # Run for branch pushes and PRs (not tags) when a token is available. + if: ((github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/')) || github.event_name == 'pull_request') steps: - uses: actions/checkout@v4 - name: Determine toolchain channel @@ -299,11 +364,19 @@ jobs: echo "[]" > target/clippy.json fi CLIPPY_REPORT="$(pwd)/target/clippy.json" + PR_OPTS=() + if [ "${{ github.event_name }}" = "pull_request" ]; then + PR_OPTS+=("-Dsonar.pullrequest.key=${{ github.event.pull_request.number }}") + PR_OPTS+=("-Dsonar.pullrequest.branch=${{ github.event.pull_request.head.ref }}") + PR_OPTS+=("-Dsonar.pullrequest.base=${{ github.event.pull_request.base.ref }}") + PR_OPTS+=("-Dsonar.pullrequest.provider=github") + fi sonar-scanner \ -Dsonar.host.url=https://sonarcloud.io \ -Dsonar.externalIssuesReportPaths=target/coverage/sonar-generic-issues.json \ -Dsonar.rust.clippy.reportPaths="${CLIPPY_REPORT}" \ - -Dsonar.rust.clippy.enabled=false + -Dsonar.rust.clippy.enabled=false \ + "${PR_OPTS[@]}" release: name: Publish Release From 88c2f74f8030f4c40cc6b90fb524d0743690f958 Mon Sep 17 00:00:00 2001 From: stephenlclarke Date: Wed, 10 Dec 2025 16:46:48 +0000 Subject: [PATCH 04/14] ci: streamline the GHA workflow --- .github/workflows/rust.yml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 26e9eb8..4cfefbb 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -2,18 +2,14 @@ name: CI Pipeline on: - push: + pull_request: branches: - main - master - develop + push: tags: - "v*" - pull_request: - branches: - - main - - master - - develop permissions: contents: read @@ -156,7 +152,7 @@ jobs: runs-on: ubuntu-latest needs: dictionaries # Run on branch pushes and PRs for fast feedback (debug build + coverage); skip tags. - if: (github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/')) || github.event_name == 'pull_request' + if: github.event_name == 'pull_request' steps: - uses: actions/checkout@v4 - name: Compute lockfile hash @@ -303,7 +299,7 @@ jobs: runs-on: ubuntu-latest needs: [quality] # Run for branch pushes and PRs (not tags) when a token is available. - if: ((github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/')) || github.event_name == 'pull_request') + if: github.event_name == 'pull_request' steps: - uses: actions/checkout@v4 - name: Determine toolchain channel From eec008af55dcecc514472e907ad5b09467a91f05 Mon Sep 17 00:00:00 2001 From: stephenlclarke Date: Wed, 10 Dec 2025 17:04:12 +0000 Subject: [PATCH 05/14] ci: Simplified GitHub Actions into two paths within a single workflow --- .github/workflows/{rust.yml => ci.yml} | 298 ++++++++++--------------- README.md | 2 +- 2 files changed, 118 insertions(+), 182 deletions(-) rename .github/workflows/{rust.yml => ci.yml} (85%) diff --git a/.github/workflows/rust.yml b/.github/workflows/ci.yml similarity index 85% rename from .github/workflows/rust.yml rename to .github/workflows/ci.yml index 4cfefbb..bdec1df 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -# CI pipeline for the Rust implementation +# CI pipeline for PRs (lint, test, coverage, sonar) and tag release builds. name: CI Pipeline on: @@ -18,7 +18,6 @@ jobs: dictionaries: name: Prepare FIX specs runs-on: ubuntu-latest - # Specs are needed for all flows. steps: - uses: actions/checkout@v4 - name: Download FIX XML dictionaries @@ -30,128 +29,10 @@ jobs: name: fix-specs path: resources - build-matrix: - name: Build release (tags only) - strategy: - fail-fast: true - matrix: - include: - - os: macos-latest - target: aarch64-apple-darwin - artifact_path: target/aarch64-apple-darwin/release/fixdecoder - asset_suffix: darwin-arm64 - ext: "" - - os: macos-15-intel - target: x86_64-apple-darwin - artifact_path: target/x86_64-apple-darwin/release/fixdecoder - asset_suffix: darwin-x86_64 - ext: "" - - os: windows-latest - target: x86_64-pc-windows-msvc - artifact_path: target/x86_64-pc-windows-msvc/release/fixdecoder.exe - asset_suffix: windows-x86_64 - ext: ".exe" - - os: ubuntu-latest - target: x86_64-unknown-linux-gnu - artifact_path: target/x86_64-unknown-linux-gnu/release/fixdecoder - asset_suffix: linux-gnu-x86_64 - ext: "" - - os: ubuntu-latest - target: x86_64-unknown-linux-musl - setup: sudo apt-get update && sudo apt-get install -y musl-tools - artifact_path: target/x86_64-unknown-linux-musl/release/fixdecoder - asset_suffix: linux-musl-x86_64 - ext: "" - runs-on: ${{ matrix.os }} - needs: [dictionaries] - # Only build release artifacts when tagging; skips lint/coverage. - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') - env: - FIXDECODER_BRANCH: ${{ github.ref_name }} - FIXDECODER_COMMIT: ${{ github.sha }} - FIXDECODER_GIT_URL: https://github.com/${{ github.repository }} - steps: - - uses: actions/checkout@v4 - - name: Compute lockfile hash - id: lockfile-hash - shell: bash - run: | - if command -v python3 >/dev/null 2>&1; then - python3 ci/compute_lockfile_hash.py Cargo.lock - elif command -v python >/dev/null 2>&1; then - python ci/compute_lockfile_hash.py Cargo.lock - else - echo "Python is required to compute the lockfile hash." >&2 - exit 1 - fi - - name: Set up Rust - uses: dtolnay/rust-toolchain@stable - with: - components: clippy,rustfmt,llvm-tools-preview - targets: ${{ matrix.target }} - - name: Download FIX specs artifact - uses: actions/download-artifact@v4 - with: - name: fix-specs - path: resources - - name: Cache cargo registry and target - uses: actions/cache@v4 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: ${{ runner.os }}-${{ matrix.target }}-cargo-${{ steps.lockfile-hash.outputs.hash }} - restore-keys: | - ${{ runner.os }}-${{ matrix.target }}-cargo- - ${{ runner.os }}-cargo- - - name: Install target dependencies - if: ${{ matrix.setup != '' }} - run: ${{ matrix.setup }} - - name: Prepare build inputs - shell: bash - run: | - source ci/ci_helper.sh - ensure_build_metadata - cargo run --quiet --bin generate_sensitive_tags >/dev/null - - name: Build release - shell: bash - run: | - if [[ "${{ matrix.target }}" == "x86_64-unknown-linux-musl" ]]; then - export CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_RUSTFLAGS="-C target-feature=+crt-static" - fi - cargo fmt --all - cargo build --release --locked --workspace --target ${{ matrix.target }} - env: - RUSTFLAGS: "" - - name: Stage artifact - shell: bash - run: | - mkdir -p dist - cp "${{ matrix.artifact_path }}" "dist/fixdecoder-${{ matrix.asset_suffix }}${{ matrix.ext }}" - pcap_path="target/${{ matrix.target }}/release/pcap2fix${{ matrix.ext }}" - if [ -f "$pcap_path" ]; then - cp "$pcap_path" "dist/pcap2fix-${{ matrix.asset_suffix }}${{ matrix.ext }}" - else - echo "pcap2fix binary not found at $pcap_path" >&2 - exit 1 - fi - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - name: fixdecoder-${{ matrix.asset_suffix }} - path: dist/fixdecoder-${{ matrix.asset_suffix }}${{ matrix.ext }} - - name: Upload pcap2fix artifact - uses: actions/upload-artifact@v4 - with: - name: pcap2fix-${{ matrix.asset_suffix }} - path: dist/pcap2fix-${{ matrix.asset_suffix }}${{ matrix.ext }} - quality: name: Lint & Coverage runs-on: ubuntu-latest needs: dictionaries - # Run on branch pushes and PRs for fast feedback (debug build + coverage); skip tags. if: github.event_name == 'pull_request' steps: - uses: actions/checkout@v4 @@ -234,71 +115,10 @@ jobs: path: target/clippy.json if-no-files-found: warn - pr-check: - name: PR Build & Test (slim) - runs-on: ubuntu-latest - needs: dictionaries - if: github.event_name == 'pull_request' - steps: - - uses: actions/checkout@v4 - - name: Compute lockfile hash - id: lockfile-hash - shell: bash - run: | - if command -v python3 >/dev/null 2>&1; then - python3 ci/compute_lockfile_hash.py Cargo.lock - elif command -v python >/dev/null 2>&1; then - python ci/compute_lockfile_hash.py Cargo.lock - else - echo "Python is required to compute the lockfile hash." >&2 - exit 1 - fi - - name: Cache Rust toolchain and cargo bin - uses: actions/cache@v4 - with: - path: | - ~/.rustup - ~/.cargo/bin - key: prcheck-rust-${{ runner.os }}-${{ hashFiles('rust-toolchain*', 'Cargo.lock') }} - restore-keys: | - prcheck-rust-${{ runner.os }}- - - name: Set up Rust - uses: dtolnay/rust-toolchain@stable - with: - components: clippy,rustfmt - - name: Download FIX specs artifact - uses: actions/download-artifact@v4 - with: - name: fix-specs - path: resources - - name: Cache cargo registry and target - uses: actions/cache@v4 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: prcheck-${{ runner.os }}-cargo-${{ steps.lockfile-hash.outputs.hash }} - restore-keys: | - prcheck-${{ runner.os }}-cargo- - - name: Prepare build inputs - shell: bash - run: | - source ci/ci_helper.sh - ensure_build_metadata - cargo run --quiet --bin generate_sensitive_tags >/dev/null - - name: Format check - run: cargo fmt --all --check - - name: Clippy (warnings only) - run: cargo clippy --workspace --all-targets -- -D warnings - - name: Cargo test (workspace) - run: cargo test --workspace --locked --all-targets -- --nocapture - sonar: name: Sonar (coverage import) runs-on: ubuntu-latest needs: [quality] - # Run for branch pushes and PRs (not tags) when a token is available. if: github.event_name == 'pull_request' steps: - uses: actions/checkout@v4 @@ -374,6 +194,122 @@ jobs: -Dsonar.rust.clippy.enabled=false \ "${PR_OPTS[@]}" + build-matrix: + name: Build release (tags only) + strategy: + fail-fast: true + matrix: + include: + - os: macos-latest + target: aarch64-apple-darwin + artifact_path: target/aarch64-apple-darwin/release/fixdecoder + asset_suffix: darwin-arm64 + ext: "" + - os: macos-15-intel + target: x86_64-apple-darwin + artifact_path: target/x86_64-apple-darwin/release/fixdecoder + asset_suffix: darwin-x86_64 + ext: "" + - os: windows-latest + target: x86_64-pc-windows-msvc + artifact_path: target/x86_64-pc-windows-msvc/release/fixdecoder.exe + asset_suffix: windows-x86_64 + ext: ".exe" + - os: ubuntu-latest + target: x86_64-unknown-linux-gnu + artifact_path: target/x86_64-unknown-linux-gnu/release/fixdecoder + asset_suffix: linux-gnu-x86_64 + ext: "" + - os: ubuntu-latest + target: x86_64-unknown-linux-musl + setup: sudo apt-get update && sudo apt-get install -y musl-tools + artifact_path: target/x86_64-unknown-linux-musl/release/fixdecoder + asset_suffix: linux-musl-x86_64 + ext: "" + runs-on: ${{ matrix.os }} + needs: [dictionaries] + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + env: + FIXDECODER_BRANCH: ${{ github.ref_name }} + FIXDECODER_COMMIT: ${{ github.sha }} + FIXDECODER_GIT_URL: https://github.com/${{ github.repository }} + steps: + - uses: actions/checkout@v4 + - name: Compute lockfile hash + id: lockfile-hash + shell: bash + run: | + if command -v python3 >/dev/null 2>&1; then + python3 ci/compute_lockfile_hash.py Cargo.lock + elif command -v python >/dev/null 2>&1; then + python ci/compute_lockfile_hash.py Cargo.lock + else + echo "Python is required to compute the lockfile hash." >&2 + exit 1 + fi + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + with: + components: clippy,rustfmt,llvm-tools-preview + targets: ${{ matrix.target }} + - name: Download FIX specs artifact + uses: actions/download-artifact@v4 + with: + name: fix-specs + path: resources + - name: Cache cargo registry and target + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-${{ matrix.target }}-cargo-${{ steps.lockfile-hash.outputs.hash }} + restore-keys: | + ${{ runner.os }}-${{ matrix.target }}-cargo- + ${{ runner.os }}-cargo- + - name: Install target dependencies + if: ${{ matrix.setup != '' }} + run: ${{ matrix.setup }} + - name: Prepare build inputs + shell: bash + run: | + source ci/ci_helper.sh + ensure_build_metadata + cargo run --quiet --bin generate_sensitive_tags >/dev/null + - name: Build release + shell: bash + run: | + if [[ "${{ matrix.target }}" == "x86_64-unknown-linux-musl" ]]; then + export CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_RUSTFLAGS="-C target-feature=+crt-static" + fi + cargo fmt --all + cargo build --release --locked --workspace --target ${{ matrix.target }} + env: + RUSTFLAGS: "" + - name: Stage artifact + shell: bash + run: | + mkdir -p dist + cp "${{ matrix.artifact_path }}" "dist/fixdecoder-${{ matrix.asset_suffix }}${{ matrix.ext }}" + pcap_path="target/${{ matrix.target }}/release/pcap2fix${{ matrix.ext }}" + if [ -f "$pcap_path" ]; then + cp "$pcap_path" "dist/pcap2fix-${{ matrix.asset_suffix }}${{ matrix.ext }}" + else + echo "pcap2fix binary not found at $pcap_path" >&2 + exit 1 + fi + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: fixdecoder-${{ matrix.asset_suffix }} + path: dist/fixdecoder-${{ matrix.asset_suffix }}${{ matrix.ext }} + - name: Upload pcap2fix artifact + uses: actions/upload-artifact@v4 + with: + name: pcap2fix-${{ matrix.asset_suffix }} + path: dist/pcap2fix-${{ matrix.asset_suffix }}${{ matrix.ext }} + release: name: Publish Release runs-on: ubuntu-latest diff --git a/README.md b/README.md index 903c34c..ced25f9 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ The utility behaves like the `cat` utility in `Linux`, except as it reads the in ```bash -fixdecoder 0.3.0 (branch:develop, commit:2a5a00b) [rust:1.91.1] +fixdecoder 0.3.0 (branch:develop, commit:88c2f74) [rust:1.91.1] FIX protocol utility - Dictionary lookup, file decoder, validator & prettifier Usage: fixdecoder [OPTIONS] [FILE]... From 94c8bc1cd3d479634505c4e4de0472d003eeacaa Mon Sep 17 00:00:00 2001 From: stephenlclarke Date: Wed, 10 Dec 2025 17:53:52 +0000 Subject: [PATCH 06/14] chore: fix capture script quoting and streamline CI/coverage --- README.md | 2 +- {bin => ci}/scan.sh | 0 scripts/capture_and_decode.sh | 40 +++++++++++++++++++++++++++++++++++ {bin => scripts}/slowcat.sh | 2 +- 4 files changed, 42 insertions(+), 2 deletions(-) rename {bin => ci}/scan.sh (100%) create mode 100755 scripts/capture_and_decode.sh rename {bin => scripts}/slowcat.sh (94%) diff --git a/README.md b/README.md index ced25f9..49d2f31 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ The utility behaves like the `cat` utility in `Linux`, except as it reads the in ```bash -fixdecoder 0.3.0 (branch:develop, commit:88c2f74) [rust:1.91.1] +fixdecoder 0.3.0 (branch:develop, commit:eec008a) [rust:1.91.1] FIX protocol utility - Dictionary lookup, file decoder, validator & prettifier Usage: fixdecoder [OPTIONS] [FILE]... diff --git a/bin/scan.sh b/ci/scan.sh similarity index 100% rename from bin/scan.sh rename to ci/scan.sh diff --git a/scripts/capture_and_decode.sh b/scripts/capture_and_decode.sh new file mode 100755 index 0000000..329a2d2 --- /dev/null +++ b/scripts/capture_and_decode.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'USAGE' +Usage: capture_and_decode.sh + +Example: + ./scripts/capture_and_decode.sh user@integration.example.com 192.168.1.10 1234 + +Notes: + - is used in both the tcpdump filter and the pcap2fix --port argument. + - Assumes fixdecoder and pcap2fix binaries are available at ./target/release/. +USAGE +} + +if [[ $# -lt 3 ]]; then + usage + exit 1 +fi + +SSH_TARGET="$1" +TCP_HOST="$2" +PORT="$3" +shift 3 +FIXDECODER_ARGS=("$@") + +REMOTE_CMD="sudo tcpdump -U -n -s0 -i any -w - \"(host ${TCP_HOST} and port ${PORT}) and tcp[((tcp[12] & 0xf0) >> 2):4] = 0x383d4649 and tcp[((tcp[12] & 0xf0) >> 2) + 4] = 0x58\"" + +PCAP2FIX_BIN="${PCAP2FIX_BIN:-./target/release/pcap2fix}" +FIXDECODER_BIN="${FIXDECODER_BIN:-./target/release/fixdecoder}" + +if [[ ! -x "${PCAP2FIX_BIN}" || ! -x "${FIXDECODER_BIN}" ]]; then + echo "error: expected binaries at ${PCAP2FIX_BIN} and ${FIXDECODER_BIN}. Build them first (cargo build --release)." >&2 + exit 1 +fi + +ssh "${SSH_TARGET}" "${REMOTE_CMD}" \ + | "${PCAP2FIX_BIN}" --port "${PORT}" \ + | "${FIXDECODER_BIN}" --follow "${FIXDECODER_ARGS[@]}" diff --git a/bin/slowcat.sh b/scripts/slowcat.sh similarity index 94% rename from bin/slowcat.sh rename to scripts/slowcat.sh index 9c7b66a..69218d4 100755 --- a/bin/slowcat.sh +++ b/scripts/slowcat.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -delay=10 # default sleep delay +delay=1s # default sleep delay # Parse options while getopts "d:" opt; do From bce0533026f7ebee9ee56baae1ddd7268f0580e8 Mon Sep 17 00:00:00 2001 From: stephenlclarke Date: Wed, 10 Dec 2025 18:00:03 +0000 Subject: [PATCH 07/14] feature: use installed app, but allow override and fallback to local build --- scripts/capture_and_decode.sh | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/scripts/capture_and_decode.sh b/scripts/capture_and_decode.sh index 329a2d2..71140c2 100755 --- a/scripts/capture_and_decode.sh +++ b/scripts/capture_and_decode.sh @@ -27,8 +27,27 @@ FIXDECODER_ARGS=("$@") REMOTE_CMD="sudo tcpdump -U -n -s0 -i any -w - \"(host ${TCP_HOST} and port ${PORT}) and tcp[((tcp[12] & 0xf0) >> 2):4] = 0x383d4649 and tcp[((tcp[12] & 0xf0) >> 2) + 4] = 0x58\"" -PCAP2FIX_BIN="${PCAP2FIX_BIN:-./target/release/pcap2fix}" -FIXDECODER_BIN="${FIXDECODER_BIN:-./target/release/fixdecoder}" +# Resolve binaries: prefer explicit env override, then PATH, then local release build. +resolve_bin() { + local env_path="$1" + local name="$2" + local local_fallback="$3" + + if [[ -n "${env_path}" ]]; then + echo "${env_path}" + return + fi + + if command -v "${name}" >/dev/null 2>&1; then + command -v "${name}" + return + fi + + echo "${local_fallback}" +} + +PCAP2FIX_BIN="$(resolve_bin "${PCAP2FIX_BIN:-}" pcap2fix ./target/release/pcap2fix)" +FIXDECODER_BIN="$(resolve_bin "${FIXDECODER_BIN:-}" fixdecoder ./target/release/fixdecoder)" if [[ ! -x "${PCAP2FIX_BIN}" || ! -x "${FIXDECODER_BIN}" ]]; then echo "error: expected binaries at ${PCAP2FIX_BIN} and ${FIXDECODER_BIN}. Build them first (cargo build --release)." >&2 From 9f467f8e023ec480d64f6af315d137b1a82b90ff Mon Sep 17 00:00:00 2001 From: stephenlclarke Date: Wed, 10 Dec 2025 22:15:04 +0000 Subject: [PATCH 08/14] feat: highlight selected FIX dictionary in info output, restructure README, remove auto-update script, and refine info output --- Makefile | 5 -- README.md | 149 +++++++++++++++--------------------- ci/update_readme.py | 45 ----------- docs/capture_and_decode.png | Bin 0 -> 181304 bytes docs/info_command.png | Bin 0 -> 90644 bytes src/main.rs | 56 ++++++++------ 6 files changed, 96 insertions(+), 159 deletions(-) delete mode 100755 ci/update_readme.py create mode 100644 docs/capture_and_decode.png create mode 100644 docs/info_command.png diff --git a/Makefile b/Makefile index 4bf304e..f8859ca 100644 --- a/Makefile +++ b/Makefile @@ -17,11 +17,6 @@ build: prepare build-release: prepare @bash -lc 'source $(CI_SCRIPT) && ensure_build_metadata && cargo fmt --all && cargo build --workspace --release' - @python3 ci/update_readme.py - -.PHONY: update-readme -update-readme: - @python3 ci/update_readme.py scan: prepare @bash -lc '\ diff --git a/README.md b/README.md index 49d2f31..c61651f 100644 --- a/README.md +++ b/README.md @@ -44,124 +44,99 @@ I have written utilities like this in past in Java, Python, C, C++, [go](https:/ --- -# How to use it +## What is it -The utility behaves like the `cat` utility in `Linux`, except as it reads the input (either piped in from `stdin` or from a filename specified on the commandline) it scans each line for `FIX protocol` messages and prints them out highlighted in bold white while the rest of the line will be in a mid grey colour. After the line is output it will be followed by a detailed breakdown of all the `FIX Protocol` tags that were found in the message. The detailed output will use the appropriate `FIX` dictionary for the version of `FIX` specified in `BeginString (tag 8)` tag. It will also look at `DefaultApplVerID (tag 1137)` when `8=FIXT.1.1` is detected in the message. +fixdecoder is a FIX-aware “tail-like” tool and dictionary explorer. It reads from stdin or multiple log files, detects and prettifies FIX messages in stream, and fits naturally into pipelines. Each highlighted message is followed by a detailed tag breakdown using the correct dictionary for BeginString (8) (or DefaultApplVerID (1137) when 8=FIXT.1.1). It can validate on the fly (`--validate`), reporting protocol issues as it decodes, and track order state with summaries (`--summary`). For lookups, `--info` shows available/overridden dictionaries, and `--message`, `--component`, or `--tag` inspect definitions in the selected FIX version (`--fix` or default) without a live decode. -## Running the utility +## Quick start - ```bash -fixdecoder 0.3.0 (branch:develop, commit:eec008a) [rust:1.91.1] -FIX protocol utility - Dictionary lookup, file decoder, validator & prettifier +# Stream and prettify stdin (pipeline-friendly) +cat fixlog.txt | fixdecoder -Usage: fixdecoder [OPTIONS] [FILE]... +# Stream with validation + order summaries +cat fixlog.txt | fixdecoder --validate --summary +``` + + -Arguments: - [FILE]... +## Running the fixdecoder utility -Options: - --fix FIX version to use [default: 44] - --xml Path to alternative FIX XML dictionary (repeatable) - --message [] FIX Message name or MsgType (omit value to list all) - --component [] FIX Component to display (omit value to list all) - --tag [] FIX Tag number to display (omit value to list all) - --column Display enums in columns - --header Include Header block - --trailer Include Trailer block - --verbose Show full message structure with enums - --info Show schema summary - --secret Obfuscate sensitive FIX tag values - --validate Validate FIX messages during decoding - --colour [] Force coloured output - --delimiter Display delimiter between FIX fields (default: SOH) - --version Print version information and exit - --summary Track order state across messages and print a summary - -f, --follow Stream input like tail -f - -h, --help Print help +You can run fixdecoder anywhere you can run a Rust binary — no extra OS dependencies or runtime services are required. It ships with a full set of embedded FIX dictionaries. The sections below cover the key options for selecting and browsing dictionaries, controlling output/formatting, and adjusting processing modes. -Command line option examples: +## Key options at a glance - FIX dictionary lookup +- Dictionaries: `--xml`, `--fix`, `--info`, `--message`, `--component`, `--tag` +- Output/layout: `--column`, `--verbose`, `--header`, `--trailer`, `--colour`, `--delimiter` +- Processing modes: `--follow`, `--validate`, `--secret`, `--summary` - Query FIX dictionary contents by FIX Message Name or MsgType: +## `--xml` - fixdecoder [[--fix=44] [--xml=FILE --xml=FILE2 ...]] [--message[=NAME|MSGTYPE] [--verbose] [--column] [--header] [--trailer] +The `--xml` flag lets you load custom FIX dictionaries from XML files; you can pass it multiple times to register several custom dictionaries. Each file is parsed, normalised to a canonical key (e.g., FIX44, FIX50SP2), and has FIXT11 session header/trailer injected for 5.0+ if missing. Custom entries are registered for tag lookup and schema loading; they override built-ins for the same key and replace earlier `--xml` files for that key, with warnings emitted in both cases. - $ fixdecoder --message=NewOrderSingle --verbose --column --header --trailer - $ fixdecoder --message=D --verbose --column --header --trailer - - Query FIX dictionary contents by FIX Tag number: +The XML dictionaries can be downloaded from the [QuickFIX GitHub Repo](https://github.com/quickfix/quickfix/tree/master/spec) - fixdecoder [[--fix=44] [--xml=FILE --xml=FILE2 ...]] [--tag[=TAG] [--verbose] [--column] +## `--fix` - $ fixdecoder --tag=44 --verbose --column - - Query FIX dictionary contents by FIX Component Name: +The `--fix` option allows you to specify the default FIX dictionary. This defaults to FIX 4.4 (`44`). It accepts either just the version digits (e.g., `44`, `4.4`) or the same value prefixed with FIX/fix (e.g., `FIX44`, `fix4.4`). The parser normalises your input by stripping dots, uppercasing, and adding FIX if it’s missing; it then checks that key against built‑ins (`FIX27`…`FIXT11`) and any custom `--xml` overrides. If the normalised key isn’t known, it errors. - fixdecoder [[--fix=44] [--xml=FILE --xml=FILE2 ...]] [--component[=NAME] [--verbose] [--column] +## `--info` - $ fixdecoder --component=Instrument --verbose --column +`--info` is an informational mode: it prints the list of available FIX dictionary keys (built-ins plus any loaded via `--xml`), then a table of loaded dictionaries with counts and their source (built-in vs file path). The table highlights the currently selected/default FIX version (from `--fix` or the default `44`) with a leading `*` so you can see which dictionary will be used. It does not decode messages or print schema details; it’s meant to verify which dictionaries are present, which ones are being overridden by custom XML, and which version is active. - Show summary information about available FIX dictionaries: +![--info](docs/info_command.png) - fixdecoder [[--fix=44] [--xml=FILE --xml=FILE2 ...]] [--info] +## Querying the FIX dictionaries `--message`, `--component` and `--tag` - $ fixdecoder --info +Use these flags to explore the active FIX dictionary. `--verbose` adds detail / metadata, `--column` uses a compact table layout. `--header`/`--trailer` only apply to `--message` and `--component` (not `--tag`). - Prettify FIX log files with optional validation and obfuscation; if output is piped then colour is disabled by - default but can be forced on with --colour=yes: +### `--message[=]` - fixdecoder [--xml=FILE --xml=FILE2 ...] [--validate] [--colour=yes|no] [--secret] [--summary] [--follow] [--fix=VER] [--delimiter=CHAR] [file1.log file2.log ...] +Browse messages. With no value, list all message types (use --`column` for a compact view). With a name or MsgType (e.g., `D` or `NewOrderSingle`), render the message structure (fields, components, repeating groups); `--header`/`--trailer` include session blocks. Reports “Message not found” if absent. - Validate and Obfuscate a FIX logfile. +### `--component[=]` - $ fixdecoder --validate --secret logs/fix.log +Browse components. With no value, list all components (or use `--column`). With a name, render that component’s fields, nested components, and repeating groups. Reports “Component not found” if absent. - Decode all the NewOrderSingle messages in a FIX logfile and output the fix messages using a custom delimiter - also force colour mode because this example pipes the output into less. Normally colour mode is turned off - when piping the output due to the output containing ANSI control chars which may mess up processing further - down the pipe chain. +### `--tag[=]` - $ grep '35=D' logs/fix.log | fixdecoder --colour=yes --delimiter='|' | less +Browse fields. With no value, list all tags (or use `--column`). With a tag number, show that field’s details (name, type, enums, etc.). Reports “Tag not found” if absent. - Force the decoding of a FIX log to use the FIX 4.4 dictionary. Only uses the version of the FIX dictionary - specified in the FIX message header if the tag being processed is not defined in the override dictionary. - for example FIX 4.4 does not have the FIX 4.2 tag 20 (ExecTransType) +### `--validate` - $ fixdecoder --fix=44 trades.log +Validate each decoded FIX message against the active dictionary (honours `--fix` and any `--xml` overrides). Checks MsgType, BodyLength, checksum, required fields, enum/type correctness, field ordering, repeating-group structure, and duplicate disallowed tags. Validation runs alongside prettified output; any errors are appended after the message. It doesn’t stop the stream—use it to flag protocol issues while decoding - Process a FIX log file and display an order summary for each order that is processed. +### `--secret` - $ fixdecoder --summary --follow logs/fix.log +Obfuscate sensitive FIX fields while decoding. When enabled, values for a predefined set of sensitive tags (e.g., session IDs, sender/target IDs) are replaced with stable aliases (e.g., `SenderCompID0001`) so logs stay readable without exposing real identifiers. Obfuscation is applied per line/message and resets between files; disabled by default. -``` - +### `--colour[=yes|no]` -```bash -❯ target/debug/fixdecoder --info -fixdecoder 0.2.0 (branch:develop, commit:7a2d535) [rust:1.91.1] -Available FIX Dictionaries: FIX27,FIX30,FIX40,FIX41,FIX42,FIX43,FIX44,FIX50,FIX50SP1,FIX50SP2,FIXT11 - -Loaded dictionaries: - Version ServicePack Fields Components Messages Source - FIX27 0 138 2 27 built-in - FIX30 0 138 2 27 built-in - FIX40 0 138 2 27 built-in - FIX41 0 206 2 28 built-in - FIX42 0 405 2 46 built-in - FIX43 0 635 12 68 built-in - FIX44 0 912 106 93 built-in - FIX50 0 1090 123 93 built-in - FIX50SP1 1 1373 165 105 built-in - FIX50SP2 2 6028 727 156 built-in - FIXT11 0 71 4 8 built-in -``` +Control coloured output. By default, colours are shown when writing to a terminal and disabled when output is piped. Use `--colour`/`--colour=yes` to force colours on, or `--colour=no` to force them off. Non-tty output defaults to no colour unless you explicitly opt in. + +### `--delimiter=` + +Set the display delimiter between FIX fields (default: `SOH`). Specify a single character after `=` sign. + +Accepted values: + +- A single literal character (e.g.`,`, `|`, or a single Unicode character like `—`). + +- SOH (case-insensitive) or a hex escape like `\x01`/`0x01` (quote to protect the backslash, e.g. `--delimiter='\x1f'`). + +Empty values or anything longer than one character are rejected. + +## `-f`, `--follow` + +Stream input like `tail -f`. Keeps reading and decoding as new data arrives on stdin or a file, sleeping briefly on `EOF` rather than exiting, until interrupted. This mirrors `tail -f` behaviour but with FIX decoding, validation, and prettification applied in real time. + +## `--summary` + +Track FIX order lifecycles and emit a summary instead of full decoded messages. When enabled, each message is consumed into an order tracker (keyed by `OrderID`/`ClOrdID`/`OrigClOrdID`), updating state, quantities, prices, and events. At the end (or live in `--follow` mode) it prints a concise per-order summary/footer using the chosen display delimiter. This mode suppresses the usual prettified message output; use it to monitor order state across a stream or log. ## Download it -Check out the Repo's [Releases Page](https://github.com/stephenlclarke/fixdecoder2/releases) -to see what versions are available for the computer you want to run it on. +Check out the Repo's [Releases Page](https://github.com/stephenlclarke/fixdecoder2/releases) to see what versions are available for the computer you want to run it on. ## Build it @@ -530,9 +505,9 @@ fixdecoder 0.2.0 (branch:develop, commit:7a2d535) [rust:1.91.1] git clone git@github.com:stephenlclarke/fixdecoder2.git ``` -# PCAP → FIX filter (`pcap2fix`) +# PCAP to FIX filter (`pcap2fix`) -The workspace includes a helper that reassembles TCP streams from PCAP data and emits FIX messages to stdout so you can pipe them into `fixdecoder`. +The workspace includes a helper that reassembles TCP streams from PCAP data and emits FIX messages to stdout so you can pipe them into `fixdecoder`. I have wrapped it in a shell script (`./scripts/capture_and_decode.sh`) to make it easy to run. - Build: `cargo build -p pcap2fix` (also built via `make build`). - Offline: `pcap2fix --input capture.pcap | fixdecoder` @@ -540,6 +515,8 @@ The workspace includes a helper that reassembles TCP streams from PCAP data and - Delimiter defaults to SOH; override with `--delimiter`. - Flow buffers are capped (size + idle timeout) to avoid runaway memory during long captures. +![Capture and Decode](docs/capture_and_decode.png) + # Technical Notes on the use of the `--summary` flag - As messages stream by, the decoder builds one “record” per order (keyed by OrderID/ClOrdID/OrigClOrdID). diff --git a/ci/update_readme.py b/ci/update_readme.py deleted file mode 100755 index 96da53d..0000000 --- a/ci/update_readme.py +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env python3 -"""Refresh README usage block from `fixdecoder --help` output.""" - -from __future__ import annotations - -import re -import subprocess -import sys -from pathlib import Path - - -def generate_usage() -> str: - """Return the CLI usage text by invoking the built binary.""" - try: - return subprocess.check_output( - ["./target/release/fixdecoder", "--help"], - text=True, - stderr=subprocess.STDOUT, - ) - except (OSError, subprocess.CalledProcessError) as exc: - sys.stderr.write(f"Failed to run fixdecoder --help: {exc}\n") - sys.exit(1) - - -def update_readme(root: Path, usage: str) -> None: - """Replace the usage block in README.md with the provided text.""" - readme = root / "README.md" - text = readme.read_text(encoding="utf-8") - pattern = r".*?" - replacement = f"\n```bash\n{usage}```\n" - new_text = re.sub(pattern, replacement, text, flags=re.S) - if new_text != text: - readme.write_text(new_text, encoding="utf-8") - - -def main() -> int: - """Entry point to refresh the README usage block.""" - root = Path(__file__).resolve().parent.parent - usage = generate_usage() - update_readme(root, usage) - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/docs/capture_and_decode.png b/docs/capture_and_decode.png new file mode 100644 index 0000000000000000000000000000000000000000..e552b5e63a20a27cea0e6715ce8be483e197a8b4 GIT binary patch literal 181304 zcmZUacQ~72+yC2YX=|h{EoxO;v$mEBlA@}p-PWcywQ5AIP*t=wYwy^5@2#jH+8RM@ zDt5#QK_c->pZE8?&v^gHao@Rg}uN&Yh!Ieg0JU+_{TK z=gwVNyG%`avcjHYMR~yBproYjsHCFg>g4LK_s+t~M#aX(#@)e6S4HvMxyO-_uT1UP zUonBwnrk22{~bJ5P!P*NeiUElq&xCydYx=j>pUwI=TxTnd7|TRb!F;^Tf>iVYXesngE67|ee>Oi;-lWxj%GmaJ`!YjSKKoIP1h-w9Zo_t>7N zjcF1xPMqhtAauJzcm=ss+qe4T2hg(%I!q587Jd+wAeg{=o(d)!3WCC_;hEl#s zd^T4W;xCCH|Csqqvvv<}bdknmcP2{iQBk?T-9hC6snS%Xly``?_aw ziS)?#2ZduPcGnhyy&+w42>ABX(|U39^sA5n-E_KD0Oz^IB|+gT-=lk+RT>V0p9drL z?WOwmKOZ-(-m<*_-4?cU0HTIv@@>B=2yKU>_piS$1pUx2&P-AynF}{E!Fl9E&C=;S z`^6xqhXgPEjFihuu`<#17m^%Omf8#f~g;#H{+h?!m+h-q-FK#V#3!IS8HNJdH z?#HZ;h$$AF-P;{5>E5w_b+3&sz{kE6GJM7M-Muk!RZ-R0;A8f?am!+?+_`sR`3Cf? zBdkY&Y7CG167PCgJvz z)I~qM_1SA!>x9RK=Y6}|Z-qVkr^hnfht9gq*HBgFYtlw^WSRh~K+MeR#z&ZkKVM&k zwzwMqP{GM-T7&Y1UpZOS=H?hadQG^%HFyr%?A?0Vj@tNi`Dg$f>o{-}%f%aro?iz2 z`RxI7hq*0$=r>&yaW{82cjNMask|NY*?H)=U71^bb=4tBXBMVtG$|~{Zyh4v-P3KY zuC;zM^wSj!QC~0VQpQMT(^l%fDp%`LXxRtmVvjACvf7D1;Fr%5srmd1&^wY;nl9*1 z_f)~GcSD?8eLrCj3%ml<<){^BJ9M`8#@sh*1{szj z95U+aQ#Zf%3@oE9uU1M3-lALip2d%8S#M<@DPX8ivR^q@Ak znLjt_-)?qBB>6&dRAUS0nzGKQ&Un0Od#xr0DFX4>#z56pL*twv<^J-y^I;C>E>iB! zQ(m_zFN%(2UHIpdi$_^h|GdAj_V+`@8>&3#&OJG&`c&bi_xUZ;OOr2^nL(am=hcet zT)4|~_rk60&qZLbpz_aOgMz*YH{AWoQFQrZ5x=DsmEcDO!Ou4?2+q-;wbd@PE=-+l z!Vj7Vni*A=n4b& zYbVRT9y~QAZpeN9Zc;b$@aMUiVe`IoT7g(L9;7dt{=CxnYhoAa{^!yldhx2O`CR?{ z?F*sb6C<8@#|@@gYt++wci0D$9oWW{I1Xa|C>!fe$A-F3T-RqtJmLY!?)qo{KF*8T z$boXBAit0_Zg=>`K{4V0sDsMY=7|vUn8I&^MZ-Eyi3x{rpig-r*rx70Po;CeBq#1e zEJ`X~hik%uErCX2QkMq7j9yC&Z8o$sS^fH!apr=W*_s{QW<5h2{yf*M)u_>t*E9C5 zVp3amX2ilcNg4sHDl09d;|>yUr0F-T9R(!Sarmsq-6Vua#Yh5teq?cw?%d;2b^x?i zX{+4cEO)v54;#MupGy^q`%SRl%z}6$C))*MrVD@Q1zkORo+)Y?|3XFqb%n!(QqoVJ zZ_r$Yi6vFU**T2~gn!Mo8SsXG%#^d->;@iYdFxaNcyF=>ON`ZXZH@&kgslFCuHWQ3G@?#$xPWHl>*M4N>Vk^-)8om5aR8#fHlly>k(4 zFCSi&Th60c0wDVf31jlT)!)tRsnY=DB`I)+wND^nz|~=?h-_zWTFkBmJK@tq!HOv5 z%xcBJ3D&TegFRk4Dp%X_R9akigi9{ouwMrEUW zGJ=tySK%PRwi+bt%x}fr`c?|A5JBM8{s`b zy8m*Ng=a-L+nDOX@6bC7(5$EvgHcjO%s15szxO0b=yJqK85MYZD5NK;D#@0z{dLX% zZ`L=LojNa1F3#iqw#wH+zjsDab}{?J$esD7Slt3l%b#LPdk*imYhmti3nzpsHwqu$ z1MrWJa-X!9-xP`o<2eYg`Lb`3_FM^!_(N-21h5*7a-S3F_mrCsl{%n5v>=PUUN6LU z71pABv4dV8xHbVS`=NaFlpm^lz2SmwlfiAgiG-a7DRd03YX`hHCnGg?1gyl9z7& ztVzI02@&f>h~~5oHF21)&Vr5*Q)60&94h+PiYeRrJ3jaW{kAi>Z`$|m<n;PJ($P4Rvb*Cs~35=;DuWJ zcJc`QzOYPj{zB2>-x|hP8rlAO4(qz}<3NsJ_vyD8t350SRxY}PDu%B)Si?eLezR%b zGRF)+@=Zo}_=udd;_c~B(3m55?$+iM=h)$c1Eq|HtYy|PUxY%om3)MX>H5&)x6K^jJ)vs8TqCONu@XHDj%e?+bop*xM^MA)n}|K5v@r! z{#bQ0V&0&-ynlZjZ+Ng=HvJ~Q4v<3yXF<2g5)PTG91epJ?2QSIx2H#7QWhEeZMqqv z5gZc`hH@*q^{Gy6Eg2_!E*@H|tlt;>2)egM`Po)2(iFe47=;3%-)q-rv(1QMKtkMT zB4n69WrDAEBw*1nwmt}-SI0-cQoK3f8B?3ZQM_3<7#~WqbV5MV`r|@n`1wN2a26Rr z?Faw9iT^4U{q~g1-(7un^+H?q1k)FvxZ6@6rOWHWGNpg%G}NivQgLO3%N0*ZN?Q*s zg#=V7tJ-kYexCY7PHBM2)Pd2lZ#ldZt(|j0bvEfQmOpoa#LNr;&YphBN`ovajrYdv zN~~Pq_s7ZE^pLts`E(CoS;$gseNZTRIHvO(h}PCT<5x=?4s*|-m4H9b4~ufn<|zKn z2oH@+qf%8tP8i1bRDj-4sRe8b%Dip4pHZ)x-?c((6DryAA3d zz5Orgz57>scd8pR*)E(3To`aE5l}(*$LS_k{2}nNbhLbuh8t;towYK71Y|U1GDKo* zqwjICJ#dC!-X_n&Ul`?YAJ#GvCWoEk8=ErUtx_407!xQIi5T=AIE~%7E?Oc65^#Dr z1hh|Ptf~6WtUUA9kg3vY9be0Fkj-Hg=DwQGg;mX622!I(IUiCy)fxyNgI4L_+kzHW;`fDBH_Fu9_Jk$}3$oM|oMlPE>5{VX70 zlYctN|Ap1gf7)f!iA#FH;K^79P#a%#MO z85wp4m{zpLnxC2Q(Xo@Ur}=~N3Ed&I10^?0t&yvd3XZY*KDjSwp;jfXCdGU!^n%#9 zDP2vqKwV*)=r<8Pk=!E~Tx7V5ze)MOHzFHqqrb@H*V_v>`QewCA*X3h!-GbJoFEzaq z|4c+ELF;_-M_$Fc%AC!WNuHQ#9%%H{;he5{NS+LYs$#=#eHy5hVuzE%AwAud1J^b4)4^!YN<0>zU;cb3 zR61u`X`z2K>g7y`Ke{LS%fgl-4Hw)p14MYn+}02rS>b5BS%kt~#de<_DA29fiCPO) zqG;s?i9KxVS{G|n)Mgfi9^SAVMF=S3mZKn;D|54{Z8_{%Dl*CWCu`W=Gx6l-4#

akZsxflDkoM4u3#&X_al7u=tI4i0Z-Lhq-I- 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 aATkMH)a}e^M0)}M$Ve(mRET~1`F{Xg60UUs literal 0 HcmV?d00001 diff --git a/docs/info_command.png b/docs/info_command.png new file mode 100644 index 0000000000000000000000000000000000000000..2afa9322165c15c5d20ad43e4a1f1f122bafe818 GIT binary patch literal 90644 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̝W=`m|n49+JKz^!*d#%F`^j6*8% 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 Q1@P~KsO, key: &str /// Print the table header for dictionary listings. fn print_dictionary_header() { println!( - " {:<10} {:>12} {:>8} {:>11} {:>11} Source", - "Version", "ServicePack", "Fields", "Components", "Messages", + " {:<1}{:<10} {:>12} {:>8} {:>11} {:>11} Source", + "", "Version", "ServicePack", "Fields", "Components", "Messages", ); } /// Print one row of dictionary metadata. -fn print_dictionary_row(key: &str, schema: &SchemaTree, source: &str) { +fn print_dictionary_row(marker: &str, key: &str, schema: &SchemaTree, source: &str) { println!( - " {:<10} {:>12} {:>8} {:>11} {:>11} {}", + " {:<1}{:<10} {:>12} {:>8} {:>11} {:>11} {}", + marker, key, schema.service_pack, schema.fields.len(), @@ -774,6 +775,15 @@ fn print_dictionary_row(key: &str, schema: &SchemaTree, source: &str) { ); } +/// Prefix a row when the FIX key should be highlighted. +fn dictionary_marker(highlight: Option<&str>, key: &str) -> &'static str { + if matches!(highlight, Some(target) if target.eq_ignore_ascii_case(key)) { + "*" + } else { + " " + } +} + /// Determine whether a particular FIX dictionary needs the FIXT11 session /// header/trailer merged in. Saves the rest of the code from hard-coding /// these version checks repeatedly. @@ -826,8 +836,12 @@ fn key_to_xml_id(key: &str) -> Option<&'static str> { } } -/// Print a summary table of all available dictionaries (built-in and custom). -fn print_all_dictionary_info(custom_dicts: &HashMap) -> Result<()> { +/// Print a summary table of all available dictionaries (built-in and custom), +/// optionally highlighting a selected entry. +fn print_all_dictionary_info( + custom_dicts: &HashMap, + highlight: Option<&str>, +) -> Result<()> { println!( "Available FIX Dictionaries: {}", available_fix_versions(custom_dicts) @@ -839,7 +853,8 @@ fn print_all_dictionary_info(custom_dicts: &HashMap) - match load_schema_for_key(&key, custom_dicts) { Ok(schema) => { let source = dictionary_source(custom_dicts, &key); - print_dictionary_row(&key, &schema, &source); + let marker = dictionary_marker(highlight, &key); + print_dictionary_row(marker, &key, &schema, &source); } Err(err) => eprintln!("warning: failed to load {key}: {err}"), } @@ -848,26 +863,14 @@ fn print_all_dictionary_info(custom_dicts: &HashMap) - Ok(()) } -/// Handle the `--info` command, printing either all dictionaries or the selected one. +/// Handle the `--info` command, printing all dictionaries and highlighting the selected one. fn handle_info( opts: &CliOptions, - schema: &SchemaTree, + _schema: &SchemaTree, custom_dicts: &HashMap, ) -> Result<()> { - if opts.fix_from_user { - println!( - "Available FIX Dictionaries: {}", - available_fix_versions(custom_dicts) - ); - println!("\nCurrent Schema:"); - print_dictionary_header(); - let key = normalise_fix_key(&opts.fix_version).unwrap_or_else(|| "FIX44".to_string()); - let source = dictionary_source(custom_dicts, &key); - print_dictionary_row(&key, schema, &source); - println!(); - } else { - print_all_dictionary_info(custom_dicts)?; - } + let selected_key = normalise_fix_key(&opts.fix_version).unwrap_or_else(|| "FIX44".to_string()); + print_all_dictionary_info(custom_dicts, Some(&selected_key))?; Ok(()) } @@ -1161,4 +1164,11 @@ mod tests { assert!(all.contains(&"FIX44".into())); assert!(all.contains(&"FIX27".into())); } + + #[test] + fn dictionary_marker_highlights_selected_entry() { + assert_eq!(dictionary_marker(Some("fix44"), "FIX44"), "*"); + assert_eq!(dictionary_marker(Some("fix44"), "FIX50"), " "); + assert_eq!(dictionary_marker(None, "FIX44"), " "); + } } From 771503fd673080fab2b877271ad598e0120bb462 Mon Sep 17 00:00:00 2001 From: stephenlclarke Date: Wed, 10 Dec 2025 22:20:26 +0000 Subject: [PATCH 09/14] docs: updated README.md --- README.md | 321 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 186 insertions(+), 135 deletions(-) diff --git a/README.md b/README.md index c61651f..ac7d9d6 100644 --- a/README.md +++ b/README.md @@ -173,8 +173,7 @@ Then build it. Debug version with clippy and code coverage ```bash ❯ make clean build scan coverage build-release - Cleaning [ ] 0.00% - Removed 29068 files, 2.8GiB total + Removed 51032 files, 3.9GiB total >> Ensuring Rust toolchain and coverage tools @@ -182,205 +181,235 @@ Then build it. Debug version with clippy and code coverage info: component 'llvm-tools' for target 'aarch64-apple-darwin' is up to date >> Ensuring FIX XML specs are present - Compiling fixdecoder v0.2.1 (/Users/sclarke/github/fixdecoder2) -warning: fixdecoder@0.2.1: Building fixdecoder v0.2.1 (branch:develop, commit:02b044e) [rust:1.91.1] - Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.59s + Compiling minimal-lexical v0.2.1 + Compiling thiserror v1.0.69 + Compiling fixdecoder v0.3.0 (/Users/sclarke/github/fixdecoder2) + Compiling thiserror-impl v1.0.69 + Compiling arrayvec v0.7.6 + Compiling circular v0.3.0 + Compiling etherparse v0.15.0 + Compiling nom v7.1.3 +warning: fixdecoder@0.3.0: Building fixdecoder 0.3.0 (branch:develop, commit:9f467f8) [rust:1.91.1] + Compiling rusticata-macros v4.1.0 + Compiling pcap-parser v0.14.1 + Compiling pcap2fix v0.1.0 (/Users/sclarke/github/fixdecoder2/pcap2fix) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.88s Checking memchr v2.7.6 + Checking anstyle v1.0.13 + Checking utf8parse v0.2.2 Checking bitflags v2.10.0 Checking regex-syntax v0.8.8 - Checking anstyle v1.0.13 - Checking cfg-if v1.0.4 Checking libc v0.2.178 - Compiling rustix v1.1.2 + Checking anstyle-query v1.1.5 + Checking is_terminal_polyfill v1.70.2 + Checking colorchoice v1.0.4 Checking crossbeam-utils v0.8.21 Checking num-traits v0.2.19 - Checking utf8parse v0.2.2 - Checking objc2-encode v4.1.0 - Checking colorchoice v1.0.4 - Checking anstyle-query v1.1.5 + Checking strsim v0.11.1 Checking anstyle-parse v0.2.7 - Checking is_terminal_polyfill v1.70.2 - Checking serde_core v1.0.228 + Checking cfg-if v1.0.4 + Compiling rustix v1.1.2 + Checking objc2-encode v4.1.0 Checking clap_lex v0.7.6 + Checking serde_core v1.0.228 + Checking anyhow v1.0.100 + Checking predicates-core v1.0.9 Checking core-foundation-sys v0.8.7 - Checking strsim v0.11.1 - Checking anstream v0.6.21 Checking objc2 v0.6.3 - Checking aho-corasick v1.1.4 + Checking anstream v0.6.21 + Compiling fixdecoder v0.3.0 (/Users/sclarke/github/fixdecoder2) Checking crossbeam-epoch v0.9.18 - Compiling fixdecoder v0.2.1 (/Users/sclarke/github/fixdecoder2) - Compiling getrandom v0.3.4 - Checking iana-time-zone v0.1.64 + Checking aho-corasick v1.1.4 + Checking normalize-line-endings v0.3.0 Checking clap_builder v4.5.53 - Checking predicates-core v1.0.9 - Checking once_cell v1.21.3 + Checking iana-time-zone v0.1.64 Checking crossbeam-deque v0.8.6 Checking either v1.15.0 - Checking chrono v0.4.42 - Checking rayon-core v1.13.0 + Checking termtree v0.5.1 Checking float-cmp v0.10.0 - Checking anyhow v1.0.100 + Checking difflib v0.4.0 + Checking rayon-core v1.13.0 + Checking once_cell v1.21.3 + Compiling assert_cmd v2.1.1 + Compiling getrandom v0.3.4 + Checking predicates-tree v1.0.12 + Checking chrono v0.4.42 + Checking rayon v1.11.0 Checking errno v0.3.14 Checking nix v0.30.1 - Checking rayon v1.11.0 + Checking wait-timeout v0.2.1 Checking roxmltree v0.21.1 - Compiling assert_cmd v2.1.1 Checking block2 v0.6.2 - Checking normalize-line-endings v0.3.0 - Checking termtree v0.5.1 - Checking difflib v0.4.0 - Checking wait-timeout v0.2.1 - Checking regex-automata v0.4.13 - Checking predicates-tree v1.0.12 +warning: fixdecoder@0.3.0: Building fixdecoder 0.3.0 (branch:develop, commit:9f467f8) [rust:1.91.1] + Checking minimal-lexical v0.2.1 Checking fastrand v2.3.0 + Checking arrayvec v0.7.6 Checking dispatch2 v0.3.0 + Checking nom v7.1.3 + Checking circular v0.3.0 + Checking etherparse v0.15.0 + Checking regex-automata v0.4.13 + Checking thiserror v1.0.69 Checking clap v4.5.53 - Checking serde v1.0.228 Checking ctrlc v3.5.1 -warning: fixdecoder@0.2.1: Building fixdecoder v0.2.1 (branch:develop, commit:02b044e) [rust:1.91.1] + Checking serde v1.0.228 Checking terminal_size v0.4.3 - Checking tempfile v3.23.0 Checking quick-xml v0.36.2 + Checking tempfile v3.23.0 + Checking rusticata-macros v4.1.0 + Checking pcap-parser v0.14.1 + Checking pcap2fix v0.1.0 (/Users/sclarke/github/fixdecoder2/pcap2fix) Checking regex v1.12.2 Checking bstr v1.12.1 Checking predicates v3.1.3 - Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.87s + Finished `dev` profile [unoptimized + debuginfo] target(s) in 3.30s Running cargo-audit (text output) - Fetching advisory database from `https://github.com/RustSec/advisory-db.git` Loaded 884 security advisories (from /Users/sclarke/.cargo/advisory-db) - Updating crates.io index - Scanning Cargo.lock for vulnerabilities (105 crate dependencies) + Scanning Cargo.lock for vulnerabilities (115 crate dependencies) Running cargo-audit (JSON) → target/coverage/rustsec.json Converting RustSec report to Sonar generic issues (target/coverage/sonar-generic-issues.json) info: cargo-llvm-cov currently setting cfg(coverage); you can opt-out it by passing --no-cfg-coverage Compiling libc v0.2.178 - Compiling proc-macro2 v1.0.103 - Compiling unicode-ident v1.0.22 Compiling memchr v2.7.6 + Compiling proc-macro2 v1.0.103 Compiling quote v1.0.42 + Compiling unicode-ident v1.0.22 Compiling autocfg v1.5.0 - Compiling bitflags v2.10.0 + Compiling regex-syntax v0.8.8 Compiling crossbeam-utils v0.8.21 - Compiling objc2 v0.6.3 + Compiling bitflags v2.10.0 Compiling anstyle v1.0.13 - Compiling regex-syntax v0.8.8 - Compiling cfg-if v1.0.4 + Compiling objc2 v0.6.3 Compiling utf8parse v0.2.2 + Compiling anstyle-parse v0.2.7 + Compiling cfg-if v1.0.4 + Compiling colorchoice v1.0.4 + Compiling is_terminal_polyfill v1.70.2 Compiling rustix v1.1.2 Compiling cfg_aliases v0.2.1 - Compiling objc2-encode v4.1.0 Compiling serde_core v1.0.228 - Compiling nix v0.30.1 + Compiling objc2-encode v4.1.0 + Compiling anstyle-query v1.1.5 + Compiling anstream v0.6.21 Compiling num-traits v0.2.19 Compiling aho-corasick v1.1.4 - Compiling anstyle-parse v0.2.7 - Compiling is_terminal_polyfill v1.70.2 + Compiling nix v0.30.1 + Compiling rayon-core v1.13.0 + Compiling strsim v0.11.1 + Compiling anyhow v1.0.100 + Compiling crossbeam-epoch v0.9.18 Compiling serde v1.0.228 Compiling semver v1.0.27 - Compiling rayon-core v1.13.0 - Compiling anstyle-query v1.1.5 - Compiling colorchoice v1.0.4 - Compiling anstream v0.6.21 - Compiling rustc_version v0.4.1 + Compiling heck v0.5.0 + Compiling clap_lex v0.7.6 + Compiling clap_builder v4.5.53 Compiling regex-automata v0.4.13 - Compiling syn v2.0.111 Compiling block2 v0.6.2 - Compiling clap_lex v0.7.6 - Compiling crossbeam-epoch v0.9.18 - Compiling anyhow v1.0.100 + Compiling syn v2.0.111 + Compiling rustc_version v0.4.1 Compiling crossbeam-deque v0.8.6 - Compiling heck v0.5.0 Compiling core-foundation-sys v0.8.7 - Compiling strsim v0.11.1 + Compiling predicates-core v1.0.9 Compiling iana-time-zone v0.1.64 - Compiling fixdecoder v0.2.1 (/Users/sclarke/github/fixdecoder2) - Compiling clap_builder v4.5.53 Compiling errno v0.3.14 + Compiling regex v1.12.2 Compiling dispatch2 v0.3.0 - Compiling either v1.15.0 - Compiling getrandom v0.3.4 - Compiling predicates-core v1.0.9 + Compiling fixdecoder v0.3.0 (/Users/sclarke/github/fixdecoder2) Compiling once_cell v1.21.3 - Compiling regex v1.12.2 - Compiling chrono v0.4.42 - Compiling rayon v1.11.0 - Compiling float-cmp v0.10.0 - Compiling terminal_size v0.4.3 - Compiling roxmltree v0.21.1 - Compiling difflib v0.4.0 Compiling termtree v0.5.1 + Compiling difflib v0.4.0 + Compiling getrandom v0.3.4 Compiling assert_cmd v2.1.1 - Compiling ctrlc v3.5.1 - Compiling serde_derive v1.0.228 - Compiling clap_derive v4.5.49 + Compiling either v1.15.0 + Compiling float-cmp v0.10.0 Compiling normalize-line-endings v0.3.0 Compiling predicates v3.1.3 + Compiling terminal_size v0.4.3 + Compiling chrono v0.4.42 Compiling predicates-tree v1.0.12 Compiling bstr v1.12.1 Compiling wait-timeout v0.2.1 + Compiling ctrlc v3.5.1 + Compiling roxmltree v0.21.1 + Compiling rayon v1.11.0 Compiling fastrand v2.3.0 + Compiling minimal-lexical v0.2.1 + Compiling nom v7.1.3 + Compiling thiserror v1.0.69 Compiling tempfile v3.23.0 -warning: fixdecoder@0.2.1: Building fixdecoder v0.2.1 (branch:develop, commit:02b044e) [rust:1.91.1] + Compiling clap_derive v4.5.49 + Compiling serde_derive v1.0.228 + Compiling thiserror-impl v1.0.69 +warning: fixdecoder@0.3.0: Building fixdecoder 0.3.0 (branch:develop, commit:9f467f8) [rust:1.91.1] + Compiling circular v0.3.0 + Compiling arrayvec v0.7.6 + Compiling etherparse v0.15.0 Compiling clap v4.5.53 + Compiling rusticata-macros v4.1.0 + Compiling pcap-parser v0.14.1 + Compiling pcap2fix v0.1.0 (/Users/sclarke/github/fixdecoder2/pcap2fix) Compiling quick-xml v0.36.2 - Finished `test` profile [unoptimized + debuginfo] target(s) in 7.42s - Running unittests src/main.rs (target/llvm-cov-target/debug/deps/fixdecoder-835cbd2851e2e0ab) + Finished `test` profile [unoptimized + debuginfo] target(s) in 8.38s + Running unittests src/main.rs (target/llvm-cov-target/debug/deps/fixdecoder-ae50d2dc6f6148f6) -running 72 tests +running 75 tests test decoder::display::tests::layout_stats_produces_layout ... ok test decoder::display::tests::pad_ansi_extends_to_requested_width ... ok -test decoder::display::tests::print_enum_outputs_coloured_enum ... ok -test decoder::display::tests::collect_sorted_values_orders_by_enum ... ok test decoder::display::tests::collect_group_layout_counts_nested_components ... ok test decoder::display::tests::print_field_renders_required_indicator ... ok -test decoder::display::tests::tag_and_message_cells_include_expected_text ... ok test decoder::display::tests::compute_values_layout_uses_max_entry ... ok -test decoder::display::tests::compute_message_layout_counts_header_and_trailer ... ok +test decoder::display::tests::print_enum_outputs_coloured_enum ... ok +test decoder::display::tests::tag_and_message_cells_include_expected_text ... ok +test decoder::display::tests::collect_sorted_values_orders_by_enum ... ok test decoder::display::tests::terminal_width_is_positive ... ok +test decoder::display::tests::compute_message_layout_counts_header_and_trailer ... ok test decoder::display::tests::visible_len_ignores_escape_sequences ... ok -test decoder::display::tests::print_enum_columns_respects_layout_columns ... ok test decoder::display::tests::visible_width_ignores_ansi_sequences ... ok +test decoder::display::tests::print_enum_columns_respects_layout_columns ... ok +test decoder::display::tests::write_with_padding_adds_spaces ... ok test decoder::display::tests::render_component_prints_matching_msg_type_enum_only ... ok test decoder::display::tests::render_message_includes_header_and_trailer ... ok -test decoder::display::tests::write_with_padding_adds_spaces ... ok test decoder::display::tests::cached_layout_is_reused_for_component ... ok -test decoder::prettifier::tests::read_line_with_follow_returns_zero_on_eof ... ok test decoder::prettifier::tests::trim_line_endings_strips_crlf ... ok -test decoder::prettifier::tests::header_and_trailer_are_repositioned_when_out_of_place ... ok +test decoder::prettifier::tests::read_line_with_follow_returns_zero_on_eof ... ok test decoder::schema::tests::parse_simple_vec ... ok -test decoder::schema::tests::parse_message_with_components ... ok test decoder::schema::tests::parse_message_fields ... ok +test decoder::schema::tests::parse_message_with_components ... ok +test decoder::prettifier::tests::prettify_aligns_group_entries_without_header ... ok +test decoder::prettifier::tests::header_and_trailer_are_repositioned_when_out_of_place ... ok +test decoder::prettifier::tests::build_tag_order_respects_annotations_and_trailer ... ok test decoder::summary::tests::build_summary_row_includes_bn_headers ... ok test decoder::summary::tests::display_instrument_formats_side_and_symbol ... ok test decoder::summary::tests::date_diff_days_returns_none_when_incomplete ... ok test decoder::summary::tests::extract_date_part_handles_timestamp ... ok test decoder::summary::tests::flow_label_skips_leading_unknown ... ok test decoder::summary::tests::preferred_settlement_date_prefers_primary_then_secondary ... ok -test decoder::summary::tests::links_orders_using_order_id_and_cl_ord_id ... ok test decoder::summary::tests::render_record_header_includes_id_and_instrument ... ok test decoder::summary::tests::resolve_key_prefers_alias_then_ids ... ok test decoder::summary::tests::state_path_deduplicates_consecutive_states ... ok test decoder::summary::tests::terminal_status_from_non_exec_report_updates_header ... ok test decoder::tag_lookup::tests::detects_schema_from_default_appl_ver_id ... ok -test decoder::summary::tests::collects_states_for_single_order ... ok -test decoder::prettifier::tests::prettify_orders_without_msg_type_header_first ... ok -test decoder::prettifier::tests::build_tag_order_respects_annotations_and_trailer ... ok -test decoder::prettifier::tests::prettify_includes_missing_tag_annotations_once ... ok +test decoder::summary::tests::absorb_fields_sets_core_values ... ok +test decoder::summary::tests::render_outputs_state_headline ... ok +test decoder::summary::tests::bn_message_sets_state_and_spot_price ... ok +test decoder::summary::tests::absorb_fields_sets_block_notice_specifics ... ok test decoder::validator::tests::allows_repeating_group_tags ... ok test decoder::validator::tests::detects_body_length_mismatch ... ok test decoder::validator::tests::detects_checksum_mismatch ... ok test decoder::validator::tests::missing_msg_type_still_reports_length_and_tag ... ok test fix::obfuscator::tests::reset_starts_aliases_over ... ok -test tests::add_flag_args_sets_flags ... ok test tests::add_entity_arg_defaults_to_true_when_missing_value ... ok +test tests::add_flag_args_sets_flags ... ok +test tests::build_cli_parses_follow_and_summary_flags ... ok test tests::dictionary_key_includes_service_pack ... ok +test tests::dictionary_marker_highlights_selected_entry ... ok test tests::dictionary_source_prefers_custom_entry ... ok test tests::final_exit_code_marks_interrupt ... ok -test tests::build_cli_parses_follow_and_summary_flags ... ok -test tests::normalise_fix_key_handles_variants ... ok test tests::invalid_fix_version_errors ... ok -test tests::parse_colour_rejects_invalid ... ok +test tests::normalise_fix_key_handles_variants ... ok test tests::parse_colour_recognises_yes_no ... ok +test tests::parse_colour_rejects_invalid ... ok +test decoder::summary::tests::links_orders_using_order_id_and_cl_ord_id ... ok test tests::parse_delimiter_accepts_hex ... ok test tests::parse_delimiter_accepts_literal ... ok test tests::parse_delimiter_rejects_empty ... ok @@ -389,96 +418,118 @@ test tests::resolve_input_files_preserves_inputs ... ok test tests::valid_fix_version_passes ... ok test tests::version_str_is_cached ... ok test tests::version_string_matches_components ... ok +test decoder::prettifier::tests::prettify_includes_missing_tag_annotations_once ... ok +test decoder::prettifier::tests::prettify_orders_without_msg_type_header_first ... ok +test decoder::summary::tests::collects_states_for_single_order ... ok test decoder::prettifier::tests::validation_inserts_missing_tags ... ok test decoder::prettifier::tests::validation_only_outputs_invalid_messages ... ok test decoder::prettifier::tests::validation_skips_valid_messages ... ok -test decoder::summary::tests::absorb_fields_sets_block_notice_specifics ... ok -test decoder::summary::tests::bn_message_sets_state_and_spot_price ... ok -test decoder::summary::tests::render_outputs_state_headline ... ok -test decoder::summary::tests::absorb_fields_sets_core_values ... ok test decoder::tag_lookup::tests::load_dictionary_respects_override_key ... ok test decoder::tag_lookup::tests::override_uses_fallback_dictionary_for_missing_tags ... ok +test decoder::tag_lookup::tests::repeatable_tags_include_nested_groups ... ok test decoder::tag_lookup::tests::warns_and_falls_back_on_unknown_override ... ok -test result: ok. 72 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.99s +test result: ok. 75 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.00s - Running unittests src/bin/generate_sensitive_tags.rs (target/llvm-cov-target/debug/deps/generate_sensitive_tags-bcdddcbc14128afb) + Running unittests src/bin/generate_sensitive_tags.rs (target/llvm-cov-target/debug/deps/generate_sensitive_tags-79d8bd38aa9fdc5a) running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s - Running tests/cli.rs (target/llvm-cov-target/debug/deps/cli-94133c405ce02098) + Running tests/cli.rs (target/llvm-cov-target/debug/deps/cli-762abd0244a0dac8) running 5 tests -test decodes_message_from_file_path ... ok test summary_mode_outputs_order_summary ... ok test decodes_single_message_from_stdin ... ok test validation_reports_missing_fields ... ok +test decodes_message_from_file_path ... ok test override_is_honoured_with_fallback ... ok -test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.67s +test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.24s + + Running unittests src/main.rs (target/llvm-cov-target/debug/deps/pcap2fix-19171fc4d58700e9) + +running 6 tests +test tests::out_of_order_future_segment_is_skipped ... ok +test tests::flushes_full_messages_only ... ok +test tests::flush_complete_messages_emits_and_retains_tail ... ok +test tests::parse_delimiter_variants ... ok +test tests::reassembly_appends_in_order ... ok +test tests::retransmit_is_ignored ... ok + +test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Finished report saved to target/coverage/coverage.xml Compiling proc-macro2 v1.0.103 - Compiling unicode-ident v1.0.22 Compiling quote v1.0.42 + Compiling unicode-ident v1.0.22 + Compiling memchr v2.7.6 Compiling libc v0.2.178 Compiling crossbeam-utils v0.8.21 Compiling bitflags v2.10.0 Compiling objc2 v0.6.3 - Compiling memchr v2.7.6 - Compiling serde_core v1.0.228 - Compiling objc2-encode v4.1.0 + Compiling utf8parse v0.2.2 + Compiling colorchoice v1.0.4 Compiling cfg_aliases v0.2.1 + Compiling is_terminal_polyfill v1.70.2 + Compiling anstyle-parse v0.2.7 + Compiling serde_core v1.0.228 Compiling autocfg v1.5.0 - Compiling utf8parse v0.2.2 + Compiling objc2-encode v4.1.0 + Compiling anstyle-query v1.1.5 + Compiling anstyle v1.0.13 Compiling nix v0.30.1 - Compiling anstyle-parse v0.2.7 + Compiling anstream v0.6.21 Compiling semver v1.0.27 Compiling num-traits v0.2.19 - Compiling is_terminal_polyfill v1.70.2 - Compiling colorchoice v1.0.4 - Compiling anstyle v1.0.13 + Compiling clap_lex v0.7.6 + Compiling rustix v1.1.2 Compiling serde v1.0.228 - Compiling anstyle-query v1.1.5 + Compiling anyhow v1.0.100 + Compiling strsim v0.11.1 Compiling rayon-core v1.13.0 - Compiling rustix v1.1.2 - Compiling anstream v0.6.21 - Compiling rustc_version v0.4.1 + Compiling heck v0.5.0 + Compiling clap_builder v4.5.53 + Compiling block2 v0.6.2 Compiling crossbeam-epoch v0.9.18 Compiling crossbeam-deque v0.8.6 + Compiling rustc_version v0.4.1 + Compiling syn v2.0.111 Compiling aho-corasick v1.1.4 + Compiling minimal-lexical v0.2.1 Compiling core-foundation-sys v0.8.7 - Compiling regex-syntax v0.8.8 - Compiling clap_lex v0.7.6 - Compiling block2 v0.6.2 - Compiling strsim v0.11.1 - Compiling anyhow v1.0.100 Compiling cfg-if v1.0.4 - Compiling heck v0.5.0 - Compiling syn v2.0.111 - Compiling regex-automata v0.4.13 - Compiling clap_builder v4.5.53 + Compiling regex-syntax v0.8.8 Compiling errno v0.3.14 Compiling dispatch2 v0.3.0 Compiling iana-time-zone v0.1.64 - Compiling fixdecoder v0.2.1 (/Users/sclarke/github/fixdecoder2) + Compiling nom v7.1.3 + Compiling fixdecoder v0.3.0 (/Users/sclarke/github/fixdecoder2) + Compiling thiserror v1.0.69 Compiling either v1.15.0 + Compiling regex-automata v0.4.13 + Compiling rusticata-macros v4.1.0 Compiling rayon v1.11.0 - Compiling chrono v0.4.42 -warning: fixdecoder@0.2.1: Building fixdecoder v0.2.1 (branch:develop, commit:02b044e) [rust:1.91.1] Compiling terminal_size v0.4.3 Compiling ctrlc v3.5.1 Compiling roxmltree v0.21.1 - Compiling once_cell v1.21.3 - Compiling regex v1.12.2 + Compiling arrayvec v0.7.6 Compiling serde_derive v1.0.228 Compiling clap_derive v4.5.49 + Compiling thiserror-impl v1.0.69 + Compiling once_cell v1.21.3 +warning: fixdecoder@0.3.0: Building fixdecoder 0.3.0 (branch:develop, commit:9f467f8) [rust:1.91.1] + Compiling circular v0.3.0 + Compiling chrono v0.4.42 + Compiling pcap-parser v0.14.1 + Compiling etherparse v0.15.0 + Compiling regex v1.12.2 Compiling clap v4.5.53 Compiling quick-xml v0.36.2 - Finished `release` profile [optimized] target(s) in 9.17s + Compiling pcap2fix v0.1.0 (/Users/sclarke/github/fixdecoder2/pcap2fix) + Finished `release` profile [optimized] target(s) in 10.03s ``` Build the release version From 9dc9ca4dda43b5b284f88157646457df1ddc3a35 Mon Sep 17 00:00:00 2001 From: stephenlclarke Date: Wed, 10 Dec 2025 22:25:02 +0000 Subject: [PATCH 10/14] docs: fixed the heading levels --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index ac7d9d6..e7a9156 100644 --- a/README.md +++ b/README.md @@ -70,17 +70,17 @@ You can run fixdecoder anywhere you can run a Rust binary — no extra OS depend - Output/layout: `--column`, `--verbose`, `--header`, `--trailer`, `--colour`, `--delimiter` - Processing modes: `--follow`, `--validate`, `--secret`, `--summary` -## `--xml` +### `--xml` The `--xml` flag lets you load custom FIX dictionaries from XML files; you can pass it multiple times to register several custom dictionaries. Each file is parsed, normalised to a canonical key (e.g., FIX44, FIX50SP2), and has FIXT11 session header/trailer injected for 5.0+ if missing. Custom entries are registered for tag lookup and schema loading; they override built-ins for the same key and replace earlier `--xml` files for that key, with warnings emitted in both cases. The XML dictionaries can be downloaded from the [QuickFIX GitHub Repo](https://github.com/quickfix/quickfix/tree/master/spec) -## `--fix` +### `--fix` The `--fix` option allows you to specify the default FIX dictionary. This defaults to FIX 4.4 (`44`). It accepts either just the version digits (e.g., `44`, `4.4`) or the same value prefixed with FIX/fix (e.g., `FIX44`, `fix4.4`). The parser normalises your input by stripping dots, uppercasing, and adding FIX if it’s missing; it then checks that key against built‑ins (`FIX27`…`FIXT11`) and any custom `--xml` overrides. If the normalised key isn’t known, it errors. -## `--info` +### `--info` `--info` is an informational mode: it prints the list of available FIX dictionary keys (built-ins plus any loaded via `--xml`), then a table of loaded dictionaries with counts and their source (built-in vs file path). The table highlights the currently selected/default FIX version (from `--fix` or the default `44`) with a leading `*` so you can see which dictionary will be used. It does not decode messages or print schema details; it’s meant to verify which dictionaries are present, which ones are being overridden by custom XML, and which version is active. @@ -126,19 +126,19 @@ Accepted values: Empty values or anything longer than one character are rejected. -## `-f`, `--follow` +### `-f`, `--follow` Stream input like `tail -f`. Keeps reading and decoding as new data arrives on stdin or a file, sleeping briefly on `EOF` rather than exiting, until interrupted. This mirrors `tail -f` behaviour but with FIX decoding, validation, and prettification applied in real time. -## `--summary` +### `--summary` Track FIX order lifecycles and emit a summary instead of full decoded messages. When enabled, each message is consumed into an order tracker (keyed by `OrderID`/`ClOrdID`/`OrigClOrdID`), updating state, quantities, prices, and events. At the end (or live in `--follow` mode) it prints a concise per-order summary/footer using the chosen display delimiter. This mode suppresses the usual prettified message output; use it to monitor order state across a stream or log. -## Download it +# Download it Check out the Repo's [Releases Page](https://github.com/stephenlclarke/fixdecoder2/releases) to see what versions are available for the computer you want to run it on. -## Build it +# Build it Build it from source. This now requires `bash` version 5+ and a recent `Rust` toolchain (the project is tested with Rust 1.91+). From c50cdc5a9ab6544358a74b590e9e651a09a5b96f Mon Sep 17 00:00:00 2001 From: stephenlclarke Date: Wed, 10 Dec 2025 22:56:09 +0000 Subject: [PATCH 11/14] ci: added more unit tests --- pcap2fix/tests/roundtrip.rs | 89 +++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 pcap2fix/tests/roundtrip.rs diff --git a/pcap2fix/tests/roundtrip.rs b/pcap2fix/tests/roundtrip.rs new file mode 100644 index 0000000..0059a5f --- /dev/null +++ b/pcap2fix/tests/roundtrip.rs @@ -0,0 +1,89 @@ +use assert_cmd::Command; + +/// Build a minimal FIX message with correct BodyLength/Checksum using the given delimiter. +fn build_fix_message(delim: u8) -> Vec { + let d = delim as char; + let body = format!("35=0{d}"); + let body_len = body.len(); + let mut msg = format!("8=FIX.4.2{d}9={body_len}{d}{body}").into_bytes(); + let checksum: u8 = msg.iter().fold(0u16, |acc, b| acc + *b as u16) as u8; + msg.extend_from_slice(format!("10={:03}{}", checksum, d).as_bytes()); + msg +} + +/// Construct a tiny PCAP (classic) containing one Ethernet/IPv4/TCP packet with the FIX payload. +fn build_pcap(payload: &[u8]) -> Vec { + let mut buf = Vec::new(); + + // PCAP global header (little-endian, Ethernet linktype) + buf.extend_from_slice(&0xa1b2c3d4u32.to_le_bytes()); // magic + buf.extend_from_slice(&0x0002u16.to_le_bytes()); // version major + buf.extend_from_slice(&0x0004u16.to_le_bytes()); // version minor + buf.extend_from_slice(&0u32.to_le_bytes()); // thiszone + buf.extend_from_slice(&0u32.to_le_bytes()); // sigfigs + buf.extend_from_slice(&65535u32.to_le_bytes()); // snaplen + buf.extend_from_slice(&1u32.to_le_bytes()); // network = Ethernet + + // Build packet bytes + let mut pkt = Vec::new(); + // Ethernet + pkt.extend_from_slice(&[0, 1, 2, 3, 4, 5]); // dst + pkt.extend_from_slice(&[6, 7, 8, 9, 10, 11]); // src + pkt.extend_from_slice(&[0x08, 0x00]); // ethertype IPv4 + // IPv4 header + let ip_header_len = 20u16; + let tcp_header_len = 20u16; + let total_len = ip_header_len + tcp_header_len + payload.len() as u16; + pkt.extend_from_slice(&[0x45, 0x00]); // version/IHL, DSCP + pkt.extend_from_slice(&total_len.to_be_bytes()); + pkt.extend_from_slice(&[0x00, 0x00]); // identification + pkt.extend_from_slice(&[0x40, 0x00]); // flags/frag offset + pkt.extend_from_slice(&[64]); // TTL + pkt.extend_from_slice(&[6]); // protocol TCP + pkt.extend_from_slice(&[0x00, 0x00]); // checksum (omitted) + pkt.extend_from_slice(&[10, 0, 0, 1]); // src IP + pkt.extend_from_slice(&[10, 0, 0, 2]); // dst IP + // TCP header + let src_port: u16 = 40000; + let dst_port: u16 = 12083; + pkt.extend_from_slice(&src_port.to_be_bytes()); + pkt.extend_from_slice(&dst_port.to_be_bytes()); + pkt.extend_from_slice(&1u32.to_be_bytes()); // seq + pkt.extend_from_slice(&0u32.to_be_bytes()); // ack + pkt.extend_from_slice(&[0x50, 0x18]); // data offset=5, flags=PSH+ACK + pkt.extend_from_slice(&0xffffu16.to_be_bytes()); // window + pkt.extend_from_slice(&[0x00, 0x00]); // checksum (omitted) + pkt.extend_from_slice(&[0x00, 0x00]); // urgent ptr + // Payload + pkt.extend_from_slice(payload); + + // PCAP packet header + let pkt_len = pkt.len() as u32; + buf.extend_from_slice(&0u32.to_le_bytes()); // ts_sec + buf.extend_from_slice(&0u32.to_le_bytes()); // ts_usec + buf.extend_from_slice(&pkt_len.to_le_bytes()); // incl_len + buf.extend_from_slice(&pkt_len.to_le_bytes()); // orig_len + + buf.extend_from_slice(&pkt); + buf +} + +#[test] +fn pcap_roundtrip_matches_expected_output() { + let delim = 0x01; + let msg = build_fix_message(delim); + let pcap_bytes = build_pcap(&msg); + let expected_output = { + let mut v = msg.clone(); + v.push(b'\n'); + v + }; + + let bin = assert_cmd::cargo::cargo_bin!("pcap2fix"); + Command::new(bin) + .args(["--input", "-", "--port", "12083"]) + .write_stdin(pcap_bytes) + .assert() + .success() + .stdout(expected_output); +} From 2b193925bbcd9d463d8531e243d88a1e1eced666 Mon Sep 17 00:00:00 2001 From: stephenlclarke Date: Wed, 10 Dec 2025 22:58:56 +0000 Subject: [PATCH 12/14] make scan now creates target/coverage up front, and conditionally runs cargo audit with --no-fetch only if the advisory DB already exists; otherwise it fetches first. --- Makefile | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index f8859ca..d6314a1 100644 --- a/Makefile +++ b/Makefile @@ -27,9 +27,17 @@ scan: prepare mkdir -p target/coverage && \ if command -v cargo-audit >/dev/null 2>&1; then \ echo "Running cargo-audit (text output)"; \ - cargo audit --no-fetch || true; \ + if [ -d "$${HOME}/.cargo/advisory-db" ]; then \ + cargo audit --no-fetch || true; \ + else \ + cargo audit || true; \ + fi; \ echo "Running cargo-audit (JSON) → target/coverage/rustsec.json"; \ - cargo audit --no-fetch --json > target/coverage/rustsec.json || true; \ + if [ -d "$${HOME}/.cargo/advisory-db" ]; then \ + cargo audit --no-fetch --json > target/coverage/rustsec.json || true; \ + else \ + cargo audit --json > target/coverage/rustsec.json || true; \ + fi; \ echo "Converting RustSec report to Sonar generic issues (target/coverage/sonar-generic-issues.json)"; \ python3 ci/convert_rustsec_to_sonar.py target/coverage/rustsec.json target/coverage/sonar-generic-issues.json || true; \ else \ From b36393975ea3da3a1c49b241d76a3cd559b47acc Mon Sep 17 00:00:00 2001 From: stephenlclarke Date: Wed, 10 Dec 2025 23:03:05 +0000 Subject: [PATCH 13/14] fix: Added a pre-cache step in the Sonar job to create the cache directories --- .github/workflows/ci.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bdec1df..9e14200 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -128,11 +128,14 @@ jobs: toolchain="stable" if [ -f rust-toolchain.toml ]; then parsed=$(grep -m1 'channel' rust-toolchain.toml | sed -E 's/.*"([^"]+)".*/\1/') - toolchain=${parsed:-$toolchain} - elif [ -f rust-toolchain ]; then - toolchain=$(head -n1 rust-toolchain | tr -d '[:space:]') - fi - echo "toolchain=$toolchain" >> "$GITHUB_OUTPUT" + toolchain=${parsed:-$toolchain} + elif [ -f rust-toolchain ]; then + toolchain=$(head -n1 rust-toolchain | tr -d '[:space:]') + fi + echo "toolchain=$toolchain" >> "$GITHUB_OUTPUT" + - name: Ensure cache directories exist + run: | + mkdir -p "$HOME/.cargo/bin" "$HOME/.rustup" - name: Compute lockfile hash id: lockfile-hash run: echo "hash=$(sha256sum Cargo.lock | cut -d ' ' -f1)" >> "$GITHUB_OUTPUT" From ba48181abe60c2a4af5519091c05641660f241fa Mon Sep 17 00:00:00 2001 From: stephenlclarke Date: Wed, 10 Dec 2025 23:10:42 +0000 Subject: [PATCH 14/14] fix: added yamllint --- .github/workflows/ci.yml | 17 ++++++++++++----- Makefile | 5 +++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9e14200..b1dd93d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,3 +1,5 @@ +--- +# yamllint disable rule:truthy rule:line-length # CI pipeline for PRs (lint, test, coverage, sonar) and tag release builds. name: CI Pipeline @@ -36,6 +38,11 @@ jobs: if: github.event_name == 'pull_request' steps: - uses: actions/checkout@v4 + - name: Lint workflow YAML + run: | + python3 -m pip install --upgrade pip + python3 -m pip install yamllint + yamllint .github/workflows - name: Compute lockfile hash id: lockfile-hash shell: bash @@ -128,11 +135,11 @@ jobs: toolchain="stable" if [ -f rust-toolchain.toml ]; then parsed=$(grep -m1 'channel' rust-toolchain.toml | sed -E 's/.*"([^"]+)".*/\1/') - toolchain=${parsed:-$toolchain} - elif [ -f rust-toolchain ]; then - toolchain=$(head -n1 rust-toolchain | tr -d '[:space:]') - fi - echo "toolchain=$toolchain" >> "$GITHUB_OUTPUT" + toolchain=${parsed:-$toolchain} + elif [ -f rust-toolchain ]; then + toolchain=$(head -n1 rust-toolchain | tr -d '[:space:]') + fi + echo "toolchain=$toolchain" >> "$GITHUB_OUTPUT" - name: Ensure cache directories exist run: | mkdir -p "$HOME/.cargo/bin" "$HOME/.rustup" diff --git a/Makefile b/Makefile index d6314a1..b2180f6 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,11 @@ scan: prepare ensure_build_metadata && \ cargo fmt --all --check && \ cargo clippy --workspace --all-targets -- -D warnings && \ + if command -v yamllint >/dev/null 2>&1; then \ + yamllint .github/workflows || true; \ + else \ + echo "yamllint not installed; skipping YAML lint"; \ + fi; \ mkdir -p target/coverage && \ if command -v cargo-audit >/dev/null 2>&1; then \ echo "Running cargo-audit (text output)"; \