diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index a786d23..e582f5b 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,36 +1,36 @@
-name: Cargo Build & Test
-
-on:
- pull_request:
-
-env:
- CARGO_TERM_COLOR: always
-
-jobs:
- build_and_test:
- name: Rust project - latest
- strategy:
- max-parallel: 1
- fail-fast: true
- matrix:
- include:
- - os: ubuntu-latest
- - os: windows-latest
- - os: macos-latest
- runs-on: ${{ matrix.os }}
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
- - name: Toolchain install & update
- run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }}
- - name: Build GUI
- run: cargo build --bin infuse_decoder --verbose
- - name: Build CLI
- run: cargo build --bin infuse_decoder_cli --verbose
- - name: Run CLI
- run: cargo run --bin infuse_decoder_cli -- --path ./data/tdf_test_blocks.bin --output . --name ci_test --verbose
- - name: Display and cleanup results
- shell: bash
- run: |
- ls -la *.csv
- rm ./*.csv
+name: Cargo Build & Test
+
+on:
+ pull_request:
+
+env:
+ CARGO_TERM_COLOR: always
+
+jobs:
+ build_and_test:
+ name: Rust project - latest
+ strategy:
+ max-parallel: 1
+ fail-fast: true
+ matrix:
+ include:
+ - os: ubuntu-latest
+ - os: windows-latest
+ - os: macos-latest
+ runs-on: ${{ matrix.os }}
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ - name: Toolchain install & update
+ run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }}
+ - name: Build GUI
+ run: cargo build --bin infuse_decoder --verbose
+ - name: Build CLI
+ run: cargo build --bin infuse_decoder_cli --verbose
+ - name: Run CLI
+ run: cargo run --bin infuse_decoder_cli -- --path ./data/tdf_test_blocks.bin --output . --name ci_test --verbose
+ - name: Display and cleanup results
+ shell: bash
+ run: |
+ ls -la *.csv
+ rm ./*.csv
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 30c8e05..e9ba7cb 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -1,75 +1,198 @@
-name: Release
-
-permissions:
- contents: write
-
-on:
- pull_request:
- types: [ labeled ]
- push:
- tags:
- - v[0-9]+.*
-
-jobs:
- create-release:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- if: ${{ github.event_name == 'push' }}
- - uses: taiki-e/create-gh-release-action@v1
- if: ${{ github.event_name == 'push' }}
- with:
- # (optional) Path to changelog.
- changelog: CHANGELOG.md
- # (required) GitHub token for creating GitHub Releases.
- token: ${{ secrets.GITHUB_TOKEN }}
- upload-assets:
- if: ${{ (github.event_name == 'push') || (github.event.label.name == 'release') }}
- needs: create-release
- strategy:
- matrix:
- include:
- - target: aarch64-unknown-linux-gnu
- os: ubuntu-latest
- - target: aarch64-apple-darwin
- os: macos-latest
- - target: x86_64-unknown-linux-gnu
- os: ubuntu-latest
- - target: x86_64-pc-windows-msvc
- os: windows-latest
- # Universal macOS binary is supported as universal-apple-darwin.
- - target: x86_64-apple-darwin
- os: macos-latest
- runs-on: ${{ matrix.os }}
- steps:
- - uses: actions/checkout@v4
- - name: MacOS Signing setup
- if: runner.os == 'macOS'
- shell: bash
- run: |
- echo "$MACOS_CERTIFICATE" | base64 --decode > certificate.p12
- security create-keychain -p "" build.keychain
- security import certificate.p12 -k build.keychain -P "$MACOS_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
- security list-keychains -s build.keychain
- security default-keychain -s build.keychain
- security unlock-keychain -p "" build.keychain
- security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "" build.keychain
- env:
- MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
- MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
- - uses: taiki-e/upload-rust-binary-action@v1
- with:
- # (required) Comma-separated list of binary names (non-extension portion of filename) to build and upload.
- # Note that glob pattern is not supported yet.
- bin: infuse_decoder,infuse_decoder_cli
- # (optional) Archive name (non-extension portion of filename) to be uploaded.
- archive: infuse_decoder-$tag-$target
- # (optional) Target triple, default is host triple.
- target: ${{ matrix.target }}
- # (required) GitHub token for uploading assets to GitHub Releases.
- token: ${{ secrets.GITHUB_TOKEN }}
- # Dry-run unless a release
- dry-run: ${{ github.event_name != 'push' }}
- # MacOS codesign parameters
- codesign: ${{ secrets.MACOS_SIGNING_IDENTITY }}
- codesign-options: runtime
+name: Release
+
+permissions:
+ contents: write
+
+on:
+ pull_request:
+ types: [ labeled ]
+ push:
+ tags:
+ - v[0-9]+.*
+
+jobs:
+ create-release:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Skip release creation
+ if: ${{ github.event_name != 'push' }}
+ run: echo "Release creation only runs for tagged pushes."
+ - uses: actions/checkout@v4
+ if: ${{ github.event_name == 'push' }}
+ - uses: taiki-e/create-gh-release-action@v1
+ if: ${{ github.event_name == 'push' }}
+ with:
+ # (optional) Path to changelog.
+ changelog: CHANGELOG.md
+ # (required) GitHub token for creating GitHub Releases.
+ token: ${{ secrets.GITHUB_TOKEN }}
+ upload-assets:
+ if: ${{ (github.event_name == 'push') || (github.event.label.name == 'release') }}
+ needs: create-release
+ strategy:
+ matrix:
+ include:
+ - target: aarch64-unknown-linux-gnu
+ os: ubuntu-latest
+ - target: x86_64-unknown-linux-gnu
+ os: ubuntu-latest
+ - target: x86_64-pc-windows-msvc
+ os: windows-latest
+ runs-on: ${{ matrix.os }}
+ steps:
+ - uses: actions/checkout@v4
+ - uses: taiki-e/upload-rust-binary-action@v1
+ with:
+ # (required) Comma-separated list of binary names (non-extension portion of filename) to build and upload.
+ # Note that glob pattern is not supported yet.
+ bin: infuse_decoder,infuse_decoder_cli
+ # (optional) Archive name (non-extension portion of filename) to be uploaded.
+ archive: infuse_decoder-$tag-$target
+ # (optional) Target triple, default is host triple.
+ target: ${{ matrix.target }}
+ # (required) GitHub token for uploading assets to GitHub Releases.
+ token: ${{ secrets.GITHUB_TOKEN }}
+ # Dry-run unless a release
+ dry-run: ${{ github.event_name != 'push' }}
+
+ upload-macos-assets:
+ if: ${{ (github.event_name == 'push') || (github.event.label.name == 'release') }}
+ needs: create-release
+ strategy:
+ matrix:
+ target:
+ - aarch64-apple-darwin
+ - x86_64-apple-darwin
+ runs-on: macos-latest
+ env:
+ MACOS_APP_NAME: Infuse Decoder
+ MACOS_BUNDLE_ID: iot.infuse.decoder
+ MACOS_REQUIRE_SIGNING: ${{ github.event_name == 'push' }}
+ MACOS_SIGNING_IDENTITY: ${{ secrets.MACOS_SIGNING_IDENTITY }}
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install Rust target
+ run: rustup target add ${{ matrix.target }}
+
+ - name: macOS signing setup
+ shell: bash
+ env:
+ MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
+ MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
+ run: |
+ set -euo pipefail
+
+ if [[ -z "${MACOS_CERTIFICATE:-}" || -z "${MACOS_CERTIFICATE_PASSWORD:-}" ]]; then
+ if [[ "$MACOS_REQUIRE_SIGNING" == "true" ]]; then
+ echo "MACOS_CERTIFICATE and MACOS_CERTIFICATE_PASSWORD are required for release signing" >&2
+ exit 1
+ fi
+
+ echo "Skipping Developer ID certificate import for unsigned dry-run build."
+ exit 0
+ fi
+
+ certificate_path="$RUNNER_TEMP/developer_id_application.p12"
+ keychain_path="$RUNNER_TEMP/app-signing.keychain-db"
+ keychain_password="$(openssl rand -hex 24)"
+
+ if ! printf '%s' "$MACOS_CERTIFICATE" | base64 --decode > "$certificate_path" 2>/dev/null; then
+ printf '%s' "$MACOS_CERTIFICATE" | base64 -D > "$certificate_path"
+ fi
+
+ security create-keychain -p "$keychain_password" "$keychain_path"
+ security set-keychain-settings -lut 21600 "$keychain_path"
+ security unlock-keychain -p "$keychain_password" "$keychain_path"
+ security import "$certificate_path" \
+ -k "$keychain_path" \
+ -P "$MACOS_CERTIFICATE_PASSWORD" \
+ -T /usr/bin/codesign
+ security list-keychains -d user -s "$keychain_path"
+ security default-keychain -s "$keychain_path"
+ security set-key-partition-list \
+ -S apple-tool:,apple:,codesign: \
+ -s \
+ -k "$keychain_password" \
+ "$keychain_path"
+
+ - name: Build and sign macOS CLI
+ shell: bash
+ run: |
+ set -euo pipefail
+
+ cargo build --release --bin infuse_decoder_cli --target "${{ matrix.target }}"
+
+ if [[ -n "${MACOS_SIGNING_IDENTITY:-}" ]]; then
+ codesign --force \
+ --timestamp \
+ --options runtime \
+ --sign "$MACOS_SIGNING_IDENTITY" \
+ "target/${{ matrix.target }}/release/infuse_decoder_cli"
+ codesign --verify --strict --verbose=2 "target/${{ matrix.target }}/release/infuse_decoder_cli"
+ elif [[ "$MACOS_REQUIRE_SIGNING" == "true" ]]; then
+ echo "MACOS_SIGNING_IDENTITY is required for release signing" >&2
+ exit 1
+ else
+ codesign --force --sign - "target/${{ matrix.target }}/release/infuse_decoder_cli"
+ fi
+
+ - name: Package macOS DMG
+ id: package
+ shell: bash
+ run: |
+ set -euo pipefail
+
+ dmg_path="$(bash scripts/package_macos_dmg.sh "${{ matrix.target }}" | tail -n 1)"
+ version="$(sed -nE 's/^version[[:space:]]*=[[:space:]]*"([^"]+)".*/\1/p' Cargo.toml | head -n 1)"
+ cli_archive="target/macos-dmg/${{ matrix.target }}/infuse_decoder_cli-$version-${{ matrix.target }}.tar.gz"
+
+ tar -czf "$cli_archive" -C "target/${{ matrix.target }}/release" infuse_decoder_cli
+
+ echo "dmg_path=$dmg_path" >> "$GITHUB_OUTPUT"
+ echo "cli_archive=$cli_archive" >> "$GITHUB_OUTPUT"
+
+ - name: Notarize macOS DMG
+ if: github.event_name == 'push'
+ shell: bash
+ env:
+ APPLE_ID: ${{ secrets.APPLE_ID }}
+ APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
+ APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
+ DMG_PATH: ${{ steps.package.outputs.dmg_path }}
+ run: |
+ set -euo pipefail
+
+ if [[ -z "${APPLE_ID:-}" || -z "${APPLE_TEAM_ID:-}" || -z "${APPLE_APP_SPECIFIC_PASSWORD:-}" ]]; then
+ echo "Skipping notarization; APPLE_ID, APPLE_TEAM_ID, and APPLE_APP_SPECIFIC_PASSWORD were not provided."
+ exit 0
+ fi
+
+ xcrun notarytool submit "$DMG_PATH" \
+ --apple-id "$APPLE_ID" \
+ --team-id "$APPLE_TEAM_ID" \
+ --password "$APPLE_APP_SPECIFIC_PASSWORD" \
+ --wait
+ xcrun stapler staple "$DMG_PATH"
+ xcrun stapler validate "$DMG_PATH"
+ spctl -a -vvv -t open --context context:primary-signature "$DMG_PATH"
+
+ - name: Upload macOS release assets
+ if: github.event_name == 'push'
+ shell: bash
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ gh release upload "$GITHUB_REF_NAME" \
+ "${{ steps.package.outputs.dmg_path }}" \
+ "${{ steps.package.outputs.cli_archive }}" \
+ --clobber
+
+ - name: Upload macOS dry-run artifacts
+ if: github.event_name != 'push'
+ uses: actions/upload-artifact@v4
+ with:
+ name: macos-${{ matrix.target }}-artifacts
+ path: |
+ ${{ steps.package.outputs.dmg_path }}
+ ${{ steps.package.outputs.cli_archive }}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2ea9f98..e148b35 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](https://semver.org).
+## [1.8.1] - 2026-06-05
+
+ - MacOS GUI distributed as notorized `.dmg`
+
## [1.8.0] - 2026-05-01
- Fix decoding of constant length strings (Introduced in `1.7.0`)
diff --git a/Cargo.toml b/Cargo.toml
index af0e63f..498cffc 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "infuse_decoder"
-version = "1.8.0"
+version = "1.8.1"
edition = "2024"
[[bin]]
diff --git a/packaging/macos/entitlements.plist b/packaging/macos/entitlements.plist
new file mode 100644
index 0000000..d238725
--- /dev/null
+++ b/packaging/macos/entitlements.plist
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/scripts/package_macos_dmg.sh b/scripts/package_macos_dmg.sh
new file mode 100755
index 0000000..5627664
--- /dev/null
+++ b/scripts/package_macos_dmg.sh
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+if [[ $# -ne 1 ]]; then
+ echo "Usage: $0 " >&2
+ exit 64
+fi
+
+TARGET="$1"
+ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+
+APP_NAME="${MACOS_APP_NAME:-Infuse Decoder}"
+BUNDLE_ID="${MACOS_BUNDLE_ID:-iot.infuse.decoder}"
+BIN_NAME="${MACOS_BIN_NAME:-infuse_decoder}"
+EXECUTABLE_NAME="${MACOS_EXECUTABLE_NAME:-infuse_decoder}"
+SIGNING_IDENTITY="${MACOS_SIGNING_IDENTITY:-${CODESIGN_IDENTITY:-}}"
+REQUIRE_SIGNING="${MACOS_REQUIRE_SIGNING:-false}"
+ENTITLEMENTS="${MACOS_ENTITLEMENTS:-$ROOT_DIR/packaging/macos/entitlements.plist}"
+ICON_PNG="${MACOS_ICON_PNG:-$ROOT_DIR/assets/icon.png}"
+
+cd "$ROOT_DIR"
+
+VERSION="$(sed -nE 's/^version[[:space:]]*=[[:space:]]*"([^"]+)".*/\1/p' Cargo.toml | head -n 1)"
+if [[ -z "$VERSION" ]]; then
+ echo "Could not determine package version from Cargo.toml" >&2
+ exit 1
+fi
+
+if [[ "$REQUIRE_SIGNING" == "true" || "$REQUIRE_SIGNING" == "1" ]]; then
+ if [[ -z "$SIGNING_IDENTITY" ]]; then
+ echo "MACOS_SIGNING_IDENTITY is required when MACOS_REQUIRE_SIGNING is true" >&2
+ exit 1
+ fi
+fi
+
+if [[ -n "$SIGNING_IDENTITY" && ! -f "$ENTITLEMENTS" ]]; then
+ echo "Entitlements file not found: $ENTITLEMENTS" >&2
+ exit 1
+fi
+
+for required_tool in cargo codesign hdiutil iconutil sips; do
+ if ! command -v "$required_tool" >/dev/null 2>&1; then
+ echo "Required macOS packaging tool not found: $required_tool" >&2
+ exit 1
+ fi
+done
+
+APP_BASENAME="${APP_NAME// /-}"
+BUILD_ROOT="$ROOT_DIR/target/macos-dmg/$TARGET"
+APP_PATH="$BUILD_ROOT/$APP_NAME.app"
+DMG_ROOT="$BUILD_ROOT/dmg-root"
+DMG_PATH="$BUILD_ROOT/$APP_BASENAME-$VERSION-$TARGET.dmg"
+ICONSET="$BUILD_ROOT/AppIcon.iconset"
+
+cargo build --release --bin "$BIN_NAME" --target "$TARGET"
+
+rm -rf "$APP_PATH" "$DMG_ROOT" "$DMG_PATH" "$ICONSET"
+mkdir -p "$APP_PATH/Contents/MacOS" "$APP_PATH/Contents/Resources" "$DMG_ROOT" "$ICONSET"
+
+cp "$ROOT_DIR/target/$TARGET/release/$BIN_NAME" "$APP_PATH/Contents/MacOS/$EXECUTABLE_NAME"
+chmod 755 "$APP_PATH/Contents/MacOS/$EXECUTABLE_NAME"
+
+make_icon() {
+ local size="$1"
+ local output="$2"
+
+ sips -z "$size" "$size" "$ICON_PNG" --out "$ICONSET/$output" >/dev/null
+}
+
+make_icon 16 "icon_16x16.png"
+make_icon 32 "icon_16x16@2x.png"
+make_icon 32 "icon_32x32.png"
+make_icon 64 "icon_32x32@2x.png"
+make_icon 128 "icon_128x128.png"
+make_icon 256 "icon_128x128@2x.png"
+make_icon 256 "icon_256x256.png"
+make_icon 512 "icon_256x256@2x.png"
+make_icon 512 "icon_512x512.png"
+make_icon 1024 "icon_512x512@2x.png"
+iconutil -c icns "$ICONSET" -o "$APP_PATH/Contents/Resources/AppIcon.icns"
+
+cat > "$APP_PATH/Contents/Info.plist" <
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleDisplayName
+ $APP_NAME
+ CFBundleExecutable
+ $EXECUTABLE_NAME
+ CFBundleIconFile
+ AppIcon
+ CFBundleIdentifier
+ $BUNDLE_ID
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $APP_NAME
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ $VERSION
+ CFBundleVersion
+ $VERSION
+ LSApplicationCategoryType
+ public.app-category.utilities
+ NSHighResolutionCapable
+
+ NSPrincipalClass
+ NSApplication
+ NSSupportsAutomaticGraphicsSwitching
+
+
+
+EOF
+
+printf 'APPL????' > "$APP_PATH/Contents/PkgInfo"
+
+if [[ -n "$SIGNING_IDENTITY" ]]; then
+ codesign --force \
+ --timestamp \
+ --options runtime \
+ --entitlements "$ENTITLEMENTS" \
+ --sign "$SIGNING_IDENTITY" \
+ "$APP_PATH/Contents/MacOS/$EXECUTABLE_NAME"
+
+ codesign --force \
+ --timestamp \
+ --options runtime \
+ --entitlements "$ENTITLEMENTS" \
+ --sign "$SIGNING_IDENTITY" \
+ "$APP_PATH"
+else
+ echo "MACOS_SIGNING_IDENTITY is not set; creating an ad-hoc signed DMG for local validation only." >&2
+ codesign --force --sign - "$APP_PATH/Contents/MacOS/$EXECUTABLE_NAME"
+ codesign --force --sign - "$APP_PATH"
+fi
+
+codesign --verify --strict --deep --verbose=2 "$APP_PATH"
+
+ditto "$APP_PATH" "$DMG_ROOT/$APP_NAME.app"
+ln -s /Applications "$DMG_ROOT/Applications"
+
+hdiutil create \
+ -volname "$APP_NAME" \
+ -srcfolder "$DMG_ROOT" \
+ -ov \
+ -format UDZO \
+ "$DMG_PATH"
+hdiutil verify "$DMG_PATH"
+
+if [[ -n "$SIGNING_IDENTITY" ]]; then
+ codesign --force --timestamp --sign "$SIGNING_IDENTITY" "$DMG_PATH"
+ codesign --verify --verbose=2 "$DMG_PATH"
+fi
+
+echo "$DMG_PATH"